mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
41 Commits
gamepad-de
...
v0.20.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0b46a03b5 | ||
|
|
114c18e70d | ||
|
|
7367b8be6c | ||
|
|
dd768fe655 | ||
|
|
d9bda3e985 | ||
|
|
fa7a3025ec | ||
|
|
e4451d6786 | ||
|
|
468b6b83ec | ||
|
|
8a3a32f286 | ||
|
|
20e81695ca | ||
|
|
027c52171d | ||
|
|
cc206d31b7 | ||
|
|
5d99316c96 | ||
|
|
d59eec4633 | ||
|
|
25e018d1ce | ||
|
|
25123bed23 | ||
|
|
a8d6db0fc1 | ||
|
|
8a9a9cd92d | ||
|
|
530ff5420b | ||
|
|
133b11fa6d | ||
|
|
5b489284e4 | ||
|
|
cdc32eb817 | ||
|
|
eb38ff453a | ||
|
|
8eb7853a1a | ||
|
|
0c151f9fb3 | ||
|
|
c10c820311 | ||
|
|
82889e2367 | ||
|
|
92741aa4ec | ||
|
|
2f352ca5cf | ||
|
|
38c8cb9f4a | ||
|
|
73248bdced | ||
|
|
01203b247b | ||
|
|
3e1d169160 | ||
|
|
c1b93fc3d0 | ||
|
|
1f81e5c872 | ||
|
|
e5291c9e28 | ||
|
|
35505a3114 | ||
|
|
830d47a5f7 | ||
|
|
1a514dff38 | ||
|
|
2888d5c6cf | ||
|
|
400f75a2b3 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,7 +2,6 @@ Cargo.lock
|
||||
target/
|
||||
rls/
|
||||
.vscode/
|
||||
.cargo/
|
||||
util/
|
||||
*~
|
||||
*.wasm
|
||||
|
||||
52
CHANGELOG.md
52
CHANGELOG.md
@@ -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`.
|
||||
|
||||
29
Cargo.toml
29
Cargo.toml
@@ -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"
|
||||
|
||||
@@ -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**|
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.20.0-alpha4"
|
||||
winit = "0.20.0-alpha6"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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,
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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
35
examples/minimize.rs
Normal 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,
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
_ => (),
|
||||
|
||||
@@ -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
72
examples/web.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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,
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
7
examples/web/gamepad/websys/.gitignore
vendored
7
examples/web/gamepad/websys/.gitignore
vendored
@@ -1,7 +0,0 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
bin/
|
||||
pkg/
|
||||
wasm-pack.log
|
||||
.DS_Store
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ fn main() {
|
||||
quit = true;
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
Event::EventsCleared => {
|
||||
Event::MainEventsCleared => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
|
||||
315
src/event.rs
315
src/event.rs
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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`"
|
||||
}
|
||||
|
||||
11
src/lib.rs
11
src/lib.rs
@@ -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;
|
||||
|
||||
@@ -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 _
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)) => {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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!()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
92
src/platform_impl/linux/x11/util/keys.rs
Normal file
92
src/platform_impl/linux/x11/util/keys.rs
Normal 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()
|
||||
}
|
||||
@@ -9,6 +9,7 @@ mod geometry;
|
||||
mod hint;
|
||||
mod icon;
|
||||
mod input;
|
||||
pub mod keys;
|
||||
mod memory;
|
||||
pub mod modifiers;
|
||||
mod randr;
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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`");
|
||||
|
||||
@@ -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,
|
||||
|
||||
8
src/platform_impl/web/device.rs
Normal file
8
src/platform_impl/web/device.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
216
src/platform_impl/windows/dark_mode.rs
Normal file
216
src/platform_impl/windows/dark_mode.rs
Normal 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()
|
||||
}
|
||||
@@ -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
411
src/platform_impl/windows/event_loop/runner.rs
Normal file
411
src/platform_impl/windows/event_loop/runner.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/util.rs
29
src/util.rs
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user