Compare commits

..

17 Commits

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

* modify dependency

* changelog

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

* add Android build documentation & cargo-apk to CI

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

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

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

Fixes #942.

* Add additional mouse motion events

Co-authored-by: Ryan Goldstein <ryan@ryanisaacg.com>
2020-04-26 16:42:45 -04:00
Matthias Fauconneau
114fe9d502 wayland: rework scale factor handling (#1538)
- Always send Resized events in case of scale factor change
- Properly take into account the resize the user can do using the resize event.
2020-04-22 18:00:41 +02:00
Héctor Ramón
54bc41f68b Implement Drop for Proxy on macOS platform (#1526) 2020-04-20 17:48:42 -04:00
Osspial
47ff8d61d1 Document that platforms will display garbage data in the window by default (#1541) 2020-04-20 00:04:30 -04:00
Benjamin Saunders
849b8f5dce Clarify when RedrawRequested is useful (#1529)
Co-Authored-By: Osspial <osspial@gmail.com>
2020-04-19 17:09:08 -04:00
Yanchi Toth
aabe42d252 Preserve with_maximized on windows (#1515) 2020-04-19 15:52:48 -04:00
simlay
78a62ec547 Added more docs.rs targets (#1521) 2020-04-19 15:37:13 -04:00
Benjamin Saunders
6dae994bb4 Mention raw-window-handle in library docs (#1528) 2020-04-19 14:58:58 -04:00
Philippe Renon
4c4d0916fd control_flow example: fix wait_cancelled logic again (#1511) 2020-04-19 13:55:10 -04:00
20 changed files with 1518 additions and 1237 deletions

View File

@@ -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

View File

@@ -1,10 +1,20 @@
# 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)
@@ -26,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)

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.22.1"
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"]

View File

@@ -109,8 +109,8 @@ If your PR makes notable changes to Winit's features, please update this section
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
- **Gamepad/Joystick events**: Capturing input from gampads and joysticks.
- **Device movement events:**: Capturing input from the device gyroscope and accelerometer.
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
## Platform
### Windows

View File

@@ -6,7 +6,7 @@
```toml
[dependencies]
winit = "0.22.1"
winit = "0.22.2"
```
## [Documentation](https://docs.rs/winit)
@@ -76,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`

View File

@@ -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 {

View File

@@ -81,10 +81,11 @@ pub enum Event<'a, T: 'static> {
///
/// This event is useful as a place to put your code that should be run after all
/// state-changing events have been handled and you want to do stuff (updating state, performing
/// calculations, etc) that happens as the "main body" of your event loop. If your program draws
/// graphics, it's usually better to do it in response to
/// calculations, etc) that happens as the "main body" of your event loop. If your program only draws
/// graphics when something changes, it's usually better to do it in response to
/// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted
/// immediately after this event.
/// immediately after this event. Programs that draw graphics continuously, like most games,
/// can render here unconditionally for simplicity.
MainEventsCleared,
/// Emitted after `MainEventsCleared` when a window should be redrawn.
@@ -97,6 +98,9 @@ pub enum Event<'a, T: 'static> {
///
/// During each iteration of the event loop, Winit will aggregate duplicate redraw requests
/// into a single event, to help avoid duplicating rendering work.
///
/// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless
/// something changes, like most non-game GUIs.
RedrawRequested(WindowId),
/// Emitted after all `RedrawRequested` events have been processed and control flow is about to
@@ -114,6 +118,30 @@ pub enum Event<'a, T: 'static> {
LoopDestroyed,
}
impl<T: Clone> Clone for Event<'static, T> {
fn clone(&self) -> Self {
use self::Event::*;
match self {
WindowEvent { window_id, event } => WindowEvent {
window_id: *window_id,
event: event.clone(),
},
UserEvent(event) => UserEvent(event.clone()),
DeviceEvent { device_id, event } => DeviceEvent {
device_id: *device_id,
event: event.clone(),
},
NewEvents(cause) => NewEvents(cause.clone()),
MainEventsCleared => MainEventsCleared,
RedrawRequested(wid) => RedrawRequested(*wid),
RedrawEventsCleared => RedrawEventsCleared,
LoopDestroyed => LoopDestroyed,
Suspended => Suspended,
Resumed => Resumed,
}
}
}
impl<'a, T> Event<'a, T> {
pub fn map_nonuser_event<U>(self) -> Result<Event<'a, U>, Event<'a, T>> {
use self::Event::*;
@@ -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,

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ use objc::{
};
use crate::{
dpi::PhysicalPosition,
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::MonitorHandleExtIOS,
platform_impl::platform::{
@@ -209,7 +210,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
if touch == nil {
break;
}
let location: CGPoint = msg_send![touch, locationInView: nil];
let logical_location: CGPoint = msg_send![touch, locationInView: nil];
let touch_type: UITouchType = msg_send![touch, type];
let force = if os_supports_force {
let trait_collection: id = msg_send![object, traitCollection];
@@ -248,12 +249,19 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
_ => panic!("unexpected touch phase: {:?}", phase as i32),
};
let physical_location = {
let scale_factor: CGFloat = msg_send![object, contentScaleFactor];
PhysicalPosition::from_logical::<(f64, f64), f64>(
(logical_location.x as _, logical_location.y as _),
scale_factor,
)
};
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId { uiscreen }),
id: touch_id,
location: (location.x as f64, location.y as f64).into(),
location: physical_location,
force,
phase,
}),

View File

@@ -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,

View File

@@ -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;
}

View File

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

View File

@@ -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);

View File

@@ -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)
}

View File

@@ -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();
}
}
}

View File

@@ -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 {

View File

@@ -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 |