Compare commits

..

2 Commits

Author SHA1 Message Date
Christophe Massolin
e004bd2bb3 Gamepad device events - Web/WASM (#1414)
Add gamepad support for stdweb and web-sys, as well as web-specific gamepad examples. 

* [web] Fix compilation error from device api

* [wasm] Apply device api changes

* [wasm] Format and cleanup

* [wasm32] Implement gamepad connections

* [wasm] Harmonize

* [Test] Made some tests with wasm-pack

* Quick fix instant non supporting Hash trait

* Fix on_received_character

* [web_sys] Split add_event and add_window_event

* [web] split device implementations

* Update tests/web...still does not work

* [tests/web] do not ignore index.html

* [web/web_sys] split canvas and window

* [tests/web] enable stack trace

* [web] fix borrowmut

* [web_sys] fix gamepad registration

* [web] harmonize naming

* [web_sys] create global emitter

* [web] implement gamepad buttons

* [web] implement gamepad axis

* [web] cleanup

* [web] update test

* [web] move tests/web to examples/web

* [web] axis does produce stick event

* [web] Support Stick event

* [web] implement gamepad to stdweb

* [web] rename examples/web to examples/wasm

* [web/web-sys] Move gamepad_manager from backend

* [web/web_sys] implement EventLoop::gamepads

* [web/web_sys] Drain gamepad events

* [web/stdweb] apply web_sys changes

* [web] update web/examples

* [web] move gamepads code to gamepad_manager

* [web] simplify and optimise

* [web] replace EventCode to GamepadAxis and GamepadButton structs

* [web] reuse gamepad events due to chrome issue

* [web] rumble does not work

* [web/stdweb] try debugging

* [web] fix Chrome gamepad not updated

* [web/stdweb] created an example

* [examples] fix paths

* fix warnings

* [web/examples] update comments

* [web/stdweb] add experimental support to vibrate()

* [web] add CR
2020-03-03 09:56:11 -05:00
Osspial
0729074ce3 Overhaul device events API and add gamepad support on Windows (#804)
* Initial implementation

* Corrected RAWINPUT buffer sizing

* Mostly complete XInput implementation

* XInput triggers

* Add preliminary CHANGELOG entry.

* match unix common API to evl 2.0

* wayland: eventloop2.0

* make EventLoopProxy require T: 'static

* Revamp device event API, as well as several misc. fixes on Windows:

* When you have multiple windows, you no longer receive duplicate device
  events
* Mouse Device Events now send X-button input
* Mouse Device Events now send horizontal scroll wheel input

* Add MouseEvent documentation and Device ID debug passthrough

* Improve type safety on get_raw_input_data

* Remove button_id field from MouseEvent::Button in favor of utton

* Remove regex dependency on Windows

* Remove axis filtering in XInput

* Make gamepads not use lazy_static

* Publicly expose gamepad rumble

* Unstack DeviceEvent and fix examples/tests

* Add HANDLE retrieval method to DeviceExtWindows

* Add distinction between non-joystick axes and joystick axes.

This helps with properly calculating the deadzone for controller
joysticks. One potential issue is that the `Stick` variant isn't used
for *all* joysticks, which could be potentially confusing - for example,
raw input joysticks will never use the `Stick` variant because we don't
understand the semantic meaning of raw input joystick axes.

* Add ability to get gamepad port

* Fix xinput controller hot swapping

* Add functions for enumerating attached devices

* Clamp input to [0.0, 1.0] on gamepad rumble

* Expose gamepad rumble errors

* Add method to check if device is still connected

* Add docs

* Rename AxisHint and ButtonHint to GamepadAxis and GamepadButton

* Add CHANGELOG entry

* Update CHANGELOG.md

* Add HidId and MovedAbsolute

* Fix xinput deprecation warnings

* Add ability to retrieve gamepad battery level

* Fix weird imports in gamepad example

* Update CHANGELOG.md

* Resolve francesca64 comments
2019-11-29 16:50:50 -05:00
139 changed files with 7252 additions and 6565 deletions

View File

@@ -1,106 +0,0 @@
name: CI
on:
pull_request:
paths:
- '**.rs'
- '**.toml'
- '.github/workflows/ci.yml'
push:
branches: [master]
paths:
- '**.rs'
- '**.toml'
- '.github/workflows/ci.yml'
jobs:
Check_Formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable
components: rustfmt
- name: Check Formatting
run: cargo +stable fmt --all -- --check
Tests:
strategy:
fail-fast: false
matrix:
rust_version: [stable, nightly]
platform:
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
- { target: i686-pc-windows-msvc, os: windows-latest, }
- { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
- { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-apple-darwin, os: macos-latest, }
- { target: x86_64-apple-ios, os: macos-latest, }
- { target: aarch64-apple-ios, os: macos-latest, }
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
# doesn't currently work on Linux.
- { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, web: web }
- { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, web: web }
env:
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0"
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
WEB: ${{ matrix.platform.web }}
runs-on: ${{ matrix.platform.os }}
steps:
- uses: actions/checkout@v1
# Used to cache cargo-web
- name: Cache cargo folder
uses: actions/cache@v1
with:
path: ~/.cargo
key: ${{ matrix.platform.target }}-cargo-${{ matrix.rust_version }}
- uses: hecrj/setup-rust-action@v1
with:
rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }}
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo apt-get update && sudo apt-get install gcc-multilib
- name: Install cargo-web
continue-on-error: true
if: contains(matrix.platform.target, 'wasm32')
run: cargo install cargo-web
- name: Check documentation
shell: bash
if: matrix.platform.target != 'wasm32-unknown-unknown'
run: cargo doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES
- name: Build
shell: bash
run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features $FEATURES
- name: Build tests
shell: bash
run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES
- name: Run tests
shell: bash
if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32'))
run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features $FEATURES
- name: Build with serde enabled
shell: bash
run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
- name: Build tests with serde enabled
shell: bash
run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
- name: Run tests with serde enabled
shell: bash
if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32'))
run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES

View File

@@ -1,18 +0,0 @@
name: Publish
on:
push:
branches: [master]
paths: "Cargo.toml"
jobs:
Publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable
components: rustfmt
- name: Publish to crates.io
run: cargo publish --token ${{ secrets.cratesio_token }}

2
.gitignore vendored
View File

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

104
.travis.yml Normal file
View File

@@ -0,0 +1,104 @@
language: rust
matrix:
include:
# Linux 32bit
- env: TARGET=i686-unknown-linux-gnu
os: linux
rust: nightly
addons:
apt:
# Cross compiler and cross compiled C libraries
packages: &i686_packages
- gcc-multilib
- env: TARGET=i686-unknown-linux-gnu
os: linux
rust: stable
addons:
apt:
packages: *i686_packages
# Linux 64bit
- env: TARGET=x86_64-unknown-linux-gnu
os: linux
rust: nightly
- env: TARGET=x86_64-unknown-linux-gnu
os: linux
rust: stable
# macOS
- env: TARGET=x86_64-apple-darwin
os: osx
rust: nightly
- env: TARGET=x86_64-apple-darwin
os: osx
rust: stable
# iOS x86_64
- env: TARGET=x86_64-apple-ios
os: osx
rust: nightly
- env: TARGET=x86_64-apple-ios
os: osx
rust: stable
# iOS armv7
- env: TARGET=armv7-apple-ios
os: osx
rust: nightly
- env: TARGET=armv7-apple-ios
os: osx
rust: stable
# iOS arm64
- env: TARGET=aarch64-apple-ios
os: osx
rust: nightly
- env: TARGET=aarch64-apple-ios
os: osx
rust: stable
# wasm stdweb
- env: TARGET=wasm32-unknown-unknown WEB=web FEATURES=stdweb
os: linux
rust: stable
- env: TARGET=wasm32-unknown-unknown WEB=web FEATURES=stdweb
os: linux
rust: nightly
# wasm web-sys
- env: TARGET=wasm32-unknown-unknown FEATURES=web-sys
os: linux
rust: stable
- env: TARGET=wasm32-unknown-unknown FEATURES=web-sys
os: linux
rust: nightly
install:
- rustup self update
- rustup target add $TARGET; true
- rustup toolchain install stable
- rustup component add rustfmt --toolchain stable
script:
- cargo +stable fmt --all -- --check
# Ensure that the documentation builds properly.
- cargo doc --no-deps
# Install cargo-web to build stdweb
- if [[ $WEB = "web" ]]; then cargo install -f cargo-web; fi
# Build without serde then with serde
- if [[ -z "$FEATURES" ]]; then
cargo $WEB build --target $TARGET --verbose;
else
cargo $WEB build --target $TARGET --features $FEATURES --verbose;
fi
- cargo $WEB build --target $TARGET --features serde,$FEATURES --verbose
# Running iOS apps on macOS requires the Simulator so we skip that for now
# The web targets also don't support running tests
- if [[ $TARGET != *-apple-ios && $TARGET != wasm32-* ]]; then cargo test --target $TARGET --verbose; fi
- if [[ $TARGET != *-apple-ios && $TARGET != wasm32-* ]]; then cargo test --target $TARGET --features serde --verbose; fi
after_success:
- |
[ $TRAVIS_BRANCH = master ] &&
[ $TRAVIS_PULL_REQUEST = false ] &&
cargo publish --token ${CRATESIO_TOKEN}

View File

@@ -1,65 +1,5 @@
# Unreleased
# 0.21.0 (2020-02-04)
- On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows.
- On macOS, fix set_minimized(true) works only with decorations.
- On macOS, add `hide_application` to `EventLoopWindowTarget` via a new `EventLoopWindowTargetExtMacOS` trait. `hide_application` will hide the entire application by calling `-[NSApplication hide: nil]`.
- On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
- On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates.
- On macOS, fix issue where unbundled applications would sometimes open without being focused.
- On macOS, fix `run_return` does not return unless it receives a message.
- On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop.
- On X11, fix deadlock on window state when handling certain window events.
- `WindowBuilder` now implements `Default`.
- **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends
- On Wayland, fix coordinates in mouse events when scale factor isn't 1
- On Web, add the ability to provide a custom canvas
- **Breaking:** On Wayland, the `WaylandTheme` struct has been replaced with a `Theme` trait, allowing for extra configuration
# 0.20.0 (2020-01-05)
- On X11, fix `ModifiersChanged` emitting incorrect modifier change events
- **Breaking**: Overhaul how Winit handles DPI:
+ Window functions and events now return `PhysicalSize` instead of `LogicalSize`.
+ Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types.
+ `hidpi_factor` has been renamed to `scale_factor`.
+ `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS
resizes the window in response to the change.
+ On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`.
+ `Size` and `Position` types are now generic over their exact pixel type.
# 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 terminating on `run_return`.
- On Wayland, fix cursor icon updates on window borders when using CSD.
- On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode.
- On Windows, theme the title bar according to whether the system theme is "Light" or "Dark".
- 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.
@@ -68,13 +8,6 @@
- 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)
@@ -102,7 +35,6 @@
- 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.
@@ -119,7 +51,6 @@
reduces the potential for cross-platform compatibility gotchyas.
- On Windows and Linux X11/Wayland, add platform-specific functions for creating an `EventLoop` outside the main thread.
- On Wayland, drop resize events identical to the current window size.
- On Windows, fix window rectangle not getting set correctly on high-DPI systems.
# 0.20.0 Alpha 3 (2019-08-14)
@@ -219,6 +150,20 @@ and `WindowEvent::HoveredFile`.
- On Windows, fix initial dimensions of a fullscreen window.
- On Windows, Fix transparent borderless windows rendering wrong.
- Improve event API documentation.
- Overhaul device event API:
- **Breaking**: `Event::DeviceEvent` split into `MouseEvent`, `KeyboardEvent`, and `GamepadEvent`.
- **Breaking**: Remove `DeviceEvent::Text` variant.
- **Breaking**: `DeviceId` split into `MouseId`, `KeyboardId`, and `GamepadHandle`.
- **Breaking**: Removed device IDs from `WindowEvent` variants.
- Add `enumerate` function on device ID types to list all attached devices of that type.
- Add `is_connected` function on device ID types check if the specified device is still available.
- **Breaking**: On Windows, rename `DeviceIdExtWindows` to `DeviceExtWindows`.
- Add `handle` function to retrieve the underlying `HANDLE`.
- On Windows, fix duplicate device events getting sent if Winit managed multiple windows.
- On Windows, raw mouse events now report Mouse4 and Mouse5 presses and releases.
- Added gamepad support on Windows via raw input and XInput.
# Version 0.19.1 (2019-04-08)
- On Wayland, added a `get_wayland_display` function to `EventsLoopExt`.

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.21.0"
version = "0.20.0-alpha4"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2018"
@@ -25,11 +25,10 @@ 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"
simple_logger = "1"
env_logger = "0.5"
[target.'cfg(target_os = "android")'.dependencies.android_glue]
version = "0.2"
@@ -41,14 +40,18 @@ objc = "0.2.3"
cocoa = "0.19.1"
core-foundation = "0.6"
core-graphics = "0.17.3"
dispatch = "0.2.0"
objc = "0.2.6"
dispatch = "0.1.4"
objc = "0.2.3"
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
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 = [
@@ -56,6 +59,7 @@ features = [
"commctrl",
"dwmapi",
"errhandlingapi",
"hidpi",
"hidusage",
"libloaderapi",
"objbase",
@@ -71,12 +75,12 @@ features = [
"wingdi",
"winnt",
"winuser",
"xinput",
]
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] }
mio = "0.6"
mio-extras = "2.0"
calloop = "0.4.2"
smithay-client-toolkit = "0.6"
x11-dl = "2.18.3"
percent-encoding = "2.0"
@@ -90,7 +94,6 @@ version = "0.3.22"
optional = true
features = [
'console',
'CssStyleDeclaration',
'BeforeUnloadEvent',
'Document',
'DomRect',
@@ -103,9 +106,24 @@ features = [
'KeyboardEvent',
'MouseEvent',
'Node',
'Navigator',
'PointerEvent',
'Window',
'WheelEvent'
'WheelEvent',
'Gamepad',
'GamepadAxisMoveEvent',
'GamepadAxisMoveEventInit',
'GamepadButton',
'GamepadButtonEvent',
'GamepadButtonEventInit',
'GamepadEvent',
'GamepadEventInit',
'GamepadHand',
'GamepadHapticActuator',
'GamepadHapticActuatorType',
'GamepadMappingType',
'GamepadPose',
'GamepadServiceTest'
]
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
@@ -117,6 +135,3 @@ package = "stdweb"
version = "=0.4.20"
optional = true
features = ["experimental_features_which_may_break_on_minor_version_bumps"]
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
console_log = "0.1"

View File

@@ -80,7 +80,6 @@ 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.
@@ -117,7 +116,6 @@ 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
@@ -175,7 +173,6 @@ Legend:
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|

View File

@@ -7,7 +7,7 @@
```toml
[dependencies]
winit = "0.21.0"
winit = "0.20.0-alpha4"
```
## [Documentation](https://docs.rs/winit)
@@ -46,14 +46,12 @@ fn main() {
let window = WindowBuilder::new().build(&event_loop).unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
_ => (),
_ => *control_flow = ControlFlow::Wait,
}
});
}

35
appveyor.yml Normal file
View File

@@ -0,0 +1,35 @@
environment:
matrix:
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
- TARGET: i686-pc-windows-msvc
CHANNEL: stable
- TARGET: x86_64-pc-windows-gnu
CHANNEL: stable
- TARGET: i686-pc-windows-gnu
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
- TARGET: x86_64-pc-windows-gnu
CHANNEL: nightly
- TARGET: i686-pc-windows-gnu
CHANNEL: nightly
matrix:
allow_failures:
- CHANNEL: nightly
install:
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init -yv --default-toolchain %CHANNEL% --default-host %TARGET%
- SET PATH=%PATH%;%USERPROFILE%\.cargo\bin
- SET PATH=%PATH%;C:\MinGW\bin
- rustc -V
- cargo -V
build: false
test_script:
- cargo test --verbose
- cargo test --features serde --verbose
- cargo doc --no-deps

View File

@@ -5,7 +5,6 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
@@ -13,39 +12,31 @@ fn main() {
let mut cursor_idx = 0;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
..
},
..
},
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor_icon(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 {
cursor_idx += 1;
} else {
cursor_idx = 0;
}
event_loop.run(move |event, _, control_flow| match event {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Pressed,
..
}),
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor_icon(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 {
cursor_idx += 1;
} else {
cursor_idx = 0;
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
*control_flow = ControlFlow::Exit;
return;
}
_ => (),
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
*control_flow = ControlFlow::Exit;
return;
}
_ => (),
});
}

View File

@@ -1,11 +1,10 @@
use winit::{
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
event::{DeviceEvent, ElementState, Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
@@ -13,28 +12,22 @@ 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 {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
..
},
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
} => {
}) => {
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),
_ => (),
}
}
@@ -46,7 +39,6 @@ fn main() {
ElementState::Pressed => println!("mouse button {} pressed", button),
ElementState::Released => println!("mouse button {} released", button),
},
DeviceEvent::ModifiersChanged(m) => modifiers = m,
_ => (),
},
_ => (),

View File

@@ -11,7 +11,6 @@ fn main() {
Timer,
}
simple_logger::init().unwrap();
let event_loop = EventLoop::<CustomEvent>::with_user_event();
let _window = WindowBuilder::new()
@@ -32,17 +31,13 @@ fn main() {
}
});
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::UserEvent(event) => println!("user event: {:?}", event),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
event_loop.run(move |event, _, control_flow| match event {
Event::UserEvent(event) => println!("user event: {:?}", event),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
});
}

View File

@@ -5,7 +5,6 @@ use winit::monitor::{MonitorHandle, VideoMode};
use winit::window::{Fullscreen, WindowBuilder};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
@@ -36,15 +35,11 @@ fn main() {
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state,
..
},
WindowEvent::KeyboardInput(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() {

52
examples/gamepad.rs Normal file
View File

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

View File

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

View File

@@ -5,7 +5,6 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
@@ -42,15 +41,11 @@ fn main() {
// closing the window. How to close the window is detailed in the handler for
// the Y key.
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state: Released,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(virtual_code),
state: Released,
..
} => {
}) => {
match virtual_code {
Y => {
if close_requested {

View File

@@ -6,7 +6,6 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
@@ -15,7 +14,6 @@ fn main() {
window.set_max_inner_size(Some(LogicalSize::new(800.0, 400.0)));
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
println!("{:?}", event);
match event {
@@ -23,7 +21,7 @@ fn main() {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
_ => *control_flow = ControlFlow::Wait,
}
});
}

View File

@@ -1,40 +0,0 @@
extern crate winit;
use winit::event::{Event, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
// 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);
}
}
}
_ => (),
}
});
}

View File

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

View File

@@ -1,23 +1,24 @@
#[cfg(not(target_arch = "wasm32"))]
fn main() {
extern crate env_logger;
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use winit::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{CursorIcon, Fullscreen, WindowBuilder},
};
const WINDOW_COUNT: usize = 3;
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
const WINDOW_SIZE: (u32, u32) = (600, 400);
simple_logger::init().unwrap();
env_logger::init();
let event_loop = EventLoop::new();
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
for _ in 0..WINDOW_COUNT {
let window = WindowBuilder::new()
.with_inner_size(WINDOW_SIZE)
.with_inner_size(WINDOW_SIZE.into())
.build(&event_loop)
.unwrap();
@@ -48,19 +49,14 @@ fn main() {
);
}
}
#[allow(deprecated)]
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
},
WindowEvent::KeyboardInput(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),
@@ -81,7 +77,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()))
}
@@ -101,38 +97,31 @@ fn main() {
println!("-> fullscreen : {:?}", window.fullscreen());
}
L => window.set_min_inner_size(match state {
true => Some(WINDOW_SIZE),
true => Some(WINDOW_SIZE.into()),
false => None,
}),
M => window.set_maximized(state),
P => window.set_outer_position({
let mut position = window.outer_position().unwrap();
let sign = if state { 1 } else { -1 };
position.x += 10 * sign;
position.y += 10 * sign;
let sign = if state { 1.0 } else { -1.0 };
position.x += 10.0 * sign;
position.y += 10.0 * sign;
position
}),
Q => window.request_redraw(),
R => window.set_resizable(state),
S => window.set_inner_size(match state {
true => PhysicalSize::new(
WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 100,
),
false => WINDOW_SIZE,
}),
W => {
if let Size::Physical(size) = WINDOW_SIZE.into() {
window
.set_cursor_position(Position::Physical(
PhysicalPosition::new(
size.width as i32 / 2,
size.height as i32 / 2,
),
))
.unwrap()
S => window.set_inner_size(
match state {
true => (WINDOW_SIZE.0 + 100, WINDOW_SIZE.1 + 100),
false => WINDOW_SIZE,
}
}
.into(),
),
W => window
.set_cursor_position(
(WINDOW_SIZE.0 as i32 / 2, WINDOW_SIZE.1 as i32 / 2).into(),
)
.unwrap(),
Z => {
window.set_visible(false);
thread::sleep(Duration::from_secs(1));
@@ -155,22 +144,16 @@ fn main() {
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested
| WindowEvent::Destroyed
| WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
| WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
} => {
}) => {
window_senders.remove(&window_id);
}
_ => {
if let Some(tx) = window_senders.get(&window_id) {
if let Some(event) = event.to_static() {
tx.send(event).unwrap();
}
tx.send(event).unwrap();
}
}
},

View File

@@ -6,7 +6,6 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let mut windows = HashMap::new();
@@ -17,7 +16,6 @@ fn main() {
event_loop.run(move |event, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, window_id } => {
match event {
@@ -31,14 +29,10 @@ fn main() {
*control_flow = ControlFlow::Exit;
}
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
state: ElementState::Pressed,
..
} => {
}) => {
let window = Window::new(&event_loop).unwrap();
windows.insert(window.id(), window);
}

View File

@@ -1,11 +1,13 @@
use instant::Instant;
use std::time::Duration;
use winit::{
event::{ElementState, Event, WindowEvent},
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
@@ -13,26 +15,21 @@ fn main() {
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::MouseInput {
state: ElementState::Released,
..
} => {
window.request_redraw();
}
_ => (),
},
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
}
_ => (),
event_loop.run(move |event, _, control_flow| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::EventsCleared => {
window.request_redraw();
*control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(1, 0))
}
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
println!("{:?}", event);
}
_ => (),
});
}

View File

@@ -1,38 +1,31 @@
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let mut resizable = false;
let window = WindowBuilder::new()
.with_title("Hit space to toggle resizability.")
.with_inner_size(LogicalSize::new(400.0, 200.0))
.with_inner_size((400, 200).into())
.with_resizable(resizable)
.build(&event_loop)
.unwrap();
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 {
input:
KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Released,
..
},
WindowEvent::KeyboardInput(KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Released,
..
} => {
}) => {
resizable = !resizable;
println!("Resizable: {}", resizable);
window.set_resizable(resizable);

View File

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

View File

@@ -5,7 +5,6 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
@@ -17,7 +16,6 @@ fn main() {
window.set_title("A fantastic window!");
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
println!("{:?}", event);
match event {
@@ -25,7 +23,7 @@ fn main() {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
_ => *control_flow = ControlFlow::Wait,
}
});
}

View File

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

View File

@@ -1,74 +0,0 @@
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| {
*control_flow = ControlFlow::Wait;
#[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();
}
_ => (),
}
});
}
#[cfg(feature = "web-sys")]
mod wasm {
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(log::Level::Debug);
super::main();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,17 +5,14 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
println!("{:?}", event);
match event {
@@ -23,10 +20,7 @@ fn main() {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
Event::MainEventsCleared => {
window.request_redraw();
}
_ => (),
_ => *control_flow = ControlFlow::Wait,
}
});
}

View File

@@ -8,23 +8,24 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(LogicalSize::new(100.0, 100.0))
.with_inner_size(LogicalSize::from((100, 100)))
.build(&event_loop)
.unwrap();
eprintln!("debugging keys:");
eprintln!(" (E) Enter exclusive fullscreen");
eprintln!(" (F) Toggle borderless fullscreen");
#[cfg(waiting_for_set_minimized)]
eprintln!(" (M) Toggle minimized");
eprintln!(" (Q) Quit event loop");
eprintln!(" (V) Toggle visibility");
eprintln!(" (X) Toggle maximized");
#[cfg(waiting_for_set_minimized)]
let mut minimized = false;
let mut maximized = false;
let mut visible = true;
@@ -42,6 +43,7 @@ fn main() {
}),
..
} => match key {
#[cfg(waiting_for_set_minimized)]
VirtualKeyCode::M => {
if minimized {
minimized = !minimized;
@@ -66,15 +68,16 @@ fn main() {
..
} => match key {
VirtualKeyCode::E => {
fn area(size: PhysicalSize<u32>) -> u32 {
fn area(size: PhysicalSize) -> f64 {
size.width * size.height
}
let monitor = window.current_monitor();
if let Some(mode) = monitor
.video_modes()
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
{
if let Some(mode) = monitor.video_modes().max_by(|a, b| {
area(a.size())
.partial_cmp(&area(b.size()))
.expect("NaN in video mode size")
}) {
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
} else {
eprintln!("no video modes available");
@@ -88,6 +91,7 @@ fn main() {
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
}
}
#[cfg(waiting_for_set_minimized)]
VirtualKeyCode::M => {
minimized = !minimized;
window.set_minimized(minimized);

View File

@@ -7,8 +7,6 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
// since it seems to work well enough in most cases. Be careful about going too high, or
@@ -29,7 +27,6 @@ fn main() {
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent { event, .. } = event {
use winit::event::WindowEvent::*;
match event {

View File

@@ -18,7 +18,6 @@ fn main() {
};
let mut event_loop = EventLoop::new();
simple_logger::init().unwrap();
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
@@ -28,8 +27,6 @@ fn main() {
while !quit {
event_loop.run_return(|event, _, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{:?}", event);
@@ -41,16 +38,16 @@ fn main() {
..
} => {
quit = true;
}
Event::MainEventsCleared => {
*control_flow = ControlFlow::Exit;
}
_ => (),
Event::EventsCleared => {
*control_flow = ControlFlow::Exit;
}
_ => *control_flow = ControlFlow::Wait,
}
});
// Sleep for 1/60 second to simulate rendering
println!("rendering");
sleep(Duration::from_millis(16));
}
}

View File

@@ -1,498 +1,331 @@
//! UI scaling is important, so read the docs for this module if you don't want to be confused.
//! DPI is important, so read the docs for this module if you don't want to be confused.
//!
//! ## Why should I care about UI scaling?
//! Originally, `winit` dealt entirely in physical pixels (excluding unintentional inconsistencies), but now all
//! window-related functions both produce and consume logical pixels. Monitor-related functions still use physical
//! pixels, as do any context-related functions in `glutin`.
//!
//! Modern computer screens don't have a consistent relationship between resolution and size.
//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens
//! normally being less than a quarter the size of their desktop counterparts. What's more, neither
//! desktop nor mobile screens are consistent resolutions within their own size classes - common
//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K
//! and beyond.
//! If you've never heard of these terms before, then you're not alone, and this documentation will explain the
//! concepts.
//!
//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen,
//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up
//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially
//! problematic with text rendering, where quarter-sized text becomes a significant legibility
//! problem.
//! Modern screens have a defined physical resolution, most commonly 1920x1080. Indepedent of that is the amount of
//! space the screen occupies, which is to say, the height and width in millimeters. The relationship between these two
//! measurements is the *pixel density*. Mobile screens require a high pixel density, as they're held close to the
//! eyes. Larger displays also require a higher pixel density, hence the growing presence of 1440p and 4K displays.
//!
//! Failure to account for the scale factor can create a significantly degraded user experience.
//! Most notably, it can make users feel like they have bad eyesight, which will potentially cause
//! them to think about growing elderly, resulting in them having an existential crisis. Once users
//! enter that state, they will no longer be focused on your application.
//! So, this presents a problem. Let's say we want to render a square 100px button. It will occupy 100x100 of the
//! screen's pixels, which in many cases, seems perfectly fine. However, because this size doesn't account for the
//! screen's dimensions or pixel density, the button's size can vary quite a bit. On a 4K display, it would be unusably
//! small.
//!
//! ## How should I handle it?
//! That's a description of what happens when the button is 100x100 *physical* pixels. Instead, let's try using 100x100
//! *logical* pixels. To map logical pixels to physical pixels, we simply multiply by the DPI (dots per inch) factor.
//! On a "typical" desktop display, the DPI factor will be 1.0, so 100x100 logical pixels equates to 100x100 physical
//! pixels. However, a 1440p display may have a DPI factor of 1.25, so the button is rendered as 125x125 physical pixels.
//! Ideally, the button now has approximately the same perceived size across varying displays.
//!
//! The solution to this problem is to account for the device's *scale factor*. The scale factor is
//! the factor UI elements should be scaled by to be consistent with the rest of the user's system -
//! for example, a button that's normally 50 pixels across would be 100 pixels across on a device
//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`.
//! Failure to account for the DPI factor can create a badly degraded user experience. Most notably, it can make users
//! feel like they have bad eyesight, which will potentially cause them to think about growing elderly, resulting in
//! them entering an existential panic. Once users enter that state, they will no longer be focused on your application.
//!
//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's
//! usually a mistake, since there's no consistent mapping between the scale factor and the screen's
//! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather
//! than any DPI-dependent units.
//! There are two ways to get the DPI factor:
//! - You can track the [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) event of your
//! windows. This event is sent any time the DPI factor changes, either because the window moved to another monitor,
//! or because the user changed the configuration of their screen.
//! - You can also retrieve the DPI factor of a monitor by calling
//! [`MonitorHandle::hidpi_factor`](crate::monitor::MonitorHandle::hidpi_factor), or the
//! current DPI factor applied to a window by calling
//! [`Window::hidpi_factor`](crate::window::Window::hidpi_factor), which is roughly equivalent
//! to `window.current_monitor().hidpi_factor()`.
//!
//! ### Position and Size types
//! Depending on the platform, the window's actual DPI factor may only be known after
//! the event loop has started and your window has been drawn once. To properly handle these cases,
//! the most robust way is to monitor the [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged)
//! event and dynamically adapt your drawing logic to follow the DPI factor.
//!
//! Winit's `Physical(Position|Size)` types correspond with the actual pixels on the device, and the
//! `Logical(Position|Size)` types correspond to the physical pixels divided by the scale factor.
//! All of Winit's functions return physical types, but can take either logical or physical
//! coordinates as input, allowing you to use the most convenient coordinate system for your
//! particular application.
//! Here's an overview of what sort of DPI factors you can expect, and where they come from:
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the display settings.
//! While users are free to select any option they want, they're only given a selection of "nice" DPI factors, i.e.
//! 1.0, 1.25, 1.5... on Windows 7, the DPI factor is global and changing it requires logging out.
//! - **macOS:** The buzzword is "retina displays", which have a DPI factor of 2.0. Otherwise, the DPI factor is 1.0.
//! Intermediate DPI factors are never used, thus 1440p displays/etc. aren't properly supported. It's possible for any
//! display to use that 2.0 DPI factor, given the use of the command line.
//! - **X11:** On X11, we calculate the DPI factor based on the millimeter dimensions provided by XRandR. This can
//! result in a wide range of possible values, including some interesting ones like 1.0833333333333333. This can be
//! overridden using the `WINIT_HIDPI_FACTOR` environment variable, though that's not recommended.
//! - **Wayland:** On Wayland, DPI factors are set per-screen by the server, and are always integers (most often 1 or 2).
//! - **iOS:** DPI factors are both constant and device-specific on iOS.
//! - **Android:** This feature isn't yet implemented on Android, so the DPI factor will always be returned as 1.0.
//! - **Web:** DPI factors are handled by the browser and will always be 1.0 for your application.
//!
//! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
//! will truncate the fractional part of the float, rather than properly round to the nearest
//! integer. Use the provided `cast` function or `From`/`Into` conversions, which handle the
//! rounding properly. Note that precision loss will still occur when rounding from a float to an
//! int, although rounding lessens the problem.
//! The window's logical size is conserved across DPI changes, resulting in the physical size changing instead. This
//! may be surprising on X11, but is quite standard elsewhere. Physical size changes always produce a
//! [`Resized`](crate::event::WindowEvent::Resized) event, even on platforms where no resize actually occurs,
//! such as macOS and Wayland. As a result, it's not necessary to separately handle
//! [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) if you're only listening for size.
//!
//! ### Events
//! Your GPU has no awareness of the concept of logical pixels, and unless you like wasting pixel density, your
//! framebuffer's size should be in physical pixels.
//!
//! Winit will dispatch a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged)
//! event whenever a window's scale factor has changed. This can happen if the user drags their
//! window from a standard-resolution monitor to a high-DPI monitor, or if the user changes their
//! DPI settings. This gives you a chance to rescale your application's UI elements and adjust how
//! the platform changes the window's size to reflect the new scale factor. If a window hasn't
//! received a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged) event,
//! then its scale factor is `1.0`.
//! `winit` will send [`Resized`](crate::event::WindowEvent::Resized) events whenever a window's logical size
//! changes, and [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) events
//! whenever the DPI factor changes. Receiving either of these events means that the physical size of your window has
//! changed, and you should recompute it using the latest values you received for each. If the logical size and the
//! DPI factor change simultaneously, `winit` will send both events together; thus, it's recommended to buffer
//! these events and process them at the end of the queue.
//!
//! ## How is the scale factor calculated?
//!
//! Scale factor is calculated differently on different platforms:
//!
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the
//! display settings. While users are free to select any option they want, they're only given a
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
//! global and changing it requires logging out. See [this article][windows_1] for technical
//! details.
//! - **macOS:** "retina displays" have a scale factor of 2.0. Otherwise, the scale factor is 1.0.
//! Intermediate scale factors are never used. It's possible for any display to use that 2.0 scale
//! factor, given the use of the command line.
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
//! currently uses a three-pronged approach:
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
//! + If not present, use the value set in `Xft.dpi` in Xresources.
//! + Otherwise, calcuate the scale factor based on the millimeter monitor dimensions provided by XRandR.
//!
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
//! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always
//! integers (most often 1 or 2).
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
//! information.
//! - **Android:** Scale factors are set by the manufacturer to the value that best suits the
//! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information.
//! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels.
//!
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities
//! If you never received any [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) events,
//! then your window's DPI factor is 1.
pub trait Pixel: Copy + Into<f64> {
fn from_f64(f: f64) -> Self;
fn cast<P: Pixel>(self) -> P {
P::from_f64(self.into())
}
}
impl Pixel for u8 {
fn from_f64(f: f64) -> Self {
f.round() as u8
}
}
impl Pixel for u16 {
fn from_f64(f: f64) -> Self {
f.round() as u16
}
}
impl Pixel for u32 {
fn from_f64(f: f64) -> Self {
f.round() as u32
}
}
impl Pixel for i8 {
fn from_f64(f: f64) -> Self {
f.round() as i8
}
}
impl Pixel for i16 {
fn from_f64(f: f64) -> Self {
f.round() as i16
}
}
impl Pixel for i32 {
fn from_f64(f: f64) -> Self {
f.round() as i32
}
}
impl Pixel for f32 {
fn from_f64(f: f64) -> Self {
f as f32
}
}
impl Pixel for f64 {
fn from_f64(f: f64) -> Self {
f
}
}
/// Checks that the scale factor is a normal positive `f64`.
/// Checks that the DPI factor is a normal positive `f64`.
///
/// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from
/// All functions that take a DPI factor assert that this will return `true`. If you're sourcing DPI factors from
/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
/// otherwise, you risk panics.
#[inline]
pub fn validate_scale_factor(dpi_factor: f64) -> bool {
pub fn validate_hidpi_factor(dpi_factor: f64) -> bool {
dpi_factor.is_sign_positive() && dpi_factor.is_normal()
}
/// A position represented in logical pixels.
///
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>`
/// implementation is provided which does the rounding for you.
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalPosition<P> {
pub x: P,
pub y: P,
pub struct LogicalPosition {
pub x: f64,
pub y: f64,
}
impl<P> LogicalPosition<P> {
impl LogicalPosition {
#[inline]
pub const fn new(x: P, y: P) -> Self {
pub fn new(x: f64, y: f64) -> Self {
LogicalPosition { x, y }
}
}
impl<P: Pixel> LogicalPosition<P> {
#[inline]
pub fn from_physical<T: Into<PhysicalPosition<X>>, X: Pixel>(
physical: T,
dpi_factor: f64,
) -> Self {
pub fn from_physical<T: Into<PhysicalPosition>>(physical: T, dpi_factor: f64) -> Self {
physical.into().to_logical(dpi_factor)
}
#[inline]
pub fn to_physical<X: Pixel>(&self, dpi_factor: f64) -> PhysicalPosition<X> {
assert!(validate_scale_factor(dpi_factor));
let x = self.x.into() * dpi_factor;
let y = self.y.into() * dpi_factor;
PhysicalPosition::new(x, y).cast()
pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition {
assert!(validate_hidpi_factor(dpi_factor));
let x = self.x * dpi_factor;
let y = self.y * dpi_factor;
PhysicalPosition::new(x, y)
}
}
impl From<(f64, f64)> for LogicalPosition {
#[inline]
pub fn cast<X: Pixel>(&self) -> LogicalPosition<X> {
LogicalPosition {
x: self.x.cast(),
y: self.y.cast(),
}
fn from((x, y): (f64, f64)) -> Self {
Self::new(x, y)
}
}
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> {
fn from((x, y): (X, X)) -> LogicalPosition<P> {
LogicalPosition::new(x.cast(), y.cast())
impl From<(i32, i32)> for LogicalPosition {
#[inline]
fn from((x, y): (i32, i32)) -> Self {
Self::new(x as f64, y as f64)
}
}
impl<P: Pixel, X: Pixel> Into<(X, X)> for LogicalPosition<P> {
fn into(self: Self) -> (X, X) {
(self.x.cast(), self.y.cast())
impl Into<(f64, f64)> for LogicalPosition {
#[inline]
fn into(self) -> (f64, f64) {
(self.x, self.y)
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> {
fn from([x, y]: [X; 2]) -> LogicalPosition<P> {
LogicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalPosition<P> {
fn into(self: Self) -> [X; 2] {
[self.x.cast(), self.y.cast()]
impl Into<(i32, i32)> for LogicalPosition {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (i32, i32) {
(self.x.round() as _, self.y.round() as _)
}
}
/// A position represented in physical pixels.
///
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalPosition<P> {
pub x: P,
pub y: P,
pub struct PhysicalPosition {
pub x: f64,
pub y: f64,
}
impl<P> PhysicalPosition<P> {
impl PhysicalPosition {
#[inline]
pub const fn new(x: P, y: P) -> Self {
pub fn new(x: f64, y: f64) -> Self {
PhysicalPosition { x, y }
}
}
impl<P: Pixel> PhysicalPosition<P> {
#[inline]
pub fn from_logical<T: Into<LogicalPosition<X>>, X: Pixel>(
logical: T,
dpi_factor: f64,
) -> Self {
pub fn from_logical<T: Into<LogicalPosition>>(logical: T, dpi_factor: f64) -> Self {
logical.into().to_physical(dpi_factor)
}
#[inline]
pub fn to_logical<X: Pixel>(&self, dpi_factor: f64) -> LogicalPosition<X> {
assert!(validate_scale_factor(dpi_factor));
let x = self.x.into() / dpi_factor;
let y = self.y.into() / dpi_factor;
LogicalPosition::new(x, y).cast()
pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition {
assert!(validate_hidpi_factor(dpi_factor));
let x = self.x / dpi_factor;
let y = self.y / dpi_factor;
LogicalPosition::new(x, y)
}
}
impl From<(f64, f64)> for PhysicalPosition {
#[inline]
pub fn cast<X: Pixel>(&self) -> PhysicalPosition<X> {
PhysicalPosition {
x: self.x.cast(),
y: self.y.cast(),
}
fn from((x, y): (f64, f64)) -> Self {
Self::new(x, y)
}
}
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> {
fn from((x, y): (X, X)) -> PhysicalPosition<P> {
PhysicalPosition::new(x.cast(), y.cast())
impl From<(i32, i32)> for PhysicalPosition {
#[inline]
fn from((x, y): (i32, i32)) -> Self {
Self::new(x as f64, y as f64)
}
}
impl<P: Pixel, X: Pixel> Into<(X, X)> for PhysicalPosition<P> {
fn into(self: Self) -> (X, X) {
(self.x.cast(), self.y.cast())
impl Into<(f64, f64)> for PhysicalPosition {
#[inline]
fn into(self) -> (f64, f64) {
(self.x, self.y)
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> {
fn from([x, y]: [X; 2]) -> PhysicalPosition<P> {
PhysicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalPosition<P> {
fn into(self: Self) -> [X; 2] {
[self.x.cast(), self.y.cast()]
impl Into<(i32, i32)> for PhysicalPosition {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (i32, i32) {
(self.x.round() as _, self.y.round() as _)
}
}
/// A size represented in logical pixels.
///
/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalSize<P> {
pub width: P,
pub height: P,
pub struct LogicalSize {
pub width: f64,
pub height: f64,
}
impl<P> LogicalSize<P> {
impl LogicalSize {
#[inline]
pub const fn new(width: P, height: P) -> Self {
pub fn new(width: f64, height: f64) -> Self {
LogicalSize { width, height }
}
}
impl<P: Pixel> LogicalSize<P> {
#[inline]
pub fn from_physical<T: Into<PhysicalSize<X>>, X: Pixel>(physical: T, dpi_factor: f64) -> Self {
pub fn from_physical<T: Into<PhysicalSize>>(physical: T, dpi_factor: f64) -> Self {
physical.into().to_logical(dpi_factor)
}
#[inline]
pub fn to_physical<X: Pixel>(&self, dpi_factor: f64) -> PhysicalSize<X> {
assert!(validate_scale_factor(dpi_factor));
let width = self.width.into() * dpi_factor;
let height = self.height.into() * dpi_factor;
PhysicalSize::new(width, height).cast()
pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize {
assert!(validate_hidpi_factor(dpi_factor));
let width = self.width * dpi_factor;
let height = self.height * dpi_factor;
PhysicalSize::new(width, height)
}
}
impl From<(f64, f64)> for LogicalSize {
#[inline]
pub fn cast<X: Pixel>(&self) -> LogicalSize<X> {
LogicalSize {
width: self.width.cast(),
height: self.height.cast(),
}
fn from((width, height): (f64, f64)) -> Self {
Self::new(width, height)
}
}
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> {
fn from((x, y): (X, X)) -> LogicalSize<P> {
LogicalSize::new(x.cast(), y.cast())
impl From<(u32, u32)> for LogicalSize {
#[inline]
fn from((width, height): (u32, u32)) -> Self {
Self::new(width as f64, height as f64)
}
}
impl<P: Pixel, X: Pixel> Into<(X, X)> for LogicalSize<P> {
fn into(self: LogicalSize<P>) -> (X, X) {
(self.width.cast(), self.height.cast())
impl Into<(f64, f64)> for LogicalSize {
#[inline]
fn into(self) -> (f64, f64) {
(self.width, self.height)
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalSize<P> {
fn from([x, y]: [X; 2]) -> LogicalSize<P> {
LogicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalSize<P> {
fn into(self: Self) -> [X; 2] {
[self.width.cast(), self.height.cast()]
impl Into<(u32, u32)> for LogicalSize {
/// Note that this rounds instead of truncating.
#[inline]
fn into(self) -> (u32, u32) {
(self.width.round() as _, self.height.round() as _)
}
}
/// A size represented in physical pixels.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
///
/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part,
/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which
/// does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalSize<P> {
pub width: P,
pub height: P,
pub struct PhysicalSize {
pub width: f64,
pub height: f64,
}
impl<P> PhysicalSize<P> {
impl PhysicalSize {
#[inline]
pub const fn new(width: P, height: P) -> Self {
pub fn new(width: f64, height: f64) -> Self {
PhysicalSize { width, height }
}
}
impl<P: Pixel> PhysicalSize<P> {
#[inline]
pub fn from_logical<T: Into<LogicalSize<X>>, X: Pixel>(logical: T, dpi_factor: f64) -> Self {
pub fn from_logical<T: Into<LogicalSize>>(logical: T, dpi_factor: f64) -> Self {
logical.into().to_physical(dpi_factor)
}
#[inline]
pub fn to_logical<X: Pixel>(&self, dpi_factor: f64) -> LogicalSize<X> {
assert!(validate_scale_factor(dpi_factor));
let width = self.width.into() / dpi_factor;
let height = self.height.into() / dpi_factor;
LogicalSize::new(width, height).cast()
pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize {
assert!(validate_hidpi_factor(dpi_factor));
let width = self.width / dpi_factor;
let height = self.height / dpi_factor;
LogicalSize::new(width, height)
}
}
impl From<(f64, f64)> for PhysicalSize {
#[inline]
pub fn cast<X: Pixel>(&self) -> PhysicalSize<X> {
PhysicalSize {
width: self.width.cast(),
height: self.height.cast(),
}
fn from((width, height): (f64, f64)) -> Self {
Self::new(width, height)
}
}
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> {
fn from((x, y): (X, X)) -> PhysicalSize<P> {
PhysicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> Into<(X, X)> for PhysicalSize<P> {
fn into(self: Self) -> (X, X) {
(self.width.cast(), self.height.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalSize<P> {
fn from([x, y]: [X; 2]) -> PhysicalSize<P> {
PhysicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalSize<P> {
fn into(self: Self) -> [X; 2] {
[self.width.cast(), self.height.cast()]
}
}
/// A size that's either physical or logical.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Size {
Physical(PhysicalSize<u32>),
Logical(LogicalSize<f64>),
}
impl Size {
pub fn new<S: Into<Size>>(size: S) -> Size {
size.into()
}
pub fn to_logical<P: Pixel>(&self, dpi_factor: f64) -> LogicalSize<P> {
match *self {
Size::Physical(size) => size.to_logical(dpi_factor),
Size::Logical(size) => size.cast(),
}
}
pub fn to_physical<P: Pixel>(&self, dpi_factor: f64) -> PhysicalSize<P> {
match *self {
Size::Physical(size) => size.cast(),
Size::Logical(size) => size.to_physical(dpi_factor),
}
}
}
impl<P: Pixel> From<PhysicalSize<P>> for Size {
impl From<(u32, u32)> for PhysicalSize {
#[inline]
fn from(size: PhysicalSize<P>) -> Size {
Size::Physical(size.cast())
fn from((width, height): (u32, u32)) -> Self {
Self::new(width as f64, height as f64)
}
}
impl<P: Pixel> From<LogicalSize<P>> for Size {
impl Into<(f64, f64)> for PhysicalSize {
#[inline]
fn from(size: LogicalSize<P>) -> Size {
Size::Logical(size.cast())
fn into(self) -> (f64, f64) {
(self.width, self.height)
}
}
/// A position that's either physical or logical.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Position {
Physical(PhysicalPosition<i32>),
Logical(LogicalPosition<f64>),
}
impl Position {
pub fn new<S: Into<Position>>(position: S) -> Position {
position.into()
}
pub fn to_logical<P: Pixel>(&self, dpi_factor: f64) -> LogicalPosition<P> {
match *self {
Position::Physical(position) => position.to_logical(dpi_factor),
Position::Logical(position) => position.cast(),
}
}
pub fn to_physical<P: Pixel>(&self, dpi_factor: f64) -> PhysicalPosition<P> {
match *self {
Position::Physical(position) => position.cast(),
Position::Logical(position) => position.to_physical(dpi_factor),
}
}
}
impl<P: Pixel> From<PhysicalPosition<P>> for Position {
impl Into<(u32, u32)> for PhysicalSize {
/// Note that this rounds instead of truncating.
#[inline]
fn from(position: PhysicalPosition<P>) -> Position {
Position::Physical(position.cast())
}
}
impl<P: Pixel> From<LogicalPosition<P>> for Position {
#[inline]
fn from(position: LogicalPosition<P>) -> Position {
Position::Logical(position.cast())
fn into(self) -> (u32, u32) {
(self.width.round() as _, self.height.round() as _)
}
}

View File

@@ -3,156 +3,73 @@
//! These are sent to the closure given to [`EventLoop::run(...)`][event_loop_run], where they get
//! processed and used to modify the program state. For more details, see the root-level documentation.
//!
//! Some of these events represent different "parts" of a traditional event-handling loop. You could
//! approximate the basic ordering loop of [`EventLoop::run(...)`][event_loop_run] like this:
//!
//! ```rust,ignore
//! let mut control_flow = ControlFlow::Poll;
//! let mut start_cause = StartCause::Init;
//!
//! while control_flow != ControlFlow::Exit {
//! event_handler(NewEvents(start_cause), ..., &mut control_flow);
//!
//! for e in (window events, user events, device events) {
//! event_handler(e, ..., &mut control_flow);
//! }
//! event_handler(MainEventsCleared, ..., &mut control_flow);
//!
//! for w in (redraw windows) {
//! event_handler(RedrawRequested(w), ..., &mut control_flow);
//! }
//! event_handler(RedrawEventsCleared, ..., &mut control_flow);
//!
//! start_cause = wait_if_necessary(control_flow);
//! }
//!
//! event_handler(LoopDestroyed, ..., &mut control_flow);
//! ```
//!
//! This leaves out timing details like `ControlFlow::WaitUntil` but hopefully
//! describes what happens in what order.
//!
//! [event_loop_run]: crate::event_loop::EventLoop::run
use instant::Instant;
use std::path::PathBuf;
use crate::{
dpi::{LogicalPosition, PhysicalPosition, PhysicalSize},
platform_impl,
window::{Theme, WindowId},
dpi::{LogicalPosition, LogicalSize},
window::WindowId,
};
/// Describes a generic event.
///
/// See the module-level docs for more information on the event loop manages each event.
#[derive(Debug, PartialEq)]
pub enum Event<'a, T: 'static> {
/// Emitted when new events arrive from the OS to be processed.
///
/// This event type is useful as a place to put code that should be done before you start
/// processing events, such as updating frame timing information for benchmarking or checking
/// the [`StartCause`][crate::event::StartCause] to see if a timer set by
/// [`ControlFlow::WaitUntil`](crate::event_loop::ControlFlow::WaitUntil) has elapsed.
NewEvents(StartCause),
pub mod device;
/// A generic event.
#[derive(Clone, Debug, PartialEq)]
pub enum Event<T> {
/// Emitted when the OS sends an event to a winit window.
WindowEvent {
window_id: WindowId,
event: WindowEvent<'a>,
event: WindowEvent,
},
/// Emitted when the OS sends an event to a device.
DeviceEvent {
device_id: DeviceId,
event: DeviceEvent,
},
/// 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`](crate::event_loop::EventLoopProxy::send_event)
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](../event_loop/struct.EventLoopProxy.html#method.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 the event loop is being shut down. This is irreversable - if this event is
/// emitted, it is guaranteed to be the last event emitted.
LoopDestroyed,
/// Emitted when the application has been suspended.
Suspended,
/// Emitted when the application has been resumed.
Resumed,
/// Emitted when all of the event loop's input events have been processed and redraw processing
/// is about to begin.
///
/// This event is useful as a place to put your code that should be run after all
/// state-changing events have been handled and you want to do stuff (updating state, performing
/// calculations, etc) that happens as the "main body" of your event loop. If your program draws
/// graphics, it's usually better to do it in response to
/// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted
/// immediately after this event.
MainEventsCleared,
/// Emitted after `MainEventsCleared` when a window should be redrawn.
///
/// This gets triggered in two scenarios:
/// - The OS has performed an operation that's invalidated the window's contents (such as
/// resizing the window).
/// - The application has explicitly requested a redraw via
/// [`Window::request_redraw`](crate::window::Window::request_redraw).
///
/// During each iteration of the event loop, Winit will aggregate duplicate redraw requests
/// into a single event, to help avoid duplicating rendering work.
RedrawRequested(WindowId),
/// Emitted after all `RedrawRequested` events have been processed and control flow is about to
/// be taken away from the program. If there are no `RedrawRequested` events, it is emitted
/// immediately after `MainEventsCleared`.
///
/// This event is useful for doing any cleanup or bookkeeping work after all the rendering
/// tasks have been completed.
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 that
/// gets emitted. You generally want to treat this as an "do on quit" event.
LoopDestroyed,
}
impl<'a, T> Event<'a, T> {
pub fn map_nonuser_event<U>(self) -> Result<Event<'a, U>, Event<'a, T>> {
impl<T> Event<T> {
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
use self::Event::*;
match self {
UserEvent(_) => Err(self),
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_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)),
NewEvents(cause) => Ok(NewEvents(cause)),
MainEventsCleared => Ok(MainEventsCleared),
RedrawRequested(wid) => Ok(RedrawRequested(wid)),
RedrawEventsCleared => Ok(RedrawEventsCleared),
EventsCleared => Ok(EventsCleared),
LoopDestroyed => Ok(LoopDestroyed),
Suspended => Ok(Suspended),
Resumed => Ok(Resumed),
}
}
/// If the event doesn't contain a reference, turn it into an event with a `'static` lifetime.
/// Otherwise, return `None`.
pub fn to_static(self) -> Option<Event<'static, T>> {
use self::Event::*;
match self {
WindowEvent { window_id, event } => event
.to_static()
.map(|event| WindowEvent { window_id, event }),
UserEvent(_) => None,
DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }),
NewEvents(cause) => Some(NewEvents(cause)),
MainEventsCleared => Some(MainEventsCleared),
RedrawRequested(wid) => Some(RedrawRequested(wid)),
RedrawEventsCleared => Some(RedrawEventsCleared),
LoopDestroyed => Some(LoopDestroyed),
Suspended => Some(Suspended),
Resumed => Some(Resumed),
}
}
}
/// Describes the reason the event loop is resuming.
/// 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
@@ -178,14 +95,14 @@ pub enum StartCause {
Init,
}
/// Describes an event from a `Window`.
#[derive(Debug, PartialEq)]
pub enum WindowEvent<'a> {
/// 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.
Resized(PhysicalSize<u32>),
Resized(LogicalSize),
/// The position of the window has changed. Contains the window's new position.
Moved(PhysicalPosition<u32>),
Moved(LogicalPosition),
/// The window has been requested to close.
CloseRequested,
@@ -220,54 +137,34 @@ pub enum WindowEvent<'a> {
Focused(bool),
/// An event from the keyboard has been received.
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,
},
KeyboardInput(KeyboardInput),
/// 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: PhysicalPosition<f64>,
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
position: LogicalPosition,
modifiers: ModifiersState,
},
/// The cursor has entered the window.
CursorEntered { device_id: DeviceId },
CursorEntered,
/// The cursor has left the window.
CursorLeft { device_id: DeviceId },
CursorLeft,
/// 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,
},
@@ -276,210 +173,27 @@ pub enum WindowEvent<'a> {
/// 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 {
device_id: DeviceId,
pressure: f32,
stage: i64,
},
TouchpadPressure { pressure: f32, stage: i64 },
/// Motion on some analog axis. May report data redundant to other, more specific events.
AxisMotion {
device_id: DeviceId,
axis: AxisId,
value: f64,
},
/// The OS or application has requested that the window be redrawn.
RedrawRequested,
/// Touch event has been received
Touch(Touch),
/// The window's scale factor has changed.
/// The DPI factor of the window has changed.
///
/// The following user actions can cause DPI changes:
///
/// * Changing the display's resolution.
/// * Changing the display's scale factor (e.g. in Control Panel on Windows).
/// * Moving the window to a display with a different scale factor.
///
/// After this event callback has been processed, the window will be resized to whatever value
/// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested
/// by the OS, but it can be changed to any value.
/// * Changing the display's DPI factor (e.g. in Control Panel on Windows).
/// * Moving the window to a display with a different DPI factor.
///
/// For more information about DPI in general, see the [`dpi`](crate::dpi) module.
ScaleFactorChanged {
scale_factor: f64,
new_inner_size: &'a mut PhysicalSize<u32>,
},
/// 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),
HiDpiFactorChanged(f64),
}
impl<'a> WindowEvent<'a> {
pub fn to_static(self) -> Option<WindowEvent<'static>> {
use self::WindowEvent::*;
match self {
Resized(size) => Some(Resized(size)),
Moved(position) => Some(Moved(position)),
CloseRequested => Some(CloseRequested),
Destroyed => Some(Destroyed),
DroppedFile(file) => Some(DroppedFile(file)),
HoveredFile(file) => Some(HoveredFile(file)),
HoveredFileCancelled => Some(HoveredFileCancelled),
ReceivedCharacter(c) => Some(ReceivedCharacter(c)),
Focused(focused) => Some(Focused(focused)),
KeyboardInput {
device_id,
input,
is_synthetic,
} => Some(KeyboardInput {
device_id,
input,
is_synthetic,
}),
#[allow(deprecated)]
CursorMoved {
device_id,
position,
modifiers,
} => Some(CursorMoved {
device_id,
position,
modifiers,
}),
CursorEntered { device_id } => Some(CursorEntered { device_id }),
CursorLeft { device_id } => Some(CursorLeft { device_id }),
#[allow(deprecated)]
MouseWheel {
device_id,
delta,
phase,
modifiers,
} => Some(MouseWheel {
device_id,
delta,
phase,
modifiers,
}),
#[allow(deprecated)]
MouseInput {
device_id,
state,
button,
modifiers,
} => Some(MouseInput {
device_id,
state,
button,
modifiers,
}),
TouchpadPressure {
device_id,
pressure,
stage,
} => Some(TouchpadPressure {
device_id,
pressure,
stage,
}),
AxisMotion {
device_id,
axis,
value,
} => Some(AxisMotion {
device_id,
axis,
value,
}),
Touch(touch) => Some(Touch(touch)),
ThemeChanged(theme) => Some(ThemeChanged(theme)),
ScaleFactorChanged { .. } => None,
}
}
}
/// 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.
/// A keyboard input event.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyboardInput {
@@ -502,17 +216,19 @@ 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,
}
/// Describes touch-screen input state.
/// Touch 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,
}
@@ -534,9 +250,8 @@ pub enum TouchPhase {
/// device against their face.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Touch {
pub device_id: DeviceId,
pub phase: TouchPhase,
pub location: PhysicalPosition<f64>,
pub location: LogicalPosition,
/// Describes how hard the screen was pressed. May be `None` if the platform
/// does not support pressure sensitivity.
///
@@ -545,6 +260,8 @@ 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,
}
@@ -613,16 +330,16 @@ pub type AxisId = u32;
/// Identifier for a specific button on some device.
pub type ButtonId = u32;
/// Describes the input state of a key.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
/// The input state of a key or button.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ElementState {
Pressed,
Released,
}
/// Describes a button of a mouse controller.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
/// A button on a mouse.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseButton {
Left,
@@ -631,7 +348,7 @@ pub enum MouseButton {
Other(u8),
}
/// Describes a difference in the mouse scroll wheel state.
/// A difference in the mouse scroll wheel state.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseScrollDelta {
@@ -647,10 +364,10 @@ pub enum MouseScrollDelta {
/// Scroll events are expressed as a PixelDelta if
/// supported by the device (eg. a touchpad) and
/// platform.
PixelDelta(LogicalPosition<f64>),
PixelDelta(LogicalPosition),
}
/// Symbolic name for a keyboard key.
/// Symbolic name of a keyboard key.
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -849,99 +566,21 @@ pub enum VirtualKeyCode {
Cut,
}
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
/// 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
///
/// 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)
}
}
/// This is the "windows" key on PC and "command" key on Mac.
pub logo: bool,
}

363
src/event/device.rs Normal file
View File

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

View File

@@ -59,7 +59,7 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
/// Set by the user callback given to the `EventLoop::run` method.
///
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`][events_cleared]
/// Indicates the desired behavior of the event loop after [`Event::EventsCleared`][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::RedrawEventsCleared
/// [events_cleared]: crate::event::Event::EventsCleared
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
@@ -143,7 +143,7 @@ impl<T> EventLoop<T> {
#[inline]
pub fn run<F>(self, event_handler: F) -> !
where
F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
F: 'static + FnMut(Event<T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
{
self.event_loop.run(event_handler)
}
@@ -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<T>> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.event_loop_proxy.send_event(event)
}
}
@@ -211,14 +211,18 @@ 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. Contains the original event given to `send_event`.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventLoopClosed<T>(pub T);
/// no longer exists.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct EventLoopClosed;
impl<T> fmt::Display for EventLoopClosed<T> {
impl fmt::Display for EventLoopClosed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Tried to wake up a closed `EventLoop`")
write!(f, "{}", error::Error::description(self))
}
}
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
impl error::Error for EventLoopClosed {
fn description(&self) -> &str {
"Tried to wake up a closed `EventLoop`"
}
}

View File

@@ -29,26 +29,31 @@ pub enum BadIcon {
impl fmt::Display for BadIcon {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
let msg = match self {
&BadIcon::ByteCountNotDivisibleBy4 { byte_count } => format!(
"The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
byte_count,
),
BadIcon::DimensionsVsPixelCount {
&BadIcon::DimensionsVsPixelCount {
width,
height,
width_x_height,
pixel_count,
} => write!(f,
} => format!(
"The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.",
width, height, pixel_count, width_x_height,
),
}
};
write!(f, "{}", msg)
}
}
impl Error for BadIcon {
fn source(&self) -> Option<&(dyn Error + 'static)> {
fn description(&self) -> &str {
"A valid icon cannot be created from these arguments"
}
fn cause(&self) -> Option<&dyn Error> {
Some(self)
}
}

View File

@@ -1,6 +1,6 @@
//! Winit is a cross-platform window creation and event loop management library.
//! Winit allows you to build a window on as many platforms as possible.
//!
//! # Building windows
//! # Building a window
//!
//! Before you can build a [`Window`], you first need to build an [`EventLoop`]. This is done with the
//! [`EventLoop::new()`] function.
@@ -15,31 +15,26 @@
//! - Calling [`Window::new(&event_loop)`][window_new].
//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
//!
//! The first method is the simplest, and will give you default values for everything. The second
//! method allows you to customize the way your [`Window`] will look and behave by modifying the
//! fields of the [`WindowBuilder`] object before you create the [`Window`].
//! The first way is the simplest way and will give you default values for everything.
//!
//! The second way allows you to customize the way your [`Window`] will look and behave by modifying
//! the fields of the [`WindowBuilder`] object before you create the [`Window`].
//!
//! # Event handling
//!
//! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can
//! generate [`WindowEvent`]s when certain input events occur, such as a cursor moving over the
//! window or a key getting pressed while the window is focused. Devices can generate
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired.
//! generate a [`WindowEvent`] when certain things happen, like whenever the user moves their mouse
//! or presses a key inside the [`Window`]. Devices can generate a [`DeviceEvent`] directly as well,
//! which contains unfiltered event data that isn't specific to a certain window. Some user
//! activity, like mouse movement, can generate both a [`WindowEvent`] *and* a [`DeviceEvent`]. You
//! can also create and handle your own custom [`UserEvent`]s, if desired.
//!
//! You can retreive events by calling [`EventLoop::run`][event_loop_run]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until the `control_flow` argument given to the closure is set to
//! [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`] is emitted and the
//! entire program terminates.
//!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on web and mobile platforms and works poorly on
//! most desktop platforms. However, this model can be re-implemented to an extent on desktops with
//! [`EventLoopExtDesktop::run_return`]. See that method's documentation for more reasons about why
//! it's discouraged, beyond mobile/web compatibility reasons.
//! Events can be retreived by using an [`EventLoop`]. A [`Window`] will send its events to the
//! [`EventLoop`] object it was created with.
//!
//! You do this by calling [`event_loop.run(...)`][event_loop_run]. This function will run forever
//! unless `control_flow` is set to [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`]
//! is emitted and the entire program terminates.
//!
//! ```no_run
//! use winit::{
@@ -52,16 +47,23 @@
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
//!
//! event_loop.run(move |event, _, control_flow| {
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
//! *control_flow = ControlFlow::Poll;
//!
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
//! // This is ideal for non-game applications that only update in response to user
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! *control_flow = ControlFlow::Wait;
//!
//! match event {
//! Event::EventsCleared => {
//! // Application update code.
//!
//! // Queue a RedrawRequested event.
//! window.request_redraw();
//! },
//! Event::WindowEvent {
//! event: WindowEvent::RedrawRequested,
//! ..
//! } => {
//! // Redraw the application.
//! //
//! // It's preferrable to render in this event rather than in EventsCleared, since
//! // rendering in here allows the program to gracefully handle redraws requested
//! // by the OS.
//! },
//! Event::WindowEvent {
//! event: WindowEvent::CloseRequested,
//! ..
@@ -69,42 +71,33 @@
//! println!("The close button was pressed; stopping");
//! *control_flow = ControlFlow::Exit
//! },
//! Event::MainEventsCleared => {
//! // Application update code.
//!
//! // Queue a RedrawRequested event.
//! window.request_redraw();
//! },
//! Event::RedrawRequested(_) => {
//! // Redraw the application.
//! //
//! // It's preferrable to render in this event rather than in MainEventsCleared, since
//! // rendering in here allows the program to gracefully handle redraws requested
//! // by the OS.
//! },
//! _ => ()
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
//! _ => *control_flow = ControlFlow::Poll,
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
//! // This is ideal for non-game applications that only update in response to user
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! // _ => *control_flow = ControlFlow::Wait,
//! }
//! });
//! ```
//!
//! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
//! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`]
//! dispatched the event.
//! If you use multiple [`Window`]s, [`Event`]`::`[`WindowEvent`] has a member named `window_id`. You can
//! compare it with the value returned by the [`id()`][window_id_fn] method of [`Window`] in order to know which
//! [`Window`] has received the event.
//!
//! # Drawing on the window
//!
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
//! Winit doesn't provide any function that allows drawing on a [`Window`]. However it allows you to
//! retrieve the raw handle of the window (see the [`platform`] module), which in turn allows you
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the [`Window`].
//!
//! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoopExtDesktop::run_return`]: ./platform/desktop/trait.EventLoopExtDesktop.html#tymethod.run_return
//! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [event_loop_run]: event_loop::EventLoop::run
//! [`ControlFlow`]: event_loop::ControlFlow
//! [`Exit`]: event_loop::ControlFlow::Exit
//! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId
//! [`WindowBuilder`]: window::WindowBuilder
//! [window_new]: window::Window::new
//! [window_builder_new]: window::WindowBuilder::new
@@ -123,13 +116,13 @@
#[allow(unused_imports)]
#[macro_use]
extern crate lazy_static;
#[allow(unused_imports)]
#[macro_use]
extern crate log;
#[cfg(feature = "serde")]
#[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]
@@ -145,6 +138,7 @@ pub mod event_loop;
mod icon;
pub mod monitor;
mod platform_impl;
mod util;
pub mod window;
pub mod platform;

View File

@@ -58,7 +58,7 @@ impl Ord for VideoMode {
impl VideoMode {
/// Returns the resolution of this video mode.
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
self.video_mode.size()
}
@@ -133,7 +133,7 @@ impl MonitorHandle {
///
/// - **Web:** Always returns (0,0)
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
self.inner.size()
}
@@ -144,22 +144,22 @@ impl MonitorHandle {
///
/// - **Web:** Always returns (0,0)
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
pub fn position(&self) -> PhysicalPosition {
self.inner.position()
}
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](crate::dpi) module for more information.
///
/// ## Platform-specific
///
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
/// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
/// - **Android:** Always returns 1.0.
/// - **Web:** Always returns 1.0
#[inline]
pub fn scale_factor(&self) -> f64 {
self.inner.scale_factor()
pub fn hidpi_factor(&self) -> f64 {
self.inner.hidpi_factor()
}
/// Returns all fullscreen video modes supported by this monitor.

View File

@@ -30,11 +30,7 @@ pub trait EventLoopExtDesktop {
/// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary.
fn run_return<F>(&mut self, event_handler: F)
where
F: FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
);
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
}
impl<T> EventLoopExtDesktop for EventLoop<T> {
@@ -42,11 +38,7 @@ impl<T> EventLoopExtDesktop for EventLoop<T> {
fn run_return<F>(&mut self, event_handler: F)
where
F: FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
),
F: FnMut(Event<T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
{
self.event_loop.run_return(event_handler)
}

View File

@@ -43,14 +43,14 @@ pub trait WindowExtIOS {
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn ui_view(&self) -> *mut c_void;
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to [`MonitorHandle::scale_factor()`].
/// this to [`MonitorHandle::hidpi_factor()`].
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
fn set_scale_factor(&self, scale_factor: f64);
fn set_hidpi_factor(&self, hidpi_factor: f64);
/// Sets the valid orientations for the [`Window`].
///
@@ -113,8 +113,8 @@ impl WindowExtIOS for Window {
}
#[inline]
fn set_scale_factor(&self, scale_factor: f64) {
self.window.set_scale_factor(scale_factor)
fn set_hidpi_factor(&self, hidpi_factor: f64) {
self.window.set_hidpi_factor(hidpi_factor)
}
#[inline]
@@ -148,14 +148,14 @@ pub trait WindowBuilderExtIOS {
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to [`MonitorHandle::scale_factor()`].
/// this to [`MonitorHandle::hidpi_factor()`].
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
fn with_scale_factor(self, scale_factor: f64) -> WindowBuilder;
fn with_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder;
/// Sets the valid orientations for the [`Window`].
///
@@ -204,8 +204,8 @@ impl WindowBuilderExtIOS for WindowBuilder {
}
#[inline]
fn with_scale_factor(mut self, scale_factor: f64) -> WindowBuilder {
self.platform_specific.scale_factor = Some(scale_factor);
fn with_hidpi_factor(mut self, hidpi_factor: f64) -> WindowBuilder {
self.platform_specific.hidpi_factor = Some(hidpi_factor);
self
}

View File

@@ -4,7 +4,6 @@ use std::os::raw::c_void;
use crate::{
dpi::LogicalSize,
event_loop::EventLoopWindowTarget,
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
@@ -129,7 +128,7 @@ pub trait WindowBuilderExtMacOS {
/// Makes the window content appear behind the titlebar.
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
/// Build window with `resizeIncrements` property. Values must not be 0.
fn with_resize_increments(self, increments: LogicalSize<f64>) -> WindowBuilder;
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
}
@@ -180,7 +179,7 @@ impl WindowBuilderExtMacOS for WindowBuilder {
}
#[inline]
fn with_resize_increments(mut self, increments: LogicalSize<f64>) -> WindowBuilder {
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
self.platform_specific.resize_increments = Some(increments.into());
self
}
@@ -210,17 +209,3 @@ impl MonitorHandleExtMacOS for MonitorHandle {
self.inner.ns_screen().map(|s| s as *mut c_void)
}
}
/// Additional methods on `EventLoopWindowTarget` that are specific to macOS.
pub trait EventLoopWindowTargetExtMacOS {
/// Hide the entire application. In most applications this is typically triggered with Command-H.
fn hide_application(&self);
}
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
fn hide_application(&self) {
let cls = objc::runtime::Class::get("NSApplication").unwrap();
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
unsafe { msg_send![app, hide: 0] }
}
}

View File

@@ -2,10 +2,10 @@
use std::{os::raw, ptr, sync::Arc};
use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme};
use smithay_client_toolkit::window::{ButtonState, Theme};
use crate::{
dpi::Size,
dpi::LogicalSize,
event_loop::{EventLoop, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
@@ -23,6 +23,74 @@ pub use crate::platform_impl::x11;
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
/// Theme for wayland client side decorations
///
/// Colors must be in ARGB8888 format
pub struct WaylandTheme {
/// Primary color when the window is focused
pub primary_active: [u8; 4],
/// Primary color when the window is unfocused
pub primary_inactive: [u8; 4],
/// Secondary color when the window is focused
pub secondary_active: [u8; 4],
/// Secondary color when the window is unfocused
pub secondary_inactive: [u8; 4],
/// Close button color when hovered over
pub close_button_hovered: [u8; 4],
/// Close button color
pub close_button: [u8; 4],
/// Close button color when hovered over
pub maximize_button_hovered: [u8; 4],
/// Maximize button color
pub maximize_button: [u8; 4],
/// Minimize button color when hovered over
pub minimize_button_hovered: [u8; 4],
/// Minimize button color
pub minimize_button: [u8; 4],
}
struct WaylandThemeObject(WaylandTheme);
impl Theme for WaylandThemeObject {
fn get_primary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.primary_active
} else {
self.0.primary_inactive
}
}
// Used for division line
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.secondary_active
} else {
self.0.secondary_inactive
}
}
fn get_close_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.close_button_hovered,
_ => self.0.close_button,
}
}
fn get_maximize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.maximize_button_hovered,
_ => self.0.maximize_button,
}
}
fn get_minimize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.minimize_button_hovered,
_ => self.0.minimize_button,
}
}
}
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
pub trait EventLoopWindowTargetExtUnix {
/// True if the `EventLoopWindowTarget` uses Wayland.
@@ -207,7 +275,7 @@ pub trait WindowExtUnix {
fn wayland_display(&self) -> Option<*mut raw::c_void>;
/// Sets the color theme of the client side window decorations on wayland
fn set_wayland_theme<T: Theme>(&self, theme: T);
fn set_wayland_theme(&self, theme: WaylandTheme);
/// Check if the window is ready for drawing
///
@@ -285,9 +353,9 @@ impl WindowExtUnix for Window {
}
#[inline]
fn set_wayland_theme<T: Theme>(&self, theme: T) {
fn set_wayland_theme(&self, theme: WaylandTheme) {
match self.window {
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)),
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandThemeObject(theme)),
_ => {}
}
}
@@ -312,9 +380,9 @@ pub trait WindowBuilderExtUnix {
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
fn with_gtk_theme_variant(self, variant: String) -> Self;
/// Build window with resize increment hint. Only implemented on X11.
fn with_resize_increments<S: Into<Size>>(self, increments: S) -> Self;
fn with_resize_increments(self, increments: LogicalSize) -> Self;
/// Build window with base size hint. Only implemented on X11.
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
fn with_base_size(self, base_size: LogicalSize) -> Self;
/// Build window with a given application ID. It should match the `.desktop` file distributed with
/// your program. Only relevant on Wayland.
@@ -363,13 +431,13 @@ impl WindowBuilderExtUnix for WindowBuilder {
}
#[inline]
fn with_resize_increments<S: Into<Size>>(mut self, increments: S) -> Self {
fn with_resize_increments(mut self, increments: LogicalSize) -> Self {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
fn with_base_size(mut self, base_size: LogicalSize) -> Self {
self.platform_specific.base_size = Some(base_size.into());
self
}
@@ -393,96 +461,3 @@ impl MonitorHandleExtUnix for MonitorHandle {
self.inner.native_identifier()
}
}
/// Wrapper for implementing SCTK's theme trait.
struct WaylandTheme<T: Theme>(T);
pub trait Theme: Send + 'static {
/// Primary color of the scheme.
fn primary_color(&self, window_active: bool) -> [u8; 4];
/// Secondary color of the scheme.
fn secondary_color(&self, window_active: bool) -> [u8; 4];
/// Color for the close button.
fn close_button_color(&self, status: ButtonState) -> [u8; 4];
/// Icon color for the close button, defaults to the secondary color.
#[allow(unused_variables)]
fn close_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
self.secondary_color(true)
}
/// Background color for the maximize button.
fn maximize_button_color(&self, status: ButtonState) -> [u8; 4];
/// Icon color for the maximize button, defaults to the secondary color.
#[allow(unused_variables)]
fn maximize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
self.secondary_color(true)
}
/// Background color for the minimize button.
fn minimize_button_color(&self, status: ButtonState) -> [u8; 4];
/// Icon color for the minimize button, defaults to the secondary color.
#[allow(unused_variables)]
fn minimize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
self.secondary_color(true)
}
}
impl<T: Theme> SCTKTheme for WaylandTheme<T> {
fn get_primary_color(&self, active: bool) -> [u8; 4] {
self.0.primary_color(active)
}
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
self.0.secondary_color(active)
}
fn get_close_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0.close_button_color(ButtonState::from_sctk(status))
}
fn get_close_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0.close_button_color(ButtonState::from_sctk(status))
}
fn get_maximize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0.maximize_button_color(ButtonState::from_sctk(status))
}
fn get_maximize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0
.maximize_button_icon_color(ButtonState::from_sctk(status))
}
fn get_minimize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0.minimize_button_color(ButtonState::from_sctk(status))
}
fn get_minimize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0
.minimize_button_icon_color(ButtonState::from_sctk(status))
}
}
pub enum ButtonState {
/// Button is being hovered over by pointer.
Hovered,
/// Button is not being hovered over by pointer.
Idle,
/// Button is disabled.
Disabled,
}
impl ButtonState {
fn from_sctk(button_state: SCTKButtonState) -> Self {
match button_state {
SCTKButtonState::Hovered => Self::Hovered,
SCTKButtonState::Idle => Self::Idle,
SCTKButtonState::Disabled => Self::Disabled,
}
}
}

View File

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

View File

@@ -6,7 +6,7 @@ use libc;
use winapi::shared::windef::HWND;
use crate::{
event::DeviceId,
event::device::{GamepadHandle, KeyboardId, MouseId},
event_loop::EventLoop,
monitor::MonitorHandle,
platform_impl::EventLoop as WindowsEventLoop,
@@ -77,9 +77,6 @@ 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 {
@@ -97,11 +94,6 @@ 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.
@@ -157,17 +149,49 @@ impl MonitorHandleExtWindows for MonitorHandle {
}
}
/// Additional methods on `DeviceId` that are specific to Windows.
pub trait DeviceIdExtWindows {
/// Additional methods on device types that are specific to Windows.
pub trait DeviceExtWindows {
/// 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 DeviceIdExtWindows for DeviceId {
impl DeviceExtWindows for MouseId {
#[inline]
fn persistent_identifier(&self) -> Option<String> {
self.0.persistent_identifier()
}
#[inline]
fn handle(&self) -> *mut c_void {
self.0.handle() as _
}
}
impl DeviceExtWindows for KeyboardId {
#[inline]
fn persistent_identifier(&self) -> Option<String> {
self.0.persistent_identifier()
}
#[inline]
fn handle(&self) -> *mut c_void {
self.0.handle() as _
}
}
impl DeviceExtWindows for GamepadHandle {
#[inline]
fn persistent_identifier(&self) -> Option<String> {
self.0.persistent_identifier()
}
#[inline]
fn handle(&self) -> *mut c_void {
self.0.handle() as _
}
}

View File

@@ -61,7 +61,7 @@ impl EventLoop {
while let Ok(event) = self.event_rx.try_recv() {
let e = match event {
android_glue::Event::EventMotion(motion) => {
let dpi_factor = MonitorHandle.scale_factor();
let dpi_factor = MonitorHandle.hidpi_factor();
let location = LogicalPosition::from_physical(
(motion.x as f64, motion.y as f64),
dpi_factor,
@@ -102,7 +102,7 @@ impl EventLoop {
if native_window.is_null() {
None
} else {
let dpi_factor = MonitorHandle.scale_factor();
let dpi_factor = MonitorHandle.hidpi_factor();
let physical_size = MonitorHandle.size();
let size = LogicalSize::from_physical(physical_size, dpi_factor);
Some(Event::WindowEvent {
@@ -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(())
}
@@ -193,16 +193,16 @@ impl fmt::Debug for MonitorHandle {
#[derive(Debug)]
struct MonitorHandle {
name: Option<String>,
dimensions: PhysicalSize<u32>,
position: PhysicalPosition<i32>,
scale_factor: f64,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorHandle {
name: self.name(),
dimensions: self.size(),
position: self.outer_position(),
scale_factor: self.scale_factor(),
hidpi_factor: self.hidpi_factor(),
};
monitor_id_proxy.fmt(f)
@@ -216,7 +216,7 @@ impl MonitorHandle {
}
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
unsafe {
let window = android_glue::native_window();
(
@@ -228,13 +228,13 @@ impl MonitorHandle {
}
#[inline]
pub fn outer_position(&self) -> PhysicalPosition<i32> {
pub fn outer_position(&self) -> PhysicalPosition {
// Android assumes single screen
(0, 0).into()
}
#[inline]
pub fn scale_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
1.0
}
}
@@ -283,29 +283,29 @@ impl Window {
}
#[inline]
pub fn outer_position(&self) -> Option<LogicalPosition<f64>> {
pub fn outer_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn inner_position(&self) -> Option<LogicalPosition<f64>> {
pub fn inner_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn set_outer_position(&self, _position: LogicalPosition<f64>) {
pub fn set_outer_position(&self, _position: LogicalPosition) {
// N/A
}
#[inline]
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize<f64>>) {
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize<f64>>) {
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
@@ -315,29 +315,29 @@ impl Window {
}
#[inline]
pub fn inner_size(&self) -> Option<LogicalSize<f64>> {
pub fn inner_size(&self) -> Option<LogicalSize> {
if self.native_window.is_null() {
None
} else {
let dpi_factor = self.scale_factor();
let dpi_factor = self.hidpi_factor();
let physical_size = self.current_monitor().size();
Some(LogicalSize::from_physical(physical_size, dpi_factor))
}
}
#[inline]
pub fn outer_size(&self) -> Option<LogicalSize<f64>> {
pub fn outer_size(&self) -> Option<LogicalSize> {
self.inner_size()
}
#[inline]
pub fn set_inner_size(&self, _size: LogicalSize<f64>) {
pub fn set_inner_size(&self, _size: LogicalSize) {
// N/A
}
#[inline]
pub fn scale_factor(&self) -> f64 {
self.current_monitor().scale_factor()
pub fn hidpi_factor(&self) -> f64 {
self.current_monitor().hidpi_factor()
}
#[inline]
@@ -356,18 +356,10 @@ impl Window {
}
#[inline]
pub fn set_cursor_position(
&self,
_position: LogicalPosition<f64>,
) -> Result<(), ExternalError> {
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
pub fn set_minimized(&self, _minimized: bool) {
unimplemented!()
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// N/A
@@ -403,7 +395,7 @@ impl Window {
}
#[inline]
pub fn set_ime_position(&self, _spot: LogicalPosition<f64>) {
pub fn set_ime_position(&self, _spot: LogicalPosition) {
// N/A
}

View File

@@ -12,16 +12,15 @@ use std::{
use objc::runtime::{BOOL, YES};
use crate::{
dpi::LogicalSize,
event::{Event, StartCause, WindowEvent},
event_loop::ControlFlow,
platform_impl::platform::{
event_loop::{EventHandler, EventProxy, EventWrapper, Never},
event_loop::{EventHandler, Never},
ffi::{
id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer,
CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CGRect, CGSize, NSInteger,
NSOperatingSystemVersion, NSUInteger,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, NSInteger, NSOperatingSystemVersion,
NSUInteger,
},
},
window::WindowId as RootWindowId,
@@ -46,13 +45,17 @@ enum UserCallbackTransitionResult<'a> {
processing_redraws: bool,
},
ReentrancyPrevented {
queued_events: &'a mut Vec<EventWrapper>,
queued_events: &'a mut Vec<Event<Never>>,
},
}
impl Event<'static, Never> {
impl Event<Never> {
fn is_redraw(&self) -> bool {
if let Event::RedrawRequested(_) = self {
if let Event::WindowEvent {
window_id: _,
event: WindowEvent::RedrawRequested,
} = self
{
true
} else {
false
@@ -66,12 +69,12 @@ impl Event<'static, Never> {
enum AppStateImpl {
NotLaunched {
queued_windows: Vec<id>,
queued_events: Vec<EventWrapper>,
queued_events: Vec<Event<Never>>,
queued_gpu_redraws: HashSet<id>,
},
Launching {
queued_windows: Vec<id>,
queued_events: Vec<EventWrapper>,
queued_events: Vec<Event<Never>>,
queued_event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<id>,
},
@@ -82,7 +85,7 @@ enum AppStateImpl {
},
// special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback {
queued_events: Vec<EventWrapper>,
queued_events: Vec<Event<Never>>,
queued_gpu_redraws: HashSet<id>,
},
ProcessingRedraws {
@@ -223,7 +226,7 @@ impl AppState {
});
}
fn did_finish_launching_transition(&mut self) -> (Vec<id>, Vec<EventWrapper>) {
fn did_finish_launching_transition(&mut self) -> (Vec<id>, Vec<Event<Never>>) {
let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching {
queued_windows,
@@ -246,7 +249,7 @@ impl AppState {
(windows, events)
}
fn wakeup_transition(&mut self) -> Option<EventWrapper> {
fn wakeup_transition(&mut self) -> Option<Event<Never>> {
// before `AppState::did_finish_launching` is called, pretend there is no running
// event loop.
if !self.has_launched() {
@@ -259,10 +262,7 @@ impl AppState {
AppStateImpl::PollFinished {
waiting_event_handler,
},
) => (
waiting_event_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)),
),
) => (waiting_event_handler, Event::NewEvents(StartCause::Poll)),
(
ControlFlow::Wait,
AppStateImpl::Waiting {
@@ -271,10 +271,10 @@ impl AppState {
},
) => (
waiting_event_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: None,
})),
}),
),
(
ControlFlow::WaitUntil(requested_resume),
@@ -284,15 +284,15 @@ impl AppState {
},
) => {
let event = if Instant::now() >= requested_resume {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached {
Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume,
}))
})
} else {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(requested_resume),
}))
})
};
(waiting_event_handler, event)
}
@@ -591,10 +591,7 @@ pub unsafe fn did_finish_launching() {
let (windows, events) = AppState::get_mut().did_finish_launching_transition();
let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(
StartCause::Init,
)))
.chain(events);
let events = std::iter::once(Event::NewEvents(StartCause::Init)).chain(events);
handle_nonuser_events(events);
// the above window dance hack, could possibly trigger new windows to be created.
@@ -623,12 +620,12 @@ pub unsafe fn handle_wakeup_transition() {
}
// requires main thread
pub unsafe fn handle_nonuser_event(event: EventWrapper) {
pub unsafe fn handle_nonuser_event(event: Event<Never>) {
handle_nonuser_events(std::iter::once(event))
}
// requires main thread
pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) {
pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = Event<Never>>>(events: I) {
let mut this = AppState::get_mut();
let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
@@ -645,23 +642,16 @@ pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events
let mut control_flow = this.control_flow;
drop(this);
for wrapper in events {
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
log::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
log::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}",
event
);
}
event_handler.handle_nonuser_event(event, &mut control_flow)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
}
for event in events {
if !processing_redraws && event.is_redraw() {
log::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
log::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}",
event
);
}
event_handler.handle_nonuser_event(event, &mut control_flow)
}
loop {
@@ -702,23 +692,16 @@ pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events
}
drop(this);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
log::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
log::warn!(
"processing non-`RedrawRequested` event after the main event loop: {:#?}",
event
);
}
event_handler.handle_nonuser_event(event, &mut control_flow)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
}
for event in queued_events {
if !processing_redraws && event.is_redraw() {
log::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
log::warn!(
"processing non-`RedrawRequested` event after the main event loop: {:#?}",
event
);
}
event_handler.handle_nonuser_event(event, &mut control_flow)
}
}
}
@@ -772,15 +755,8 @@ unsafe fn handle_user_events() {
}
drop(this);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => {
event_handler.handle_nonuser_event(event, &mut control_flow)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
}
}
for event in queued_events {
event_handler.handle_nonuser_event(event, &mut control_flow)
}
event_handler.handle_user_events(&mut control_flow);
}
@@ -800,20 +776,16 @@ 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(EventWrapper::StaticEvent(Event::MainEventsCleared));
handle_nonuser_event(Event::EventsCleared);
let mut this = AppState::get_mut();
let mut redraw_events: Vec<EventWrapper> = this
let redraw_events = this
.main_events_cleared_transition()
.into_iter()
.map(|window| {
EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into())))
})
.collect();
if !redraw_events.is_empty() {
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
}
.map(|window| Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::RedrawRequested,
});
drop(this);
handle_nonuser_events(redraw_events);
@@ -834,66 +806,6 @@ pub unsafe fn terminated() {
event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow)
}
fn handle_event_proxy(
event_handler: &mut Box<dyn EventHandler>,
control_flow: ControlFlow,
proxy: EventProxy,
) {
match proxy {
EventProxy::DpiChangedProxy {
suggested_size,
scale_factor,
window_id,
} => handle_hidpi_proxy(
event_handler,
control_flow,
suggested_size,
scale_factor,
window_id,
),
}
}
fn handle_hidpi_proxy(
event_handler: &mut Box<dyn EventHandler>,
mut control_flow: ControlFlow,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
window_id: id,
) {
let mut size = suggested_size.to_physical(scale_factor);
let new_inner_size = &mut size;
let event = Event::WindowEvent {
window_id: RootWindowId(window_id.into()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
},
};
event_handler.handle_nonuser_event(event, &mut control_flow);
let (view, screen_frame) = get_view_and_screen_frame(window_id);
let physical_size = *new_inner_size;
let logical_size = physical_size.to_logical(scale_factor);
let size = CGSize::new(logical_size);
let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
unsafe {
let () = msg_send![view, setFrame: new_frame];
}
}
fn get_view_and_screen_frame(window_id: id) -> (id, CGRect) {
unsafe {
let view_controller: id = msg_send![window_id, rootViewController];
let view: id = msg_send![view_controller, view];
let bounds: CGRect = msg_send![window_id, bounds];
let screen: id = msg_send![window_id, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![window_id, convertRect:bounds toCoordinateSpace:screen_space];
(view, screen_frame)
}
}
struct EventLoopWaker {
timer: CFRunLoopTimerRef,
}

View File

@@ -8,7 +8,6 @@ use std::{
};
use crate::{
dpi::LogicalSize,
event::Event,
event_loop::{
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
@@ -29,21 +28,6 @@ use crate::platform_impl::platform::{
monitor, view, MonitorHandle,
};
#[derive(Debug)]
pub enum EventWrapper {
StaticEvent(Event<'static, Never>),
EventProxy(EventProxy),
}
#[derive(Debug, PartialEq)]
pub enum EventProxy {
DpiChangedProxy {
window_id: id,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
},
}
pub struct EventLoopWindowTarget<T: 'static> {
receiver: Receiver<T>,
sender_to_clone: Sender<T>,
@@ -85,7 +69,7 @@ impl<T: 'static> EventLoop<T> {
pub fn run<F>(self, event_handler: F) -> !
where
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
F: 'static + FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
unsafe {
let application: *mut c_void = msg_send![class!(UIApplication), sharedApplication];
@@ -181,10 +165,8 @@ impl<T> EventLoopProxy<T> {
}
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender
.send(event)
.map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.sender.send(event).map_err(|_| EventLoopClosed)?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
@@ -215,10 +197,10 @@ fn setup_control_flow_observers() {
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
// priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was
// priority to be 0, in order to send EventsCleared before RedrawRequested. This value was
// chosen conservatively to guard against apple using different priorities for their redraw
// observers in different OS's or on different devices. If it so happens that it's too
// conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`.
// conservative, the main symptom would be non-redraw events coming in after `EventsCleared`.
//
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
@@ -293,7 +275,7 @@ fn setup_control_flow_observers() {
pub enum Never {}
pub trait EventHandler: Debug {
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow);
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow);
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
}
@@ -312,10 +294,10 @@ impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
impl<F, T> EventHandler for EventLoopHandler<F, T>
where
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
F: 'static + FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
T: 'static,
{
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
(self.f)(
event.map_nonuser_event().unwrap(),
&self.event_loop,

View File

@@ -4,10 +4,7 @@ use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*};
use objc::{runtime::Object, Encode, Encoding};
use crate::{
dpi::LogicalSize,
platform::ios::{Idiom, ScreenEdge, ValidOrientations},
};
use crate::platform::ios::{Idiom, ScreenEdge, ValidOrientations};
pub type id = *mut Object;
pub const nil: id = 0 as id;
@@ -42,15 +39,6 @@ pub struct CGSize {
pub height: CGFloat,
}
impl CGSize {
pub fn new(size: LogicalSize<f64>) -> CGSize {
CGSize {
width: size.width as _,
height: size.height as _,
}
}
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGRect {
@@ -58,12 +46,6 @@ pub struct CGRect {
pub size: CGSize,
}
impl CGRect {
pub fn new(origin: CGPoint, size: CGSize) -> CGRect {
CGRect { origin, size }
}
}
unsafe impl Encode for CGRect {
fn encode() -> Encoding {
unsafe {

View File

@@ -18,44 +18,31 @@ pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) screen_mode: NativeDisplayMode,
pub(crate) screen_mode: id,
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: self.screen_mode.clone(),
screen_mode: unsafe { msg_send![self.screen_mode, retain] },
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");
@@ -77,18 +64,16 @@ 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,
screen_mode: msg_send![screen_mode, retain],
monitor: MonitorHandle::retained_new(uiscreen),
}
}
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
@@ -171,16 +156,16 @@ impl fmt::Debug for MonitorHandle {
#[derive(Debug)]
struct MonitorHandle {
name: Option<String>,
size: PhysicalSize<u32>,
position: PhysicalPosition<i32>,
scale_factor: f64,
size: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorHandle {
name: self.name(),
size: self.size(),
position: self.position(),
scale_factor: self.scale_factor(),
hidpi_factor: self.hidpi_factor(),
};
monitor_id_proxy.fmt(f)
@@ -216,21 +201,21 @@ impl Inner {
}
}
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
unsafe {
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
(bounds.size.width as f64, bounds.size.height as f64).into()
}
}
pub fn position(&self) -> PhysicalPosition<i32> {
pub fn position(&self) -> PhysicalPosition {
unsafe {
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
(bounds.origin.x as f64, bounds.origin.y as f64).into()
}
}
pub fn scale_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
unsafe {
let scale: CGFloat = msg_send![self.ui_screen(), nativeScale];
scale as f64

View File

@@ -10,7 +10,7 @@ use crate::{
platform::ios::MonitorHandleExtIOS,
platform_impl::platform::{
app_state::{self, OSCapabilities},
event_loop::{self, EventProxy, EventWrapper},
event_loop,
ffi::{
id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask,
UIRectEdge, UITouchPhase, UITouchType,
@@ -102,14 +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_events(
std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(
RootWindowId(window.into()),
)))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::RedrawEventsCleared,
))),
);
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::RedrawRequested,
});
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![super(object, superclass), drawRect: rect];
}
@@ -127,29 +123,27 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let dpi_factor: CGFloat = msg_send![screen, scale];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
}
.to_physical(dpi_factor.into());
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
};
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
}));
});
}
}
extern "C" fn set_content_scale_factor(
object: &mut Object,
_: Sel,
untrusted_scale_factor: CGFloat,
untrusted_hidpi_factor: CGFloat,
) {
unsafe {
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![
super(object, superclass),
setContentScaleFactor: untrusted_scale_factor
setContentScaleFactor: untrusted_hidpi_factor
];
let window: id = msg_send![object, window];
@@ -162,15 +156,14 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
// `setContentScaleFactor` may be called with a value of 0, which means "reset the
// content scale factor to a device-specific default value", so we can't use the
// parameter here. We can query the actual factor using the getter
let dpi_factor: CGFloat = msg_send![object, contentScaleFactor];
let hidpi_factor: CGFloat = msg_send![object, contentScaleFactor];
assert!(
!dpi_factor.is_nan()
&& dpi_factor.is_finite()
&& dpi_factor.is_sign_positive()
&& dpi_factor > 0.0,
"invalid scale_factor set on UIView",
!hidpi_factor.is_nan()
&& hidpi_factor.is_finite()
&& hidpi_factor.is_sign_positive()
&& hidpi_factor > 0.0,
"invalid hidpi_factor set on UIView",
);
let scale_factor: f64 = dpi_factor.into();
let bounds: CGRect = msg_send![object, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
@@ -181,17 +174,14 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
height: screen_frame.size.height as _,
};
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
window_id: window,
scale_factor,
suggested_size: size,
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
})
.chain(std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
})),
);
}
}
@@ -248,7 +238,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
_ => panic!("unexpected touch phase: {:?}", phase as i32),
};
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
touch_events.push(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId { uiscreen }),
@@ -257,7 +247,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
force,
phase,
}),
}));
});
}
app_state::handle_nonuser_events(touch_events);
}
@@ -377,20 +367,20 @@ unsafe fn get_window_class() -> &'static Class {
extern "C" fn become_key_window(object: &Object, _: Sel) {
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(true),
}));
});
let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
}
}
extern "C" fn resign_key_window(object: &Object, _: Sel) {
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(false),
}));
});
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
}
}
@@ -424,8 +414,8 @@ pub unsafe fn create_view(
let view: id = msg_send![view, initWithFrame: frame];
assert!(!view.is_null(), "Failed to initialize `UIView` instance");
let () = msg_send![view, setMultipleTouchEnabled: YES];
if let Some(scale_factor) = platform_attributes.scale_factor {
let () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat];
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
let () = msg_send![view, setContentScaleFactor: hidpi_factor as CGFloat];
}
view
@@ -507,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.0];
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
}
Some(Fullscreen::Borderless(ref monitor)) => {
@@ -528,11 +518,11 @@ pub fn create_delegate_class() {
}
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) }
unsafe { app_state::handle_nonuser_event(Event::Resumed) }
}
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) }
unsafe { app_state::handle_nonuser_event(Event::Suspended) }
}
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {}
@@ -551,10 +541,10 @@ pub fn create_delegate_class() {
}
let is_winit_window: BOOL = msg_send![window, isKindOfClass: class!(WinitUIWindow)];
if is_winit_window == YES {
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
events.push(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Destroyed,
}));
});
}
}
app_state::handle_nonuser_events(events);

View File

@@ -7,15 +7,14 @@ use std::{
use objc::runtime::{Class, Object, BOOL, NO, YES};
use crate::{
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
dpi::{self, LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent},
icon::Icon,
monitor::MonitorHandle as RootMonitorHandle,
platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations},
platform_impl::platform::{
app_state,
event_loop::{self, EventProxy, EventWrapper},
app_state, event_loop,
ffi::{
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
UIRectEdge, UIScreenOverscanCompensation,
@@ -76,34 +75,28 @@ impl Inner {
}
}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
unsafe {
let safe_area = self.safe_area_screen_space();
let position = LogicalPosition {
x: safe_area.origin.x as f64,
y: safe_area.origin.y as f64,
};
let dpi_factor = self.scale_factor();
Ok(position.to_physical(dpi_factor))
Ok(LogicalPosition {
x: safe_area.origin.x as _,
y: safe_area.origin.y as _,
})
}
}
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
unsafe {
let screen_frame = self.screen_frame();
let position = LogicalPosition {
x: screen_frame.origin.x as f64,
y: screen_frame.origin.y as f64,
};
let dpi_factor = self.scale_factor();
Ok(position.to_physical(dpi_factor))
Ok(LogicalPosition {
x: screen_frame.origin.x as _,
y: screen_frame.origin.y as _,
})
}
}
pub fn set_outer_position(&self, physical_position: Position) {
pub fn set_outer_position(&self, position: LogicalPosition) {
unsafe {
let dpi_factor = self.scale_factor();
let position = physical_position.to_logical::<f64>(dpi_factor);
let screen_frame = self.screen_frame();
let new_screen_frame = CGRect {
origin: CGPoint {
@@ -117,39 +110,35 @@ impl Inner {
}
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
pub fn inner_size(&self) -> LogicalSize {
unsafe {
let dpi_factor = self.scale_factor();
let safe_area = self.safe_area_screen_space();
let size = LogicalSize {
width: safe_area.size.width as f64,
height: safe_area.size.height as f64,
};
size.to_physical(dpi_factor)
LogicalSize {
width: safe_area.size.width as _,
height: safe_area.size.height as _,
}
}
}
pub fn outer_size(&self) -> PhysicalSize<u32> {
pub fn outer_size(&self) -> LogicalSize {
unsafe {
let dpi_factor = self.scale_factor();
let screen_frame = self.screen_frame();
let size = LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
size.to_physical(dpi_factor)
LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
}
}
}
pub fn set_inner_size(&self, _size: Size) {
pub fn set_inner_size(&self, _size: LogicalSize) {
unimplemented!("not clear what `Window::set_inner_size` means on iOS");
}
pub fn set_min_inner_size(&self, _dimensions: Option<Size>) {
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize>) {
warn!("`Window::set_min_inner_size` is ignored on iOS")
}
pub fn set_max_inner_size(&self, _dimensions: Option<Size>) {
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize>) {
warn!("`Window::set_max_inner_size` is ignored on iOS")
}
@@ -157,7 +146,7 @@ impl Inner {
warn!("`Window::set_resizable` is ignored on iOS")
}
pub fn scale_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
unsafe {
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
hidpi as _
@@ -168,7 +157,7 @@ impl Inner {
debug!("`Window::set_cursor_icon` ignored on iOS")
}
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
@@ -180,10 +169,6 @@ 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")
}
@@ -254,7 +239,7 @@ impl Inner {
warn!("`Window::set_window_icon` is ignored on iOS")
}
pub fn set_ime_position(&self, _position: Position) {
pub fn set_ime_position(&self, _position: LogicalPosition) {
warn!("`Window::set_ime_position` is ignored on iOS")
}
@@ -354,17 +339,13 @@ impl Window {
let screen_bounds: CGRect = msg_send![screen, bounds];
let frame = match window_attributes.inner_size {
Some(dim) => {
let dpi_factor = msg_send![screen, scale];
let size = dim.to_logical::<f64>(dpi_factor);
CGRect {
origin: screen_bounds.origin,
size: CGSize {
width: size.width as _,
height: size.height as _,
},
}
}
Some(dim) => CGRect {
origin: screen_bounds.origin,
size: CGSize {
width: dim.width as _,
height: dim.height as _,
},
},
None => screen_bounds,
};
@@ -398,11 +379,10 @@ impl Window {
};
app_state::set_key_window(window);
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// Like the Windows and macOS backends, we send a `HiDpiFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
let dpi_factor: CGFloat = msg_send![view, contentScaleFactor];
let scale_factor: f64 = dpi_factor.into();
if scale_factor != 1.0 {
let hidpi_factor: CGFloat = msg_send![view, contentScaleFactor];
if hidpi_factor != 1.0 {
let bounds: CGRect = msg_send![view, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
@@ -413,17 +393,14 @@ impl Window {
height: screen_frame.size.height as _,
};
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
window_id: window,
scale_factor,
suggested_size: size,
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
})
.chain(std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
})),
);
}
@@ -444,14 +421,14 @@ impl Inner {
self.view
}
pub fn set_scale_factor(&self, scale_factor: f64) {
pub fn set_hidpi_factor(&self, hidpi_factor: f64) {
unsafe {
assert!(
dpi::validate_scale_factor(scale_factor),
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
dpi::validate_hidpi_factor(hidpi_factor),
"`WindowExtIOS::set_hidpi_factor` received an invalid hidpi factor"
);
let scale_factor = scale_factor as CGFloat;
let () = msg_send![self.view, setContentScaleFactor: scale_factor];
let hidpi_factor = hidpi_factor as CGFloat;
let () = msg_send![self.view, setContentScaleFactor: hidpi_factor];
}
}
@@ -617,7 +594,7 @@ impl From<id> for WindowId {
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class,
pub scale_factor: Option<f64>,
pub hidpi_factor: Option<f64>,
pub valid_orientations: ValidOrientations,
pub prefers_home_indicator_hidden: bool,
pub prefers_status_bar_hidden: bool,
@@ -628,7 +605,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> PlatformSpecificWindowBuilderAttributes {
PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView),
scale_factor: None,
hidpi_factor: None,
valid_orientations: Default::default(),
prefers_home_indicator_hidden: false,
prefers_status_bar_hidden: false,

View File

@@ -7,9 +7,11 @@ use raw_window_handle::RawWindowHandle;
use smithay_client_toolkit::reexports::client::ConnectError;
pub use self::x11::XNotSupported;
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
use self::x11::{
ffi::XVisualInfo, get_xtarget, util::WindowType as XWindowType, XConnection, XError,
};
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::Event,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
@@ -34,8 +36,8 @@ const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
pub struct PlatformSpecificWindowBuilderAttributes {
pub visual_infos: Option<XVisualInfo>,
pub screen_id: Option<i32>,
pub resize_increments: Option<Size>,
pub base_size: Option<Size>,
pub resize_increments: Option<(u32, u32)>,
pub base_size: Option<(u32, u32)>,
pub class: Option<(String, String)>,
pub override_redirect: bool,
pub x11_window_types: Vec<XWindowType>,
@@ -132,7 +134,7 @@ impl MonitorHandle {
}
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
match self {
&MonitorHandle::X(ref m) => m.size(),
&MonitorHandle::Wayland(ref m) => m.size(),
@@ -140,7 +142,7 @@ impl MonitorHandle {
}
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
pub fn position(&self) -> PhysicalPosition {
match self {
&MonitorHandle::X(ref m) => m.position(),
&MonitorHandle::Wayland(ref m) => m.position(),
@@ -148,10 +150,10 @@ impl MonitorHandle {
}
#[inline]
pub fn scale_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
match self {
&MonitorHandle::X(ref m) => m.scale_factor(),
&MonitorHandle::Wayland(ref m) => m.scale_factor() as f64,
&MonitorHandle::X(ref m) => m.hidpi_factor(),
&MonitorHandle::Wayland(ref m) => m.hidpi_factor() as f64,
}
}
@@ -172,7 +174,7 @@ pub enum VideoMode {
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
match self {
&VideoMode::X(ref m) => m.size(),
&VideoMode::Wayland(ref m) => m.size(),
@@ -246,7 +248,7 @@ impl Window {
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
match self {
&Window::X(ref w) => w.outer_position(),
&Window::Wayland(ref w) => w.outer_position(),
@@ -254,7 +256,7 @@ impl Window {
}
#[inline]
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
match self {
&Window::X(ref m) => m.inner_position(),
&Window::Wayland(ref m) => m.inner_position(),
@@ -262,7 +264,7 @@ impl Window {
}
#[inline]
pub fn set_outer_position(&self, position: Position) {
pub fn set_outer_position(&self, position: LogicalPosition) {
match self {
&Window::X(ref w) => w.set_outer_position(position),
&Window::Wayland(ref w) => w.set_outer_position(position),
@@ -270,7 +272,7 @@ impl Window {
}
#[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> {
pub fn inner_size(&self) -> LogicalSize {
match self {
&Window::X(ref w) => w.inner_size(),
&Window::Wayland(ref w) => w.inner_size(),
@@ -278,7 +280,7 @@ impl Window {
}
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
pub fn outer_size(&self) -> LogicalSize {
match self {
&Window::X(ref w) => w.outer_size(),
&Window::Wayland(ref w) => w.outer_size(),
@@ -286,7 +288,7 @@ impl Window {
}
#[inline]
pub fn set_inner_size(&self, size: Size) {
pub fn set_inner_size(&self, size: LogicalSize) {
match self {
&Window::X(ref w) => w.set_inner_size(size),
&Window::Wayland(ref w) => w.set_inner_size(size),
@@ -294,7 +296,7 @@ impl Window {
}
#[inline]
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
pub fn set_min_inner_size(&self, dimensions: Option<LogicalSize>) {
match self {
&Window::X(ref w) => w.set_min_inner_size(dimensions),
&Window::Wayland(ref w) => w.set_min_inner_size(dimensions),
@@ -302,7 +304,7 @@ impl Window {
}
#[inline]
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
pub fn set_max_inner_size(&self, dimensions: Option<LogicalSize>) {
match self {
&Window::X(ref w) => w.set_max_inner_size(dimensions),
&Window::Wayland(ref w) => w.set_max_inner_size(dimensions),
@@ -342,15 +344,15 @@ impl Window {
}
#[inline]
pub fn scale_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
match self {
&Window::X(ref w) => w.scale_factor(),
&Window::Wayland(ref w) => w.scale_factor() as f64,
&Window::X(ref w) => w.hidpi_factor(),
&Window::Wayland(ref w) => w.hidpi_factor() as f64,
}
}
#[inline]
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> {
match self {
&Window::X(ref w) => w.set_cursor_position(position),
&Window::Wayland(ref w) => w.set_cursor_position(position),
@@ -365,14 +367,6 @@ 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 {
@@ -414,7 +408,7 @@ impl Window {
}
#[inline]
pub fn set_ime_position(&self, position: Position) {
pub fn set_ime_position(&self, position: LogicalPosition) {
match self {
&Window::X(ref w) => w.set_ime_position(position),
&Window::Wayland(_) => (),
@@ -601,7 +595,7 @@ impl<T: 'static> EventLoop<T> {
.into_iter()
.map(MonitorHandle::Wayland)
.collect(),
EventLoop::X(ref evlp) => evlp
EventLoop::X(ref evlp) => get_xtarget(&evlp.target)
.x_connection()
.available_monitors()
.into_iter()
@@ -614,7 +608,9 @@ impl<T: 'static> EventLoop<T> {
pub fn primary_monitor(&self) -> MonitorHandle {
match *self {
EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()),
EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()),
EventLoop::X(ref evlp) => {
MonitorHandle::X(get_xtarget(&evlp.target).x_connection().primary_monitor())
}
}
}
@@ -627,7 +623,7 @@ impl<T: 'static> EventLoop<T> {
pub fn run_return<F>(&mut self, callback: F)
where
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
match *self {
EventLoop::Wayland(ref mut evlp) => evlp.run_return(callback),
@@ -637,7 +633,7 @@ impl<T: 'static> EventLoop<T> {
pub fn run<F>(self, callback: F) -> !
where
F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: 'static + FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
match self {
EventLoop::Wayland(evlp) => evlp.run(callback),
@@ -654,7 +650,7 @@ impl<T: 'static> EventLoop<T> {
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
match *self {
EventLoopProxy::Wayland(ref proxy) => proxy.send_event(event),
EventLoopProxy::X(ref proxy) => proxy.send_event(event),
@@ -678,12 +674,12 @@ impl<T> EventLoopWindowTarget<T> {
}
fn sticky_exit_callback<T, F>(
evt: Event<'_, T>,
evt: Event<T>,
target: &RootELW<T>,
control_flow: &mut ControlFlow,
callback: &mut F,
) where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
{
// make ControlFlow::Exit sticky by providing a dummy
// control flow reference if it is already Exit.

View File

@@ -2,16 +2,11 @@ use std::{
cell::RefCell,
collections::VecDeque,
fmt,
io::ErrorKind,
rc::Rc,
sync::{Arc, Mutex},
time::{Duration, Instant},
time::Instant,
};
use mio::{Events, Poll, PollOpt, Ready, Token};
use mio_extras::channel::{channel, Receiver, SendError, Sender};
use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints::v1::client::{
zwp_locked_pointer_v1::ZwpLockedPointerV1, zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
};
@@ -26,17 +21,15 @@ use smithay_client_toolkit::reexports::client::protocol::{
};
use crate::{
dpi::{LogicalSize, PhysicalPosition, PhysicalSize},
event::{
DeviceEvent, DeviceId as RootDeviceId, Event, ModifiersState, StartCause, WindowEvent,
},
dpi::{PhysicalPosition, PhysicalSize},
event::ModifiersState,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::{
sticky_exit_callback, DeviceId as PlatformDeviceId, MonitorHandle as PlatformMonitorHandle,
VideoMode as PlatformVideoMode, WindowId as PlatformWindowId,
sticky_exit_callback, MonitorHandle as PlatformMonitorHandle,
VideoMode as PlatformVideoMode,
},
window::{CursorIcon, WindowId as RootWindowId},
window::CursorIcon,
};
use super::{window::WindowStore, DeviceId, WindowId};
@@ -50,37 +43,43 @@ use smithay_client_toolkit::{
Environment,
};
const KBD_TOKEN: Token = Token(0);
const USER_TOKEN: Token = Token(1);
const EVQ_TOKEN: Token = Token(2);
#[derive(Clone)]
pub struct EventsSink {
sender: Sender<Event<'static, ()>>,
pub struct WindowEventsSink<T> {
buffer: VecDeque<crate::event::Event<T>>,
}
impl EventsSink {
pub fn new(sender: Sender<Event<'static, ()>>) -> EventsSink {
EventsSink { sender }
impl<T> WindowEventsSink<T> {
pub fn new() -> WindowEventsSink<T> {
WindowEventsSink {
buffer: VecDeque::new(),
}
}
pub fn send_event(&self, event: Event<'static, ()>) {
self.sender.send(event).unwrap()
pub fn send_event(&mut self, evt: crate::event::Event<T>) {
self.buffer.push_back(evt);
}
pub fn send_device_event(&self, event: DeviceEvent, device_id: DeviceId) {
self.send_event(Event::DeviceEvent {
event,
device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)),
pub fn send_window_event(&mut self, evt: crate::event::WindowEvent, wid: WindowId) {
self.buffer.push_back(crate::event::Event::WindowEvent {
event: evt,
window_id: crate::window::WindowId(crate::platform_impl::WindowId::Wayland(wid)),
});
}
pub fn send_window_event(&self, event: WindowEvent<'static>, window_id: WindowId) {
self.send_event(Event::WindowEvent {
event,
window_id: RootWindowId(PlatformWindowId::Wayland(window_id)),
pub fn send_device_event(&mut self, evt: crate::event::DeviceEvent, dev_id: DeviceId) {
self.buffer.push_back(crate::event::Event::DeviceEvent {
event: evt,
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(dev_id)),
});
}
fn empty_with<F>(&mut self, mut callback: F)
where
F: FnMut(crate::event::Event<T>),
{
for evt in self.buffer.drain(..) {
callback(evt)
}
}
}
pub struct CursorManager {
@@ -231,17 +230,21 @@ impl CursorManager {
}
pub struct EventLoop<T: 'static> {
// Poll instance
poll: Poll,
// The loop
inner_loop: ::calloop::EventLoop<()>,
// The wayland display
pub display: Arc<Display>,
// The output manager
pub outputs: OutputMgr,
// Our sink, shared with some handlers, buffering the events
sink: Arc<Mutex<WindowEventsSink<T>>>,
pending_user_events: Rc<RefCell<VecDeque<T>>>,
// The cursor manager
cursor_manager: Arc<Mutex<CursorManager>>,
kbd_channel: Receiver<Event<'static, ()>>,
user_channel: Receiver<T>,
user_sender: Sender<T>,
// Utility for grabbing the cursor and changing visibility
_user_source: ::calloop::Source<::calloop::channel::Channel<T>>,
user_sender: ::calloop::channel::Sender<T>,
_kbd_source: ::calloop::Source<::calloop::channel::Channel<crate::event::Event<()>>>,
window_target: RootELW<T>,
}
@@ -249,12 +252,12 @@ pub struct EventLoop<T: 'static> {
//
// We should only try and wake up the `EventLoop` if it still exists, so we hold Weak ptrs.
pub struct EventLoopProxy<T: 'static> {
user_sender: Sender<T>,
user_sender: calloop::channel::Sender<T>,
}
pub struct EventLoopWindowTarget<T> {
// the event queue
pub evq: RefCell<EventQueue>,
// The event queue
pub evq: RefCell<::calloop::Source<EventQueue>>,
// The window store
pub store: Arc<Mutex<WindowStore>>,
// The cursor manager
@@ -279,14 +282,8 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_sender.send(event).map_err(|e| {
EventLoopClosed(if let SendError::Disconnected(x) = e {
x
} else {
unreachable!()
})
})
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.user_sender.send(event).map_err(|_| EventLoopClosed)
}
}
@@ -295,26 +292,33 @@ impl<T: 'static> EventLoop<T> {
let (display, mut event_queue) = Display::connect_to_env()?;
let display = Arc::new(display);
let sink = Arc::new(Mutex::new(WindowEventsSink::new()));
let store = Arc::new(Mutex::new(WindowStore::new()));
let seats = Arc::new(Mutex::new(Vec::new()));
let poll = Poll::new().unwrap();
let inner_loop = ::calloop::EventLoop::new().unwrap();
let (kbd_sender, kbd_channel) = channel();
let sink = EventsSink::new(kbd_sender);
poll.register(&kbd_channel, KBD_TOKEN, Ready::readable(), PollOpt::level())
let (kbd_sender, kbd_channel) = ::calloop::channel::channel::<crate::event::Event<()>>();
let kbd_sink = sink.clone();
let kbd_source = inner_loop
.handle()
.insert_source(kbd_channel, move |evt, &mut ()| {
if let ::calloop::channel::Event::Msg(evt) = evt {
let evt = evt.map_nonuser_event().ok().unwrap();
kbd_sink.lock().unwrap().send_event(evt);
}
})
.unwrap();
let pointer_constraints_proxy = Arc::new(Mutex::new(None));
let mut seat_manager = SeatManager {
sink,
store: store.clone(),
seats: seats.clone(),
sink: sink.clone(),
relative_pointer_manager_proxy: Rc::new(RefCell::new(None)),
pointer_constraints_proxy: pointer_constraints_proxy.clone(),
store: store.clone(),
seats: seats.clone(),
kbd_sender,
cursor_manager: Arc::new(Mutex::new(CursorManager::new(pointer_constraints_proxy))),
};
@@ -394,31 +398,39 @@ impl<T: 'static> EventLoop<T> {
)
.unwrap();
poll.register(&event_queue, EVQ_TOKEN, Ready::readable(), PollOpt::level())
let source = inner_loop
.handle()
.insert_source(event_queue, |(), &mut ()| {})
.unwrap();
let (user_sender, user_channel) = channel();
let pending_user_events = Rc::new(RefCell::new(VecDeque::new()));
let pending_user_events2 = pending_user_events.clone();
poll.register(
&user_channel,
USER_TOKEN,
Ready::readable(),
PollOpt::level(),
)
.unwrap();
let (user_sender, user_channel) = ::calloop::channel::channel();
let user_source = inner_loop
.handle()
.insert_source(user_channel, move |evt, &mut ()| {
if let ::calloop::channel::Event::Msg(msg) = evt {
pending_user_events2.borrow_mut().push_back(msg);
}
})
.unwrap();
let cursor_manager_clone = cursor_manager.clone();
Ok(EventLoop {
poll,
inner_loop,
sink,
pending_user_events,
display: display.clone(),
outputs: env.outputs.clone(),
_user_source: user_source,
user_sender,
user_channel,
kbd_channel,
cursor_manager,
_kbd_source: kbd_source,
window_target: RootELW {
p: crate::platform_impl::EventLoopWindowTarget::Wayland(EventLoopWindowTarget {
evq: RefCell::new(event_queue),
evq: RefCell::new(source),
store,
env,
cursor_manager: cursor_manager_clone,
@@ -440,7 +452,7 @@ impl<T: 'static> EventLoop<T> {
pub fn run<F>(mut self, callback: F) -> !
where
F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: 'static + FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
self.run_return(callback);
std::process::exit(0);
@@ -448,84 +460,67 @@ impl<T: 'static> EventLoop<T> {
pub fn run_return<F>(&mut self, mut callback: F)
where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
// send pending events to the server
self.display.flush().expect("Wayland connection lost.");
let mut control_flow = ControlFlow::default();
let mut events = Events::with_capacity(8);
let sink = self.sink.clone();
let user_events = self.pending_user_events.clone();
callback(
Event::NewEvents(StartCause::Init),
crate::event::Event::NewEvents(crate::event::StartCause::Init),
&self.window_target,
&mut control_flow,
);
loop {
// Read events from the event queue
self.post_dispatch_triggers();
// empty buffer of events
{
let mut evq = get_target(&self.window_target).evq.borrow_mut();
evq.dispatch_pending()
.expect("failed to dispatch wayland events");
if let Some(read) = evq.prepare_read() {
if let Err(e) = read.read_events() {
if e.kind() != ErrorKind::WouldBlock {
panic!("failed to read wayland events: {}", e);
}
}
evq.dispatch_pending()
.expect("failed to dispatch wayland events");
}
}
self.post_dispatch_triggers(&mut callback, &mut control_flow);
while let Ok(event) = self.kbd_channel.try_recv() {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}
while let Ok(event) = self.user_channel.try_recv() {
sticky_exit_callback(
Event::UserEvent(event),
&self.window_target,
&mut control_flow,
&mut callback,
);
}
// send Events cleared
{
sticky_exit_callback(
Event::MainEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
);
}
// handle request-redraw
{
self.redraw_triggers(|wid, window_target| {
let mut guard = sink.lock().unwrap();
guard.empty_with(|evt| {
sticky_exit_callback(
Event::RedrawRequested(crate::window::WindowId(
crate::platform_impl::WindowId::Wayland(wid),
)),
window_target,
evt,
&self.window_target,
&mut control_flow,
&mut callback,
);
});
}
// send RedrawEventsCleared
// empty user events
{
let mut guard = user_events.borrow_mut();
for evt in guard.drain(..) {
sticky_exit_callback(
crate::event::Event::UserEvent(evt),
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
// do a second run of post-dispatch-triggers, to handle user-generated "request-redraw"
// in response of resize & friends
self.post_dispatch_triggers();
{
let mut guard = sink.lock().unwrap();
guard.empty_with(|evt| {
sticky_exit_callback(
evt,
&self.window_target,
&mut control_flow,
&mut callback,
);
});
}
// send Events cleared
{
sticky_exit_callback(
Event::RedrawEventsCleared,
crate::event::Event::EventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
@@ -558,25 +553,24 @@ impl<T: 'static> EventLoop<T> {
ControlFlow::Exit => break,
ControlFlow::Poll => {
// non-blocking dispatch
self.poll
.poll(&mut events, Some(Duration::from_millis(0)))
self.inner_loop
.dispatch(Some(::std::time::Duration::from_millis(0)), &mut ())
.unwrap();
events.clear();
callback(
Event::NewEvents(StartCause::Poll),
crate::event::Event::NewEvents(crate::event::StartCause::Poll),
&self.window_target,
&mut control_flow,
);
}
ControlFlow::Wait => {
if !instant_wakeup {
self.poll.poll(&mut events, None).unwrap();
events.clear();
}
let timeout = if instant_wakeup {
Some(::std::time::Duration::from_millis(0))
} else {
None
};
self.inner_loop.dispatch(timeout, &mut ()).unwrap();
callback(
Event::NewEvents(StartCause::WaitCancelled {
crate::event::Event::NewEvents(crate::event::StartCause::WaitCancelled {
start: Instant::now(),
requested_resume: None,
}),
@@ -590,27 +584,29 @@ impl<T: 'static> EventLoop<T> {
let duration = if deadline > start && !instant_wakeup {
deadline - start
} else {
Duration::from_millis(0)
::std::time::Duration::from_millis(0)
};
self.poll.poll(&mut events, Some(duration)).unwrap();
events.clear();
self.inner_loop.dispatch(Some(duration), &mut ()).unwrap();
let now = Instant::now();
if now < deadline {
callback(
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
}),
crate::event::Event::NewEvents(
crate::event::StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
},
),
&self.window_target,
&mut control_flow,
);
} else {
callback(
Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
}),
crate::event::Event::NewEvents(
crate::event::StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
},
),
&self.window_target,
&mut control_flow,
);
@@ -619,7 +615,11 @@ impl<T: 'static> EventLoop<T> {
}
}
callback(Event::LoopDestroyed, &self.window_target, &mut control_flow);
callback(
crate::event::Event::LoopDestroyed,
&self.window_target,
&mut control_flow,
);
}
pub fn primary_monitor(&self) -> MonitorHandle {
@@ -646,44 +646,12 @@ impl<T> EventLoopWindowTarget<T> {
*/
impl<T> EventLoop<T> {
fn redraw_triggers<F>(&mut self, mut callback: F)
where
F: FnMut(WindowId, &RootELW<T>),
{
fn post_dispatch_triggers(&mut self) {
let mut sink = self.sink.lock().unwrap();
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<F>(&mut self, mut callback: F, control_flow: &mut ControlFlow)
where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
let window_target = match self.window_target.p {
crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt,
_ => unreachable!(),
};
let mut callback = |event: Event<'_, T>| {
sticky_exit_callback(event, &self.window_target, control_flow, &mut callback);
};
// prune possible dead windows
{
let mut cleanup_needed = window_target.cleanup_needed.lock().unwrap();
@@ -691,83 +659,65 @@ impl<T> EventLoop<T> {
let pruned = window_target.store.lock().unwrap().cleanup();
*cleanup_needed = false;
for wid in pruned {
callback(Event::WindowEvent {
window_id: crate::window::WindowId(
crate::platform_impl::WindowId::Wayland(wid),
),
event: WindowEvent::Destroyed,
});
sink.send_window_event(crate::event::WindowEvent::Destroyed, wid);
}
}
}
// process pending resize/refresh
window_target.store.lock().unwrap().for_each(|window| {
let window_id =
crate::window::WindowId(crate::platform_impl::WindowId::Wayland(window.wid));
if let Some(frame) = window.frame {
if let Some((w, h)) = window.newsize {
// mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case
// it overlaps mutter's `bounding box`, so we can't avoid this resize call,
// which calls `set_geometry` under the hood, for now.
frame.resize(w, h);
frame.refresh();
// Don't send resize event downstream if the new size is identical to the
// current one.
if (w, h) != *window.size {
let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64);
let physical_size = logical_size
.to_physical(window.new_dpi.unwrap_or(window.prev_dpi) as f64);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Resized(physical_size),
});
*window.size = (w, h);
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()
}
}
}
if let Some(dpi) = window.new_dpi {
let dpi = dpi as f64;
let logical_size = LogicalSize::<f64>::from(*window.size);
let mut new_inner_size = logical_size.to_physical(dpi);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: dpi,
new_inner_size: &mut new_inner_size,
},
});
let (w, h) = new_inner_size.to_logical::<u32>(dpi).into();
frame.resize(w, h);
*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 window.closed {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::CloseRequested,
});
}
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);
}
})
}
}
fn get_target<T>(target: &RootELW<T>) -> &EventLoopWindowTarget<T> {
match target.p {
crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt,
_ => unreachable!(),
if let Some(grab_cursor) = grab_cursor {
let surface = if grab_cursor { Some(surface) } else { None };
self.cursor_manager.lock().unwrap().grab_pointer(surface);
}
},
)
}
}
@@ -775,16 +725,17 @@ fn get_target<T>(target: &RootELW<T>) -> &EventLoopWindowTarget<T> {
* Wayland protocol implementations
*/
struct SeatManager {
sink: EventsSink,
struct SeatManager<T: 'static> {
sink: Arc<Mutex<WindowEventsSink<T>>>,
store: Arc<Mutex<WindowStore>>,
seats: Arc<Mutex<Vec<(u32, wl_seat::WlSeat)>>>,
kbd_sender: ::calloop::channel::Sender<crate::event::Event<()>>,
relative_pointer_manager_proxy: Rc<RefCell<Option<ZwpRelativePointerManagerV1>>>,
pointer_constraints_proxy: Arc<Mutex<Option<ZwpPointerConstraintsV1>>>,
cursor_manager: Arc<Mutex<CursorManager>>,
}
impl SeatManager {
impl<T: 'static> SeatManager<T> {
fn add_seat(&mut self, id: u32, version: u32, registry: wl_registry::WlRegistry) {
use std::cmp::min;
@@ -796,6 +747,7 @@ impl SeatManager {
relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.clone(),
keyboard: None,
touch: None,
kbd_sender: self.kbd_sender.clone(),
modifiers_tracker: Arc::new(Mutex::new(ModifiersState::default())),
cursor_manager: self.cursor_manager.clone(),
};
@@ -819,9 +771,10 @@ impl SeatManager {
}
}
struct SeatData {
sink: EventsSink,
struct SeatData<T> {
sink: Arc<Mutex<WindowEventsSink<T>>>,
store: Arc<Mutex<WindowStore>>,
kbd_sender: ::calloop::channel::Sender<crate::event::Event<()>>,
pointer: Option<wl_pointer::WlPointer>,
relative_pointer: Option<ZwpRelativePointerV1>,
relative_pointer_manager_proxy: Rc<RefCell<Option<ZwpRelativePointerManagerV1>>>,
@@ -831,7 +784,7 @@ struct SeatData {
cursor_manager: Arc<Mutex<CursorManager>>,
}
impl SeatData {
impl<T: 'static> SeatData<T> {
fn receive(&mut self, evt: wl_seat::Event, seat: wl_seat::WlSeat) {
match evt {
wl_seat::Event::Name { .. } => (),
@@ -877,7 +830,7 @@ impl SeatData {
if capabilities.contains(wl_seat::Capability::Keyboard) && self.keyboard.is_none() {
self.keyboard = Some(super::keyboard::init_keyboard(
&seat,
self.sink.clone(),
self.kbd_sender.clone(),
self.modifiers_tracker.clone(),
))
}
@@ -911,7 +864,7 @@ impl SeatData {
}
}
impl Drop for SeatData {
impl<T> Drop for SeatData<T> {
fn drop(&mut self) {
if let Some(pointer) = self.pointer.take() {
if pointer.as_ref().version() >= 3 {
@@ -945,7 +898,7 @@ pub struct VideoMode {
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
@@ -1005,9 +958,9 @@ impl fmt::Debug for MonitorHandle {
struct MonitorHandle {
name: Option<String>,
native_identifier: u32,
size: PhysicalSize<u32>,
position: PhysicalPosition<i32>,
scale_factor: i32,
size: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: i32,
}
let monitor_id_proxy = MonitorHandle {
@@ -1015,7 +968,7 @@ impl fmt::Debug for MonitorHandle {
native_identifier: self.native_identifier(),
size: self.size(),
position: self.position(),
scale_factor: self.scale_factor(),
hidpi_factor: self.hidpi_factor(),
};
monitor_id_proxy.fmt(f)
@@ -1034,7 +987,7 @@ impl MonitorHandle {
self.mgr.with_info(&self.proxy, |id, _| id).unwrap_or(0)
}
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
match self.mgr.with_info(&self.proxy, |_, info| {
info.modes
.iter()
@@ -1047,7 +1000,7 @@ impl MonitorHandle {
.into()
}
pub fn position(&self) -> PhysicalPosition<i32> {
pub fn position(&self) -> PhysicalPosition {
self.mgr
.with_info(&self.proxy, |_, info| info.location)
.unwrap_or((0, 0))
@@ -1055,7 +1008,7 @@ impl MonitorHandle {
}
#[inline]
pub fn scale_factor(&self) -> i32 {
pub fn hidpi_factor(&self) -> i32 {
self.mgr
.with_info(&self.proxy, |_, info| info.scale_factor)
.unwrap_or(1)

View File

@@ -1,6 +1,6 @@
use std::sync::{Arc, Mutex};
use super::{event_loop::EventsSink, make_wid, DeviceId};
use super::{make_wid, DeviceId};
use smithay_client_toolkit::{
keyboard::{
self, map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatEvent, KeyRepeatKind,
@@ -9,12 +9,12 @@ use smithay_client_toolkit::{
};
use crate::event::{
DeviceEvent, ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent,
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent,
};
pub fn init_keyboard(
seat: &wl_seat::WlSeat,
sink: EventsSink,
sink: ::calloop::channel::Sender<crate::event::Event<()>>,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
) -> wl_keyboard::WlKeyboard {
// { variables to be captured by the closures
@@ -31,12 +31,22 @@ pub fn init_keyboard(
match evt {
KbEvent::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send_window_event(WindowEvent::Focused(true), wid);
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(true),
})
.unwrap();
*target.lock().unwrap() = Some(wid);
}
KbEvent::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send_window_event(WindowEvent::Focused(false), wid);
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(false),
})
.unwrap();
*target.lock().unwrap() = None;
}
KbEvent::Key {
@@ -53,29 +63,32 @@ pub fn init_keyboard(
_ => unreachable!(),
};
let vkcode = key_to_vkey(rawkey, keysym);
my_sink.send_window_event(
#[allow(deprecated)]
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
input: KeyboardInput {
state,
scancode: rawkey,
virtual_keycode: vkcode,
modifiers: modifiers_tracker.lock().unwrap().clone(),
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::KeyboardInput {
device_id: device_id(),
input: KeyboardInput {
state,
scancode: rawkey,
virtual_keycode: vkcode,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
},
is_synthetic: false,
},
wid,
);
})
.unwrap();
// send char event only on key press, not release
if let ElementState::Released = state {
return;
}
if let Some(txt) = utf8 {
for chr in txt.chars() {
my_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid);
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::ReceivedCharacter(chr),
})
.unwrap();
}
}
}
@@ -88,7 +101,12 @@ pub fn init_keyboard(
*modifiers_tracker.lock().unwrap() = modifiers;
my_sink.send_device_event(DeviceEvent::ModifiersChanged(modifiers), DeviceId);
my_sink
.send(Event::DeviceEvent {
device_id: device_id(),
event: DeviceEvent::ModifiersChanged { modifiers },
})
.unwrap();
}
}
},
@@ -96,25 +114,28 @@ pub fn init_keyboard(
if let Some(wid) = *repeat_target.lock().unwrap() {
let state = ElementState::Pressed;
let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym);
repeat_sink.send_window_event(
#[allow(deprecated)]
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
input: KeyboardInput {
state,
scancode: repeat_event.rawkey,
virtual_keycode: vkcode,
modifiers: my_modifiers.lock().unwrap().clone(),
repeat_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::KeyboardInput {
device_id: device_id(),
input: KeyboardInput {
state,
scancode: repeat_event.rawkey,
virtual_keycode: vkcode,
modifiers: my_modifiers.lock().unwrap().clone(),
},
},
is_synthetic: false,
},
wid,
);
})
.unwrap();
if let Some(txt) = repeat_event.utf8 {
for chr in txt.chars() {
repeat_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid);
repeat_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::ReceivedCharacter(chr),
})
.unwrap();
}
}
}
@@ -141,12 +162,22 @@ pub fn init_keyboard(
move |evt, _| match evt {
wl_keyboard::Event::Enter { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send_window_event(WindowEvent::Focused(true), wid);
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(true),
})
.unwrap();
target = Some(wid);
}
wl_keyboard::Event::Leave { surface, .. } => {
let wid = make_wid(&surface);
my_sink.send_window_event(WindowEvent::Focused(false), wid);
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::Focused(false),
})
.unwrap();
target = None;
}
wl_keyboard::Event::Key { key, state, .. } => {
@@ -156,22 +187,20 @@ pub fn init_keyboard(
wl_keyboard::KeyState::Released => ElementState::Released,
_ => unreachable!(),
};
my_sink.send_window_event(
#[allow(deprecated)]
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
input: KeyboardInput {
state,
scancode: key,
virtual_keycode: None,
modifiers: ModifiersState::default(),
my_sink
.send(Event::WindowEvent {
window_id: mk_root_wid(wid),
event: WindowEvent::KeyboardInput {
device_id: device_id(),
input: KeyboardInput {
state,
scancode: key,
virtual_keycode: None,
modifiers: ModifiersState::default(),
},
},
is_synthetic: false,
},
wid,
);
})
.unwrap();
}
}
_ => (),
@@ -372,11 +401,19 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
impl ModifiersState {
pub(crate) fn from_wayland(mods: keyboard::ModifiersState) -> ModifiersState {
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
ModifiersState {
shift: mods.shift,
ctrl: mods.ctrl,
alt: mods.alt,
logo: mods.logo,
}
}
}
fn device_id() -> crate::event::DeviceId {
crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId))
}
fn mk_root_wid(wid: crate::platform_impl::wayland::WindowId) -> crate::window::WindowId {
crate::window::WindowId(crate::platform_impl::WindowId::Wayland(wid))
}

View File

@@ -2,7 +2,10 @@
target_os = "netbsd", target_os = "openbsd"))]
pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode},
event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode,
WindowEventsSink,
},
window::Window,
};

View File

@@ -6,14 +6,11 @@ use crate::event::{
};
use super::{
event_loop::{CursorManager, EventsSink},
make_wid,
event_loop::{CursorManager, WindowEventsSink},
window::WindowStore,
DeviceId,
};
use smithay_client_toolkit::surface;
use smithay_client_toolkit::reexports::client::protocol::{
wl_pointer::{self, Event as PtrEvent, WlPointer},
wl_seat,
@@ -31,15 +28,14 @@ use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints:
use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface;
pub fn implement_pointer(
pub fn implement_pointer<T: 'static>(
seat: &wl_seat::WlSeat,
sink: EventsSink,
sink: Arc<Mutex<WindowEventsSink<T>>>,
store: Arc<Mutex<WindowStore>>,
modifiers_tracker: Arc<Mutex<ModifiersState>>,
cursor_manager: Arc<Mutex<CursorManager>>,
) -> WlPointer {
seat.get_pointer(|pointer| {
// Currently focused winit surface
let mut mouse_focus = None;
let mut axis_buffer = None;
let mut axis_discrete_buffer = None;
@@ -47,6 +43,7 @@ pub fn implement_pointer(
pointer.implement_closure(
move |evt, pointer| {
let mut sink = sink.lock().unwrap();
let store = store.lock().unwrap();
let mut cursor_manager = cursor_manager.lock().unwrap();
match evt {
@@ -57,17 +54,8 @@ pub fn implement_pointer(
..
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
let scale_factor = surface::get_dpi_factor(&surface) as f64;
mouse_focus = Some(surface);
// Reload cursor style only when we enter winit's surface. Calling
// this function every time on `PtrEvent::Enter` could interfere with
// SCTK CSD handling, since it changes cursor icons when you hover
// cursor over the window borders.
cursor_manager.reload_cursor_style();
mouse_focus = Some(wid);
sink.send_window_event(
WindowEvent::CursorEntered {
device_id: crate::event::DeviceId(
@@ -81,13 +69,14 @@ pub fn implement_pointer(
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
position: (surface_x * scale_factor, surface_y * scale_factor)
.into(),
position: (surface_x, surface_y).into(),
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
);
}
cursor_manager.reload_cursor_style();
}
PtrEvent::Leave { surface, .. } => {
mouse_focus = None;
@@ -108,16 +97,13 @@ pub fn implement_pointer(
surface_y,
..
} => {
if let Some(surface) = mouse_focus.as_ref() {
let scale_factor = surface::get_dpi_factor(&surface) as f64;
let wid = make_wid(surface);
if let Some(wid) = mouse_focus {
sink.send_window_event(
WindowEvent::CursorMoved {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
position: (surface_x * scale_factor, surface_y * scale_factor)
.into(),
position: (surface_x, surface_y).into(),
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
@@ -125,7 +111,7 @@ pub fn implement_pointer(
}
}
PtrEvent::Button { button, state, .. } => {
if let Some(surface) = mouse_focus.as_ref() {
if let Some(wid) = mouse_focus {
let state = match state {
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
wl_pointer::ButtonState::Released => ElementState::Released,
@@ -147,13 +133,12 @@ pub fn implement_pointer(
button,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
make_wid(surface),
wid,
);
}
}
PtrEvent::Axis { axis, value, .. } => {
if let Some(surface) = mouse_focus.as_ref() {
let wid = make_wid(surface);
if let Some(wid) = mouse_focus {
if pointer.as_ref().version() < 5 {
let (mut x, mut y) = (0.0, 0.0);
// old seat compatibility
@@ -195,8 +180,7 @@ pub fn implement_pointer(
PtrEvent::Frame => {
let axis_buffer = axis_buffer.take();
let axis_discrete_buffer = axis_discrete_buffer.take();
if let Some(surface) = mouse_focus.as_ref() {
let wid = make_wid(surface);
if let Some(wid) = mouse_focus {
if let Some((x, y)) = axis_discrete_buffer {
sink.send_window_event(
WindowEvent::MouseWheel {
@@ -253,18 +237,20 @@ pub fn implement_pointer(
.unwrap()
}
pub fn implement_relative_pointer(
sink: EventsSink,
pub fn implement_relative_pointer<T: 'static>(
sink: Arc<Mutex<WindowEventsSink<T>>>,
pointer: &WlPointer,
manager: &ZwpRelativePointerManagerV1,
) -> Result<ZwpRelativePointerV1, ()> {
manager.get_relative_pointer(pointer, |rel_pointer| {
rel_pointer.implement_closure(
move |evt, _rel_pointer| match evt {
Event::RelativeMotion { dx, dy, .. } => {
sink.send_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId)
move |evt, _rel_pointer| {
let mut sink = sink.lock().unwrap();
match evt {
Event::RelativeMotion { dx, dy, .. } => sink
.send_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId),
_ => unreachable!(),
}
_ => unreachable!(),
},
(),
)

View File

@@ -2,7 +2,7 @@ use std::sync::{Arc, Mutex};
use crate::event::{TouchPhase, WindowEvent};
use super::{event_loop::EventsSink, window::WindowStore, DeviceId, WindowId};
use super::{event_loop::WindowEventsSink, window::WindowStore, DeviceId, WindowId};
use smithay_client_toolkit::reexports::client::protocol::{
wl_seat,
@@ -15,15 +15,16 @@ struct TouchPoint {
id: i32,
}
pub(crate) fn implement_touch(
pub(crate) fn implement_touch<T: 'static>(
seat: &wl_seat::WlSeat,
sink: EventsSink,
sink: Arc<Mutex<WindowEventsSink<T>>>,
store: Arc<Mutex<WindowStore>>,
) -> WlTouch {
let mut pending_ids = Vec::new();
seat.get_touch(|touch| {
touch.implement_closure(
move |evt, _| {
let mut sink = sink.lock().unwrap();
let store = store.lock().unwrap();
match evt {
TouchEvent::Down {

View File

@@ -6,7 +6,7 @@ use std::{
};
use crate::{
dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
monitor::MonitorHandle as RootMonitorHandle,
platform_impl::{
@@ -49,7 +49,11 @@ impl Window {
attributes: WindowAttributes,
pl_attribs: PlAttributes,
) -> Result<Window, RootOsError> {
// Create the surface first to get initial DPI
let (width, height) = attributes.inner_size.map(Into::into).unwrap_or((800, 600));
// Create the window
let size = Arc::new(Mutex::new((width, height)));
let fullscreen = Arc::new(Mutex::new(false));
let window_store = evlp.store.clone();
let cursor_manager = evlp.cursor_manager.clone();
let surface = evlp.env.create_surface(move |dpi, surface| {
@@ -57,18 +61,7 @@ impl Window {
surface.set_buffer_scale(dpi);
});
let dpi = get_dpi_factor(&surface) as f64;
let (width, height) = attributes
.inner_size
.map(|size| size.to_logical::<f64>(dpi).into())
.unwrap_or((800, 600));
// Create the window
let size = Arc::new(Mutex::new((width, height)));
let fullscreen = Arc::new(Mutex::new(false));
let window_store = evlp.store.clone();
let my_surface = surface.clone();
let mut frame = SWindow::<ConceptFrame>::init_from_env(
&evlp.env,
@@ -144,16 +137,8 @@ impl Window {
frame.set_title(attributes.title);
// min-max dimensions
frame.set_min_size(
attributes
.min_inner_size
.map(|size| size.to_logical::<f64>(dpi).into()),
);
frame.set_max_size(
attributes
.max_inner_size
.map(|size| size.to_logical::<f64>(dpi).into()),
);
frame.set_min_size(attributes.min_inner_size.map(Into::into));
frame.set_max_size(attributes.max_inner_size.map(Into::into));
let kill_switch = Arc::new(Mutex::new(false));
let need_frame_refresh = Arc::new(Mutex::new(true));
@@ -206,24 +191,22 @@ impl Window {
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn set_outer_position(&self, _pos: Position) {
pub fn set_outer_position(&self, _pos: LogicalPosition) {
// Not possible with wayland
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
let dpi = self.scale_factor() as f64;
let size = LogicalSize::<f64>::from(*self.size.lock().unwrap());
size.to_physical(dpi)
pub fn inner_size(&self) -> LogicalSize {
self.size.lock().unwrap().clone().into()
}
pub fn request_redraw(&self) {
@@ -231,39 +214,34 @@ impl Window {
}
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
let dpi = self.scale_factor() as f64;
pub fn outer_size(&self) -> LogicalSize {
let (w, h) = self.size.lock().unwrap().clone();
// let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
let size = LogicalSize::<f64>::from((w, h));
size.to_physical(dpi)
(w, h).into()
}
#[inline]
// NOTE: This will only resize the borders, the contents must be updated by the user
pub fn set_inner_size(&self, size: Size) {
let dpi = self.scale_factor() as f64;
let (w, h) = size.to_logical::<u32>(dpi).into();
pub fn set_inner_size(&self, size: LogicalSize) {
let (w, h) = size.into();
self.frame.lock().unwrap().resize(w, h);
*(self.size.lock().unwrap()) = (w, h);
}
#[inline]
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
let dpi = self.scale_factor() as f64;
pub fn set_min_inner_size(&self, dimensions: Option<LogicalSize>) {
self.frame
.lock()
.unwrap()
.set_min_size(dimensions.map(|dim| dim.to_logical::<f64>(dpi).into()));
.set_min_size(dimensions.map(Into::into));
}
#[inline]
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
let dpi = self.scale_factor() as f64;
pub fn set_max_inner_size(&self, dimensions: Option<LogicalSize>) {
self.frame
.lock()
.unwrap()
.set_max_size(dimensions.map(|dim| dim.to_logical::<f64>(dpi).into()));
.set_max_size(dimensions.map(Into::into));
}
#[inline]
@@ -272,7 +250,7 @@ impl Window {
}
#[inline]
pub fn scale_factor(&self) -> i32 {
pub fn hidpi_factor(&self) -> i32 {
get_dpi_factor(&self.surface)
}
@@ -281,13 +259,6 @@ 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();
@@ -347,7 +318,7 @@ impl Window {
}
#[inline]
pub fn set_cursor_position(&self, _pos: Position) -> Result<(), ExternalError> {
pub fn set_cursor_position(&self, _pos: LogicalPosition) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
@@ -397,7 +368,6 @@ impl Drop for Window {
struct InternalWindow {
surface: wl_surface::WlSurface,
// TODO: CONVERT TO LogicalSize<u32>s
newsize: Option<(u32, u32)>,
size: Arc<Mutex<(u32, u32)>>,
need_refresh: Arc<Mutex<bool>>,
@@ -415,18 +385,6 @@ pub struct WindowStore {
windows: Vec<InternalWindow>,
}
pub struct WindowStoreForEach<'a> {
pub newsize: Option<(u32, u32)>,
pub size: &'a mut (u32, u32),
pub prev_dpi: i32,
pub new_dpi: Option<i32>,
pub 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 {
@@ -476,24 +434,34 @@ impl WindowStore {
pub fn for_each<F>(&mut self, mut f: F)
where
F: FnMut(WindowStoreForEach<'_>),
F: FnMut(
Option<(u32, u32)>,
&mut (u32, u32),
Option<i32>,
bool,
bool,
bool,
Option<bool>,
&wl_surface::WlSurface,
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());
let mut size = { *window.size.lock().unwrap() };
f(WindowStoreForEach {
newsize: window.newsize.take(),
size: &mut size,
prev_dpi: window.current_dpi,
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),
});
*window.size.lock().unwrap() = size;
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),
);
if let Some(dpi) = window.new_dpi.take() {
window.current_dpi = dpi;
}
@@ -501,20 +469,4 @@ impl WindowStore {
window.closed = false;
}
}
pub fn for_each_redraw_trigger<F>(&mut self, mut f: F)
where
F: FnMut(bool, bool, WindowId, Option<&mut SWindow<ConceptFrame>>),
{
for window in &mut self.windows {
let opt_arc = window.frame.upgrade();
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
f(
replace(&mut *window.need_refresh.lock().unwrap(), false),
replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
make_wid(&window.surface),
opt_mutex_lock.as_mut().map(|m| &mut **m),
);
}
}
}

View File

@@ -1,9 +1,7 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc};
use std::{cell::RefCell, collections::HashMap, ptr, rc::Rc, slice};
use libc::{c_char, c_int, c_long, c_uint, c_ulong};
use parking_lot::MutexGuard;
use super::{
events, ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd,
DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId,
@@ -13,10 +11,8 @@ use super::{
use util::modifiers::{ModifierKeyState, ModifierKeymap};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
event::{
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, TouchPhase, WindowEvent,
},
dpi::{LogicalPosition, LogicalSize},
event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent},
event_loop::EventLoopWindowTarget as RootELW,
};
@@ -29,9 +25,6 @@ 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> {
@@ -47,7 +40,7 @@ impl<T: 'static> EventProcessor<T> {
fn with_window<F, Ret>(&self, window_id: ffi::Window, callback: F) -> Option<Ret>
where
F: Fn(&Arc<UnownedWindow>) -> Ret,
F: Fn(&UnownedWindow) -> Ret,
{
let mut deleted = false;
let window_id = WindowId(window_id);
@@ -61,7 +54,7 @@ impl<T: 'static> EventProcessor<T> {
deleted = arc.is_none();
arc
})
.map(|window| callback(&window));
.map(|window| callback(&*window));
if deleted {
// Garbage collection
wt.windows.borrow_mut().remove(&window_id);
@@ -109,7 +102,7 @@ impl<T: 'static> EventProcessor<T> {
pub(super) fn process_event<F>(&mut self, xev: &mut ffi::XEvent, mut callback: F)
where
F: FnMut(Event<'_, T>),
F: FnMut(Event<T>),
{
let wt = get_xtarget(&self.target);
// XFilterEvent tells us when an event has been discarded by the input method.
@@ -139,7 +132,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 },
});
}
}
@@ -323,11 +316,16 @@ impl<T: 'static> EventProcessor<T> {
}
ffi::ConfigureNotify => {
#[derive(Debug, Default)]
struct Events {
resized: Option<WindowEvent>,
moved: Option<WindowEvent>,
dpi_changed: Option<WindowEvent>,
}
let xev: &ffi::XConfigureEvent = xev.as_ref();
let xwindow = xev.window;
let window_id = mkwid(xwindow);
if let Some(window) = self.with_window(xwindow, Arc::clone) {
let events = self.with_window(xwindow, |window| {
// So apparently...
// `XSendEvent` (synthetic `ConfigureNotify`) -> position relative to root
// `XConfigureNotify` (real `ConfigureNotify`) -> position relative to parent
@@ -341,6 +339,7 @@ impl<T: 'static> EventProcessor<T> {
let new_inner_size = (xev.width as u32, xev.height as u32);
let new_inner_position = (xev.x as i32, xev.y as i32);
let mut monitor = window.current_monitor(); // This must be done *before* locking!
let mut shared_state_lock = window.shared_state.lock();
let (mut resized, moved) = {
@@ -370,6 +369,8 @@ impl<T: 'static> EventProcessor<T> {
(resized, moved)
};
let mut events = Events::default();
let new_outer_position = if moved || shared_state_lock.position.is_none() {
// We need to convert client area position to window position.
let frame_extents = shared_state_lock
@@ -386,13 +387,9 @@ impl<T: 'static> EventProcessor<T> {
.inner_pos_to_outer(new_inner_position.0, new_inner_position.1);
shared_state_lock.position = Some(outer);
if moved {
// Temporarily unlock shared state to prevent deadlock
MutexGuard::unlocked(&mut shared_state_lock, || {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Moved(outer.into()),
});
});
let logical_position =
LogicalPosition::from_physical(outer, monitor.hidpi_factor);
events.moved = Some(WindowEvent::Moved(logical_position));
}
outer
} else {
@@ -404,54 +401,34 @@ impl<T: 'static> EventProcessor<T> {
// resizing by dragging across monitors *without* dropping the window.
let (width, height) = shared_state_lock
.dpi_adjusted
.unwrap_or_else(|| (xev.width as u32, xev.height as u32));
.unwrap_or_else(|| (xev.width as f64, xev.height as f64));
let last_scale_factor = shared_state_lock.last_monitor.scale_factor;
let new_scale_factor = {
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);
let monitor = wt.xconn.get_monitor_for_window(Some(window_rect));
monitor = wt.xconn.get_monitor_for_window(Some(window_rect));
let new_hidpi_factor = monitor.hidpi_factor;
if monitor.is_dummy() {
// Avoid updating monitor using a dummy monitor handle
last_scale_factor
} else {
// Avoid caching an invalid dummy monitor handle
if monitor.id != 0 {
shared_state_lock.last_monitor = monitor.clone();
monitor.scale_factor
}
new_hidpi_factor
};
if last_scale_factor != new_scale_factor {
let (new_width, new_height) = window.adjust_for_dpi(
last_scale_factor,
new_scale_factor,
if last_hidpi_factor != new_hidpi_factor {
events.dpi_changed =
Some(WindowEvent::HiDpiFactorChanged(new_hidpi_factor));
let (new_width, new_height, flusher) = window.adjust_for_dpi(
last_hidpi_factor,
new_hidpi_factor,
width,
height,
&shared_state_lock,
);
let old_inner_size = PhysicalSize::new(width, height);
let mut new_inner_size = PhysicalSize::new(new_width, new_height);
// Temporarily unlock shared state to prevent deadlock
MutexGuard::unlocked(&mut shared_state_lock, || {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_scale_factor,
new_inner_size: &mut new_inner_size,
},
});
});
if new_inner_size != old_inner_size {
window.set_inner_size_physical(
new_inner_size.width,
new_inner_size.height,
);
shared_state_lock.dpi_adjusted = Some(new_inner_size.into());
// if the DPI factor changed, force a resize event to ensure the logical
// size is computed with the right DPI factor
resized = true;
}
flusher.queue();
shared_state_lock.dpi_adjusted = Some((new_width, new_height));
// if the DPI factor changed, force a resize event to ensure the logical
// size is computed with the right DPI factor
resized = true;
}
}
@@ -460,22 +437,44 @@ impl<T: 'static> EventProcessor<T> {
// WMs constrain the window size, making the resize fail. This would cause an endless stream of
// XResizeWindow requests, making Xorg, the winit client, and the WM consume 100% of CPU.
if let Some(adjusted_size) = shared_state_lock.dpi_adjusted {
if new_inner_size == adjusted_size || !util::wm_name_is_one_of(&["Xfwm4"]) {
let rounded_size = (
adjusted_size.0.round() as u32,
adjusted_size.1.round() as u32,
);
if new_inner_size == rounded_size || !util::wm_name_is_one_of(&["Xfwm4"]) {
// When this finally happens, the event will not be synthetic.
shared_state_lock.dpi_adjusted = None;
} else {
window.set_inner_size_physical(adjusted_size.0, adjusted_size.1);
unsafe {
(wt.xconn.xlib.XResizeWindow)(
wt.xconn.display,
xwindow,
rounded_size.0 as c_uint,
rounded_size.1 as c_uint,
);
}
}
}
if resized {
// Drop the shared state lock to prevent deadlock
drop(shared_state_lock);
let logical_size =
LogicalSize::from_physical(new_inner_size, monitor.hidpi_factor);
events.resized = Some(WindowEvent::Resized(logical_size));
}
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Resized(new_inner_size.into()),
});
events
});
if let Some(events) = events {
let window_id = mkwid(xwindow);
if let Some(event) = events.dpi_changed {
callback(Event::WindowEvent { window_id, event });
}
if let Some(event) = events.resized {
callback(Event::WindowEvent { window_id, event });
}
if let Some(event) = events.moved {
callback(Event::WindowEvent { window_id, event });
}
}
}
@@ -528,14 +527,13 @@ impl<T: 'static> EventProcessor<T> {
ffi::Expose => {
let xev: &ffi::XExposeEvent = xev.as_ref();
// 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);
let window = xev.window;
let window_id = mkwid(window);
callback(Event::RedrawRequested(window_id));
}
callback(Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
});
}
ffi::KeyPress | ffi::KeyRelease => {
@@ -557,13 +555,22 @@ 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 keycode != 0 {
let scancode = keycode - 8;
let keysym = wt.xconn.lookup_keysym(xkev);
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
};
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
update_modifiers!(
@@ -573,18 +580,16 @@ impl<T: 'static> EventProcessor<T> {
let modifiers = self.device_mod_state.modifiers();
#[allow(deprecated)]
callback(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
device_id,
input: KeyboardInput {
state,
scancode,
scancode: xkev.keycode - 8,
virtual_keycode,
modifiers,
},
is_synthetic: false,
},
});
}
@@ -621,7 +626,7 @@ impl<T: 'static> EventProcessor<T> {
ElementState::{Pressed, Released},
MouseButton::{Left, Middle, Other, Right},
MouseScrollDelta::LineDelta,
Touch,
Touch, TouchPhase,
WindowEvent::{
AxisMotion, CursorEntered, CursorLeft, CursorMoved, Focused, MouseInput,
MouseWheel,
@@ -723,16 +728,24 @@ impl<T: 'static> EventProcessor<T> {
util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos)
});
if cursor_moved == Some(true) {
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id,
position,
modifiers,
},
});
let dpi_factor =
self.with_window(xev.event, |window| window.hidpi_factor());
if let Some(dpi_factor) = dpi_factor {
let position = LogicalPosition::from_physical(
(xev.event_x as f64, xev.event_y as f64),
dpi_factor,
);
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id,
position,
modifiers,
},
});
} else {
return;
}
} else if cursor_moved.is_none() {
return;
}
@@ -822,14 +835,18 @@ impl<T: 'static> EventProcessor<T> {
}
}
}
callback(Event::WindowEvent {
window_id,
event: CursorEntered { device_id },
});
if self.window_exists(xev.event) {
callback(Event::WindowEvent {
window_id,
event: CursorEntered { device_id },
});
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
if let Some(dpi_factor) =
self.with_window(xev.event, |window| window.hidpi_factor())
{
let position = LogicalPosition::from_physical(
(xev.event_x as f64, xev.event_y as f64),
dpi_factor,
);
// The mods field on this event isn't actually populated, so query the
// pointer device. In the future, we can likely remove this round-trip by
@@ -872,6 +889,11 @@ impl<T: 'static> EventProcessor<T> {
ffi::XI_FocusIn => {
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
let dpi_factor =
match self.with_window(xev.event, |window| window.hidpi_factor()) {
Some(dpi_factor) => dpi_factor,
None => return,
};
let window_id = mkwid(xev.event);
wt.ime
@@ -884,10 +906,6 @@ 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
@@ -897,19 +915,18 @@ impl<T: 'static> EventProcessor<T> {
.map(|device| device.attachment)
.unwrap_or(2);
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
let position = LogicalPosition::from_physical(
(xev.event_x as f64, xev.event_y as f64),
dpi_factor,
);
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id: mkdid(pointer_id),
position,
modifiers,
modifiers: ModifiersState::from_x11(&xev.mods),
},
});
// 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 _) };
@@ -921,13 +938,8 @@ 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,
window_id: mkwid(xev.event),
event: Focused(false),
})
}
@@ -941,26 +953,13 @@ impl<T: 'static> EventProcessor<T> {
ffi::XI_TouchEnd => TouchPhase::Ended,
_ => unreachable!(),
};
if self.window_exists(xev.event) {
let id = xev.detail as u64;
let modifiers = self.device_mod_state.modifiers();
let location =
PhysicalPosition::new(xev.event_x as f64, xev.event_y as f64);
// 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.cast(),
modifiers,
},
});
}
let dpi_factor =
self.with_window(xev.event, |window| window.hidpi_factor());
if let Some(dpi_factor) = dpi_factor {
let location = LogicalPosition::from_physical(
(xev.event_x as f64, xev.event_y as f64),
dpi_factor,
);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Touch(Touch {
@@ -968,7 +967,7 @@ impl<T: 'static> EventProcessor<T> {
phase,
location,
force: None, // TODO
id,
id: xev.detail as u64,
}),
})
}
@@ -1057,11 +1056,22 @@ impl<T: 'static> EventProcessor<T> {
return;
}
let scancode = (keycode - 8) as u32;
let keysym = wt.xconn.keycode_to_keysym(keycode as ffi::KeyCode);
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 virtual_keycode = events::keysym_to_element(keysym as c_uint);
let modifiers = self.device_mod_state.modifiers();
#[allow(deprecated)]
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::Key(KeyboardInput {
@@ -1086,7 +1096,9 @@ impl<T: 'static> EventProcessor<T> {
if modifiers != new_modifiers {
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::ModifiersChanged(new_modifiers),
event: DeviceEvent::ModifiersChanged {
modifiers: new_modifiers,
},
});
}
}
@@ -1129,48 +1141,27 @@ impl<T: 'static> EventProcessor<T> {
.iter()
.find(|prev_monitor| prev_monitor.name == new_monitor.name)
.map(|prev_monitor| {
if new_monitor.scale_factor != prev_monitor.scale_factor {
if new_monitor.hidpi_factor != prev_monitor.hidpi_factor {
for (window_id, window) in wt.windows.borrow().iter() {
if let Some(window) = window.upgrade() {
// Check if the window is on this monitor
let monitor = window.current_monitor();
if monitor.name == new_monitor.name {
callback(Event::WindowEvent {
window_id: mkwid(window_id.0),
event: WindowEvent::HiDpiFactorChanged(
new_monitor.hidpi_factor,
),
});
let (width, height) =
window.inner_size_physical();
let (new_width, new_height) = window
.adjust_for_dpi(
prev_monitor.scale_factor,
new_monitor.scale_factor,
width,
height,
&*window.shared_state.lock(),
);
let window_id = crate::window::WindowId(
crate::platform_impl::platform::WindowId::X(
*window_id,
),
let (_, _, flusher) = window.adjust_for_dpi(
prev_monitor.hidpi_factor,
new_monitor.hidpi_factor,
width as f64,
height as f64,
);
let old_inner_size =
PhysicalSize::new(width, height);
let mut new_inner_size =
PhysicalSize::new(new_width, new_height);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_monitor.scale_factor,
new_inner_size: &mut new_inner_size,
},
});
if new_inner_size != old_inner_size {
let (new_width, new_height) =
new_inner_size.into();
window.set_inner_size_physical(
new_width, new_height,
);
}
flusher.queue();
}
}
}
@@ -1189,66 +1180,4 @@ 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);
#[allow(deprecated)]
callback(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
device_id,
input: KeyboardInput {
scancode,
state,
virtual_keycode,
modifiers,
},
is_synthetic: true,
},
});
}
}
}
fn is_first_touch(first: &mut Option<u64>, num: &mut u32, id: u64, phase: TouchPhase) -> bool {
match phase {
TouchPhase::Started => {
if *num == 0 {
*first = Some(id);
}
*num += 1;
}
TouchPhase::Cancelled | TouchPhase::Ended => {
if *first == Some(id) {
*first = None;
}
*num = num.saturating_sub(1);
}
_ => (),
}
*first == Some(id)
}

View File

@@ -1,10 +1,4 @@
#![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;
@@ -24,7 +18,7 @@ pub use self::{
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
collections::{HashMap, HashSet, VecDeque},
ffi::CStr,
mem::{self, MaybeUninit},
ops::Deref,
@@ -37,10 +31,6 @@ use std::{
use libc::{self, setlocale, LC_CTYPE};
use mio::{unix::EventedFd, Events, Poll, PollOpt, Ready, Token};
use mio_extras::channel::{channel, Receiver, SendError, Sender};
use self::{
dnd::{Dnd, DndState},
event_processor::EventProcessor,
@@ -49,15 +39,12 @@ use self::{
};
use crate::{
error::OsError as RootOsError,
event::{Event, StartCause},
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
platform_impl::{platform::sticky_exit_callback, PlatformSpecificWindowBuilderAttributes},
window::WindowAttributes,
};
const X_TOKEN: Token = Token(0);
const USER_TOKEN: Token = Token(1);
pub struct EventLoopWindowTarget<T> {
xconn: Arc<XConnection>,
wm_delete_window: ffi::Atom,
@@ -71,15 +58,18 @@ pub struct EventLoopWindowTarget<T> {
}
pub struct EventLoop<T: 'static> {
poll: Poll,
event_processor: EventProcessor<T>,
user_channel: Receiver<T>,
user_sender: Sender<T>,
target: Rc<RootELW<T>>,
inner_loop: ::calloop::EventLoop<()>,
_x11_source: ::calloop::Source<::calloop::generic::Generic<::calloop::generic::EventedRawFd>>,
_user_source: ::calloop::Source<::calloop::channel::Channel<T>>,
pending_user_events: Rc<RefCell<VecDeque<T>>>,
event_processor: Rc<RefCell<EventProcessor<T>>>,
user_sender: ::calloop::channel::Sender<T>,
pending_events: Rc<RefCell<VecDeque<Event<T>>>>,
pub(crate) target: Rc<RootELW<T>>,
}
pub struct EventLoopProxy<T: 'static> {
user_sender: Sender<T>,
user_sender: ::calloop::channel::Sender<T>,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
@@ -155,8 +145,6 @@ 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);
@@ -170,32 +158,33 @@ impl<T: 'static> EventLoop<T> {
xconn,
wm_delete_window,
net_wm_ping,
pending_redraws: pending_redraws.clone(),
pending_redraws: Default::default(),
}),
_marker: ::std::marker::PhantomData,
});
let poll = Poll::new().unwrap();
// A calloop event loop to drive us
let inner_loop = ::calloop::EventLoop::new().unwrap();
let (user_sender, user_channel) = channel();
// Handle user events
let pending_user_events = Rc::new(RefCell::new(VecDeque::new()));
let pending_user_events2 = pending_user_events.clone();
poll.register(
&EventedFd(&get_xtarget(&target).xconn.x11_fd),
X_TOKEN,
Ready::readable(),
PollOpt::level(),
)
.unwrap();
let (user_sender, user_channel) = ::calloop::channel::channel();
poll.register(
&user_channel,
USER_TOKEN,
Ready::readable(),
PollOpt::level(),
)
.unwrap();
let _user_source = inner_loop
.handle()
.insert_source(user_channel, move |evt, &mut ()| {
if let ::calloop::channel::Event::Msg(msg) = evt {
pending_user_events2.borrow_mut().push_back(msg);
}
})
.unwrap();
let event_processor = EventProcessor {
// Handle X11 events
let pending_events: Rc<RefCell<VecDeque<_>>> = Default::default();
let processor = EventProcessor {
target: target.clone(),
dnd,
devices: Default::default(),
@@ -204,8 +193,6 @@ impl<T: 'static> EventLoop<T> {
xi2ext,
mod_keymap,
device_mod_state: Default::default(),
num_touch: 0,
first_touch: None,
};
// Register for device hotplug events
@@ -215,12 +202,36 @@ impl<T: 'static> EventLoop<T> {
.select_xinput_events(root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask)
.queue();
event_processor.init_device(ffi::XIAllDevices);
processor.init_device(ffi::XIAllDevices);
let processor = Rc::new(RefCell::new(processor));
let event_processor = processor.clone();
// Setup the X11 event source
let mut x11_events =
::calloop::generic::Generic::from_raw_fd(get_xtarget(&target).xconn.x11_fd);
x11_events.set_interest(::calloop::mio::Ready::readable());
let _x11_source = inner_loop
.handle()
.insert_source(x11_events, {
let pending_events = pending_events.clone();
move |evt, &mut ()| {
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);
}
}
})
.unwrap();
let result = EventLoop {
poll,
user_channel,
inner_loop,
pending_events,
_x11_source,
_user_source,
user_sender,
pending_user_events,
event_processor,
target,
};
@@ -238,16 +249,12 @@ impl<T: 'static> EventLoop<T> {
&self.target
}
pub(crate) fn x_connection(&self) -> &Arc<XConnection> {
get_xtarget(&self.target).x_connection()
}
pub fn run_return<F>(&mut self, mut callback: F)
where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
{
let mut control_flow = ControlFlow::default();
let mut events = Events::with_capacity(8);
let wt = get_xtarget(&self.target);
callback(
crate::event::Event::NewEvents(crate::event::StartCause::Init),
@@ -256,31 +263,28 @@ impl<T: 'static> EventLoop<T> {
);
loop {
// Process all pending events
self.drain_events(&mut callback, &mut control_flow);
self.drain_events();
let wt = get_xtarget(&self.target);
// Empty the event buffer
{
let mut guard = self.pending_events.borrow_mut();
for evt in guard.drain(..) {
sticky_exit_callback(evt, &self.target, &mut control_flow, &mut callback);
}
}
// Empty the user event buffer
{
while let Ok(event) = self.user_channel.try_recv() {
let mut guard = self.pending_user_events.borrow_mut();
for evt in guard.drain(..) {
sticky_exit_callback(
crate::event::Event::UserEvent(event),
crate::event::Event::UserEvent(evt),
&self.target,
&mut control_flow,
&mut callback,
);
}
}
// 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
@@ -288,17 +292,20 @@ impl<T: 'static> EventLoop<T> {
for wid in windows {
sticky_exit_callback(
Event::RedrawRequested(crate::window::WindowId(super::WindowId::X(wid))),
Event::WindowEvent {
window_id: crate::window::WindowId(super::WindowId::X(wid)),
event: WindowEvent::RedrawRequested,
},
&self.target,
&mut control_flow,
&mut callback,
);
}
}
// send RedrawEventsCleared
// send Events cleared
{
sticky_exit_callback(
crate::event::Event::RedrawEventsCleared,
crate::event::Event::EventsCleared,
&self.target,
&mut control_flow,
&mut callback,
@@ -306,7 +313,7 @@ impl<T: 'static> EventLoop<T> {
}
let start = Instant::now();
let (mut cause, deadline, timeout);
let (mut cause, deadline, mut timeout);
match control_flow {
ControlFlow::Exit => break,
@@ -337,39 +344,26 @@ impl<T: 'static> EventLoop<T> {
}
}
if self.event_processor.poll() {
// If the XConnection already contains buffered events, we don't
// need to wait for data on the socket.
// However, we still need to check for user events.
self.poll
.poll(&mut events, Some(Duration::from_millis(0)))
.unwrap();
events.clear();
if self.events_waiting() {
timeout = Some(Duration::from_millis(0));
}
callback(
crate::event::Event::NewEvents(cause),
&self.target,
&mut control_flow,
);
} else {
self.poll.poll(&mut events, timeout).unwrap();
events.clear();
self.inner_loop.dispatch(timeout, &mut ()).unwrap();
let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline);
if wait_cancelled {
if let Some(deadline) = deadline {
if deadline > Instant::now() {
cause = StartCause::WaitCancelled {
start,
requested_resume: deadline,
requested_resume: Some(deadline),
};
}
callback(
crate::event::Event::NewEvents(cause),
&self.target,
&mut control_flow,
);
}
callback(
crate::event::Event::NewEvents(cause),
&self.target,
&mut control_flow,
);
}
callback(
@@ -381,41 +375,37 @@ impl<T: 'static> EventLoop<T> {
pub fn run<F>(mut self, callback: F) -> !
where
F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: 'static + FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
{
self.run_return(callback);
::std::process::exit(0);
}
fn drain_events<F>(&mut self, callback: &mut F, control_flow: &mut ControlFlow)
where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
let target = &self.target;
let mut xev = MaybeUninit::uninit();
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);
drain_events(&mut processor, &mut pending_events);
}
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
self.event_processor.process_event(&mut xev, |event| {
sticky_exit_callback(
event,
target,
control_flow,
&mut |event, window_target, control_flow| {
if let Event::RedrawRequested(crate::window::WindowId(
super::WindowId::X(wid),
)) = event
{
wt.pending_redraws.lock().unwrap().insert(wid);
} else {
callback(event, window_target, control_flow);
}
},
);
});
}
fn events_waiting(&self) -> bool {
!self.pending_events.borrow().is_empty() || self.event_processor.borrow().poll()
}
}
fn drain_events<T: 'static>(
processor: &mut EventProcessor<T>,
pending_events: &mut VecDeque<Event<T>>,
) {
let mut callback = |event| {
pending_events.push_back(event);
};
// process all pending events
let mut xev = MaybeUninit::uninit();
while unsafe { processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
processor.process_event(&mut xev, &mut callback);
}
}
@@ -435,14 +425,8 @@ impl<T> EventLoopWindowTarget<T> {
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_sender.send(event).map_err(|e| {
EventLoopClosed(if let SendError::Disconnected(x) = e {
x
} else {
unreachable!()
})
})
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.user_sender.send(event).map_err(|_| EventLoopClosed)
}
}
@@ -489,9 +473,21 @@ impl<'a> Deref for DeviceInfo<'a> {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(ffi::Window);
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId(0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(c_int);
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId(0)
}
}
pub struct Window(Arc<UnownedWindow>);
impl Deref for Window {

View File

@@ -38,7 +38,7 @@ pub struct VideoMode {
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
@@ -73,7 +73,7 @@ pub struct MonitorHandle {
/// If the monitor is the primary one
primary: bool,
/// The DPI scale factor
pub(crate) scale_factor: f64,
pub(crate) hidpi_factor: f64,
/// Used to determine which windows are on this monitor
pub(crate) rect: util::AaRect,
/// Supported video modes on this monitor
@@ -114,14 +114,14 @@ impl MonitorHandle {
crtc: *mut XRRCrtcInfo,
primary: bool,
) -> Option<Self> {
let (name, scale_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) };
let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) };
let rect = util::AaRect::new(position, dimensions);
Some(MonitorHandle {
id,
name,
scale_factor,
hidpi_factor,
dimensions,
position,
primary,
@@ -134,7 +134,7 @@ impl MonitorHandle {
MonitorHandle {
id: 0,
name: "<dummy monitor>".into(),
scale_factor: 1.0,
hidpi_factor: 1.0,
dimensions: (1, 1),
position: (0, 0),
primary: true,
@@ -143,11 +143,6 @@ 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())
}
@@ -157,17 +152,17 @@ impl MonitorHandle {
self.id as u32
}
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
self.dimensions.into()
}
pub fn position(&self) -> PhysicalPosition<i32> {
pub fn position(&self) -> PhysicalPosition {
self.position.into()
}
#[inline]
pub fn scale_factor(&self) -> f64 {
self.scale_factor
pub fn hidpi_factor(&self) -> f64 {
self.hidpi_factor
}
#[inline]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -116,10 +116,10 @@ impl ModifierKeyState {
let mut new_state = *state;
match except {
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()),
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,
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.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),
Modifier::Alt => state.alt = value,
Modifier::Ctrl => state.ctrl = value,
Modifier::Shift => state.shift = value,
Modifier::Logo => state.logo = value,
}
}

View File

@@ -4,19 +4,26 @@ use super::{
ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
*,
};
use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode};
/// Represents values of `WINIT_HIDPI_FACTOR`.
pub enum EnvVarDPI {
Randr,
Scale(f64),
NotSet,
}
use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode};
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");
@@ -26,7 +33,7 @@ pub fn calc_dpi_factor(
let ppmm = ((width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)).sqrt();
// Quantize 1/12 step size
let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0);
assert!(validate_scale_factor(dpi_factor));
assert!(validate_hidpi_factor(dpi_factor));
dpi_factor
}
@@ -100,65 +107,20 @@ impl XConnection {
(*output_info).nameLen as usize,
);
let name = String::from_utf8_lossy(name_slice).into();
// Override DPI if `WINIT_X11_SCALE_FACTOR` variable is set
let deprecated_dpi_override = env::var("WINIT_HIDPI_FACTOR").ok();
if deprecated_dpi_override.is_some() {
warn!(
"The WINIT_HIDPI_FACTOR environment variable is deprecated; use WINIT_X11_SCALE_FACTOR"
)
}
let dpi_env = env::var("WINIT_X11_SCALE_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_X11_SCALE_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`",
var
);
}
},
);
let scale_factor = match dpi_env {
EnvVarDPI::Randr => calc_dpi_factor(
let hidpi_factor = 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,
),
),
EnvVarDPI::Scale(dpi_override) => {
if !validate_scale_factor(dpi_override) {
panic!(
"`WINIT_X11_SCALE_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);
Some((name, scale_factor, modes))
Some((name, hidpi_factor, modes))
}
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Result<(), ()> {
unsafe {

View File

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

View File

@@ -15,7 +15,7 @@ use libc;
use parking_lot::Mutex;
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::{
@@ -36,7 +36,7 @@ pub struct SharedState {
pub inner_position: Option<(i32, i32)>,
pub inner_position_rel_parent: Option<(i32, i32)>,
pub last_monitor: X11MonitorHandle,
pub dpi_adjusted: Option<(u32, u32)>,
pub dpi_adjusted: Option<(f64, f64)>,
pub fullscreen: Option<Fullscreen>,
// Set when application calls `set_fullscreen` when window is not visible
pub desired_fullscreen: Option<Option<Fullscreen>>,
@@ -45,10 +45,8 @@ pub struct SharedState {
// Used to restore video mode after exiting fullscreen
pub desktop_video_mode: Option<(ffi::RRCrtc, ffi::RRMode)>,
pub frame_extents: Option<util::FrameExtentsHeuristic>,
pub min_inner_size: Option<Size>,
pub max_inner_size: Option<Size>,
pub resize_increments: Option<Size>,
pub base_size: Option<Size>,
pub min_inner_size: Option<LogicalSize>,
pub max_inner_size: Option<LogicalSize>,
pub visibility: Visibility,
}
@@ -85,8 +83,6 @@ impl SharedState {
frame_extents: None,
min_inner_size: None,
max_inner_size: None,
resize_increments: None,
base_size: None,
})
}
}
@@ -136,24 +132,24 @@ impl UnownedWindow {
})
.unwrap_or_else(|| monitors.swap_remove(0))
};
let dpi_factor = guessed_monitor.scale_factor();
let dpi_factor = guessed_monitor.hidpi_factor();
info!("Guessed window scale factor: {}", dpi_factor);
info!("Guessed window DPI factor: {}", dpi_factor);
let max_inner_size: Option<(u32, u32)> = window_attrs
.max_inner_size
.map(|size| size.to_physical::<u32>(dpi_factor).into());
.map(|size| size.to_physical(dpi_factor).into());
let min_inner_size: Option<(u32, u32)> = window_attrs
.min_inner_size
.map(|size| size.to_physical::<u32>(dpi_factor).into());
.map(|size| size.to_physical(dpi_factor).into());
let dimensions = {
// x11 only applies constraints when the window is actively resized
// by the user, so we have to manually apply the initial constraints
let mut dimensions: (u32, u32) = window_attrs
.inner_size
.map(|size| size.to_physical::<u32>(dpi_factor))
.or_else(|| Some((800, 600).into()))
.map(|size| size.to_physical(dpi_factor))
.map(Into::into)
.unwrap();
if let Some(max) = max_inner_size {
@@ -239,7 +235,7 @@ impl UnownedWindow {
)
};
let mut window = UnownedWindow {
let window = UnownedWindow {
xconn: Arc::clone(xconn),
xwindow,
root,
@@ -324,11 +320,10 @@ impl UnownedWindow {
{
let mut min_inner_size = window_attrs
.min_inner_size
.map(|size| size.to_physical::<u32>(dpi_factor));
.map(|size| size.to_physical(dpi_factor));
let mut max_inner_size = window_attrs
.max_inner_size
.map(|size| size.to_physical::<u32>(dpi_factor));
.map(|size| size.to_physical(dpi_factor));
if !window_attrs.resizable {
if util::wm_name_is_one_of(&["Xfwm4"]) {
warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4");
@@ -336,11 +331,9 @@ impl UnownedWindow {
max_inner_size = Some(dimensions.into());
min_inner_size = Some(dimensions.into());
let mut shared_state = window.shared_state.get_mut();
shared_state.min_inner_size = window_attrs.min_inner_size;
shared_state.max_inner_size = window_attrs.max_inner_size;
shared_state.resize_increments = pl_attribs.resize_increments;
shared_state.base_size = pl_attribs.base_size;
let mut shared_state_lock = window.shared_state.lock();
shared_state_lock.min_inner_size = window_attrs.min_inner_size;
shared_state_lock.max_inner_size = window_attrs.max_inner_size;
}
}
@@ -348,16 +341,8 @@ impl UnownedWindow {
normal_hints.set_size(Some(dimensions));
normal_hints.set_min_size(min_inner_size.map(Into::into));
normal_hints.set_max_size(max_inner_size.map(Into::into));
normal_hints.set_resize_increments(
pl_attribs
.resize_increments
.map(|size| size.to_physical::<u32>(dpi_factor).into()),
);
normal_hints.set_base_size(
pl_attribs
.base_size
.map(|size| size.to_physical::<u32>(dpi_factor).into()),
);
normal_hints.set_resize_increments(pl_attribs.resize_increments);
normal_hints.set_base_size(pl_attribs.base_size);
xconn.set_normal_hints(window.xwindow, normal_hints).queue();
}
@@ -455,6 +440,16 @@ impl UnownedWindow {
.map_err(|x_err| os_error!(OsError::XError(x_err)))
}
fn logicalize_coords(&self, (x, y): (i32, i32)) -> LogicalPosition {
let dpi = self.hidpi_factor();
LogicalPosition::from_physical((x, y), dpi)
}
fn logicalize_size(&self, (width, height): (u32, u32)) -> LogicalSize {
let dpi = self.hidpi_factor();
LogicalSize::from_physical((width, height), dpi)
}
fn set_pid(&self) -> Option<util::Flusher<'_>> {
let pid_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_PID\0") };
let client_machine_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_CLIENT_MACHINE\0") };
@@ -656,7 +651,7 @@ impl UnownedWindow {
};
// Don't set fullscreen on an invalid dummy monitor handle
if monitor.is_dummy() {
if monitor.id == 0 {
return None;
}
@@ -754,35 +749,6 @@ 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
@@ -956,11 +922,11 @@ impl UnownedWindow {
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
let extents = (*self.shared_state.lock()).frame_extents.clone();
if let Some(extents) = extents {
let (x, y) = self.inner_position_physical();
Ok(extents.inner_pos_to_outer(x, y).into())
let logical = self.inner_position().unwrap();
Ok(extents.inner_pos_to_outer_logical(logical, self.hidpi_factor()))
} else {
self.update_cached_frame_extents();
self.outer_position()
@@ -977,8 +943,8 @@ impl UnownedWindow {
}
#[inline]
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
Ok(self.inner_position_physical().into())
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
Ok(self.logicalize_coords(self.inner_position_physical()))
}
pub(crate) fn set_position_inner(&self, mut x: i32, mut y: i32) -> util::Flusher<'_> {
@@ -1007,8 +973,8 @@ impl UnownedWindow {
}
#[inline]
pub fn set_outer_position(&self, position: Position) {
let (x, y) = position.to_physical::<i32>(self.scale_factor()).into();
pub fn set_outer_position(&self, logical_position: LogicalPosition) {
let (x, y) = logical_position.to_physical(self.hidpi_factor()).into();
self.set_position_physical(x, y);
}
@@ -1022,16 +988,16 @@ impl UnownedWindow {
}
#[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> {
self.inner_size_physical().into()
pub fn inner_size(&self) -> LogicalSize {
self.logicalize_size(self.inner_size_physical())
}
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
pub fn outer_size(&self) -> LogicalSize {
let extents = self.shared_state.lock().frame_extents.clone();
if let Some(extents) = extents {
let (width, height) = self.inner_size_physical();
extents.inner_size_to_outer(width, height).into()
let logical = self.inner_size();
extents.inner_size_to_outer_logical(logical, self.hidpi_factor())
} else {
self.update_cached_frame_extents();
self.outer_size()
@@ -1052,9 +1018,9 @@ impl UnownedWindow {
}
#[inline]
pub fn set_inner_size(&self, size: Size) {
let dpi_factor = self.scale_factor();
let (width, height) = size.to_physical::<u32>(dpi_factor).into();
pub fn set_inner_size(&self, logical_size: LogicalSize) {
let dpi_factor = self.hidpi_factor();
let (width, height) = logical_size.to_physical(dpi_factor).into();
self.set_inner_size_physical(width, height);
}
@@ -1075,10 +1041,10 @@ impl UnownedWindow {
}
#[inline]
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
self.shared_state.lock().min_inner_size = dimensions;
let physical_dimensions =
dimensions.map(|dimensions| dimensions.to_physical::<u32>(self.scale_factor()).into());
pub fn set_min_inner_size(&self, logical_dimensions: Option<LogicalSize>) {
self.shared_state.lock().min_inner_size = logical_dimensions;
let physical_dimensions = logical_dimensions
.map(|logical_dimensions| logical_dimensions.to_physical(self.hidpi_factor()).into());
self.set_min_inner_size_physical(physical_dimensions);
}
@@ -1088,10 +1054,10 @@ impl UnownedWindow {
}
#[inline]
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
self.shared_state.lock().max_inner_size = dimensions;
let physical_dimensions =
dimensions.map(|dimensions| dimensions.to_physical::<u32>(self.scale_factor()).into());
pub fn set_max_inner_size(&self, logical_dimensions: Option<LogicalSize>) {
self.shared_state.lock().max_inner_size = logical_dimensions;
let physical_dimensions = logical_dimensions
.map(|logical_dimensions| logical_dimensions.to_physical(self.hidpi_factor()).into());
self.set_max_inner_size_physical(physical_dimensions);
}
@@ -1099,29 +1065,37 @@ impl UnownedWindow {
&self,
old_dpi_factor: f64,
new_dpi_factor: f64,
width: u32,
height: u32,
shared_state: &SharedState,
) -> (u32, u32) {
width: f64,
height: f64,
) -> (f64, f64, util::Flusher<'_>) {
let scale_factor = new_dpi_factor / old_dpi_factor;
let new_width = width * scale_factor;
let new_height = height * scale_factor;
self.update_normal_hints(|normal_hints| {
let dpi_adjuster =
|size: Size| -> (u32, u32) { size.to_physical::<u32>(new_dpi_factor).into() };
let max_size = shared_state.max_inner_size.map(&dpi_adjuster);
let min_size = shared_state.min_inner_size.map(&dpi_adjuster);
let resize_increments = shared_state.resize_increments.map(&dpi_adjuster);
let base_size = shared_state.base_size.map(&dpi_adjuster);
let dpi_adjuster = |(width, height): (u32, u32)| -> (u32, u32) {
let new_width = width as f64 * scale_factor;
let new_height = height as f64 * scale_factor;
(new_width.round() as u32, new_height.round() as u32)
};
let max_size = normal_hints.get_max_size().map(&dpi_adjuster);
let min_size = normal_hints.get_min_size().map(&dpi_adjuster);
let resize_increments = normal_hints.get_resize_increments().map(&dpi_adjuster);
let base_size = normal_hints.get_base_size().map(&dpi_adjuster);
normal_hints.set_max_size(max_size);
normal_hints.set_min_size(min_size);
normal_hints.set_resize_increments(resize_increments);
normal_hints.set_base_size(base_size);
})
.expect("Failed to update normal hints");
let new_width = (width as f64 * scale_factor).round() as u32;
let new_height = (height as f64 * scale_factor).round() as u32;
(new_width, new_height)
unsafe {
(self.xconn.xlib.XResizeWindow)(
self.xconn.display,
self.xwindow,
new_width.round() as c_uint,
new_height.round() as c_uint,
);
}
(new_width, new_height, util::Flusher::new(&self.xconn))
}
pub fn set_resizable(&self, resizable: bool) {
@@ -1133,25 +1107,25 @@ impl UnownedWindow {
return;
}
let (min_size, max_size) = if resizable {
let (logical_min, logical_max) = if resizable {
let shared_state_lock = self.shared_state.lock();
(
shared_state_lock.min_inner_size,
shared_state_lock.max_inner_size,
)
} else {
let window_size = Some(Size::from(self.inner_size()));
let window_size = Some(self.inner_size());
(window_size.clone(), window_size)
};
self.set_maximizable_inner(resizable).queue();
let dpi_factor = self.scale_factor();
let min_inner_size = min_size
.map(|size| size.to_physical::<u32>(dpi_factor))
let dpi_factor = self.hidpi_factor();
let min_inner_size = logical_min
.map(|logical_size| logical_size.to_physical(dpi_factor))
.map(Into::into);
let max_inner_size = max_size
.map(|size| size.to_physical::<u32>(dpi_factor))
let max_inner_size = logical_max
.map(|logical_size| logical_size.to_physical(dpi_factor))
.map(Into::into);
self.update_normal_hints(|normal_hints| {
normal_hints.set_min_size(min_inner_size);
@@ -1272,8 +1246,8 @@ impl UnownedWindow {
}
#[inline]
pub fn scale_factor(&self) -> f64 {
self.current_monitor().scale_factor
pub fn hidpi_factor(&self) -> f64 {
self.current_monitor().hidpi_factor
}
pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> {
@@ -1286,8 +1260,11 @@ impl UnownedWindow {
}
#[inline]
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
let (x, y) = position.to_physical::<i32>(self.scale_factor()).into();
pub fn set_cursor_position(
&self,
logical_position: LogicalPosition,
) -> Result<(), ExternalError> {
let (x, y) = logical_position.to_physical(self.hidpi_factor()).into();
self.set_cursor_position_physical(x, y)
}
@@ -1299,8 +1276,8 @@ impl UnownedWindow {
}
#[inline]
pub fn set_ime_position(&self, spot: Position) {
let (x, y) = spot.to_physical::<i32>(self.scale_factor()).into();
pub fn set_ime_position(&self, logical_spot: LogicalPosition) {
let (x, y) = logical_spot.to_physical(self.hidpi_factor()).into();
self.set_ime_position_physical(x, y);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,24 +11,12 @@ use std::{
time::Instant,
};
use cocoa::{
appkit::{NSApp, NSEventType::NSApplicationDefined, NSWindow},
base::{id, nil},
foundation::{NSAutoreleasePool, NSPoint, NSSize},
};
use objc::runtime::YES;
use cocoa::{appkit::NSApp, base::nil};
use crate::{
dpi::LogicalSize,
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
platform_impl::platform::{
event::{EventProxy, EventWrapper},
observer::EventLoopWaker,
util::{IdRef, Never},
window::get_window_id,
},
platform_impl::platform::{observer::EventLoopWaker, util::Never},
window::WindowId,
};
@@ -36,8 +24,8 @@ lazy_static! {
static ref HANDLER: Handler = Default::default();
}
impl<'a, Never> Event<'a, Never> {
fn userify<T: 'static>(self) -> Event<'a, T> {
impl Event<Never> {
fn userify<T: 'static>(self) -> Event<T> {
self.map_nonuser_event()
// `Never` can't be constructed, so the `UserEvent` variant can't
// be present here.
@@ -46,13 +34,12 @@ impl<'a, Never> Event<'a, Never> {
}
pub trait EventHandler: Debug {
// Not sure probably it should accept Event<'static, Never>
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow);
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow);
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
}
struct EventLoopHandler<T: 'static> {
callback: Box<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
callback: Box<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>,
will_exit: bool,
window_target: Rc<RootWindowTarget<T>>,
}
@@ -67,7 +54,7 @@ impl<T> Debug for EventLoopHandler<T> {
}
impl<T> EventHandler for EventLoopHandler<T> {
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
(self.callback)(event.userify(), &self.window_target, control_flow);
self.will_exit |= *control_flow == ControlFlow::Exit;
if self.will_exit {
@@ -96,7 +83,7 @@ struct Handler {
control_flow_prev: Mutex<ControlFlow>,
start_time: Mutex<Option<Instant>>,
callback: Mutex<Option<Box<dyn EventHandler>>>,
pending_events: Mutex<VecDeque<EventWrapper>>,
pending_events: Mutex<VecDeque<Event<Never>>>,
pending_redraw: Mutex<Vec<WindowId>>,
waker: Mutex<EventLoopWaker>,
}
@@ -105,7 +92,7 @@ unsafe impl Send for Handler {}
unsafe impl Sync for Handler {}
impl Handler {
fn events(&self) -> MutexGuard<'_, VecDeque<EventWrapper>> {
fn events<'a>(&'a self) -> MutexGuard<'a, VecDeque<Event<Never>>> {
self.pending_events.lock().unwrap()
}
@@ -113,7 +100,7 @@ impl Handler {
self.pending_redraw.lock().unwrap()
}
fn waker(&self) -> MutexGuard<'_, EventLoopWaker> {
fn waker<'a>(&'a self) -> MutexGuard<'a, EventLoopWaker> {
self.waker.lock().unwrap()
}
@@ -149,7 +136,7 @@ impl Handler {
*self.start_time.lock().unwrap() = Some(Instant::now());
}
fn take_events(&self) -> VecDeque<EventWrapper> {
fn take_events(&self) -> VecDeque<Event<Never>> {
mem::replace(&mut *self.events(), Default::default())
}
@@ -165,14 +152,9 @@ impl Handler {
self.in_callback.store(in_callback, Ordering::Release);
}
fn handle_nonuser_event(&self, wrapper: EventWrapper) {
fn handle_nonuser_event(&self, event: Event<Never>) {
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
match wrapper {
EventWrapper::StaticEvent(event) => {
callback.handle_nonuser_event(event, &mut *self.control_flow.lock().unwrap())
}
EventWrapper::EventProxy(proxy) => self.handle_proxy(proxy, callback),
}
callback.handle_nonuser_event(event, &mut *self.control_flow.lock().unwrap());
}
}
@@ -181,46 +163,6 @@ impl Handler {
callback.handle_user_events(&mut *self.control_flow.lock().unwrap());
}
}
fn handle_scale_factor_changed_event(
&self,
callback: &mut Box<dyn EventHandler + 'static>,
ns_window: IdRef,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
) {
let mut size = suggested_size.to_physical(scale_factor);
let new_inner_size = &mut size;
let event = Event::WindowEvent {
window_id: WindowId(get_window_id(*ns_window)),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
},
};
callback.handle_nonuser_event(event, &mut *self.control_flow.lock().unwrap());
let physical_size = *new_inner_size;
let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height);
unsafe { NSWindow::setContentSize_(*ns_window, size) };
}
fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box<dyn EventHandler + 'static>) {
match proxy {
EventProxy::DpiChangedProxy {
ns_window,
suggested_size,
scale_factor,
} => self.handle_scale_factor_changed_event(
callback,
ns_window,
suggested_size,
scale_factor,
),
}
}
}
pub enum AppState {}
@@ -229,7 +171,7 @@ impl AppState {
// This function extends lifetime of `callback` to 'static as its side effect
pub unsafe fn set_callback<F, T>(callback: F, window_target: Rc<RootWindowTarget<T>>)
where
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
{
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
// This transmute is always safe, in case it was reached through `run`, since our
@@ -237,8 +179,8 @@ impl AppState {
// they passed to callback will actually outlive it, some apps just can't move
// everything to event loop, so this is something that they should care about.
callback: mem::transmute::<
Box<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
Box<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
Box<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>,
Box<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>,
>(Box::new(callback)),
will_exit: false,
window_target,
@@ -247,7 +189,7 @@ impl AppState {
pub fn exit() {
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopDestroyed));
HANDLER.handle_nonuser_event(Event::LoopDestroyed);
HANDLER.set_in_callback(false);
HANDLER.callback.lock().unwrap().take();
}
@@ -256,9 +198,7 @@ impl AppState {
HANDLER.set_ready();
HANDLER.waker().start();
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(
StartCause::Init,
)));
HANDLER.handle_nonuser_event(Event::NewEvents(StartCause::Init));
HANDLER.set_in_callback(false);
}
@@ -289,7 +229,7 @@ impl AppState {
ControlFlow::Exit => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"),
};
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(cause)));
HANDLER.handle_nonuser_event(Event::NewEvents(cause));
HANDLER.set_in_callback(false);
}
@@ -301,18 +241,18 @@ impl AppState {
}
}
pub fn queue_event(wrapper: EventWrapper) {
pub fn queue_event(event: Event<Never>) {
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
panic!("Event queued from different thread: {:#?}", wrapper);
panic!("Event queued from different thread: {:#?}", event);
}
HANDLER.events().push_back(wrapper);
HANDLER.events().push_back(event);
}
pub fn queue_events(mut wrappers: VecDeque<EventWrapper>) {
pub fn queue_events(mut events: VecDeque<Event<Never>>) {
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
panic!("Events queued from different thread: {:#?}", wrappers);
panic!("Events queued from different thread: {:#?}", events);
}
HANDLER.events().append(&mut wrappers);
HANDLER.events().append(&mut events);
}
pub fn cleared() {
@@ -325,41 +265,18 @@ impl AppState {
for event in HANDLER.take_events() {
HANDLER.handle_nonuser_event(event);
}
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared));
for window_id in HANDLER.should_redraw() {
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(
HANDLER.handle_nonuser_event(Event::WindowEvent {
window_id,
)));
event: WindowEvent::RedrawRequested,
});
}
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
HANDLER.handle_nonuser_event(Event::EventsCleared);
HANDLER.set_in_callback(false);
}
if HANDLER.should_exit() {
unsafe {
let _: () = msg_send![NSApp(), stop: nil];
let pool = NSAutoreleasePool::new(nil);
let windows: id = msg_send![NSApp(), windows];
let window: id = msg_send![windows, objectAtIndex:0];
assert_ne!(window, nil);
let dummy_event: id = msg_send![class!(NSEvent),
otherEventWithType: NSApplicationDefined
location: NSPoint::new(0.0, 0.0)
modifierFlags: 0
timestamp: 0
windowNumber: 0
context: nil
subtype: 0
data1: 0
data2: 0
];
// To stop event loop immediately, we need to post some event here.
let _: () = msg_send![window, postEvent: dummy_event atStart: YES];
pool.drain();
};
let _: () = unsafe { msg_send![NSApp(), terminate: nil] };
return;
}
HANDLER.update_start_time();
match HANDLER.get_old_and_new_control_flow() {

View File

@@ -6,29 +6,10 @@ use cocoa::{
};
use crate::{
dpi::LogicalSize,
event::{ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent},
platform_impl::platform::{
util::{IdRef, Never},
DEVICE_ID,
},
event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent},
platform_impl::platform::DEVICE_ID,
};
#[derive(Debug)]
pub enum EventWrapper {
StaticEvent(Event<'static, Never>),
EventProxy(EventProxy),
}
#[derive(Debug, PartialEq)]
pub enum EventProxy {
DpiChangedProxy {
ns_window: IdRef,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
},
}
pub fn char_to_keycode(c: char) -> Option<VirtualKeyCode> {
// We only translate keys that are affected by keyboard layout.
//
@@ -243,24 +224,12 @@ pub fn check_function_keys(string: &str) -> Option<VirtualKeyCode> {
pub fn event_mods(event: id) -> ModifiersState {
let flags = unsafe { NSEvent::modifierFlags(event) };
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
ModifiersState {
shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask),
ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask),
alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
logo: flags.contains(NSEventModifierFlags::NSCommandKeyMask),
}
}
pub fn get_scancode(event: cocoa::base::id) -> c_ushort {
@@ -275,7 +244,7 @@ pub unsafe fn modifier_event(
ns_event: id,
keymask: NSEventModifierFlags,
was_key_pressed: bool,
) -> Option<WindowEvent<'static>> {
) -> Option<WindowEvent> {
if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask)
|| was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask)
{
@@ -287,7 +256,6 @@ pub unsafe fn modifier_event(
let scancode = get_scancode(ns_event);
let virtual_keycode = scancode_to_keycode(scancode);
#[allow(deprecated)]
Some(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
@@ -296,7 +264,6 @@ pub unsafe fn modifier_event(
virtual_keycode,
modifiers: event_mods(ns_event),
},
is_synthetic: false,
})
} else {
None

View File

@@ -84,7 +84,7 @@ impl<T> EventLoop<T> {
pub fn run<F>(mut self, callback: F) -> !
where
F: 'static + FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
{
self.run_return(callback);
process::exit(0);
@@ -92,16 +92,15 @@ impl<T> EventLoop<T> {
pub fn run_return<F>(&mut self, callback: F)
where
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
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();
}
}
@@ -143,10 +142,8 @@ impl<T> Proxy<T> {
}
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender
.send(event)
.map_err(|mpsc::SendError(x)| EventLoopClosed(x))?;
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.sender.send(event).map_err(|_| EventLoopClosed)?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);

View File

@@ -1,6 +1,5 @@
#![cfg(target_os = "macos")]
mod activation_hack;
mod app;
mod app_delegate;
mod app_state;

View File

@@ -1,14 +1,15 @@
use std::{collections::VecDeque, fmt};
use super::{ffi, util};
use super::ffi;
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::util::IdRef,
};
use cocoa::{
appkit::NSScreen,
base::{id, nil},
foundation::NSUInteger,
foundation::{NSString, NSUInteger},
};
use core_foundation::{
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
@@ -83,7 +84,7 @@ impl Clone for NativeDisplayMode {
}
impl VideoMode {
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
@@ -165,9 +166,9 @@ impl fmt::Debug for MonitorHandle {
struct MonitorHandle {
name: Option<String>,
native_identifier: u32,
size: PhysicalSize<u32>,
position: PhysicalPosition<i32>,
scale_factor: f64,
size: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorHandle {
@@ -175,7 +176,7 @@ impl fmt::Debug for MonitorHandle {
native_identifier: self.native_identifier(),
size: self.size(),
position: self.position(),
scale_factor: self.scale_factor(),
hidpi_factor: self.hidpi_factor(),
};
monitor_id_proxy.fmt(f)
@@ -198,24 +199,24 @@ impl MonitorHandle {
self.0
}
pub fn size(&self) -> PhysicalSize<u32> {
pub fn size(&self) -> PhysicalSize {
let MonitorHandle(display_id) = *self;
let display = CGDisplay::new(display_id);
let height = display.pixels_high();
let width = display.pixels_wide();
PhysicalSize::from_logical::<_, f64>((width as f64, height as f64), self.scale_factor())
PhysicalSize::from_logical((width as f64, height as f64), self.hidpi_factor())
}
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
pub fn position(&self) -> PhysicalPosition {
let bounds = unsafe { CGDisplayBounds(self.native_identifier()) };
PhysicalPosition::from_logical::<_, f64>(
PhysicalPosition::from_logical(
(bounds.origin.x as f64, bounds.origin.y as f64),
self.scale_factor(),
self.hidpi_factor(),
)
}
pub fn scale_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
let screen = match self.ns_screen() {
Some(screen) => screen,
None => return 1.0, // default to 1.0 when we can't find the screen
@@ -302,7 +303,7 @@ impl MonitorHandle {
let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0);
let screens = NSScreen::screens(nil);
let count: NSUInteger = msg_send![screens, count];
let key = util::ns_string_id_ref("NSScreenNumber");
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
for i in 0..count {
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
let device_description = NSScreen::deviceDescription(screen);

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ use std::{
use cocoa::{
appkit::{NSApp, NSEvent, NSEventModifierFlags, NSEventPhase, NSView, NSWindow},
base::{id, nil},
foundation::{NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger},
foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger},
};
use objc::{
declare::ClassDecl,
@@ -17,16 +17,15 @@ use objc::{
};
use crate::{
dpi::LogicalPosition,
event::{
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseButton,
MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent,
DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, TouchPhase,
VirtualKeyCode, WindowEvent,
},
platform_impl::platform::{
app_state::AppState,
event::{
char_to_keycode, check_function_keys, event_mods, get_scancode, modifier_event,
scancode_to_keycode, EventWrapper,
scancode_to_keycode,
},
ffi::*,
util::{self, IdRef},
@@ -36,47 +35,33 @@ use crate::{
window::WindowId,
};
pub struct CursorState {
pub visible: bool,
pub cursor: util::Cursor,
}
impl Default for CursorState {
fn default() -> Self {
Self {
visible: true,
cursor: Default::default(),
}
}
#[derive(Default)]
struct Modifiers {
shift_pressed: bool,
ctrl_pressed: bool,
win_pressed: bool,
alt_pressed: bool,
}
struct ViewState {
ns_window: id,
pub cursor_state: Arc<Mutex<CursorState>>,
pub cursor: Arc<Mutex<util::Cursor>>,
ime_spot: Option<(f64, f64)>,
raw_characters: Option<String>,
is_key_down: bool,
modifiers: ModifiersState,
tracking_rect: Option<NSInteger>,
modifiers: Modifiers,
}
impl ViewState {
fn get_scale_factor(&self) -> f64 {
(unsafe { NSWindow::backingScaleFactor(self.ns_window) }) as f64
}
}
pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<CursorState>>) {
let cursor_state = Default::default();
let cursor_access = Arc::downgrade(&cursor_state);
pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<util::Cursor>>) {
let cursor = Default::default();
let cursor_access = Arc::downgrade(&cursor);
let state = ViewState {
ns_window,
cursor_state,
cursor,
ime_spot: None,
raw_characters: None,
is_key_down: false,
modifiers: Default::default(),
tracking_rect: None,
};
unsafe {
// This is free'd in `dealloc`
@@ -251,10 +236,6 @@ 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();
@@ -280,19 +261,6 @@ 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
}
@@ -301,46 +269,17 @@ 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 tracking_rect: NSInteger = msg_send![this,
let _: () = 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");
@@ -370,12 +309,7 @@ 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 = state.cursor_state.lock().unwrap();
let cursor = if cursor_state.visible {
cursor_state.cursor.load()
} else {
util::invisible_cursor()
};
let cursor = state.cursor.lock().unwrap().load();
let _: () = msg_send![this,
addCursorRect:bounds
cursor:cursor
@@ -383,8 +317,13 @@ extern "C" fn reset_cursor_rects(this: &Object, _sel: Sel) {
}
}
extern "C" fn has_marked_text(_this: &Object, _sel: Sel) -> BOOL {
YES
extern "C" fn has_marked_text(this: &Object, _sel: Sel) -> BOOL {
unsafe {
trace!("Triggered `hasMarkedText`");
let marked_text: id = *this.get_ivar("markedText");
trace!("Completed `hasMarkedText`");
(marked_text.length() > 0) as i8
}
}
extern "C" fn marked_range(this: &Object, _sel: Sel) -> NSRange {
@@ -514,10 +453,10 @@ extern "C" fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_ran
let mut events = VecDeque::with_capacity(characters.len());
for character in string.chars().filter(|c| !is_corporate_character(*c)) {
events.push_back(EventWrapper::StaticEvent(Event::WindowEvent {
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::ReceivedCharacter(character),
}));
});
}
AppState::queue_events(events);
@@ -538,10 +477,10 @@ extern "C" fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
// The `else` condition would emit the same character, but I'm keeping this here both...
// 1) as a reminder for how `doCommandBySelector` works
// 2) to make our use of carriage return explicit
events.push_back(EventWrapper::StaticEvent(Event::WindowEvent {
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::ReceivedCharacter('\r'),
}));
});
} else {
let raw_characters = state.raw_characters.take();
if let Some(raw_characters) = raw_characters {
@@ -549,10 +488,10 @@ extern "C" fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
.chars()
.filter(|c| !is_corporate_character(*c))
{
events.push_back(EventWrapper::StaticEvent(Event::WindowEvent {
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::ReceivedCharacter(character),
}));
});
}
}
};
@@ -633,7 +572,6 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
let is_repeat = msg_send![event, isARepeat];
#[allow(deprecated)]
let window_event = Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
@@ -644,19 +582,18 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
virtual_keycode,
modifiers: event_mods(event),
},
is_synthetic: false,
},
};
let pass_along = {
AppState::queue_event(EventWrapper::StaticEvent(window_event));
AppState::queue_event(window_event);
// Emit `ReceivedCharacter` for key repeats
if is_repeat && state.is_key_down {
for character in characters.chars().filter(|c| !is_corporate_character(*c)) {
AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent {
AppState::queue_event(Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(character),
}));
});
}
false
} else {
@@ -686,7 +623,6 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) {
let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);
#[allow(deprecated)]
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::KeyboardInput {
@@ -697,11 +633,10 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) {
virtual_keycode,
modifiers: event_mods(event),
},
is_synthetic: false,
},
};
AppState::queue_event(EventWrapper::StaticEvent(window_event));
AppState::queue_event(window_event);
}
trace!("Completed `keyUp`");
}
@@ -717,50 +652,45 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) {
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSShiftKeyMask,
state.modifiers.shift(),
state.modifiers.shift_pressed,
) {
state.modifiers.toggle(ModifiersState::SHIFT);
state.modifiers.shift_pressed = !state.modifiers.shift_pressed;
events.push_back(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSControlKeyMask,
state.modifiers.ctrl(),
state.modifiers.ctrl_pressed,
) {
state.modifiers.toggle(ModifiersState::CTRL);
state.modifiers.ctrl_pressed = !state.modifiers.ctrl_pressed;
events.push_back(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSCommandKeyMask,
state.modifiers.logo(),
state.modifiers.win_pressed,
) {
state.modifiers.toggle(ModifiersState::LOGO);
state.modifiers.win_pressed = !state.modifiers.win_pressed;
events.push_back(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSAlternateKeyMask,
state.modifiers.alt(),
state.modifiers.alt_pressed,
) {
state.modifiers.toggle(ModifiersState::ALT);
state.modifiers.alt_pressed = !state.modifiers.alt_pressed;
events.push_back(window_event);
}
for event in events {
AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent {
AppState::queue_event(Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event,
}));
});
}
AppState::queue_event(EventWrapper::StaticEvent(Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::ModifiersChanged(state.modifiers),
}));
}
trace!("Completed `flagsChanged`");
}
@@ -801,7 +731,6 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
let event: id = msg_send![NSApp(), currentEvent];
#[allow(deprecated)]
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::KeyboardInput {
@@ -812,11 +741,10 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
virtual_keycode,
modifiers: event_mods(event),
},
is_synthetic: false,
},
};
AppState::queue_event(EventWrapper::StaticEvent(window_event));
AppState::queue_event(window_event);
}
trace!("Completed `cancelOperation`");
}
@@ -836,7 +764,7 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem
},
};
AppState::queue_event(EventWrapper::StaticEvent(window_event));
AppState::queue_event(window_event);
}
}
@@ -887,18 +815,17 @@ fn mouse_motion(this: &Object, event: id) {
let x = view_point.x as f64;
let y = view_rect.size.height as f64 - view_point.y as f64;
let logical_position = LogicalPosition::new(x, y);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: logical_position.to_physical(state.get_scale_factor()),
position: (x, y).into(),
modifiers: event_mods(event),
},
};
AppState::queue_event(EventWrapper::StaticEvent(window_event));
AppState::queue_event(window_event);
}
}
@@ -931,8 +858,27 @@ extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) {
},
};
AppState::queue_event(EventWrapper::StaticEvent(enter_event));
mouse_motion(this, event);
let move_event = {
let window_point = event.locationInWindow();
let view_point: NSPoint = msg_send![this,
convertPoint:window_point
fromView:nil // convert from window coordinates
];
let view_rect: NSRect = msg_send![this, frame];
let x = view_point.x as f64;
let y = (view_rect.size.height - view_point.y) as f64;
Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y).into(),
modifiers: event_mods(event),
},
}
};
AppState::queue_event(enter_event);
AppState::queue_event(move_event);
}
trace!("Completed `mouseEntered`");
}
@@ -950,7 +896,7 @@ extern "C" fn mouse_exited(this: &Object, _sel: Sel, _event: id) {
},
};
AppState::queue_event(EventWrapper::StaticEvent(window_event));
AppState::queue_event(window_event);
}
trace!("Completed `mouseExited`");
}
@@ -992,8 +938,8 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) {
},
};
AppState::queue_event(EventWrapper::StaticEvent(device_event));
AppState::queue_event(EventWrapper::StaticEvent(window_event));
AppState::queue_event(device_event);
AppState::queue_event(window_event);
}
trace!("Completed `scrollWheel`");
}
@@ -1016,7 +962,7 @@ extern "C" fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) {
},
};
AppState::queue_event(EventWrapper::StaticEvent(window_event));
AppState::queue_event(window_event);
}
trace!("Completed `pressureChangeWithEvent`");
}

View File

@@ -10,9 +10,7 @@ use std::{
};
use crate::{
dpi::{
LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical,
},
dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
@@ -22,7 +20,6 @@ use crate::{
ffi,
monitor::{self, MonitorHandle, VideoMode},
util::{self, IdRef},
view::CursorState,
view::{self, new_view},
window_delegate::new_delegate,
OsError,
@@ -36,7 +33,7 @@ use cocoa::{
NSWindow, NSWindowButton, NSWindowStyleMask,
},
base::{id, nil},
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize},
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString},
};
use core_graphics::display::{CGDisplay, CGDisplayMode};
use objc::{
@@ -68,7 +65,7 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub titlebar_hidden: bool,
pub titlebar_buttons_hidden: bool,
pub fullsize_content_view: bool,
pub resize_increments: Option<LogicalSize<f64>>,
pub resize_increments: Option<LogicalSize>,
pub disallow_hidpi: bool,
}
@@ -93,8 +90,8 @@ fn create_app(activation_policy: ActivationPolicy) -> Option<id> {
unsafe fn create_view(
ns_window: id,
pl_attribs: &PlatformSpecificWindowBuilderAttributes,
) -> Option<(IdRef, Weak<Mutex<CursorState>>)> {
let (ns_view, cursor_state) = new_view(ns_window);
) -> Option<(IdRef, Weak<Mutex<util::Cursor>>)> {
let (ns_view, cursor) = new_view(ns_window);
ns_view.non_nil().map(|ns_view| {
if !pl_attribs.disallow_hidpi {
ns_view.setWantsBestResolutionOpenGLSurface_(YES);
@@ -111,7 +108,7 @@ unsafe fn create_view(
ns_window.setContentView_(*ns_view);
ns_window.makeFirstResponder_(*ns_view);
(ns_view, cursor_state)
(ns_view, cursor)
})
}
@@ -132,17 +129,12 @@ fn create_window(
None => None,
};
let frame = match screen {
Some(screen) => NSScreen::frame(screen),
Some(screen) => appkit::NSScreen::frame(screen),
None => {
let screen = NSScreen::mainScreen(nil);
let scale_factor = NSScreen::backingScaleFactor(screen) as f64;
let (width, height) = match attrs.inner_size {
Some(size) => {
let logical = size.to_logical(scale_factor);
(logical.width, logical.height)
}
None => (800.0, 600.0),
};
let (width, height) = attrs
.inner_size
.map(|logical| (logical.width, logical.height))
.unwrap_or_else(|| (800.0, 600.0));
NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(width, height))
}
};
@@ -150,14 +142,10 @@ fn create_window(
let mut masks = if !attrs.decorations && !screen.is_some() {
// Resizable UnownedWindow without a titlebar or borders
// if decorations is set to false, ignore pl_attrs
NSWindowStyleMask::NSBorderlessWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
} else if pl_attrs.titlebar_hidden {
// if the titlebar is hidden, ignore other pl_attrs
NSWindowStyleMask::NSBorderlessWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
} else {
// default case, resizable window with titlebar and titlebar buttons
NSWindowStyleMask::NSClosableWindowMask
@@ -182,7 +170,7 @@ fn create_window(
NO,
));
let res = ns_window.non_nil().map(|ns_window| {
let title = util::ns_string_id_ref(&attrs.title);
let title = IdRef::new(NSString::alloc(nil).init_str(&attrs.title));
ns_window.setReleasedWhenClosed_(NO);
ns_window.setTitle_(*title);
ns_window.setAcceptsMouseMovedEvents_(YES);
@@ -255,13 +243,6 @@ 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,
@@ -302,8 +283,8 @@ pub struct UnownedWindow {
input_context: IdRef, // never changes
pub shared_state: Arc<Mutex<SharedState>>,
decorations: AtomicBool,
cursor_state: Weak<Mutex<CursorState>>,
pub inner_rect: Option<PhysicalSize<u32>>,
cursor: Weak<Mutex<util::Cursor>>,
cursor_visible: AtomicBool,
}
unsafe impl Send for UnownedWindow {}
@@ -332,7 +313,7 @@ impl UnownedWindow {
os_error!(OsError::CreationError("Couldn't create `NSWindow`"))
})?;
let (ns_view, cursor_state) =
let (ns_view, cursor) =
unsafe { create_view(*ns_window, &pl_attribs) }.ok_or_else(|| {
unsafe { pool.drain() };
os_error!(OsError::CreationError("Couldn't create `NSView`"))
@@ -340,8 +321,6 @@ impl UnownedWindow {
let input_context = unsafe { util::create_input_context(*ns_view) };
let dpi_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 };
unsafe {
if win_attribs.transparent {
ns_window.setOpaque_(NO);
@@ -349,14 +328,13 @@ impl UnownedWindow {
}
ns_app.activateIgnoringOtherApps_(YES);
win_attribs.min_inner_size.map(|dim| {
let logical_dim = dim.to_logical(dpi_factor);
set_min_inner_size(*ns_window, logical_dim)
});
win_attribs.max_inner_size.map(|dim| {
let logical_dim = dim.to_logical(dpi_factor);
set_max_inner_size(*ns_window, logical_dim)
});
win_attribs
.min_inner_size
.map(|dim| set_min_inner_size(*ns_window, dim));
win_attribs
.max_inner_size
.map(|dim| set_max_inner_size(*ns_window, dim));
use cocoa::foundation::NSArray;
// register for drag and drop operations.
@@ -376,9 +354,6 @@ impl UnownedWindow {
let maximized = win_attribs.maximized;
let visible = win_attribs.visible;
let decorations = win_attribs.decorations;
let inner_rect = win_attribs
.inner_size
.map(|size| size.to_physical(dpi_factor));
let window = Arc::new(UnownedWindow {
ns_view,
@@ -386,8 +361,8 @@ impl UnownedWindow {
input_context,
shared_state: Arc::new(Mutex::new(win_attribs.into())),
decorations: AtomicBool::new(decorations),
cursor_state,
inner_rect,
cursor,
cursor_visible: AtomicBool::new(true),
});
let delegate = new_delegate(&window, fullscreen.is_some());
@@ -444,31 +419,27 @@ impl UnownedWindow {
AppState::queue_redraw(RootWindowId(self.id()));
}
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
let frame_rect = unsafe { NSWindow::frame(*self.ns_window) };
let position = LogicalPosition::new(
Ok((
frame_rect.origin.x as f64,
util::bottom_left_to_top_left(frame_rect),
);
let dpi_factor = self.scale_factor();
Ok(position.to_physical(dpi_factor))
)
.into())
}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
let content_rect = unsafe {
NSWindow::contentRectForFrameRect_(*self.ns_window, NSWindow::frame(*self.ns_window))
};
let position = LogicalPosition::new(
Ok((
content_rect.origin.x as f64,
util::bottom_left_to_top_left(content_rect),
);
let dpi_factor = self.scale_factor();
Ok(position.to_physical(dpi_factor))
)
.into())
}
pub fn set_outer_position(&self, position: Position) {
let dpi_factor = self.scale_factor();
let position = position.to_logical(dpi_factor);
pub fn set_outer_position(&self, position: LogicalPosition) {
let dummy = NSRect::new(
NSPoint::new(
position.x,
@@ -484,50 +455,35 @@ impl UnownedWindow {
}
#[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> {
pub fn inner_size(&self) -> LogicalSize {
let view_frame = unsafe { NSView::frame(*self.ns_view) };
let logical: LogicalSize<f64> =
(view_frame.size.width as f64, view_frame.size.height as f64).into();
let dpi_factor = self.scale_factor();
logical.to_physical(dpi_factor)
(view_frame.size.width as f64, view_frame.size.height as f64).into()
}
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
pub fn outer_size(&self) -> LogicalSize {
let view_frame = unsafe { NSWindow::frame(*self.ns_window) };
let logical: LogicalSize<f64> =
(view_frame.size.width as f64, view_frame.size.height as f64).into();
let dpi_factor = self.scale_factor();
logical.to_physical(dpi_factor)
(view_frame.size.width as f64, view_frame.size.height as f64).into()
}
#[inline]
pub fn set_inner_size(&self, size: Size) {
pub fn set_inner_size(&self, size: LogicalSize) {
unsafe {
let dpi_factor = self.scale_factor();
util::set_content_size_async(*self.ns_window, size.to_logical(dpi_factor));
util::set_content_size_async(*self.ns_window, size);
}
}
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
pub fn set_min_inner_size(&self, dimensions: Option<LogicalSize>) {
unsafe {
let dimensions = dimensions.unwrap_or(Logical(LogicalSize {
width: 0.0,
height: 0.0,
}));
let dpi_factor = self.scale_factor();
set_min_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor));
let dimensions = dimensions.unwrap_or_else(|| (0, 0).into());
set_min_inner_size(*self.ns_window, dimensions);
}
}
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
pub fn set_max_inner_size(&self, dimensions: Option<LogicalSize>) {
unsafe {
let dimensions = dimensions.unwrap_or(Logical(LogicalSize {
width: std::f32::MAX as f64,
height: std::f32::MAX as f64,
}));
let dpi_factor = self.scale_factor();
set_max_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor));
let dimensions = dimensions.unwrap_or_else(|| (!0, !0).into());
set_max_inner_size(*self.ns_window, dimensions);
}
}
@@ -553,8 +509,8 @@ impl UnownedWindow {
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let cursor = util::Cursor::from(cursor);
if let Some(cursor_access) = self.cursor_state.upgrade() {
cursor_access.lock().unwrap().cursor = cursor;
if let Some(cursor_access) = self.cursor.upgrade() {
*cursor_access.lock().unwrap() = cursor;
}
unsafe {
let _: () = msg_send![*self.ns_window,
@@ -572,34 +528,33 @@ impl UnownedWindow {
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
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
];
}
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] };
}
self.cursor_visible.store(visible, Ordering::Release);
}
}
#[inline]
pub fn scale_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
unsafe { NSWindow::backingScaleFactor(*self.ns_window) as _ }
}
#[inline]
pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), ExternalError> {
let physical_window_position = self.inner_position().unwrap();
let dpi_factor = self.scale_factor();
let window_position = physical_window_position.to_logical::<CGFloat>(dpi_factor);
let logical_cursor_position = cursor_position.to_logical::<CGFloat>(dpi_factor);
pub fn set_cursor_position(
&self,
cursor_position: LogicalPosition,
) -> Result<(), ExternalError> {
let window_position = self.inner_position().unwrap();
let point = appkit::CGPoint {
x: logical_cursor_position.x + window_position.x,
y: logical_cursor_position.y + window_position.y,
x: (cursor_position.x + window_position.x) as CGFloat,
y: (cursor_position.y + window_position.y) as CGFloat,
};
CGDisplay::warp_mouse_cursor_position(point)
.map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?;
@@ -662,25 +617,6 @@ 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();
@@ -706,18 +642,11 @@ impl UnownedWindow {
#[inline]
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
trace!("Locked shared state in `set_fullscreen`");
let mut shared_state_lock = self.shared_state.lock().unwrap();
let 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`");
@@ -932,9 +861,7 @@ impl UnownedWindow {
}
#[inline]
pub fn set_ime_position(&self, spot: Position) {
let dpi_factor = self.scale_factor();
let logical_spot = spot.to_logical(dpi_factor);
pub fn set_ime_position(&self, logical_spot: LogicalPosition) {
unsafe {
view::set_ime_position(
*self.ns_view,
@@ -950,7 +877,7 @@ impl UnownedWindow {
unsafe {
let screen: id = msg_send![*self.ns_window, screen];
let desc = NSScreen::deviceDescription(screen);
let key = util::ns_string_id_ref("NSScreenNumber");
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
let value = NSDictionary::valueForKey_(desc, *key);
let display_id = msg_send![value, unsignedIntegerValue];
RootMonitorHandle {
@@ -1099,7 +1026,7 @@ impl Drop for UnownedWindow {
}
}
unsafe fn set_min_inner_size<V: NSWindow + Copy>(window: V, mut min_size: LogicalSize<f64>) {
unsafe fn set_min_inner_size<V: NSWindow + Copy>(window: V, mut min_size: LogicalSize) {
let mut current_rect = NSWindow::frame(window);
let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window));
// Convert from client area size to window size
@@ -1123,7 +1050,7 @@ unsafe fn set_min_inner_size<V: NSWindow + Copy>(window: V, mut min_size: Logica
}
}
unsafe fn set_max_inner_size<V: NSWindow + Copy>(window: V, mut max_size: LogicalSize<f64>) {
unsafe fn set_max_inner_size<V: NSWindow + Copy>(window: V, mut max_size: LogicalSize) {
let mut current_rect = NSWindow::frame(window);
let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window));
// Convert from client area size to window size

View File

@@ -19,7 +19,6 @@ use crate::{
event::{Event, WindowEvent},
platform_impl::platform::{
app_state::AppState,
event::{EventProxy, EventWrapper},
util::{self, IdRef},
window::{get_window_id, UnownedWindow},
},
@@ -48,18 +47,20 @@ pub struct WindowDelegateState {
impl WindowDelegateState {
pub fn new(window: &Arc<UnownedWindow>, initial_fullscreen: bool) -> Self {
let scale_factor = window.scale_factor();
let dpi_factor = window.hidpi_factor();
let mut delegate_state = WindowDelegateState {
ns_window: window.ns_window.clone(),
ns_view: window.ns_view.clone(),
window: Arc::downgrade(&window),
initial_fullscreen,
previous_position: None,
previous_dpi_factor: scale_factor,
previous_dpi_factor: dpi_factor,
};
if scale_factor != 1.0 {
delegate_state.emit_static_scale_factor_changed_event();
if dpi_factor != 1.0 {
delegate_state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor));
delegate_state.emit_resize_event();
}
delegate_state
@@ -72,34 +73,17 @@ impl WindowDelegateState {
self.window.upgrade().map(|ref window| callback(window))
}
pub fn emit_event(&mut self, event: WindowEvent<'static>) {
pub fn emit_event(&mut self, event: WindowEvent) {
let event = Event::WindowEvent {
window_id: WindowId(get_window_id(*self.ns_window)),
event,
};
AppState::queue_event(EventWrapper::StaticEvent(event));
}
pub fn emit_static_scale_factor_changed_event(&mut self) {
let scale_factor = self.get_scale_factor();
if scale_factor == self.previous_dpi_factor {
return ();
};
self.previous_dpi_factor = scale_factor;
let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
ns_window: IdRef::retain(*self.ns_window),
suggested_size: self.view_size(),
scale_factor,
});
AppState::queue_event(wrapper);
AppState::queue_event(event);
}
pub fn emit_resize_event(&mut self) {
let rect = unsafe { NSView::frame(*self.ns_view) };
let scale_factor = self.get_scale_factor();
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
let size = logical_size.to_physical(scale_factor);
let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
self.emit_event(WindowEvent::Resized(size));
}
@@ -113,15 +97,6 @@ impl WindowDelegateState {
self.emit_event(WindowEvent::Moved((x, y).into()));
}
}
fn get_scale_factor(&self) -> f64 {
(unsafe { NSWindow::backingScaleFactor(*self.ns_window) }) as f64
}
fn view_size(&self) -> LogicalSize<f64> {
let ns_size = unsafe { NSView::frame(*self.ns_view).size };
LogicalSize::new(ns_size.width as f64, ns_size.height as f64)
}
}
pub fn new_delegate(window: &Arc<UnownedWindow>, initial_fullscreen: bool) -> IdRef {
@@ -165,6 +140,10 @@ lazy_static! {
sel!(windowDidMove:),
window_did_move as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidChangeScreen:),
window_did_change_screen as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidChangeBackingProperties:),
window_did_change_backing_properties as extern "C" fn(&Object, Sel, id),
@@ -216,10 +195,6 @@ 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),
@@ -298,10 +273,29 @@ extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
trace!("Completed `windowDidMove:`");
}
extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidChangeScreen:`");
with_state(this, |state| {
let dpi_factor = unsafe { NSWindow::backingScaleFactor(*state.ns_window) } as f64;
if state.previous_dpi_factor != dpi_factor {
state.previous_dpi_factor = dpi_factor;
state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor));
state.emit_resize_event();
}
});
trace!("Completed `windowDidChangeScreen:`");
}
// This will always be called before `window_did_change_screen`.
extern "C" fn window_did_change_backing_properties(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidChangeBackingProperties:`");
with_state(this, |state| {
state.emit_static_scale_factor_changed_event();
let dpi_factor = unsafe { NSWindow::backingScaleFactor(*state.ns_window) } as f64;
if state.previous_dpi_factor != dpi_factor {
state.previous_dpi_factor = dpi_factor;
state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor));
state.emit_resize_event();
}
});
trace!("Completed `windowDidChangeBackingProperties:`");
}
@@ -425,27 +419,13 @@ 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,
@@ -471,17 +451,6 @@ 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:`");
}
@@ -492,15 +461,6 @@ 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:`");
@@ -525,13 +485,6 @@ extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) {
extern "C" fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidFailToEnterFullscreen:`");
with_state(this, |state| {
state.with_window(|window| {
trace!("Locked shared state in `window_did_fail_to_enter_fullscreen`");
let mut shared_state = window.shared_state.lock().unwrap();
shared_state.in_fullscreen_transition = false;
shared_state.target_fullscreen = None;
trace!("Unlocked shared state in `window_did_fail_to_enter_fullscreen`");
});
if state.initial_fullscreen {
let _: () = unsafe {
msg_send![*state.ns_window,

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,99 @@
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())
}
}
}

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