Compare commits

..

41 Commits

Author SHA1 Message Date
Osspial
c0b46a03b5 Relase alpha 6 (#1338)
* Relase alpha 6

* Update CHANGELOG.md

Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
2020-01-03 18:17:05 -05:00
Kirill Chibisov
114c18e70d On X11, make WINIT_HIDPI_FACTOR dominate Xft.dpi in some cases (#1354)
* On X11, make `WINIT_HIDPI_FACTOR` dominate `Xft.dpi` in some cases

This commit makes `WINIT_HIDPI_FACTOR` dominate `Xft.dpi` in general and
adds a special value `0` for `WINIT_HIDPI_FACTOR` to use winit computed
DPI factor with randr over Xft.dpi.

* Use `randr` instead of `0` for auto dpi scaling

* Update CHANGELOG

* blow up on wrong env var

* Allow empty string for env var
2020-01-03 18:15:47 -05:00
hatoo
7367b8be6c On Macos, Hide cursor only inside window (#1348)
* On MacOS, hide cursor by invisible cursor

* Add CHANGELOG

* Fix variable name

* Add comments for `CURSOR_BYTES`
2020-01-03 03:34:14 +03:00
hatoo
dd768fe655 MacOS fix CursorEntered and CursorLeft events fired at old window size. (#1335)
* On macOS, Fix `CursorEntered` and `CursorLeft`

* Add CHANGELOG

Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
2019-12-30 13:32:37 -07:00
Osspial
d9bda3e985 Implement ModifiersChanged on Windows, and fix bugs discovered in implementation process (#1344)
* Move DeviceEvent handling to the message target window.

Previously, device events seem to have only been sent to one particular
window, and when that window was closed Winit would stop receiving
device events. This also allows users to create windowless event loops
that process device events - an intriguing idea, to say the least.

* Emit LWin and RWin VirtualKeyCodes on Windows

* Implement ModifiersChanged on Windows

* Make ModifiersChanged a tuple variant instead of a struct variant

* Add changelog entries

* Format

* Update changelog entry

* Fix AltGr handling

* Reformat

* Publicly expose ModifiersChanged and deprecate misc. modifiers fields
2019-12-30 14:11:11 -05:00
hatoo
fa7a3025ec [MacOS] Fix memory management (#1342)
* macOS, Reduce memory usage

* macOS, Fix memory leak
2019-12-29 15:16:12 -07:00
Osspial
e4451d6786 Fix Window::set_visible not setting internal flags correctly (#1345) 2019-12-29 10:39:15 -05:00
Michael Tang
468b6b83ec fix: remove deprecated usage of mem::uninitialized. (#1341) 2019-12-28 18:53:41 -05:00
Osspial
8a3a32f286 Properly mark a few changes as breaking 2019-12-28 16:22:51 -05:00
Osspial
20e81695ca Change ModifiersState to a bitflags struct (#1306)
* Change ModifiersState to a bitflags struct

* Make examples work

* Add modifier state methods

* all things considered, only erroring out in one file throughout all of these changes is kinda impressive

* Make expansion plans more clear

* Move changelog entry

* Try to fix macos build

* Revert modifiers println in cursor_grab

* Make serde serialization less bug-prone
2019-12-28 15:36:06 -05:00
Osspial
027c52171d Fix changelog 2019-12-27 16:28:06 -05:00
Osspial
cc206d31b7 Implement windows focus key press/release on Windows (#1307)
* X11: Sync key press/release with window focus

* When a window loses focus, key release events are issued for all pressed keys
* When a window gains focus, key press events are issued for all pressed keys
* Adds `is_synthetic` field to `WindowEvent` variant `KeyboardInput`
  to indicate that these events are synthetic.
* Adds `is_synthetic: false` to `WindowEvent::KeyboardInput` events issued
  on all other platforms

* Implement windows focus key press/release on Windows

* Docs

Co-authored-by: Murarth <murarth@gmail.com>
2019-12-27 16:26:23 -05:00
hatoo
5d99316c96 macOS: Don't change fullscreen state during fullscreen transition (#1331)
* Register windowWillExitFullScreen

* On macOS: Do not toggle fullscreen during fullscreen transition

* Add CHANGELOG

Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
2019-12-24 11:56:56 -07:00
David Hewitt
d59eec4633 Add support for Windows Dark Mode (#1217)
* Add support for Windows Dark Mode

* Add is_dark_mode() getter to WindowExtWindows

* Add WindowEvent::DarkModeChanged

* Add support for dark mode in Windows 10 builds > 18362

* Change strategy for querying windows 10 build version

* Drop window state before sending event

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

* Change implementation of windows dark mode support

* Expand supported range of windows 10 versions with dark mode

* Use get_function! macro where possible

* Minor style fixes

* Improve documentation for ThemeChanged

* Use `as` conversion for `BOOL`

* Correct CHANGELOG entry for dark mode

Co-authored-by: daxpedda <daxpedda@gmail.com>
Co-authored-by: Osspial <osspial@gmail.com>
2019-12-22 12:04:09 -07:00
Osspial
25e018d1ce Fix extraneous # in 0.20.0 Alpha 5 2019-12-22 11:24:49 -05:00
Osspial
25123bed23 Rebasing moved the RedrawRequested changelog entry to the wrong position
:/
2019-12-22 11:23:27 -05:00
Osspial
a8d6db0fc1 Update alpha version in readme.md 2019-12-22 11:19:17 -05:00
Osspial
8a9a9cd92d Move changelog entry into proper position
Fix window_run_return

Make docs build
2019-12-22 11:17:24 -05:00
simlay
530ff5420b Implement revamped RedrawRequested on iOS. (#1299)
* Implement revamped `RedrawRequested` on iOS

* Added RedrawEventsCleared to events_cleared logic

* Fixed from comments

* Added RedrawEventsCleared to draw_rect handler.

* Fixed out of order `RedrawEventsCleared` events.

* cargo fmt
2019-12-22 11:17:23 -05:00
Héctor Ramón
133b11fa6d Implement revamped RedrawRequested on Web (#1301)
* Implement revamped `RedrawRequested` on Web

* Add `web` example
2019-12-22 11:17:23 -05:00
Héctor Ramón
5b489284e4 Implement revamped RedrawRequested on macOS (#1235) 2019-12-22 11:17:23 -05:00
Heghedus Razvan
cdc32eb817 Implemented revamped RedrawRequested for linux wayland (#1237)
Signed-off-by: Heghedus Razvan <heghedus.razvan@gmail.com>
2019-12-22 11:17:23 -05:00
Osspial
eb38ff453a Run rustfmt 2019-12-22 11:17:23 -05:00
Osspial
8eb7853a1a Implement revamped RedrawRequested on Windows (#1050)
* Move event loop runner to runner module

* Implement new redraw API
2019-12-22 11:17:23 -05:00
Murarth
0c151f9fb3 Implement changes to RedrawRequested event (#1062)
* Implement changes to `RedrawRequested` event

Implements the changes described in #1041 for the X11 platform and for
platform-independent public-facing code.

* Fix `request_redraw` example

* Fix examples in lib docs

* Only issue `RedrawRequested` on final `Expose` event
2019-12-22 11:17:23 -05:00
simlay
c10c820311 Reimplement NativeDisplayMode on iOS for #1310 (#1330)
* Reimplement NativeDisplayMode on iOS for #1310

* Type annotations from code review.

Co-Authored-By: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com>

Co-authored-by: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com>
2019-12-22 01:39:22 -07:00
Justin Miller
82889e2367 Window::set_minimized (#985) (#990)
* Expose set_minimized. Implement for macOS (#985)

* Implement set_minimized for Wayland (#985)

Co-Authored-By: Victor Berger <vberger@users.noreply.github.com>

* Implement set_minimized for Windows (#985)

* Remove debug logs (#985)

* Implement Window::set_minimized for X11

* Remove extra param from set_window_flags call

* Cargo fmt

* Add example of usage

* Update changelog

* Update feature matrix

* Cargo fmt

* Update example to remove unnecessary event var

* Stop setting window styles when minimizing (#985)

* Add stub for WASM (#985)

Co-authored-by: Victor Berger <vberger@users.noreply.github.com>
Co-authored-by: Murarth <murarth@gmail.com>
Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
Co-authored-by: Osspial <osspial@gmail.com>
2019-12-22 01:04:11 -05:00
Osspial
92741aa4ec Fix array_into_iter warning on Windows (#1329) 2019-12-21 17:49:44 -07:00
Murarth
2f352ca5cf X11: Fix CursorEntered event for non-winit window (#1320)
* X11: Fix CursorEntered event for non-winit window

* Retry CI

Co-authored-by: Osspial <osspial@gmail.com>
2019-12-21 17:47:29 -07:00
hatoo
38c8cb9f4a FIX Crash on macOS when starting maximized without decorations (#1323)
* FIX #1288

* Fix CHANGELOG.md

Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
2019-12-19 18:03:41 -07:00
Kirill Chibisov
73248bdced On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode (#1324)
Mutter can reposition window on resize, if it is behind mutter's "bounding box".
So, when you start winit window in maximized mode with CSD, mutter places its CSD
behind the GNOME's status bar initially, and then sends configure with the
exact same size as your current window. If winit decides to optimize calling
frame.resize(..) in this case, we won't call set_geometry(we're calling
it through resize) and GNOME won't reposition your window to be inside the
"bounding box", which is not a desired behavior for the end user.
2019-12-19 17:08:28 -07:00
hatoo
01203b247b Fix run_return in MacOS (#1321)
* Fix run_return in MacOS

* MacOS: Fix the way of getting a window in run_return

* Fix CHANGELOG.md
2019-12-19 12:10:47 +03:00
Kirill Chibisov
3e1d169160 On Wayland, fix cursor icon updates on window borders when using CSD (#1322)
* On Wayland, fix cursor icon updates on window borders when using CSD

* Move changelog entry to a right place
2019-12-18 06:41:44 -07:00
Christian Duerr
c1b93fc3d0 Add ModifiersChanged event for macOS (#1268)
* Add ModifiersChanged event for macOS

This implements the macOS portion of #1124.

* Fix ModifiersChanged event import

* Fix event passing window instead of device id
2019-12-13 00:48:32 +03:00
Murarth
1f81e5c872 X11: Report CursorMoved when touch event occurs (#1297)
* X11: Report `CursorMoved` when touch event occurs

* Only trigger CursorMoved events for the first touch ID

* Fix testing for current touch events

* Fix first touch logic
2019-12-11 17:23:55 -07:00
Manish Goregaokar
e5291c9e28 Release 0.20.0-alpha5 (#1315) 2019-12-09 17:29:50 -07:00
Murarth
35505a3114 X11: Sync key press/release with window focus (#1296)
* X11: Sync key press/release with window focus

* When a window loses focus, key release events are issued for all pressed keys
* When a window gains focus, key press events are issued for all pressed keys
* Adds `is_synthetic` field to `WindowEvent` variant `KeyboardInput`
  to indicate that these events are synthetic.
* Adds `is_synthetic: false` to `WindowEvent::KeyboardInput` events issued
  on all other platforms

* Clarify code with comments
2019-12-07 15:51:37 -07:00
zserik
830d47a5f7 Have EventLoopClosed contain the original event (#1294)
* Fix issue #1292

* Remove "optionally" from changelog entry
2019-12-07 10:22:03 -07:00
Murarth
1a514dff38 X11: Fix incorrect DPI factor when waking from suspend (#1303) 2019-12-04 10:18:20 -07:00
Osspial
2888d5c6cf Fix array_into_iter warning on Windows (#1308) 2019-12-04 12:02:33 -05:00
Osspial
400f75a2b3 Make WindowStore::for_each less terrifying to rebase (#1304) 2019-12-04 03:55:49 -05:00
98 changed files with 2851 additions and 4400 deletions

1
.gitignore vendored
View File

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

View File

@@ -1,5 +1,35 @@
# Unreleased
# 0.20.0 Alpha 6 (2020-01-03)
- On macOS, fix `set_cursor_visible` hides cursor outside of window.
- On macOS, fix `CursorEntered` and `CursorLeft` events fired at old window size.
- On macOS, fix error when `set_fullscreen` is called during fullscreen transition.
- 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 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".
- Added `WindowEvent::ThemeChanged` variant to handle changes to the system theme. Currently only implemented on Windows.
- **Breaking**: Changes to the `RedrawRequested` event (#1041):
- `RedrawRequested` has been moved from `WindowEvent` to `Event`.
- `EventsCleared` has been renamed to `MainEventsCleared`.
- `RedrawRequested` is now issued only after `MainEventsCleared`.
- `RedrawEventsCleared` is issued after each set of `RedrawRequested` events.
- Implement synthetic window focus key events on Windows.
- **Breaking**: Change `ModifiersState` to a `bitflags` struct.
- On Windows, implement `VirtualKeyCode` translation for `LWin` and `RWin`.
- On Windows, fix closing the last opened window causing `DeviceEvent`s to stop getting emitted.
- On Windows, fix `Window::set_visible` not setting internal flags correctly. This resulted in some weird behavior.
- Add `DeviceEvent::ModifiersChanged`.
- Deprecate `modifiers` fields in other events in favor of `ModifiersChanged`.
- On X11, `WINIT_HIDPI_FACTOR` now dominates `Xft.dpi` when picking DPI factor for output.
- On X11, add special value `randr` for `WINIT_HIDPI_FACTOR` to make winit use self computed DPI factor instead of the one from `Xft.dpi`.
# 0.20.0 Alpha 5 (2019-12-09)
- On macOS, fix application termination on `ControlFlow::Exit`
- On Windows, fix missing `ReceivedCharacter` events when Alt is held.
- On macOS, stop emitting private corporate characters in `ReceivedCharacter` events.
@@ -8,6 +38,13 @@
- On X11, fix key modifiers being incorrectly reported.
- On X11, fix window creation hanging when another window is fullscreen.
- On Windows, fix focusing unfocused windows when switching from fullscreen to windowed.
- On X11, fix reporting incorrect DPI factor when waking from suspend.
- Change `EventLoopClosed` to contain the original event.
- **Breaking**: Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`,
indicating that the event is generated by winit.
- On X11, generate synthetic key events for keys held when a window gains or loses focus.
- On X11, issue a `CursorMoved` event when a `Touch` event occurs,
as X11 implicitly moves the cursor for such events.
# 0.20.0 Alpha 4 (2019-10-18)
@@ -35,6 +72,7 @@
- On X11, return dummy monitor data to avoid panicking when no monitors exist.
- On X11, prevent stealing input focus when creating a new window.
Only steal input focus when entering fullscreen mode.
- On Wayland, fixed DeviceEvents for relative mouse movement is not always produced
- On Wayland, add support for set_cursor_visible and set_cursor_grab.
- On Wayland, fixed DeviceEvents for relative mouse movement is not always produced.
- Removed `derivative` crate dependency.
@@ -150,20 +188,6 @@ and `WindowEvent::HoveredFile`.
- On Windows, fix initial dimensions of a fullscreen window.
- On Windows, Fix transparent borderless windows rendering wrong.
- Improve event API documentation.
- Overhaul device event API:
- **Breaking**: `Event::DeviceEvent` split into `MouseEvent`, `KeyboardEvent`, and `GamepadEvent`.
- **Breaking**: Remove `DeviceEvent::Text` variant.
- **Breaking**: `DeviceId` split into `MouseId`, `KeyboardId`, and `GamepadHandle`.
- **Breaking**: Removed device IDs from `WindowEvent` variants.
- Add `enumerate` function on device ID types to list all attached devices of that type.
- Add `is_connected` function on device ID types check if the specified device is still available.
- **Breaking**: On Windows, rename `DeviceIdExtWindows` to `DeviceExtWindows`.
- Add `handle` function to retrieve the underlying `HANDLE`.
- On Windows, fix duplicate device events getting sent if Winit managed multiple windows.
- On Windows, raw mouse events now report Mouse4 and Mouse5 presses and releases.
- Added gamepad support on Windows via raw input and XInput.
# Version 0.19.1 (2019-04-08)
- On Wayland, added a `get_wayland_display` function to `EventsLoopExt`.

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.20.0-alpha4"
version = "0.20.0-alpha6"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2018"
@@ -25,6 +25,7 @@ libc = "0.2.64"
log = "0.4"
serde = { version = "1", optional = true, features = ["serde_derive"] }
raw-window-handle = "0.3"
bitflags = "1"
[dev-dependencies]
image = "0.21"
@@ -48,10 +49,6 @@ version = "0.1.3"
default_features = false
features = ["display_link"]
[target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies]
bitflags = "1"
rusty-xinput = "1.0"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3.6"
features = [
@@ -59,7 +56,6 @@ features = [
"commctrl",
"dwmapi",
"errhandlingapi",
"hidpi",
"hidusage",
"libloaderapi",
"objbase",
@@ -75,7 +71,6 @@ features = [
"wingdi",
"winnt",
"winuser",
"xinput",
]
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
@@ -106,24 +101,9 @@ features = [
'KeyboardEvent',
'MouseEvent',
'Node',
'Navigator',
'PointerEvent',
'Window',
'WheelEvent',
'Gamepad',
'GamepadAxisMoveEvent',
'GamepadAxisMoveEventInit',
'GamepadButton',
'GamepadButtonEvent',
'GamepadButtonEventInit',
'GamepadEvent',
'GamepadEventInit',
'GamepadHand',
'GamepadHapticActuator',
'GamepadHapticActuatorType',
'GamepadMappingType',
'GamepadPose',
'GamepadServiceTest'
'WheelEvent'
]
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
@@ -135,3 +115,6 @@ package = "stdweb"
version = "=0.4.20"
optional = true
features = ["experimental_features_which_may_break_on_minor_version_bumps"]
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
console_log = "0.1"

View File

@@ -80,6 +80,7 @@ If your PR makes notable changes to Winit's features, please update this section
- **Window maximization**: The windows created by winit can be maximized upon creation.
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
creation.
- **Window minimization**: The windows created by winit can be minimized after creation.
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
@@ -116,6 +117,7 @@ If your PR makes notable changes to Winit's features, please update this section
* Setting the taskbar icon
* Setting the parent window
* `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting
### macOS
* Window activation policy
@@ -173,6 +175,7 @@ Legend:
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|

View File

@@ -7,7 +7,7 @@
```toml
[dependencies]
winit = "0.20.0-alpha4"
winit = "0.20.0-alpha6"
```
## [Documentation](https://docs.rs/winit)

View File

@@ -15,10 +15,14 @@ fn main() {
event_loop.run(move |event, _, control_flow| match event {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Pressed,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
..
},
..
}),
},
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);

View File

@@ -1,5 +1,5 @@
use winit::{
event::{DeviceEvent, ElementState, Event, KeyboardInput, WindowEvent},
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
@@ -12,22 +12,27 @@ fn main() {
.build(&event_loop)
.unwrap();
let mut modifiers = ModifiersState::default();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
..
},
..
}) => {
} => {
use winit::event::VirtualKeyCode::*;
match key {
Escape => *control_flow = ControlFlow::Exit,
G => window.set_cursor_grab(!modifiers.shift).unwrap(),
H => window.set_cursor_visible(modifiers.shift),
G => window.set_cursor_grab(!modifiers.shift()).unwrap(),
H => window.set_cursor_visible(modifiers.shift()),
_ => (),
}
}
@@ -39,6 +44,7 @@ fn main() {
ElementState::Pressed => println!("mouse button {} pressed", button),
ElementState::Released => println!("mouse button {} released", button),
},
DeviceEvent::ModifiersChanged(m) => modifiers = m,
_ => (),
},
_ => (),

View File

@@ -35,11 +35,15 @@ fn main() {
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(virtual_code),
state,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state,
..
},
..
}) => match (virtual_code, state) {
} => match (virtual_code, state) {
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
(VirtualKeyCode::F, ElementState::Pressed) => {
if window.fullscreen().is_some() {

View File

@@ -1,52 +0,0 @@
use winit::{
event::{
device::{GamepadEvent, GamepadHandle},
Event, WindowEvent,
},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
.with_title("The world's worst video game")
.build(&event_loop)
.unwrap();
println!("enumerating gamepads:");
for gamepad in GamepadHandle::enumerate(&event_loop) {
println!(
" gamepad={:?}\tport={:?}\tbattery level={:?}",
gamepad,
gamepad.port(),
gamepad.battery_level()
);
}
let deadzone = 0.12;
event_loop.run(move |event, _, control_flow| {
match event {
Event::GamepadEvent(gamepad_handle, event) => {
match event {
// Discard any Axis events that has a corresponding Stick event.
GamepadEvent::Axis { stick: true, .. } => (),
// Discard any Stick event that falls inside the stick's deadzone.
GamepadEvent::Stick {
x_value, y_value, ..
} if (x_value.powi(2) + y_value.powi(2)).sqrt() < deadzone => (),
_ => println!("[{:?}] {:#?}", gamepad_handle, event),
}
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}

View File

@@ -1,60 +0,0 @@
use std::time::Instant;
use winit::event_loop::EventLoop;
#[derive(Debug, Clone)]
enum Rumble {
None,
Left,
Right,
}
fn main() {
let event_loop = EventLoop::new();
// You should generally use `GamepadEvent::Added/Removed` to detect gamepads, as doing that will
// allow you to more easily support gamepad hotswapping. However, we're using `enumerate` here
// because it makes this example more concise.
let gamepads = winit::event::device::GamepadHandle::enumerate(&event_loop).collect::<Vec<_>>();
let rumble_patterns = &[
(0.5, Rumble::None),
(2.0, Rumble::Left),
(0.5, Rumble::None),
(2.0, Rumble::Right),
];
let mut rumble_iter = rumble_patterns.iter().cloned().cycle();
let mut active_pattern = rumble_iter.next().unwrap();
let mut timeout = active_pattern.0;
let mut timeout_start = Instant::now();
event_loop.run(move |_, _, _| {
if timeout <= active_pattern.0 {
let t = (timeout / active_pattern.0) * std::f64::consts::PI;
let intensity = t.sin();
for g in &gamepads {
let result = match active_pattern.1 {
Rumble::Left => g.rumble(intensity, 0.0),
Rumble::Right => g.rumble(0.0, intensity),
Rumble::None => Ok(()),
};
if let Err(e) = result {
println!("Rumble failed: {:?}", e);
}
}
timeout = (Instant::now() - timeout_start).as_millis() as f64 / 1000.0;
} else {
active_pattern = rumble_iter.next().unwrap();
println!(
"Rumbling {:?} for {:?} seconds",
active_pattern.1, active_pattern.0
);
timeout = 0.0;
timeout_start = Instant::now();
}
});
}

View File

@@ -41,11 +41,15 @@ fn main() {
// closing the window. How to close the window is detailed in the handler for
// the Y key.
}
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(virtual_code),
state: Released,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state: Released,
..
},
..
}) => {
} => {
match virtual_code {
Y => {
if close_requested {

35
examples/minimize.rs Normal file
View File

@@ -0,0 +1,35 @@
extern crate winit;
use winit::event::{Event, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
// Keyboard input event to handle minimize via a hotkey
Event::WindowEvent {
event: WindowEvent::KeyboardInput { input, .. },
window_id,
} => {
if window_id == window.id() {
// Pressing the 'M' key will minimize the window
if input.virtual_keycode == Some(VirtualKeyCode::M) {
window.set_minimized(true);
}
}
}
_ => *control_flow = ControlFlow::Wait,
});
}

View File

@@ -49,14 +49,18 @@ fn main() {
);
}
}
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
},
..
}) => {
} => {
window.set_title(&format!("{:?}", key));
let state = !modifiers.shift;
let state = !modifiers.shift();
use VirtualKeyCode::*;
match key {
A => window.set_always_on_top(state),
@@ -77,7 +81,7 @@ fn main() {
video_modes.iter().nth(video_mode_id).unwrap()
);
}
F => window.set_fullscreen(match (state, modifiers.alt) {
F => window.set_fullscreen(match (state, modifiers.alt()) {
(true, false) => {
Some(Fullscreen::Borderless(window.current_monitor()))
}
@@ -144,11 +148,15 @@ fn main() {
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested
| WindowEvent::Destroyed
| WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::Escape),
| WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
..
}) => {
} => {
window_senders.remove(&window_id);
}
_ => {

View File

@@ -29,10 +29,14 @@ fn main() {
*control_flow = ControlFlow::Exit;
}
}
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Pressed,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
..
},
..
}) => {
} => {
let window = Window::new(&event_loop).unwrap();
windows.insert(window.id(), window);
}

View File

@@ -20,14 +20,11 @@ fn main() {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::EventsCleared => {
Event::MainEventsCleared => {
window.request_redraw();
*control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(1, 0))
}
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
Event::RedrawRequested(_) => {
println!("{:?}", event);
}
_ => (),

View File

@@ -21,11 +21,15 @@ fn main() {
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Released,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Released,
..
},
..
}) => {
} => {
resizable = !resizable;
println!("Resizable: {}", resizable);
window.set_resizable(resizable);

72
examples/web.rs Normal file
View File

@@ -0,0 +1,72 @@
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
pub fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
#[cfg(feature = "web-sys")]
{
use winit::platform::web::WindowExtWebSys;
let canvas = window.canvas();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
body.append_child(&canvas)
.expect("Append canvas to HTML body");
}
#[cfg(feature = "stdweb")]
{
use std_web::web::INode;
use winit::platform::web::WindowExtStdweb;
let canvas = window.canvas();
let document = std_web::web::document();
let body: std_web::web::Node = document.body().expect("Get HTML body").into();
body.append_child(&canvas);
}
event_loop.run(move |event, _, control_flow| {
#[cfg(feature = "web-sys")]
log::debug!("{:?}", event);
#[cfg(feature = "stdweb")]
std_web::console!(log, "%s", format!("{:?}", event));
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
Event::MainEventsCleared => {
window.request_redraw();
}
_ => *control_flow = ControlFlow::Wait,
}
});
}
#[cfg(feature = "web-sys")]
mod wasm {
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(log::Level::Debug);
super::main();
}
}

View File

@@ -1,11 +0,0 @@
[package]
name = "stdweb-gamepad"
version = "0.1.0"
authors = ["furiouzz <christophe.massolin@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
winit = { path = "../../../../", features = [ "stdweb" ] }
stdweb = "0.4.20"

View File

@@ -1,80 +0,0 @@
use winit::{
event::{device::GamepadEvent, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
use stdweb::js;
/**
* Build example (from examples/web/gamepad/stdweb):
* cargo web build
* Run example (from examples/web/gamepad/stdweb):
* cargo web start
* Development (from project root):
* npx nodemon --watch src --watch examples/web/gamepad/stdweb/src -e rs --exec 'cargo web check'
*/
pub fn main() {
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
.with_title("Gamepad tests")
.build(&event_loop)
.unwrap();
let deadzone = 0.12;
event_loop.run(move |event, _, control_flow| match event {
Event::GamepadEvent(gamepad_handle, event) => match event {
GamepadEvent::Axis {
axis_id,
axis,
value,
stick,
} if value > deadzone => {
let string = format!("Axis {:#?} {:#?} {:#?} {:#?}", axis_id, axis, value, stick);
js! { console.log( @{string} ); }
}
GamepadEvent::Stick {
x_id,
y_id,
x_value,
y_value,
side,
} if (x_value.powi(2) + y_value.powi(2)).sqrt() > deadzone => {
let string = format!(
"Stick {:#?} {:#?} {:#?} {:#?} {:#?}",
x_id, y_id, x_value, y_value, side
);
js! { console.log( @{string} ); }
}
GamepadEvent::Button {
button_id,
button,
state,
} => {
let string = format!("Button {:#?} {:#?} {:#?}", button_id, button, state);
js! { console.log( @{string} ); }
}
GamepadEvent::Added => {
let string = format!("[{:?}] {:#?}", gamepad_handle, event);
js! { console.log( @{string} ); }
}
GamepadEvent::Removed => {
let string = format!("[{:?}] {:#?}", gamepad_handle, event);
js! { console.log( @{string} ); }
}
_ => {}
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
});
}

View File

@@ -1,7 +0,0 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log
.DS_Store

View File

@@ -1,20 +0,0 @@
[package]
name = "websys-gamepad"
version = "0.0.1"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
winit = { path = "../../../../", features = [ "web-sys" ] }
wasm-bindgen = "0.2.45"
wasm-bindgen-test = "0.3.8"
web-sys = { version = "0.3.22", features = [ "console" ] }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = "0.1.6"

View File

@@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Gamepad</title>
</head>
<body>
<canvas id="my_id"></canvas>
<script>
window.Module = {
canvas: document.getElementById('my_id')
}
</script>
<script type="module">
import example_gamepad from "../pkg/websys_examples.js"
example_gamepad()
.then((m) => console.log('Success', m))
.catch((e) => console.log('Ar error occured', e))
</script>
</body>
</html>

View File

@@ -1,78 +0,0 @@
mod utils;
use wasm_bindgen::prelude::*;
use winit::{
event::{device::GamepadEvent, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
/**
* Build example (from examples/gamepad/websys):
* wasm-pack build --target web
* Run web server (from examples/gamepad/websys):
* npx http-server
* Open your browser at http://localhost:8000/files/${EXAMPLE}.html
* Development (from project root):
* npx nodemon --watch src --watch examples/web/gamepad/websys/src -e rs --exec 'cd examples/web/gamepad/websys && wasm-pack build --target web'
*/
macro_rules! console_log {
($($t:tt)*) => (web_sys::console::log_1(&format_args!($($t)*).to_string().into()))
}
#[wasm_bindgen(start)]
pub fn example_gamepad() {
utils::set_panic_hook(); // needed for error stack trace
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
.with_title("Gamepad tests")
.build(&event_loop)
.unwrap();
let deadzone = 0.12;
event_loop.run(move |event, _, control_flow| {
match event {
Event::GamepadEvent(gamepad_handle, event) => {
match event {
GamepadEvent::Axis {
axis_id,
axis,
value,
stick,
} if value > deadzone => {
console_log!("Axis {:#?} {:#?} {:#?} {:#?}", axis_id, axis, value, stick)
},
GamepadEvent::Stick {
x_id, y_id, x_value, y_value, side
} if (x_value.powi(2) + y_value.powi(2)).sqrt() > deadzone => {
console_log!("Stick {:#?} {:#?} {:#?} {:#?} {:#?}", x_id, y_id, x_value, y_value, side)
},
GamepadEvent::Button {
button_id,
button,
state,
} => {
console_log!("Button {:#?} {:#?} {:#?}", button_id, button, state)
},
GamepadEvent::Added => {
console_log!("[{:?}] {:#?}", gamepad_handle, event)
},
GamepadEvent::Removed => console_log!("[{:?}] {:#?}", gamepad_handle, event),
_ => {},
}
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}

View File

@@ -1,10 +0,0 @@
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
// #[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}

View File

@@ -20,7 +20,10 @@ fn main() {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
Event::MainEventsCleared => {
window.request_redraw();
}
_ => *control_flow = ControlFlow::Poll,
}
});
}

View File

@@ -40,7 +40,7 @@ fn main() {
quit = true;
*control_flow = ControlFlow::Exit;
}
Event::EventsCleared => {
Event::MainEventsCleared => {
*control_flow = ControlFlow::Exit;
}
_ => *control_flow = ControlFlow::Wait,

View File

@@ -9,12 +9,11 @@ use std::path::PathBuf;
use crate::{
dpi::{LogicalPosition, LogicalSize},
window::WindowId,
platform_impl,
window::{Theme, WindowId},
};
pub mod device;
/// A generic event.
/// Describes a generic event.
#[derive(Clone, Debug, PartialEq)]
pub enum Event<T> {
/// Emitted when the OS sends an event to a winit window.
@@ -22,22 +21,31 @@ pub enum Event<T> {
window_id: WindowId,
event: WindowEvent,
},
/// Emitted when a mouse device has generated input.
MouseEvent(device::MouseId, device::MouseEvent),
/// Emitted when a keyboard device has generated input.
KeyboardEvent(device::KeyboardId, device::KeyboardEvent),
HidEvent(device::HidId, device::HidEvent),
/// Emitted when a gamepad/joystick device has generated input.
GamepadEvent(device::GamepadHandle, device::GamepadEvent),
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](../event_loop/struct.EventLoopProxy.html#method.send_event)
/// Emitted when the OS sends an event to a device.
DeviceEvent {
device_id: DeviceId,
event: DeviceEvent,
},
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event)
UserEvent(T),
/// Emitted when new events arrive from the OS to be processed.
NewEvents(StartCause),
/// Emitted when all of the event loop's events have been processed and control flow is about
/// to be taken away from the program.
EventsCleared,
/// Emitted when all events (except for `RedrawRequested`) have been reported.
///
/// This event is followed by zero or more instances of `RedrawRequested`
/// and, finally, `RedrawEventsCleared`.
MainEventsCleared,
/// The OS or application has requested that a window be redrawn.
///
/// Emitted only after `MainEventsCleared`.
RedrawRequested(WindowId),
/// Emitted after any `RedrawRequested` events.
///
/// If there are no `RedrawRequested` events, it is reported immediately after
/// `MainEventsCleared`.
RedrawEventsCleared,
/// Emitted when the event loop is being shut down. This is irreversable - if this event is
/// emitted, it is guaranteed to be the last event emitted.
@@ -56,12 +64,11 @@ impl<T> Event<T> {
match self {
UserEvent(_) => Err(self),
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
MouseEvent(id, event) => Ok(MouseEvent(id, event)),
KeyboardEvent(id, event) => Ok(KeyboardEvent(id, event)),
HidEvent(id, event) => Ok(HidEvent(id, event)),
GamepadEvent(id, event) => Ok(GamepadEvent(id, event)),
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
NewEvents(cause) => Ok(NewEvents(cause)),
EventsCleared => Ok(EventsCleared),
MainEventsCleared => Ok(MainEventsCleared),
RedrawRequested(wid) => Ok(RedrawRequested(wid)),
RedrawEventsCleared => Ok(RedrawEventsCleared),
LoopDestroyed => Ok(LoopDestroyed),
Suspended => Ok(Suspended),
Resumed => Ok(Resumed),
@@ -69,7 +76,7 @@ impl<T> Event<T> {
}
}
/// The reason the event loop is resuming.
/// Describes the reason the event loop is resuming.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StartCause {
/// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the
@@ -95,7 +102,7 @@ pub enum StartCause {
Init,
}
/// An event from a `Window`.
/// Describes an event from a `Window`.
#[derive(Clone, Debug, PartialEq)]
pub enum WindowEvent {
/// The size of the window has changed. Contains the client area's new dimensions.
@@ -137,34 +144,54 @@ pub enum WindowEvent {
Focused(bool),
/// An event from the keyboard has been received.
KeyboardInput(KeyboardInput),
KeyboardInput {
device_id: DeviceId,
input: KeyboardInput,
/// If `true`, the event was generated synthetically by winit
/// in one of the following circumstances:
///
/// * Synthetic key press events are generated for all keys pressed
/// when a window gains focus. Likewise, synthetic key release events
/// are generated for all keys pressed when a window goes out of focus.
/// ***Currently, this is only functional on X11 and Windows***
///
/// Otherwise, this value is always `false`.
is_synthetic: bool,
},
/// The cursor has moved on the window.
CursorMoved {
device_id: DeviceId,
/// (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: LogicalPosition,
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
/// The cursor has entered the window.
CursorEntered,
CursorEntered { device_id: DeviceId },
/// The cursor has left the window.
CursorLeft,
CursorLeft { device_id: DeviceId },
/// A mouse wheel movement or touchpad scroll occurred.
MouseWheel {
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
/// An mouse button press has been received.
MouseInput {
device_id: DeviceId,
state: ElementState,
button: MouseButton,
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
@@ -173,10 +200,18 @@ pub enum WindowEvent {
/// At the moment, only supported on Apple forcetouch-capable macbooks.
/// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad
/// is being pressed) and stage (integer representing the click level).
TouchpadPressure { pressure: f32, stage: i64 },
TouchpadPressure {
device_id: DeviceId,
pressure: f32,
stage: i64,
},
/// The OS or application has requested that the window be redrawn.
RedrawRequested,
/// Motion on some analog axis. May report data redundant to other, more specific events.
AxisMotion {
device_id: DeviceId,
axis: AxisId,
value: f64,
},
/// Touch event has been received
Touch(Touch),
@@ -191,9 +226,94 @@ pub enum WindowEvent {
///
/// For more information about DPI in general, see the [`dpi`](crate::dpi) module.
HiDpiFactorChanged(f64),
/// The system window theme has changed.
///
/// Applications might wish to react to this to change the theme of the content of the window
/// when the system changes the window theme.
///
/// At the moment this is only supported on Windows.
ThemeChanged(Theme),
}
/// A keyboard input event.
/// Identifier of an input device.
///
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
impl DeviceId {
/// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `DeviceId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
DeviceId(platform_impl::DeviceId::dummy())
}
}
/// Represents raw hardware events that are not associated with any particular window.
///
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person
/// game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because
/// window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs
/// may not match.
///
/// Note that these events are delivered regardless of input focus.
#[derive(Clone, Debug, PartialEq)]
pub enum DeviceEvent {
Added,
Removed,
/// Change in physical position of a pointing device.
///
/// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`.
MouseMotion {
/// (x, y) change in position in unspecified units.
///
/// Different devices may use different units.
delta: (f64, f64),
},
/// Physical scroll event
MouseWheel {
delta: MouseScrollDelta,
},
/// Motion on some analog axis. This event will be reported for all arbitrary input devices
/// that winit supports on this platform, including mouse devices. If the device is a mouse
/// device then this will be reported alongside the MouseMotion event.
Motion {
axis: AxisId,
value: f64,
},
Button {
button: ButtonId,
state: ElementState,
},
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,
},
}
/// Describes a keyboard input event.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyboardInput {
@@ -216,19 +336,17 @@ 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"]
pub modifiers: ModifiersState,
}
/// Touch input state.
/// Describes touch-screen input state.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TouchPhase {
Started,
Moved,
Ended,
/// The touch has been cancelled by the OS.
///
/// This can occur in a variety of situations, such as the window losing focus.
Cancelled,
}
@@ -250,6 +368,7 @@ pub enum TouchPhase {
/// device against their face.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Touch {
pub device_id: DeviceId,
pub phase: TouchPhase,
pub location: LogicalPosition,
/// Describes how hard the screen was pressed. May be `None` if the platform
@@ -260,8 +379,6 @@ pub struct Touch {
/// - Only available on **iOS** 9.0+ and **Windows** 8+.
pub force: Option<Force>,
/// Unique identifier of a finger.
///
/// This may get reused by the system after the touch ends.
pub id: u64,
}
@@ -330,16 +447,16 @@ pub type AxisId = u32;
/// Identifier for a specific button on some device.
pub type ButtonId = u32;
/// The input state of a key or button.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
/// Describes the input state of a key.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ElementState {
Pressed,
Released,
}
/// A button on a mouse.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
/// Describes a button of a mouse controller.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseButton {
Left,
@@ -348,7 +465,7 @@ pub enum MouseButton {
Other(u8),
}
/// A difference in the mouse scroll wheel state.
/// Describes a difference in the mouse scroll wheel state.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseScrollDelta {
@@ -367,7 +484,7 @@ pub enum MouseScrollDelta {
PixelDelta(LogicalPosition),
}
/// Symbolic name of a keyboard key.
/// Symbolic name for a keyboard key.
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -566,21 +683,99 @@ pub enum VirtualKeyCode {
Cut,
}
/// The current state of the keyboard modifiers
///
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct ModifiersState {
/// The "shift" key
pub shift: bool,
/// The "control" key
pub ctrl: bool,
/// The "alt" key
pub alt: bool,
/// The "logo" key
///
/// This is the "windows" key on PC and "command" key on Mac.
pub logo: bool,
impl ModifiersState {
/// Returns `true` if the shift key is pressed.
pub fn shift(&self) -> bool {
self.intersects(Self::SHIFT)
}
/// Returns `true` if the control key is pressed.
pub fn ctrl(&self) -> bool {
self.intersects(Self::CTRL)
}
/// Returns `true` if the alt key is pressed.
pub fn alt(&self) -> bool {
self.intersects(Self::ALT)
}
/// Returns `true` if the logo key is pressed.
pub fn logo(&self) -> bool {
self.intersects(Self::LOGO)
}
}
bitflags! {
/// Represents the current state of the keyboard modifiers
///
/// Each flag represents a modifier and is set if this modifier is active.
#[derive(Default)]
pub struct ModifiersState: u32 {
// left and right modifiers are currently commented out, but we should be able to support
// them in a future release
/// The "shift" key.
const SHIFT = 0b100 << 0;
// const LSHIFT = 0b010 << 0;
// const RSHIFT = 0b001 << 0;
/// The "control" key.
const CTRL = 0b100 << 3;
// const LCTRL = 0b010 << 3;
// const RCTRL = 0b001 << 3;
/// The "alt" key.
const ALT = 0b100 << 6;
// const LALT = 0b010 << 6;
// const RALT = 0b001 << 6;
/// This is the "windows" key on PC and "command" key on Mac.
const LOGO = 0b100 << 9;
// const LLOGO = 0b010 << 9;
// const RLOGO = 0b001 << 9;
}
}
#[cfg(feature = "serde")]
mod modifiers_serde {
use super::ModifiersState;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Default, Serialize, Deserialize)]
#[serde(default)]
#[serde(rename = "ModifiersState")]
pub struct ModifiersStateSerialize {
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
pub logo: bool,
}
impl Serialize for ModifiersState {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = ModifiersStateSerialize {
shift: self.shift(),
ctrl: self.ctrl(),
alt: self.alt(),
logo: self.logo(),
};
s.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ModifiersState {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let ModifiersStateSerialize {
shift,
ctrl,
alt,
logo,
} = ModifiersStateSerialize::deserialize(deserializer)?;
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, shift);
m.set(ModifiersState::CTRL, ctrl);
m.set(ModifiersState::ALT, alt);
m.set(ModifiersState::LOGO, logo);
Ok(m)
}
}
}

View File

@@ -1,363 +0,0 @@
//! Raw hardware events that are not associated with any particular window.
//!
//! Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person
//! game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because
//! window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs
//! may not match.
//!
//! All attached devices are guaranteed to emit an `Added` event upon the initialization of the event loop.
//!
//! Note that device events are always delivered regardless of window focus.
use crate::{
dpi::PhysicalPosition,
event::{AxisId, ButtonId, ElementState, KeyboardInput, MouseButton, ModifiersState},
event_loop::EventLoop,
platform_impl,
};
use std::{fmt, io};
/// A hint suggesting the type of button that was pressed.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum GamepadButton {
Start,
Select,
/// The north face button.
///
/// * Nintendo: X
/// * Playstation: Triangle
/// * XBox: Y
North,
/// The south face button.
///
/// * Nintendo: B
/// * Playstation: X
/// * XBox: A
South,
/// The east face button.
///
/// * Nintendo: A
/// * Playstation: Circle
/// * XBox: B
East,
/// The west face button.
///
/// * Nintendo: Y
/// * Playstation: Square
/// * XBox: X
West,
LeftStick,
RightStick,
LeftTrigger,
RightTrigger,
LeftShoulder,
RightShoulder,
DPadUp,
DPadDown,
DPadLeft,
DPadRight,
}
/// A hint suggesting the type of axis that moved.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum GamepadAxis {
LeftStickX,
LeftStickY,
RightStickX,
RightStickY,
LeftTrigger,
RightTrigger,
}
/// A given joystick's side on the gamepad.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Side {
Left,
Right,
}
/// Raw mouse events.
///
/// See the module-level docs for more information.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum MouseEvent {
/// A mouse device has been added.
Added,
/// A mouse device has been removed.
Removed,
/// A mouse button has been pressed or released.
Button {
state: ElementState,
button: MouseButton,
},
/// Relative change in physical position of a pointing device.
///
/// This represents raw, unfiltered physical motion, NOT the position of the mouse. Accordingly,
/// the values provided here are the change in position of the mouse since the previous
/// `MovedRelative` event.
MovedRelative(f64, f64),
/// Change in absolute position of a pointing device.
///
/// The `PhysicalPosition` value is the new position of the cursor relative to the desktop. This
/// generally doesn't get output by standard mouse devices, but can get output from tablet devices.
MovedAbsolute(PhysicalPosition),
/// Change in rotation of mouse wheel.
Wheel(f64, f64),
}
/// Raw keyboard events.
///
/// See the module-level docs for more information.
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
pub enum KeyboardEvent {
/// A keyboard device has been added.
Added,
/// A keyboard device has been removed.
Removed,
/// A key has been pressed or released.
Input(KeyboardInput),
ModifiersChanged(ModifiersState),
}
/// Raw HID event.
///
/// See the module-level docs for more information.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum HidEvent {
/// A Human Interface Device device has been added.
Added,
/// A Human Interface Device device has been removed.
Removed,
/// A raw data packet has been received from the Human Interface Device.
Data(Box<[u8]>),
}
/// Gamepad/joystick events.
///
/// These can be generated by any of a variety of game controllers, including (but not limited to)
/// gamepads, joysicks, and HOTAS devices.
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub enum GamepadEvent {
/// A gamepad/joystick device has been added.
Added,
/// A gamepad/joystick device has been removed.
Removed,
/// An analog axis value on the gamepad/joystick has changed.
///
/// Winit does NOT provide [deadzone filtering](https://www.quora.com/What-does-the-term-joystick-deadzone-mean),
/// and such filtering may have to be provided by API users for joystick axes.
Axis {
axis_id: AxisId,
/// A hint regarding the physical axis that moved.
///
/// On traditional gamepads (such as an X360 controller) this can be assumed to have a
/// non-`None` value; however, other joystick devices with more varied layouts generally won't
/// provide a value here.
///
/// TODO: DISCUSS CONTROLLER MAPPING ONCE WE FIGURE OUT WHAT WE'RE DOING THERE.
axis: Option<GamepadAxis>,
value: f64,
/// Whether or not this axis has also produced a [`GamepadEvent::Stick`] event.
stick: bool,
},
/// A two-axis joystick's value has changed.
///
/// This is mainly provided to assist with deadzone calculation, as proper deadzones should be
/// calculated via the combined distance of each joystick axis from the center of the joystick,
/// rather than per-axis.
///
/// Note that this is only guaranteed to be emitted for traditionally laid out gamepads. More
/// complex joysticks generally don't report specifics of their layout to the operating system,
/// preventing Winit from automatically aggregating their axis input into two-axis stick events.
Stick {
/// The X axis' ID.
x_id: AxisId,
/// The Y axis' ID.
y_id: AxisId,
x_value: f64,
y_value: f64,
/// Which joystick side produced this event.
side: Side,
},
Button {
button_id: ButtonId,
/// A hint regarding the location of the button.
///
/// The caveats on the `Axis.hint` field also apply here.
button: Option<GamepadButton>,
state: ElementState,
},
}
/// Error reported if a rumble attempt unexpectedly failed.
#[derive(Debug)]
pub enum RumbleError {
/// The device is no longer connected.
DeviceNotConnected,
/// An unknown OS error has occured.
OsError(io::Error),
}
/// A typed identifier for a mouse device.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MouseId(pub(crate) platform_impl::MouseId);
/// A typed identifier for a keyboard device.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct KeyboardId(pub(crate) platform_impl::KeyboardId);
/// A typed if for a Human Interface Device.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HidId(pub(crate) platform_impl::HidId);
/// A handle to a gamepad/joystick device.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GamepadHandle(pub(crate) platform_impl::GamepadHandle);
impl MouseId {
/// Returns a dummy `MouseId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `MouseId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
MouseId(platform_impl::MouseId::dummy())
}
/// Enumerate all attached mouse devices.
pub fn enumerate<T>(event_loop: &EventLoop<T>) -> impl '_ + Iterator<Item = Self> {
platform_impl::MouseId::enumerate(&event_loop.event_loop)
}
/// Check to see if this mouse device is still connected.
pub fn is_connected(&self) -> bool {
self.0.is_connected()
}
}
impl KeyboardId {
/// Returns a dummy `KeyboardId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `KeyboardId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
KeyboardId(platform_impl::KeyboardId::dummy())
}
/// Enumerate all attached keyboard devices.
pub fn enumerate<T>(event_loop: &EventLoop<T>) -> impl '_ + Iterator<Item = Self> {
platform_impl::KeyboardId::enumerate(&event_loop.event_loop)
}
/// Check to see if this keyboard device is still connected.
pub fn is_connected(&self) -> bool {
self.0.is_connected()
}
}
impl HidId {
/// Returns a dummy `HidId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `HidId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
HidId(platform_impl::HidId::dummy())
}
/// Enumerate all attached keyboard devices.
pub fn enumerate<T>(event_loop: &EventLoop<T>) -> impl '_ + Iterator<Item = Self> {
platform_impl::HidId::enumerate(&event_loop.event_loop)
}
/// Check to see if this keyboard device is still connected.
pub fn is_connected(&self) -> bool {
self.0.is_connected()
}
}
impl GamepadHandle {
/// Returns a dummy `GamepadHandle`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `GamepadHandle`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
GamepadHandle(platform_impl::GamepadHandle::dummy())
}
/// Enumerate all attached gamepad/joystick devices.
pub fn enumerate<T>(event_loop: &EventLoop<T>) -> impl '_ + Iterator<Item = Self> {
platform_impl::GamepadHandle::enumerate(&event_loop.event_loop)
}
/// Check to see if this gamepad/joystick device is still connected.
pub fn is_connected(&self) -> bool {
self.0.is_connected()
}
/// Attempts to set the rumble values for an attached controller. Input values are automatically
/// bound to a [`0.0`, `1.0`] range.
///
/// Certain gamepads assign different usages to the left and right motors - for example, X360
/// controllers treat the left motor as a low-frequency rumble and the right motor as a
/// high-frequency rumble. However, this cannot necessarily be assumed for all gamepad devices.
///
/// Note that, if the given gamepad does not support rumble, no error value gets thrown.
pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), RumbleError> {
self.0.rumble(left_speed, right_speed)
}
/// Gets the port number assigned to the gamepad.
pub fn port(&self) -> Option<u8> {
self.0.port()
}
/// Gets the controller's battery level.
///
/// If the controller doesn't report a battery level, this returns `None`.
pub fn battery_level(&self) -> Option<BatteryLevel> {
self.0.battery_level()
}
}
/// TODO: IS THIS THE RIGHT ABSTRACTION FOR ALL PLATFORMS?
/// This is exposed in its current form because it's what Microsoft does for XInput, and that's my
/// (@Osspial's) main point of reference. If you're implementing this on a different platform and
/// that platform exposes battery level differently, please bring it up in the tracking issue!
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum BatteryLevel {
Empty,
Low,
Medium,
Full,
}
impl fmt::Debug for MouseId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.0.fmt(f)
}
}
impl fmt::Debug for KeyboardId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.0.fmt(f)
}
}
impl fmt::Debug for HidId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.0.fmt(f)
}
}
impl fmt::Debug for GamepadHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.0.fmt(f)
}
}

View File

@@ -59,7 +59,7 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
/// Set by the user callback given to the `EventLoop::run` method.
///
/// Indicates the desired behavior of the event loop after [`Event::EventsCleared`][events_cleared]
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`][events_cleared]
/// is emitted. Defaults to `Poll`.
///
/// ## Persistency
@@ -68,7 +68,7 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
/// are **not** persistent between multiple calls to `run_return` - issuing a new call will reset
/// the control flow to `Poll`.
///
/// [events_cleared]: crate::event::Event::EventsCleared
/// [events_cleared]: crate::event::Event::RedrawEventsCleared
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
@@ -199,7 +199,7 @@ impl<T: 'static> EventLoopProxy<T> {
/// function.
///
/// Returns an `Err` if the associated `EventLoop` no longer exists.
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.event_loop_proxy.send_event(event)
}
}
@@ -211,17 +211,17 @@ impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
}
/// The error that is returned when an `EventLoopProxy` attempts to wake up an `EventLoop` that
/// no longer exists.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct EventLoopClosed;
/// no longer exists. Contains the original event given to `send_event`.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventLoopClosed<T>(pub T);
impl fmt::Display for EventLoopClosed {
impl<T: fmt::Debug> fmt::Display for EventLoopClosed<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", error::Error::description(self))
}
}
impl error::Error for EventLoopClosed {
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {
fn description(&self) -> &str {
"Tried to wake up a closed `EventLoop`"
}

View File

@@ -48,19 +48,16 @@
//!
//! event_loop.run(move |event, _, control_flow| {
//! match event {
//! Event::EventsCleared => {
//! Event::MainEventsCleared => {
//! // Application update code.
//!
//! // Queue a RedrawRequested event.
//! window.request_redraw();
//! },
//! Event::WindowEvent {
//! event: WindowEvent::RedrawRequested,
//! ..
//! } => {
//! Event::RedrawRequested(_) => {
//! // Redraw the application.
//! //
//! // It's preferrable to render in this event rather than in EventsCleared, since
//! // 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.
//! },
@@ -122,7 +119,6 @@ extern crate log;
#[macro_use]
extern crate serde;
#[macro_use]
#[cfg(any(target_os = "ios", target_os = "windows"))]
extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
@@ -138,7 +134,6 @@ pub mod event_loop;
mod icon;
pub mod monitor;
mod platform_impl;
mod util;
pub mod window;
pub mod platform;

View File

@@ -6,7 +6,7 @@ use libc;
use winapi::shared::windef::HWND;
use crate::{
event::device::{GamepadHandle, KeyboardId, MouseId},
event::DeviceId,
event_loop::EventLoop,
monitor::MonitorHandle,
platform_impl::EventLoop as WindowsEventLoop,
@@ -77,6 +77,9 @@ pub trait WindowExtWindows {
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
/// Whether the system theme is currently Windows 10's "Dark Mode".
fn is_dark_mode(&self) -> bool;
}
impl WindowExtWindows for Window {
@@ -94,6 +97,11 @@ impl WindowExtWindows for Window {
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
self.window.set_taskbar_icon(taskbar_icon)
}
#[inline]
fn is_dark_mode(&self) -> bool {
self.window.is_dark_mode()
}
}
/// Additional methods on `WindowBuilder` that are specific to Windows.
@@ -149,49 +157,17 @@ impl MonitorHandleExtWindows for MonitorHandle {
}
}
/// Additional methods on device types that are specific to Windows.
pub trait DeviceExtWindows {
/// Additional methods on `DeviceId` that are specific to Windows.
pub trait DeviceIdExtWindows {
/// Returns an identifier that persistently refers to this specific device.
///
/// Will return `None` if the device is no longer available.
fn persistent_identifier(&self) -> Option<String>;
/// Returns the handle of the device - `HANDLE`.
fn handle(&self) -> *mut c_void;
}
impl DeviceExtWindows for MouseId {
impl DeviceIdExtWindows for DeviceId {
#[inline]
fn persistent_identifier(&self) -> Option<String> {
self.0.persistent_identifier()
}
#[inline]
fn handle(&self) -> *mut c_void {
self.0.handle() as _
}
}
impl DeviceExtWindows for KeyboardId {
#[inline]
fn persistent_identifier(&self) -> Option<String> {
self.0.persistent_identifier()
}
#[inline]
fn handle(&self) -> *mut c_void {
self.0.handle() as _
}
}
impl DeviceExtWindows for GamepadHandle {
#[inline]
fn persistent_identifier(&self) -> Option<String> {
self.0.persistent_identifier()
}
#[inline]
fn handle(&self) -> *mut c_void {
self.0.handle() as _
}
}

View File

@@ -157,7 +157,7 @@ impl EventLoop {
}
impl EventLoopProxy {
pub fn wakeup(&self) -> Result<(), ::EventLoopClosed> {
pub fn wakeup(&self) -> Result<(), ::EventLoopClosed<()>> {
android_glue::wake_event_loop();
Ok(())
}
@@ -360,6 +360,11 @@ impl Window {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
pub fn set_minimized(&self, _minimized: bool) {
unimplemented!()
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// N/A

View File

@@ -12,7 +12,7 @@ use std::{
use objc::runtime::{BOOL, YES};
use crate::{
event::{Event, StartCause, WindowEvent},
event::{Event, StartCause},
event_loop::ControlFlow,
platform_impl::platform::{
event_loop::{EventHandler, Never},
@@ -51,11 +51,7 @@ enum UserCallbackTransitionResult<'a> {
impl Event<Never> {
fn is_redraw(&self) -> bool {
if let Event::WindowEvent {
window_id: _,
event: WindowEvent::RedrawRequested,
} = self
{
if let Event::RedrawRequested(_) = self {
true
} else {
false
@@ -776,16 +772,18 @@ pub unsafe fn handle_main_events_cleared() {
// User events are always sent out at the end of the "MainEventLoop"
handle_user_events();
handle_nonuser_event(Event::EventsCleared);
handle_nonuser_event(Event::MainEventsCleared);
let mut this = AppState::get_mut();
let redraw_events = this
let mut redraw_events: Vec<Event<Never>> = this
.main_events_cleared_transition()
.into_iter()
.map(|window| Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::RedrawRequested,
});
.map(|window| Event::RedrawRequested(RootWindowId(window.into())))
.collect();
if !redraw_events.is_empty() {
redraw_events.push(Event::RedrawEventsCleared);
}
drop(this);
handle_nonuser_events(redraw_events);

View File

@@ -165,8 +165,10 @@ impl<T> EventLoopProxy<T> {
}
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.sender.send(event).map_err(|_| EventLoopClosed)?;
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender
.send(event)
.map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);

View File

@@ -18,31 +18,44 @@ pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) screen_mode: id,
pub(crate) screen_mode: NativeDisplayMode,
pub(crate) monitor: MonitorHandle,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct NativeDisplayMode(pub id);
unsafe impl Send for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
let () = msg_send![self.0, release];
}
}
}
impl Clone for NativeDisplayMode {
fn clone(&self) -> Self {
unsafe {
let _: id = msg_send![self.0, retain];
}
NativeDisplayMode(self.0)
}
}
impl Clone for VideoMode {
fn clone(&self) -> VideoMode {
VideoMode {
size: self.size,
bit_depth: self.bit_depth,
refresh_rate: self.refresh_rate,
screen_mode: unsafe { msg_send![self.screen_mode, retain] },
screen_mode: self.screen_mode.clone(),
monitor: self.monitor.clone(),
}
}
}
impl Drop for VideoMode {
fn drop(&mut self) {
unsafe {
assert_main_thread!("`VideoMode` can only be dropped on the main thread on iOS");
let () = msg_send![self.screen_mode, release];
}
}
}
impl VideoMode {
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
@@ -64,11 +77,13 @@ impl VideoMode {
60
};
let size: CGSize = msg_send![screen_mode, size];
let screen_mode: id = msg_send![screen_mode, retain];
let screen_mode = NativeDisplayMode(screen_mode);
VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate: refresh_rate as u16,
screen_mode: msg_send![screen_mode, retain],
screen_mode,
monitor: MonitorHandle::retained_new(uiscreen),
}
}

View File

@@ -102,10 +102,10 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
unsafe {
let window: id = msg_send![object, window];
assert!(!window.is_null());
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::RedrawRequested,
});
app_state::handle_nonuser_events(
std::iter::once(Event::RedrawRequested(RootWindowId(window.into())))
.chain(std::iter::once(Event::RedrawEventsCleared)),
);
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![super(object, superclass), drawRect: rect];
}
@@ -497,7 +497,7 @@ pub unsafe fn create_window(
match window_attributes.fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => {
let uiscreen = video_mode.monitor().ui_screen() as id;
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
}
Some(Fullscreen::Borderless(ref monitor)) => {

View File

@@ -169,6 +169,10 @@ impl Inner {
debug!("`Window::set_cursor_visible` is ignored on iOS")
}
pub fn set_minimized(&self, _minimized: bool) {
warn!("`Window::set_minimized` is ignored on iOS")
}
pub fn set_maximized(&self, _maximized: bool) {
warn!("`Window::set_maximized` is ignored on iOS")
}

View File

@@ -367,6 +367,14 @@ impl Window {
}
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
match self {
&Window::X(ref w) => w.set_minimized(minimized),
&Window::Wayland(ref w) => w.set_minimized(minimized),
}
}
#[inline]
pub fn fullscreen(&self) -> Option<Fullscreen> {
match self {
@@ -650,7 +658,7 @@ impl<T: 'static> EventLoop<T> {
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
match *self {
EventLoopProxy::Wayland(ref proxy) => proxy.send_event(event),
EventLoopProxy::X(ref proxy) => proxy.send_event(event),

View File

@@ -282,8 +282,14 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.user_sender.send(event).map_err(|_| EventLoopClosed)
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_sender.send(event).map_err(|e| {
EventLoopClosed(if let ::calloop::channel::SendError::Disconnected(x) = e {
x
} else {
unreachable!()
})
})
}
}
@@ -503,24 +509,34 @@ impl<T: 'static> EventLoop<T> {
);
}
}
// do a second run of post-dispatch-triggers, to handle user-generated "request-redraw"
// in response of resize & friends
self.post_dispatch_triggers();
// send Events cleared
{
let mut guard = sink.lock().unwrap();
guard.empty_with(|evt| {
sticky_exit_callback(
crate::event::Event::MainEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
);
}
// handle request-redraw
{
self.redraw_triggers(|wid, window_target| {
sticky_exit_callback(
evt,
&self.window_target,
crate::event::Event::RedrawRequested(crate::window::WindowId(
crate::platform_impl::WindowId::Wayland(wid),
)),
window_target,
&mut control_flow,
&mut callback,
);
});
}
// send Events cleared
// send RedrawEventsCleared
{
sticky_exit_callback(
crate::event::Event::EventsCleared,
crate::event::Event::RedrawEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
@@ -646,6 +662,31 @@ impl<T> EventLoopWindowTarget<T> {
*/
impl<T> EventLoop<T> {
fn redraw_triggers<F>(&mut self, mut callback: F)
where
F: FnMut(WindowId, &RootELW<T>),
{
let window_target = match self.window_target.p {
crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt,
_ => unreachable!(),
};
window_target.store.lock().unwrap().for_each_redraw_trigger(
|refresh, frame_refresh, wid, frame| {
if let Some(frame) = frame {
if frame_refresh {
frame.refresh();
if !refresh {
frame.surface().commit()
}
}
}
if refresh {
callback(wid, &self.window_target);
}
},
)
}
fn post_dispatch_triggers(&mut self) {
let mut sink = self.sink.lock().unwrap();
let window_target = match self.window_target.p {
@@ -664,60 +705,47 @@ impl<T> EventLoop<T> {
}
}
// process pending resize/refresh
window_target.store.lock().unwrap().for_each(
|newsize,
size,
new_dpi,
refresh,
frame_refresh,
closed,
grab_cursor,
surface,
wid,
frame| {
if let Some(frame) = frame {
if let Some(newsize) = newsize {
// Drop resize events equaled to the current size
if newsize != *size {
let (w, h) = newsize;
frame.resize(w, h);
frame.refresh();
let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64);
sink.send_window_event(
crate::event::WindowEvent::Resized(logical_size),
wid,
);
*size = (w, h);
} else {
// Refresh csd, etc, otherwise
frame.refresh();
}
} else if frame_refresh {
frame.refresh();
if !refresh {
frame.surface().commit()
}
window_target.store.lock().unwrap().for_each(|window| {
if let Some(frame) = window.frame {
if let Some(newsize) = window.newsize {
let (w, h) = 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 newsize != *window.size {
let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64);
sink.send_window_event(
crate::event::WindowEvent::Resized(logical_size),
window.wid,
);
*window.size = (w, h);
}
}
if let Some(dpi) = new_dpi {
sink.send_window_event(
crate::event::WindowEvent::HiDpiFactorChanged(dpi as f64),
wid,
);
}
if refresh {
sink.send_window_event(crate::event::WindowEvent::RedrawRequested, wid);
}
if closed {
sink.send_window_event(crate::event::WindowEvent::CloseRequested, wid);
}
}
if let Some(dpi) = window.new_dpi {
sink.send_window_event(
crate::event::WindowEvent::HiDpiFactorChanged(dpi as f64),
window.wid,
);
}
if window.closed {
sink.send_window_event(crate::event::WindowEvent::CloseRequested, window.wid);
}
if let Some(grab_cursor) = grab_cursor {
let surface = if grab_cursor { Some(surface) } else { None };
self.cursor_manager.lock().unwrap().grab_pointer(surface);
}
},
)
if let Some(grab_cursor) = window.grab_cursor {
let surface = if grab_cursor {
Some(window.surface)
} else {
None
};
self.cursor_manager.lock().unwrap().grab_pointer(surface);
}
})
}
}

View File

@@ -74,6 +74,7 @@ pub fn init_keyboard(
virtual_keycode: vkcode,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
is_synthetic: false,
},
})
.unwrap();
@@ -104,7 +105,7 @@ pub fn init_keyboard(
my_sink
.send(Event::DeviceEvent {
device_id: device_id(),
event: DeviceEvent::ModifiersChanged { modifiers },
event: DeviceEvent::ModifiersChanged(modifiers),
})
.unwrap();
}
@@ -125,6 +126,7 @@ pub fn init_keyboard(
virtual_keycode: vkcode,
modifiers: my_modifiers.lock().unwrap().clone(),
},
is_synthetic: false,
},
})
.unwrap();
@@ -198,6 +200,7 @@ pub fn init_keyboard(
virtual_keycode: None,
modifiers: ModifiersState::default(),
},
is_synthetic: false,
},
})
.unwrap();
@@ -401,12 +404,12 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
impl ModifiersState {
pub(crate) fn from_wayland(mods: keyboard::ModifiersState) -> ModifiersState {
ModifiersState {
shift: mods.shift,
ctrl: mods.ctrl,
alt: mods.alt,
logo: mods.logo,
}
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, mods.shift);
m.set(ModifiersState::CTRL, mods.ctrl);
m.set(ModifiersState::ALT, mods.alt);
m.set(ModifiersState::LOGO, mods.logo);
m
}
}

View File

@@ -56,6 +56,13 @@ pub fn implement_pointer<T: 'static>(
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
mouse_focus = Some(wid);
// Reload cursor style only when we enter winit's surface. Calling
// this function every time on `PtrEvent::Enter` could interfere with
// SCTK CSD handling, since it changes cursor icons when you hover
// cursor over the window borders.
cursor_manager.reload_cursor_style();
sink.send_window_event(
WindowEvent::CursorEntered {
device_id: crate::event::DeviceId(
@@ -75,8 +82,6 @@ pub fn implement_pointer<T: 'static>(
wid,
);
}
cursor_manager.reload_cursor_style();
}
PtrEvent::Leave { surface, .. } => {
mouse_focus = None;

View File

@@ -259,6 +259,13 @@ impl Window {
*(self.need_frame_refresh.lock().unwrap()) = true;
}
pub fn set_minimized(&self, minimized: bool) {
// An app cannot un-minimize itself on Wayland
if minimized {
self.frame.lock().unwrap().set_minimized();
}
}
pub fn set_maximized(&self, maximized: bool) {
if maximized {
self.frame.lock().unwrap().set_maximized();
@@ -385,6 +392,17 @@ pub struct WindowStore {
windows: Vec<InternalWindow>,
}
pub struct WindowStoreForEach<'a> {
pub newsize: Option<(u32, u32)>,
pub size: &'a mut (u32, u32),
pub new_dpi: 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>>,
}
impl WindowStore {
pub fn new() -> WindowStore {
WindowStore {
@@ -434,34 +452,21 @@ impl WindowStore {
pub fn for_each<F>(&mut self, mut f: F)
where
F: FnMut(
Option<(u32, u32)>,
&mut (u32, u32),
Option<i32>,
bool,
bool,
bool,
Option<bool>,
&wl_surface::WlSurface,
WindowId,
Option<&mut SWindow<ConceptFrame>>,
),
F: FnMut(WindowStoreForEach<'_>),
{
for window in &mut self.windows {
let opt_arc = window.frame.upgrade();
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
f(
window.newsize.take(),
&mut *(window.size.lock().unwrap()),
window.new_dpi,
replace(&mut *window.need_refresh.lock().unwrap(), false),
replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
window.closed,
window.cursor_grab_changed.lock().unwrap().take(),
&window.surface,
make_wid(&window.surface),
opt_mutex_lock.as_mut().map(|m| &mut **m),
);
f(WindowStoreForEach {
newsize: window.newsize.take(),
size: &mut *(window.size.lock().unwrap()),
new_dpi: window.new_dpi,
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),
});
if let Some(dpi) = window.new_dpi.take() {
window.current_dpi = dpi;
}
@@ -469,4 +474,20 @@ impl WindowStore {
window.closed = false;
}
}
pub fn for_each_redraw_trigger<F>(&mut self, mut f: F)
where
F: FnMut(bool, bool, WindowId, Option<&mut SWindow<ConceptFrame>>),
{
for window in &mut self.windows {
let opt_arc = window.frame.upgrade();
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
f(
replace(&mut *window.need_refresh.lock().unwrap(), false),
replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
make_wid(&window.surface),
opt_mutex_lock.as_mut().map(|m| &mut **m),
);
}
}
}

View File

@@ -1,4 +1,4 @@
use std::{cell::RefCell, collections::HashMap, ptr, rc::Rc, slice};
use std::{cell::RefCell, collections::HashMap, rc::Rc, slice};
use libc::{c_char, c_int, c_long, c_uint, c_ulong};
@@ -12,7 +12,9 @@ use util::modifiers::{ModifierKeyState, ModifierKeymap};
use crate::{
dpi::{LogicalPosition, LogicalSize},
event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent},
event::{
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, TouchPhase, WindowEvent,
},
event_loop::EventLoopWindowTarget as RootELW,
};
@@ -25,6 +27,9 @@ pub(super) struct EventProcessor<T: 'static> {
pub(super) target: Rc<RootELW<T>>,
pub(super) mod_keymap: ModifierKeymap,
pub(super) device_mod_state: ModifierKeyState,
// Number of touch events currently in progress
pub(super) num_touch: u32,
pub(super) first_touch: Option<u64>,
}
impl<T: 'static> EventProcessor<T> {
@@ -132,7 +137,7 @@ impl<T: 'static> EventProcessor<T> {
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::ModifiersChanged { modifiers },
event: DeviceEvent::ModifiersChanged(modifiers),
});
}
}
@@ -406,14 +411,16 @@ impl<T: 'static> EventProcessor<T> {
let last_hidpi_factor = shared_state_lock.last_monitor.hidpi_factor;
let new_hidpi_factor = {
let window_rect = util::AaRect::new(new_outer_position, new_inner_size);
monitor = wt.xconn.get_monitor_for_window(Some(window_rect));
let new_hidpi_factor = monitor.hidpi_factor;
let new_monitor = wt.xconn.get_monitor_for_window(Some(window_rect));
// Avoid caching an invalid dummy monitor handle
if monitor.id != 0 {
if new_monitor.is_dummy() {
// Avoid updating monitor using a dummy monitor handle
last_hidpi_factor
} else {
monitor = new_monitor;
shared_state_lock.last_monitor = monitor.clone();
monitor.hidpi_factor
}
new_hidpi_factor
};
if last_hidpi_factor != new_hidpi_factor {
events.dpi_changed =
@@ -527,13 +534,14 @@ impl<T: 'static> EventProcessor<T> {
ffi::Expose => {
let xev: &ffi::XExposeEvent = xev.as_ref();
let window = xev.window;
let window_id = mkwid(window);
// Multiple Expose events may be received for subareas of a window.
// We issue `RedrawRequested` only for the last event of such a series.
if xev.count == 0 {
let window = xev.window;
let window_id = mkwid(window);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
});
callback(Event::RedrawRequested(window_id));
}
}
ffi::KeyPress | ffi::KeyRelease => {
@@ -555,22 +563,13 @@ impl<T: 'static> EventProcessor<T> {
// value, though this should only be an issue under multiseat configurations.
let device = util::VIRTUAL_CORE_KEYBOARD;
let device_id = mkdid(device);
let keycode = xkev.keycode;
// When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with
// a keycode of 0.
if xkev.keycode != 0 {
let keysym = unsafe {
let mut keysym = 0;
(wt.xconn.xlib.XLookupString)(
xkev,
ptr::null_mut(),
0,
&mut keysym,
ptr::null_mut(),
);
wt.xconn.check_errors().expect("Failed to lookup keysym");
keysym
};
if keycode != 0 {
let scancode = keycode - 8;
let keysym = wt.xconn.lookup_keysym(xkev);
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
update_modifiers!(
@@ -586,10 +585,11 @@ impl<T: 'static> EventProcessor<T> {
device_id,
input: KeyboardInput {
state,
scancode: xkev.keycode - 8,
scancode,
virtual_keycode,
modifiers,
},
is_synthetic: false,
},
});
}
@@ -626,7 +626,7 @@ impl<T: 'static> EventProcessor<T> {
ElementState::{Pressed, Released},
MouseButton::{Left, Middle, Other, Right},
MouseScrollDelta::LineDelta,
Touch, TouchPhase,
Touch,
WindowEvent::{
AxisMotion, CursorEntered, CursorLeft, CursorMoved, Focused, MouseInput,
MouseWheel,
@@ -835,14 +835,15 @@ impl<T: 'static> EventProcessor<T> {
}
}
}
callback(Event::WindowEvent {
window_id,
event: CursorEntered { device_id },
});
if let Some(dpi_factor) =
self.with_window(xev.event, |window| window.hidpi_factor())
{
callback(Event::WindowEvent {
window_id,
event: CursorEntered { device_id },
});
let position = LogicalPosition::from_physical(
(xev.event_x as f64, xev.event_y as f64),
dpi_factor,
@@ -906,6 +907,10 @@ impl<T: 'static> EventProcessor<T> {
event: Focused(true),
});
let modifiers = ModifiersState::from_x11(&xev.mods);
update_modifiers!(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
@@ -924,9 +929,12 @@ impl<T: 'static> EventProcessor<T> {
event: CursorMoved {
device_id: mkdid(pointer_id),
position,
modifiers: ModifiersState::from_x11(&xev.mods),
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 _) };
@@ -938,8 +946,13 @@ impl<T: 'static> EventProcessor<T> {
.unfocus(xev.event)
.expect("Failed to unfocus input context");
let window_id = mkwid(xev.event);
// Issue key release events for all pressed keys
self.handle_pressed_keys(window_id, ElementState::Released, &mut callback);
callback(Event::WindowEvent {
window_id: mkwid(xev.event),
window_id,
event: Focused(false),
})
}
@@ -956,10 +969,27 @@ impl<T: 'static> EventProcessor<T> {
let dpi_factor =
self.with_window(xev.event, |window| window.hidpi_factor());
if let Some(dpi_factor) = dpi_factor {
let id = xev.detail as u64;
let modifiers = self.device_mod_state.modifiers();
let location = LogicalPosition::from_physical(
(xev.event_x as f64, xev.event_y as f64),
dpi_factor,
);
// Mouse cursor position changes when touch events are received.
// Only the first concurrently active touch ID moves the mouse cursor.
if is_first_touch(&mut self.first_touch, &mut self.num_touch, id, phase)
{
callback(Event::WindowEvent {
window_id,
event: WindowEvent::CursorMoved {
device_id: mkdid(util::VIRTUAL_CORE_POINTER),
position: location,
modifiers,
},
});
}
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Touch(Touch {
@@ -967,7 +997,7 @@ impl<T: 'static> EventProcessor<T> {
phase,
location,
force: None, // TODO
id: xev.detail as u64,
id,
}),
})
}
@@ -1056,20 +1086,8 @@ impl<T: 'static> EventProcessor<T> {
return;
}
let scancode = (keycode - 8) as u32;
let keysym = unsafe {
(wt.xconn.xlib.XKeycodeToKeysym)(
wt.xconn.display,
xev.detail as ffi::KeyCode,
0,
)
};
wt.xconn
.check_errors()
.expect("Failed to lookup raw keysym");
let keysym = wt.xconn.keycode_to_keysym(keycode as ffi::KeyCode);
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
let modifiers = self.device_mod_state.modifiers();
callback(Event::DeviceEvent {
@@ -1096,9 +1114,7 @@ impl<T: 'static> EventProcessor<T> {
if modifiers != new_modifiers {
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::ModifiersChanged {
modifiers: new_modifiers,
},
event: DeviceEvent::ModifiersChanged(new_modifiers),
});
}
}
@@ -1180,4 +1196,65 @@ impl<T: 'static> EventProcessor<T> {
Err(_) => (),
}
}
fn handle_pressed_keys<F>(
&self,
window_id: crate::window::WindowId,
state: ElementState,
callback: &mut F,
) where
F: FnMut(Event<T>),
{
let wt = get_xtarget(&self.target);
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
let modifiers = self.device_mod_state.modifiers();
// Get the set of keys currently pressed and apply Key events to each
let keys = wt.xconn.query_keymap();
for keycode in &keys {
if keycode < 8 {
continue;
}
let scancode = (keycode - 8) as u32;
let keysym = wt.xconn.keycode_to_keysym(keycode);
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
device_id,
input: KeyboardInput {
scancode,
state,
virtual_keycode,
modifiers,
},
is_synthetic: true,
},
});
}
}
}
fn is_first_touch(first: &mut Option<u64>, num: &mut u32, id: u64, phase: TouchPhase) -> bool {
match phase {
TouchPhase::Started => {
if *num == 0 {
*first = Some(id);
}
*num += 1;
}
TouchPhase::Cancelled | TouchPhase::Ended => {
if *first == Some(id) {
*first = None;
}
*num = num.saturating_sub(1);
}
_ => (),
}
*first == Some(id)
}

View File

@@ -1,4 +1,10 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
#![cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
mod dnd;
mod event_processor;
@@ -39,7 +45,7 @@ use self::{
};
use crate::{
error::OsError as RootOsError,
event::{Event, StartCause, WindowEvent},
event::{Event, StartCause},
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
platform_impl::{platform::sticky_exit_callback, PlatformSpecificWindowBuilderAttributes},
window::WindowAttributes,
@@ -145,6 +151,8 @@ impl<T: 'static> EventLoop<T> {
xconn.update_cached_wm_info(root);
let pending_redraws: Arc<Mutex<HashSet<WindowId>>> = Default::default();
let mut mod_keymap = ModifierKeymap::new();
mod_keymap.reset_from_x_connection(&xconn);
@@ -158,7 +166,7 @@ impl<T: 'static> EventLoop<T> {
xconn,
wm_delete_window,
net_wm_ping,
pending_redraws: Default::default(),
pending_redraws: pending_redraws.clone(),
}),
_marker: ::std::marker::PhantomData,
});
@@ -193,6 +201,8 @@ impl<T: 'static> EventLoop<T> {
xi2ext,
mod_keymap,
device_mod_state: Default::default(),
num_touch: 0,
first_touch: None,
};
// Register for device hotplug events
@@ -219,7 +229,9 @@ impl<T: 'static> EventLoop<T> {
if evt.readiness.is_readable() {
let mut processor = processor.borrow_mut();
let mut pending_events = pending_events.borrow_mut();
drain_events(&mut processor, &mut pending_events);
let mut pending_redraws = pending_redraws.lock().unwrap();
drain_events(&mut processor, &mut pending_events, &mut pending_redraws);
}
}
})
@@ -285,6 +297,15 @@ impl<T: 'static> EventLoop<T> {
);
}
}
// send MainEventsCleared
{
sticky_exit_callback(
crate::event::Event::MainEventsCleared,
&self.target,
&mut control_flow,
&mut callback,
);
}
// Empty the redraw requests
{
// Release the lock to prevent deadlock
@@ -292,20 +313,17 @@ impl<T: 'static> EventLoop<T> {
for wid in windows {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(super::WindowId::X(wid)),
event: WindowEvent::RedrawRequested,
},
Event::RedrawRequested(crate::window::WindowId(super::WindowId::X(wid))),
&self.target,
&mut control_flow,
&mut callback,
);
}
}
// send Events cleared
// send RedrawEventsCleared
{
sticky_exit_callback(
crate::event::Event::EventsCleared,
crate::event::Event::RedrawEventsCleared,
&self.target,
&mut control_flow,
&mut callback,
@@ -384,8 +402,10 @@ impl<T: 'static> EventLoop<T> {
fn drain_events(&self) {
let mut processor = self.event_processor.borrow_mut();
let mut pending_events = self.pending_events.borrow_mut();
let wt = get_xtarget(&self.target);
let mut pending_redraws = wt.pending_redraws.lock().unwrap();
drain_events(&mut processor, &mut pending_events);
drain_events(&mut processor, &mut pending_events, &mut pending_redraws);
}
fn events_waiting(&self) -> bool {
@@ -396,9 +416,14 @@ impl<T: 'static> EventLoop<T> {
fn drain_events<T: 'static>(
processor: &mut EventProcessor<T>,
pending_events: &mut VecDeque<Event<T>>,
pending_redraws: &mut HashSet<WindowId>,
) {
let mut callback = |event| {
pending_events.push_back(event);
if let Event::RedrawRequested(crate::window::WindowId(super::WindowId::X(wid))) = event {
pending_redraws.insert(wid);
} else {
pending_events.push_back(event);
}
};
// process all pending events
@@ -425,8 +450,14 @@ impl<T> EventLoopWindowTarget<T> {
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.user_sender.send(event).map_err(|_| EventLoopClosed)
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_sender.send(event).map_err(|e| {
EventLoopClosed(if let ::calloop::channel::SendError::Disconnected(x) = e {
x
} else {
unreachable!()
})
})
}
}

View File

@@ -143,6 +143,11 @@ impl MonitorHandle {
}
}
pub(crate) fn is_dummy(&self) -> bool {
// Zero is an invalid XID value; no real monitor will have it
self.id == 0
}
pub fn name(&self) -> Option<String> {
Some(self.name.clone())
}

View File

@@ -17,12 +17,12 @@ impl ModifiersState {
}
pub(crate) fn from_x11_mask(mask: c_uint) -> Self {
ModifiersState {
alt: mask & ffi::Mod1Mask != 0,
shift: mask & ffi::ShiftMask != 0,
ctrl: mask & ffi::ControlMask != 0,
logo: mask & ffi::Mod4Mask != 0,
}
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, mask & ffi::Mod1Mask != 0);
m.set(ModifiersState::CTRL, mask & ffi::ShiftMask != 0);
m.set(ModifiersState::ALT, mask & ffi::ControlMask != 0);
m.set(ModifiersState::LOGO, mask & ffi::Mod4Mask != 0);
m
}
}

View File

@@ -0,0 +1,92 @@
use std::{iter::Enumerate, ptr, slice::Iter};
use super::*;
pub struct Keymap {
keys: [u8; 32],
}
pub struct KeymapIter<'a> {
iter: Enumerate<Iter<'a, u8>>,
index: usize,
item: Option<u8>,
}
impl Keymap {
pub fn iter(&self) -> KeymapIter<'_> {
KeymapIter {
iter: self.keys.iter().enumerate(),
index: 0,
item: None,
}
}
}
impl<'a> IntoIterator for &'a Keymap {
type Item = ffi::KeyCode;
type IntoIter = KeymapIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl Iterator for KeymapIter<'_> {
type Item = ffi::KeyCode;
fn next(&mut self) -> Option<ffi::KeyCode> {
if self.item.is_none() {
while let Some((index, &item)) = self.iter.next() {
if item != 0 {
self.index = index;
self.item = Some(item);
break;
}
}
}
self.item.take().map(|item| {
debug_assert!(item != 0);
let bit = first_bit(item);
if item != bit {
// Remove the first bit; save the rest for further iterations
self.item = Some(item ^ bit);
}
let shift = bit.trailing_zeros() + (self.index * 8) as u32;
shift as ffi::KeyCode
})
}
}
impl XConnection {
pub fn keycode_to_keysym(&self, keycode: ffi::KeyCode) -> ffi::KeySym {
unsafe { (self.xlib.XKeycodeToKeysym)(self.display, keycode, 0) }
}
pub fn lookup_keysym(&self, xkev: &mut ffi::XKeyEvent) -> ffi::KeySym {
let mut keysym = 0;
unsafe {
(self.xlib.XLookupString)(xkev, ptr::null_mut(), 0, &mut keysym, ptr::null_mut());
}
keysym
}
pub fn query_keymap(&self) -> Keymap {
let mut keys = [0; 32];
unsafe {
(self.xlib.XQueryKeymap)(self.display, keys.as_mut_ptr() as *mut c_char);
}
Keymap { keys }
}
}
fn first_bit(b: u8) -> u8 {
1 << b.trailing_zeros()
}

View File

@@ -9,6 +9,7 @@ mod geometry;
mod hint;
mod icon;
mod input;
pub mod keys;
mod memory;
pub mod modifiers;
mod randr;

View File

@@ -116,10 +116,10 @@ impl ModifierKeyState {
let mut new_state = *state;
match except {
Some(Modifier::Alt) => new_state.alt = self.state.alt,
Some(Modifier::Ctrl) => new_state.ctrl = self.state.ctrl,
Some(Modifier::Shift) => new_state.shift = self.state.shift,
Some(Modifier::Logo) => new_state.logo = self.state.logo,
Some(Modifier::Alt) => new_state.set(ModifiersState::ALT, self.state.alt()),
Some(Modifier::Ctrl) => new_state.set(ModifiersState::CTRL, self.state.ctrl()),
Some(Modifier::Shift) => new_state.set(ModifiersState::SHIFT, self.state.shift()),
Some(Modifier::Logo) => new_state.set(ModifiersState::LOGO, self.state.logo()),
None => (),
}
@@ -170,18 +170,18 @@ impl ModifierKeyState {
fn get_modifier(state: &ModifiersState, modifier: Modifier) -> bool {
match modifier {
Modifier::Alt => state.alt,
Modifier::Ctrl => state.ctrl,
Modifier::Shift => state.shift,
Modifier::Logo => state.logo,
Modifier::Alt => state.alt(),
Modifier::Ctrl => state.ctrl(),
Modifier::Shift => state.shift(),
Modifier::Logo => state.logo(),
}
}
fn set_modifier(state: &mut ModifiersState, modifier: Modifier, value: bool) {
match modifier {
Modifier::Alt => state.alt = value,
Modifier::Ctrl => state.ctrl = value,
Modifier::Shift => state.shift = value,
Modifier::Logo => state.logo = value,
Modifier::Alt => state.set(ModifiersState::ALT, value),
Modifier::Ctrl => state.set(ModifiersState::CTRL, value),
Modifier::Shift => state.set(ModifiersState::SHIFT, value),
Modifier::Logo => state.set(ModifiersState::LOGO, value),
}
}

View File

@@ -6,24 +6,17 @@ use super::{
};
use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode};
/// Represents values of `WINIT_HIDPI_FACTOR`.
pub enum EnvVarDPI {
Randr,
Scale(f64),
NotSet,
}
pub fn calc_dpi_factor(
(width_px, height_px): (u32, u32),
(width_mm, height_mm): (u64, u64),
) -> f64 {
// Override DPI if `WINIT_HIDPI_FACTOR` variable is set
let dpi_override = env::var("WINIT_HIDPI_FACTOR")
.ok()
.and_then(|var| f64::from_str(&var).ok());
if let Some(dpi_override) = dpi_override {
if !validate_hidpi_factor(dpi_override) {
panic!(
"`WINIT_HIDPI_FACTOR` invalid; DPI factors must be normal floats greater than 0. Got `{}`",
dpi_override,
);
}
return dpi_override;
}
// See http://xpra.org/trac/ticket/728 for more information.
if width_mm == 0 || height_mm == 0 {
warn!("XRandR reported that the display's 0mm in size, which is certifiably insane");
@@ -107,16 +100,55 @@ impl XConnection {
(*output_info).nameLen as usize,
);
let name = String::from_utf8_lossy(name_slice).into();
let hidpi_factor = if let Some(dpi) = self.get_xft_dpi() {
dpi / 96.
} else {
calc_dpi_factor(
// Override DPI if `WINIT_HIDPI_FACTOR` variable is set
let dpi_env = env::var("WINIT_HIDPI_FACTOR").ok().map_or_else(
|| EnvVarDPI::NotSet,
|var| {
if var.to_lowercase() == "randr" {
EnvVarDPI::Randr
} else if let Ok(dpi) = f64::from_str(&var) {
EnvVarDPI::Scale(dpi)
} else if var.is_empty() {
EnvVarDPI::NotSet
} else {
panic!(
"`WINIT_HIDPI_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`",
var
);
}
},
);
let hidpi_factor = match dpi_env {
EnvVarDPI::Randr => calc_dpi_factor(
((*crtc).width as u32, (*crtc).height as u32),
(
(*output_info).mm_width as u64,
(*output_info).mm_height as u64,
),
)
),
EnvVarDPI::Scale(dpi_override) => {
if !validate_hidpi_factor(dpi_override) {
panic!(
"`WINIT_HIDPI_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`",
dpi_override,
);
}
dpi_override
}
EnvVarDPI::NotSet => {
if let Some(dpi) = self.get_xft_dpi() {
dpi / 96.
} else {
calc_dpi_factor(
((*crtc).width as u32, (*crtc).height as u32),
(
(*output_info).mm_width as u64,
(*output_info).mm_height as u64,
),
)
}
}
};
(self.xrandr.XRRFreeOutputInfo)(output_info);

View File

@@ -651,7 +651,7 @@ impl UnownedWindow {
};
// Don't set fullscreen on an invalid dummy monitor handle
if monitor.id == 0 {
if monitor.is_dummy() {
return None;
}
@@ -749,6 +749,35 @@ impl UnownedWindow {
self.xconn.primary_monitor()
}
fn set_minimized_inner(&self, minimized: bool) -> util::Flusher<'_> {
unsafe {
if minimized {
let screen = (self.xconn.xlib.XDefaultScreen)(self.xconn.display);
(self.xconn.xlib.XIconifyWindow)(self.xconn.display, self.xwindow, screen);
util::Flusher::new(&self.xconn)
} else {
let atom = self.xconn.get_atom_unchecked(b"_NET_ACTIVE_WINDOW\0");
self.xconn.send_client_msg(
self.xwindow,
self.root,
atom,
Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask),
[1, ffi::CurrentTime as c_long, 0, 0, 0],
)
}
}
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
self.set_minimized_inner(minimized)
.flush()
.expect("Failed to change window minimization");
}
fn set_maximized_inner(&self, maximized: bool) -> util::Flusher<'_> {
let horz_atom = unsafe {
self.xconn

View File

@@ -11,14 +11,19 @@ use std::{
time::Instant,
};
use cocoa::{appkit::NSApp, base::nil};
use cocoa::{
appkit::NSApp,
base::nil,
foundation::{NSAutoreleasePool, NSString},
};
use crate::{
event::{Event, StartCause, WindowEvent},
event::{Event, StartCause},
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
platform_impl::platform::{observer::EventLoopWaker, util::Never},
window::WindowId,
};
use objc::runtime::Object;
lazy_static! {
static ref HANDLER: Handler = Default::default();
@@ -265,18 +270,37 @@ impl AppState {
for event in HANDLER.take_events() {
HANDLER.handle_nonuser_event(event);
}
HANDLER.handle_nonuser_event(Event::MainEventsCleared);
for window_id in HANDLER.should_redraw() {
HANDLER.handle_nonuser_event(Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
});
HANDLER.handle_nonuser_event(Event::RedrawRequested(window_id));
}
HANDLER.handle_nonuser_event(Event::EventsCleared);
HANDLER.handle_nonuser_event(Event::RedrawEventsCleared);
HANDLER.set_in_callback(false);
}
if HANDLER.should_exit() {
let _: () = unsafe { msg_send![NSApp(), terminate: nil] };
return;
unsafe {
let _: () = msg_send![NSApp(), stop: nil];
let pool = NSAutoreleasePool::new(nil);
let windows: *const Object = msg_send![NSApp(), windows];
let window: *const Object = 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];
pool.drain();
};
}
HANDLER.update_start_time();
match HANDLER.get_old_and_new_control_flow() {

View File

@@ -224,12 +224,24 @@ pub fn check_function_keys(string: &str) -> Option<VirtualKeyCode> {
pub fn event_mods(event: id) -> ModifiersState {
let flags = unsafe { NSEvent::modifierFlags(event) };
ModifiersState {
shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask),
ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask),
alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
logo: flags.contains(NSEventModifierFlags::NSCommandKeyMask),
}
let mut m = ModifiersState::empty();
m.set(
ModifiersState::SHIFT,
flags.contains(NSEventModifierFlags::NSShiftKeyMask),
);
m.set(
ModifiersState::CTRL,
flags.contains(NSEventModifierFlags::NSControlKeyMask),
);
m.set(
ModifiersState::ALT,
flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
);
m.set(
ModifiersState::LOGO,
flags.contains(NSEventModifierFlags::NSCommandKeyMask),
);
m
}
pub fn get_scancode(event: cocoa::base::id) -> c_ushort {
@@ -264,6 +276,7 @@ pub unsafe fn modifier_event(
virtual_keycode,
modifiers: event_mods(ns_event),
},
is_synthetic: false,
})
} else {
None

View File

@@ -95,12 +95,13 @@ impl<T> EventLoop<T> {
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
{
unsafe {
let _pool = NSAutoreleasePool::new(nil);
let pool = NSAutoreleasePool::new(nil);
let app = NSApp();
assert_ne!(app, nil);
AppState::set_callback(callback, Rc::clone(&self.window_target));
let _: () = msg_send![app, run];
AppState::exit();
pool.drain();
}
}
@@ -142,8 +143,10 @@ impl<T> Proxy<T> {
}
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.sender.send(event).map_err(|_| EventLoopClosed)?;
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender
.send(event)
.map_err(|mpsc::SendError(x)| EventLoopClosed(x))?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);

View File

@@ -60,11 +60,15 @@ pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowSty
}
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);
dispatch_sync_f(
dispatch_get_main_queue(),
context as *mut _,
set_style_mask_callback,
);
if msg_send![class!(NSThread), isMainThread] {
set_style_mask_callback(context as *mut _);
} else {
dispatch_sync_f(
dispatch_get_main_queue(),
context as *mut _,
set_style_mask_callback,
);
}
}
struct SetContentSizeData {

View File

@@ -3,7 +3,8 @@ use cocoa::{
base::{id, nil},
foundation::{NSDictionary, NSPoint, NSString},
};
use objc::runtime::Sel;
use objc::{runtime::Sel, runtime::NO};
use std::cell::RefCell;
use crate::window::CursorIcon;
@@ -126,3 +127,38 @@ pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id {
hotSpot:point
]
}
pub unsafe fn invisible_cursor() -> id {
// 16x16 GIF data for invisible cursor
// You can reproduce this via ImageMagick.
// $ convert -size 16x16 xc:none cursor.gif
static CURSOR_BYTES: &[u8] = &[
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00,
0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x0F,
0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B,
];
thread_local! {
// We can't initialize this at startup.
static CURSOR_OBJECT: RefCell<id> = RefCell::new(nil);
}
CURSOR_OBJECT.with(|cursor_obj| {
if *cursor_obj.borrow() == nil {
// Create a cursor from `CURSOR_BYTES`
let cursor_data: id = msg_send![class!(NSData),
dataWithBytesNoCopy:CURSOR_BYTES as *const [u8]
length:CURSOR_BYTES.len()
freeWhenDone:NO
];
let ns_image: id = msg_send![class!(NSImage), alloc];
let _: id = msg_send![ns_image, initWithData: cursor_data];
let cursor: id = msg_send![class!(NSCursor), alloc];
*cursor_obj.borrow_mut() =
msg_send![cursor, initWithImage:ns_image hotSpot: NSPoint::new(0.0, 0.0)];
}
*cursor_obj.borrow()
})
}

View File

@@ -9,7 +9,7 @@ use std::{
use cocoa::{
appkit::{NSApp, NSEvent, NSEventModifierFlags, NSEventPhase, NSView, NSWindow},
base::{id, nil},
foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger},
foundation::{NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger},
};
use objc::{
declare::ClassDecl,
@@ -18,8 +18,8 @@ use objc::{
use crate::{
event::{
DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, TouchPhase,
VirtualKeyCode, WindowEvent,
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseButton,
MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent,
},
platform_impl::platform::{
app_state::AppState,
@@ -35,33 +35,41 @@ use crate::{
window::WindowId,
};
#[derive(Default)]
struct Modifiers {
shift_pressed: bool,
ctrl_pressed: bool,
win_pressed: bool,
alt_pressed: bool,
pub struct CursorState {
pub visible: bool,
pub cursor: util::Cursor,
}
impl Default for CursorState {
fn default() -> Self {
Self {
visible: true,
cursor: Default::default(),
}
}
}
struct ViewState {
ns_window: id,
pub cursor: Arc<Mutex<util::Cursor>>,
pub cursor_state: Arc<Mutex<CursorState>>,
ime_spot: Option<(f64, f64)>,
raw_characters: Option<String>,
is_key_down: bool,
modifiers: Modifiers,
modifiers: ModifiersState,
tracking_rect: Option<NSInteger>,
}
pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<util::Cursor>>) {
let cursor = Default::default();
let cursor_access = Arc::downgrade(&cursor);
pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<CursorState>>) {
let cursor_state = Default::default();
let cursor_access = Arc::downgrade(&cursor_state);
let state = ViewState {
ns_window,
cursor,
cursor_state,
ime_spot: None,
raw_characters: None,
is_key_down: false,
modifiers: Default::default(),
tracking_rect: None,
};
unsafe {
// This is free'd in `dealloc`
@@ -236,6 +244,10 @@ lazy_static! {
sel!(cancelOperation:),
cancel_operation as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(frameDidChange:),
frame_did_change as extern "C" fn(&Object, Sel, id),
);
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<id>("markedText");
let protocol = Protocol::get("NSTextInputClient").unwrap();
@@ -261,6 +273,19 @@ extern "C" fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> i
let marked_text =
<id as NSMutableAttributedString>::init(NSMutableAttributedString::alloc(nil));
(*this).set_ivar("markedText", marked_text);
let _: () = msg_send![this, setPostsFrameChangedNotifications: YES];
let notification_center: &Object =
msg_send![class!(NSNotificationCenter), defaultCenter];
let notification_name =
NSString::alloc(nil).init_str("NSViewFrameDidChangeNotification");
let _: () = msg_send![
notification_center,
addObserver: this
selector: sel!(frameDidChange:)
name: notification_name
object: this
];
}
this
}
@@ -269,17 +294,46 @@ extern "C" fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> i
extern "C" fn view_did_move_to_window(this: &Object, _sel: Sel) {
trace!("Triggered `viewDidMoveToWindow`");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
if let Some(tracking_rect) = state.tracking_rect.take() {
let _: () = msg_send![this, removeTrackingRect: tracking_rect];
}
let rect: NSRect = msg_send![this, visibleRect];
let _: () = msg_send![this,
let tracking_rect: NSInteger = msg_send![this,
addTrackingRect:rect
owner:this
userData:nil
assumeInside:NO
];
state.tracking_rect = Some(tracking_rect);
}
trace!("Completed `viewDidMoveToWindow`");
}
extern "C" fn frame_did_change(this: &Object, _sel: Sel, _event: id) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
if let Some(tracking_rect) = state.tracking_rect.take() {
let _: () = msg_send![this, removeTrackingRect: tracking_rect];
}
let rect: NSRect = msg_send![this, visibleRect];
let tracking_rect: NSInteger = msg_send![this,
addTrackingRect:rect
owner:this
userData:nil
assumeInside:NO
];
state.tracking_rect = Some(tracking_rect);
}
}
extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
@@ -309,7 +363,12 @@ extern "C" fn reset_cursor_rects(this: &Object, _sel: Sel) {
let state = &mut *(state_ptr as *mut ViewState);
let bounds: NSRect = msg_send![this, bounds];
let cursor = state.cursor.lock().unwrap().load();
let cursor_state = state.cursor_state.lock().unwrap();
let cursor = if cursor_state.visible {
cursor_state.cursor.load()
} else {
util::invisible_cursor()
};
let _: () = msg_send![this,
addCursorRect:bounds
cursor:cursor
@@ -582,6 +641,7 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
virtual_keycode,
modifiers: event_mods(event),
},
is_synthetic: false,
},
};
@@ -633,6 +693,7 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) {
virtual_keycode,
modifiers: event_mods(event),
},
is_synthetic: false,
},
};
@@ -652,36 +713,36 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) {
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSShiftKeyMask,
state.modifiers.shift_pressed,
state.modifiers.shift(),
) {
state.modifiers.shift_pressed = !state.modifiers.shift_pressed;
state.modifiers.toggle(ModifiersState::SHIFT);
events.push_back(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSControlKeyMask,
state.modifiers.ctrl_pressed,
state.modifiers.ctrl(),
) {
state.modifiers.ctrl_pressed = !state.modifiers.ctrl_pressed;
state.modifiers.toggle(ModifiersState::CTRL);
events.push_back(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSCommandKeyMask,
state.modifiers.win_pressed,
state.modifiers.logo(),
) {
state.modifiers.win_pressed = !state.modifiers.win_pressed;
state.modifiers.toggle(ModifiersState::LOGO);
events.push_back(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSAlternateKeyMask,
state.modifiers.alt_pressed,
state.modifiers.alt(),
) {
state.modifiers.alt_pressed = !state.modifiers.alt_pressed;
state.modifiers.toggle(ModifiersState::ALT);
events.push_back(window_event);
}
@@ -691,6 +752,11 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) {
event,
});
}
AppState::queue_event(Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::ModifiersChanged(state.modifiers),
});
}
trace!("Completed `flagsChanged`");
}
@@ -741,6 +807,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
virtual_keycode,
modifiers: event_mods(event),
},
is_synthetic: false,
},
};

View File

@@ -20,6 +20,7 @@ use crate::{
ffi,
monitor::{self, MonitorHandle, VideoMode},
util::{self, IdRef},
view::CursorState,
view::{self, new_view},
window_delegate::new_delegate,
OsError,
@@ -90,8 +91,8 @@ fn create_app(activation_policy: ActivationPolicy) -> Option<id> {
unsafe fn create_view(
ns_window: id,
pl_attribs: &PlatformSpecificWindowBuilderAttributes,
) -> Option<(IdRef, Weak<Mutex<util::Cursor>>)> {
let (ns_view, cursor) = new_view(ns_window);
) -> Option<(IdRef, Weak<Mutex<CursorState>>)> {
let (ns_view, cursor_state) = new_view(ns_window);
ns_view.non_nil().map(|ns_view| {
if !pl_attribs.disallow_hidpi {
ns_view.setWantsBestResolutionOpenGLSurface_(YES);
@@ -108,7 +109,7 @@ unsafe fn create_view(
ns_window.setContentView_(*ns_view);
ns_window.makeFirstResponder_(*ns_view);
(ns_view, cursor)
(ns_view, cursor_state)
})
}
@@ -243,6 +244,13 @@ lazy_static! {
pub struct SharedState {
pub resizable: bool,
pub fullscreen: Option<Fullscreen>,
// This is true between windowWillEnterFullScreen and windowDidEnterFullScreen
// or windowWillExitFullScreen and windowDidExitFullScreen.
// We must not toggle fullscreen when this is true.
pub in_fullscreen_transition: bool,
// If it is attempted to toggle fullscreen when in_fullscreen_transition is true,
// Set target_fullscreen and do after fullscreen transition is end.
pub target_fullscreen: Option<Option<Fullscreen>>,
pub maximized: bool,
pub standard_frame: Option<NSRect>,
is_simple_fullscreen: bool,
@@ -283,8 +291,7 @@ pub struct UnownedWindow {
input_context: IdRef, // never changes
pub shared_state: Arc<Mutex<SharedState>>,
decorations: AtomicBool,
cursor: Weak<Mutex<util::Cursor>>,
cursor_visible: AtomicBool,
cursor_state: Weak<Mutex<CursorState>>,
}
unsafe impl Send for UnownedWindow {}
@@ -313,7 +320,7 @@ impl UnownedWindow {
os_error!(OsError::CreationError("Couldn't create `NSWindow`"))
})?;
let (ns_view, cursor) =
let (ns_view, cursor_state) =
unsafe { create_view(*ns_window, &pl_attribs) }.ok_or_else(|| {
unsafe { pool.drain() };
os_error!(OsError::CreationError("Couldn't create `NSView`"))
@@ -361,8 +368,7 @@ impl UnownedWindow {
input_context,
shared_state: Arc::new(Mutex::new(win_attribs.into())),
decorations: AtomicBool::new(decorations),
cursor,
cursor_visible: AtomicBool::new(true),
cursor_state,
});
let delegate = new_delegate(&window, fullscreen.is_some());
@@ -509,8 +515,8 @@ impl UnownedWindow {
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let cursor = util::Cursor::from(cursor);
if let Some(cursor_access) = self.cursor.upgrade() {
*cursor_access.lock().unwrap() = cursor;
if let Some(cursor_access) = self.cursor_state.upgrade() {
cursor_access.lock().unwrap().cursor = cursor;
}
unsafe {
let _: () = msg_send![*self.ns_window,
@@ -528,16 +534,17 @@ impl UnownedWindow {
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
let cursor_class = class!(NSCursor);
// macOS uses a "hide counter" like Windows does, so we avoid incrementing it more than once.
// (otherwise, `hide_cursor(false)` would need to be called n times!)
if visible != self.cursor_visible.load(Ordering::Acquire) {
if visible {
let _: () = unsafe { msg_send![cursor_class, unhide] };
} else {
let _: () = unsafe { msg_send![cursor_class, hide] };
if let Some(cursor_access) = self.cursor_state.upgrade() {
let mut cursor_state = cursor_access.lock().unwrap();
if visible != cursor_state.visible {
cursor_state.visible = visible;
drop(cursor_state);
unsafe {
let _: () = msg_send![*self.ns_window,
invalidateCursorRectsForView:*self.ns_view
];
}
}
self.cursor_visible.store(visible, Ordering::Release);
}
}
@@ -617,6 +624,25 @@ impl UnownedWindow {
self.set_maximized(maximized);
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
let is_minimized: BOOL = unsafe { msg_send![*self.ns_window, isMiniaturized] };
let is_minimized: bool = is_minimized == YES;
if is_minimized == minimized {
return;
}
if minimized {
unsafe {
NSWindow::miniaturize_(*self.ns_window, *self.ns_window);
}
} else {
unsafe {
NSWindow::deminiaturize_(*self.ns_window, *self.ns_window);
}
}
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
let is_zoomed = self.is_zoomed();
@@ -642,11 +668,18 @@ impl UnownedWindow {
#[inline]
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
trace!("Locked shared state in `set_fullscreen`");
let shared_state_lock = self.shared_state.lock().unwrap();
let mut shared_state_lock = self.shared_state.lock().unwrap();
if shared_state_lock.is_simple_fullscreen {
trace!("Unlocked shared state in `set_fullscreen`");
return;
}
if shared_state_lock.in_fullscreen_transition {
// We can't set fullscreen here.
// Set fullscreen after transition.
shared_state_lock.target_fullscreen = Some(fullscreen);
trace!("Unlocked shared state in `set_fullscreen`");
return;
}
let old_fullscreen = shared_state_lock.fullscreen.clone();
if fullscreen == old_fullscreen {
trace!("Unlocked shared state in `set_fullscreen`");

View File

@@ -195,6 +195,10 @@ lazy_static! {
sel!(windowDidExitFullScreen:),
window_did_exit_fullscreen as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowWillExitFullScreen:),
window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidFailToEnterFullScreen:),
window_did_fail_to_enter_fullscreen as extern "C" fn(&Object, Sel, id),
@@ -419,13 +423,27 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
shared_state.fullscreen = Some(Fullscreen::Borderless(window.current_monitor()))
}
}
shared_state.in_fullscreen_transition = true;
trace!("Unlocked shared state in `window_will_enter_fullscreen`");
})
});
trace!("Completed `windowWillEnterFullscreen:`");
}
/// Invoked when before exit fullscreen
extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowWillExitFullScreen:`");
with_state(this, |state| {
state.with_window(|window| {
trace!("Locked shared state in `window_will_exit_fullscreen`");
let mut shared_state = window.shared_state.lock().unwrap();
shared_state.in_fullscreen_transition = true;
trace!("Unlocked shared state in `window_will_exit_fullscreen`");
});
});
trace!("Completed `windowWillExitFullScreen:`");
}
extern "C" fn window_will_use_fullscreen_presentation_options(
_this: &Object,
_: Sel,
@@ -451,6 +469,17 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidEnterFullscreen:`");
with_state(this, |state| {
state.initial_fullscreen = false;
state.with_window(|window| {
trace!("Locked shared state in `window_did_enter_fullscreen`");
let mut shared_state = window.shared_state.lock().unwrap();
shared_state.in_fullscreen_transition = false;
let target_fullscreen = shared_state.target_fullscreen.take();
trace!("Unlocked shared state in `window_did_enter_fullscreen`");
drop(shared_state);
if let Some(target_fullscreen) = target_fullscreen {
window.set_fullscreen(target_fullscreen);
}
});
});
trace!("Completed `windowDidEnterFullscreen:`");
}
@@ -461,6 +490,15 @@ extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) {
with_state(this, |state| {
state.with_window(|window| {
window.restore_state_from_fullscreen();
trace!("Locked shared state in `window_did_exit_fullscreen`");
let mut shared_state = window.shared_state.lock().unwrap();
shared_state.in_fullscreen_transition = false;
let target_fullscreen = shared_state.target_fullscreen.take();
trace!("Unlocked shared state in `window_did_exit_fullscreen`");
drop(shared_state);
if let Some(target_fullscreen) = target_fullscreen {
window.set_fullscreen(target_fullscreen);
}
})
});
trace!("Completed `windowDidExitFullscreen:`");
@@ -485,6 +523,13 @@ extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) {
extern "C" fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidFailToEnterFullscreen:`");
with_state(this, |state| {
state.with_window(|window| {
trace!("Locked shared state in `window_did_fail_to_enter_fullscreen`");
let mut shared_state = window.shared_state.lock().unwrap();
shared_state.in_fullscreen_transition = false;
shared_state.target_fullscreen = None;
trace!("Unlocked shared state in `window_did_fail_to_enter_fullscreen`");
});
if state.initial_fullscreen {
let _: () = unsafe {
msg_send![*state.ns_window,

View File

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

View File

@@ -1,37 +0,0 @@
use crate::event::device::{GamepadAxis, GamepadButton};
pub(crate) static BUTTONS: [GamepadButton; 16] = [
GamepadButton::South,
GamepadButton::East,
GamepadButton::West,
GamepadButton::North,
GamepadButton::LeftTrigger,
GamepadButton::RightTrigger,
GamepadButton::LeftShoulder,
GamepadButton::RightShoulder,
GamepadButton::Select,
GamepadButton::Start,
GamepadButton::LeftStick,
GamepadButton::RightStick,
GamepadButton::DPadUp,
GamepadButton::DPadDown,
GamepadButton::DPadLeft,
GamepadButton::DPadRight,
];
pub(crate) static AXES: [GamepadAxis; 6] = [
GamepadAxis::LeftStickX,
GamepadAxis::LeftStickY,
GamepadAxis::RightStickX,
GamepadAxis::RightStickY,
GamepadAxis::LeftTrigger,
GamepadAxis::RightTrigger,
];
pub(crate) fn button_code(index: usize) -> Option<GamepadButton> {
BUTTONS.get(index).map(|ev| ev.clone())
}
pub(crate) fn axis_code(index: usize) -> Option<GamepadAxis> {
AXES.get(index).map(|ev| ev.clone())
}

View File

@@ -1,167 +0,0 @@
use super::utils;
use crate::event::device;
use crate::platform_impl::platform::{backend, device::gamepad, GamepadHandle, event_loop::global};
use std::collections::VecDeque;
pub struct Manager {
pub(crate) gamepads: Vec<backend::gamepad::Gamepad>,
pub(crate) events: VecDeque<(backend::gamepad::Gamepad, device::GamepadEvent)>,
pub(crate) global_window: Option<global::Shared>,
}
impl Manager {
pub fn new() -> Self {
Self {
gamepads: Vec::new(),
events: VecDeque::new(),
global_window: None,
}
}
// Register global window to fetch gamepads.
// Due to Chrome issue, I prefer to use its gamepad list
pub fn set_global_window(&mut self, global_window: global::Shared) {
self.global_window.replace(global_window);
}
// Get an updated raw gamepad and generate a new mapping
pub fn collect_gamepads(&self) -> Option<Vec<backend::gamepad::Gamepad>> {
self.global_window.as_ref().map(|w| w.get_gamepads())
}
// Collect gamepad events (buttons/axes/sticks)
// dispatch to handler and update gamepads
pub fn collect_events<F>(&mut self, mut handler: F)
where
F: 'static + FnMut((device::GamepadHandle, device::GamepadEvent)),
{
let opt_new_gamepads = self.collect_gamepads();
if opt_new_gamepads.is_none() {
return;
}
let new_gamepads = opt_new_gamepads.unwrap();
let old_gamepads = &self.gamepads;
let mut old_index = 0;
let mut new_index = 0;
// Collect events
loop {
match (old_gamepads.get(old_index), new_gamepads.get(new_index)) {
(Some(old), Some(new)) if old.index() == new.index() => {
// Button events
let buttons = old.mapping.buttons().zip(new.mapping.buttons()).enumerate();
for (btn_index, (old_button, new_button)) in buttons {
match (old_button, new_button) {
(false, true) => {
self.events.push_back((new.clone(), utils::gamepad_button(btn_index, true)))
}
(true, false) => {
self.events.push_back((new.clone(), utils::gamepad_button(btn_index, false)))
}
_ => (),
}
}
// Axis events
let axes = old.mapping.axes().zip(new.mapping.axes()).enumerate();
for (axis_index, (old_axis, new_axis)) in axes {
if old_axis != new_axis {
self.events.push_back((new.clone(), utils::gamepad_axis(axis_index, new_axis)))
}
}
// Stick events
let mut old_axes = old.mapping.axes();
let mut new_axes = new.mapping.axes();
let old_left = (old_axes.next(), old_axes.next());
let new_left = (new_axes.next(), new_axes.next());
if old_left != new_left {
if let (Some(x), Some(y)) = (new_left.0, new_left.1) {
self.events.push_back((
new.clone(),
utils::gamepad_stick(0, 1, x, y, device::Side::Left),
));
}
}
let old_right = (old_axes.next(), old_axes.next());
let new_right = (new_axes.next(), new_axes.next());
if old_right != new_right {
if let (Some(x), Some(y)) = (new_right.0, new_right.1) {
self.events.push_back((
new.clone(),
utils::gamepad_stick(2, 3, x, y, device::Side::Right),
));
}
}
// Increment indices
old_index += 1;
new_index += 1;
},
// Connect
(None, Some(new)) => {
self.events.push_back((
new.clone(),
device::GamepadEvent::Added,
));
new_index += 1;
},
// Connect
(Some(old), Some(new)) if old.index > new.index => {
self.events.push_back((
new.clone(),
device::GamepadEvent::Added,
));
new_index += 1;
},
// Disconnect
(Some(old), Some(_new)) => {
self.events.push_back((
old.clone(),
device::GamepadEvent::Removed,
));
old_index += 1;
},
// Disconnect
(Some(old), None) => {
self.events.push_back((
old.clone(),
device::GamepadEvent::Removed,
));
old_index += 1;
},
// Break loop
(None, None) => {
break
}
}
}
// Dispatch events and drain events vec
loop {
if let Some((gamepad, event)) = self.events.pop_front() {
handler((
device::GamepadHandle(GamepadHandle {
id: gamepad.index,
gamepad: gamepad::Shared::Raw(gamepad),
}),
event,
));
} else {
break;
}
}
// Update gamepads
self.gamepads = new_gamepads;
}
}

View File

@@ -1,23 +0,0 @@
#[derive(Debug, Clone)]
pub enum Mapping {
Standard { buttons: [bool; 16], axes: [f64; 6] },
NoMapping { buttons: Vec<bool>, axes: Vec<f64> },
}
impl Mapping {
pub(crate) fn buttons<'a>(&'a self) -> impl Iterator<Item = bool> + 'a {
match self {
Mapping::Standard { buttons, .. } => buttons.iter(),
Mapping::NoMapping { buttons, .. } => buttons.iter(),
}
.cloned()
}
pub(crate) fn axes<'a>(&'a self) -> impl Iterator<Item = f64> + 'a {
match self {
Mapping::Standard { axes, .. } => axes.iter(),
Mapping::NoMapping { axes, .. } => axes.iter(),
}
.cloned()
}
}

View File

@@ -1,99 +0,0 @@
mod manager;
mod mapping;
mod utils;
pub mod constants;
pub use manager::Manager;
pub use mapping::Mapping;
use crate::event::device::{BatteryLevel, RumbleError};
use crate::platform_impl::platform::backend;
use std::fmt;
pub enum Shared {
Raw(backend::gamepad::Gamepad),
Dummy,
}
impl Shared {
// An integer that is auto-incremented to be unique for each device
// currently connected to the system.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/index
pub fn id(&self) -> i32 {
match self {
Shared::Raw(g) => g.index() as i32,
Shared::Dummy => -1,
}
}
// A string containing some information about the controller.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id
pub fn info(&self) -> String {
match self {
Shared::Raw(g) => g.id(),
Shared::Dummy => String::new(),
}
}
// A boolean indicating whether the gamepad is still connected to the system.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/connected
pub fn connected(&self) -> bool {
match self {
Shared::Raw(g) => g.connected(),
Shared::Dummy => false,
}
}
// [EXPERIMENTAL] An array containing GamepadHapticActuator objects,
// each of which represents haptic feedback hardware available on the controller.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/hapticActuators
pub fn rumble(&self, left_speed: f64, _right_speed: f64) -> Result<(), RumbleError> {
match self {
Shared::Dummy => Ok(()),
Shared::Raw(g) => {
g.vibrate(left_speed, 1000f64);
Ok(())
}
}
}
pub fn is_dummy(&self) -> bool {
match self {
Shared::Dummy => true,
_ => false,
}
}
pub fn port(&self) -> Option<u8> {
None
}
pub fn battery_level(&self) -> Option<BatteryLevel> {
None
}
}
impl Clone for Shared {
fn clone(&self) -> Self {
match self {
Shared::Raw(g) => Shared::Raw(g.clone()),
Shared::Dummy => Shared::Dummy,
}
}
}
impl Default for Shared {
fn default() -> Self {
Shared::Dummy
}
}
impl fmt::Debug for Shared {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
if self.is_dummy() {
write!(f, "Gamepad (Dummy)")
} else {
write!(f, "Gamepad ({}#{})", self.id(), self.info())
}
}
}

View File

@@ -1,44 +0,0 @@
use crate::event::{ElementState, device};
use super::constants;
pub fn gamepad_button(code: usize, pressed: bool) -> device::GamepadEvent {
let button_id = code as u32;
let button = constants::button_code(code);
let state = if pressed {
ElementState::Pressed
} else {
ElementState::Released
};
device::GamepadEvent::Button {
button_id,
button,
state,
}
}
pub fn gamepad_axis(code: usize, value: f64) -> device::GamepadEvent {
let axis_id = code as u32;
let axis = constants::axis_code(code);
device::GamepadEvent::Axis {
axis_id,
axis,
value,
stick: true,
}
}
pub fn gamepad_stick(x_code: usize, y_code: usize, x_value: f64, y_value: f64, side: device::Side) -> device::GamepadEvent {
let x_id = x_code as u32;
let y_id = y_code as u32;
device::GamepadEvent::Stick {
x_id,
y_id,
x_value,
y_value,
side,
}
}

View File

@@ -1,161 +0,0 @@
pub mod gamepad;
use super::event_loop::EventLoop;
use crate::event::device;
use std::{
cmp::{Eq, Ordering, PartialEq, PartialOrd},
hash::{Hash, Hasher},
};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct MouseId(pub i32);
unsafe impl Send for MouseId {}
unsafe impl Sync for MouseId {}
impl MouseId {
pub unsafe fn dummy() -> Self {
Self(0)
}
pub fn is_connected(&self) -> bool {
false
}
pub fn enumerate<'a, T>(
event_loop: &'a EventLoop<T>,
) -> impl 'a + Iterator<Item = device::MouseId> {
event_loop.mice()
}
}
impl From<MouseId> for device::MouseId {
fn from(platform_id: MouseId) -> Self {
Self(platform_id)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct KeyboardId(pub i32);
unsafe impl Send for KeyboardId {}
unsafe impl Sync for KeyboardId {}
impl KeyboardId {
pub unsafe fn dummy() -> Self {
Self(0)
}
pub fn is_connected(&self) -> bool {
false
}
pub fn enumerate<'a, T>(
event_loop: &'a EventLoop<T>,
) -> impl 'a + Iterator<Item = device::KeyboardId> {
event_loop.keyboards()
}
}
impl From<KeyboardId> for device::KeyboardId {
fn from(platform_id: KeyboardId) -> Self {
Self(platform_id)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct HidId(pub i32);
unsafe impl Send for HidId {}
unsafe impl Sync for HidId {}
impl HidId {
pub unsafe fn dummy() -> Self {
Self(0)
}
pub fn is_connected(&self) -> bool {
false
}
pub fn enumerate<'a, T>(
event_loop: &'a EventLoop<T>,
) -> impl 'a + Iterator<Item = device::HidId> {
event_loop.hids()
}
}
impl From<HidId> for device::HidId {
fn from(platform_id: HidId) -> Self {
Self(platform_id)
}
}
#[derive(Clone, Debug)]
pub(crate) struct GamepadHandle {
pub(crate) id: i32,
pub(crate) gamepad: gamepad::Shared,
}
unsafe impl Send for GamepadHandle {}
unsafe impl Sync for GamepadHandle {}
impl GamepadHandle {
pub unsafe fn dummy() -> Self {
Self {
id: -1,
gamepad: gamepad::Shared::default(),
}
}
pub fn is_connected(&self) -> bool {
self.gamepad.connected()
}
pub fn enumerate<'a, T>(
event_loop: &'a EventLoop<T>,
) -> impl 'a + Iterator<Item = device::GamepadHandle> {
event_loop.gamepads()
}
pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), device::RumbleError> {
self.gamepad.rumble(left_speed, right_speed)
}
pub fn port(&self) -> Option<u8> {
self.gamepad.port()
}
pub fn battery_level(&self) -> Option<device::BatteryLevel> {
self.gamepad.battery_level()
}
}
impl Eq for GamepadHandle {}
impl PartialEq for GamepadHandle {
#[inline(always)]
fn eq(&self, othr: &Self) -> bool {
self.id == othr.id
}
}
impl Ord for GamepadHandle {
#[inline(always)]
fn cmp(&self, othr: &Self) -> Ordering {
self.id.cmp(&othr.id)
}
}
impl PartialOrd for GamepadHandle {
#[inline(always)]
fn partial_cmp(&self, othr: &Self) -> Option<Ordering> {
self.id.partial_cmp(&othr.id)
}
}
impl Hash for GamepadHandle {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state)
}
}

View File

@@ -1,81 +0,0 @@
use super::super::device::{gamepad, GamepadHandle};
use super::backend;
use crate::event::device;
use std::{cell::RefCell, rc::Rc, collections::HashSet};
#[derive(Debug)]
pub struct Window {
raw: RefCell<Option<backend::window::Shared>>,
gamepads: Rc<RefCell<HashSet<i32>>>,
}
#[derive(Debug)]
pub struct Shared(Rc<Window>);
impl Shared {
pub fn new() -> Self {
Self(Rc::new(Window {
raw: RefCell::new(None),
gamepads: Rc::new(RefCell::new(HashSet::new())),
}))
}
// Request window object and listen global events
pub fn register_events(&self) -> Result<(), crate::error::OsError> {
if (*self.0.raw.borrow()).is_none() {
let shared = backend::window::Shared::create()?;
let mut window = shared.0.borrow_mut();
let shared_gamepads = self.0.gamepads.clone();
window.on_gamepad_connected(move |gamepad: backend::gamepad::Gamepad| {
let mut gamepads = shared_gamepads.borrow_mut();
let index = gamepad.index();
if !gamepads.contains(&index) {
gamepads.insert(index);
}
});
let shared_gamepads = self.0.gamepads.clone();
window.on_gamepad_disconnected(move |gamepad: backend::gamepad::Gamepad| {
let mut gamepads = shared_gamepads.borrow_mut();
let index = gamepad.index();
if gamepads.contains(&index) {
gamepads.remove(&index);
}
});
self.0.raw.replace(Some(shared.clone()));
}
Ok(())
}
// Google Chrome create an array of [null, null, null, null].
// To fix that issue, I create my own list of gamepads
// by listening "gamepadconnected" and "gamepaddisconnected"
pub fn get_gamepads(&self) -> Vec<backend::gamepad::Gamepad> {
let gamepads = self.0.gamepads.borrow_mut();
backend::get_gamepads()
.filter(|g| gamepads.contains(&g.index()))
.collect()
}
// Return gamepads handles required for EventLoop::gamepads()
pub fn get_gamepad_handles(&self) -> Vec<device::GamepadHandle> {
self.get_gamepads()
.iter()
.map(|gamepad| {
device::GamepadHandle(GamepadHandle {
id: gamepad.index,
gamepad: gamepad::Shared::Raw(gamepad.clone()),
})
})
.collect()
}
}
impl Clone for Shared {
fn clone(&self) -> Self {
Shared(self.0.clone())
}
}

View File

@@ -2,12 +2,11 @@ mod proxy;
mod runner;
mod state;
mod window_target;
pub(crate) mod global;
pub use self::proxy::Proxy;
pub use self::window_target::WindowTarget;
use super::{backend, monitor, window};
use super::{backend, device, monitor, window};
use crate::event::Event;
use crate::event_loop as root;
@@ -65,20 +64,4 @@ impl<T> EventLoop<T> {
pub fn window_target(&self) -> &root::EventLoopWindowTarget<T> {
&self.elw
}
pub fn mice(&self) -> impl '_ + Iterator<Item = crate::event::device::MouseId> {
std::iter::empty()
}
pub fn keyboards(&self) -> impl '_ + Iterator<Item = crate::event::device::KeyboardId> {
std::iter::empty()
}
pub fn hids(&self) -> impl '_ + Iterator<Item = crate::event::device::HidId> {
std::iter::empty()
}
pub fn gamepads(&self) -> impl '_ + Iterator<Item = crate::event::device::GamepadHandle> {
self.elw.p.collect_gamepads().into_iter()
}
}

View File

@@ -11,7 +11,7 @@ impl<T: 'static> Proxy<T> {
Proxy { runner }
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.runner.send_event(Event::UserEvent(event));
Ok(())
}

View File

@@ -1,8 +1,7 @@
use super::{backend, state::State};
use crate::event::{Event, StartCause, WindowEvent};
use crate::event::{Event, StartCause};
use crate::event_loop as root;
use crate::window::WindowId;
use crate::platform_impl::platform::device::gamepad;
use instant::{Duration, Instant};
use std::{
@@ -25,7 +24,6 @@ pub struct Execution<T> {
events: RefCell<VecDeque<Event<T>>>,
id: RefCell<u32>,
redraw_pending: RefCell<HashSet<WindowId>>,
gamepad_manager: RefCell<gamepad::Manager>,
}
struct Runner<T> {
@@ -51,14 +49,9 @@ impl<T: 'static> Shared<T> {
events: RefCell::new(VecDeque::new()),
id: RefCell::new(0),
redraw_pending: RefCell::new(HashSet::new()),
gamepad_manager: RefCell::new(gamepad::Manager::new()),
}))
}
pub fn set_global_window(&self, global_window: super::global::Shared) {
self.0.gamepad_manager.borrow_mut().set_global_window(global_window);
}
// Set the event callback to use for the event loop runner
// This the event callback is a fairly thin layer over the user-provided callback that closes
// over a RootEventLoopWindowTarget reference
@@ -134,26 +127,15 @@ impl<T: 'static> Shared<T> {
if !event_is_start {
self.handle_event(event, &mut control);
}
self.handle_event(Event::MainEventsCleared, &mut control);
// Collect all of the redraw events to avoid double-locking the RefCell
let redraw_events: Vec<WindowId> = self.0.redraw_pending.borrow_mut().drain().collect();
for window_id in redraw_events {
self.handle_event(
Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
},
&mut control,
);
self.handle_event(Event::RedrawRequested(window_id), &mut control);
}
// Collect all global events
let mut gamepad_manager = self.0.gamepad_manager.borrow_mut();
let instance = self.clone();
gamepad_manager.collect_events(move |(handle, event)| {
instance.handle_event(Event::GamepadEvent(handle, event), &mut control);
});
self.handle_event(Event::RedrawEventsCleared, &mut control);
// Every events are cleared
self.handle_event(Event::EventsCleared, &mut control);
self.apply_control_flow(control);
// If the event loop is closed, it has been closed this iteration and now the closing
// event should be emitted

View File

@@ -1,20 +1,18 @@
use super::{backend, proxy::Proxy, runner, window, global};
use super::{backend, device, proxy::Proxy, runner, window};
use crate::dpi::LogicalSize;
use crate::event::{device, ElementState, Event, KeyboardInput, WindowEvent};
use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::platform_impl::platform::device::{KeyboardId, MouseId};
use crate::window::WindowId;
use std::clone::Clone;
pub struct WindowTarget<T: 'static> {
pub(crate) runner: runner::Shared<T>,
pub(crate) global_window: global::Shared,
}
impl<T> Clone for WindowTarget<T> {
fn clone(&self) -> Self {
WindowTarget {
runner: self.runner.clone(),
global_window: self.global_window.clone(),
}
}
}
@@ -23,7 +21,6 @@ impl<T> WindowTarget<T> {
pub fn new() -> Self {
WindowTarget {
runner: runner::Shared::new(),
global_window: global::Shared::new(),
}
}
@@ -32,7 +29,6 @@ impl<T> WindowTarget<T> {
}
pub fn run(&self, event_handler: Box<dyn FnMut(Event<T>, &mut ControlFlow)>) {
self.runner.set_global_window(self.global_window.clone());
self.runner.set_listener(event_handler);
}
@@ -40,14 +36,6 @@ impl<T> WindowTarget<T> {
window::Id(self.runner.generate_id())
}
pub fn collect_gamepads(&self) -> Vec<crate::event::device::GamepadHandle> {
self.global_window.get_gamepad_handles()
}
pub fn register_global_events(&self) -> Result<(), crate::error::OsError> {
self.global_window.register_events()
}
pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) {
let runner = self.runner.clone();
canvas.set_attribute("data-raw-handle", &id.0.to_string());
@@ -69,28 +57,36 @@ impl<T> WindowTarget<T> {
let runner = self.runner.clone();
canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| {
runner.send_event(Event::KeyboardEvent(
device::KeyboardId(unsafe { KeyboardId::dummy() }),
device::KeyboardEvent::Input(KeyboardInput {
scancode,
state: ElementState::Pressed,
virtual_keycode,
modifiers,
}),
));
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::KeyboardInput {
device_id: DeviceId(unsafe { device::Id::dummy() }),
input: KeyboardInput {
scancode,
state: ElementState::Pressed,
virtual_keycode,
modifiers,
},
is_synthetic: false,
},
});
});
let runner = self.runner.clone();
canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| {
runner.send_event(Event::KeyboardEvent(
device::KeyboardId(unsafe { KeyboardId::dummy() }),
device::KeyboardEvent::Input(KeyboardInput {
scancode,
state: ElementState::Released,
virtual_keycode,
modifiers,
}),
));
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::KeyboardInput {
device_id: DeviceId(unsafe { device::Id::dummy() }),
input: KeyboardInput {
scancode,
state: ElementState::Released,
virtual_keycode,
modifiers,
},
is_synthetic: false,
},
});
});
let runner = self.runner.clone();
@@ -102,26 +98,31 @@ impl<T> WindowTarget<T> {
});
let runner = self.runner.clone();
canvas.on_cursor_leave(move || {
canvas.on_cursor_leave(move |pointer_id| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::CursorLeft,
event: WindowEvent::CursorLeft {
device_id: DeviceId(device::Id(pointer_id)),
},
});
});
let runner = self.runner.clone();
canvas.on_cursor_enter(move || {
canvas.on_cursor_enter(move |pointer_id| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::CursorEntered,
event: WindowEvent::CursorEntered {
device_id: DeviceId(device::Id(pointer_id)),
},
});
});
let runner = self.runner.clone();
canvas.on_cursor_move(move |position, modifiers| {
canvas.on_cursor_move(move |pointer_id, position, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::CursorMoved {
device_id: DeviceId(device::Id(pointer_id)),
position,
modifiers,
},
@@ -129,33 +130,42 @@ impl<T> WindowTarget<T> {
});
let runner = self.runner.clone();
canvas.on_mouse_press(move |pointer_id, button| {
runner.send_event(Event::MouseEvent(
device::MouseId(MouseId(pointer_id)),
device::MouseEvent::Button {
canvas.on_mouse_press(move |pointer_id, button, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::MouseInput {
device_id: DeviceId(device::Id(pointer_id)),
state: ElementState::Pressed,
button,
modifiers,
},
));
});
});
let runner = self.runner.clone();
canvas.on_mouse_release(move |pointer_id, button| {
runner.send_event(Event::MouseEvent(
device::MouseId(MouseId(pointer_id)),
device::MouseEvent::Button {
canvas.on_mouse_release(move |pointer_id, button, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::MouseInput {
device_id: DeviceId(device::Id(pointer_id)),
state: ElementState::Released,
button,
modifiers,
},
));
});
});
let runner = self.runner.clone();
canvas.on_mouse_wheel(move |pointer_id, delta| {
runner.send_event(Event::MouseEvent(
device::MouseId(MouseId(pointer_id)),
device::MouseEvent::Wheel(delta.0, delta.1),
));
canvas.on_mouse_wheel(move |pointer_id, delta, modifiers| {
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::MouseWheel {
device_id: DeviceId(device::Id(pointer_id)),
delta,
phase: TouchPhase::Moved,
modifiers,
},
});
});
let runner = self.runner.clone();

View File

@@ -19,6 +19,7 @@ mod backend;
#[cfg(not(any(feature = "web-sys", feature = "stdweb")))]
compile_error!("Please select a feature to build for web: `web-sys`, `stdweb`");
pub use self::device::Id as DeviceId;
pub use self::error::OsError;
pub use self::event_loop::{
EventLoop, Proxy as EventLoopProxy, WindowTarget as EventLoopWindowTarget,
@@ -28,5 +29,3 @@ pub use self::window::{
Id as WindowId, PlatformSpecificBuilderAttributes as PlatformSpecificWindowBuilderAttributes,
Window,
};
pub(crate) use self::device::*;

View File

@@ -1,7 +1,7 @@
use super::utils;
use super::event;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::error::OsError as RootOE;
use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode};
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::platform_impl::OsError;
use std::cell::RefCell;
@@ -129,9 +129,9 @@ impl Canvas {
{
self.on_keyboard_release = Some(self.add_user_event(move |event: KeyUpEvent| {
handler(
utils::scan_code(&event),
utils::virtual_key_code(&event),
utils::keyboard_modifiers(&event),
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
);
}));
}
@@ -142,9 +142,9 @@ impl Canvas {
{
self.on_keyboard_press = Some(self.add_user_event(move |event: KeyDownEvent| {
handler(
utils::scan_code(&event),
utils::virtual_key_code(&event),
utils::keyboard_modifiers(&event),
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
);
}));
}
@@ -159,65 +159,75 @@ impl Canvas {
// viable/compatible alternative as of now. `beforeinput` is still widely
// unsupported.
self.on_received_character = Some(self.add_user_event(move |event: KeyPressEvent| {
handler(utils::codepoint(&event));
handler(event::codepoint(&event));
}));
}
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
F: 'static + FnMut(i32),
{
self.on_cursor_leave = Some(self.add_event(move |_event: PointerOutEvent| {
handler();
self.on_cursor_leave = Some(self.add_event(move |event: PointerOutEvent| {
handler(event.pointer_id());
}));
}
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
F: 'static + FnMut(i32),
{
self.on_cursor_enter = Some(self.add_event(move |_event: PointerOverEvent| {
handler();
self.on_cursor_enter = Some(self.add_event(move |event: PointerOverEvent| {
handler(event.pointer_id());
}));
}
pub fn on_mouse_release<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton),
F: 'static + FnMut(i32, MouseButton, ModifiersState),
{
self.on_mouse_release = Some(self.add_user_event(move |event: PointerUpEvent| {
handler(event.pointer_id(), utils::mouse_button(&event));
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
}));
}
pub fn on_mouse_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton),
F: 'static + FnMut(i32, MouseButton, ModifiersState),
{
self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| {
handler(event.pointer_id(), utils::mouse_button(&event));
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
}));
}
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(LogicalPosition, ModifiersState),
F: 'static + FnMut(i32, LogicalPosition, ModifiersState),
{
self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
handler(
utils::mouse_position(&event),
utils::mouse_modifiers(&event),
event.pointer_id(),
event::mouse_position(&event),
event::mouse_modifiers(&event),
);
}));
}
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, (f64, f64)),
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
{
self.on_mouse_wheel = Some(self.add_event(move |event: MouseWheelEvent| {
let delta = utils::mouse_scroll_delta(&event);
handler(0, delta);
if let Some(delta) = event::mouse_scroll_delta(&event) {
handler(0, delta, event::mouse_modifiers(&event));
}
}));
}

View File

@@ -1,11 +1,7 @@
use crate::dpi::LogicalPosition;
use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode};
use crate::platform_impl::platform::device::gamepad;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use stdweb::web::{
event::{IKeyboardEvent, IMouseEvent, MouseWheelEvent},
Gamepad, GamepadMappingType,
};
use stdweb::web::event::{IKeyboardEvent, IMouseEvent, MouseWheelDeltaMode, MouseWheelEvent};
use stdweb::{js, unstable::TryInto, JsSerialize};
pub fn mouse_button(event: &impl IMouseEvent) -> MouseButton {
@@ -19,12 +15,12 @@ pub fn mouse_button(event: &impl IMouseEvent) -> MouseButton {
}
pub fn mouse_modifiers(event: &impl IMouseEvent) -> ModifiersState {
ModifiersState {
shift: event.shift_key(),
ctrl: event.ctrl_key(),
alt: event.alt_key(),
logo: event.meta_key(),
}
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, event.shift_key());
m.set(ModifiersState::CTRL, event.ctrl_key());
m.set(ModifiersState::ALT, event.alt_key());
m.set(ModifiersState::LOGO, event.meta_key());
m
}
pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition {
@@ -34,10 +30,15 @@ pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition {
}
}
pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> (f64, f64) {
pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option<MouseScrollDelta> {
let x = event.delta_x();
let y = event.delta_y();
(x, y)
match event.delta_mode() {
MouseWheelDeltaMode::Line => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
MouseWheelDeltaMode::Pixel => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })),
MouseWheelDeltaMode::Page => None,
}
}
pub fn scan_code<T: JsSerialize>(event: &T) -> ScanCode {
@@ -212,12 +213,12 @@ pub fn virtual_key_code(event: &impl IKeyboardEvent) -> Option<VirtualKeyCode> {
}
pub fn keyboard_modifiers(event: &impl IKeyboardEvent) -> ModifiersState {
ModifiersState {
shift: event.shift_key(),
ctrl: event.ctrl_key(),
alt: event.alt_key(),
logo: event.meta_key(),
}
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, event.shift_key());
m.set(ModifiersState::CTRL, event.ctrl_key());
m.set(ModifiersState::ALT, event.alt_key());
m.set(ModifiersState::LOGO, event.meta_key());
m
}
pub fn codepoint(event: &impl IKeyboardEvent) -> char {
@@ -226,36 +227,3 @@ pub fn codepoint(event: &impl IKeyboardEvent) -> char {
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
event.key().chars().next().unwrap()
}
pub fn create_mapping(raw: &Gamepad) -> gamepad::Mapping {
match raw.mapping() {
GamepadMappingType::Standard => {
let mut buttons = [false; 16];
let mut axes = [0.0; 6];
for (index, button) in raw
.buttons()
.into_iter()
.enumerate()
.take(buttons.len())
{
buttons[index] = button.pressed();
}
for (index, axis) in raw.axes().into_iter().enumerate().take(axes.len()) {
axes[index] = axis;
}
gamepad::Mapping::Standard { buttons, axes }
}
_ => {
let buttons = raw
.buttons()
.into_iter()
.map(|button| button.pressed())
.collect();
let axes = raw.axes();
gamepad::Mapping::NoMapping { buttons, axes }
}
}
}

View File

@@ -1,82 +0,0 @@
use std::{cmp::PartialEq};
use crate::platform_impl::platform::device;
use super::utils;
use stdweb::js;
#[derive(Debug)]
pub struct Gamepad {
pub(crate) index: i32,
pub(crate) raw: stdweb::web::Gamepad,
pub(crate) mapping: device::gamepad::Mapping,
}
impl Gamepad {
pub fn new(raw: stdweb::web::Gamepad) -> Self {
let mapping = utils::create_mapping(&raw);
Self {
index: raw.index(),
raw,
mapping,
}
}
// An integer that is auto-incremented to be unique for each device
// currently connected to the system.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/index
pub fn index(&self) -> i32 {
self.raw.index()
}
// A string containing some information about the controller.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id
pub fn id(&self) -> String {
self.raw.id()
}
// A boolean indicating whether the gamepad is still connected to the system.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/connected
pub fn connected(&self) -> bool {
self.raw.connected()
}
// EXPERIMENTAL
#[allow(dead_code)]
pub fn vibrate(&self, value: f64, duration: f64) {
let index = self.index;
js! {
const gamepads = navigator.getGamepads();
let gamepad = null;
for (let i = 0; i < gamepads.length; i++) {
if (gamepads[i] && gamepads[i].index == @{index}) {
gamepad = gamepads[i];
break
}
}
if (!gamepad || !gamepad.hapticActuators) return;
for (let i = 0; i < gamepad.hapticActuators.length; i++) {
const actuator = gamepad.hapticActuators[i];
if (actuator && actuator.type === "vibration") {
actuator.pulse(@{value}, @{duration});
}
}
}
}
}
impl Clone for Gamepad {
fn clone(&self) -> Self {
Self {
index: self.index,
raw: self.raw.clone(),
mapping: self.mapping.clone(),
}
}
}
impl PartialEq for Gamepad {
#[inline(always)]
fn eq(&self, othr: &Self) -> bool {
self.raw.index() == othr.raw.index()
}
}

View File

@@ -1,8 +1,6 @@
mod canvas;
pub mod gamepad;
mod event;
mod timeout;
mod utils;
pub mod window;
pub use self::canvas::Canvas;
pub use self::timeout::Timeout;
@@ -52,9 +50,3 @@ pub fn is_fullscreen(canvas: &CanvasElement) -> bool {
None => false,
}
}
pub fn get_gamepads() -> impl Iterator<Item = gamepad::Gamepad> {
stdweb::web::Gamepad::get_all()
.into_iter()
.filter_map(|gamepad| gamepad.map(|gamepad| gamepad::Gamepad::new(gamepad)))
}

View File

@@ -3,7 +3,7 @@ use stdweb::web::{window, IWindowOrWorker, TimeoutHandle};
#[derive(Debug)]
pub struct Timeout {
handle: TimeoutHandle,
handle: Option<TimeoutHandle>,
}
impl Timeout {
@@ -12,14 +12,14 @@ impl Timeout {
F: 'static + FnMut(),
{
Timeout {
handle: window().set_clearable_timeout(f, duration.as_millis() as u32),
handle: Some(window().set_clearable_timeout(f, duration.as_millis() as u32)),
}
}
}
impl Drop for Timeout {
fn drop(&mut self) {
let handle = std::mem::replace(&mut self.handle, unsafe { std::mem::uninitialized() });
let handle = self.handle.take().unwrap();
handle.clear();
}
}

View File

@@ -1,77 +0,0 @@
use super::gamepad;
use crate::error::OsError as RootOE;
use std::{cell::RefCell, rc::Rc};
use stdweb::web;
use stdweb::web::{IEventTarget, event::IGamepadEvent};
#[derive(Debug)]
pub struct Shared(pub Rc<RefCell<Window>>);
#[derive(Debug)]
pub struct Window {
raw: web::Window,
on_gamepad_connected: Option<web::EventListenerHandle>,
on_gamepad_disconnected: Option<web::EventListenerHandle>,
}
impl Shared {
pub fn create() -> Result<Self, RootOE> {
let global = Window::create()?;
Ok(Shared(Rc::new(RefCell::new(global))))
}
}
impl Clone for Shared {
fn clone(&self) -> Self {
Shared(self.0.clone())
}
}
impl Window {
pub fn create() -> Result<Self, RootOE> {
let raw = stdweb::web::window();
Ok(Window {
raw,
on_gamepad_connected: None,
on_gamepad_disconnected: None,
})
}
pub fn on_gamepad_connected<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(gamepad::Gamepad),
{
self.on_gamepad_connected = Some(self.add_event(
move |event: stdweb::web::event::GamepadConnectedEvent| {
let gamepad = event.gamepad();
handler(gamepad::Gamepad::new(gamepad));
},
));
}
pub fn on_gamepad_disconnected<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(gamepad::Gamepad),
{
self.on_gamepad_connected = Some(self.add_event(
move |event: stdweb::web::event::GamepadDisconnectedEvent| {
let gamepad = event.gamepad();
handler(gamepad::Gamepad::new(gamepad));
},
));
}
fn add_event<E, F>(&self, mut handler: F) -> web::EventListenerHandle
where
E: web::event::ConcreteEvent,
F: 'static + FnMut(E),
{
self.raw.add_event_listener(move |event: E| {
event.stop_propagation();
event.cancel_bubble();
handler(event);
})
}
}

View File

@@ -1,16 +1,14 @@
use super::utils;
use super::event;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::error::OsError as RootOE;
use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode};
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::platform_impl::OsError;
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, PointerEvent, WheelEvent};
pub struct Canvas {
raw: HtmlCanvasElement,
@@ -109,56 +107,46 @@ impl Canvas {
where
F: 'static + FnMut(),
{
self.on_blur = Some(self.add_event(
"blur",
move |_: FocusEvent| {
handler();
},
));
self.on_blur = Some(self.add_event("blur", move |_: FocusEvent| {
handler();
}));
}
pub fn on_focus<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_focus = Some(self.add_event(
"focus",
move |_: FocusEvent| {
handler();
},
));
self.on_focus = Some(self.add_event("focus", move |_: FocusEvent| {
handler();
}));
}
pub fn on_keyboard_release<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{
self.on_keyboard_release = Some(self.add_user_event(
"keyup",
move |event: KeyboardEvent| {
self.on_keyboard_release =
Some(self.add_user_event("keyup", move |event: KeyboardEvent| {
handler(
utils::scan_code(&event),
utils::virtual_key_code(&event),
utils::keyboard_modifiers(&event),
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
);
},
));
}));
}
pub fn on_keyboard_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{
self.on_keyboard_press = Some(self.add_user_event(
"keydown",
move |event: KeyboardEvent| {
self.on_keyboard_press =
Some(self.add_user_event("keydown", move |event: KeyboardEvent| {
handler(
utils::scan_code(&event),
utils::virtual_key_code(&event),
utils::keyboard_modifiers(&event),
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
);
},
));
}));
}
pub fn on_received_character<F>(&mut self, mut handler: F)
@@ -173,85 +161,83 @@ impl Canvas {
self.on_received_character = Some(self.add_user_event(
"keypress",
move |event: KeyboardEvent| {
handler(utils::codepoint(&event));
},
));
}
pub fn on_mouse_release<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton),
{
self.on_mouse_release = Some(self.add_event(
"pointerup",
move |event: PointerEvent| {
handler(event.pointer_id(), utils::mouse_button(&event));
},
));
}
pub fn on_mouse_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton),
{
self.on_mouse_press = Some(self.add_event(
"pointerdown",
move |event: PointerEvent| {
handler(event.pointer_id(), utils::mouse_button(&event));
},
));
}
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, (f64, f64)),
{
self.on_mouse_wheel = Some(self.add_event(
"wheel",
move |event: WheelEvent| {
let delta = utils::mouse_scroll_delta(&event);
handler(0, delta);
handler(event::codepoint(&event));
},
));
}
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
F: 'static + FnMut(i32),
{
self.on_cursor_leave = Some(self.add_event(
"pointerout",
move |_event: PointerEvent| {
handler();
},
));
self.on_cursor_leave = Some(self.add_event("pointerout", move |event: PointerEvent| {
handler(event.pointer_id());
}));
}
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
F: 'static + FnMut(i32),
{
self.on_cursor_enter = Some(self.add_event(
"pointerover",
move |_event: PointerEvent| {
handler();
self.on_cursor_enter = Some(self.add_event("pointerover", move |event: PointerEvent| {
handler(event.pointer_id());
}));
}
pub fn on_mouse_release<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton, ModifiersState),
{
self.on_mouse_release = Some(self.add_user_event(
"pointerup",
move |event: PointerEvent| {
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
},
));
}
pub fn on_mouse_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseButton, ModifiersState),
{
self.on_mouse_press = Some(self.add_user_event(
"pointerdown",
move |event: PointerEvent| {
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
},
));
}
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(LogicalPosition, ModifiersState),
F: 'static + FnMut(i32, LogicalPosition, ModifiersState),
{
self.on_cursor_move = Some(self.add_event(
"pointermove",
move |event: PointerEvent| {
handler(
utils::mouse_position(&event),
utils::mouse_modifiers(&event),
);
},
));
self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| {
handler(
event.pointer_id(),
event::mouse_position(&event),
event::mouse_modifiers(&event),
);
}));
}
pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
{
self.on_mouse_wheel = Some(self.add_event("wheel", move |event: WheelEvent| {
if let Some(delta) = event::mouse_scroll_delta(&event) {
handler(0, delta, event::mouse_modifiers(&event));
}
}));
}
pub fn on_fullscreen_change<F>(&mut self, mut handler: F)
@@ -262,11 +248,7 @@ impl Canvas {
Some(self.add_event("fullscreenchange", move |_: Event| handler()));
}
fn add_event<E, F>(
&self,
event_name: &str,
mut handler: F,
) -> Closure<dyn FnMut(E)>
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,
F: 'static + FnMut(E),
@@ -291,11 +273,7 @@ impl Canvas {
// The difference between add_event and add_user_event is that the latter has a special meaning
// for browser security. A user event is a deliberate action by the user (like a mouse or key
// press) and is the only time things like a fullscreen request may be successfully completed.)
fn add_user_event<E, F>(
&self,
event_name: &str,
mut handler: F,
) -> Closure<dyn FnMut(E)>
fn add_user_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,
F: 'static + FnMut(E),
@@ -303,19 +281,16 @@ impl Canvas {
let wants_fullscreen = self.wants_fullscreen.clone();
let canvas = self.raw.clone();
self.add_event(
event_name,
move |event: E| {
handler(event);
self.add_event(event_name, move |event: E| {
handler(event);
if *wants_fullscreen.borrow() {
canvas
.request_fullscreen()
.expect("Failed to enter fullscreen");
*wants_fullscreen.borrow_mut() = false;
}
},
)
if *wants_fullscreen.borrow() {
canvas
.request_fullscreen()
.expect("Failed to enter fullscreen");
*wants_fullscreen.borrow_mut() = false;
}
})
}
pub fn request_fullscreen(&self) {

View File

@@ -1,9 +1,8 @@
use crate::dpi::LogicalPosition;
use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode};
use crate::platform_impl::platform;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use std::convert::TryInto;
use web_sys::{Gamepad, GamepadButton, GamepadMappingType, KeyboardEvent, MouseEvent, WheelEvent};
use web_sys::{KeyboardEvent, MouseEvent, WheelEvent};
pub fn mouse_button(event: &MouseEvent) -> MouseButton {
match event.button() {
@@ -15,12 +14,12 @@ pub fn mouse_button(event: &MouseEvent) -> MouseButton {
}
pub fn mouse_modifiers(event: &MouseEvent) -> ModifiersState {
ModifiersState {
shift: event.shift_key(),
ctrl: event.ctrl_key(),
alt: event.alt_key(),
logo: event.meta_key(),
}
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, event.shift_key());
m.set(ModifiersState::CTRL, event.ctrl_key());
m.set(ModifiersState::ALT, event.alt_key());
m.set(ModifiersState::LOGO, event.meta_key());
m
}
pub fn mouse_position(event: &MouseEvent) -> LogicalPosition {
@@ -30,10 +29,15 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition {
}
}
pub fn mouse_scroll_delta(event: &WheelEvent) -> (f64, f64) {
pub fn mouse_scroll_delta(event: &WheelEvent) -> Option<MouseScrollDelta> {
let x = event.delta_x();
let y = event.delta_y();
(x, y)
match event.delta_mode() {
WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
WheelEvent::DOM_DELTA_PIXEL => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })),
_ => None,
}
}
pub fn scan_code(event: &KeyboardEvent) -> ScanCode {
@@ -207,12 +211,12 @@ pub fn virtual_key_code(event: &KeyboardEvent) -> Option<VirtualKeyCode> {
}
pub fn keyboard_modifiers(event: &KeyboardEvent) -> ModifiersState {
ModifiersState {
shift: event.shift_key(),
ctrl: event.ctrl_key(),
alt: event.alt_key(),
logo: event.meta_key(),
}
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, event.shift_key());
m.set(ModifiersState::CTRL, event.ctrl_key());
m.set(ModifiersState::ALT, event.alt_key());
m.set(ModifiersState::LOGO, event.meta_key());
m
}
pub fn codepoint(event: &KeyboardEvent) -> char {
@@ -221,44 +225,3 @@ pub fn codepoint(event: &KeyboardEvent) -> char {
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
event.key().chars().next().unwrap()
}
pub fn create_mapping(raw: &Gamepad) -> platform::device::gamepad::Mapping {
match raw.mapping() {
GamepadMappingType::Standard => {
let mut buttons = [false; 16];
let mut axes = [0.0; 6];
let gbuttons = raw.buttons();
for index in 0..buttons.len() {
let button: GamepadButton = gbuttons.get(index as u32).into();
buttons[index] = button.pressed();
}
let gaxes = raw.axes();
for index in 0..axes.len() {
let axe: f64 = gaxes.get(index as u32).as_f64().unwrap_or(0.0);
axes[index] = axe;
}
platform::device::gamepad::Mapping::Standard { buttons, axes }
}
_ => {
let mut buttons: Vec<bool> = Vec::new();
let mut axes: Vec<f64> = Vec::new();
let gbuttons = raw.buttons();
for index in 0..gbuttons.length() {
let button: GamepadButton = gbuttons.get(index as u32).into();
buttons.push(button.pressed());
}
let gaxes = raw.axes();
for index in 0..gaxes.length() {
let axe: f64 = gaxes.get(index as u32).as_f64().unwrap_or(0.0);
axes.push(axe);
}
platform::device::gamepad::Mapping::NoMapping { buttons, axes }
}
}
}

View File

@@ -1,75 +0,0 @@
use super::utils;
use crate::platform_impl::platform::device;
use std::cmp::PartialEq;
#[derive(Debug)]
pub struct Gamepad {
pub(crate) index: i32,
pub(crate) raw: web_sys::Gamepad,
pub(crate) mapping: device::gamepad::Mapping,
}
impl Gamepad {
pub fn new(raw: web_sys::Gamepad) -> Self {
let mapping = utils::create_mapping(&raw);
Self {
index: raw.index() as i32,
raw,
mapping,
}
}
// An integer that is auto-incremented to be unique for each device
// currently connected to the system.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/index
pub fn index(&self) -> i32 {
self.raw.index() as i32
}
// A string containing some information about the controller.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id
pub fn id(&self) -> String {
self.raw.id()
}
// A boolean indicating whether the gamepad is still connected to the system.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/connected
pub fn connected(&self) -> bool {
self.raw.connected()
}
// An array containing GamepadHapticActuator objects,
// each of which represents haptic feedback hardware available on the controller.
// https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/hapticActuators
pub fn vibrate(&self, value: f64, duration: f64) {
for actuator in self.raw.haptic_actuators().values() {
actuator.ok().and_then(|a| {
let actuator: web_sys::GamepadHapticActuator = a.into();
match actuator.type_() {
web_sys::GamepadHapticActuatorType::Vibration => {
actuator.pulse(value, duration).ok()
}
_ => None,
}
});
}
}
}
impl Clone for Gamepad {
fn clone(&self) -> Self {
Self {
index: self.index,
raw: self.raw.clone(),
mapping: self.mapping.clone(),
}
}
}
impl PartialEq for Gamepad {
#[inline(always)]
fn eq(&self, othr: &Self) -> bool {
self.raw.index() == othr.raw.index()
}
}

View File

@@ -1,11 +1,9 @@
mod canvas;
pub mod gamepad;
mod event;
mod timeout;
mod utils;
pub mod window;
pub use canvas::Canvas;
pub use timeout::Timeout;
pub use self::canvas::Canvas;
pub use self::timeout::Timeout;
use crate::dpi::LogicalSize;
use crate::platform::web::WindowExtWebSys;
@@ -70,16 +68,3 @@ pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool {
None => false,
}
}
pub fn get_gamepads() -> impl Iterator<Item = gamepad::Gamepad> {
let mut gamepads: Vec<gamepad::Gamepad> = Vec::new();
let web_gamepads = web_sys::window().unwrap().navigator().get_gamepads().ok().unwrap();
for index in 0..web_gamepads.length() {
let jsvalue = web_gamepads.get(index);
if !jsvalue.is_null() {
let gamepad: web_sys::Gamepad = jsvalue.into();
gamepads.push(gamepad::Gamepad::new(gamepad));
}
}
gamepads.into_iter()
}

View File

@@ -1,88 +0,0 @@
use super::gamepad;
use crate::error::OsError as RootOE;
use crate::platform_impl::OsError;
use std::{cell::RefCell, rc::Rc};
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::GamepadEvent;
#[derive(Debug)]
pub struct Shared(pub Rc<RefCell<Window>>);
#[derive(Debug)]
pub struct Window {
raw: web_sys::Window,
on_gamepad_connected: Option<Closure<dyn FnMut(GamepadEvent)>>,
on_gamepad_disconnected: Option<Closure<dyn FnMut(GamepadEvent)>>,
}
impl Shared {
pub fn create() -> Result<Self, RootOE> {
let global = Window::create()?;
Ok(Shared(Rc::new(RefCell::new(global))))
}
}
impl Clone for Shared {
fn clone(&self) -> Self {
Shared(self.0.clone())
}
}
impl Window {
pub fn create() -> Result<Self, RootOE> {
let raw =
web_sys::window().ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?;
Ok(Window {
raw,
on_gamepad_connected: None,
on_gamepad_disconnected: None,
})
}
pub fn on_gamepad_connected<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(gamepad::Gamepad),
{
self.on_gamepad_connected = Some(self.add_event(
"gamepadconnected",
move |event: GamepadEvent| {
let gamepad = event
.gamepad()
.expect("[gamepadconnected] expected gamepad");
handler(gamepad::Gamepad::new(gamepad));
},
))
}
pub fn on_gamepad_disconnected<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(gamepad::Gamepad),
{
self.on_gamepad_disconnected = Some(self.add_event(
"gamepaddisconnected",
move |event: GamepadEvent| {
let gamepad = event
.gamepad()
.expect("[gamepaddisconnected] expected gamepad");
handler(gamepad::Gamepad::new(gamepad));
},
))
}
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,
F: 'static + FnMut(E),
{
let closure = Closure::wrap(Box::new(move |event: E| {
handler(event);
}) as Box<dyn FnMut(E)>);
self.raw
.add_event_listener_with_callback(event_name, &closure.as_ref().unchecked_ref())
.expect("Failed to add event listener with callback");
closure
}
}

View File

@@ -34,7 +34,6 @@ impl Window {
let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id)));
target.register_global_events()?;
target.register(&mut canvas, id);
let window = Window {
@@ -200,6 +199,11 @@ impl Window {
}
}
#[inline]
pub fn set_minimized(&self, _minimized: bool) {
// Intentionally a no-op, as canvases cannot be 'minimized'
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// Intentionally a no-op, as canvases cannot be 'maximized'

View File

@@ -0,0 +1,216 @@
/// This is a simple implementation of support for Windows Dark Mode,
/// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use winapi::{
shared::{
basetsd::SIZE_T,
minwindef::{BOOL, DWORD, UINT, ULONG, WORD},
ntdef::{LPSTR, NTSTATUS, NT_SUCCESS, PVOID, WCHAR},
windef::HWND,
},
um::{libloaderapi, uxtheme, winuser},
};
lazy_static! {
static ref WIN10_BUILD_VERSION: Option<DWORD> = {
// FIXME: RtlGetVersion is a documented windows API,
// should be part of winapi!
#[allow(non_snake_case)]
#[repr(C)]
struct OSVERSIONINFOW {
dwOSVersionInfoSize: ULONG,
dwMajorVersion: ULONG,
dwMinorVersion: ULONG,
dwBuildNumber: ULONG,
dwPlatformId: ULONG,
szCSDVersion: [WCHAR; 128],
}
type RtlGetVersion = unsafe extern "system" fn (*mut OSVERSIONINFOW) -> NTSTATUS;
let handle = get_function!("ntdll.dll", RtlGetVersion);
if let Some(rtl_get_version) = handle {
unsafe {
let mut vi = OSVERSIONINFOW {
dwOSVersionInfoSize: 0,
dwMajorVersion: 0,
dwMinorVersion: 0,
dwBuildNumber: 0,
dwPlatformId: 0,
szCSDVersion: [0; 128],
};
let status = (rtl_get_version)(&mut vi as _);
assert!(NT_SUCCESS(status));
if vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 {
Some(vi.dwBuildNumber)
} else {
None
}
}
} else {
None
}
};
static ref DARK_MODE_SUPPORTED: bool = {
// We won't try to do anything for windows versions < 17763
// (Windows 10 October 2018 update)
match *WIN10_BUILD_VERSION {
Some(v) => v >= 17763,
None => false
}
};
static ref DARK_THEME_NAME: Vec<u16> = widestring("DarkMode_Explorer");
static ref LIGHT_THEME_NAME: Vec<u16> = widestring("");
}
/// Attempt to set dark mode on a window, if necessary.
/// Returns true if dark mode was set, false if not.
pub fn try_dark_mode(hwnd: HWND) -> bool {
if *DARK_MODE_SUPPORTED {
let is_dark_mode = should_use_dark_mode();
let theme_name = if is_dark_mode {
DARK_THEME_NAME.as_ptr()
} else {
LIGHT_THEME_NAME.as_ptr()
};
unsafe {
assert_eq!(
0,
uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null())
);
set_dark_mode_for_window(hwnd, is_dark_mode)
}
is_dark_mode
} else {
false
}
}
fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) {
// Uses Windows undocumented API SetWindowCompositionAttribute,
// as seen in win32-darkmode example linked at top of file.
type SetWindowCompositionAttribute =
unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL;
#[allow(non_snake_case)]
type WINDOWCOMPOSITIONATTRIB = u32;
const WCA_USEDARKMODECOLORS: WINDOWCOMPOSITIONATTRIB = 26;
#[allow(non_snake_case)]
#[repr(C)]
struct WINDOWCOMPOSITIONATTRIBDATA {
Attrib: WINDOWCOMPOSITIONATTRIB,
pvData: PVOID,
cbData: SIZE_T,
}
lazy_static! {
static ref SET_WINDOW_COMPOSITION_ATTRIBUTE: Option<SetWindowCompositionAttribute> =
get_function!("user32.dll", SetWindowCompositionAttribute);
}
if let Some(set_window_composition_attribute) = *SET_WINDOW_COMPOSITION_ATTRIBUTE {
unsafe {
// SetWindowCompositionAttribute needs a bigbool (i32), not bool.
let mut is_dark_mode_bigbool = is_dark_mode as BOOL;
let mut data = WINDOWCOMPOSITIONATTRIBDATA {
Attrib: WCA_USEDARKMODECOLORS,
pvData: &mut is_dark_mode_bigbool as *mut _ as _,
cbData: std::mem::size_of_val(&is_dark_mode_bigbool) as _,
};
assert_eq!(
1,
set_window_composition_attribute(hwnd, &mut data as *mut _)
);
}
}
}
fn should_use_dark_mode() -> bool {
should_apps_use_dark_mode() && !is_high_contrast()
}
fn should_apps_use_dark_mode() -> bool {
type ShouldAppsUseDarkMode = unsafe extern "system" fn() -> bool;
lazy_static! {
static ref SHOULD_APPS_USE_DARK_MODE: Option<ShouldAppsUseDarkMode> = {
unsafe {
const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: WORD = 132;
let module = libloaderapi::LoadLibraryA("uxtheme.dll\0".as_ptr() as _);
if module.is_null() {
return None;
}
let handle = libloaderapi::GetProcAddress(
module,
winuser::MAKEINTRESOURCEA(UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL),
);
if handle.is_null() {
None
} else {
Some(std::mem::transmute(handle))
}
}
};
}
SHOULD_APPS_USE_DARK_MODE
.map(|should_apps_use_dark_mode| unsafe { (should_apps_use_dark_mode)() })
.unwrap_or(false)
}
// FIXME: This definition was missing from winapi. Can remove from
// here and use winapi once the following PR is released:
// https://github.com/retep998/winapi-rs/pull/815
#[repr(C)]
#[allow(non_snake_case)]
struct HIGHCONTRASTA {
cbSize: UINT,
dwFlags: DWORD,
lpszDefaultScheme: LPSTR,
}
const HCF_HIGHCONTRASTON: DWORD = 1;
fn is_high_contrast() -> bool {
let mut hc = HIGHCONTRASTA {
cbSize: 0,
dwFlags: 0,
lpszDefaultScheme: std::ptr::null_mut(),
};
let ok = unsafe {
winuser::SystemParametersInfoA(
winuser::SPI_GETHIGHCONTRAST,
std::mem::size_of_val(&hc) as _,
&mut hc as *mut _ as _,
0,
)
};
(ok > 0) && ((HCF_HIGHCONTRASTON & hc.dwFlags) == 1)
}
fn widestring(src: &'static str) -> Vec<u16> {
OsStr::new(src)
.encode_wide()
.chain(Some(0).into_iter())
.collect()
}

View File

@@ -17,16 +17,84 @@ fn key_pressed(vkey: c_int) -> bool {
}
pub fn get_key_mods() -> ModifiersState {
let mut mods = ModifiersState::default();
let filter_out_altgr = layout_uses_altgr() && key_pressed(winuser::VK_RMENU);
mods.shift = key_pressed(winuser::VK_SHIFT);
mods.ctrl = key_pressed(winuser::VK_CONTROL) && !filter_out_altgr;
mods.alt = key_pressed(winuser::VK_MENU) && !filter_out_altgr;
mods.logo = key_pressed(winuser::VK_LWIN) || key_pressed(winuser::VK_RWIN);
let mut mods = ModifiersState::empty();
mods.set(ModifiersState::SHIFT, key_pressed(winuser::VK_SHIFT));
mods.set(
ModifiersState::CTRL,
key_pressed(winuser::VK_CONTROL) && !filter_out_altgr,
);
mods.set(
ModifiersState::ALT,
key_pressed(winuser::VK_MENU) && !filter_out_altgr,
);
mods.set(
ModifiersState::LOGO,
key_pressed(winuser::VK_LWIN) || key_pressed(winuser::VK_RWIN),
);
mods
}
bitflags! {
#[derive(Default)]
pub struct ModifiersStateSide: u32 {
const LSHIFT = 0b010 << 0;
const RSHIFT = 0b001 << 0;
const LCTRL = 0b010 << 3;
const RCTRL = 0b001 << 3;
const LALT = 0b010 << 6;
const RALT = 0b001 << 6;
const LLOGO = 0b010 << 9;
const RLOGO = 0b001 << 9;
}
}
impl ModifiersStateSide {
pub fn filter_out_altgr(&self) -> ModifiersStateSide {
match layout_uses_altgr() && self.contains(Self::RALT) {
false => *self,
true => *self & !(Self::LCTRL | Self::RCTRL | Self::LALT | Self::RALT),
}
}
}
impl From<ModifiersStateSide> for ModifiersState {
fn from(side: ModifiersStateSide) -> Self {
let mut state = ModifiersState::default();
state.set(
Self::SHIFT,
side.intersects(ModifiersStateSide::LSHIFT | ModifiersStateSide::RSHIFT),
);
state.set(
Self::CTRL,
side.intersects(ModifiersStateSide::LCTRL | ModifiersStateSide::RCTRL),
);
state.set(
Self::ALT,
side.intersects(ModifiersStateSide::LALT | ModifiersStateSide::RALT),
);
state.set(
Self::LOGO,
side.intersects(ModifiersStateSide::LLOGO | ModifiersStateSide::RLOGO),
);
state
}
}
pub fn get_pressed_keys() -> impl Iterator<Item = c_int> {
let mut keyboard_state = vec![0u8; 256];
unsafe { winuser::GetKeyboardState(keyboard_state.as_mut_ptr()) };
keyboard_state
.into_iter()
.enumerate()
.filter(|(_, p)| (*p & (1 << 7)) != 0) // whether or not a key is pressed is communicated via the high-order bit
.map(|(i, _)| i as c_int)
}
unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option<char> {
let mut unicode_bytes = [0u16; 5];
let len = winuser::ToUnicodeEx(
@@ -39,7 +107,7 @@ unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option<c
hkl,
);
if len >= 1 {
char::decode_utf16(unicode_bytes.into_iter().cloned())
char::decode_utf16(unicode_bytes.iter().cloned())
.next()
.and_then(|c| c.ok())
} else {
@@ -177,8 +245,8 @@ pub fn vkey_to_winit_vkey(vkey: c_int) -> Option<VirtualKeyCode> {
0x58 => Some(VirtualKeyCode::X),
0x59 => Some(VirtualKeyCode::Y),
0x5A => Some(VirtualKeyCode::Z),
//winuser::VK_LWIN => Some(VirtualKeyCode::Lwin),
//winuser::VK_RWIN => Some(VirtualKeyCode::Rwin),
winuser::VK_LWIN => Some(VirtualKeyCode::LWin),
winuser::VK_RWIN => Some(VirtualKeyCode::RWin),
winuser::VK_APPS => Some(VirtualKeyCode::Apps),
winuser::VK_SLEEP => Some(VirtualKeyCode::Sleep),
winuser::VK_NUMPAD0 => Some(VirtualKeyCode::Numpad0),

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,411 @@
use std::{any::Any, cell::RefCell, collections::VecDeque, mem, panic, ptr, rc::Rc, time::Instant};
use winapi::{shared::windef::HWND, um::winuser};
use crate::{
event::{Event, StartCause},
event_loop::ControlFlow,
platform_impl::platform::event_loop::EventLoop,
window::WindowId,
};
pub(crate) type EventLoopRunnerShared<T> = Rc<ELRShared<T>>;
pub(crate) struct ELRShared<T> {
runner: RefCell<Option<EventLoopRunner<T>>>,
buffer: RefCell<VecDeque<Event<T>>>,
redraw_buffer: Rc<RefCell<VecDeque<WindowId>>>,
}
struct EventLoopRunner<T> {
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 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<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;
}
}
// 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 call_event_handler(&self, event: Event<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<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)]
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),
/// 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,
}
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),
{
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,
}
}
fn take_panic_error(&mut 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),
}
}
};
}
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,
);
}
}
// 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();
// 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,
}))
}
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)),
}
}
match (self.runner_state, &event) {
(RunnerState::HandlingRedraw, Event::RedrawRequested(_)) => {
self.call_event_handler(event)
}
(_, 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);
}
}
}
fn flush_redraws(&mut 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)),
None => break,
}
}
}
fn events_cleared(&mut self) {
match self.runner_state {
// If we were handling events, send the EventsCleared message.
RunnerState::HandlingEvents => {
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());
}
// 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)
}
}
}
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);
} else {
(*event_handler)(event, &mut ControlFlow::Exit);
}
}))
.err();
}
}
}

View File

@@ -1,89 +0,0 @@
use std::sync::Weak;
use winapi::um::winnt::HANDLE;
use crate::{
event::device::{BatteryLevel, GamepadEvent, RumbleError},
platform_impl::platform::{
raw_input::{get_raw_input_device_name, RawGamepad},
xinput::{self, XInputGamepad, XInputGamepadShared},
},
};
#[derive(Debug)]
pub enum GamepadType {
Raw(RawGamepad),
XInput(XInputGamepad),
}
#[derive(Clone)]
pub enum GamepadShared {
Raw(()),
XInput(Weak<XInputGamepadShared>),
Dummy,
}
#[derive(Debug)]
pub struct Gamepad {
handle: HANDLE,
backend: GamepadType,
}
impl Gamepad {
pub fn new(handle: HANDLE) -> Option<Self> {
// TODO: Verify that this is an HID device
let name = get_raw_input_device_name(handle)?;
xinput::id_from_name(&name)
.and_then(XInputGamepad::new)
.map(GamepadType::XInput)
.or_else(|| RawGamepad::new(handle).map(GamepadType::Raw))
.map(|backend| Gamepad { handle, backend })
}
pub unsafe fn update_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> {
match self.backend {
GamepadType::Raw(ref mut gamepad) => gamepad.update_state(raw_input_report),
GamepadType::XInput(ref mut gamepad) => gamepad.update_state(),
}
}
pub fn get_gamepad_events(&self) -> Vec<GamepadEvent> {
match self.backend {
GamepadType::Raw(ref gamepad) => gamepad.get_gamepad_events(),
GamepadType::XInput(ref gamepad) => gamepad.get_gamepad_events(),
}
}
pub fn shared_data(&self) -> GamepadShared {
match self.backend {
GamepadType::Raw(_) => GamepadShared::Raw(()),
GamepadType::XInput(ref gamepad) => GamepadShared::XInput(gamepad.shared_data()),
}
}
}
impl GamepadShared {
pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), RumbleError> {
match self {
GamepadShared::Raw(_) | GamepadShared::Dummy => Ok(()),
GamepadShared::XInput(ref data) => data
.upgrade()
.map(|r| r.rumble(left_speed, right_speed))
.unwrap_or(Err(RumbleError::DeviceNotConnected)),
}
}
pub fn port(&self) -> Option<u8> {
match self {
GamepadShared::Raw(_) | GamepadShared::Dummy => None,
GamepadShared::XInput(ref data) => data.upgrade().map(|r| r.port()),
}
}
pub fn battery_level(&self) -> Option<BatteryLevel> {
match self {
GamepadShared::Raw(_) | GamepadShared::Dummy => None,
GamepadShared::XInput(ref data) => data.upgrade().and_then(|r| r.battery_level()),
}
}
}

View File

@@ -1,35 +1,14 @@
#![cfg(target_os = "windows")]
#[macro_use]
mod util;
mod dpi;
mod drop_handler;
mod event;
mod event_loop;
mod gamepad;
mod icon;
mod monitor;
mod raw_input;
mod window;
mod window_state;
mod xinput;
use std::{
cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd},
fmt,
hash::{Hash, Hasher},
ptr,
};
use winapi::{self, shared::windef::HWND, um::winnt::HANDLE};
use winapi::{self, shared::windef::HWND};
pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
gamepad::GamepadShared,
monitor::{MonitorHandle, VideoMode},
window::Window,
};
use crate::{event::device, window::Icon};
use crate::{event::DeviceId as RootDeviceId, window::Icon};
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes {
@@ -47,6 +26,34 @@ pub struct Cursor(pub *const winapi::ctypes::wchar_t);
unsafe impl Send for Cursor {}
unsafe impl Sync for Cursor {}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(u32);
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId(0)
}
}
impl DeviceId {
pub fn persistent_identifier(&self) -> Option<String> {
if self.0 != 0 {
raw_input::get_raw_input_device_name(self.0 as _)
} else {
None
}
}
}
// Constant device ID, to be removed when this backend is updated to report real device IDs.
const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId(0));
fn wrap_device_id(id: u32) -> RootDeviceId {
RootDeviceId(DeviceId(id))
}
pub type OsError = std::io::Error;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(HWND);
unsafe impl Send for WindowId {}
@@ -54,142 +61,21 @@ unsafe impl Sync for WindowId {}
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId(ptr::null_mut())
use std::ptr::null_mut;
WindowId(null_mut())
}
}
macro_rules! device_id {
($name:ident, $enumerate:ident) => {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct $name(HANDLE);
unsafe impl Send for $name {}
unsafe impl Sync for $name {}
impl $name {
pub unsafe fn dummy() -> Self {
Self(ptr::null_mut())
}
pub fn persistent_identifier(&self) -> Option<String> {
raw_input::get_raw_input_device_name(self.0)
}
pub fn is_connected(&self) -> bool {
raw_input::get_raw_input_device_info(self.0).is_some()
}
#[inline(always)]
pub fn handle(&self) -> HANDLE {
self.0
}
pub fn enumerate<'a, T>(
event_loop: &'a EventLoop<T>,
) -> impl 'a + Iterator<Item = device::$name> {
event_loop.$enumerate()
}
}
impl From<$name> for device::$name {
fn from(platform_id: $name) -> Self {
Self(platform_id)
}
}
};
}
device_id!(MouseId, mouses);
device_id!(KeyboardId, keyboards);
device_id!(HidId, hids);
#[derive(Clone)]
pub(crate) struct GamepadHandle {
handle: HANDLE,
shared_data: GamepadShared,
}
pub type OsError = std::io::Error;
unsafe impl Send for GamepadHandle where GamepadShared: Send {}
unsafe impl Sync for GamepadHandle where GamepadShared: Sync {}
impl GamepadHandle {
pub unsafe fn dummy() -> Self {
Self {
handle: ptr::null_mut(),
shared_data: GamepadShared::Dummy,
}
}
pub fn persistent_identifier(&self) -> Option<String> {
raw_input::get_raw_input_device_name(self.handle)
}
pub fn is_connected(&self) -> bool {
raw_input::get_raw_input_device_info(self.handle).is_some()
}
#[inline(always)]
pub fn handle(&self) -> HANDLE {
self.handle
}
pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), device::RumbleError> {
self.shared_data.rumble(left_speed, right_speed)
}
pub fn port(&self) -> Option<u8> {
self.shared_data.port()
}
pub fn battery_level(&self) -> Option<device::BatteryLevel> {
self.shared_data.battery_level()
}
pub fn enumerate<'a, T>(
event_loop: &'a EventLoop<T>,
) -> impl 'a + Iterator<Item = device::GamepadHandle> {
event_loop.gamepads()
}
}
impl From<GamepadHandle> for device::GamepadHandle {
fn from(platform_id: GamepadHandle) -> Self {
Self(platform_id)
}
}
impl fmt::Debug for GamepadHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_tuple("GamepadHandle").field(&self.handle).finish()
}
}
impl Eq for GamepadHandle {}
impl PartialEq for GamepadHandle {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.handle == other.handle
}
}
impl Ord for GamepadHandle {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
self.handle.cmp(&other.handle)
}
}
impl PartialOrd for GamepadHandle {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.handle.partial_cmp(&other.handle)
}
}
impl Hash for GamepadHandle {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.handle.hash(state);
}
}
#[macro_use]
mod util;
mod dark_mode;
mod dpi;
mod drop_handler;
mod event;
mod event_loop;
mod icon;
mod monitor;
mod raw_input;
mod window;
mod window_state;

View File

@@ -1,43 +1,27 @@
use std::{
cmp::max,
fmt,
mem::{self, size_of},
ptr, slice,
ptr,
};
use winapi::{
ctypes::wchar_t,
shared::{
hidpi::{
HidP_GetButtonCaps, HidP_GetCaps, HidP_GetScaledUsageValue, HidP_GetUsageValue,
HidP_GetUsagesEx, HidP_GetValueCaps, HidP_Input, HIDP_STATUS_SUCCESS, HIDP_VALUE_CAPS,
PHIDP_PREPARSED_DATA,
},
hidusage::{
HID_USAGE_GENERIC_GAMEPAD, HID_USAGE_GENERIC_JOYSTICK, HID_USAGE_GENERIC_KEYBOARD,
HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC,
},
minwindef::{INT, TRUE, UINT, USHORT},
hidusage::{HID_USAGE_GENERIC_KEYBOARD, HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC},
minwindef::{TRUE, UINT, USHORT},
windef::HWND,
},
um::{
winnt::{HANDLE, PCHAR},
winnt::HANDLE,
winuser::{
self, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST, RAWINPUTHEADER,
RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDI_DEVICEINFO, RIDI_DEVICENAME, RIDI_PREPARSEDDATA,
RID_DEVICE_INFO, RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE,
RID_INPUT, RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDI_DEVICEINFO, RIDI_DEVICENAME, RID_DEVICE_INFO,
RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, RID_INPUT,
RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
},
},
};
use crate::{
event::{
device::{GamepadAxis, GamepadEvent},
ElementState,
},
platform_impl::platform::util,
};
use crate::{event::ElementState, platform_impl::platform::util};
#[allow(dead_code)]
pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
@@ -68,6 +52,7 @@ pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
Some(buffer)
}
#[allow(dead_code)]
pub enum RawDeviceInfo {
Mouse(RID_DEVICE_INFO_MOUSE),
Keyboard(RID_DEVICE_INFO_KEYBOARD),
@@ -87,27 +72,28 @@ impl From<RID_DEVICE_INFO> for RawDeviceInfo {
}
}
#[allow(dead_code)]
pub fn get_raw_input_device_info(handle: HANDLE) -> Option<RawDeviceInfo> {
let mut info: RID_DEVICE_INFO = unsafe { mem::zeroed() };
let info_size = size_of::<RID_DEVICE_INFO>() as UINT;
info.cbSize = info_size;
let mut data_size = info_size;
let mut minimum_size = 0;
let status = unsafe {
winuser::GetRawInputDeviceInfoW(
handle,
RIDI_DEVICEINFO,
&mut info as *mut _ as _,
&mut data_size,
&mut minimum_size,
)
} as INT;
};
if status <= 0 {
if status == UINT::max_value() || status == 0 {
return None;
}
debug_assert_eq!(info_size, status as _);
debug_assert_eq!(info_size, status);
Some(info.into())
}
@@ -144,43 +130,6 @@ pub fn get_raw_input_device_name(handle: HANDLE) -> Option<String> {
Some(util::wchar_to_string(&name))
}
pub fn get_raw_input_pre_parse_info(handle: HANDLE) -> Option<Vec<u8>> {
let mut minimum_size = 0;
let status = unsafe {
winuser::GetRawInputDeviceInfoW(
handle,
RIDI_PREPARSEDDATA,
ptr::null_mut(),
&mut minimum_size,
)
};
if status != 0 {
return None;
}
let mut buf: Vec<u8> = Vec::with_capacity(minimum_size as _);
let status = unsafe {
winuser::GetRawInputDeviceInfoW(
handle,
RIDI_PREPARSEDDATA,
buf.as_ptr() as _,
&mut minimum_size,
)
};
if status == UINT::max_value() || status == 0 {
return None;
}
debug_assert_eq!(minimum_size, status);
unsafe { buf.set_len(minimum_size as _) };
Some(buf)
}
pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool {
let device_size = size_of::<RAWINPUTDEVICE>() as UINT;
@@ -191,12 +140,12 @@ pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool {
success == TRUE
}
pub fn register_for_raw_input(window_handle: HWND) -> bool {
// `RIDEV_DEVNOTIFY`: receive hotplug events
// `RIDEV_INPUTSINK`: receive events even if we're not in the foreground
pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> bool {
// RIDEV_DEVNOTIFY: receive hotplug events
// RIDEV_INPUTSINK: receive events even if we're not in the foreground
let flags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
let devices: [RAWINPUTDEVICE; 5] = [
let devices: [RAWINPUTDEVICE; 2] = [
RAWINPUTDEVICE {
usUsagePage: HID_USAGE_PAGE_GENERIC,
usUsage: HID_USAGE_GENERIC_MOUSE,
@@ -209,182 +158,27 @@ pub fn register_for_raw_input(window_handle: HWND) -> bool {
dwFlags: flags,
hwndTarget: window_handle,
},
RAWINPUTDEVICE {
usUsagePage: HID_USAGE_PAGE_GENERIC,
usUsage: HID_USAGE_GENERIC_JOYSTICK,
dwFlags: flags,
hwndTarget: window_handle,
},
RAWINPUTDEVICE {
usUsagePage: HID_USAGE_PAGE_GENERIC,
usUsage: HID_USAGE_GENERIC_GAMEPAD,
dwFlags: flags,
hwndTarget: window_handle,
},
RAWINPUTDEVICE {
usUsagePage: HID_USAGE_PAGE_GENERIC,
usUsage: 0x08, // multi-axis
dwFlags: flags,
hwndTarget: window_handle,
},
];
register_raw_input_devices(&devices)
}
pub enum RawInputData {
Mouse {
device_handle: HANDLE,
raw_mouse: winuser::RAWMOUSE,
},
Keyboard {
device_handle: HANDLE,
raw_keyboard: winuser::RAWKEYBOARD,
},
Hid {
device_handle: HANDLE,
raw_hid: RawHidData,
},
}
pub struct RawHidData {
pub hid_input_size: u32,
pub hid_input_count: u32,
pub raw_input: Box<[u8]>,
}
pub fn get_raw_input_data(handle: HRAWINPUT) -> Option<RawInputData> {
let mut data_size = 0;
pub fn get_raw_input_data(handle: HRAWINPUT) -> Option<RAWINPUT> {
let mut data: RAWINPUT = unsafe { mem::zeroed() };
let mut data_size = size_of::<RAWINPUT>() as UINT;
let header_size = size_of::<RAWINPUTHEADER>() as UINT;
// There are two classes of data this function can output:
// - Raw mouse and keyboard data
// - Raw HID data
// The first class (mouse and keyboard) is always going to write data formatted like the
// `RAWINPUT` struct, with no other data, and can be placed on the stack into `RAWINPUT`.
// The second class (raw HID data) writes the struct, and then a buffer of data appended to
// the end. That data needs to be heap-allocated so we can store all of it.
unsafe {
let status = unsafe {
winuser::GetRawInputData(
handle,
RID_INPUT,
ptr::null_mut(),
&mut data as *mut _ as _,
&mut data_size,
header_size,
)
};
let (status, data): (INT, RawInputData);
if data_size <= size_of::<RAWINPUT>() as UINT {
// Since GetRawInputData is going to write... well, a buffer that's `RAWINPUT` bytes long
// and structured like `RAWINPUT`, we're just going to cut to the chase and write directly into
// a `RAWINPUT` struct.
let mut rawinput_data: RAWINPUT = unsafe { mem::zeroed() };
status = unsafe {
winuser::GetRawInputData(
handle,
RID_INPUT,
&mut rawinput_data as *mut RAWINPUT as *mut _,
&mut data_size,
header_size,
)
} as INT;
assert_ne!(-1, status);
let device_handle = rawinput_data.header.hDevice;
data = match rawinput_data.header.dwType {
winuser::RIM_TYPEMOUSE => {
let raw_mouse = unsafe { rawinput_data.data.mouse().clone() };
RawInputData::Mouse {
device_handle,
raw_mouse,
}
}
winuser::RIM_TYPEKEYBOARD => {
let raw_keyboard = unsafe { rawinput_data.data.keyboard().clone() };
RawInputData::Keyboard {
device_handle,
raw_keyboard,
}
}
winuser::RIM_TYPEHID => {
let hid_data = unsafe { rawinput_data.data.hid() };
let buf_len = hid_data.dwSizeHid as usize * hid_data.dwCount as usize;
let data = unsafe { slice::from_raw_parts(hid_data.bRawData.as_ptr(), buf_len) };
RawInputData::Hid {
device_handle,
raw_hid: RawHidData {
hid_input_size: hid_data.dwSizeHid,
hid_input_count: hid_data.dwCount,
raw_input: Box::from(data),
},
}
}
_ => unreachable!(),
};
} else {
let mut buf = vec![0u8; data_size as usize];
status = unsafe {
winuser::GetRawInputData(
handle,
RID_INPUT,
buf.as_mut_ptr() as *mut _,
&mut data_size,
header_size,
)
} as INT;
let rawinput_data = buf.as_ptr() as *const RAWINPUT;
let device_handle = unsafe { (&*rawinput_data).header.hDevice };
data = match unsafe { *rawinput_data }.header.dwType {
winuser::RIM_TYPEMOUSE => {
let raw_mouse = unsafe { (&*rawinput_data).data.mouse().clone() };
RawInputData::Mouse {
device_handle,
raw_mouse,
}
}
winuser::RIM_TYPEKEYBOARD => {
let raw_keyboard = unsafe { (&*rawinput_data).data.keyboard().clone() };
RawInputData::Keyboard {
device_handle,
raw_keyboard,
}
}
winuser::RIM_TYPEHID => {
let hid_data: winuser::RAWHID = unsafe { (&*rawinput_data).data.hid().clone() };
let hid_data_index = {
let hid_data_start =
unsafe { &((&*rawinput_data).data.hid().bRawData) } as *const _;
hid_data_start as usize - buf.as_ptr() as usize
};
buf.drain(..hid_data_index);
RawInputData::Hid {
device_handle,
raw_hid: RawHidData {
hid_input_size: hid_data.dwSizeHid,
hid_input_count: hid_data.dwCount,
raw_input: buf.into_boxed_slice(),
},
}
}
_ => unreachable!(),
};
assert_ne!(-1, status);
}
if status == 0 {
if status == UINT::max_value() || status == 0 {
return None;
}
@@ -406,7 +200,7 @@ fn button_flags_to_element_state(
}
}
pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option<ElementState>; 5] {
pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option<ElementState>; 3] {
[
button_flags_to_element_state(
button_flags,
@@ -423,264 +217,5 @@ pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option<ElementState>
winuser::RI_MOUSE_RIGHT_BUTTON_DOWN,
winuser::RI_MOUSE_RIGHT_BUTTON_UP,
),
button_flags_to_element_state(
button_flags,
winuser::RI_MOUSE_BUTTON_4_DOWN,
winuser::RI_MOUSE_BUTTON_4_UP,
),
button_flags_to_element_state(
button_flags,
winuser::RI_MOUSE_BUTTON_5_DOWN,
winuser::RI_MOUSE_BUTTON_5_UP,
),
]
}
pub struct Axis {
caps: HIDP_VALUE_CAPS,
value: f64,
prev_value: f64,
axis: Option<GamepadAxis>,
}
impl fmt::Debug for Axis {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[derive(Debug)]
struct Axis {
value: f64,
prev_value: f64,
axis: Option<GamepadAxis>,
}
let axis_proxy = Axis {
value: self.value,
prev_value: self.prev_value,
axis: self.axis,
};
axis_proxy.fmt(f)
}
}
#[derive(Debug)]
pub struct RawGamepad {
handle: HANDLE,
pre_parsed_data: Vec<u8>,
button_count: usize,
pub button_state: Vec<bool>,
pub prev_button_state: Vec<bool>,
axis_count: usize,
pub axis_state: Vec<Axis>,
}
// Reference: https://chromium.googlesource.com/chromium/chromium/+/trunk/content/browser/gamepad/raw_input_data_fetcher_win.cc
impl RawGamepad {
pub fn new(handle: HANDLE) -> Option<Self> {
let pre_parsed_data = get_raw_input_pre_parse_info(handle)?;
let data_ptr = pre_parsed_data.as_ptr() as PHIDP_PREPARSED_DATA;
let mut caps = unsafe { mem::zeroed() };
let status = unsafe { HidP_GetCaps(data_ptr, &mut caps) };
if status != HIDP_STATUS_SUCCESS {
return None;
}
let mut button_caps_len = caps.NumberInputButtonCaps;
let mut button_caps = Vec::with_capacity(button_caps_len as _);
let status = unsafe {
HidP_GetButtonCaps(
HidP_Input,
button_caps.as_mut_ptr(),
&mut button_caps_len,
data_ptr,
)
};
if status != HIDP_STATUS_SUCCESS {
return None;
}
unsafe { button_caps.set_len(button_caps_len as _) };
let mut button_count = 0;
for button_cap in button_caps {
let range = unsafe { button_cap.u.Range() };
button_count = max(button_count, range.UsageMax);
}
let button_state = vec![false; button_count as usize];
let mut axis_caps_len = caps.NumberInputValueCaps;
let mut axis_caps = Vec::with_capacity(axis_caps_len as _);
let status = unsafe {
HidP_GetValueCaps(
HidP_Input,
axis_caps.as_mut_ptr(),
&mut axis_caps_len,
data_ptr,
)
};
if status != HIDP_STATUS_SUCCESS {
return None;
}
unsafe { axis_caps.set_len(axis_caps_len as _) };
let mut axis_state = Vec::with_capacity(axis_caps_len as _);
let mut axis_count = 0;
for (axis_index, axis_cap) in axis_caps.drain(0..).enumerate() {
axis_state.push(Axis {
caps: axis_cap,
value: 0.0,
prev_value: 0.0,
axis: None,
});
axis_count = max(axis_count, axis_index + 1);
}
Some(RawGamepad {
handle,
pre_parsed_data,
button_count: button_count as usize,
button_state: button_state.clone(),
prev_button_state: button_state,
axis_count,
axis_state,
})
}
fn pre_parsed_data_ptr(&mut self) -> PHIDP_PREPARSED_DATA {
self.pre_parsed_data.as_mut_ptr() as PHIDP_PREPARSED_DATA
}
fn update_button_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> {
let pre_parsed_data_ptr = self.pre_parsed_data_ptr();
self.prev_button_state =
mem::replace(&mut self.button_state, vec![false; self.button_count]);
let mut usages_len = 0;
// This is the officially documented way to get the required length, but it nonetheless returns
// `HIDP_STATUS_BUFFER_TOO_SMALL`...
unsafe {
HidP_GetUsagesEx(
HidP_Input,
0,
ptr::null_mut(),
&mut usages_len,
pre_parsed_data_ptr,
raw_input_report.as_mut_ptr() as PCHAR,
raw_input_report.len() as _,
)
};
let mut usages = Vec::with_capacity(usages_len as _);
let status = unsafe {
HidP_GetUsagesEx(
HidP_Input,
0,
usages.as_mut_ptr(),
&mut usages_len,
pre_parsed_data_ptr,
raw_input_report.as_mut_ptr() as PCHAR,
raw_input_report.len() as _,
)
};
if status != HIDP_STATUS_SUCCESS {
return None;
}
unsafe { usages.set_len(usages_len as _) };
for usage in usages {
if usage.UsagePage != 0xFF << 8 {
let button_index = (usage.Usage - 1) as usize;
self.button_state[button_index] = true;
}
}
Some(())
}
fn update_axis_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> {
let pre_parsed_data_ptr = self.pre_parsed_data_ptr();
for axis in &mut self.axis_state {
let (status, axis_value) = if axis.caps.LogicalMin < 0 {
let mut scaled_axis_value = 0;
let status = unsafe {
HidP_GetScaledUsageValue(
HidP_Input,
axis.caps.UsagePage,
0,
axis.caps.u.Range().UsageMin,
&mut scaled_axis_value,
pre_parsed_data_ptr,
raw_input_report.as_mut_ptr() as PCHAR,
raw_input_report.len() as _,
)
};
(status, scaled_axis_value as f64)
} else {
let mut axis_value = 0;
let status = unsafe {
HidP_GetUsageValue(
HidP_Input,
axis.caps.UsagePage,
0,
axis.caps.u.Range().UsageMin,
&mut axis_value,
pre_parsed_data_ptr,
raw_input_report.as_mut_ptr() as PCHAR,
raw_input_report.len() as _,
)
};
(status, axis_value as f64)
};
if status != HIDP_STATUS_SUCCESS {
return None;
}
axis.prev_value = axis.value;
axis.value = util::normalize_symmetric(
axis_value,
axis.caps.LogicalMin as f64,
axis.caps.LogicalMax as f64,
);
}
Some(())
}
pub unsafe fn update_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> {
self.update_button_state(raw_input_report)?;
self.update_axis_state(raw_input_report)?;
Some(())
}
pub fn get_changed_buttons(&self) -> impl '_ + Iterator<Item = GamepadEvent> {
self.button_state
.iter()
.zip(self.prev_button_state.iter())
.enumerate()
.filter(|&(_, (button, prev_button))| button != prev_button)
.map(|(index, (button, _))| {
let state = if *button {
ElementState::Pressed
} else {
ElementState::Released
};
GamepadEvent::Button {
button_id: index as _,
button: None,
state,
}
})
}
pub fn get_changed_axes(&self) -> impl '_ + Iterator<Item = GamepadEvent> {
self.axis_state
.iter()
.enumerate()
.filter(|&(_, axis)| axis.value != axis.prev_value)
.map(|(index, axis)| GamepadEvent::Axis {
axis_id: index as _,
axis: axis.axis,
value: axis.value,
stick: false,
})
}
pub fn get_gamepad_events(&self) -> Vec<GamepadEvent> {
self.get_changed_axes()
.chain(self.get_changed_buttons())
.collect()
}
// pub fn rumble(&mut self, _left_speed: u16, _right_speed: u16) {
// // Even though I can't read German, this is still the most useful resource I found:
// // https://zfx.info/viewtopic.php?t=3574&f=7
// // I'm not optimistic about it being possible to implement this.
// }
}

View File

@@ -1,5 +1,6 @@
use std::{
io, mem,
ops::BitAnd,
os::raw::c_void,
ptr, slice,
sync::atomic::{AtomicBool, Ordering},
@@ -20,8 +21,6 @@ use winapi::{
},
};
pub use crate::util::*;
// Helper function to dynamically load function pointer.
// `library` and `function` must be zero-terminated.
pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
@@ -52,6 +51,13 @@ macro_rules! get_function {
};
}
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where
T: Copy + PartialEq + BitAnd<T, Output = T>,
{
bitset & flag == flag
}
pub fn wchar_to_string(wchar: &[wchar_t]) -> String {
String::from_utf16_lossy(wchar).to_string()
}

View File

@@ -34,12 +34,10 @@ use crate::{
error::{ExternalError, NotSupportedError, OsError as RootOsError},
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, INITIAL_DPI_MSG_ID,
REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID,
},
event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID},
icon::{self, IconType, WinIcon},
monitor, util,
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
@@ -84,12 +82,12 @@ impl Window {
panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`");
}
let shared_data = event_loop.shared_data.clone();
let file_drop_runner = event_loop.runner_shared.clone();
let file_drop_handler = FileDropHandler::new(
win.window.0,
Box::new(move |event| {
if let Ok(e) = event.map_nonuser_event() {
shared_data.runner_shared.send_event(e)
file_drop_runner.send_event(e)
}
}),
);
@@ -105,7 +103,7 @@ impl Window {
let subclass_input = event_loop::SubclassInput {
window_state: win.window_state.clone(),
shared_data: event_loop.shared_data.clone(),
event_loop_runner: event_loop.runner_shared.clone(),
file_drop_handler,
};
@@ -127,33 +125,24 @@ impl Window {
#[inline]
pub fn set_visible(&self, visible: bool) {
match visible {
true => unsafe {
winuser::ShowWindow(self.window.0, winuser::SW_SHOW);
},
false => unsafe {
winuser::ShowWindow(self.window.0, winuser::SW_HIDE);
},
}
let window_state = Arc::clone(&self.window_state);
let window = self.window.clone();
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::VISIBLE, visible)
});
});
}
#[inline]
pub fn request_redraw(&self) {
unsafe {
if self.thread_executor.trigger_newevents_on_redraw() {
winuser::RedrawWindow(
self.window.0,
ptr::null(),
ptr::null_mut(),
winuser::RDW_INTERNALPAINT,
);
} else {
let mut window_state = self.window_state.lock();
if !window_state.queued_out_of_band_redraw {
window_state.queued_out_of_band_redraw = true;
winuser::PostMessageW(self.window.0, *REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID, 0, 0);
}
}
winuser::RedrawWindow(
self.window.0,
ptr::null(),
ptr::null_mut(),
winuser::RDW_INTERNALPAINT,
);
}
}
@@ -450,6 +439,18 @@ impl Window {
WindowId(self.window.0)
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::MINIMIZED, minimized)
});
});
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
let window = self.window.clone();
@@ -693,6 +694,11 @@ impl Window {
pub fn set_ime_position(&self, _logical_spot: LogicalPosition) {
unimplemented!();
}
#[inline]
pub fn is_dark_mode(&self) -> bool {
self.window_state.lock().is_dark_mode
}
}
impl Drop for Window {
@@ -897,8 +903,19 @@ unsafe fn init<T: 'static>(
window_flags.set(WindowFlags::VISIBLE, attributes.visible);
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).
let dark_mode = try_dark_mode(real_window.0);
let window_state = {
let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor);
let window_state = WindowState::new(
&attributes,
window_icon,
taskbar_icon,
dpi_factor,
dark_mode,
);
let window_state = Arc::new(Mutex::new(window_state));
WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags);
window_state

View File

@@ -32,6 +32,7 @@ pub struct WindowState {
/// 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,
}
@@ -79,6 +80,8 @@ 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 FULLSCREEN_AND_MASK = !(
WindowFlags::DECORATIONS.bits |
WindowFlags::RESIZABLE.bits |
@@ -96,6 +99,7 @@ impl WindowState {
window_icon: Option<WinIcon>,
taskbar_icon: Option<WinIcon>,
dpi_factor: f64,
is_dark_mode: bool,
) -> WindowState {
WindowState {
mouse: MouseProperties {
@@ -115,6 +119,7 @@ impl WindowState {
fullscreen: None,
queued_out_of_band_redraw: false,
is_dark_mode,
high_surrogate: None,
window_flags: WindowFlags::empty(),
}
@@ -212,6 +217,9 @@ impl WindowFlags {
if self.contains(WindowFlags::CHILD) {
style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually.
}
if self.contains(WindowFlags::MINIMIZED) {
style |= WS_MINIMIZE;
}
if self.contains(WindowFlags::MAXIMIZED) {
style |= WS_MAXIMIZE;
}
@@ -276,14 +284,30 @@ impl WindowFlags {
}
}
// Minimize operations should execute after maximize for proper window animations
if diff.contains(WindowFlags::MINIMIZED) {
unsafe {
winuser::ShowWindow(
window,
match new.contains(WindowFlags::MINIMIZED) {
true => winuser::SW_MINIMIZE,
false => winuser::SW_RESTORE,
},
);
}
}
if diff != WindowFlags::empty() {
let (style, style_ex) = new.to_window_styles();
unsafe {
winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 1, 0);
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
// This condition is necessary to avoid having an unrestorable window
if !new.contains(WindowFlags::MINIMIZED) {
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
}
let mut flags = winuser::SWP_NOZORDER
| winuser::SWP_NOMOVE

View File

@@ -1,334 +0,0 @@
use std::{
io, mem,
sync::{Arc, Weak},
};
use rusty_xinput::*;
use winapi::{
shared::minwindef::{DWORD, WORD},
um::xinput::*,
};
use crate::{
event::{
device::{BatteryLevel, GamepadAxis, GamepadButton, GamepadEvent, RumbleError, Side},
ElementState,
},
platform_impl::platform::util,
};
lazy_static! {
static ref XINPUT_HANDLE: Option<XInputHandle> = XInputHandle::load_default().ok();
}
static BUTTONS: &[(WORD, u32, GamepadButton)] = &[
(XINPUT_GAMEPAD_DPAD_UP, 12, GamepadButton::DPadUp),
(XINPUT_GAMEPAD_DPAD_DOWN, 13, GamepadButton::DPadDown),
(XINPUT_GAMEPAD_DPAD_LEFT, 14, GamepadButton::DPadLeft),
(XINPUT_GAMEPAD_DPAD_RIGHT, 15, GamepadButton::DPadRight),
(XINPUT_GAMEPAD_START, 9, GamepadButton::Start),
(XINPUT_GAMEPAD_BACK, 8, GamepadButton::Select),
(XINPUT_GAMEPAD_LEFT_THUMB, 10, GamepadButton::LeftStick),
(XINPUT_GAMEPAD_RIGHT_THUMB, 11, GamepadButton::RightStick),
(XINPUT_GAMEPAD_LEFT_SHOULDER, 4, GamepadButton::LeftShoulder),
(
XINPUT_GAMEPAD_RIGHT_SHOULDER,
5,
GamepadButton::RightShoulder,
),
(XINPUT_GAMEPAD_A, 0, GamepadButton::South),
(XINPUT_GAMEPAD_B, 1, GamepadButton::East),
(XINPUT_GAMEPAD_X, 2, GamepadButton::West),
(XINPUT_GAMEPAD_Y, 3, GamepadButton::North),
];
pub fn id_from_name(name: &str) -> Option<DWORD> {
// A device name looks like \\?\HID#VID_046D&PID_C21D&IG_00#8&6daf3b6&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
// The IG_00 substring indicates that this is an XInput gamepad, and that the ID is 00
let pat = "IG_0";
name.find(pat)
.and_then(|i| name[i + pat.len()..].chars().next())
.and_then(|c| match c {
'0' => Some(0),
'1' => Some(1),
'2' => Some(2),
'3' => Some(3),
_ => None,
})
}
#[derive(Debug)]
pub struct XInputGamepad {
shared: Arc<XInputGamepadShared>,
prev_state: Option<XInputState>,
state: Option<XInputState>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct XInputGamepadShared {
port: DWORD,
}
impl XInputGamepad {
pub fn new(port: DWORD) -> Option<Self> {
XINPUT_HANDLE.as_ref().map(|_| XInputGamepad {
shared: Arc::new(XInputGamepadShared { port }),
prev_state: None,
state: None,
})
}
pub fn update_state(&mut self) -> Option<()> {
let state = XINPUT_HANDLE
.as_ref()
.and_then(|h| h.get_state(self.shared.port).ok());
if state.is_some() {
self.prev_state = mem::replace(&mut self.state, state);
Some(())
} else {
None
}
}
fn check_trigger_digital(
events: &mut Vec<GamepadEvent>,
value: bool,
prev_value: Option<bool>,
side: Side,
) {
const LEFT_TRIGGER_ID: u32 = /*BUTTONS.len() as _*/ 16;
const RIGHT_TRIGGER_ID: u32 = LEFT_TRIGGER_ID + 1;
if Some(value) != prev_value {
let state = if value {
ElementState::Pressed
} else {
ElementState::Released
};
let (button_id, button) = match side {
Side::Left => (LEFT_TRIGGER_ID, Some(GamepadButton::LeftTrigger)),
Side::Right => (RIGHT_TRIGGER_ID, Some(GamepadButton::RightTrigger)),
};
events.push(GamepadEvent::Button {
button_id,
button,
state,
});
}
}
pub fn get_changed_buttons(&self, events: &mut Vec<GamepadEvent>) {
let (buttons, left_trigger, right_trigger) = match self.state.as_ref() {
Some(state) => (
state.raw.Gamepad.wButtons,
state.left_trigger_bool(),
state.right_trigger_bool(),
),
None => return,
};
let (prev_buttons, prev_left, prev_right) = self
.prev_state
.as_ref()
.map(|state| {
(
state.raw.Gamepad.wButtons,
Some(state.left_trigger_bool()),
Some(state.right_trigger_bool()),
)
})
.unwrap_or_else(|| (0, None, None));
/*
A = buttons
B = prev_buttons
C = changed
P = pressed
R = released
A B C C A P C B R
(0 0) 0 (0 0) 0 (0 0) 0
(0 1) 1 (1 1) 1 (1 0) 0
(1 0) 1 (1 0) 0 (1 1) 1
(1 1) 0 (0 1) 0 (0 1) 0
*/
let changed = buttons ^ prev_buttons;
let pressed = changed & buttons;
let released = changed & prev_buttons;
for &(flag, button_id, button) in BUTTONS {
let button = Some(button);
if util::has_flag(pressed, flag) {
events.push(GamepadEvent::Button {
button_id,
button,
state: ElementState::Pressed,
});
} else if util::has_flag(released, flag) {
events.push(GamepadEvent::Button {
button_id,
button,
state: ElementState::Released,
});
}
}
Self::check_trigger_digital(events, left_trigger, prev_left, Side::Left);
Self::check_trigger_digital(events, right_trigger, prev_right, Side::Right);
}
fn check_trigger(
events: &mut Vec<GamepadEvent>,
value: u8,
prev_value: Option<u8>,
side: Side,
) {
const LEFT_TRIGGER_ID: u32 = 4;
const RIGHT_TRIGGER_ID: u32 = LEFT_TRIGGER_ID + 1;
if Some(value) != prev_value {
let (axis_id, axis) = match side {
Side::Left => (LEFT_TRIGGER_ID, Some(GamepadAxis::LeftTrigger)),
Side::Right => (RIGHT_TRIGGER_ID, Some(GamepadAxis::RightTrigger)),
};
events.push(GamepadEvent::Axis {
axis_id,
axis,
value: value as f64 / u8::max_value() as f64,
stick: false,
});
}
}
fn check_stick(
events: &mut Vec<GamepadEvent>,
value: (i16, i16),
prev_value: Option<(i16, i16)>,
stick: Side,
) {
let (id, axis) = match stick {
Side::Left => ((0, 1), (GamepadAxis::LeftStickX, GamepadAxis::LeftStickY)),
Side::Right => ((2, 3), (GamepadAxis::RightStickX, GamepadAxis::RightStickY)),
};
let prev_x = prev_value.map(|prev| prev.0);
let prev_y = prev_value.map(|prev| prev.1);
let value_f64 = |value_int: i16| match value_int.signum() {
0 => 0.0,
1 => value_int as f64 / i16::max_value() as f64,
-1 => value_int as f64 / (i16::min_value() as f64).abs(),
_ => unreachable!(),
};
let value_f64 = (value_f64(value.0), value_f64(value.1));
if prev_x != Some(value.0) {
events.push(GamepadEvent::Axis {
axis_id: id.0,
axis: Some(axis.0),
value: value_f64.0,
stick: true,
});
}
if prev_y != Some(value.1) {
events.push(GamepadEvent::Axis {
axis_id: id.1,
axis: Some(axis.1),
value: value_f64.1,
stick: true,
});
}
if prev_x != Some(value.0) || prev_y != Some(value.1) {
events.push(GamepadEvent::Stick {
x_id: id.0,
y_id: id.1,
x_value: value_f64.0,
y_value: value_f64.1,
side: stick,
})
}
}
pub fn get_changed_axes(&self, events: &mut Vec<GamepadEvent>) {
let state = match self.state {
Some(ref state) => state,
None => return,
};
let left_stick = state.left_stick_raw();
let right_stick = state.right_stick_raw();
let left_trigger = state.left_trigger();
let right_trigger = state.right_trigger();
let prev_state = self.prev_state.as_ref();
let prev_left_stick = prev_state.map(|state| state.left_stick_raw());
let prev_right_stick = prev_state.map(|state| state.right_stick_raw());
let prev_left_trigger = prev_state.map(|state| state.left_trigger());
let prev_right_trigger = prev_state.map(|state| state.right_trigger());
Self::check_stick(events, left_stick, prev_left_stick, Side::Left);
Self::check_stick(events, right_stick, prev_right_stick, Side::Right);
Self::check_trigger(events, left_trigger, prev_left_trigger, Side::Left);
Self::check_trigger(events, right_trigger, prev_right_trigger, Side::Right);
}
pub fn get_gamepad_events(&self) -> Vec<GamepadEvent> {
let mut events = Vec::new();
self.get_changed_axes(&mut events);
self.get_changed_buttons(&mut events);
events
}
pub fn shared_data(&self) -> Weak<XInputGamepadShared> {
Arc::downgrade(&self.shared)
}
}
impl Drop for XInputGamepad {
fn drop(&mut self) {
// For some reason, if you don't attempt to retrieve the xinput gamepad state at least once
// after the gamepad was disconnected, all future attempts to read from a given port (even
// if a controller was plugged back into said port) will fail! I don't know why that happens,
// but this fixes it, so 🤷.
XINPUT_HANDLE
.as_ref()
.and_then(|h| h.get_state(self.shared.port).ok());
}
}
impl XInputGamepadShared {
pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), RumbleError> {
let left_speed = (left_speed.max(0.0).min(1.0) * u16::max_value() as f64) as u16;
let right_speed = (right_speed.max(0.0).min(1.0) * u16::max_value() as f64) as u16;
let result = XINPUT_HANDLE
.as_ref()
.unwrap()
.set_state(self.port, left_speed, right_speed);
result.map_err(|e| match e {
XInputUsageError::XInputNotLoaded | XInputUsageError::InvalidControllerID => panic!(
"unexpected xinput error {:?}; this is a bug and should be reported",
e
),
XInputUsageError::DeviceNotConnected => RumbleError::DeviceNotConnected,
XInputUsageError::UnknownError(code) => {
RumbleError::OsError(io::Error::from_raw_os_error(code as i32))
}
})
}
pub fn port(&self) -> u8 {
self.port as _
}
pub fn battery_level(&self) -> Option<BatteryLevel> {
use rusty_xinput::BatteryLevel as XBatteryLevel;
let battery_info = XINPUT_HANDLE
.as_ref()
.unwrap()
.get_gamepad_battery_information(self.port)
.ok()?;
match battery_info.battery_type {
BatteryType::ALKALINE | BatteryType::NIMH => match battery_info.battery_level {
XBatteryLevel::EMPTY => Some(BatteryLevel::Empty),
XBatteryLevel::LOW => Some(BatteryLevel::Low),
XBatteryLevel::MEDIUM => Some(BatteryLevel::Medium),
XBatteryLevel::FULL => Some(BatteryLevel::Full),
_ => None,
},
_ => None,
}
}
}

View File

@@ -1,29 +0,0 @@
use std::ops::BitAnd;
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where
T: Copy + PartialEq + BitAnd<T, Output = T>,
{
bitset & flag == flag
}
pub fn clamp(value: f64, min: f64, max: f64) -> f64 {
if value > max {
max
} else if value < min {
min
} else {
value
}
}
pub fn normalize_asymmetric(value: f64, min: f64, max: f64) -> f64 {
let range = max - min;
let translated = value - min;
let scaled = translated / range;
clamp(scaled, 0.0, 1.0)
}
pub fn normalize_symmetric(value: f64, min: f64, max: f64) -> f64 {
(2.0 * normalize_asymmetric(value, min, max)) - 1.0
}

View File

@@ -572,6 +572,16 @@ impl Window {
self.window.set_resizable(resizable)
}
/// Sets the window to minimized or back
///
/// ## Platform-specific
///
/// - **iOS:** Has no effect
#[inline]
pub fn set_minimized(&self, minimized: bool) {
self.window.set_minimized(minimized);
}
/// Sets the window to maximized or back.
///
/// ## Platform-specific
@@ -847,3 +857,9 @@ pub enum Fullscreen {
Exclusive(VideoMode),
Borderless(MonitorHandle),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Theme {
Light,
Dark,
}

View File

@@ -22,8 +22,6 @@ fn window_send() {
fn ids_send() {
// ensures that the various `..Id` types implement `Send`
needs_send::<winit::window::WindowId>();
needs_send::<winit::event::device::MouseId>();
needs_send::<winit::event::device::KeyboardId>();
needs_send::<winit::event::device::GamepadHandle>();
needs_send::<winit::event::DeviceId>();
needs_send::<winit::monitor::MonitorHandle>();
}