mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-27 07:03:15 -04:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49bcec1d27 | ||
|
|
878c179761 | ||
|
|
bc19c04339 | ||
|
|
c7a33f926b | ||
|
|
3c38afdb47 | ||
|
|
b8828105cf | ||
|
|
007b195a5e | ||
|
|
b4c6cdf9a3 | ||
|
|
26775fa0b6 | ||
|
|
114fe9d502 | ||
|
|
54bc41f68b | ||
|
|
47ff8d61d1 | ||
|
|
849b8f5dce | ||
|
|
aabe42d252 | ||
|
|
78a62ec547 | ||
|
|
6dae994bb4 | ||
|
|
4c4d0916fd | ||
|
|
d5609729cc | ||
|
|
1f24a09570 | ||
|
|
a8e777a5df | ||
|
|
0bc58f695b | ||
|
|
28023d9f5b | ||
|
|
c2aed1979d | ||
|
|
7e04273719 | ||
|
|
0683bdcd42 | ||
|
|
29ab0bb629 |
34
.github/workflows/ci.yml
vendored
34
.github/workflows/ci.yml
vendored
@@ -37,20 +37,21 @@ jobs:
|
||||
- { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
|
||||
- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
|
||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
||||
- { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' }
|
||||
- { target: x86_64-apple-darwin, os: macos-latest, }
|
||||
- { target: x86_64-apple-ios, os: macos-latest, }
|
||||
- { target: aarch64-apple-ios, os: macos-latest, }
|
||||
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
|
||||
# doesn't currently work on Linux.
|
||||
- { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, web: web }
|
||||
- { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, web: web }
|
||||
- { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, cmd: web }
|
||||
- { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, cmd: web }
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: "-C debuginfo=0"
|
||||
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
|
||||
WEB: ${{ matrix.platform.web }}
|
||||
CMD: ${{ matrix.platform.cmd }}
|
||||
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
steps:
|
||||
@@ -70,6 +71,9 @@ jobs:
|
||||
- name: Install GCC Multilib
|
||||
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
||||
run: sudo apt-get update && sudo apt-get install gcc-multilib
|
||||
- name: Install cargo-apk
|
||||
if: contains(matrix.platform.target, 'android')
|
||||
run: cargo install cargo-apk
|
||||
- name: Install cargo-web
|
||||
continue-on-error: true
|
||||
if: contains(matrix.platform.target, 'wasm32')
|
||||
@@ -78,29 +82,35 @@ jobs:
|
||||
- name: Check documentation
|
||||
shell: bash
|
||||
if: matrix.platform.target != 'wasm32-unknown-unknown'
|
||||
run: cargo doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES
|
||||
run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features $FEATURES
|
||||
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features $FEATURES
|
||||
|
||||
- name: Build tests
|
||||
shell: bash
|
||||
run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES
|
||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32'))
|
||||
run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features $FEATURES
|
||||
if: (
|
||||
!contains(matrix.platform.target, 'android') &&
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32'))
|
||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features $FEATURES
|
||||
|
||||
|
||||
- name: Build with serde enabled
|
||||
shell: bash
|
||||
run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
|
||||
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
|
||||
|
||||
- name: Build tests with serde enabled
|
||||
shell: bash
|
||||
run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
|
||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
|
||||
- name: Run tests with serde enabled
|
||||
shell: bash
|
||||
if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32'))
|
||||
run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
|
||||
if: (
|
||||
!contains(matrix.platform.target, 'android') &&
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32'))
|
||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
|
||||
|
||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,4 +1,22 @@
|
||||
# 0.22.0 (2020-03-07)
|
||||
# Unreleased
|
||||
|
||||
# 0.22.2 (2020-05-16)
|
||||
|
||||
- Added Clone implementation for 'static events.
|
||||
- On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`.
|
||||
- On Windows, fix `WindowBuilder::with_maximized` being ignored.
|
||||
- On Android, minimal platform support.
|
||||
- On iOS, touch positions are now properly converted to physical pixels.
|
||||
- On macOS, updated core-* dependencies and cocoa
|
||||
|
||||
# 0.22.1 (2020-04-16)
|
||||
|
||||
- On X11, fix `ResumeTimeReached` being fired too early.
|
||||
- On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame`
|
||||
- On Web, fix a possible panic during event handling
|
||||
- On macOS, fix `EventLoopProxy` leaking memory for every instance.
|
||||
|
||||
# 0.22.0 (2020-03-09)
|
||||
|
||||
- On Windows, fix minor timing issue in wait_until_time_or_msg
|
||||
- On Windows, rework handling of request_redraw() to address panics.
|
||||
@@ -18,6 +36,7 @@
|
||||
- Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
|
||||
- on macOS, fix incorrect ReceivedCharacter events for some key combinations.
|
||||
- **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`.
|
||||
- On macOS, a mouse motion event is now generated before every mouse click.
|
||||
|
||||
# 0.21.0 (2020-02-04)
|
||||
|
||||
|
||||
22
Cargo.toml
22
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.22.0"
|
||||
version = "0.22.2"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2018"
|
||||
@@ -12,7 +12,9 @@ documentation = "https://docs.rs/winit"
|
||||
categories = ["gui"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["serde"]
|
||||
features = ["serde", "web-sys"]
|
||||
default-target = "x86_64-unknown-linux-gnu"
|
||||
targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"]
|
||||
|
||||
[features]
|
||||
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
|
||||
@@ -31,21 +33,23 @@ bitflags = "1"
|
||||
image = "0.23"
|
||||
simple_logger = "1"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies.android_glue]
|
||||
version = "0.2"
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
ndk = "0.1.0"
|
||||
ndk-sys = "0.1.0"
|
||||
ndk-glue = "0.1.0"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
objc = "0.2.3"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
cocoa = "0.19.1"
|
||||
core-foundation = "0.6"
|
||||
core-graphics = "0.17.3"
|
||||
cocoa = "0.20"
|
||||
core-foundation = "0.7"
|
||||
core-graphics = "0.19"
|
||||
dispatch = "0.2.0"
|
||||
objc = "0.2.6"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
default_features = false
|
||||
features = ["display_link"]
|
||||
|
||||
@@ -78,7 +82,7 @@ wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "
|
||||
mio = "0.6"
|
||||
mio-extras = "2.0"
|
||||
smithay-client-toolkit = "^0.6.6"
|
||||
x11-dl = "2.18.3"
|
||||
x11-dl = "2.18.5"
|
||||
percent-encoding = "2.0"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
|
||||
|
||||
@@ -109,8 +109,8 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
|
||||
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
|
||||
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
|
||||
- **Gamepad/Joystick events**: Capturing input from gampads and joysticks.
|
||||
- **Device movement events:**: Capturing input from the device gyroscope and accelerometer.
|
||||
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
|
||||
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
|
||||
|
||||
## Platform
|
||||
### Windows
|
||||
|
||||
24
README.md
24
README.md
@@ -2,12 +2,11 @@
|
||||
|
||||
[](https://crates.io/crates/winit)
|
||||
[](https://docs.rs/winit)
|
||||
[](https://travis-ci.org/rust-windowing/winit)
|
||||
[](https://ci.appveyor.com/project/Osspial/winit/branch/master)
|
||||
[](https://github.com/rust-windowing/winit/actions)
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.22.0"
|
||||
winit = "0.22.2"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -77,3 +76,22 @@ Building a binary will yield a `.js` file. In order to use it in an HTML file, y
|
||||
the element of the `<canvas>` element (in the example you would retrieve it via `document.getElementById("my_id")`).
|
||||
More information [here](https://kripken.github.io/emscripten-site/docs/api_reference/module.html).
|
||||
- Make sure that you insert the `.js` file generated by Rust after the `Module` variable is created.
|
||||
|
||||
#### Android
|
||||
|
||||
This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation.
|
||||
|
||||
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
|
||||
```toml
|
||||
[[example]]
|
||||
name = "request_redraw_threaded"
|
||||
crate-type = ["cdylib"]
|
||||
```
|
||||
|
||||
And add this to the example file to add the native activity glue:
|
||||
```rust
|
||||
#[cfg(target_os = "android")]
|
||||
ndk_glue::ndk_glue!(main);
|
||||
```
|
||||
|
||||
And run the application with `cargo apk run --example request_redraw_threaded`
|
||||
|
||||
@@ -41,15 +41,9 @@ fn main() {
|
||||
println!("{:?}", event);
|
||||
match event {
|
||||
Event::NewEvents(start_cause) => {
|
||||
wait_cancelled = mode == Mode::WaitUntil;
|
||||
match start_cause {
|
||||
StartCause::ResumeTimeReached {
|
||||
start: _,
|
||||
requested_resume: _,
|
||||
} => {
|
||||
wait_cancelled = false;
|
||||
}
|
||||
_ => (),
|
||||
wait_cancelled = match start_cause {
|
||||
StartCause::WaitCancelled { .. } => mode == Mode::WaitUntil,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
|
||||
131
src/event.rs
131
src/event.rs
@@ -81,10 +81,11 @@ pub enum Event<'a, T: 'static> {
|
||||
///
|
||||
/// This event is useful as a place to put your code that should be run after all
|
||||
/// state-changing events have been handled and you want to do stuff (updating state, performing
|
||||
/// calculations, etc) that happens as the "main body" of your event loop. If your program draws
|
||||
/// graphics, it's usually better to do it in response to
|
||||
/// calculations, etc) that happens as the "main body" of your event loop. If your program only draws
|
||||
/// graphics when something changes, it's usually better to do it in response to
|
||||
/// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted
|
||||
/// immediately after this event.
|
||||
/// immediately after this event. Programs that draw graphics continuously, like most games,
|
||||
/// can render here unconditionally for simplicity.
|
||||
MainEventsCleared,
|
||||
|
||||
/// Emitted after `MainEventsCleared` when a window should be redrawn.
|
||||
@@ -97,6 +98,9 @@ pub enum Event<'a, T: 'static> {
|
||||
///
|
||||
/// During each iteration of the event loop, Winit will aggregate duplicate redraw requests
|
||||
/// into a single event, to help avoid duplicating rendering work.
|
||||
///
|
||||
/// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless
|
||||
/// something changes, like most non-game GUIs.
|
||||
RedrawRequested(WindowId),
|
||||
|
||||
/// Emitted after all `RedrawRequested` events have been processed and control flow is about to
|
||||
@@ -114,6 +118,30 @@ pub enum Event<'a, T: 'static> {
|
||||
LoopDestroyed,
|
||||
}
|
||||
|
||||
impl<T: Clone> Clone for Event<'static, T> {
|
||||
fn clone(&self) -> Self {
|
||||
use self::Event::*;
|
||||
match self {
|
||||
WindowEvent { window_id, event } => WindowEvent {
|
||||
window_id: *window_id,
|
||||
event: event.clone(),
|
||||
},
|
||||
UserEvent(event) => UserEvent(event.clone()),
|
||||
DeviceEvent { device_id, event } => DeviceEvent {
|
||||
device_id: *device_id,
|
||||
event: event.clone(),
|
||||
},
|
||||
NewEvents(cause) => NewEvents(cause.clone()),
|
||||
MainEventsCleared => MainEventsCleared,
|
||||
RedrawRequested(wid) => RedrawRequested(*wid),
|
||||
RedrawEventsCleared => RedrawEventsCleared,
|
||||
LoopDestroyed => LoopDestroyed,
|
||||
Suspended => Suspended,
|
||||
Resumed => Resumed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Event<'a, T> {
|
||||
pub fn map_nonuser_event<U>(self) -> Result<Event<'a, U>, Event<'a, T>> {
|
||||
use self::Event::*;
|
||||
@@ -326,6 +354,97 @@ pub enum WindowEvent<'a> {
|
||||
ThemeChanged(Theme),
|
||||
}
|
||||
|
||||
impl Clone for WindowEvent<'static> {
|
||||
fn clone(&self) -> Self {
|
||||
use self::WindowEvent::*;
|
||||
return match self {
|
||||
Resized(size) => Resized(size.clone()),
|
||||
Moved(pos) => Moved(pos.clone()),
|
||||
CloseRequested => CloseRequested,
|
||||
Destroyed => Destroyed,
|
||||
DroppedFile(file) => DroppedFile(file.clone()),
|
||||
HoveredFile(file) => HoveredFile(file.clone()),
|
||||
HoveredFileCancelled => HoveredFileCancelled,
|
||||
ReceivedCharacter(c) => ReceivedCharacter(*c),
|
||||
Focused(f) => Focused(*f),
|
||||
KeyboardInput {
|
||||
device_id,
|
||||
input,
|
||||
is_synthetic,
|
||||
} => KeyboardInput {
|
||||
device_id: *device_id,
|
||||
input: *input,
|
||||
is_synthetic: *is_synthetic,
|
||||
},
|
||||
|
||||
ModifiersChanged(modifiers) => ModifiersChanged(modifiers.clone()),
|
||||
#[allow(deprecated)]
|
||||
CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
modifiers,
|
||||
} => CursorMoved {
|
||||
device_id: *device_id,
|
||||
position: *position,
|
||||
modifiers: *modifiers,
|
||||
},
|
||||
CursorEntered { device_id } => CursorEntered {
|
||||
device_id: *device_id,
|
||||
},
|
||||
CursorLeft { device_id } => CursorLeft {
|
||||
device_id: *device_id,
|
||||
},
|
||||
#[allow(deprecated)]
|
||||
MouseWheel {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
modifiers,
|
||||
} => MouseWheel {
|
||||
device_id: *device_id,
|
||||
delta: *delta,
|
||||
phase: *phase,
|
||||
modifiers: *modifiers,
|
||||
},
|
||||
#[allow(deprecated)]
|
||||
MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button,
|
||||
modifiers,
|
||||
} => MouseInput {
|
||||
device_id: *device_id,
|
||||
state: *state,
|
||||
button: *button,
|
||||
modifiers: *modifiers,
|
||||
},
|
||||
TouchpadPressure {
|
||||
device_id,
|
||||
pressure,
|
||||
stage,
|
||||
} => TouchpadPressure {
|
||||
device_id: *device_id,
|
||||
pressure: *pressure,
|
||||
stage: *stage,
|
||||
},
|
||||
AxisMotion {
|
||||
device_id,
|
||||
axis,
|
||||
value,
|
||||
} => AxisMotion {
|
||||
device_id: *device_id,
|
||||
axis: *axis,
|
||||
value: *value,
|
||||
},
|
||||
Touch(touch) => Touch(*touch),
|
||||
ThemeChanged(theme) => ThemeChanged(theme.clone()),
|
||||
ScaleFactorChanged { .. } => {
|
||||
unreachable!("Static event can't be about scale factor changing")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> WindowEvent<'a> {
|
||||
pub fn to_static(self) -> Option<WindowEvent<'static>> {
|
||||
use self::WindowEvent::*;
|
||||
@@ -805,8 +924,10 @@ pub enum VirtualKeyCode {
|
||||
Multiply,
|
||||
Mute,
|
||||
MyComputer,
|
||||
NavigateForward, // also called "Prior"
|
||||
NavigateBackward, // also called "Next"
|
||||
// also called "Next"
|
||||
NavigateForward,
|
||||
// also called "Prior"
|
||||
NavigateBackward,
|
||||
NextTrack,
|
||||
NoConvert,
|
||||
NumpadComma,
|
||||
|
||||
@@ -72,7 +72,8 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ControlFlow {
|
||||
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
|
||||
/// whether or not new events are available to process.
|
||||
/// whether or not new events are available to process. For web, events are sent when
|
||||
/// `requestAnimationFrame` fires.
|
||||
Poll,
|
||||
/// When the current loop iteration finishes, suspend the thread until another event arrives.
|
||||
Wait,
|
||||
|
||||
22
src/lib.rs
22
src/lib.rs
@@ -73,14 +73,18 @@
|
||||
//! // Application update code.
|
||||
//!
|
||||
//! // Queue a RedrawRequested event.
|
||||
//! //
|
||||
//! // You only need to call this if you've determined that you need to redraw, in
|
||||
//! // applications which do not always need to. Applications that redraw continuously
|
||||
//! // can just render here instead.
|
||||
//! window.request_redraw();
|
||||
//! },
|
||||
//! Event::RedrawRequested(_) => {
|
||||
//! // Redraw the application.
|
||||
//! //
|
||||
//! // It's preferrable to render in this event rather than in MainEventsCleared, since
|
||||
//! // rendering in here allows the program to gracefully handle redraws requested
|
||||
//! // by the OS.
|
||||
//! // It's preferable for applications that do not render continuously to render in
|
||||
//! // this event rather than in MainEventsCleared, since rendering in here allows
|
||||
//! // the program to gracefully handle redraws requested by the OS.
|
||||
//! },
|
||||
//! _ => ()
|
||||
//! }
|
||||
@@ -94,8 +98,15 @@
|
||||
//! # Drawing on the window
|
||||
//!
|
||||
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
|
||||
//! retrieve the raw handle of the window (see the [`platform`] module), which in turn allows you
|
||||
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
|
||||
//! retrieve the raw handle of the window (see the [`platform`] module and/or the
|
||||
//! [`raw_window_handle`] method), which in turn allows you to create an
|
||||
//! OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
|
||||
//!
|
||||
//! Note that many platforms will display garbage data in the window's client area if the
|
||||
//! application doesn't render anything to the window by the time the desktop compositor is ready to
|
||||
//! display the window to the user. If you notice this happening, you should create the window with
|
||||
//! [`visible` set to `false`](crate::window::WindowBuilder::with_visible) and explicitly make the
|
||||
//! window visible only once you're ready to render into it.
|
||||
//!
|
||||
//! [`EventLoop`]: event_loop::EventLoop
|
||||
//! [`EventLoopExtDesktop::run_return`]: ./platform/desktop/trait.EventLoopExtDesktop.html#tymethod.run_return
|
||||
@@ -116,6 +127,7 @@
|
||||
//! [`UserEvent`]: event::Event::UserEvent
|
||||
//! [`LoopDestroyed`]: event::Event::LoopDestroyed
|
||||
//! [`platform`]: platform
|
||||
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
|
||||
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![deny(intra_doc_link_resolution_failure)]
|
||||
|
||||
@@ -1,32 +1,39 @@
|
||||
#![cfg(any(target_os = "android"))]
|
||||
|
||||
use crate::{EventLoop, Window, WindowBuilder};
|
||||
use std::os::raw::c_void;
|
||||
use crate::{
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
use ndk::configuration::Configuration;
|
||||
use ndk_glue::Rect;
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Android.
|
||||
pub trait EventLoopExtAndroid {
|
||||
/// Makes it possible for glutin to register a callback when a suspend event happens on Android
|
||||
fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>);
|
||||
}
|
||||
pub trait EventLoopExtAndroid {}
|
||||
|
||||
impl EventLoopExtAndroid for EventLoop {
|
||||
fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>) {
|
||||
self.event_loop.set_suspend_callback(cb);
|
||||
}
|
||||
}
|
||||
impl<T> EventLoopExtAndroid for EventLoop<T> {}
|
||||
|
||||
/// Additional methods on `EventLoopWindowTarget` that are specific to Android.
|
||||
pub trait EventLoopWindowTargetExtAndroid {}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Android.
|
||||
pub trait WindowExtAndroid {
|
||||
fn native_window(&self) -> *const c_void;
|
||||
fn content_rect(&self) -> Rect;
|
||||
|
||||
fn config(&self) -> Configuration;
|
||||
}
|
||||
|
||||
impl WindowExtAndroid for Window {
|
||||
#[inline]
|
||||
fn native_window(&self) -> *const c_void {
|
||||
self.window.native_window()
|
||||
fn content_rect(&self) -> Rect {
|
||||
self.window.content_rect()
|
||||
}
|
||||
|
||||
fn config(&self) -> Configuration {
|
||||
self.window.config()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Android.
|
||||
pub trait WindowBuilderExtAndroid {}
|
||||
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use libc;
|
||||
use std::os::raw;
|
||||
|
||||
#[link(name = "android")]
|
||||
#[link(name = "EGL")]
|
||||
#[link(name = "GLESv2")]
|
||||
extern "C" {}
|
||||
|
||||
/**
|
||||
** asset_manager.h
|
||||
**/
|
||||
pub type AAssetManager = raw::c_void;
|
||||
|
||||
/**
|
||||
** native_window.h
|
||||
**/
|
||||
pub type ANativeWindow = raw::c_void;
|
||||
|
||||
extern "C" {
|
||||
pub fn ANativeWindow_getHeight(window: *const ANativeWindow) -> libc::int32_t;
|
||||
pub fn ANativeWindow_getWidth(window: *const ANativeWindow) -> libc::int32_t;
|
||||
}
|
||||
|
||||
/**
|
||||
** native_activity.h
|
||||
**/
|
||||
pub type JavaVM = ();
|
||||
pub type JNIEnv = ();
|
||||
pub type jobject = *const libc::c_void;
|
||||
|
||||
pub type AInputQueue = (); // FIXME: wrong
|
||||
pub type ARect = (); // FIXME: wrong
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ANativeActivity {
|
||||
pub callbacks: *mut ANativeActivityCallbacks,
|
||||
pub vm: *mut JavaVM,
|
||||
pub env: *mut JNIEnv,
|
||||
pub clazz: jobject,
|
||||
pub internalDataPath: *const libc::c_char,
|
||||
pub externalDataPath: *const libc::c_char,
|
||||
pub sdkVersion: libc::int32_t,
|
||||
pub instance: *mut libc::c_void,
|
||||
pub assetManager: *mut AAssetManager,
|
||||
pub obbPath: *const libc::c_char,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ANativeActivityCallbacks {
|
||||
pub onStart: extern "C" fn(*mut ANativeActivity),
|
||||
pub onResume: extern "C" fn(*mut ANativeActivity),
|
||||
pub onSaveInstanceState: extern "C" fn(*mut ANativeActivity, *mut libc::size_t),
|
||||
pub onPause: extern "C" fn(*mut ANativeActivity),
|
||||
pub onStop: extern "C" fn(*mut ANativeActivity),
|
||||
pub onDestroy: extern "C" fn(*mut ANativeActivity),
|
||||
pub onWindowFocusChanged: extern "C" fn(*mut ANativeActivity, libc::c_int),
|
||||
pub onNativeWindowCreated: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onNativeWindowResized: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onNativeWindowRedrawNeeded: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onNativeWindowDestroyed: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
|
||||
pub onInputQueueCreated: extern "C" fn(*mut ANativeActivity, *mut AInputQueue),
|
||||
pub onInputQueueDestroyed: extern "C" fn(*mut ANativeActivity, *mut AInputQueue),
|
||||
pub onContentRectChanged: extern "C" fn(*mut ANativeActivity, *const ARect),
|
||||
pub onConfigurationChanged: extern "C" fn(*mut ANativeActivity),
|
||||
pub onLowMemory: extern "C" fn(*mut ANativeActivity),
|
||||
}
|
||||
|
||||
/**
|
||||
** looper.h
|
||||
**/
|
||||
pub type ALooper = ();
|
||||
|
||||
#[link(name = "android")]
|
||||
extern "C" {
|
||||
pub fn ALooper_forThread() -> *const ALooper;
|
||||
pub fn ALooper_acquire(looper: *const ALooper);
|
||||
pub fn ALooper_release(looper: *const ALooper);
|
||||
pub fn ALooper_prepare(opts: libc::c_int) -> *const ALooper;
|
||||
pub fn ALooper_pollOnce(
|
||||
timeoutMillis: libc::c_int,
|
||||
outFd: *mut libc::c_int,
|
||||
outEvents: *mut libc::c_int,
|
||||
outData: *mut *mut libc::c_void,
|
||||
) -> libc::c_int;
|
||||
pub fn ALooper_pollAll(
|
||||
timeoutMillis: libc::c_int,
|
||||
outFd: *mut libc::c_int,
|
||||
outEvents: *mut libc::c_int,
|
||||
outData: *mut *mut libc::c_void,
|
||||
) -> libc::c_int;
|
||||
pub fn ALooper_wake(looper: *const ALooper);
|
||||
pub fn ALooper_addFd(
|
||||
looper: *const ALooper,
|
||||
fd: libc::c_int,
|
||||
ident: libc::c_int,
|
||||
events: libc::c_int,
|
||||
callback: ALooper_callbackFunc,
|
||||
data: *mut libc::c_void,
|
||||
) -> libc::c_int;
|
||||
pub fn ALooper_removeFd(looper: *const ALooper, fd: libc::c_int) -> libc::c_int;
|
||||
}
|
||||
|
||||
pub const ALOOPER_PREPARE_ALLOW_NON_CALLBACKS: libc::c_int = 1 << 0;
|
||||
|
||||
pub const ALOOPER_POLL_WAKE: libc::c_int = -1;
|
||||
pub const ALOOPER_POLL_CALLBACK: libc::c_int = -2;
|
||||
pub const ALOOPER_POLL_TIMEOUT: libc::c_int = -3;
|
||||
pub const ALOOPER_POLL_ERROR: libc::c_int = -4;
|
||||
|
||||
pub const ALOOPER_EVENT_INPUT: libc::c_int = 1 << 0;
|
||||
pub const ALOOPER_EVENT_OUTPUT: libc::c_int = 1 << 1;
|
||||
pub const ALOOPER_EVENT_ERROR: libc::c_int = 1 << 2;
|
||||
pub const ALOOPER_EVENT_HANGUP: libc::c_int = 1 << 3;
|
||||
pub const ALOOPER_EVENT_INVALID: libc::c_int = 1 << 4;
|
||||
|
||||
pub type ALooper_callbackFunc =
|
||||
extern "C" fn(libc::c_int, libc::c_int, *mut libc::c_void) -> libc::c_int;
|
||||
@@ -1,450 +1,519 @@
|
||||
#![cfg(target_os = "android")]
|
||||
|
||||
extern crate android_glue;
|
||||
|
||||
mod ffi;
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::VecDeque,
|
||||
fmt,
|
||||
os::raw::c_void,
|
||||
sync::mpsc::{channel, Receiver},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{ExternalError, NotSupportedError},
|
||||
events::{Touch, TouchPhase},
|
||||
window::MonitorHandle as RootMonitorHandle,
|
||||
CreationError, CursorIcon, Event, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize,
|
||||
WindowAttributes, WindowEvent, WindowId as RootWindowId,
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error, event,
|
||||
event_loop::{self, ControlFlow},
|
||||
monitor, window,
|
||||
};
|
||||
use ndk::{
|
||||
configuration::Configuration,
|
||||
event::{InputEvent, MotionAction},
|
||||
looper::{ForeignLooper, Poll, ThreadLooper},
|
||||
};
|
||||
use ndk_glue::{Event, Rect};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use raw_window_handle::{android::AndroidHandle, RawWindowHandle};
|
||||
use CreationError::OsError;
|
||||
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
pub type OsError = std::io::Error;
|
||||
|
||||
pub struct EventLoop {
|
||||
event_rx: Receiver<android_glue::Event>,
|
||||
suspend_callback: RefCell<Option<Box<dyn Fn(bool) -> ()>>>,
|
||||
lazy_static! {
|
||||
static ref CONFIG: RwLock<Configuration> = RwLock::new(Configuration::new());
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventLoopProxy;
|
||||
enum EventSource {
|
||||
Callback,
|
||||
InputQueue,
|
||||
User,
|
||||
}
|
||||
|
||||
impl EventLoop {
|
||||
pub fn new() -> EventLoop {
|
||||
let (tx, rx) = channel();
|
||||
android_glue::add_sender(tx);
|
||||
EventLoop {
|
||||
event_rx: rx,
|
||||
suspend_callback: Default::default(),
|
||||
fn poll(poll: Poll) -> Option<EventSource> {
|
||||
match poll {
|
||||
Poll::Event { data, .. } => match data as usize {
|
||||
0 => Some(EventSource::Callback),
|
||||
1 => Some(EventSource::InputQueue),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Poll::Timeout => None,
|
||||
Poll::Wake => Some(EventSource::User),
|
||||
Poll::Callback => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
window_target: event_loop::EventLoopWindowTarget<T>,
|
||||
user_queue: Arc<Mutex<VecDeque<T>>>,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
window_target: event_loop::EventLoopWindowTarget {
|
||||
p: EventLoopWindowTarget {
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
user_queue: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorHandle);
|
||||
rb
|
||||
pub fn run<F>(self, mut event_handler: F) -> !
|
||||
where
|
||||
F: 'static
|
||||
+ FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
let mut cf = ControlFlow::default();
|
||||
let mut first_event = None;
|
||||
let mut start_cause = event::StartCause::Init;
|
||||
let looper = ThreadLooper::for_thread().unwrap();
|
||||
let mut running = false;
|
||||
|
||||
loop {
|
||||
event_handler(
|
||||
event::Event::NewEvents(start_cause),
|
||||
self.window_target(),
|
||||
&mut cf,
|
||||
);
|
||||
|
||||
let mut redraw = false;
|
||||
let mut resized = false;
|
||||
|
||||
match first_event.take() {
|
||||
Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() {
|
||||
Event::WindowCreated => {
|
||||
event_handler(event::Event::Resumed, self.window_target(), &mut cf);
|
||||
}
|
||||
Event::WindowResized => resized = true,
|
||||
Event::WindowRedrawNeeded => redraw = true,
|
||||
Event::WindowDestroyed => {
|
||||
event_handler(event::Event::Suspended, self.window_target(), &mut cf);
|
||||
}
|
||||
Event::Pause => running = false,
|
||||
Event::Resume => running = true,
|
||||
Event::ConfigChanged => {
|
||||
let am = ndk_glue::native_activity().asset_manager();
|
||||
let config = Configuration::from_asset_manager(&am);
|
||||
let old_scale_factor = MonitorHandle.scale_factor();
|
||||
*CONFIG.write().unwrap() = config;
|
||||
let scale_factor = MonitorHandle.scale_factor();
|
||||
if (scale_factor - old_scale_factor).abs() < f64::EPSILON {
|
||||
let mut size = MonitorHandle.size();
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::ScaleFactorChanged {
|
||||
new_inner_size: &mut size,
|
||||
scale_factor,
|
||||
},
|
||||
};
|
||||
event_handler(event, self.window_target(), &mut cf);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(EventSource::InputQueue) => {
|
||||
if let Some(input_queue) = ndk_glue::input_queue().as_ref() {
|
||||
while let Some(event) = input_queue.get_event() {
|
||||
println!("event {:?}", event);
|
||||
if let Some(event) = input_queue.pre_dispatch(event) {
|
||||
let window_id = window::WindowId(WindowId);
|
||||
let device_id = event::DeviceId(DeviceId);
|
||||
match &event {
|
||||
InputEvent::MotionEvent(motion_event) => {
|
||||
let phase = match motion_event.action() {
|
||||
MotionAction::Down => Some(event::TouchPhase::Started),
|
||||
MotionAction::Up => Some(event::TouchPhase::Ended),
|
||||
MotionAction::Move => Some(event::TouchPhase::Moved),
|
||||
MotionAction::Cancel => {
|
||||
Some(event::TouchPhase::Cancelled)
|
||||
}
|
||||
_ => None, // TODO mouse events
|
||||
};
|
||||
let pointer = motion_event.pointer_at_index(0);
|
||||
let location = PhysicalPosition {
|
||||
x: pointer.x() as _,
|
||||
y: pointer.y() as _,
|
||||
};
|
||||
|
||||
if let Some(phase) = phase {
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id,
|
||||
event: event::WindowEvent::Touch(event::Touch {
|
||||
device_id,
|
||||
phase,
|
||||
location,
|
||||
id: 0,
|
||||
force: None,
|
||||
}),
|
||||
};
|
||||
event_handler(event, self.window_target(), &mut cf);
|
||||
}
|
||||
}
|
||||
InputEvent::KeyEvent(_) => {} // TODO
|
||||
};
|
||||
input_queue.finish_event(event, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(EventSource::User) => {
|
||||
let mut user_queue = self.user_queue.lock().unwrap();
|
||||
while let Some(event) = user_queue.pop_front() {
|
||||
event_handler(
|
||||
event::Event::UserEvent(event),
|
||||
self.window_target(),
|
||||
&mut cf,
|
||||
);
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
event_handler(
|
||||
event::Event::MainEventsCleared,
|
||||
self.window_target(),
|
||||
&mut cf,
|
||||
);
|
||||
|
||||
if resized && running {
|
||||
let size = MonitorHandle.size();
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::Resized(size),
|
||||
};
|
||||
event_handler(event, self.window_target(), &mut cf);
|
||||
}
|
||||
|
||||
if redraw && running {
|
||||
let event = event::Event::RedrawRequested(window::WindowId(WindowId));
|
||||
event_handler(event, self.window_target(), &mut cf);
|
||||
}
|
||||
|
||||
event_handler(
|
||||
event::Event::RedrawEventsCleared,
|
||||
self.window_target(),
|
||||
&mut cf,
|
||||
);
|
||||
|
||||
match cf {
|
||||
ControlFlow::Exit => panic!(),
|
||||
ControlFlow::Poll => {
|
||||
start_cause = event::StartCause::Poll;
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
first_event = poll(looper.poll_all().unwrap());
|
||||
start_cause = event::StartCause::WaitCancelled {
|
||||
start: Instant::now(),
|
||||
requested_resume: None,
|
||||
}
|
||||
}
|
||||
ControlFlow::WaitUntil(instant) => {
|
||||
let start = Instant::now();
|
||||
let duration = if instant <= start {
|
||||
Duration::default()
|
||||
} else {
|
||||
instant - start
|
||||
};
|
||||
first_event = poll(looper.poll_all_timeout(duration).unwrap());
|
||||
start_cause = if first_event.is_some() {
|
||||
event::StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(instant),
|
||||
}
|
||||
} else {
|
||||
event::StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume: instant,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget<T> {
|
||||
&self.window_target
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
||||
where
|
||||
F: FnMut(::Event),
|
||||
{
|
||||
while let Ok(event) = self.event_rx.try_recv() {
|
||||
let e = match event {
|
||||
android_glue::Event::EventMotion(motion) => {
|
||||
let scale_factor = MonitorHandle.scale_factor();
|
||||
let location = LogicalPosition::from_physical(
|
||||
(motion.x as f64, motion.y as f64),
|
||||
scale_factor,
|
||||
);
|
||||
Some(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId),
|
||||
event: WindowEvent::Touch(Touch {
|
||||
phase: match motion.action {
|
||||
android_glue::MotionAction::Down => TouchPhase::Started,
|
||||
android_glue::MotionAction::Move => TouchPhase::Moved,
|
||||
android_glue::MotionAction::Up => TouchPhase::Ended,
|
||||
android_glue::MotionAction::Cancel => TouchPhase::Cancelled,
|
||||
},
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: motion.pointer_id as u64,
|
||||
device_id: DEVICE_ID,
|
||||
}),
|
||||
})
|
||||
}
|
||||
android_glue::Event::InitWindow => {
|
||||
// The activity went to foreground.
|
||||
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
|
||||
(*cb)(false);
|
||||
}
|
||||
Some(Event::Resumed)
|
||||
}
|
||||
android_glue::Event::TermWindow => {
|
||||
// The activity went to background.
|
||||
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
|
||||
(*cb)(true);
|
||||
}
|
||||
Some(Event::Suspended)
|
||||
}
|
||||
android_glue::Event::WindowResized | android_glue::Event::ConfigChanged => {
|
||||
// Activity Orientation changed or resized.
|
||||
let native_window = unsafe { android_glue::native_window() };
|
||||
if native_window.is_null() {
|
||||
None
|
||||
} else {
|
||||
let scale_factor = MonitorHandle.scale_factor();
|
||||
let physical_size = MonitorHandle.size();
|
||||
let size = LogicalSize::from_physical(physical_size, scale_factor);
|
||||
Some(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId),
|
||||
event: WindowEvent::Resized(size),
|
||||
})
|
||||
}
|
||||
}
|
||||
android_glue::Event::WindowRedrawNeeded => {
|
||||
// The activity needs to be redrawn.
|
||||
Some(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId),
|
||||
event: WindowEvent::Redraw,
|
||||
})
|
||||
}
|
||||
android_glue::Event::Wake => Some(Event::Awakened),
|
||||
_ => None,
|
||||
};
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
let mut v = VecDeque::with_capacity(1);
|
||||
v.push_back(self.primary_monitor());
|
||||
v
|
||||
}
|
||||
|
||||
if let Some(event) = e {
|
||||
callback(event);
|
||||
}
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy {
|
||||
queue: self.user_queue.clone(),
|
||||
looper: ForeignLooper::for_thread().expect("called from event loop thread"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>) {
|
||||
*self.suspend_callback.borrow_mut() = cb;
|
||||
}
|
||||
|
||||
pub fn run_forever<F>(&mut self, mut callback: F)
|
||||
where
|
||||
F: FnMut(::Event) -> ::ControlFlow,
|
||||
{
|
||||
// Yeah that's a very bad implementation.
|
||||
loop {
|
||||
let mut control_flow = ::ControlFlow::Continue;
|
||||
self.poll_events(|e| {
|
||||
if let ::ControlFlow::Break = callback(e) {
|
||||
control_flow = ::ControlFlow::Break;
|
||||
}
|
||||
});
|
||||
if let ::ControlFlow::Break = control_flow {
|
||||
break;
|
||||
}
|
||||
::std::thread::sleep(::std::time::Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy {
|
||||
EventLoopProxy
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
pub fn wakeup(&self) -> Result<(), ::EventLoopClosed<()>> {
|
||||
android_glue::wake_event_loop();
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
queue: Arc<Mutex<VecDeque<T>>>,
|
||||
looper: ForeignLooper,
|
||||
}
|
||||
|
||||
impl<T> EventLoopProxy<T> {
|
||||
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
|
||||
self.queue.lock().unwrap().push_back(event);
|
||||
self.looper.wake();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
impl<T> Clone for EventLoopProxy<T> {
|
||||
fn clone(&self) -> Self {
|
||||
EventLoopProxy {
|
||||
queue: self.queue.clone(),
|
||||
looper: self.looper.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopWindowTarget<T: 'static> {
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct WindowId;
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
pub fn dummy() -> Self {
|
||||
WindowId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
pub fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
native_window: *const c_void,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorHandle;
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MonitorHandle {
|
||||
name: Option<String>,
|
||||
dimensions: PhysicalSize<u32>,
|
||||
position: PhysicalPosition<i32>,
|
||||
scale_factor: f64,
|
||||
}
|
||||
|
||||
let monitor_id_proxy = MonitorHandle {
|
||||
name: self.name(),
|
||||
dimensions: self.size(),
|
||||
position: self.outer_position(),
|
||||
scale_factor: self.scale_factor(),
|
||||
};
|
||||
|
||||
monitor_id_proxy.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<String> {
|
||||
Some("Primary".to_string())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
unsafe {
|
||||
let window = android_glue::native_window();
|
||||
(
|
||||
ffi::ANativeWindow_getWidth(window) as f64,
|
||||
ffi::ANativeWindow_getHeight(window) as f64,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_position(&self) -> PhysicalPosition<i32> {
|
||||
// Android assumes single screen
|
||||
(0, 0).into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes;
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PlatformSpecificHeadlessBuilderAttributes;
|
||||
|
||||
pub struct Window;
|
||||
|
||||
impl Window {
|
||||
pub fn new(
|
||||
_: &EventLoop,
|
||||
win_attribs: WindowAttributes,
|
||||
pub fn new<T: 'static>(
|
||||
_el: &EventLoopWindowTarget<T>,
|
||||
_window_attrs: window::WindowAttributes,
|
||||
_: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Window, CreationError> {
|
||||
let native_window = unsafe { android_glue::native_window() };
|
||||
if native_window.is_null() {
|
||||
return Err(OsError(format!("Android's native window is null")));
|
||||
}
|
||||
|
||||
android_glue::set_multitouch(true);
|
||||
|
||||
Ok(Window {
|
||||
native_window: native_window as *const _,
|
||||
})
|
||||
) -> Result<Self, error::OsError> {
|
||||
// FIXME this ignores requested window attributes
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn native_window(&self) -> *const c_void {
|
||||
self.native_window
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, _: &str) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_position(&self) -> Option<LogicalPosition<f64>> {
|
||||
// N/A
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_position(&self) -> Option<LogicalPosition<f64>> {
|
||||
// N/A
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_outer_position(&self, _position: LogicalPosition<f64>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize<f64>>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize<f64>>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, _resizable: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_size(&self) -> Option<LogicalSize<f64>> {
|
||||
if self.native_window.is_null() {
|
||||
None
|
||||
} else {
|
||||
let scale_factor = self.scale_factor();
|
||||
let physical_size = self.current_monitor().size();
|
||||
Some(LogicalSize::from_physical(physical_size, scale_factor))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_size(&self) -> Option<LogicalSize<f64>> {
|
||||
self.inner_size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, _size: LogicalSize<f64>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
self.current_monitor().scale_factor()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, _: CursorIcon) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hide_cursor(&self, _hide: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(
|
||||
&self,
|
||||
_position: LogicalPosition<f64>,
|
||||
) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_minimized(&self, _minimized: bool) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, _maximized: bool) {
|
||||
// N/A
|
||||
// Android has single screen maximized apps so nothing to do
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
||||
// N/A
|
||||
// Android has single screen maximized apps so nothing to do
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorHandle>) {
|
||||
// N/A
|
||||
// Android has single screen maximized apps so nothing to do
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, _decorations: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_position(&self, _spot: LogicalPosition<f64>) {
|
||||
// N/A
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_monitor(&self) -> RootMonitorHandle {
|
||||
RootMonitorHandle {
|
||||
inner: MonitorHandle,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
let mut rb = VecDeque::with_capacity(1);
|
||||
rb.push_back(MonitorHandle);
|
||||
rb
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let handle = AndroidHandle {
|
||||
a_native_window: self.native_window,
|
||||
..WindowsHandle::empty()
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
let mut v = VecDeque::with_capacity(1);
|
||||
v.push_back(MonitorHandle);
|
||||
v
|
||||
}
|
||||
|
||||
pub fn current_monitor(&self) -> monitor::MonitorHandle {
|
||||
monitor::MonitorHandle {
|
||||
inner: MonitorHandle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
MonitorHandle.scale_factor()
|
||||
}
|
||||
|
||||
pub fn request_redraw(&self) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
|
||||
Err(error::NotSupportedError::new())
|
||||
}
|
||||
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
|
||||
Err(error::NotSupportedError::new())
|
||||
}
|
||||
|
||||
pub fn set_outer_position(&self, _position: Position) {
|
||||
// no effect
|
||||
}
|
||||
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
self.outer_size()
|
||||
}
|
||||
|
||||
pub fn set_inner_size(&self, _size: Size) {
|
||||
panic!("Cannot set window size on Android");
|
||||
}
|
||||
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
MonitorHandle.size()
|
||||
}
|
||||
|
||||
pub fn set_min_inner_size(&self, _: Option<Size>) {}
|
||||
|
||||
pub fn set_max_inner_size(&self, _: Option<Size>) {}
|
||||
|
||||
pub fn set_title(&self, _title: &str) {}
|
||||
|
||||
pub fn set_visible(&self, _visibility: bool) {}
|
||||
|
||||
pub fn set_resizable(&self, _resizeable: bool) {}
|
||||
|
||||
pub fn set_minimized(&self, _minimized: bool) {}
|
||||
|
||||
pub fn set_maximized(&self, _maximized: bool) {}
|
||||
|
||||
pub fn set_fullscreen(&self, _monitor: Option<window::Fullscreen>) {
|
||||
panic!("Cannot set fullscreen on Android");
|
||||
}
|
||||
|
||||
pub fn fullscreen(&self) -> Option<window::Fullscreen> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_decorations(&self, _decorations: bool) {}
|
||||
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {}
|
||||
|
||||
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
|
||||
|
||||
pub fn set_ime_position(&self, _position: Position) {}
|
||||
|
||||
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
|
||||
|
||||
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn set_cursor_grab(&self, _: bool) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn set_cursor_visible(&self, _: bool) {}
|
||||
|
||||
pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
|
||||
let a_native_window = if let Some(native_window) = ndk_glue::native_window().as_ref() {
|
||||
unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ }
|
||||
} else {
|
||||
panic!("native window null");
|
||||
};
|
||||
RawWindowHandle::Android(handle)
|
||||
let mut handle = raw_window_handle::android::AndroidHandle::empty();
|
||||
handle.a_native_window = a_native_window;
|
||||
raw_window_handle::RawWindowHandle::Android(handle)
|
||||
}
|
||||
|
||||
pub fn config(&self) -> Configuration {
|
||||
CONFIG.read().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn content_rect(&self) -> Rect {
|
||||
ndk_glue::content_rect()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Window {}
|
||||
unsafe impl Sync for Window {}
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct OsError;
|
||||
|
||||
// Constant device ID, to be removed when this backend is updated to report real device IDs.
|
||||
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
impl Display for OsError {
|
||||
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(fmt, "Android OS Error")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct MonitorHandle;
|
||||
|
||||
impl MonitorHandle {
|
||||
pub fn name(&self) -> Option<String> {
|
||||
Some("Android Device".to_owned())
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
if let Some(native_window) = ndk_glue::native_window().as_ref() {
|
||||
let width = native_window.width() as _;
|
||||
let height = native_window.height() as _;
|
||||
PhysicalSize::new(width, height)
|
||||
} else {
|
||||
PhysicalSize::new(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
(0, 0).into()
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
let config = CONFIG.read().unwrap();
|
||||
config
|
||||
.density()
|
||||
.map(|dpi| dpi as f64 / 160.0)
|
||||
.unwrap_or(1.0)
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = monitor::VideoMode> {
|
||||
let size = self.size().into();
|
||||
let mut v = Vec::new();
|
||||
// FIXME this is not the real refresh rate
|
||||
// (it is guarunteed to support 32 bit color though)
|
||||
v.push(monitor::VideoMode {
|
||||
video_mode: VideoMode {
|
||||
size,
|
||||
bit_depth: 32,
|
||||
refresh_rate: 60,
|
||||
monitor: self.clone(),
|
||||
},
|
||||
});
|
||||
v.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct VideoMode {
|
||||
size: (u32, u32),
|
||||
bit_depth: u16,
|
||||
refresh_rate: u16,
|
||||
monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size.into()
|
||||
}
|
||||
|
||||
pub fn bit_depth(&self) -> u16 {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
self.refresh_rate
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> monitor::MonitorHandle {
|
||||
monitor::MonitorHandle {
|
||||
inner: self.monitor.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use objc::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::PhysicalPosition,
|
||||
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
|
||||
platform::ios::MonitorHandleExtIOS,
|
||||
platform_impl::platform::{
|
||||
@@ -209,7 +210,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
if touch == nil {
|
||||
break;
|
||||
}
|
||||
let location: CGPoint = msg_send![touch, locationInView: nil];
|
||||
let logical_location: CGPoint = msg_send![touch, locationInView: nil];
|
||||
let touch_type: UITouchType = msg_send![touch, type];
|
||||
let force = if os_supports_force {
|
||||
let trait_collection: id = msg_send![object, traitCollection];
|
||||
@@ -248,12 +249,19 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
_ => panic!("unexpected touch phase: {:?}", phase as i32),
|
||||
};
|
||||
|
||||
let physical_location = {
|
||||
let scale_factor: CGFloat = msg_send![object, contentScaleFactor];
|
||||
PhysicalPosition::from_logical::<(f64, f64), f64>(
|
||||
(logical_location.x as _, logical_location.y as _),
|
||||
scale_factor,
|
||||
)
|
||||
};
|
||||
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Touch(Touch {
|
||||
device_id: RootDeviceId(DeviceId { uiscreen }),
|
||||
id: touch_id,
|
||||
location: (location.x as f64, location.y as f64).into(),
|
||||
location: physical_location,
|
||||
force,
|
||||
phase,
|
||||
}),
|
||||
|
||||
@@ -63,7 +63,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
|
||||
lazy_static! {
|
||||
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> =
|
||||
{ Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) };
|
||||
Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new));
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -714,8 +714,43 @@ impl<T> EventLoop<T> {
|
||||
window_target.store.lock().unwrap().for_each(|window| {
|
||||
let window_id =
|
||||
crate::window::WindowId(crate::platform_impl::WindowId::Wayland(window.wid));
|
||||
if let Some(frame) = window.frame {
|
||||
if let Some((w, h)) = window.newsize {
|
||||
|
||||
// Update window logical .size field (for callbacks using .inner_size)
|
||||
let (old_logical_size, mut logical_size) = {
|
||||
let mut window_size = window.size.lock().unwrap();
|
||||
let old_logical_size = *window_size;
|
||||
*window_size = window.new_size.unwrap_or(old_logical_size);
|
||||
(old_logical_size, *window_size)
|
||||
};
|
||||
|
||||
if let Some(scale_factor) = window.new_scale_factor {
|
||||
// Update cursor scale factor
|
||||
self.cursor_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.update_scale_factor(scale_factor as u32);
|
||||
let new_logical_size = {
|
||||
let scale_factor = scale_factor as f64;
|
||||
let mut physical_size =
|
||||
LogicalSize::<f64>::from(logical_size).to_physical(scale_factor);
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size: &mut physical_size,
|
||||
},
|
||||
});
|
||||
physical_size.to_logical::<u32>(scale_factor).into()
|
||||
};
|
||||
// Update size if changed by callback
|
||||
if new_logical_size != logical_size {
|
||||
logical_size = new_logical_size;
|
||||
*window.size.lock().unwrap() = logical_size.into();
|
||||
}
|
||||
}
|
||||
|
||||
if window.new_size.is_some() || window.new_scale_factor.is_some() {
|
||||
if let Some(frame) = window.frame {
|
||||
// Update decorations state
|
||||
match window.decorations_action {
|
||||
Some(DecorationsAction::Hide) => frame.set_decorate(false),
|
||||
@@ -726,51 +761,23 @@ impl<T> EventLoop<T> {
|
||||
// mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case
|
||||
// it overlaps mutter's `bounding box`, so we can't avoid this resize call,
|
||||
// which calls `set_geometry` under the hood, for now.
|
||||
let (w, h) = logical_size;
|
||||
frame.resize(w, h);
|
||||
frame.refresh();
|
||||
|
||||
// Don't send resize event downstream if the new size is identical to the
|
||||
// current one.
|
||||
if (w, h) != *window.size {
|
||||
let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64);
|
||||
let physical_size = logical_size
|
||||
.to_physical(window.new_dpi.unwrap_or(window.prev_dpi) as f64);
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
});
|
||||
*window.size = (w, h);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(dpi) = window.new_dpi {
|
||||
// Update cursor scale factor
|
||||
{
|
||||
self.cursor_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.update_scale_factor(dpi as u32);
|
||||
};
|
||||
let dpi = dpi as f64;
|
||||
let logical_size = LogicalSize::<f64>::from(*window.size);
|
||||
let mut new_inner_size = logical_size.to_physical(dpi);
|
||||
|
||||
// Don't send resize event downstream if the new logical size and scale is identical to the
|
||||
// current one
|
||||
if logical_size != old_logical_size || window.new_scale_factor.is_some() {
|
||||
let physical_size = LogicalSize::<f64>::from(logical_size).to_physical(
|
||||
window.new_scale_factor.unwrap_or(window.prev_scale_factor) as f64,
|
||||
);
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: dpi,
|
||||
new_inner_size: &mut new_inner_size,
|
||||
},
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
});
|
||||
|
||||
let (w, h) = new_inner_size.to_logical::<u32>(dpi).into();
|
||||
frame.resize(w, h);
|
||||
// Refresh frame to rescale decorations
|
||||
frame.refresh();
|
||||
*window.size = (w, h);
|
||||
}
|
||||
}
|
||||
|
||||
if window.closed {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
|
||||
@@ -59,15 +59,20 @@ impl Window {
|
||||
// Create the surface first to get initial DPI
|
||||
let window_store = evlp.store.clone();
|
||||
let cursor_manager = evlp.cursor_manager.clone();
|
||||
let surface = evlp.env.create_surface(move |dpi, surface| {
|
||||
window_store.lock().unwrap().dpi_change(&surface, dpi);
|
||||
surface.set_buffer_scale(dpi);
|
||||
let surface = evlp.env.create_surface(move |scale_factor, surface| {
|
||||
window_store
|
||||
.lock()
|
||||
.unwrap()
|
||||
.scale_factor_change(&surface, scale_factor);
|
||||
surface.set_buffer_scale(scale_factor);
|
||||
});
|
||||
|
||||
let dpi = get_dpi_factor(&surface) as f64;
|
||||
// Always 1.
|
||||
let scale_factor = get_dpi_factor(&surface);
|
||||
|
||||
let (width, height) = attributes
|
||||
.inner_size
|
||||
.map(|size| size.to_logical::<f64>(dpi).into())
|
||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into())
|
||||
.unwrap_or((800, 600));
|
||||
|
||||
// Create the window
|
||||
@@ -91,7 +96,7 @@ impl Window {
|
||||
|
||||
for window in &mut store.windows {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
window.newsize = new_size;
|
||||
window.new_size = new_size;
|
||||
*(window.need_refresh.lock().unwrap()) = true;
|
||||
{
|
||||
// Get whether we're in fullscreen
|
||||
@@ -173,12 +178,12 @@ impl Window {
|
||||
frame.set_min_size(
|
||||
attributes
|
||||
.min_inner_size
|
||||
.map(|size| size.to_logical::<f64>(dpi).into()),
|
||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into()),
|
||||
);
|
||||
frame.set_max_size(
|
||||
attributes
|
||||
.max_inner_size
|
||||
.map(|size| size.to_logical::<f64>(dpi).into()),
|
||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into()),
|
||||
);
|
||||
|
||||
let kill_switch = Arc::new(Mutex::new(false));
|
||||
@@ -189,7 +194,7 @@ impl Window {
|
||||
|
||||
evlp.store.lock().unwrap().windows.push(InternalWindow {
|
||||
closed: false,
|
||||
newsize: None,
|
||||
new_size: None,
|
||||
size: size.clone(),
|
||||
need_refresh: need_refresh.clone(),
|
||||
fullscreen: fullscreen.clone(),
|
||||
@@ -198,8 +203,8 @@ impl Window {
|
||||
surface: surface.clone(),
|
||||
kill_switch: kill_switch.clone(),
|
||||
frame: Arc::downgrade(&frame),
|
||||
current_dpi: 1,
|
||||
new_dpi: None,
|
||||
current_scale_factor: scale_factor,
|
||||
new_scale_factor: None,
|
||||
decorated: decorated.clone(),
|
||||
pending_decorations_action: pending_decorations_action.clone(),
|
||||
});
|
||||
@@ -250,9 +255,9 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
let dpi = self.scale_factor() as f64;
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let size = LogicalSize::<f64>::from(*self.size.lock().unwrap());
|
||||
size.to_physical(dpi)
|
||||
size.to_physical(scale_factor)
|
||||
}
|
||||
|
||||
pub fn request_redraw(&self) {
|
||||
@@ -261,38 +266,38 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
let dpi = self.scale_factor() as f64;
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let (w, h) = self.size.lock().unwrap().clone();
|
||||
// let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
|
||||
let size = LogicalSize::<f64>::from((w, h));
|
||||
size.to_physical(dpi)
|
||||
size.to_physical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
// NOTE: This will only resize the borders, the contents must be updated by the user
|
||||
pub fn set_inner_size(&self, size: Size) {
|
||||
let dpi = self.scale_factor() as f64;
|
||||
let (w, h) = size.to_logical::<u32>(dpi).into();
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let (w, h) = size.to_logical::<u32>(scale_factor).into();
|
||||
self.frame.lock().unwrap().resize(w, h);
|
||||
*(self.size.lock().unwrap()) = (w, h);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
|
||||
let dpi = self.scale_factor() as f64;
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
self.frame
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_min_size(dimensions.map(|dim| dim.to_logical::<f64>(dpi).into()));
|
||||
.set_min_size(dimensions.map(|dim| dim.to_logical::<f64>(scale_factor).into()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
|
||||
let dpi = self.scale_factor() as f64;
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
self.frame
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_max_size(dimensions.map(|dim| dim.to_logical::<f64>(dpi).into()));
|
||||
.set_max_size(dimensions.map(|dim| dim.to_logical::<f64>(scale_factor).into()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -428,7 +433,7 @@ impl Drop for Window {
|
||||
struct InternalWindow {
|
||||
surface: wl_surface::WlSurface,
|
||||
// TODO: CONVERT TO LogicalSize<u32>s
|
||||
newsize: Option<(u32, u32)>,
|
||||
new_size: Option<(u32, u32)>,
|
||||
size: Arc<Mutex<(u32, u32)>>,
|
||||
need_refresh: Arc<Mutex<bool>>,
|
||||
fullscreen: Arc<Mutex<bool>>,
|
||||
@@ -437,8 +442,8 @@ struct InternalWindow {
|
||||
closed: bool,
|
||||
kill_switch: Arc<Mutex<bool>>,
|
||||
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
|
||||
current_dpi: i32,
|
||||
new_dpi: Option<i32>,
|
||||
current_scale_factor: i32,
|
||||
new_scale_factor: Option<i32>,
|
||||
decorated: Arc<Mutex<bool>>,
|
||||
pending_decorations_action: Arc<Mutex<Option<DecorationsAction>>>,
|
||||
}
|
||||
@@ -448,10 +453,10 @@ pub struct WindowStore {
|
||||
}
|
||||
|
||||
pub struct WindowStoreForEach<'a> {
|
||||
pub newsize: Option<(u32, u32)>,
|
||||
pub size: &'a mut (u32, u32),
|
||||
pub prev_dpi: i32,
|
||||
pub new_dpi: Option<i32>,
|
||||
pub new_size: Option<(u32, u32)>,
|
||||
pub size: &'a Mutex<(u32, u32)>,
|
||||
pub prev_scale_factor: i32,
|
||||
pub new_scale_factor: Option<i32>,
|
||||
pub closed: bool,
|
||||
pub grab_cursor: Option<bool>,
|
||||
pub surface: &'a wl_surface::WlSurface,
|
||||
@@ -499,10 +504,11 @@ impl WindowStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn dpi_change(&mut self, surface: &wl_surface::WlSurface, new: i32) {
|
||||
fn scale_factor_change(&mut self, surface: &wl_surface::WlSurface, new: i32) {
|
||||
for window in &mut self.windows {
|
||||
if surface.as_ref().equals(&window.surface.as_ref()) {
|
||||
window.new_dpi = Some(new);
|
||||
window.new_scale_factor = Some(new);
|
||||
*(window.need_refresh.lock().unwrap()) = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -512,15 +518,18 @@ impl WindowStore {
|
||||
F: FnMut(WindowStoreForEach<'_>),
|
||||
{
|
||||
for window in &mut self.windows {
|
||||
let prev_scale_factor = window.current_scale_factor;
|
||||
if let Some(scale_factor) = window.new_scale_factor {
|
||||
window.current_scale_factor = scale_factor;
|
||||
}
|
||||
let opt_arc = window.frame.upgrade();
|
||||
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
|
||||
let mut size = { *window.size.lock().unwrap() };
|
||||
let decorations_action = { window.pending_decorations_action.lock().unwrap().take() };
|
||||
f(WindowStoreForEach {
|
||||
newsize: window.newsize.take(),
|
||||
size: &mut size,
|
||||
prev_dpi: window.current_dpi,
|
||||
new_dpi: window.new_dpi,
|
||||
new_size: window.new_size.take(),
|
||||
size: &window.size,
|
||||
prev_scale_factor,
|
||||
new_scale_factor: window.new_scale_factor.take(),
|
||||
closed: window.closed,
|
||||
grab_cursor: window.cursor_grab_changed.lock().unwrap().take(),
|
||||
surface: &window.surface,
|
||||
@@ -528,10 +537,6 @@ impl WindowStore {
|
||||
frame: opt_mutex_lock.as_mut().map(|m| &mut **m),
|
||||
decorations_action,
|
||||
});
|
||||
*window.size.lock().unwrap() = size;
|
||||
if let Some(dpi) = window.new_dpi.take() {
|
||||
window.current_dpi = dpi;
|
||||
}
|
||||
// avoid re-spamming the event
|
||||
window.closed = false;
|
||||
}
|
||||
|
||||
@@ -268,14 +268,16 @@ impl<T: 'static> EventLoop<T> {
|
||||
{
|
||||
let mut control_flow = ControlFlow::default();
|
||||
let mut events = Events::with_capacity(8);
|
||||
|
||||
callback(
|
||||
crate::event::Event::NewEvents(crate::event::StartCause::Init),
|
||||
&self.target,
|
||||
&mut control_flow,
|
||||
);
|
||||
let mut cause = StartCause::Init;
|
||||
|
||||
loop {
|
||||
sticky_exit_callback(
|
||||
crate::event::Event::NewEvents(cause),
|
||||
&self.target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
// Process all pending events
|
||||
self.drain_events(&mut callback, &mut control_flow);
|
||||
|
||||
@@ -326,7 +328,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
let (mut cause, deadline, timeout);
|
||||
let (deadline, timeout);
|
||||
|
||||
match control_flow {
|
||||
ControlFlow::Exit => break,
|
||||
@@ -357,38 +359,20 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.event_processor.poll() {
|
||||
// If the XConnection already contains buffered events, we don't
|
||||
// need to wait for data on the socket.
|
||||
// However, we still need to check for user events.
|
||||
self.poll
|
||||
.poll(&mut events, Some(Duration::from_millis(0)))
|
||||
.unwrap();
|
||||
events.clear();
|
||||
|
||||
callback(
|
||||
crate::event::Event::NewEvents(cause),
|
||||
&self.target,
|
||||
&mut control_flow,
|
||||
);
|
||||
} else {
|
||||
// If the XConnection already contains buffered events, we don't
|
||||
// need to wait for data on the socket.
|
||||
if !self.event_processor.poll() {
|
||||
self.poll.poll(&mut events, timeout).unwrap();
|
||||
events.clear();
|
||||
}
|
||||
|
||||
let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline);
|
||||
let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline);
|
||||
|
||||
if wait_cancelled {
|
||||
cause = StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: deadline,
|
||||
};
|
||||
}
|
||||
|
||||
callback(
|
||||
crate::event::Event::NewEvents(cause),
|
||||
&self.target,
|
||||
&mut control_flow,
|
||||
);
|
||||
if wait_cancelled {
|
||||
cause = StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: deadline,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,14 @@ pub struct Proxy<T> {
|
||||
|
||||
unsafe impl<T: Send> Send for Proxy<T> {}
|
||||
|
||||
impl<T> Drop for Proxy<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRelease(self.source as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Proxy<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Proxy::new(self.sender.clone())
|
||||
|
||||
@@ -869,26 +869,32 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem
|
||||
}
|
||||
|
||||
extern "C" fn mouse_down(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
mouse_click(this, event, MouseButton::Left, ElementState::Pressed);
|
||||
}
|
||||
|
||||
extern "C" fn mouse_up(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
mouse_click(this, event, MouseButton::Left, ElementState::Released);
|
||||
}
|
||||
|
||||
extern "C" fn right_mouse_down(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
mouse_click(this, event, MouseButton::Right, ElementState::Pressed);
|
||||
}
|
||||
|
||||
extern "C" fn right_mouse_up(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
mouse_click(this, event, MouseButton::Right, ElementState::Released);
|
||||
}
|
||||
|
||||
extern "C" fn other_mouse_down(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
mouse_click(this, event, MouseButton::Middle, ElementState::Pressed);
|
||||
}
|
||||
|
||||
extern "C" fn other_mouse_up(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
mouse_click(this, event, MouseButton::Middle, ElementState::Released);
|
||||
}
|
||||
|
||||
@@ -986,6 +992,9 @@ extern "C" fn mouse_exited(this: &Object, _sel: Sel, _event: id) {
|
||||
|
||||
extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) {
|
||||
trace!("Triggered `scrollWheel`");
|
||||
|
||||
mouse_motion(this, event);
|
||||
|
||||
unsafe {
|
||||
let delta = {
|
||||
let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY());
|
||||
@@ -1031,6 +1040,9 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) {
|
||||
|
||||
extern "C" fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) {
|
||||
trace!("Triggered `pressureChangeWithEvent`");
|
||||
|
||||
mouse_motion(this, event);
|
||||
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
@@ -202,7 +202,6 @@ impl<T: 'static> Shared<T> {
|
||||
// It should only ever be called from send_event
|
||||
fn handle_event(&self, event: Event<'static, T>, control: &mut root::ControlFlow) {
|
||||
let is_closed = self.is_closed();
|
||||
|
||||
match *self.0.runner.borrow_mut() {
|
||||
Some(ref mut runner) => {
|
||||
// An event is being processed, so the runner should be marked busy
|
||||
@@ -227,7 +226,9 @@ impl<T: 'static> Shared<T> {
|
||||
// If the runner doesn't exist and this method recurses, it will recurse infinitely
|
||||
if !is_closed && self.0.runner.borrow().is_some() {
|
||||
// Take an event out of the queue and handle it
|
||||
if let Some(event) = self.0.events.borrow_mut().pop_front() {
|
||||
// Make sure not to let the borrow_mut live during the next handle_event
|
||||
let event = { self.0.events.borrow_mut().pop_front() };
|
||||
if let Some(event) = event {
|
||||
self.handle_event(event, control);
|
||||
}
|
||||
}
|
||||
@@ -240,7 +241,7 @@ impl<T: 'static> Shared<T> {
|
||||
root::ControlFlow::Poll => {
|
||||
let cloned = self.clone();
|
||||
State::Poll {
|
||||
timeout: backend::Timeout::new(move || cloned.poll(), Duration::from_millis(0)),
|
||||
request: backend::AnimationFrameRequest::new(move || cloned.poll()),
|
||||
}
|
||||
}
|
||||
root::ControlFlow::Wait => State::Wait {
|
||||
|
||||
@@ -15,7 +15,7 @@ pub enum State {
|
||||
start: Instant,
|
||||
},
|
||||
Poll {
|
||||
timeout: backend::Timeout,
|
||||
request: backend::AnimationFrameRequest,
|
||||
},
|
||||
Exit,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ mod event;
|
||||
mod timeout;
|
||||
|
||||
pub use self::canvas::Canvas;
|
||||
pub use self::timeout::Timeout;
|
||||
pub use self::timeout::{AnimationFrameRequest, Timeout};
|
||||
|
||||
use crate::dpi::{LogicalSize, Size};
|
||||
use crate::platform::web::WindowExtStdweb;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
use stdweb::web::{window, IWindowOrWorker, TimeoutHandle};
|
||||
use stdweb::web::{window, IWindowOrWorker, RequestAnimationFrameHandle, TimeoutHandle};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Timeout {
|
||||
@@ -23,3 +25,39 @@ impl Drop for Timeout {
|
||||
handle.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AnimationFrameRequest {
|
||||
handle: Option<RequestAnimationFrameHandle>,
|
||||
// track callback state, because `cancelAnimationFrame` is slow
|
||||
fired: Rc<Cell<bool>>,
|
||||
}
|
||||
|
||||
impl AnimationFrameRequest {
|
||||
pub fn new<F>(mut f: F) -> AnimationFrameRequest
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
let fired = Rc::new(Cell::new(false));
|
||||
let c_fired = fired.clone();
|
||||
let handle = window().request_animation_frame(move |_| {
|
||||
(*c_fired).set(true);
|
||||
f();
|
||||
});
|
||||
|
||||
AnimationFrameRequest {
|
||||
handle: Some(handle),
|
||||
fired,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AnimationFrameRequest {
|
||||
fn drop(&mut self) {
|
||||
if !(*self.fired).get() {
|
||||
if let Some(handle) = self.handle.take() {
|
||||
handle.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ mod event;
|
||||
mod timeout;
|
||||
|
||||
pub use self::canvas::Canvas;
|
||||
pub use self::timeout::Timeout;
|
||||
pub use self::timeout::{AnimationFrameRequest, Timeout};
|
||||
|
||||
use crate::dpi::{LogicalSize, Size};
|
||||
use crate::platform::web::WindowExtWebSys;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::JsCast;
|
||||
@@ -38,3 +40,48 @@ impl Drop for Timeout {
|
||||
window.clear_timeout_with_handle(self.handle);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AnimationFrameRequest {
|
||||
handle: i32,
|
||||
// track callback state, because `cancelAnimationFrame` is slow
|
||||
fired: Rc<Cell<bool>>,
|
||||
_closure: Closure<dyn FnMut()>,
|
||||
}
|
||||
|
||||
impl AnimationFrameRequest {
|
||||
pub fn new<F>(mut f: F) -> AnimationFrameRequest
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
let window = web_sys::window().expect("Failed to obtain window");
|
||||
|
||||
let fired = Rc::new(Cell::new(false));
|
||||
let c_fired = fired.clone();
|
||||
let closure = Closure::wrap(Box::new(move || {
|
||||
(*c_fired).set(true);
|
||||
f();
|
||||
}) as Box<dyn FnMut()>);
|
||||
|
||||
let handle = window
|
||||
.request_animation_frame(&closure.as_ref().unchecked_ref())
|
||||
.expect("Failed to request animation frame");
|
||||
|
||||
AnimationFrameRequest {
|
||||
handle,
|
||||
fired,
|
||||
_closure: closure,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AnimationFrameRequest {
|
||||
fn drop(&mut self) {
|
||||
if !(*self.fired).get() {
|
||||
let window = web_sys::window().expect("Failed to obtain window");
|
||||
window
|
||||
.cancel_animation_frame(self.handle)
|
||||
.expect("Failed to cancel animation frame");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,4 @@
|
||||
#![allow(non_snake_case)]
|
||||
//! An events loop on Win32 is a background thread.
|
||||
//!
|
||||
//! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop.
|
||||
//! Destroying the events loop stops the thread.
|
||||
//!
|
||||
//! You can use the `execute_in_thread` method to execute some code in the background thread.
|
||||
//! Since Win32 requires you to create a window in the right thread, you must use this method
|
||||
//! to create a window.
|
||||
//!
|
||||
//! If you create a window whose class is set to `callback`, the window's events will be
|
||||
//! propagated with `run_forever` and `poll_events`.
|
||||
//! The closure passed to the `execute_in_thread` method takes an `Inserter` that you can use to
|
||||
//! add a `WindowState` entry to a list of window to be used by the callback.
|
||||
|
||||
mod runner;
|
||||
|
||||
@@ -24,6 +11,7 @@ use std::{
|
||||
mpsc::{self, Receiver, Sender},
|
||||
Arc,
|
||||
},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use winapi::shared::basetsd::{DWORD_PTR, UINT_PTR};
|
||||
@@ -41,7 +29,6 @@ use winapi::{
|
||||
},
|
||||
};
|
||||
|
||||
use self::runner::{ELRShared, EventLoopRunnerShared};
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
event::{DeviceEvent, Event, Force, KeyboardInput, Touch, TouchPhase, WindowEvent},
|
||||
@@ -57,6 +44,7 @@ use crate::{
|
||||
},
|
||||
window::{Fullscreen, WindowId as RootWindowId},
|
||||
};
|
||||
use runner::{EventLoopRunner, EventLoopRunnerShared};
|
||||
|
||||
type GetPointerFrameInfoHistory = unsafe extern "system" fn(
|
||||
pointerId: UINT,
|
||||
@@ -160,9 +148,17 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
pub fn new_dpi_unaware_any_thread() -> EventLoop<T> {
|
||||
let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() };
|
||||
let runner_shared = Rc::new(ELRShared::new());
|
||||
let (thread_msg_target, thread_msg_sender) =
|
||||
thread_event_target_window(runner_shared.clone());
|
||||
|
||||
let thread_msg_target = create_event_target_window();
|
||||
|
||||
let send_thread_msg_target = thread_msg_target as usize;
|
||||
thread::spawn(move || wait_thread(thread_id, send_thread_msg_target as HWND));
|
||||
let wait_thread_id = get_wait_thread_id();
|
||||
|
||||
let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id));
|
||||
|
||||
let thread_msg_sender =
|
||||
subclass_event_target_window(thread_msg_target, runner_shared.clone());
|
||||
raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target);
|
||||
|
||||
EventLoop {
|
||||
@@ -200,87 +196,39 @@ impl<T: 'static> EventLoop<T> {
|
||||
self.window_target
|
||||
.p
|
||||
.runner_shared
|
||||
.set_runner(self, move |event, control_flow| {
|
||||
.set_event_handler(move |event, control_flow| {
|
||||
event_handler(event, event_loop_windows_ref, control_flow)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
let runner = &self.window_target.p.runner_shared;
|
||||
|
||||
unsafe {
|
||||
let mut msg = mem::zeroed();
|
||||
let mut unread_message_exists = false;
|
||||
|
||||
runner.poll();
|
||||
'main: loop {
|
||||
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
|
||||
break 'main;
|
||||
}
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
|
||||
if let Err(payload) = runner.take_panic_error() {
|
||||
runner.destroy_runner();
|
||||
runner.reset_runner();
|
||||
panic::resume_unwind(payload);
|
||||
}
|
||||
|
||||
runner.new_events();
|
||||
loop {
|
||||
if !unread_message_exists {
|
||||
if 0 == winuser::PeekMessageW(
|
||||
&mut msg,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
winuser::PM_REMOVE,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
|
||||
unread_message_exists = false;
|
||||
|
||||
if msg.message == winuser::WM_PAINT {
|
||||
// An "external" redraw was requested.
|
||||
// Note that the WM_PAINT has been dispatched and
|
||||
// has caused the event loop to emit the MainEventsCleared event.
|
||||
// See EventLoopRunner::process_event().
|
||||
// The call to main_events_cleared() below will do nothing.
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Make sure we emit the MainEventsCleared event if no WM_PAINT message was received.
|
||||
runner.main_events_cleared();
|
||||
// Drain eventual WM_PAINT messages sent if user called request_redraw()
|
||||
// during handling of MainEventsCleared.
|
||||
loop {
|
||||
if 0 == winuser::PeekMessageW(
|
||||
&mut msg,
|
||||
ptr::null_mut(),
|
||||
winuser::WM_PAINT,
|
||||
winuser::WM_PAINT,
|
||||
winuser::PM_QS_PAINT | winuser::PM_REMOVE,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
}
|
||||
runner.redraw_events_cleared();
|
||||
match runner.control_flow() {
|
||||
ControlFlow::Exit => break 'main,
|
||||
ControlFlow::Wait => {
|
||||
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
|
||||
break 'main;
|
||||
}
|
||||
unread_message_exists = true;
|
||||
}
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
wait_until_time_or_msg(resume_time);
|
||||
}
|
||||
ControlFlow::Poll => (),
|
||||
if runner.control_flow() == ControlFlow::Exit && !runner.handling_events() {
|
||||
break 'main;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runner.destroy_loop();
|
||||
runner.destroy_runner();
|
||||
unsafe {
|
||||
runner.call_event_handler(Event::LoopDestroyed);
|
||||
}
|
||||
runner.reset_runner();
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
@@ -316,24 +264,83 @@ fn main_thread_id() -> DWORD {
|
||||
unsafe { MAIN_THREAD_ID }
|
||||
}
|
||||
|
||||
unsafe fn wait_until_time_or_msg(wait_until: Instant) {
|
||||
let now = Instant::now();
|
||||
if now < wait_until {
|
||||
// MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract 1 millisecond
|
||||
// from the requested time and spinlock for the remainder to compensate for that.
|
||||
let resume_reason = winuser::MsgWaitForMultipleObjectsEx(
|
||||
fn get_wait_thread_id() -> DWORD {
|
||||
unsafe {
|
||||
let mut msg = mem::zeroed();
|
||||
let result = winuser::GetMessageW(
|
||||
&mut msg,
|
||||
-1 as _,
|
||||
*SEND_WAIT_THREAD_ID_MSG_ID,
|
||||
*SEND_WAIT_THREAD_ID_MSG_ID,
|
||||
);
|
||||
assert_eq!(
|
||||
msg.message, *SEND_WAIT_THREAD_ID_MSG_ID,
|
||||
"this shouldn't be possible. please open an issue with Winit. error code: {}",
|
||||
result
|
||||
);
|
||||
msg.lParam as DWORD
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_thread(parent_thread_id: DWORD, msg_window_id: HWND) {
|
||||
unsafe {
|
||||
let mut msg: winuser::MSG;
|
||||
|
||||
let cur_thread_id = processthreadsapi::GetCurrentThreadId();
|
||||
winuser::PostThreadMessageW(
|
||||
parent_thread_id,
|
||||
*SEND_WAIT_THREAD_ID_MSG_ID,
|
||||
0,
|
||||
ptr::null(),
|
||||
dur2timeout(wait_until - now).saturating_sub(1),
|
||||
winuser::QS_ALLEVENTS,
|
||||
winuser::MWMO_INPUTAVAILABLE,
|
||||
cur_thread_id as LPARAM,
|
||||
);
|
||||
|
||||
if resume_reason == winerror::WAIT_TIMEOUT {
|
||||
let mut msg = mem::zeroed();
|
||||
while Instant::now() < wait_until {
|
||||
if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) {
|
||||
break;
|
||||
let mut wait_until_opt = None;
|
||||
'main: loop {
|
||||
// Zeroing out the message ensures that the `WaitUntilInstantBox` doesn't get
|
||||
// double-freed if `MsgWaitForMultipleObjectsEx` returns early and there aren't
|
||||
// additional messages to process.
|
||||
msg = mem::zeroed();
|
||||
|
||||
if wait_until_opt.is_some() {
|
||||
if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, winuser::PM_REMOVE) {
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
}
|
||||
} else {
|
||||
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
|
||||
break 'main;
|
||||
} else {
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
}
|
||||
}
|
||||
|
||||
if msg.message == *WAIT_UNTIL_MSG_ID {
|
||||
wait_until_opt = Some(*WaitUntilInstantBox::from_raw(msg.lParam as *mut _));
|
||||
} else if msg.message == *CANCEL_WAIT_UNTIL_MSG_ID {
|
||||
wait_until_opt = None;
|
||||
}
|
||||
|
||||
if let Some(wait_until) = wait_until_opt {
|
||||
let now = Instant::now();
|
||||
if now < wait_until {
|
||||
// MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract
|
||||
// 1 millisecond from the requested time and spinlock for the remainder to
|
||||
// compensate for that.
|
||||
let resume_reason = winuser::MsgWaitForMultipleObjectsEx(
|
||||
0,
|
||||
ptr::null(),
|
||||
dur2timeout(wait_until - now).saturating_sub(1),
|
||||
winuser::QS_ALLEVENTS,
|
||||
winuser::MWMO_INPUTAVAILABLE,
|
||||
);
|
||||
if resume_reason == winerror::WAIT_TIMEOUT {
|
||||
winuser::PostMessageW(msg_window_id, *PROCESS_NEW_EVENTS_MSG_ID, 0, 0);
|
||||
wait_until_opt = None;
|
||||
}
|
||||
} else {
|
||||
winuser::PostMessageW(msg_window_id, *PROCESS_NEW_EVENTS_MSG_ID, 0, 0);
|
||||
wait_until_opt = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -461,6 +468,8 @@ impl<T: 'static> EventLoopProxy<T> {
|
||||
}
|
||||
}
|
||||
|
||||
type WaitUntilInstantBox = Box<Instant>;
|
||||
|
||||
lazy_static! {
|
||||
// Message sent by the `EventLoopProxy` when we want to wake up the thread.
|
||||
// WPARAM and LPARAM are unused.
|
||||
@@ -477,6 +486,29 @@ lazy_static! {
|
||||
winuser::RegisterWindowMessageA("Winit::ExecMsg\0".as_ptr() as *const i8)
|
||||
}
|
||||
};
|
||||
static ref PROCESS_NEW_EVENTS_MSG_ID: u32 = {
|
||||
unsafe {
|
||||
winuser::RegisterWindowMessageA("Winit::ProcessNewEvents\0".as_ptr() as *const i8)
|
||||
}
|
||||
};
|
||||
/// lparam is the wait thread's message id.
|
||||
static ref SEND_WAIT_THREAD_ID_MSG_ID: u32 = {
|
||||
unsafe {
|
||||
winuser::RegisterWindowMessageA("Winit::SendWaitThreadId\0".as_ptr() as *const i8)
|
||||
}
|
||||
};
|
||||
/// lparam points to a `Box<Instant>` signifying the time `PROCESS_NEW_EVENTS_MSG_ID` should
|
||||
/// be sent.
|
||||
static ref WAIT_UNTIL_MSG_ID: u32 = {
|
||||
unsafe {
|
||||
winuser::RegisterWindowMessageA("Winit::WaitUntil\0".as_ptr() as *const i8)
|
||||
}
|
||||
};
|
||||
static ref CANCEL_WAIT_UNTIL_MSG_ID: u32 = {
|
||||
unsafe {
|
||||
winuser::RegisterWindowMessageA("Winit::CancelWaitUntil\0".as_ptr() as *const i8)
|
||||
}
|
||||
};
|
||||
// Message sent by a `Window` when it wants to be destroyed by the main thread.
|
||||
// WPARAM and LPARAM are unused.
|
||||
pub static ref DESTROY_MSG_ID: u32 = {
|
||||
@@ -519,7 +551,7 @@ lazy_static! {
|
||||
};
|
||||
}
|
||||
|
||||
fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) -> (HWND, Sender<T>) {
|
||||
fn create_event_target_window() -> HWND {
|
||||
unsafe {
|
||||
let window = winuser::CreateWindowExW(
|
||||
winuser::WS_EX_NOACTIVATE | winuser::WS_EX_TRANSPARENT | winuser::WS_EX_LAYERED,
|
||||
@@ -543,7 +575,15 @@ fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) ->
|
||||
// the LAYERED style.
|
||||
(winuser::WS_VISIBLE | winuser::WS_POPUP) as _,
|
||||
);
|
||||
window
|
||||
}
|
||||
}
|
||||
|
||||
fn subclass_event_target_window<T>(
|
||||
window: HWND,
|
||||
event_loop_runner: EventLoopRunnerShared<T>,
|
||||
) -> Sender<T> {
|
||||
unsafe {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let subclass_input = ThreadMsgTargetSubclassInput {
|
||||
@@ -559,7 +599,7 @@ fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) ->
|
||||
);
|
||||
assert_eq!(subclass_result, 1);
|
||||
|
||||
(window, tx)
|
||||
tx
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,6 +622,7 @@ unsafe fn release_mouse(window_state: &mut WindowState) {
|
||||
const WINDOW_SUBCLASS_ID: UINT_PTR = 0;
|
||||
const THREAD_EVENT_TARGET_SUBCLASS_ID: UINT_PTR = 1;
|
||||
pub(crate) fn subclass_window<T>(window: HWND, subclass_input: SubclassInput<T>) {
|
||||
subclass_input.event_loop_runner.register_window(window);
|
||||
let input_ptr = Box::into_raw(Box::new(subclass_input));
|
||||
let subclass_result = unsafe {
|
||||
commctrl::SetWindowSubclass(
|
||||
@@ -601,6 +642,68 @@ fn normalize_pointer_pressure(pressure: u32) -> Option<Force> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Flush redraw events for Winit's windows.
|
||||
///
|
||||
/// Winit's API guarantees that all redraw events will be clustered together and dispatched all at
|
||||
/// once, but the standard Windows message loop doesn't always exhibit that behavior. If multiple
|
||||
/// windows have had redraws scheduled, but an input event is pushed to the message queue between
|
||||
/// the `WM_PAINT` call for the first window and the `WM_PAINT` call for the second window, Windows
|
||||
/// will dispatch the input event immediately instead of flushing all the redraw events. This
|
||||
/// function explicitly pulls all of Winit's redraw events out of the event queue so that they
|
||||
/// always all get processed in one fell swoop.
|
||||
///
|
||||
/// Returns `true` if this invocation flushed all the redraw events. If this function is re-entrant,
|
||||
/// it won't flush the redraw events and will return `false`.
|
||||
#[must_use]
|
||||
unsafe fn flush_paint_messages<T: 'static>(
|
||||
except: Option<HWND>,
|
||||
runner: &EventLoopRunner<T>,
|
||||
) -> bool {
|
||||
if !runner.redrawing() {
|
||||
runner.main_events_cleared();
|
||||
let mut msg = mem::zeroed();
|
||||
runner.owned_windows(|redraw_window| {
|
||||
if Some(redraw_window) == except {
|
||||
return;
|
||||
}
|
||||
|
||||
if 0 == winuser::PeekMessageW(
|
||||
&mut msg,
|
||||
redraw_window,
|
||||
winuser::WM_PAINT,
|
||||
winuser::WM_PAINT,
|
||||
winuser::PM_REMOVE | winuser::PM_QS_PAINT,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
});
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn process_control_flow<T: 'static>(runner: &EventLoopRunner<T>) {
|
||||
match runner.control_flow() {
|
||||
ControlFlow::Poll => {
|
||||
winuser::PostMessageW(runner.thread_msg_target(), *PROCESS_NEW_EVENTS_MSG_ID, 0, 0);
|
||||
}
|
||||
ControlFlow::Wait => (),
|
||||
ControlFlow::WaitUntil(until) => {
|
||||
winuser::PostThreadMessageW(
|
||||
runner.wait_thread_id(),
|
||||
*WAIT_UNTIL_MSG_ID,
|
||||
0,
|
||||
Box::into_raw(WaitUntilInstantBox::new(until)) as LPARAM,
|
||||
);
|
||||
}
|
||||
ControlFlow::Exit => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a `ModifiersChanged` event whenever modifiers have changed.
|
||||
fn update_modifiers<T>(window: HWND, subclass_input: &SubclassInput<T>) {
|
||||
use crate::event::WindowEvent::ModifiersChanged;
|
||||
@@ -639,20 +742,37 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
) -> LRESULT {
|
||||
let subclass_input = &*(subclass_input_ptr as *const SubclassInput<T>);
|
||||
|
||||
match msg {
|
||||
winuser::RedrawWindow(
|
||||
subclass_input.event_loop_runner.thread_msg_target(),
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
winuser::RDW_INTERNALPAINT,
|
||||
);
|
||||
|
||||
// I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing
|
||||
// the closure to catch_unwind directly so that the match body indendation wouldn't change and
|
||||
// the git blame and history would be preserved.
|
||||
let callback = || match msg {
|
||||
winuser::WM_ENTERSIZEMOVE => {
|
||||
subclass_input.event_loop_runner.set_modal_loop(true);
|
||||
subclass_input
|
||||
.window_state
|
||||
.lock()
|
||||
.set_window_flags_in_place(|f| f.insert(WindowFlags::MARKER_IN_SIZE_MOVE));
|
||||
0
|
||||
}
|
||||
|
||||
winuser::WM_EXITSIZEMOVE => {
|
||||
subclass_input.event_loop_runner.set_modal_loop(false);
|
||||
subclass_input
|
||||
.window_state
|
||||
.lock()
|
||||
.set_window_flags_in_place(|f| f.remove(WindowFlags::MARKER_IN_SIZE_MOVE));
|
||||
0
|
||||
}
|
||||
|
||||
winuser::WM_NCCREATE => {
|
||||
enable_non_client_dpi_scaling(window);
|
||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||
}
|
||||
|
||||
winuser::WM_NCLBUTTONDOWN => {
|
||||
if wparam == winuser::HTCAPTION as _ {
|
||||
winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, 0);
|
||||
@@ -676,6 +796,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: Destroyed,
|
||||
});
|
||||
subclass_input.event_loop_runner.remove_window(window);
|
||||
|
||||
drop(subclass_input);
|
||||
Box::from_raw(subclass_input_ptr as *mut SubclassInput<T>);
|
||||
@@ -683,7 +804,25 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
}
|
||||
|
||||
winuser::WM_PAINT => {
|
||||
subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId(window))));
|
||||
if subclass_input.event_loop_runner.should_buffer() {
|
||||
// this branch can happen in response to `UpdateWindow`, if win32 decides to
|
||||
// redraw the window outside the normal flow of the event loop.
|
||||
winuser::RedrawWindow(
|
||||
window,
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
winuser::RDW_INTERNALPAINT,
|
||||
);
|
||||
} else {
|
||||
let managing_redraw =
|
||||
flush_paint_messages(Some(window), &subclass_input.event_loop_runner);
|
||||
subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId(window))));
|
||||
if managing_redraw {
|
||||
subclass_input.event_loop_runner.redraw_events_cleared();
|
||||
process_control_flow(&subclass_input.event_loop_runner);
|
||||
}
|
||||
}
|
||||
|
||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||
}
|
||||
|
||||
@@ -1583,11 +1722,19 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
},
|
||||
});
|
||||
|
||||
// Unset maximized if we're changing the window's size.
|
||||
if new_physical_inner_size != old_physical_inner_size {
|
||||
WindowState::set_window_flags(subclass_input.window_state.lock(), window, |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, false)
|
||||
});
|
||||
let dragging_window: bool;
|
||||
|
||||
{
|
||||
let window_state = subclass_input.window_state.lock();
|
||||
dragging_window = window_state
|
||||
.window_flags()
|
||||
.contains(WindowFlags::MARKER_IN_SIZE_MOVE);
|
||||
// Unset maximized if we're changing the window's size.
|
||||
if new_physical_inner_size != old_physical_inner_size {
|
||||
WindowState::set_window_flags(window_state, window, |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, false)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let new_outer_rect: RECT;
|
||||
@@ -1612,9 +1759,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
)
|
||||
.unwrap_or(conservative_rect);
|
||||
|
||||
// If we're not dragging the window, offset the window so that the cursor's
|
||||
// If we're dragging the window, offset the window so that the cursor's
|
||||
// relative horizontal position in the title bar is preserved.
|
||||
let dragging_window = subclass_input.event_loop_runner.in_modal_loop();
|
||||
if dragging_window {
|
||||
let bias = {
|
||||
let cursor_pos = {
|
||||
@@ -1742,7 +1888,12 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
subclass_input
|
||||
.event_loop_runner
|
||||
.catch_unwind(callback)
|
||||
.unwrap_or(-1)
|
||||
}
|
||||
|
||||
unsafe extern "system" fn thread_event_target_callback<T: 'static>(
|
||||
@@ -1754,7 +1905,21 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
|
||||
subclass_input_ptr: DWORD_PTR,
|
||||
) -> LRESULT {
|
||||
let subclass_input = &mut *(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput<T>);
|
||||
match msg {
|
||||
let runner = subclass_input.event_loop_runner.clone();
|
||||
|
||||
if msg != winuser::WM_PAINT {
|
||||
winuser::RedrawWindow(
|
||||
window,
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
winuser::RDW_INTERNALPAINT,
|
||||
);
|
||||
}
|
||||
|
||||
// I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing
|
||||
// the closure to catch_unwind directly so that the match body indendation wouldn't change and
|
||||
// the git blame and history would be preserved.
|
||||
let callback = || match msg {
|
||||
winuser::WM_DESTROY => {
|
||||
Box::from_raw(subclass_input);
|
||||
drop(subclass_input);
|
||||
@@ -1764,52 +1929,20 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
|
||||
// when the event queue has been emptied. See `process_event` for more details.
|
||||
winuser::WM_PAINT => {
|
||||
winuser::ValidateRect(window, ptr::null());
|
||||
let queue_call_again = || {
|
||||
winuser::RedrawWindow(
|
||||
window,
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
winuser::RDW_INTERNALPAINT,
|
||||
);
|
||||
};
|
||||
let in_modal_loop = subclass_input.event_loop_runner.in_modal_loop();
|
||||
if in_modal_loop {
|
||||
let runner = &subclass_input.event_loop_runner;
|
||||
runner.main_events_cleared();
|
||||
// Drain eventual WM_PAINT messages sent if user called request_redraw()
|
||||
// during handling of MainEventsCleared.
|
||||
let mut msg = mem::zeroed();
|
||||
loop {
|
||||
if 0 == winuser::PeekMessageW(
|
||||
&mut msg,
|
||||
ptr::null_mut(),
|
||||
winuser::WM_PAINT,
|
||||
winuser::WM_PAINT,
|
||||
winuser::PM_QS_PAINT | winuser::PM_REMOVE,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
if msg.hwnd != window {
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
}
|
||||
}
|
||||
runner.redraw_events_cleared();
|
||||
match runner.control_flow() {
|
||||
// Waiting is handled by the modal loop.
|
||||
ControlFlow::Exit | ControlFlow::Wait => runner.new_events(),
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
wait_until_time_or_msg(resume_time);
|
||||
runner.new_events();
|
||||
queue_call_again();
|
||||
}
|
||||
ControlFlow::Poll => {
|
||||
runner.new_events();
|
||||
queue_call_again();
|
||||
}
|
||||
}
|
||||
// If the WM_PAINT handler in `public_window_callback` has already flushed the redraw
|
||||
// events, `handling_events` will return false and we won't emit a second
|
||||
// `RedrawEventsCleared` event.
|
||||
if subclass_input.event_loop_runner.handling_events() {
|
||||
// This WM_PAINT handler will never be re-entrant because `flush_paint_messages`
|
||||
// doesn't call WM_PAINT for the thread event target (i.e. this window).
|
||||
assert!(flush_paint_messages(
|
||||
None,
|
||||
&subclass_input.event_loop_runner
|
||||
));
|
||||
subclass_input.event_loop_runner.redraw_events_cleared();
|
||||
process_control_flow(&subclass_input.event_loop_runner);
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
@@ -1940,6 +2073,49 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
|
||||
function();
|
||||
0
|
||||
}
|
||||
_ if msg == *PROCESS_NEW_EVENTS_MSG_ID => {
|
||||
winuser::PostThreadMessageW(
|
||||
subclass_input.event_loop_runner.wait_thread_id(),
|
||||
*CANCEL_WAIT_UNTIL_MSG_ID,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
// if the control_flow is WaitUntil, make sure the given moment has actually passed
|
||||
// before emitting NewEvents
|
||||
if let ControlFlow::WaitUntil(wait_until) =
|
||||
subclass_input.event_loop_runner.control_flow()
|
||||
{
|
||||
let mut msg = mem::zeroed();
|
||||
while Instant::now() < wait_until {
|
||||
if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) {
|
||||
// This works around a "feature" in PeekMessageW. If the message PeekMessageW
|
||||
// gets is a WM_PAINT message that had RDW_INTERNALPAINT set (i.e. doesn't
|
||||
// have an update region), PeekMessageW will remove that window from the
|
||||
// redraw queue even though we told it not to remove messages from the
|
||||
// queue. We fix it by re-dispatching an internal paint message to that
|
||||
// window.
|
||||
if msg.message == winuser::WM_PAINT {
|
||||
let mut rect = mem::zeroed();
|
||||
if 0 == winuser::GetUpdateRect(msg.hwnd, &mut rect, 0) {
|
||||
winuser::RedrawWindow(
|
||||
msg.hwnd,
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
winuser::RDW_INTERNALPAINT,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
subclass_input.event_loop_runner.poll();
|
||||
0
|
||||
}
|
||||
_ => commctrl::DefSubclassProc(window, msg, wparam, lparam),
|
||||
}
|
||||
};
|
||||
|
||||
runner.catch_unwind(callback).unwrap_or(-1)
|
||||
}
|
||||
|
||||
@@ -1,37 +1,378 @@
|
||||
use std::{any::Any, cell::RefCell, collections::VecDeque, mem, panic, ptr, rc::Rc, time::Instant};
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::{Cell, RefCell},
|
||||
collections::{HashSet, VecDeque},
|
||||
mem, panic, ptr,
|
||||
rc::Rc,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use winapi::{shared::windef::HWND, um::winuser};
|
||||
use winapi::{
|
||||
shared::{minwindef::DWORD, windef::HWND},
|
||||
um::winuser,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::PhysicalSize,
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::ControlFlow,
|
||||
platform_impl::platform::event_loop::{util, EventLoop},
|
||||
platform_impl::platform::util,
|
||||
window::WindowId,
|
||||
};
|
||||
|
||||
pub(crate) type EventLoopRunnerShared<T> = Rc<ELRShared<T>>;
|
||||
pub(crate) struct ELRShared<T: 'static> {
|
||||
runner: RefCell<Option<EventLoopRunner<T>>>,
|
||||
buffer: RefCell<VecDeque<BufferedEvent<T>>>,
|
||||
}
|
||||
pub(crate) type EventLoopRunnerShared<T> = Rc<EventLoopRunner<T>>;
|
||||
pub(crate) struct EventLoopRunner<T: 'static> {
|
||||
// The event loop's win32 handles
|
||||
thread_msg_target: HWND,
|
||||
wait_thread_id: DWORD,
|
||||
|
||||
struct EventLoopRunner<T: 'static> {
|
||||
control_flow: ControlFlow,
|
||||
runner_state: RunnerState,
|
||||
modal_redraw_window: HWND,
|
||||
in_modal_loop: bool,
|
||||
event_handler: Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>,
|
||||
panic_error: Option<PanicError>,
|
||||
control_flow: Cell<ControlFlow>,
|
||||
runner_state: Cell<RunnerState>,
|
||||
last_events_cleared: Cell<Instant>,
|
||||
|
||||
event_handler: Cell<Option<Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>>>,
|
||||
event_buffer: RefCell<VecDeque<BufferedEvent<T>>>,
|
||||
|
||||
owned_windows: Cell<HashSet<HWND>>,
|
||||
|
||||
panic_error: Cell<Option<PanicError>>,
|
||||
}
|
||||
|
||||
pub type PanicError = Box<dyn Any + Send + 'static>;
|
||||
|
||||
pub enum BufferedEvent<T: 'static> {
|
||||
/// See `move_state_to` function for details on how the state loop works.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
enum RunnerState {
|
||||
/// The event loop has just been created, and an `Init` event must be sent.
|
||||
Uninitialized,
|
||||
/// The event loop is idling.
|
||||
Idle,
|
||||
/// The event loop is handling the OS's events and sending them to the user's callback.
|
||||
/// `NewEvents` has been sent, and `MainEventsCleared` hasn't.
|
||||
HandlingMainEvents,
|
||||
/// The event loop is handling the redraw events and sending them to the user's callback.
|
||||
/// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't.
|
||||
HandlingRedrawEvents,
|
||||
}
|
||||
|
||||
enum BufferedEvent<T: 'static> {
|
||||
Event(Event<'static, T>),
|
||||
ScaleFactorChanged(WindowId, f64, PhysicalSize<u32>),
|
||||
}
|
||||
|
||||
impl<T> EventLoopRunner<T> {
|
||||
pub(crate) fn new(thread_msg_target: HWND, wait_thread_id: DWORD) -> EventLoopRunner<T> {
|
||||
EventLoopRunner {
|
||||
thread_msg_target,
|
||||
wait_thread_id,
|
||||
runner_state: Cell::new(RunnerState::Uninitialized),
|
||||
control_flow: Cell::new(ControlFlow::Poll),
|
||||
panic_error: Cell::new(None),
|
||||
last_events_cleared: Cell::new(Instant::now()),
|
||||
event_handler: Cell::new(None),
|
||||
event_buffer: RefCell::new(VecDeque::new()),
|
||||
owned_windows: Cell::new(HashSet::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn set_event_handler<F>(&self, f: F)
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &mut ControlFlow),
|
||||
{
|
||||
let old_event_handler = self.event_handler.replace(mem::transmute::<
|
||||
Option<Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>>,
|
||||
Option<Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>>,
|
||||
>(Some(Box::new(f))));
|
||||
assert!(old_event_handler.is_none());
|
||||
}
|
||||
|
||||
pub(crate) fn reset_runner(&self) {
|
||||
let EventLoopRunner {
|
||||
thread_msg_target: _,
|
||||
wait_thread_id: _,
|
||||
runner_state,
|
||||
panic_error,
|
||||
control_flow,
|
||||
last_events_cleared: _,
|
||||
event_handler,
|
||||
event_buffer: _,
|
||||
owned_windows: _,
|
||||
} = self;
|
||||
runner_state.set(RunnerState::Uninitialized);
|
||||
panic_error.set(None);
|
||||
control_flow.set(ControlFlow::Poll);
|
||||
event_handler.set(None);
|
||||
}
|
||||
}
|
||||
|
||||
/// State retrieval functions.
|
||||
impl<T> EventLoopRunner<T> {
|
||||
pub fn thread_msg_target(&self) -> HWND {
|
||||
self.thread_msg_target
|
||||
}
|
||||
|
||||
pub fn wait_thread_id(&self) -> DWORD {
|
||||
self.wait_thread_id
|
||||
}
|
||||
|
||||
pub fn redrawing(&self) -> bool {
|
||||
self.runner_state.get() == RunnerState::HandlingRedrawEvents
|
||||
}
|
||||
|
||||
pub fn take_panic_error(&self) -> Result<(), PanicError> {
|
||||
match self.panic_error.take() {
|
||||
Some(err) => Err(err),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn control_flow(&self) -> ControlFlow {
|
||||
self.control_flow.get()
|
||||
}
|
||||
|
||||
pub fn handling_events(&self) -> bool {
|
||||
self.runner_state.get() != RunnerState::Idle
|
||||
}
|
||||
|
||||
pub fn should_buffer(&self) -> bool {
|
||||
let handler = self.event_handler.take();
|
||||
let should_buffer = handler.is_none();
|
||||
self.event_handler.set(handler);
|
||||
should_buffer
|
||||
}
|
||||
}
|
||||
|
||||
/// Misc. functions
|
||||
impl<T> EventLoopRunner<T> {
|
||||
pub fn catch_unwind<R>(&self, f: impl FnOnce() -> R) -> Option<R> {
|
||||
let panic_error = self.panic_error.take();
|
||||
if panic_error.is_none() {
|
||||
let result = panic::catch_unwind(panic::AssertUnwindSafe(f));
|
||||
|
||||
// Check to see if the panic error was set in a re-entrant call to catch_unwind inside
|
||||
// of `f`. If it was, that error takes priority. If it wasn't, check if our call to
|
||||
// catch_unwind caught any panics and set panic_error appropriately.
|
||||
match self.panic_error.take() {
|
||||
None => match result {
|
||||
Ok(r) => Some(r),
|
||||
Err(e) => {
|
||||
self.panic_error.set(Some(e));
|
||||
None
|
||||
}
|
||||
},
|
||||
Some(e) => {
|
||||
self.panic_error.set(Some(e));
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.panic_error.set(panic_error);
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn register_window(&self, window: HWND) {
|
||||
let mut owned_windows = self.owned_windows.take();
|
||||
owned_windows.insert(window);
|
||||
self.owned_windows.set(owned_windows);
|
||||
}
|
||||
|
||||
pub fn remove_window(&self, window: HWND) {
|
||||
let mut owned_windows = self.owned_windows.take();
|
||||
owned_windows.remove(&window);
|
||||
self.owned_windows.set(owned_windows);
|
||||
}
|
||||
|
||||
pub fn owned_windows(&self, mut f: impl FnMut(HWND)) {
|
||||
let mut owned_windows = self.owned_windows.take();
|
||||
for hwnd in &owned_windows {
|
||||
f(*hwnd);
|
||||
}
|
||||
let new_owned_windows = self.owned_windows.take();
|
||||
owned_windows.extend(&new_owned_windows);
|
||||
self.owned_windows.set(owned_windows);
|
||||
}
|
||||
}
|
||||
|
||||
/// Event dispatch functions.
|
||||
impl<T> EventLoopRunner<T> {
|
||||
pub(crate) unsafe fn poll(&self) {
|
||||
self.move_state_to(RunnerState::HandlingMainEvents);
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn send_event(&self, event: Event<'_, T>) {
|
||||
if let Event::RedrawRequested(_) = event {
|
||||
if self.runner_state.get() != RunnerState::HandlingRedrawEvents {
|
||||
warn!("RedrawRequested dispatched without explicit MainEventsCleared");
|
||||
self.move_state_to(RunnerState::HandlingRedrawEvents);
|
||||
}
|
||||
self.call_event_handler(event);
|
||||
} else {
|
||||
if self.should_buffer() {
|
||||
// If the runner is already borrowed, we're in the middle of an event loop invocation. Add
|
||||
// the event to a buffer to be processed later.
|
||||
self.event_buffer
|
||||
.borrow_mut()
|
||||
.push_back(BufferedEvent::from_event(event))
|
||||
} else {
|
||||
self.move_state_to(RunnerState::HandlingMainEvents);
|
||||
self.call_event_handler(event);
|
||||
self.dispatch_buffered_events();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn main_events_cleared(&self) {
|
||||
self.move_state_to(RunnerState::HandlingRedrawEvents);
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn redraw_events_cleared(&self) {
|
||||
self.move_state_to(RunnerState::Idle);
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn call_event_handler(&self, event: Event<'_, T>) {
|
||||
self.catch_unwind(|| {
|
||||
let mut control_flow = self.control_flow.take();
|
||||
let mut event_handler = self.event_handler.take()
|
||||
.expect("either event handler is re-entrant (likely), or no event handler is registered (very unlikely)");
|
||||
|
||||
if control_flow != ControlFlow::Exit {
|
||||
event_handler(event, &mut control_flow);
|
||||
} else {
|
||||
event_handler(event, &mut ControlFlow::Exit);
|
||||
}
|
||||
|
||||
assert!(self.event_handler.replace(Some(event_handler)).is_none());
|
||||
self.control_flow.set(control_flow);
|
||||
});
|
||||
}
|
||||
|
||||
unsafe fn dispatch_buffered_events(&self) {
|
||||
loop {
|
||||
// We do this instead of using a `while let` loop because if we use a `while let`
|
||||
// loop the reference returned `borrow_mut()` doesn't get dropped until the end
|
||||
// of the loop's body and attempts to add events to the event buffer while in
|
||||
// `process_event` will fail.
|
||||
let buffered_event_opt = self.event_buffer.borrow_mut().pop_front();
|
||||
match buffered_event_opt {
|
||||
Some(e) => e.dispatch_event(|e| self.call_event_handler(e)),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatch control flow events (`NewEvents`, `MainEventsCleared`, and `RedrawEventsCleared`) as
|
||||
/// necessary to bring the internal `RunnerState` to the new runner state.
|
||||
///
|
||||
/// The state transitions are defined as follows:
|
||||
///
|
||||
/// ```text
|
||||
/// Uninitialized
|
||||
/// |
|
||||
/// V
|
||||
/// HandlingMainEvents
|
||||
/// ^ |
|
||||
/// | V
|
||||
/// Idle <--- HandlingRedrawEvents
|
||||
/// ```
|
||||
///
|
||||
/// Attempting to transition back to `Uninitialized` will result in a panic. Transitioning to
|
||||
/// the current state is a no-op. Even if the `new_runner_state` isn't the immediate next state
|
||||
/// in the runner state machine (e.g. `self.runner_state == HandlingMainEvents` and
|
||||
/// `new_runner_state == Idle`), the intermediate state transitions will still be executed.
|
||||
unsafe fn move_state_to(&self, new_runner_state: RunnerState) {
|
||||
use RunnerState::{HandlingMainEvents, HandlingRedrawEvents, Idle, Uninitialized};
|
||||
|
||||
match (
|
||||
self.runner_state.replace(new_runner_state),
|
||||
new_runner_state,
|
||||
) {
|
||||
(Uninitialized, Uninitialized)
|
||||
| (Idle, Idle)
|
||||
| (HandlingMainEvents, HandlingMainEvents)
|
||||
| (HandlingRedrawEvents, HandlingRedrawEvents) => (),
|
||||
|
||||
// State transitions that initialize the event loop.
|
||||
(Uninitialized, HandlingMainEvents) => {
|
||||
self.call_new_events(true);
|
||||
}
|
||||
(Uninitialized, HandlingRedrawEvents) => {
|
||||
self.call_new_events(true);
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
}
|
||||
(Uninitialized, Idle) => {
|
||||
self.call_new_events(true);
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.call_redraw_events_cleared();
|
||||
}
|
||||
(_, Uninitialized) => panic!("cannot move state to Uninitialized"),
|
||||
|
||||
// State transitions that start the event handling process.
|
||||
(Idle, HandlingMainEvents) => {
|
||||
self.call_new_events(false);
|
||||
}
|
||||
(Idle, HandlingRedrawEvents) => {
|
||||
self.call_new_events(false);
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
}
|
||||
|
||||
(HandlingMainEvents, HandlingRedrawEvents) => {
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
}
|
||||
(HandlingMainEvents, Idle) => {
|
||||
warn!("RedrawEventsCleared emitted without explicit MainEventsCleared");
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.call_redraw_events_cleared();
|
||||
}
|
||||
|
||||
(HandlingRedrawEvents, Idle) => {
|
||||
self.call_redraw_events_cleared();
|
||||
}
|
||||
(HandlingRedrawEvents, HandlingMainEvents) => {
|
||||
warn!("NewEvents emitted without explicit RedrawEventsCleared");
|
||||
self.call_redraw_events_cleared();
|
||||
self.call_new_events(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn call_new_events(&self, init: bool) {
|
||||
let start_cause = match (init, self.control_flow()) {
|
||||
(true, _) => StartCause::Init,
|
||||
(false, ControlFlow::Poll) => StartCause::Poll,
|
||||
(false, ControlFlow::Exit) | (false, ControlFlow::Wait) => StartCause::WaitCancelled {
|
||||
requested_resume: None,
|
||||
start: self.last_events_cleared.get(),
|
||||
},
|
||||
(false, ControlFlow::WaitUntil(requested_resume)) => {
|
||||
if Instant::now() < requested_resume {
|
||||
StartCause::WaitCancelled {
|
||||
requested_resume: Some(requested_resume),
|
||||
start: self.last_events_cleared.get(),
|
||||
}
|
||||
} else {
|
||||
StartCause::ResumeTimeReached {
|
||||
requested_resume,
|
||||
start: self.last_events_cleared.get(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
self.call_event_handler(Event::NewEvents(start_cause));
|
||||
self.dispatch_buffered_events();
|
||||
winuser::RedrawWindow(
|
||||
self.thread_msg_target,
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
winuser::RDW_INTERNALPAINT,
|
||||
);
|
||||
}
|
||||
|
||||
unsafe fn call_redraw_events_cleared(&self) {
|
||||
self.call_event_handler(Event::RedrawEventsCleared);
|
||||
self.last_events_cleared.set(Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BufferedEvent<T> {
|
||||
pub fn from_event(event: Event<'_, T>) -> BufferedEvent<T> {
|
||||
match event {
|
||||
@@ -67,409 +408,3 @@ impl<T> BufferedEvent<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ELRShared<T> {
|
||||
pub(crate) fn new() -> ELRShared<T> {
|
||||
ELRShared {
|
||||
runner: RefCell::new(None),
|
||||
buffer: RefCell::new(VecDeque::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn set_runner<F>(&self, event_loop: &EventLoop<T>, f: F)
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &mut ControlFlow),
|
||||
{
|
||||
let mut runner = EventLoopRunner::new(event_loop, f);
|
||||
{
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
// Dispatch any events that were buffered during the creation of the window
|
||||
self.dispatch_buffered_events(&mut runner);
|
||||
*runner_ref = Some(runner);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn destroy_runner(&self) {
|
||||
*self.runner.borrow_mut() = None;
|
||||
}
|
||||
|
||||
pub(crate) fn new_events(&self) {
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.new_events();
|
||||
// Dispatch any events that were buffered during the call `new_events`
|
||||
self.dispatch_buffered_events(runner);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn send_event(&self, event: Event<'_, T>) {
|
||||
if let Err(event) = self.send_event_unbuffered(event) {
|
||||
// If the runner is already borrowed, we're in the middle of an event loop invocation.
|
||||
// Add the event to a buffer to be processed later.
|
||||
if let Event::RedrawRequested(_) = event {
|
||||
panic!("buffering RedrawRequested event");
|
||||
}
|
||||
self.buffer
|
||||
.borrow_mut()
|
||||
.push_back(BufferedEvent::from_event(event));
|
||||
}
|
||||
}
|
||||
|
||||
fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> {
|
||||
if let Ok(mut runner_ref) = self.runner.try_borrow_mut() {
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.process_event(event);
|
||||
// Dispatch any events that were buffered during the call to `process_event`.
|
||||
self.dispatch_buffered_events(runner);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(event)
|
||||
}
|
||||
|
||||
fn dispatch_buffered_events(&self, runner: &mut EventLoopRunner<T>) {
|
||||
// We do this instead of using a `while let` loop because if we use a `while let`
|
||||
// loop the reference returned `borrow_mut()` doesn't get dropped until the end
|
||||
// of the loop's body and attempts to add events to the event buffer while in
|
||||
// `process_event` will fail.
|
||||
loop {
|
||||
let buffered_event_opt = self.buffer.borrow_mut().pop_front();
|
||||
match buffered_event_opt {
|
||||
Some(e) => e.dispatch_event(|e| runner.process_event(e)),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn main_events_cleared(&self) {
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.main_events_cleared();
|
||||
if !self.buffer.borrow().is_empty() {
|
||||
warn!("Buffered events while dispatching MainEventsCleared");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn redraw_events_cleared(&self) {
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.redraw_events_cleared();
|
||||
if !self.buffer.borrow().is_empty() {
|
||||
warn!("Buffered events while dispatching RedrawEventsCleared");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn destroy_loop(&self) {
|
||||
if let Ok(mut runner_ref) = self.runner.try_borrow_mut() {
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.call_event_handler(Event::LoopDestroyed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn take_panic_error(&self) -> Result<(), PanicError> {
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.take_panic_error()
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_modal_loop(&self, in_modal_loop: bool) {
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.in_modal_loop = in_modal_loop;
|
||||
if in_modal_loop {
|
||||
// jumpstart the modal loop
|
||||
unsafe {
|
||||
winuser::RedrawWindow(
|
||||
runner.modal_redraw_window,
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
winuser::RDW_INTERNALPAINT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn in_modal_loop(&self) -> bool {
|
||||
let runner = self.runner.borrow();
|
||||
if let Some(ref runner) = *runner {
|
||||
runner.in_modal_loop
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn control_flow(&self) -> ControlFlow {
|
||||
let runner_ref = self.runner.borrow();
|
||||
if let Some(ref runner) = *runner_ref {
|
||||
runner.control_flow
|
||||
} else {
|
||||
ControlFlow::Exit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum RunnerState {
|
||||
/// The event loop has just been created, and an `Init` event must be sent.
|
||||
New,
|
||||
/// The event loop is idling, and began idling at the given instant.
|
||||
Idle(Instant),
|
||||
/// The event loop has received a signal from the OS that the loop may resume, but no winit
|
||||
/// events have been generated yet. We're waiting for an event to be processed or the events
|
||||
/// to be marked as cleared to send `NewEvents`, depending on the current `ControlFlow`.
|
||||
DeferredNewEvents(Instant),
|
||||
/// The event loop is handling the OS's events and sending them to the user's callback.
|
||||
/// `NewEvents` has been sent, and `MainEventsCleared` hasn't.
|
||||
HandlingEvents,
|
||||
/// The event loop is handling the redraw events and sending them to the user's callback.
|
||||
/// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't.
|
||||
HandlingRedraw,
|
||||
}
|
||||
|
||||
impl<T> EventLoopRunner<T> {
|
||||
unsafe fn new<F>(event_loop: &EventLoop<T>, f: F) -> EventLoopRunner<T>
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &mut ControlFlow),
|
||||
{
|
||||
EventLoopRunner {
|
||||
control_flow: ControlFlow::default(),
|
||||
runner_state: RunnerState::New,
|
||||
in_modal_loop: false,
|
||||
modal_redraw_window: event_loop.window_target.p.thread_msg_target,
|
||||
event_handler: mem::transmute::<
|
||||
Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>,
|
||||
Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>,
|
||||
>(Box::new(f)),
|
||||
panic_error: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn take_panic_error(&mut self) -> Result<(), PanicError> {
|
||||
match self.panic_error.take() {
|
||||
Some(err) => Err(err),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_events(&mut self) {
|
||||
self.runner_state = match self.runner_state {
|
||||
// If we're already handling events or have deferred `NewEvents`, we don't need to do
|
||||
// do any processing.
|
||||
RunnerState::HandlingEvents
|
||||
| RunnerState::HandlingRedraw
|
||||
| RunnerState::DeferredNewEvents(..) => self.runner_state,
|
||||
|
||||
// Send the `Init` `NewEvents` and immediately move into event processing.
|
||||
RunnerState::New => {
|
||||
self.call_event_handler(Event::NewEvents(StartCause::Init));
|
||||
RunnerState::HandlingEvents
|
||||
}
|
||||
|
||||
// When `NewEvents` gets sent after an idle depends on the control flow...
|
||||
// Some `NewEvents` are deferred because not all Windows messages trigger an event_loop event.
|
||||
// So we defer the `NewEvents` to when we actually process an event.
|
||||
RunnerState::Idle(wait_start) => {
|
||||
match self.control_flow {
|
||||
// If we're polling, send `NewEvents` and immediately move into event processing.
|
||||
ControlFlow::Poll => {
|
||||
self.call_event_handler(Event::NewEvents(StartCause::Poll));
|
||||
RunnerState::HandlingEvents
|
||||
},
|
||||
// If the user was waiting until a specific time, the `NewEvents` call gets sent
|
||||
// at varying times depending on the current time.
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
match Instant::now() >= resume_time {
|
||||
// If the current time is later than the requested resume time, we can tell the
|
||||
// user that the resume time has been reached with `NewEvents` and immdiately move
|
||||
// into event processing.
|
||||
true => {
|
||||
self.call_event_handler(Event::NewEvents(StartCause::ResumeTimeReached {
|
||||
start: wait_start,
|
||||
requested_resume: resume_time,
|
||||
}));
|
||||
RunnerState::HandlingEvents
|
||||
},
|
||||
// However, if the current time is EARLIER than the requested resume time, we
|
||||
// don't want to send the `WaitCancelled` event until we know an event is being
|
||||
// sent. Defer.
|
||||
false => RunnerState::DeferredNewEvents(wait_start)
|
||||
}
|
||||
},
|
||||
// If we're waiting, `NewEvents` doesn't get sent until winit gets an event, so
|
||||
// we defer.
|
||||
ControlFlow::Wait |
|
||||
// `Exit` shouldn't really ever get sent here, but if it does do something somewhat sane.
|
||||
ControlFlow::Exit => RunnerState::DeferredNewEvents(wait_start),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: Event<'_, T>) {
|
||||
// If we're in the modal loop, we need to have some mechanism for finding when the event
|
||||
// queue has been cleared so we can call `events_cleared`. Windows doesn't give any utilities
|
||||
// for doing this, but it DOES guarantee that WM_PAINT will only occur after input events have
|
||||
// been processed. So, we send WM_PAINT to a dummy window which calls `events_cleared` when
|
||||
// the events queue has been emptied.
|
||||
if self.in_modal_loop {
|
||||
unsafe {
|
||||
winuser::RedrawWindow(
|
||||
self.modal_redraw_window,
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
winuser::RDW_INTERNALPAINT,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If new event processing has to be done (i.e. call NewEvents or defer), do it. If we're
|
||||
// already in processing nothing happens with this call.
|
||||
self.new_events();
|
||||
|
||||
// Now that an event has been received, we have to send any `NewEvents` calls that were
|
||||
// deferred.
|
||||
if let RunnerState::DeferredNewEvents(wait_start) = self.runner_state {
|
||||
match self.control_flow {
|
||||
ControlFlow::Exit | ControlFlow::Wait => {
|
||||
self.call_event_handler(Event::NewEvents(StartCause::WaitCancelled {
|
||||
start: wait_start,
|
||||
requested_resume: None,
|
||||
}))
|
||||
}
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
let start_cause = match Instant::now() >= resume_time {
|
||||
// If the current time is later than the requested resume time, the resume time
|
||||
// has been reached.
|
||||
true => StartCause::ResumeTimeReached {
|
||||
start: wait_start,
|
||||
requested_resume: resume_time,
|
||||
},
|
||||
// Otherwise, the requested resume time HASN'T been reached and we send a WaitCancelled.
|
||||
false => StartCause::WaitCancelled {
|
||||
start: wait_start,
|
||||
requested_resume: Some(resume_time),
|
||||
},
|
||||
};
|
||||
self.call_event_handler(Event::NewEvents(start_cause));
|
||||
}
|
||||
// This can be reached if the control flow is changed to poll during a `RedrawRequested`
|
||||
// that was sent after `MainEventsCleared`.
|
||||
ControlFlow::Poll => self.call_event_handler(Event::NewEvents(StartCause::Poll)),
|
||||
}
|
||||
self.runner_state = RunnerState::HandlingEvents;
|
||||
}
|
||||
|
||||
match (self.runner_state, &event) {
|
||||
(RunnerState::HandlingEvents, Event::RedrawRequested(window_id)) => {
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.runner_state = RunnerState::HandlingRedraw;
|
||||
self.call_event_handler(Event::RedrawRequested(*window_id));
|
||||
}
|
||||
(RunnerState::HandlingRedraw, Event::RedrawRequested(window_id)) => {
|
||||
self.call_event_handler(Event::RedrawRequested(*window_id));
|
||||
}
|
||||
(RunnerState::HandlingRedraw, _) => {
|
||||
warn!(
|
||||
"non-redraw event in redraw phase: {:?}",
|
||||
event.map_nonuser_event::<()>().ok()
|
||||
);
|
||||
}
|
||||
(_, _) => {
|
||||
self.runner_state = RunnerState::HandlingEvents;
|
||||
self.call_event_handler(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main_events_cleared(&mut self) {
|
||||
match self.runner_state {
|
||||
// If we were handling events, send the MainEventsCleared message.
|
||||
RunnerState::HandlingEvents => {
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.runner_state = RunnerState::HandlingRedraw;
|
||||
}
|
||||
|
||||
// We already cleared the main events, we don't have to do anything.
|
||||
// This happens when process_events() processed a RedrawRequested event.
|
||||
RunnerState::HandlingRedraw => {}
|
||||
|
||||
// If we *weren't* handling events, we don't have to do anything.
|
||||
RunnerState::New | RunnerState::Idle(..) => (),
|
||||
|
||||
// Some control flows require a NewEvents call even if no events were received. This
|
||||
// branch handles those.
|
||||
RunnerState::DeferredNewEvents(wait_start) => {
|
||||
match self.control_flow {
|
||||
// If we had deferred a Poll, send the Poll NewEvents and MainEventsCleared.
|
||||
ControlFlow::Poll => {
|
||||
self.call_event_handler(Event::NewEvents(StartCause::Poll));
|
||||
self.runner_state = RunnerState::HandlingEvents;
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.runner_state = RunnerState::HandlingRedraw;
|
||||
}
|
||||
// If we had deferred a WaitUntil and the resume time has since been reached,
|
||||
// send the resume notification and MainEventsCleared event.
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
if Instant::now() >= resume_time {
|
||||
self.call_event_handler(Event::NewEvents(
|
||||
StartCause::ResumeTimeReached {
|
||||
start: wait_start,
|
||||
requested_resume: resume_time,
|
||||
},
|
||||
));
|
||||
self.runner_state = RunnerState::HandlingEvents;
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.runner_state = RunnerState::HandlingRedraw;
|
||||
}
|
||||
}
|
||||
// If we deferred a wait and no events were received, the user doesn't have to
|
||||
// get an event.
|
||||
ControlFlow::Wait | ControlFlow::Exit => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn redraw_events_cleared(&mut self) {
|
||||
match self.runner_state {
|
||||
// If we were handling redraws, send the RedrawEventsCleared message.
|
||||
RunnerState::HandlingRedraw => {
|
||||
self.call_event_handler(Event::RedrawEventsCleared);
|
||||
self.runner_state = RunnerState::Idle(Instant::now());
|
||||
}
|
||||
// No event was processed, we don't have to do anything.
|
||||
RunnerState::DeferredNewEvents(_) => (),
|
||||
// Should not happen.
|
||||
_ => warn!(
|
||||
"unexpected state in redraw_events_cleared: {:?}",
|
||||
self.runner_state
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn call_event_handler(&mut self, event: Event<'_, T>) {
|
||||
if self.panic_error.is_none() {
|
||||
let EventLoopRunner {
|
||||
ref mut panic_error,
|
||||
ref mut event_handler,
|
||||
ref mut control_flow,
|
||||
..
|
||||
} = self;
|
||||
*panic_error = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
||||
if *control_flow != ControlFlow::Exit {
|
||||
(*event_handler)(event, control_flow);
|
||||
} else {
|
||||
(*event_handler)(event, &mut ControlFlow::Exit);
|
||||
}
|
||||
}))
|
||||
.err();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -728,8 +728,6 @@ unsafe fn init<T: 'static>(
|
||||
}
|
||||
}
|
||||
|
||||
window_flags.set(WindowFlags::MAXIMIZED, attributes.maximized);
|
||||
|
||||
// If the system theme is dark, we need to set the window theme now
|
||||
// before we update the window flags (and possibly show the
|
||||
// window for the first time).
|
||||
@@ -757,6 +755,11 @@ unsafe fn init<T: 'static>(
|
||||
.inner_size
|
||||
.unwrap_or_else(|| PhysicalSize::new(1024, 768).into());
|
||||
win.set_inner_size(dimensions);
|
||||
if attributes.maximized {
|
||||
// Need to set MAXIMIZED after setting `inner_size` as
|
||||
// `Window::set_inner_size` changes MAXIMIZED to false.
|
||||
win.set_maximized(true);
|
||||
}
|
||||
win.set_visible(attributes.visible);
|
||||
|
||||
if let Some(_) = attributes.fullscreen {
|
||||
|
||||
@@ -80,7 +80,9 @@ bitflags! {
|
||||
/// window's state to match our stored state. This controls whether to accept those changes.
|
||||
const MARKER_RETAIN_STATE_ON_SIZE = 1 << 10;
|
||||
|
||||
const MINIMIZED = 1 << 11;
|
||||
const MARKER_IN_SIZE_MOVE = 1 << 11;
|
||||
|
||||
const MINIMIZED = 1 << 12;
|
||||
|
||||
const FULLSCREEN_AND_MASK = !(
|
||||
WindowFlags::DECORATIONS.bits |
|
||||
|
||||
Reference in New Issue
Block a user