mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
1 Commits
madsmtm/ev
...
v0.31.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8bccfff4f |
@@ -1,8 +1,8 @@
|
||||
name: iOS bug
|
||||
description: Create an iOS/UIKit-specific bug report
|
||||
description: Create an iOS-specific bug report
|
||||
labels:
|
||||
- B - bug
|
||||
- DS - uikit
|
||||
- DS - ios
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -1,8 +1,8 @@
|
||||
name: macOS bug
|
||||
name: MacOS bug
|
||||
description: Create a macOS-specific bug report
|
||||
labels:
|
||||
- B - bug
|
||||
- DS - appkit
|
||||
- DS - macos
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -2,7 +2,7 @@ name: Windows bug
|
||||
description: Create a Windows-specific bug report
|
||||
labels:
|
||||
- B - bug
|
||||
- DS - win32
|
||||
- DS - windows
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -124,7 +124,7 @@ jobs:
|
||||
# the cache has been downloaded.
|
||||
#
|
||||
# This could be avoided if we added Cargo.lock to the repository.
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
|
||||
path: |
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
- name: Cache cargo-apk
|
||||
if: contains(matrix.platform.target, 'android')
|
||||
id: cargo-apk-cache
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cargo/bin/cargo-apk
|
||||
# Change this key if we update the required cargo-apk version
|
||||
@@ -160,7 +160,7 @@ jobs:
|
||||
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
|
||||
run: cargo install cargo-apk --version=^0.9.7 --locked
|
||||
|
||||
- uses: taiki-e/cache-cargo-install-action@v3
|
||||
- uses: taiki-e/cache-cargo-install-action@v2
|
||||
if: contains(matrix.platform.target, 'wasm32') && matrix.toolchain == 'nightly'
|
||||
with:
|
||||
tool: wasm-bindgen-cli
|
||||
@@ -281,7 +281,7 @@ jobs:
|
||||
|
||||
# See restore step above
|
||||
- name: Save cache of cargo folder
|
||||
uses: actions/cache/save@v5
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index/
|
||||
|
||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
||||
32
Cargo.toml
32
Cargo.toml
@@ -8,22 +8,22 @@ edition = "2024"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/rust-windowing/winit"
|
||||
rust-version = "1.85"
|
||||
version = "0.31.0-beta.2"
|
||||
version = "0.31.0-beta.1"
|
||||
|
||||
[workspace.dependencies]
|
||||
# Workspace dependencies.
|
||||
# `winit` has no version here to allow using it in dev deps for docs.
|
||||
winit = { path = "winit" }
|
||||
winit-android = { version = "=0.31.0-beta.2", path = "winit-android" }
|
||||
winit-appkit = { version = "=0.31.0-beta.2", path = "winit-appkit" }
|
||||
winit-common = { version = "=0.31.0-beta.2", path = "winit-common" }
|
||||
winit-core = { version = "=0.31.0-beta.2", path = "winit-core" }
|
||||
winit-orbital = { version = "=0.31.0-beta.2", path = "winit-orbital" }
|
||||
winit-uikit = { version = "=0.31.0-beta.2", path = "winit-uikit" }
|
||||
winit-wayland = { version = "=0.31.0-beta.2", path = "winit-wayland", default-features = false }
|
||||
winit-web = { version = "=0.31.0-beta.2", path = "winit-web" }
|
||||
winit-win32 = { version = "=0.31.0-beta.2", path = "winit-win32" }
|
||||
winit-x11 = { version = "=0.31.0-beta.2", path = "winit-x11" }
|
||||
winit-android = { version = "=0.31.0-beta.1", path = "winit-android" }
|
||||
winit-appkit = { version = "=0.31.0-beta.1", path = "winit-appkit" }
|
||||
winit-common = { version = "=0.31.0-beta.1", path = "winit-common" }
|
||||
winit-core = { version = "=0.31.0-beta.1", path = "winit-core" }
|
||||
winit-orbital = { version = "=0.31.0-beta.1", path = "winit-orbital" }
|
||||
winit-uikit = { version = "=0.31.0-beta.1", path = "winit-uikit" }
|
||||
winit-wayland = { version = "=0.31.0-beta.1", path = "winit-wayland", default-features = false }
|
||||
winit-web = { version = "=0.31.0-beta.1", path = "winit-web" }
|
||||
winit-win32 = { version = "=0.31.0-beta.1", path = "winit-win32" }
|
||||
winit-x11 = { version = "=0.31.0-beta.1", path = "winit-x11" }
|
||||
|
||||
# Core dependencies.
|
||||
bitflags = "2"
|
||||
@@ -39,7 +39,7 @@ tracing = { version = "0.1.40", default-features = false }
|
||||
|
||||
# Dev dependencies.
|
||||
image = { version = "0.25.0", default-features = false }
|
||||
softbuffer = { version = "0.4.8", default-features = false, features = [
|
||||
softbuffer = { version = "0.4.6", default-features = false, features = [
|
||||
"x11",
|
||||
"x11-dlopen",
|
||||
"wayland",
|
||||
@@ -60,17 +60,16 @@ objc2-core-foundation = { version = "0.3.2", default-features = false }
|
||||
objc2-core-graphics = { version = "0.3.2", default-features = false }
|
||||
objc2-core-video = { version = "0.3.2", default-features = false }
|
||||
objc2-foundation = { version = "0.3.2", default-features = false }
|
||||
objc2-quartz-core = { version = "0.3.2", default-features = false }
|
||||
objc2-ui-kit = { version = "0.3.2", default-features = false }
|
||||
|
||||
# Windows dependencies.
|
||||
unicode-segmentation = "1.7.1"
|
||||
windows-sys = "0.61"
|
||||
windows-sys = "0.59.0"
|
||||
|
||||
# Linux dependencies.
|
||||
ahash = { version = "0.8.7", features = ["no-rng"] }
|
||||
bytemuck = { version = "1.13.1", default-features = false }
|
||||
calloop = "0.14.3"
|
||||
foldhash = { version = "0.2.0", default-features = false, features = ["std"] }
|
||||
libc = "0.2.64"
|
||||
memmap2 = "0.9.0"
|
||||
percent-encoding = "2.0"
|
||||
@@ -80,9 +79,8 @@ x11rb = { version = "0.13.0", default-features = false }
|
||||
xkbcommon-dl = "0.4.2"
|
||||
|
||||
# Orbital dependencies.
|
||||
libredox = "0.1.12"
|
||||
orbclient = { version = "0.3.47", default-features = false }
|
||||
redox_event = { package = "redox_event", version = "0.4.5" }
|
||||
redox_syscall = "0.5.7"
|
||||
|
||||
# Web dependencies.
|
||||
atomic-waker = "1"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.31.0-beta.2"
|
||||
winit = "0.31.0-beta.1"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
|
||||
@@ -32,7 +32,6 @@ allow = [
|
||||
"ISC", # https://tldrlegal.com/license/isc-license
|
||||
"MIT", # https://tldrlegal.com/license/mit-license
|
||||
"Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html
|
||||
"Zlib", # https://spdx.org/licenses/Zlib.html
|
||||
]
|
||||
confidence-threshold = 1.0
|
||||
private = { ignore = true }
|
||||
|
||||
234
dpi/src/lib.rs
234
dpi/src/lib.rs
@@ -84,18 +84,36 @@ pub trait Pixel: Copy + Into<f64> {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! pixel_int_impl {
|
||||
($($t:ty),*) => {$(
|
||||
impl Pixel for $t {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
round(f) as $t
|
||||
}
|
||||
}
|
||||
)*}
|
||||
impl Pixel for u8 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
round(f) as u8
|
||||
}
|
||||
}
|
||||
impl Pixel for u16 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
round(f) as u16
|
||||
}
|
||||
}
|
||||
impl Pixel for u32 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
round(f) as u32
|
||||
}
|
||||
}
|
||||
impl Pixel for i8 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
round(f) as i8
|
||||
}
|
||||
}
|
||||
impl Pixel for i16 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
round(f) as i16
|
||||
}
|
||||
}
|
||||
impl Pixel for i32 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
round(f) as i32
|
||||
}
|
||||
}
|
||||
|
||||
pixel_int_impl!(u8, u16, u32, i8, i16, i32);
|
||||
|
||||
impl Pixel for f32 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f as f32
|
||||
@@ -360,48 +378,6 @@ impl<P: Pixel> From<LogicalUnit<P>> for PixelUnit {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! vec2_from_impls {
|
||||
($t:ident, $a:ident, $b:ident, $mint_ty:ident) => {
|
||||
impl<P: Pixel, X: Pixel> From<(X, X)> for $t<P> {
|
||||
fn from(($a, $b): (X, X)) -> Self {
|
||||
Self::new($a.cast(), $b.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<$t<P>> for (X, X) {
|
||||
fn from(p: $t<P>) -> Self {
|
||||
(p.$a.cast(), p.$b.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for $t<P> {
|
||||
fn from([$a, $b]: [X; 2]) -> Self {
|
||||
Self::new($a.cast(), $b.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<$t<P>> for [X; 2] {
|
||||
fn from(p: $t<P>) -> Self {
|
||||
[p.$a.cast(), p.$b.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<mint::$mint_ty<P>> for $t<P> {
|
||||
fn from(p: mint::$mint_ty<P>) -> Self {
|
||||
Self::new(p.x, p.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<$t<P>> for mint::$mint_ty<P> {
|
||||
fn from(p: $t<P>) -> Self {
|
||||
Self { x: p.$a, y: p.$b }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A position represented in logical pixels.
|
||||
///
|
||||
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
|
||||
@@ -444,7 +420,43 @@ impl<P: Pixel> LogicalPosition<P> {
|
||||
}
|
||||
}
|
||||
|
||||
vec2_from_impls!(LogicalPosition, x, y, Point2);
|
||||
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> {
|
||||
fn from((x, y): (X, X)) -> LogicalPosition<P> {
|
||||
LogicalPosition::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for (X, X) {
|
||||
fn from(p: LogicalPosition<P>) -> (X, X) {
|
||||
(p.x.cast(), p.y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> {
|
||||
fn from([x, y]: [X; 2]) -> LogicalPosition<P> {
|
||||
LogicalPosition::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for [X; 2] {
|
||||
fn from(p: LogicalPosition<P>) -> [X; 2] {
|
||||
[p.x.cast(), p.y.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> {
|
||||
fn from(p: mint::Point2<P>) -> Self {
|
||||
Self::new(p.x, p.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
|
||||
fn from(p: LogicalPosition<P>) -> Self {
|
||||
mint::Point2 { x: p.x, y: p.y }
|
||||
}
|
||||
}
|
||||
|
||||
/// A position represented in physical pixels.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||
@@ -484,7 +496,43 @@ impl<P: Pixel> PhysicalPosition<P> {
|
||||
}
|
||||
}
|
||||
|
||||
vec2_from_impls!(PhysicalPosition, x, y, Point2);
|
||||
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> {
|
||||
fn from((x, y): (X, X)) -> PhysicalPosition<P> {
|
||||
PhysicalPosition::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for (X, X) {
|
||||
fn from(p: PhysicalPosition<P>) -> (X, X) {
|
||||
(p.x.cast(), p.y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> {
|
||||
fn from([x, y]: [X; 2]) -> PhysicalPosition<P> {
|
||||
PhysicalPosition::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for [X; 2] {
|
||||
fn from(p: PhysicalPosition<P>) -> [X; 2] {
|
||||
[p.x.cast(), p.y.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> {
|
||||
fn from(p: mint::Point2<P>) -> Self {
|
||||
Self::new(p.x, p.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
|
||||
fn from(p: PhysicalPosition<P>) -> Self {
|
||||
mint::Point2 { x: p.x, y: p.y }
|
||||
}
|
||||
}
|
||||
|
||||
/// A size represented in logical pixels.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||
@@ -524,7 +572,43 @@ impl<P: Pixel> LogicalSize<P> {
|
||||
}
|
||||
}
|
||||
|
||||
vec2_from_impls!(LogicalSize, width, height, Vector2);
|
||||
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> {
|
||||
fn from((x, y): (X, X)) -> LogicalSize<P> {
|
||||
LogicalSize::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for (X, X) {
|
||||
fn from(s: LogicalSize<P>) -> (X, X) {
|
||||
(s.width.cast(), s.height.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalSize<P> {
|
||||
fn from([x, y]: [X; 2]) -> LogicalSize<P> {
|
||||
LogicalSize::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for [X; 2] {
|
||||
fn from(s: LogicalSize<P>) -> [X; 2] {
|
||||
[s.width.cast(), s.height.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
|
||||
fn from(v: mint::Vector2<P>) -> Self {
|
||||
Self::new(v.x, v.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
|
||||
fn from(s: LogicalSize<P>) -> Self {
|
||||
mint::Vector2 { x: s.width, y: s.height }
|
||||
}
|
||||
}
|
||||
|
||||
/// A size represented in physical pixels.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||
@@ -561,7 +645,43 @@ impl<P: Pixel> PhysicalSize<P> {
|
||||
}
|
||||
}
|
||||
|
||||
vec2_from_impls!(PhysicalSize, width, height, Vector2);
|
||||
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> {
|
||||
fn from((x, y): (X, X)) -> PhysicalSize<P> {
|
||||
PhysicalSize::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for (X, X) {
|
||||
fn from(s: PhysicalSize<P>) -> (X, X) {
|
||||
(s.width.cast(), s.height.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalSize<P> {
|
||||
fn from([x, y]: [X; 2]) -> PhysicalSize<P> {
|
||||
PhysicalSize::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for [X; 2] {
|
||||
fn from(s: PhysicalSize<P>) -> [X; 2] {
|
||||
[s.width.cast(), s.height.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
|
||||
fn from(v: mint::Vector2<P>) -> Self {
|
||||
Self::new(v.x, v.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
|
||||
fn from(s: PhysicalSize<P>) -> Self {
|
||||
mint::Vector2 { x: s.width, y: s.height }
|
||||
}
|
||||
}
|
||||
|
||||
/// A size that's either physical or logical.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
|
||||
@@ -473,23 +473,16 @@ impl EventLoop {
|
||||
&mut self.combining_accent,
|
||||
);
|
||||
|
||||
let logical_key = keycodes::to_logical(key_char, keycode);
|
||||
let text = if state == event::ElementState::Pressed {
|
||||
logical_key.to_text().map(smol_str::SmolStr::new)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let event = event::WindowEvent::KeyboardInput {
|
||||
device_id: Some(DeviceId::from_raw(key.device_id() as i64)),
|
||||
event: event::KeyEvent {
|
||||
state,
|
||||
physical_key: keycodes::to_physical_key(keycode),
|
||||
logical_key,
|
||||
logical_key: keycodes::to_logical(key_char, keycode),
|
||||
location: keycodes::to_location(keycode),
|
||||
repeat: key.repeat_count() > 0,
|
||||
text: text.clone(),
|
||||
text_with_all_modifiers: text,
|
||||
text: None,
|
||||
text_with_all_modifiers: None,
|
||||
key_without_modifiers: keycodes::to_logical(key_char, keycode),
|
||||
},
|
||||
is_synthetic: false,
|
||||
@@ -507,6 +500,10 @@ impl EventLoop {
|
||||
input_status
|
||||
}
|
||||
|
||||
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
|
||||
self.run_app_on_demand(app)
|
||||
}
|
||||
|
||||
pub fn run_app_on_demand<A: ApplicationHandler>(
|
||||
&mut self,
|
||||
mut app: A,
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
//! with `cargo apk`, then the minimal changes would be:
|
||||
//! 1. Remove `ndk-glue` from your `Cargo.toml`
|
||||
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version =
|
||||
//! "0.31.0-beta.2", features = [ "android-native-activity" ] }`
|
||||
//! "0.31.0-beta.1", features = [ "android-native-activity" ] }`
|
||||
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc
|
||||
//! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
|
||||
//! logging as above).
|
||||
|
||||
@@ -51,7 +51,6 @@ objc2-app-kit = { workspace = true, features = [
|
||||
"NSScreen",
|
||||
"NSTextInputClient",
|
||||
"NSTextInputContext",
|
||||
"NSTrackingArea",
|
||||
"NSToolbar",
|
||||
"NSView",
|
||||
"NSWindow",
|
||||
@@ -107,7 +106,6 @@ objc2-foundation = { workspace = true, features = [
|
||||
"NSThread",
|
||||
"NSValue",
|
||||
] }
|
||||
objc2-quartz-core = { workspace = true, features = ["std", "CABase"] }
|
||||
winit-common = { workspace = true, features = ["core-foundation", "event-handler"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -98,7 +98,6 @@ pub(crate) fn override_send_event(global_app: &NSApplication) {
|
||||
}
|
||||
|
||||
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
|
||||
let time = app_state.event_time(event);
|
||||
let event_type = event.r#type();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match event_type {
|
||||
@@ -111,7 +110,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
|
||||
|
||||
if delta_x != 0.0 || delta_y != 0.0 {
|
||||
app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.device_event(event_loop, None, time, DeviceEvent::PointerMotion {
|
||||
app.device_event(event_loop, None, DeviceEvent::PointerMotion {
|
||||
delta: (delta_x, delta_y),
|
||||
});
|
||||
});
|
||||
@@ -120,7 +119,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
|
||||
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
|
||||
let button = event.buttonNumber() as u32;
|
||||
app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.device_event(event_loop, None, time, DeviceEvent::Button {
|
||||
app.device_event(event_loop, None, DeviceEvent::Button {
|
||||
button,
|
||||
state: ElementState::Pressed,
|
||||
});
|
||||
@@ -129,7 +128,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
|
||||
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
|
||||
let button = event.buttonNumber() as u32;
|
||||
app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.device_event(event_loop, None, time, DeviceEvent::Button {
|
||||
app.device_event(event_loop, None, DeviceEvent::Button {
|
||||
button,
|
||||
state: ElementState::Released,
|
||||
});
|
||||
|
||||
@@ -2,15 +2,13 @@ use std::cell::{Cell, OnceCell, RefCell};
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Instant;
|
||||
|
||||
use dispatch2::MainThreadBound;
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSEvent, NSRunningApplication};
|
||||
use objc2_foundation::{NSNotification, NSTimeInterval};
|
||||
use objc2_quartz_core::CACurrentMediaTime;
|
||||
use tracing::warn;
|
||||
use winit_common::core_foundation::{EventLoopProxy, MainRunLoop};
|
||||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
|
||||
use objc2_foundation::NSNotification;
|
||||
use winit_common::core_foundation::EventLoopProxy;
|
||||
use winit_common::event_handler::EventHandler;
|
||||
use winit_core::application::ApplicationHandler;
|
||||
use winit_core::event::{StartCause, WindowEvent};
|
||||
@@ -19,7 +17,7 @@ use winit_core::window::WindowId;
|
||||
|
||||
use super::event_loop::{ActiveEventLoop, notify_windows_of_exit, stop_app_immediately};
|
||||
use super::menu;
|
||||
use super::observer::EventLoopWaker;
|
||||
use super::observer::{EventLoopWaker, RunLoop};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct AppState {
|
||||
@@ -27,7 +25,7 @@ pub(super) struct AppState {
|
||||
activation_policy: Option<NSApplicationActivationPolicy>,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
run_loop: MainRunLoop,
|
||||
run_loop: RunLoop,
|
||||
event_loop_proxy: Arc<EventLoopProxy>,
|
||||
event_handler: EventHandler,
|
||||
stop_on_launch: Cell<bool>,
|
||||
@@ -45,8 +43,6 @@ pub(super) struct AppState {
|
||||
start_time: Cell<Option<Instant>>,
|
||||
wait_timeout: Cell<Option<Instant>>,
|
||||
pending_redraw: RefCell<Vec<WindowId>>,
|
||||
startup_instant: Instant,
|
||||
startup_timestamp: NSTimeInterval,
|
||||
// NOTE: This is strongly referenced by our `NSWindowDelegate` and our `NSView` subclass, and
|
||||
// as such should be careful to not add fields that, in turn, strongly reference those.
|
||||
}
|
||||
@@ -67,23 +63,12 @@ impl AppState {
|
||||
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
|
||||
}));
|
||||
|
||||
// Prime dylib caches etc.
|
||||
let _ = CACurrentMediaTime();
|
||||
|
||||
// Find the current Rust timestamp.
|
||||
let startup_instant = Instant::now();
|
||||
// Find the timestamp
|
||||
//
|
||||
// `NSProcessInfo::processInfo().systemUptime()` needs the required reason manifest,
|
||||
// `CACurrentMediaTime` (currently) doesn't, so we use that instead.
|
||||
let startup_timestamp = CACurrentMediaTime();
|
||||
|
||||
let this = Rc::new(Self {
|
||||
mtm,
|
||||
activation_policy,
|
||||
default_menu,
|
||||
activate_ignoring_other_apps,
|
||||
run_loop: MainRunLoop::get(mtm),
|
||||
run_loop: RunLoop::main(mtm),
|
||||
event_loop_proxy,
|
||||
event_handler: EventHandler::new(),
|
||||
stop_on_launch: Cell::new(false),
|
||||
@@ -98,8 +83,6 @@ impl AppState {
|
||||
start_time: Cell::new(None),
|
||||
wait_timeout: Cell::new(None),
|
||||
pending_redraw: RefCell::new(vec![]),
|
||||
startup_instant,
|
||||
startup_timestamp,
|
||||
});
|
||||
|
||||
GLOBAL.get(mtm).set(this.clone()).ok().and(Some(this))
|
||||
@@ -113,16 +96,6 @@ impl AppState {
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn event_time(&self, event: &NSEvent) -> Instant {
|
||||
if event.timestamp() == 0.0 {
|
||||
warn!(?event, "got zero timestamp");
|
||||
return Instant::now();
|
||||
}
|
||||
let duration_since_startup = event.timestamp() - self.startup_timestamp;
|
||||
let duration_since_startup = Duration::from_secs_f64(duration_since_startup as f64);
|
||||
self.startup_instant + duration_since_startup
|
||||
}
|
||||
|
||||
// NOTE: This notification will, globally, only be emitted once,
|
||||
// no matter how many `EventLoop`s the user creates.
|
||||
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
|
||||
@@ -274,12 +247,7 @@ impl AppState {
|
||||
// -> Don't go back into the event handler when our callstack originates from there
|
||||
if !self.event_handler.in_use() {
|
||||
self.with_handler(|app, event_loop| {
|
||||
app.window_event(
|
||||
event_loop,
|
||||
window_id,
|
||||
Instant::now(),
|
||||
WindowEvent::RedrawRequested,
|
||||
);
|
||||
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
|
||||
});
|
||||
|
||||
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
|
||||
@@ -297,7 +265,7 @@ impl AppState {
|
||||
if !pending_redraw.contains(&window_id) {
|
||||
pending_redraw.push(window_id);
|
||||
}
|
||||
self.run_loop.wake_up();
|
||||
self.run_loop.wakeup();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
@@ -342,7 +310,6 @@ impl AppState {
|
||||
// Called by RunLoopObserver after finishing waiting for new events
|
||||
pub fn wakeup(self: &Rc<Self>) {
|
||||
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
||||
// (we have registered to observe all modes, including modal event loops).
|
||||
if !self.event_handler.ready() || !self.is_running() {
|
||||
return;
|
||||
}
|
||||
@@ -371,7 +338,8 @@ impl AppState {
|
||||
// Called by RunLoopObserver before waiting for new events
|
||||
pub fn cleared(self: &Rc<Self>) {
|
||||
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
||||
// (we have registered to observe all modes, including modal event loops).
|
||||
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
|
||||
// we're about to return to the `CFRunLoop` to poll for new events?
|
||||
if !self.event_handler.ready() || !self.is_running() {
|
||||
return;
|
||||
}
|
||||
@@ -379,12 +347,7 @@ impl AppState {
|
||||
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
|
||||
for window_id in redraw {
|
||||
self.with_handler(|app, event_loop| {
|
||||
app.window_event(
|
||||
event_loop,
|
||||
window_id,
|
||||
Instant::now(),
|
||||
WindowEvent::RedrawRequested,
|
||||
);
|
||||
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
|
||||
});
|
||||
}
|
||||
self.with_handler(|app, event_loop| {
|
||||
|
||||
@@ -9,10 +9,8 @@ use objc2_app_kit::{
|
||||
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
|
||||
NSApplicationWillTerminateNotification, NSWindow,
|
||||
};
|
||||
use objc2_core_foundation::{CFIndex, CFRunLoopActivity, kCFRunLoopCommonModes};
|
||||
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
|
||||
use rwh_06::HasDisplayHandle;
|
||||
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver};
|
||||
use winit_core::application::ApplicationHandler;
|
||||
use winit_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
|
||||
use winit_core::error::{EventLoopError, RequestError};
|
||||
@@ -30,6 +28,7 @@ use super::cursor::CustomCursor;
|
||||
use super::event::dummy_event;
|
||||
use super::monitor;
|
||||
use super::notification_center::create_observer;
|
||||
use super::observer::setup_control_flow_observers;
|
||||
use crate::ActivationPolicy;
|
||||
use crate::window::Window;
|
||||
|
||||
@@ -151,9 +150,6 @@ pub struct EventLoop {
|
||||
// Though we do still need to keep the observers around to prevent them from being deallocated.
|
||||
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
|
||||
_before_waiting_observer: MainRunLoopObserver,
|
||||
_after_waiting_observer: MainRunLoopObserver,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
@@ -221,31 +217,7 @@ impl EventLoop {
|
||||
},
|
||||
);
|
||||
|
||||
let main_loop = MainRunLoop::get(mtm);
|
||||
let mode = unsafe { kCFRunLoopCommonModes }.unwrap();
|
||||
|
||||
let app_state_clone = Rc::clone(&app_state);
|
||||
let _before_waiting_observer = MainRunLoopObserver::new(
|
||||
mtm,
|
||||
CFRunLoopActivity::BeforeWaiting,
|
||||
true,
|
||||
// Queued with the lowest priority to ensure it is processed after other observers.
|
||||
// Without that, we'd get a `LoopExiting` after `AboutToWait`.
|
||||
CFIndex::MAX,
|
||||
move |_| app_state_clone.cleared(),
|
||||
);
|
||||
main_loop.add_observer(&_before_waiting_observer, mode);
|
||||
|
||||
let app_state_clone = Rc::clone(&app_state);
|
||||
let _after_waiting_observer = MainRunLoopObserver::new(
|
||||
mtm,
|
||||
CFRunLoopActivity::AfterWaiting,
|
||||
true,
|
||||
// Queued with the highest priority to ensure it is processed before other observers.
|
||||
CFIndex::MIN,
|
||||
move |_| app_state_clone.wakeup(),
|
||||
);
|
||||
main_loop.add_observer(&_after_waiting_observer, mode);
|
||||
setup_control_flow_observers(mtm);
|
||||
|
||||
Ok(EventLoop {
|
||||
app,
|
||||
@@ -253,8 +225,6 @@ impl EventLoop {
|
||||
window_target: ActiveEventLoop { app_state, mtm },
|
||||
_did_finish_launching_observer,
|
||||
_will_terminate_observer,
|
||||
_before_waiting_observer,
|
||||
_after_waiting_observer,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -262,6 +232,10 @@ impl EventLoop {
|
||||
&self.window_target
|
||||
}
|
||||
|
||||
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
|
||||
self.run_app_on_demand(app)
|
||||
}
|
||||
|
||||
// NB: we don't base this on `pump_events` because for `MacOs` we can't support
|
||||
// `pump_events` elegantly (we just ask to run the loop for a "short" amount of
|
||||
// time and so a layered implementation would end up using a lot of CPU due to
|
||||
|
||||
@@ -1,10 +1,171 @@
|
||||
//! Utilities for working with `CFRunLoop`.
|
||||
//!
|
||||
//! See Apple's documentation on Run Loops for details:
|
||||
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
|
||||
use std::cell::Cell;
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::time::Instant;
|
||||
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{
|
||||
CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopTimer, kCFRunLoopCommonModes,
|
||||
CFAbsoluteTimeGetCurrent, CFIndex, CFRetained, CFRunLoop, CFRunLoopActivity, CFRunLoopObserver,
|
||||
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopTimer, kCFRunLoopCommonModes,
|
||||
kCFRunLoopDefaultMode,
|
||||
};
|
||||
use tracing::error;
|
||||
|
||||
use super::app_state::AppState;
|
||||
|
||||
// begin is queued with the highest priority to ensure it is processed before other observers
|
||||
extern "C-unwind" fn control_flow_begin_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_info: *mut c_void,
|
||||
) {
|
||||
match activity {
|
||||
CFRunLoopActivity::AfterWaiting => {
|
||||
AppState::get(MainThreadMarker::new().unwrap()).wakeup();
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
||||
// without that, LoopExiting would get sent after AboutToWait
|
||||
extern "C-unwind" fn control_flow_end_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_info: *mut c_void,
|
||||
) {
|
||||
match activity {
|
||||
CFRunLoopActivity::BeforeWaiting => {
|
||||
AppState::get(MainThreadMarker::new().unwrap()).cleared();
|
||||
},
|
||||
CFRunLoopActivity::Exit => (), // unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RunLoop(CFRetained<CFRunLoop>);
|
||||
|
||||
impl RunLoop {
|
||||
pub fn main(mtm: MainThreadMarker) -> Self {
|
||||
// SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so
|
||||
// scheduling (and scheduling a non-`Send` block) to that thread is allowed.
|
||||
let _ = mtm;
|
||||
RunLoop(CFRunLoop::main().unwrap())
|
||||
}
|
||||
|
||||
pub fn wakeup(&self) {
|
||||
self.0.wake_up();
|
||||
}
|
||||
|
||||
unsafe fn add_observer(
|
||||
&self,
|
||||
flags: CFRunLoopActivity,
|
||||
// The lower the value, the sooner this will run
|
||||
priority: CFIndex,
|
||||
handler: CFRunLoopObserverCallBack,
|
||||
context: *mut CFRunLoopObserverContext,
|
||||
) {
|
||||
let observer =
|
||||
unsafe { CFRunLoopObserver::new(None, flags.0, true, priority, handler, context) }
|
||||
.unwrap();
|
||||
self.0.add_observer(Some(&observer), unsafe { kCFRunLoopCommonModes });
|
||||
}
|
||||
|
||||
/// Submit a closure to run on the main thread as the next step in the run loop, before other
|
||||
/// event sources are processed.
|
||||
///
|
||||
/// This is used for running event handlers, as those are not allowed to run re-entrantly.
|
||||
///
|
||||
/// # Implementation
|
||||
///
|
||||
/// This queuing could be implemented in the following several ways with subtle differences in
|
||||
/// timing. This list is sorted in rough order in which they are run:
|
||||
///
|
||||
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`.
|
||||
///
|
||||
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
|
||||
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
|
||||
/// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop).
|
||||
///
|
||||
/// a. `atStart = true`.
|
||||
///
|
||||
/// b. `atStart = false`.
|
||||
///
|
||||
/// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not
|
||||
/// respect the ordering that runloop events have.
|
||||
///
|
||||
/// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we
|
||||
/// want the event to be queued in a way that preserves the order the events originally arrived
|
||||
/// in.
|
||||
///
|
||||
/// As an example, let's assume that we receive two events from the user, a mouse click which we
|
||||
/// handled by queuing it, and a window resize which we handled immediately. If we allowed
|
||||
/// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of
|
||||
/// the queue, and the events would appear out of order to the user of Winit. So we must instead
|
||||
/// put the event at the very front of the queue, to be handled as soon as possible after
|
||||
/// handling whatever event it's currently handling.
|
||||
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
|
||||
// Convert `FnOnce()` to `Block<dyn Fn()>`.
|
||||
let closure = Cell::new(Some(closure));
|
||||
let block = block2::RcBlock::new(move || {
|
||||
if let Some(closure) = closure.take() {
|
||||
closure()
|
||||
} else {
|
||||
error!("tried to execute queued closure on main thread twice");
|
||||
}
|
||||
});
|
||||
|
||||
// There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa:
|
||||
// - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`.
|
||||
// - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window.
|
||||
// - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop.
|
||||
// - `NSConnectionReplyMode`: TODO.
|
||||
//
|
||||
// We only want to run event handlers in the default mode, as we support running a blocking
|
||||
// modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and
|
||||
// resizing such panel window enters the event tracking run loop mode, so we can't directly
|
||||
// trigger events inside that mode either.
|
||||
//
|
||||
// Any events that are queued while running a modal or when live-resizing will instead wait,
|
||||
// and be delivered to the application afterwards.
|
||||
//
|
||||
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
|
||||
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
|
||||
|
||||
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
|
||||
unsafe { self.0.perform_block(Some(mode), Some(&block)) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_control_flow_observers(mtm: MainThreadMarker) {
|
||||
let run_loop = RunLoop::main(mtm);
|
||||
unsafe {
|
||||
let mut context = CFRunLoopObserverContext {
|
||||
info: ptr::null_mut(),
|
||||
version: 0,
|
||||
retain: None,
|
||||
release: None,
|
||||
copyDescription: None,
|
||||
};
|
||||
run_loop.add_observer(
|
||||
CFRunLoopActivity::AfterWaiting,
|
||||
CFIndex::MIN,
|
||||
Some(control_flow_begin_handler),
|
||||
&mut context as *mut _,
|
||||
);
|
||||
run_loop.add_observer(
|
||||
CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting,
|
||||
CFIndex::MAX,
|
||||
Some(control_flow_end_handler),
|
||||
&mut context as *mut _,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoopWaker {
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
use std::time::Instant;
|
||||
|
||||
use dpi::{LogicalPosition, LogicalSize};
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::{AnyObject, Sel};
|
||||
use objc2::{AnyThread, DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send};
|
||||
use objc2::{DefinedClass, MainThreadMarker, define_class, msg_send};
|
||||
use objc2_app_kit::{
|
||||
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingArea,
|
||||
NSTrackingAreaOptions, NSView, NSWindow,
|
||||
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
|
||||
NSTrackingRectTag, NSView, NSWindow,
|
||||
};
|
||||
use objc2_core_foundation::CGRect;
|
||||
use objc2_foundation::{
|
||||
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
|
||||
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
|
||||
};
|
||||
use tracing::warn;
|
||||
use winit_core::event::{
|
||||
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
|
||||
PointerKind, PointerSource, TouchPhase, WindowEvent,
|
||||
@@ -120,6 +119,7 @@ pub struct ViewState {
|
||||
ime_size: Cell<NSSize>,
|
||||
modifiers: Cell<Modifiers>,
|
||||
phys_modifiers: RefCell<HashMap<Key, ModLocationMask>>,
|
||||
tracking_rect: Cell<Option<NSTrackingRectTag>>,
|
||||
ime_state: Cell<ImeState>,
|
||||
input_source: RefCell<String>,
|
||||
|
||||
@@ -131,6 +131,7 @@ pub struct ViewState {
|
||||
/// True if the current key event should be forwarded
|
||||
/// to the application, even during IME
|
||||
forward_key_to_app: Cell<bool>,
|
||||
|
||||
marked_text: RefCell<Retained<NSMutableAttributedString>>,
|
||||
accepts_first_mouse: bool,
|
||||
|
||||
@@ -152,17 +153,41 @@ define_class!(
|
||||
true
|
||||
}
|
||||
|
||||
#[unsafe(method(viewDidMoveToWindow))]
|
||||
fn view_did_move_to_window(&self) {
|
||||
trace_scope!("viewDidMoveToWindow");
|
||||
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
|
||||
self.removeTrackingRect(tracking_rect);
|
||||
}
|
||||
|
||||
let rect = self.frame();
|
||||
let tracking_rect = unsafe {
|
||||
self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false)
|
||||
};
|
||||
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
|
||||
self.ivars().tracking_rect.set(Some(tracking_rect));
|
||||
}
|
||||
|
||||
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
|
||||
#[unsafe(method(viewFrameDidChangeNotification:))]
|
||||
fn frame_did_change(&self, _notification: Option<&AnyObject>) {
|
||||
trace_scope!("NSViewFrameDidChangeNotification");
|
||||
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
|
||||
self.removeTrackingRect(tracking_rect);
|
||||
}
|
||||
|
||||
let rect = self.frame();
|
||||
let tracking_rect = unsafe {
|
||||
self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false)
|
||||
};
|
||||
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
|
||||
self.ivars().tracking_rect.set(Some(tracking_rect));
|
||||
|
||||
// Emit resize event here rather than from windowDidResize because:
|
||||
// 1. When a new window is created as a tab, the frame size may change without a window
|
||||
// resize occurring.
|
||||
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong
|
||||
// size (includes tab height).
|
||||
let rect = self.frame();
|
||||
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
|
||||
let size = logical_size.to_physical::<u32>(self.scale_factor());
|
||||
self.queue_event(WindowEvent::SurfaceResized(size));
|
||||
@@ -279,15 +304,9 @@ define_class!(
|
||||
// sending a `None` cursor range.
|
||||
None
|
||||
} else {
|
||||
// Clamp to string length to avoid NSRangeException from out-of-bounds
|
||||
// indices sent by macOS IME (e.g. native Pinyin, see
|
||||
// https://github.com/alacritty/alacritty/issues/8791).
|
||||
let len = string.length();
|
||||
let location = selected_range.location.min(len);
|
||||
let end = selected_range.end().min(len);
|
||||
// Convert the selected range from UTF-16 indices to UTF-8 indices.
|
||||
let sub_string_a = string.substringToIndex(location);
|
||||
let sub_string_b = string.substringToIndex(end);
|
||||
let sub_string_a = string.substringToIndex(selected_range.location);
|
||||
let sub_string_b = string.substringToIndex(selected_range.end());
|
||||
let lowerbound_utf8 = sub_string_a.len();
|
||||
let upperbound_utf8 = sub_string_b.len();
|
||||
Some((lowerbound_utf8, upperbound_utf8))
|
||||
@@ -682,9 +701,8 @@ define_class!(
|
||||
|
||||
self.update_modifiers(event, false);
|
||||
|
||||
let time = self.ivars().app_state.event_time(event);
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.device_event(event_loop, None, time, DeviceEvent::MouseWheel { delta })
|
||||
app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta })
|
||||
});
|
||||
self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase });
|
||||
}
|
||||
@@ -784,6 +802,7 @@ impl WinitView {
|
||||
ime_size: Default::default(),
|
||||
modifiers: Default::default(),
|
||||
phys_modifiers: Default::default(),
|
||||
tracking_rect: Default::default(),
|
||||
ime_state: Default::default(),
|
||||
input_source: Default::default(),
|
||||
ime_capabilities: Default::default(),
|
||||
@@ -793,52 +812,9 @@ impl WinitView {
|
||||
option_as_alt: Cell::new(option_as_alt),
|
||||
});
|
||||
let this: Retained<Self> = unsafe { msg_send![super(this), init] };
|
||||
|
||||
*this.ivars().input_source.borrow_mut() = this.current_input_source();
|
||||
|
||||
// `MouseEnteredAndExited` enables receiving events through `mouseEntered:` and
|
||||
// `mouseExited:`.
|
||||
//
|
||||
// `MouseMoved` enables receiving events through `mouseMoved:`
|
||||
//
|
||||
// We do not set `CursorUpdate` because it is part of the "flexible" alternative to
|
||||
// `cursorRect` based cursor image updates, and we currently still use
|
||||
// `cursorRect`s. We also can't really switch to this approach because "The
|
||||
// cursorUpdate(with:) message is not sent when the NSTrackingCursorUpdate option is
|
||||
// specified along with [`ActiveAlways`]."
|
||||
//
|
||||
// `ActiveAlways` indicates we want to receive events when the window is not
|
||||
// focused ("key window" in Cocoa terms), which matches the behavior on other
|
||||
// platforms.
|
||||
//
|
||||
// We do not set `AssumeInside` because we want to avoid emitting `Left` events without a
|
||||
// correspondering `Entered` to our consumers, and not setting this flag tells AppKit to
|
||||
// handle this for us by synthesizing entry and exit events in some cases.
|
||||
//
|
||||
// `InVisibleRect` instructs the tracking area's `owner` (our `NSView`) to ignore the value
|
||||
// we provide in `rect` and keep the tracking area's bounds up to date with the
|
||||
// current view bounds automatically.
|
||||
//
|
||||
// We do not set `EnabledDuringMouseDrag` to match the platform behavior on Windows
|
||||
// and Wayland, since neither emit events while being dragged over with an empty
|
||||
// cursor without focus.
|
||||
//
|
||||
// See also https://developer.apple.com/documentation/appkit/nstrackingareaoptions.
|
||||
|
||||
// Safety: the type of `owner` should be `NSView` and is.
|
||||
// The type of `user_info` is irrelevant because it is None.
|
||||
this.addTrackingArea(&*unsafe {
|
||||
NSTrackingArea::initWithRect_options_owner_userInfo(
|
||||
NSTrackingArea::alloc(),
|
||||
NSRect::ZERO,
|
||||
NSTrackingAreaOptions::MouseEnteredAndExited
|
||||
| NSTrackingAreaOptions::MouseMoved
|
||||
| NSTrackingAreaOptions::ActiveAlways
|
||||
| NSTrackingAreaOptions::InVisibleRect,
|
||||
Some(&this),
|
||||
None,
|
||||
)
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
@@ -847,16 +823,9 @@ impl WinitView {
|
||||
}
|
||||
|
||||
fn queue_event(&self, event: WindowEvent) {
|
||||
let app = NSApplication::sharedApplication(self.mtm());
|
||||
let window_id = window_id(&self.window());
|
||||
let time = if let Some(nsevent) = app.currentEvent() {
|
||||
self.ivars().app_state.event_time(&nsevent)
|
||||
} else {
|
||||
warn!("queued event with wrong timestamp, no active NSEvent found");
|
||||
Instant::now()
|
||||
};
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.window_event(event_loop, window_id, time, event);
|
||||
app.window_event(event_loop, window_id, event);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -897,33 +866,24 @@ impl WinitView {
|
||||
false
|
||||
}
|
||||
}
|
||||
pub(super) fn enable_ime(&self, capabilities: ImeCapabilities) {
|
||||
// This seems reasonable but the prior behavior of `set_ime_allowed` doesn't do this
|
||||
// (it was also broken but let's not break things worse)
|
||||
|
||||
// if self.ivars().ime_capabilities.get().is_none() {
|
||||
// self.ivars().ime_state.set(ImeState::Ground);
|
||||
// }
|
||||
pub(super) fn set_ime_allowed(&self, capabilities: Option<ImeCapabilities>) {
|
||||
if self.ivars().ime_capabilities.get().is_some() {
|
||||
return;
|
||||
}
|
||||
self.ivars().ime_capabilities.set(capabilities);
|
||||
|
||||
if capabilities.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear markedText
|
||||
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
|
||||
// why are we disabling things in an enable fn? who knows. it's what the previous one did
|
||||
// though
|
||||
if self.ivars().ime_state.get() != ImeState::Disabled {
|
||||
self.ivars().ime_state.set(ImeState::Disabled);
|
||||
self.queue_event(WindowEvent::Ime(Ime::Disabled));
|
||||
}
|
||||
self.ivars().ime_capabilities.set(Some(capabilities));
|
||||
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
}
|
||||
pub(super) fn disable_ime(&self) {
|
||||
// see above
|
||||
self.ivars().ime_capabilities.set(None);
|
||||
if self.ivars().ime_state.get() != ImeState::Disabled {
|
||||
self.ivars().ime_state.set(ImeState::Disabled);
|
||||
self.queue_event(WindowEvent::Ime(Ime::Disabled));
|
||||
}
|
||||
// we probably don't need to do this, but again this mirrors the prior behavior of
|
||||
// `set_ime_allowed`
|
||||
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
}
|
||||
|
||||
pub(super) fn ime_capabilities(&self) -> Option<ImeCapabilities> {
|
||||
|
||||
@@ -5,7 +5,6 @@ use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Instant;
|
||||
|
||||
use dpi::{
|
||||
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
|
||||
@@ -43,7 +42,6 @@ use objc2_foundation::{
|
||||
NSRect, NSSize, NSString, ns_string,
|
||||
};
|
||||
use tracing::{trace, warn};
|
||||
use winit_common::core_foundation::MainRunLoop;
|
||||
use winit_core::cursor::Cursor;
|
||||
use winit_core::error::{NotSupportedError, RequestError};
|
||||
use winit_core::event::{SurfaceSizeWriter, WindowEvent};
|
||||
@@ -58,6 +56,7 @@ use super::app_state::AppState;
|
||||
use super::cursor::{CustomCursor, cursor_from_icon};
|
||||
use super::ffi;
|
||||
use super::monitor::{self, MonitorHandle, flip_window_screen_coordinates, get_display_id};
|
||||
use super::observer::RunLoop;
|
||||
use super::util::cgerr;
|
||||
use super::view::WinitView;
|
||||
use super::window::{WinitPanel, WinitWindow, window_id};
|
||||
@@ -178,7 +177,7 @@ define_class!(
|
||||
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let this = self.retain();
|
||||
MainRunLoop::get(mtm).queue_closure(move || {
|
||||
RunLoop::main(mtm).queue_closure(move || {
|
||||
this.handle_scale_factor_changed(scale_factor);
|
||||
});
|
||||
}
|
||||
@@ -331,20 +330,6 @@ define_class!(
|
||||
trace_scope!("windowDidChangeOcclusionState:");
|
||||
let visible = self.window().occlusionState().contains(NSWindowOcclusionState::Visible);
|
||||
self.queue_event(WindowEvent::Occluded(!visible));
|
||||
|
||||
// We do this here rather than in `window:willUseFullScreenPresentationOptions`
|
||||
// because doing it there leaves the menu bar interactable despite being hidden.
|
||||
// Moving it here produces the most consistent results.
|
||||
if self.is_borderless_game()
|
||||
&& matches!(*self.ivars().fullscreen.borrow(), Some(Fullscreen::Borderless(_)))
|
||||
{
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
app.setPresentationOptions(
|
||||
NSApplicationPresentationOptions::HideDock
|
||||
| NSApplicationPresentationOptions::HideMenuBar,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(method(windowDidChangeScreen:))]
|
||||
@@ -904,16 +889,9 @@ impl WindowDelegate {
|
||||
}
|
||||
|
||||
pub(crate) fn queue_event(&self, event: WindowEvent) {
|
||||
let app = NSApplication::sharedApplication(self.mtm());
|
||||
let window_id = window_id(self.window());
|
||||
let time = if let Some(nsevent) = app.currentEvent() {
|
||||
self.ivars().app_state.event_time(&nsevent)
|
||||
} else {
|
||||
warn!("queued event with wrong timestamp, no active NSEvent found");
|
||||
Instant::now()
|
||||
};
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.window_event(event_loop, window_id, time, event);
|
||||
app.window_event(event_loop, window_id, event);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1556,7 +1534,7 @@ impl WindowDelegate {
|
||||
}
|
||||
|
||||
match (old_fullscreen, fullscreen) {
|
||||
(None, Some(_)) => {
|
||||
(None, Some(fullscreen)) => {
|
||||
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
|
||||
// set a normal style temporarily. The previous state will be
|
||||
// restored in `WindowDelegate::window_did_exit_fullscreen`.
|
||||
@@ -1567,6 +1545,16 @@ impl WindowDelegate {
|
||||
self.ivars().saved_style.set(Some(curr_mask));
|
||||
}
|
||||
|
||||
// In borderless games, we want to disable the dock and menu bar
|
||||
// by setting the presentation options. We do this here rather than in
|
||||
// `window:willUseFullScreenPresentationOptions` because for some reason
|
||||
// the menu bar remains interactable despite being hidden.
|
||||
if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) {
|
||||
let presentation_options = NSApplicationPresentationOptions::HideDock
|
||||
| NSApplicationPresentationOptions::HideMenuBar;
|
||||
app.setPresentationOptions(presentation_options);
|
||||
}
|
||||
|
||||
toggle_fullscreen(self.window());
|
||||
},
|
||||
(Some(Fullscreen::Borderless(_)), None) => {
|
||||
@@ -1694,7 +1682,7 @@ impl WindowDelegate {
|
||||
if current_caps.is_some() {
|
||||
return Err(ImeRequestError::AlreadyEnabled);
|
||||
}
|
||||
self.view().enable_ime(capabilities);
|
||||
self.view().set_ime_allowed(Some(capabilities));
|
||||
request_data
|
||||
},
|
||||
ImeRequest::Update(request_data) => {
|
||||
@@ -1704,7 +1692,7 @@ impl WindowDelegate {
|
||||
request_data
|
||||
},
|
||||
ImeRequest::Disable => {
|
||||
self.view().disable_ime();
|
||||
self.view().set_ime_allowed(None);
|
||||
return Ok(());
|
||||
},
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ x11 = ["xkbcommon-dl?/x11", "dep:x11-dl"]
|
||||
xkb = ["dep:xkbcommon-dl", "dep:smol_str"]
|
||||
|
||||
# CoreFoundation
|
||||
core-foundation = ["dep:block2", "dep:objc2", "dep:objc2-core-foundation"]
|
||||
core-foundation = ["dep:objc2", "dep:objc2-core-foundation"]
|
||||
|
||||
[dependencies]
|
||||
smol_str = { workspace = true, optional = true }
|
||||
@@ -31,12 +31,9 @@ x11-dl = { workspace = true, optional = true }
|
||||
xkbcommon-dl = { workspace = true, optional = true }
|
||||
|
||||
# CoreFoundation
|
||||
block2 = { workspace = true, optional = true }
|
||||
objc2 = { workspace = true, optional = true }
|
||||
objc2-core-foundation = { workspace = true, optional = true, features = [
|
||||
"std",
|
||||
"block2",
|
||||
"objc2",
|
||||
"CFRunLoop",
|
||||
"CFString",
|
||||
] }
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
//! Various utilities for interfacing with the main run loop.
|
||||
//!
|
||||
//! These allow for using closures without the `Send + Sync` requirement that is otherwise required
|
||||
//! when interfacing with run loops.
|
||||
//!
|
||||
//! See also <https://github.com/madsmtm/objc2/issues/696> for work on this at a lower level.
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{CFRetained, CFRunLoop, CFRunLoopMode, kCFRunLoopDefaultMode};
|
||||
use tracing::error;
|
||||
|
||||
use super::MainRunLoopObserver;
|
||||
|
||||
/// Wrapper around [`CFRunLoop::main`].
|
||||
#[derive(Debug)]
|
||||
pub struct MainRunLoop {
|
||||
/// This is on the main thread.
|
||||
_mtm: MainThreadMarker,
|
||||
/// A cached reference to the main run loop.
|
||||
main_run_loop: CFRetained<CFRunLoop>,
|
||||
}
|
||||
|
||||
impl MainRunLoop {
|
||||
/// Get the main run loop.
|
||||
pub fn get(_mtm: MainThreadMarker) -> Self {
|
||||
let main_run_loop = CFRunLoop::main().unwrap();
|
||||
Self { _mtm, main_run_loop }
|
||||
}
|
||||
|
||||
/// Get a reference to the underlying [`CFRunLoop`].
|
||||
pub fn run_loop(&self) -> &CFRunLoop {
|
||||
&self.main_run_loop
|
||||
}
|
||||
|
||||
/// Wake the main run loop.
|
||||
pub fn wake_up(&self) {
|
||||
self.main_run_loop.wake_up();
|
||||
}
|
||||
|
||||
/// Submit a closure to run on the main thread as the next step in the run loop, before other
|
||||
/// event sources are processed.
|
||||
///
|
||||
/// This is used for running event handlers, as those are not allowed to run re-entrantly.
|
||||
///
|
||||
/// # Implementation
|
||||
///
|
||||
/// This queuing could be implemented in the following several ways with subtle differences in
|
||||
/// timing. This list is sorted in rough order in which they are run:
|
||||
///
|
||||
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`.
|
||||
///
|
||||
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
|
||||
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
|
||||
/// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop).
|
||||
///
|
||||
/// a. `atStart = true`.
|
||||
///
|
||||
/// b. `atStart = false`.
|
||||
///
|
||||
/// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not
|
||||
/// respect the ordering that runloop events have.
|
||||
///
|
||||
/// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we
|
||||
/// want the event to be queued in a way that preserves the order the events originally arrived
|
||||
/// in.
|
||||
///
|
||||
/// As an example, let's assume that we receive two events from the user, a mouse click which we
|
||||
/// handled by queuing it, and a window resize which we handled immediately. If we allowed
|
||||
/// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of
|
||||
/// the queue, and the events would appear out of order to the user of Winit. So we must instead
|
||||
/// put the event at the very front of the queue, to be handled as soon as possible after
|
||||
/// handling whatever event it's currently handling.
|
||||
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
|
||||
// Convert `FnOnce()` to `Block<dyn Fn()>`.
|
||||
let closure = Cell::new(Some(closure));
|
||||
let block = block2::RcBlock::new(move || {
|
||||
debug_assert!(MainThreadMarker::new().is_some());
|
||||
if let Some(closure) = closure.take() {
|
||||
closure()
|
||||
} else {
|
||||
error!("tried to execute queued closure on main thread twice");
|
||||
}
|
||||
});
|
||||
|
||||
// There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa:
|
||||
// - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`.
|
||||
// - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window.
|
||||
// - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop.
|
||||
// - `NSConnectionReplyMode`: TODO.
|
||||
//
|
||||
// We only want to run event handlers in the default mode, as we support running a blocking
|
||||
// modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and
|
||||
// resizing such panel window enters the event tracking run loop mode, so we can't directly
|
||||
// trigger events inside that mode either.
|
||||
//
|
||||
// Any events that are queued while running a modal or when live-resizing will instead wait,
|
||||
// and be delivered to the application afterwards.
|
||||
//
|
||||
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
|
||||
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
|
||||
|
||||
let _ = self._mtm;
|
||||
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
|
||||
//
|
||||
// Additionally, we have a `MainThreadMarker` here, which means we know we're on the main
|
||||
// thread. We also know that the run loop is the main-thread run loop, so scheduling a
|
||||
// non-`Send` block to that is allowed.
|
||||
unsafe { self.main_run_loop.perform_block(Some(mode), Some(&block)) }
|
||||
}
|
||||
|
||||
/// Add an observer to the main run loop.
|
||||
pub fn add_observer(&self, observer: &MainRunLoopObserver, mode: &CFRunLoopMode) {
|
||||
// Accessing the `MainObserver`'s observer is fine here, since we're adding it to the main
|
||||
// run loop (which is on the same thread that the observer was created on).
|
||||
self.main_run_loop.add_observer(Some(&observer.observer), Some(mode));
|
||||
}
|
||||
|
||||
/// Remove an observer from the main run loop.
|
||||
///
|
||||
/// This is also done automatically when the [`MainRunLoopObserver`] is dropped.
|
||||
pub fn remove_observer(&self, observer: &MainRunLoopObserver, mode: &CFRunLoopMode) {
|
||||
// Same as in `add_observer`, accessing the main loop's observer is fine.
|
||||
self.main_run_loop.add_observer(Some(&observer.observer), Some(mode));
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
use block2::RcBlock;
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{
|
||||
CFIndex, CFRetained, CFRunLoopActivity, CFRunLoopObserver, kCFAllocatorDefault,
|
||||
};
|
||||
|
||||
/// A [`CFRunLoopObserver`] on the main thread.
|
||||
///
|
||||
/// This type has "ownership" semantics, and invalidates the observer when dropped.
|
||||
#[derive(Debug)]
|
||||
pub struct MainRunLoopObserver {
|
||||
/// This must be private, otherwise the user might add it to an arbitrary run loop (but this
|
||||
/// observer is not designed to only be on the main thread).
|
||||
pub(crate) observer: CFRetained<CFRunLoopObserver>,
|
||||
}
|
||||
|
||||
impl MainRunLoopObserver {
|
||||
/// Create a new run loop observer that observes the main run loop.
|
||||
pub fn new(
|
||||
mtm: MainThreadMarker,
|
||||
activities: CFRunLoopActivity,
|
||||
repeats: bool,
|
||||
// The lower the value, the sooner this will run (inverse of a "priority").
|
||||
order: CFIndex,
|
||||
callback: impl Fn(CFRunLoopActivity) + 'static,
|
||||
) -> Self {
|
||||
let block = RcBlock::new(move |_: *mut _, activity| {
|
||||
debug_assert!(MainThreadMarker::new().is_some());
|
||||
callback(activity)
|
||||
});
|
||||
|
||||
let _ = mtm;
|
||||
// SAFETY: The callback is not Send + Sync, which would normally be unsound, but since we
|
||||
// restrict the callback to only ever be on the main thread (by taking `MainThreadMarker`,
|
||||
// and in `MainRunLoop::add_observer`), the callback doesn't have to be thread safe.
|
||||
let observer = unsafe {
|
||||
CFRunLoopObserver::with_handler(
|
||||
kCFAllocatorDefault,
|
||||
activities.0,
|
||||
repeats,
|
||||
order,
|
||||
Some(&block),
|
||||
)
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
Self { observer }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MainRunLoopObserver {
|
||||
fn drop(&mut self) {
|
||||
self.observer.invalidate();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,3 @@
|
||||
//! Various wrappers around [`CFRunLoop`][objc2_core_foundation::CFRunLoop].
|
||||
//!
|
||||
//! See Apple's documentation on Run Loops for details:
|
||||
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
|
||||
|
||||
mod event_loop_proxy;
|
||||
mod main_run_loop;
|
||||
mod main_run_loop_observer;
|
||||
|
||||
pub use self::event_loop_proxy::*;
|
||||
pub use self::main_run_loop::*;
|
||||
pub use self::main_run_loop_observer::*;
|
||||
pub use event_loop_proxy::*;
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
//! End user application handling.
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use std::time::Instant;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
use web_time::Instant;
|
||||
|
||||
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
|
||||
use crate::event_loop::ActiveEventLoop;
|
||||
@@ -197,14 +192,10 @@ pub trait ApplicationHandler {
|
||||
}
|
||||
|
||||
/// Emitted when the OS sends an event to a winit window.
|
||||
///
|
||||
/// Contains the ID of the window, the event, and the time the event was received. Note that
|
||||
/// since events are queued, the time will differ from [`Instant::now()`].
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
window_id: WindowId,
|
||||
timestamp: Instant,
|
||||
event: WindowEvent,
|
||||
);
|
||||
|
||||
@@ -215,10 +206,9 @@ pub trait ApplicationHandler {
|
||||
&mut self,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
device_id: Option<DeviceId>,
|
||||
timestamp: Instant,
|
||||
event: DeviceEvent,
|
||||
) {
|
||||
let _ = (event_loop, device_id, timestamp, event);
|
||||
let _ = (event_loop, device_id, event);
|
||||
}
|
||||
|
||||
/// Emitted when the event loop is about to block and wait for new events.
|
||||
@@ -382,10 +372,9 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
|
||||
&mut self,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
window_id: WindowId,
|
||||
timestamp: Instant,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
(**self).window_event(event_loop, window_id, timestamp, event);
|
||||
(**self).window_event(event_loop, window_id, event);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -393,10 +382,9 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
|
||||
&mut self,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
device_id: Option<DeviceId>,
|
||||
timestamp: Instant,
|
||||
event: DeviceEvent,
|
||||
) {
|
||||
(**self).device_event(event_loop, device_id, timestamp, event);
|
||||
(**self).device_event(event_loop, device_id, event);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -452,10 +440,9 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
|
||||
&mut self,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
window_id: WindowId,
|
||||
timestamp: Instant,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
(**self).window_event(event_loop, window_id, timestamp, event);
|
||||
(**self).window_event(event_loop, window_id, event);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -463,10 +450,9 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
|
||||
&mut self,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
device_id: Option<DeviceId>,
|
||||
timestamp: Instant,
|
||||
event: DeviceEvent,
|
||||
) {
|
||||
(**self).device_event(event_loop, device_id, timestamp, event);
|
||||
(**self).device_event(event_loop, device_id, event);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -156,8 +156,6 @@ pub enum WindowEvent {
|
||||
Ime(Ime),
|
||||
|
||||
/// The pointer has moved on the window.
|
||||
///
|
||||
/// Should be emitted regardless of window focus.
|
||||
PointerMoved {
|
||||
device_id: Option<DeviceId>,
|
||||
|
||||
@@ -186,8 +184,6 @@ pub enum WindowEvent {
|
||||
},
|
||||
|
||||
/// The pointer has entered the window.
|
||||
///
|
||||
/// Should be emitted regardless of window focus.
|
||||
PointerEntered {
|
||||
device_id: Option<DeviceId>,
|
||||
|
||||
@@ -213,8 +209,6 @@ pub enum WindowEvent {
|
||||
},
|
||||
|
||||
/// The pointer has left the window.
|
||||
///
|
||||
/// Should be emitted regardless of window focus.
|
||||
PointerLeft {
|
||||
device_id: Option<DeviceId>,
|
||||
|
||||
@@ -522,19 +516,6 @@ impl From<PointerSource> for PointerKind {
|
||||
/// system.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ButtonSource {
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// ### macOS
|
||||
///
|
||||
/// Users may expect holding [<kbd>CTRL</kbd>](ModifiersState::CONTROL) while
|
||||
/// clicking [`MouseButton::Left`] to result in a "secondary" click, but the way these
|
||||
/// clicks behave natively is slightly different from how a physical secondary
|
||||
/// button press would, depending on the content under the cursor when clicked. If
|
||||
/// applications want this behavior they should implement it themselves by interpreting
|
||||
/// [`Left`](MouseButton::Left) clicks as secondary when
|
||||
/// [<kbd>CTRL</kbd>](ModifiersState::CONTROL) is held and their internal logic deems it
|
||||
/// appropriate for the content under the pointer.
|
||||
/// See also https://github.com/rust-windowing/winit/issues/4469.
|
||||
Mouse(MouseButton),
|
||||
/// See [`PointerSource::Touch`] for more details.
|
||||
///
|
||||
@@ -810,7 +791,7 @@ pub struct KeyEvent {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android:** This field is always the same value as `text`.
|
||||
/// - **Android:** Unimplemented, this field is always the same value as `text`.
|
||||
/// - **iOS:** Unimplemented, this field is always the same value as `text`.
|
||||
/// - **Web:** Unsupported, this field is always the same value as `text`.
|
||||
pub text_with_all_modifiers: Option<SmolStr>,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
pub mod never_return;
|
||||
pub mod pump_events;
|
||||
pub mod register;
|
||||
pub mod run_on_demand;
|
||||
|
||||
use std::fmt::{self, Debug};
|
||||
@@ -176,11 +174,11 @@ pub trait EventLoopProxyProvider: Send + Sync + Debug {
|
||||
/// - A reference-counted pointer to the underlying type.
|
||||
#[derive(Clone)]
|
||||
pub struct OwnedDisplayHandle {
|
||||
pub(crate) handle: Arc<dyn HasDisplayHandle + Send + Sync>,
|
||||
pub(crate) handle: Arc<dyn HasDisplayHandle>,
|
||||
}
|
||||
|
||||
impl OwnedDisplayHandle {
|
||||
pub fn new(handle: Arc<dyn HasDisplayHandle + Send + Sync>) -> Self {
|
||||
pub fn new(handle: Arc<dyn HasDisplayHandle>) -> Self {
|
||||
Self { handle }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
use crate::application::ApplicationHandler;
|
||||
|
||||
/// Additional methods on `EventLoop` for platforms whose run method never return.
|
||||
pub trait EventLoopExtNeverReturn {
|
||||
/// Run the event loop and call `process::exit` once finished.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS**: This registers the callbacks with the system and calls `UIApplicationMain`.
|
||||
/// - **macOS**: Unimplemented (TODO: Should call `NSApplicationMain`).
|
||||
/// - **Android/Orbital/Wayland/Windows/X11**: Unsupported.
|
||||
/// - **Web**: Impossible to support properly.
|
||||
fn run_app_never_return<A: ApplicationHandler + 'static>(self, app: A) -> !;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
use crate::application::ApplicationHandler;
|
||||
|
||||
/// Additional methods on `EventLoop` that registers it with the system event loop.
|
||||
pub trait EventLoopExtRegister {
|
||||
/// Initialize and register the application with the system's event loop, such that the
|
||||
/// callbacks will be run later.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web**: Once the event loop has been destroyed, it's possible to reinitialize another
|
||||
/// event loop by calling this function again. This can be useful if you want to recreate the
|
||||
/// event loop while the WebAssembly module is still loaded. For example, this can be used to
|
||||
/// recreate the event loop when switching between tabs on a single page application.
|
||||
/// - **iOS/macOS**: Unimplemented.
|
||||
/// - **Android/Orbital/Wayland/Windows/X11**: Unsupported.
|
||||
fn register_app<A: ApplicationHandler + 'static>(self, app: A);
|
||||
}
|
||||
@@ -6,13 +6,15 @@ use crate::{
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[allow(rustdoc::broken_intra_doc_links)] // FIXME(madsmtm): Fix these.
|
||||
/// Additional methods on [`EventLoop`] to return control flow to the caller.
|
||||
pub trait EventLoopExtRunOnDemand {
|
||||
/// Run the application with the event loop on the calling thread.
|
||||
///
|
||||
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
|
||||
/// state and it is possible to return control back to the caller without consuming the
|
||||
/// `EventLoop` (by using [`exit()`]) and so the event loop can be re-run after it has exit.
|
||||
/// closures and it is possible to return control back to the caller without
|
||||
/// consuming the `EventLoop` (by using [`exit()`]) and
|
||||
/// so the event loop can be re-run after it has exit.
|
||||
///
|
||||
/// It's expected that each run of the loop will be for orthogonal instantiations of your
|
||||
/// Winit application, but internally each instantiation may re-use some common window
|
||||
@@ -29,7 +31,8 @@ pub trait EventLoopExtRunOnDemand {
|
||||
///
|
||||
/// # Caveats
|
||||
/// - This extension isn't available on all platforms, since it's not always possible to return
|
||||
/// to the caller (specifically this is impossible on iOS and Web).
|
||||
/// to the caller (specifically this is impossible on iOS and Web - though with the Web
|
||||
/// backend it is possible to use `EventLoopExtWeb::spawn_app()`[^1] more than once instead).
|
||||
/// - No [`Window`] state can be carried between separate runs of the event loop.
|
||||
///
|
||||
/// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
|
||||
@@ -48,6 +51,8 @@ pub trait EventLoopExtRunOnDemand {
|
||||
/// are delivered via callbacks based on an event loop that is internal to the browser itself.
|
||||
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS.
|
||||
///
|
||||
/// [^1]: `spawn_app()` is only available on the Web platforms.
|
||||
///
|
||||
/// [`exit()`]: ActiveEventLoop::exit()
|
||||
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
|
||||
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError>;
|
||||
|
||||
@@ -29,13 +29,11 @@ pub enum NativeKeyCode {
|
||||
Windows(u16),
|
||||
/// An XKB "keycode".
|
||||
Xkb(u32),
|
||||
/// An OpenHarmony "scancode".
|
||||
Ohos(u32),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for NativeKeyCode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use NativeKeyCode::{Android, MacOS, Ohos, Unidentified, Windows, Xkb};
|
||||
use NativeKeyCode::{Android, MacOS, Unidentified, Windows, Xkb};
|
||||
let mut debug_tuple;
|
||||
match self {
|
||||
Unidentified => {
|
||||
@@ -57,10 +55,6 @@ impl std::fmt::Debug for NativeKeyCode {
|
||||
debug_tuple = f.debug_tuple("Xkb");
|
||||
debug_tuple.field(&format_args!("0x{code:04X}"));
|
||||
},
|
||||
Ohos(code) => {
|
||||
debug_tuple = f.debug_tuple("OpenHarmony");
|
||||
debug_tuple.field(&format_args!("0x{code:04X}"));
|
||||
},
|
||||
}
|
||||
debug_tuple.finish()
|
||||
}
|
||||
@@ -91,13 +85,11 @@ pub enum NativeKey {
|
||||
Xkb(u32),
|
||||
/// A "key value string".
|
||||
Web(SmolStr),
|
||||
/// An OpenHarmony "keycode", which is similar to a "virtual-key code" on Windows.
|
||||
Ohos(u32),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for NativeKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use NativeKey::{Android, MacOS, Ohos, Unidentified, Web, Windows, Xkb};
|
||||
use NativeKey::{Android, MacOS, Unidentified, Web, Windows, Xkb};
|
||||
let mut debug_tuple;
|
||||
match self {
|
||||
Unidentified => {
|
||||
@@ -123,10 +115,6 @@ impl std::fmt::Debug for NativeKey {
|
||||
debug_tuple = f.debug_tuple("Web");
|
||||
debug_tuple.field(code);
|
||||
},
|
||||
Ohos(code) => {
|
||||
debug_tuple = f.debug_tuple("OpenHarmony");
|
||||
debug_tuple.field(code);
|
||||
},
|
||||
}
|
||||
debug_tuple.finish()
|
||||
}
|
||||
@@ -141,7 +129,6 @@ impl From<NativeKeyCode> for NativeKey {
|
||||
NativeKeyCode::MacOS(x) => NativeKey::MacOS(x),
|
||||
NativeKeyCode::Windows(x) => NativeKey::Windows(x),
|
||||
NativeKeyCode::Xkb(x) => NativeKey::Xkb(x),
|
||||
NativeKeyCode::Ohos(x) => NativeKey::Ohos(x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -834,7 +834,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web / Orbital:** Always returns [`None`].
|
||||
/// - **iOS / Android / Web / Wayland / Orbital:** Always returns [`None`].
|
||||
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>>;
|
||||
|
||||
/// Sets resize increments of the surface.
|
||||
@@ -846,6 +846,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
|
||||
///
|
||||
/// - **macOS:** Increments are converted to logical size and then macOS rounds them to whole
|
||||
/// numbers.
|
||||
/// - **Wayland:** Not implemented.
|
||||
/// - **iOS / Android / Web / Orbital:** Unsupported.
|
||||
fn set_surface_resize_increments(&self, increments: Option<Size>);
|
||||
|
||||
@@ -1135,7 +1136,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
|
||||
let action = if allowed {
|
||||
let position = LogicalPosition::new(0, 0);
|
||||
let size = LogicalSize::new(0, 0);
|
||||
let ime_caps = ImeCapabilities::new().with_hint_and_purpose().with_cursor_area();
|
||||
let ime_caps = ImeCapabilities::new().without_hint_and_purpose().with_cursor_area();
|
||||
let request_data = ImeRequestData {
|
||||
hint_and_purpose: Some((ImeHint::NONE, ImePurpose::Normal)),
|
||||
// WARNING: there's nothing sensible to use here by default.
|
||||
@@ -1690,22 +1691,6 @@ pub enum ImeSurroundingTextError {
|
||||
AnchorBadPosition,
|
||||
}
|
||||
|
||||
impl fmt::Display for ImeSurroundingTextError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ImeSurroundingTextError::TextTooLong => write!(f, "text exceeds maximum length"),
|
||||
ImeSurroundingTextError::CursorBadPosition => {
|
||||
write!(f, "cursor is not at a valid text index")
|
||||
},
|
||||
ImeSurroundingTextError::AnchorBadPosition => {
|
||||
write!(f, "anchor is not at a valid text index")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ImeSurroundingTextError {}
|
||||
|
||||
/// Defines the text surrounding the caret
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
|
||||
@@ -21,6 +21,5 @@ tracing.workspace = true
|
||||
winit-core.workspace = true
|
||||
|
||||
# Platform-specific
|
||||
libredox.workspace = true
|
||||
orbclient.workspace = true
|
||||
redox_event.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
|
||||
@@ -10,7 +10,6 @@ use orbclient::{
|
||||
ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MouseRelativeEvent,
|
||||
MoveEvent, QuitEvent, ResizeEvent, ScrollEvent, TextInputEvent,
|
||||
};
|
||||
use redox_event::{EventFlags, EventQueue};
|
||||
use smol_str::SmolStr;
|
||||
use winit_core::application::ApplicationHandler;
|
||||
use winit_core::cursor::{CustomCursor, CustomCursorSource};
|
||||
@@ -101,7 +100,6 @@ fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) {
|
||||
orbclient::K_LEFT => (KeyCode::ArrowLeft, Some(NamedKey::ArrowLeft)),
|
||||
orbclient::K_LEFT_SHIFT => (KeyCode::ShiftLeft, Some(NamedKey::Shift)),
|
||||
orbclient::K_MINUS => (KeyCode::Minus, None),
|
||||
|
||||
orbclient::K_NUM_0 => (KeyCode::Numpad0, None),
|
||||
orbclient::K_NUM_1 => (KeyCode::Numpad1, None),
|
||||
orbclient::K_NUM_2 => (KeyCode::Numpad2, None),
|
||||
@@ -112,20 +110,12 @@ fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) {
|
||||
orbclient::K_NUM_7 => (KeyCode::Numpad7, None),
|
||||
orbclient::K_NUM_8 => (KeyCode::Numpad8, None),
|
||||
orbclient::K_NUM_9 => (KeyCode::Numpad9, None),
|
||||
orbclient::K_NUM_ASTERISK => (KeyCode::NumpadMultiply, None),
|
||||
orbclient::K_NUM_ENTER => (KeyCode::NumpadEnter, Some(NamedKey::Enter)),
|
||||
orbclient::K_NUM_MINUS => (KeyCode::NumpadSubtract, None),
|
||||
orbclient::K_NUM_PLUS => (KeyCode::NumpadAdd, None),
|
||||
orbclient::K_NUM_SLASH => (KeyCode::NumpadDivide, None),
|
||||
orbclient::K_NUM_PERIOD => (KeyCode::NumpadDecimal, None),
|
||||
|
||||
orbclient::K_PERIOD => (KeyCode::Period, None),
|
||||
orbclient::K_PGDN => (KeyCode::PageDown, Some(NamedKey::PageDown)),
|
||||
orbclient::K_PGUP => (KeyCode::PageUp, Some(NamedKey::PageUp)),
|
||||
orbclient::K_QUOTE => (KeyCode::Quote, None),
|
||||
orbclient::K_RIGHT => (KeyCode::ArrowRight, Some(NamedKey::ArrowRight)),
|
||||
orbclient::K_RIGHT_SHIFT => (KeyCode::ShiftRight, Some(NamedKey::Shift)),
|
||||
orbclient::K_RIGHT_SUPER => (KeyCode::MetaRight, Some(NamedKey::Meta)),
|
||||
orbclient::K_SEMICOLON => (KeyCode::Semicolon, None),
|
||||
orbclient::K_SLASH => (KeyCode::Slash, None),
|
||||
orbclient::K_SPACE => (KeyCode::Space, None),
|
||||
@@ -137,20 +127,6 @@ fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) {
|
||||
orbclient::K_VOLUME_TOGGLE => (KeyCode::AudioVolumeMute, Some(NamedKey::AudioVolumeMute)),
|
||||
orbclient::K_VOLUME_UP => (KeyCode::AudioVolumeUp, Some(NamedKey::AudioVolumeUp)),
|
||||
|
||||
orbclient::K_INS => (KeyCode::Insert, Some(NamedKey::Insert)),
|
||||
orbclient::K_PRTSC => (KeyCode::PrintScreen, Some(NamedKey::PrintScreen)),
|
||||
orbclient::K_NUM => (KeyCode::NumLock, Some(NamedKey::NumLock)),
|
||||
orbclient::K_SCROLL => (KeyCode::ScrollLock, Some(NamedKey::ScrollLock)),
|
||||
orbclient::K_APP => (KeyCode::ContextMenu, Some(NamedKey::ContextMenu)),
|
||||
|
||||
orbclient::K_MEDIA_FAST_FORWARD => {
|
||||
(KeyCode::MediaFastForward, Some(NamedKey::MediaFastForward))
|
||||
},
|
||||
orbclient::K_MEDIA_REWIND => (KeyCode::MediaRewind, Some(NamedKey::MediaRewind)),
|
||||
orbclient::K_MEDIA_STOP => (KeyCode::MediaStop, Some(NamedKey::MediaStop)),
|
||||
|
||||
orbclient::K_POWER => (KeyCode::Power, Some(NamedKey::Power)),
|
||||
|
||||
_ => return (PhysicalKey::Unidentified(NativeKeyCode::Unidentified), None),
|
||||
};
|
||||
(PhysicalKey::Code(key_code), named_key_opt)
|
||||
@@ -312,12 +288,16 @@ impl EventLoop {
|
||||
let (user_events_sender, user_events_receiver) = mpsc::sync_channel(1);
|
||||
|
||||
let event_socket =
|
||||
Arc::new(EventQueue::new().map_err(|error| os_error!(format!("{error}")))?);
|
||||
Arc::new(RedoxSocket::event().map_err(|error| os_error!(format!("{error}")))?);
|
||||
|
||||
let wake_socket = TimeSocket::open().map_err(|error| os_error!(format!("{error}")))?;
|
||||
|
||||
event_socket
|
||||
.subscribe(wake_socket.0.fd(), EventSource::Time, EventFlags::READ)
|
||||
.write(&syscall::Event {
|
||||
id: wake_socket.0.fd,
|
||||
flags: syscall::EventFlags::EVENT_READ,
|
||||
data: wake_socket.0.fd,
|
||||
})
|
||||
.map_err(|error| os_error!(format!("{error}")))?;
|
||||
|
||||
Ok(Self {
|
||||
@@ -501,10 +481,7 @@ impl EventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_app_on_demand<A: ApplicationHandler>(
|
||||
&mut self,
|
||||
mut app: A,
|
||||
) -> Result<(), EventLoopError> {
|
||||
pub fn run_app<A: ApplicationHandler>(mut self, mut app: A) -> Result<(), EventLoopError> {
|
||||
let mut start_cause = StartCause::Init;
|
||||
loop {
|
||||
app.new_events(&self.window_target, start_cause);
|
||||
@@ -518,7 +495,7 @@ impl EventLoop {
|
||||
let mut creates = self.window_target.creates.lock().unwrap();
|
||||
creates.pop_front()
|
||||
} {
|
||||
let window_id = WindowId::from_raw(window.fd());
|
||||
let window_id = WindowId::from_raw(window.fd);
|
||||
|
||||
let mut buf: [u8; 4096] = [0; 4096];
|
||||
let path = window.fpath(&mut buf).expect("failed to read properties");
|
||||
@@ -542,18 +519,18 @@ impl EventLoop {
|
||||
} {
|
||||
app.window_event(&self.window_target, destroy_id, event::WindowEvent::Destroyed);
|
||||
self.windows
|
||||
.retain(|(window, _event_state)| WindowId::from_raw(window.fd()) != destroy_id);
|
||||
.retain(|(window, _event_state)| WindowId::from_raw(window.fd) != destroy_id);
|
||||
}
|
||||
|
||||
// Handle window events.
|
||||
let mut i = 0;
|
||||
// While loop is used here because the same window may be processed more than once.
|
||||
while let Some((window, event_state)) = self.windows.get_mut(i) {
|
||||
let window_id = WindowId::from_raw(window.fd());
|
||||
let window_id = WindowId::from_raw(window.fd);
|
||||
|
||||
let mut event_buf = [0u8; 16 * mem::size_of::<orbclient::Event>()];
|
||||
let count = libredox::call::read(window.fd(), &mut event_buf)
|
||||
.expect("failed to read window events");
|
||||
let count =
|
||||
syscall::read(window.fd, &mut event_buf).expect("failed to read window events");
|
||||
// Safety: orbclient::Event is a packed struct designed to be transferred over a
|
||||
// socket.
|
||||
let events = unsafe {
|
||||
@@ -633,7 +610,11 @@ impl EventLoop {
|
||||
|
||||
self.window_target
|
||||
.event_socket
|
||||
.subscribe(timeout_socket.0.fd(), EventSource::Time, EventFlags::READ)
|
||||
.write(&syscall::Event {
|
||||
id: timeout_socket.0.fd,
|
||||
flags: syscall::EventFlags::EVENT_READ,
|
||||
data: 0,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let start = Instant::now();
|
||||
@@ -642,7 +623,7 @@ impl EventLoop {
|
||||
|
||||
if let Some(duration) = instant.checked_duration_since(start) {
|
||||
time.tv_sec += duration.as_secs() as i64;
|
||||
time.tv_nsec += duration.subsec_nanos() as i64;
|
||||
time.tv_nsec += duration.subsec_nanos() as i32;
|
||||
// Normalize timespec so tv_nsec is not greater than one second.
|
||||
while time.tv_nsec >= 1_000_000_000 {
|
||||
time.tv_sec += 1;
|
||||
@@ -654,22 +635,12 @@ impl EventLoop {
|
||||
}
|
||||
|
||||
// Wait for event if needed.
|
||||
let event = loop {
|
||||
match self.window_target.event_socket.next_event() {
|
||||
Ok(event) => break event,
|
||||
Err(err) if err.is_interrupt() => continue,
|
||||
Err(err) => {
|
||||
return Err(os_error!(format!("failed to read event: {err}")).into());
|
||||
},
|
||||
}
|
||||
};
|
||||
let mut event = syscall::Event::default();
|
||||
self.window_target.event_socket.read(&mut event).unwrap();
|
||||
|
||||
// TODO: handle spurious wakeups (redraw caused wakeup but redraw already handled)
|
||||
match requested_resume {
|
||||
Some(requested_resume)
|
||||
if event.fd == timeout_socket.0.fd()
|
||||
&& matches!(event.user_data, EventSource::Time) =>
|
||||
{
|
||||
Some(requested_resume) if event.id == timeout_socket.0.fd => {
|
||||
// If the event is from the special timeout socket, report that resume
|
||||
// time was reached.
|
||||
start_cause = StartCause::ResumeTimeReached { start, requested_resume };
|
||||
@@ -707,13 +678,6 @@ impl EventLoopProxyProvider for EventLoopProxy {
|
||||
|
||||
impl Unpin for EventLoopProxy {}
|
||||
|
||||
redox_event::user_data! {
|
||||
pub enum EventSource {
|
||||
Orbital,
|
||||
Time,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActiveEventLoop {
|
||||
control_flow: Cell<ControlFlow>,
|
||||
@@ -721,7 +685,7 @@ pub struct ActiveEventLoop {
|
||||
pub(super) creates: Mutex<VecDeque<Arc<RedoxSocket>>>,
|
||||
pub(super) redraws: Arc<Mutex<VecDeque<WindowId>>>,
|
||||
pub(super) destroys: Arc<Mutex<VecDeque<WindowId>>>,
|
||||
pub(super) event_socket: Arc<EventQueue<EventSource>>,
|
||||
pub(super) event_socket: Arc<RedoxSocket>,
|
||||
pub(super) event_loop_proxy: Arc<EventLoopProxy>,
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,7 @@
|
||||
//! Redox OS has some functionality not yet present that will be implemented
|
||||
//! when its orbital display server provides it.
|
||||
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Result, Write};
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::{fmt, mem, slice, str};
|
||||
|
||||
use libredox::data::TimeSpec;
|
||||
use std::{fmt, str};
|
||||
|
||||
pub use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes};
|
||||
|
||||
@@ -21,11 +16,15 @@ pub mod window;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RedoxSocket {
|
||||
fd: File,
|
||||
fd: usize,
|
||||
}
|
||||
|
||||
impl RedoxSocket {
|
||||
fn orbital(properties: &WindowProperties<'_>) -> Result<Self> {
|
||||
fn event() -> syscall::Result<Self> {
|
||||
Self::open_raw("/scheme/event")
|
||||
}
|
||||
|
||||
fn orbital(properties: &WindowProperties<'_>) -> syscall::Result<Self> {
|
||||
Self::open_raw(&format!("{properties}"))
|
||||
}
|
||||
|
||||
@@ -33,27 +32,30 @@ impl RedoxSocket {
|
||||
// non-socket path is used, it could cause read and write to not function as expected. For
|
||||
// example, the seek would change in a potentially unpredictable way if either read or write
|
||||
// were called at the same time by multiple threads.
|
||||
fn open_raw(path: &str) -> Result<Self> {
|
||||
let fd = OpenOptions::new().read(true).write(true).open(path)?;
|
||||
fn open_raw(path: &str) -> syscall::Result<Self> {
|
||||
let fd = syscall::open(path, syscall::O_RDWR | syscall::O_CLOEXEC)?;
|
||||
Ok(Self { fd })
|
||||
}
|
||||
|
||||
fn fd(&self) -> usize {
|
||||
self.fd.as_raw_fd() as usize
|
||||
fn read(&self, buf: &mut [u8]) -> syscall::Result<()> {
|
||||
let count = syscall::read(self.fd, buf)?;
|
||||
if count == buf.len() { Ok(()) } else { Err(syscall::Error::new(syscall::EINVAL)) }
|
||||
}
|
||||
|
||||
fn read(&self, buf: &mut [u8]) -> Result<()> {
|
||||
(&self.fd).read_exact(buf)
|
||||
fn write(&self, buf: &[u8]) -> syscall::Result<()> {
|
||||
let count = syscall::write(self.fd, buf)?;
|
||||
if count == buf.len() { Ok(()) } else { Err(syscall::Error::new(syscall::EINVAL)) }
|
||||
}
|
||||
|
||||
fn write(&self, buf: &[u8]) -> Result<()> {
|
||||
(&self.fd).write_all(buf)
|
||||
fn fpath<'a>(&self, buf: &'a mut [u8]) -> syscall::Result<&'a str> {
|
||||
let count = syscall::fpath(self.fd, buf)?;
|
||||
str::from_utf8(&buf[..count]).map_err(|_err| syscall::Error::new(syscall::EINVAL))
|
||||
}
|
||||
}
|
||||
|
||||
fn fpath<'a>(&self, buf: &'a mut [u8]) -> Result<&'a str> {
|
||||
let count = libredox::call::fpath(self.fd(), buf)?;
|
||||
str::from_utf8(&buf[..count])
|
||||
.map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData))
|
||||
impl Drop for RedoxSocket {
|
||||
fn drop(&mut self) {
|
||||
let _ = syscall::close(self.fd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,36 +63,26 @@ impl RedoxSocket {
|
||||
struct TimeSocket(RedoxSocket);
|
||||
|
||||
impl TimeSocket {
|
||||
fn open() -> Result<Self> {
|
||||
fn open() -> syscall::Result<Self> {
|
||||
RedoxSocket::open_raw("/scheme/time/4").map(Self)
|
||||
}
|
||||
|
||||
// Read current time.
|
||||
fn current_time(&self) -> Result<TimeSpec> {
|
||||
let mut timespec: libredox::data::TimeSpec = unsafe { mem::zeroed() };
|
||||
let timespec_bytes = unsafe {
|
||||
slice::from_raw_parts_mut(
|
||||
&mut timespec as *mut _ as *mut u8,
|
||||
mem::size_of::<TimeSpec>(),
|
||||
)
|
||||
};
|
||||
self.0.read(timespec_bytes)?;
|
||||
fn current_time(&self) -> syscall::Result<syscall::TimeSpec> {
|
||||
let mut timespec = syscall::TimeSpec::default();
|
||||
self.0.read(&mut timespec)?;
|
||||
Ok(timespec)
|
||||
}
|
||||
|
||||
// Write a timeout.
|
||||
fn timeout(&self, timespec: &TimeSpec) -> Result<()> {
|
||||
let timespec_bytes = unsafe {
|
||||
slice::from_raw_parts(timespec as *const _ as *const u8, mem::size_of::<TimeSpec>())
|
||||
};
|
||||
self.0.write(timespec_bytes)
|
||||
fn timeout(&self, timespec: &syscall::TimeSpec) -> syscall::Result<()> {
|
||||
self.0.write(timespec)
|
||||
}
|
||||
|
||||
// Wake immediately.
|
||||
fn wake(&self) -> Result<()> {
|
||||
fn wake(&self) -> syscall::Result<()> {
|
||||
// Writing a default TimeSpec will always trigger a time event.
|
||||
let timespec: TimeSpec = unsafe { mem::zeroed() };
|
||||
self.timeout(×pec)
|
||||
self.timeout(&syscall::TimeSpec::default())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +97,7 @@ struct WindowProperties<'a> {
|
||||
|
||||
impl<'a> WindowProperties<'a> {
|
||||
fn new(path: &'a str) -> Self {
|
||||
// /scheme/orbital/flags/x/y/w/h/t
|
||||
// orbital:flags/x/y/w/h/t
|
||||
let mut parts = path.splitn(6, '/');
|
||||
let flags = parts.next().unwrap_or("");
|
||||
let x = parts.next().map_or(0, |part| part.parse::<i32>().unwrap_or(0));
|
||||
|
||||
@@ -3,13 +3,12 @@ use std::iter;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use redox_event::EventFlags;
|
||||
use winit_core::cursor::Cursor;
|
||||
use winit_core::error::{NotSupportedError, RequestError};
|
||||
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
|
||||
use winit_core::window::{self, Window as CoreWindow, WindowId};
|
||||
|
||||
use crate::event_loop::{ActiveEventLoop, EventLoopProxy, EventSource};
|
||||
use crate::event_loop::{ActiveEventLoop, EventLoopProxy};
|
||||
use crate::{RedoxSocket, WindowProperties};
|
||||
|
||||
// These values match the values uses in the `window_new` function in orbital:
|
||||
@@ -104,7 +103,13 @@ impl Window {
|
||||
.expect("failed to open window");
|
||||
|
||||
// Add to event socket.
|
||||
el.event_socket.subscribe(window.fd(), EventSource::Orbital, EventFlags::READ).unwrap();
|
||||
el.event_socket
|
||||
.write(&syscall::Event {
|
||||
id: window.fd,
|
||||
flags: syscall::EventFlags::EVENT_READ,
|
||||
data: window.fd,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let window_socket = Arc::new(window);
|
||||
|
||||
@@ -141,7 +146,7 @@ impl Window {
|
||||
#[inline]
|
||||
fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
let handle = rwh_06::OrbitalWindowHandle::new({
|
||||
let window = self.window_socket.fd() as *mut _;
|
||||
let window = self.window_socket.fd as *mut _;
|
||||
std::ptr::NonNull::new(window).expect("orbital fd should never be null")
|
||||
});
|
||||
Ok(rwh_06::RawWindowHandle::Orbital(handle))
|
||||
@@ -155,7 +160,7 @@ impl Window {
|
||||
|
||||
impl CoreWindow for Window {
|
||||
fn id(&self) -> WindowId {
|
||||
WindowId::from_raw(self.window_socket.fd())
|
||||
WindowId::from_raw(self.window_socket.fd)
|
||||
}
|
||||
|
||||
fn ime_capabilities(&self) -> Option<window::ImeCapabilities> {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2::{ClassType, MainThreadMarker, msg_send};
|
||||
use objc2_core_foundation::{CFIndex, CFRunLoopActivity, kCFRunLoopDefaultMode};
|
||||
use objc2_core_foundation::{
|
||||
CFIndex, CFRunLoop, CFRunLoopActivity, CFRunLoopObserver, kCFRunLoopDefaultMode,
|
||||
};
|
||||
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
|
||||
use objc2_ui_kit::{
|
||||
UIApplication, UIApplicationDidBecomeActiveNotification,
|
||||
@@ -12,7 +16,6 @@ use objc2_ui_kit::{
|
||||
UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen,
|
||||
};
|
||||
use rwh_06::HasDisplayHandle;
|
||||
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver};
|
||||
use winit_core::application::ApplicationHandler;
|
||||
use winit_core::cursor::{CustomCursor, CustomCursorSource};
|
||||
use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
|
||||
@@ -133,10 +136,6 @@ pub struct EventLoop {
|
||||
_did_enter_background_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
|
||||
_wakeup_observer: MainRunLoopObserver,
|
||||
_main_events_cleared_observer: MainRunLoopObserver,
|
||||
_events_cleared_observer: MainRunLoopObserver,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
@@ -152,6 +151,9 @@ impl EventLoop {
|
||||
return Err(EventLoopError::RecreationAttempt);
|
||||
}
|
||||
|
||||
// this line sets up the main run loop before `UIApplicationMain`
|
||||
setup_control_flow_observers();
|
||||
|
||||
let center = NSNotificationCenter::defaultCenter();
|
||||
|
||||
let _did_finish_launching_observer = create_observer(
|
||||
@@ -223,50 +225,6 @@ impl EventLoop {
|
||||
move |_| app_state::handle_memory_warning(mtm),
|
||||
);
|
||||
|
||||
let main_loop = MainRunLoop::get(mtm);
|
||||
let mode = unsafe { kCFRunLoopDefaultMode }.unwrap();
|
||||
|
||||
let _wakeup_observer = MainRunLoopObserver::new(
|
||||
mtm,
|
||||
CFRunLoopActivity::AfterWaiting,
|
||||
true,
|
||||
// Queued with the highest priority to ensure it is processed before other observers.
|
||||
CFIndex::MIN,
|
||||
move |_| app_state::handle_wakeup_transition(mtm),
|
||||
);
|
||||
main_loop.add_observer(&_wakeup_observer, mode);
|
||||
|
||||
let _main_events_cleared_observer = MainRunLoopObserver::new(
|
||||
mtm,
|
||||
CFRunLoopActivity::BeforeWaiting,
|
||||
true,
|
||||
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
|
||||
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the
|
||||
// main_end priority to be 0, in order to send `AboutToWait` before `RedrawRequested`.
|
||||
// This value was chosen conservatively to guard against apple using different
|
||||
// priorities for their redraw observers in different OS's or on different devices. If
|
||||
// it so happens that it's too conservative, the main symptom would be non-redraw
|
||||
// events coming in after `AboutToWait`.
|
||||
//
|
||||
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
|
||||
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
|
||||
//
|
||||
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
|
||||
0,
|
||||
move |_| app_state::handle_main_events_cleared(mtm),
|
||||
);
|
||||
main_loop.add_observer(&_main_events_cleared_observer, mode);
|
||||
|
||||
let _events_cleared_observer = MainRunLoopObserver::new(
|
||||
mtm,
|
||||
CFRunLoopActivity::BeforeWaiting,
|
||||
true,
|
||||
// Queued with the lowest priority to ensure it is processed after other observers.
|
||||
CFIndex::MAX,
|
||||
move |_| app_state::handle_events_cleared(mtm),
|
||||
);
|
||||
main_loop.add_observer(&_events_cleared_observer, mode);
|
||||
|
||||
Ok(EventLoop {
|
||||
mtm,
|
||||
window_target: ActiveEventLoop { mtm },
|
||||
@@ -277,14 +235,10 @@ impl EventLoop {
|
||||
_did_enter_background_observer,
|
||||
_will_terminate_observer,
|
||||
_did_receive_memory_warning_observer,
|
||||
_wakeup_observer,
|
||||
_main_events_cleared_observer,
|
||||
_events_cleared_observer,
|
||||
})
|
||||
}
|
||||
|
||||
// Require `'static` for correctness, we won't be able to `Drop` the user's state otherwise.
|
||||
pub fn run_app_never_return<A: ApplicationHandler + 'static>(self, app: A) -> ! {
|
||||
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
|
||||
let application: Option<Retained<UIApplication>> =
|
||||
unsafe { msg_send![UIApplication::class(), sharedApplication] };
|
||||
assert!(
|
||||
@@ -296,10 +250,6 @@ impl EventLoop {
|
||||
|
||||
// We intentionally override neither the application nor the delegate,
|
||||
// to allow the user to do so themselves!
|
||||
//
|
||||
// NOTE: `UIApplicationMain` is _the only way_ to start the event loop on iOS/UIKit, there
|
||||
// are no other feasible options. See also:
|
||||
// <https://github.com/rust-windowing/winit/pull/4165#issuecomment-2760514167>
|
||||
app_state::launch(self.mtm, app, || UIApplication::main(None, None, self.mtm))
|
||||
}
|
||||
|
||||
@@ -307,3 +257,97 @@ impl EventLoop {
|
||||
&self.window_target
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_control_flow_observers() {
|
||||
unsafe {
|
||||
// begin is queued with the highest priority to ensure it is processed before other
|
||||
// observers
|
||||
extern "C-unwind" fn control_flow_begin_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
CFRunLoopActivity::AfterWaiting => app_state::handle_wakeup_transition(mtm),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
|
||||
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
|
||||
// priority to be 0, in order to send AboutToWait before RedrawRequested. This value was
|
||||
// chosen conservatively to guard against apple using different priorities for their redraw
|
||||
// observers in different OS's or on different devices. If it so happens that it's too
|
||||
// conservative, the main symptom would be non-redraw events coming in after `AboutToWait`.
|
||||
//
|
||||
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
|
||||
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
|
||||
//
|
||||
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
|
||||
extern "C-unwind" fn control_flow_main_end_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
CFRunLoopActivity::BeforeWaiting => app_state::handle_main_events_cleared(mtm),
|
||||
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
||||
extern "C-unwind" fn control_flow_end_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
CFRunLoopActivity::BeforeWaiting => app_state::handle_events_cleared(mtm),
|
||||
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
let main_loop = CFRunLoop::main().unwrap();
|
||||
|
||||
let begin_observer = CFRunLoopObserver::new(
|
||||
None,
|
||||
CFRunLoopActivity::AfterWaiting.0,
|
||||
true,
|
||||
CFIndex::MIN,
|
||||
Some(control_flow_begin_handler),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
.unwrap();
|
||||
main_loop.add_observer(Some(&begin_observer), kCFRunLoopDefaultMode);
|
||||
|
||||
let main_end_observer = CFRunLoopObserver::new(
|
||||
None,
|
||||
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
|
||||
true,
|
||||
0, // see comment on `control_flow_main_end_handler`
|
||||
Some(control_flow_main_end_handler),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
.unwrap();
|
||||
main_loop.add_observer(Some(&main_end_observer), kCFRunLoopDefaultMode);
|
||||
|
||||
let end_observer = CFRunLoopObserver::new(
|
||||
None,
|
||||
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
|
||||
true,
|
||||
CFIndex::MAX,
|
||||
Some(control_flow_end_handler),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
.unwrap();
|
||||
main_loop.add_observer(Some(&end_observer), kCFRunLoopDefaultMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ use objc2_ui_kit::{
|
||||
};
|
||||
use tracing::debug;
|
||||
use winit_core::event::{
|
||||
ButtonSource, ElementState, FingerId, Force, KeyEvent, PointerKind, PointerSource,
|
||||
TabletToolAngle, TabletToolButton, TabletToolData, TabletToolKind, TouchPhase, WindowEvent,
|
||||
ButtonSource, ElementState, FingerId, Force, KeyEvent, PointerKind, PointerSource, TouchPhase,
|
||||
WindowEvent,
|
||||
};
|
||||
use winit_core::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey};
|
||||
|
||||
@@ -461,18 +461,19 @@ impl WinitView {
|
||||
let window = self.window().unwrap();
|
||||
let mut touch_events = Vec::new();
|
||||
for touch in touches {
|
||||
if let UITouchType::Pencil = touch.r#type() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let logical_location = touch.locationInView(None);
|
||||
let touch_type = touch.r#type();
|
||||
let force = if available!(ios = 9.0, tvos = 9.0, visionos = 1.0) {
|
||||
let use_force = match touch_type {
|
||||
UITouchType::Pencil => true,
|
||||
_ => {
|
||||
let trait_collection = self.traitCollection();
|
||||
trait_collection.forceTouchCapability() == UIForceTouchCapability::Available
|
||||
},
|
||||
};
|
||||
|
||||
if use_force {
|
||||
let force = if let UITouchType::Pencil = touch_type {
|
||||
None
|
||||
} else if available!(ios = 9.0, tvos = 9.0, visionos = 1.0) {
|
||||
let trait_collection = self.traitCollection();
|
||||
let touch_capability = trait_collection.forceTouchCapability();
|
||||
// Both the OS _and_ the device need to be checked for force touch support.
|
||||
if touch_capability == UIForceTouchCapability::Available {
|
||||
let force = touch.force();
|
||||
let max_possible_force = touch.maximumPossibleForce();
|
||||
Some(Force::Calibrated {
|
||||
@@ -528,7 +529,7 @@ impl WinitView {
|
||||
primary,
|
||||
position,
|
||||
kind: if let UITouchType::Pencil = touch_type {
|
||||
PointerKind::TabletTool(TabletToolKind::Pencil)
|
||||
PointerKind::Unknown
|
||||
} else {
|
||||
PointerKind::Touch(finger_id)
|
||||
},
|
||||
@@ -542,12 +543,7 @@ impl WinitView {
|
||||
state: ElementState::Pressed,
|
||||
position,
|
||||
button: if let UITouchType::Pencil = touch_type {
|
||||
let tool_data = self.tablet_tool_data_for_pencil(&touch);
|
||||
ButtonSource::TabletTool {
|
||||
kind: TabletToolKind::Pencil,
|
||||
button: TabletToolButton::Contact,
|
||||
data: tool_data,
|
||||
}
|
||||
ButtonSource::Unknown(0)
|
||||
} else {
|
||||
ButtonSource::Touch { finger_id, force }
|
||||
},
|
||||
@@ -556,11 +552,7 @@ impl WinitView {
|
||||
},
|
||||
UITouchPhase::Moved => {
|
||||
let (primary, source) = if let UITouchType::Pencil = touch_type {
|
||||
let tool_data = self.tablet_tool_data_for_pencil(&touch);
|
||||
(true, PointerSource::TabletTool {
|
||||
kind: TabletToolKind::Pencil,
|
||||
data: tool_data,
|
||||
})
|
||||
(true, PointerSource::Unknown)
|
||||
} else {
|
||||
(ivars.primary_finger.get().unwrap() == finger_id, PointerSource::Touch {
|
||||
finger_id,
|
||||
@@ -600,12 +592,7 @@ impl WinitView {
|
||||
state: ElementState::Released,
|
||||
position,
|
||||
button: if let UITouchType::Pencil = touch_type {
|
||||
let tool_data = self.tablet_tool_data_for_pencil(&touch);
|
||||
ButtonSource::TabletTool {
|
||||
kind: TabletToolKind::Pencil,
|
||||
button: TabletToolButton::Contact,
|
||||
data: tool_data,
|
||||
}
|
||||
ButtonSource::Unknown(0)
|
||||
} else {
|
||||
ButtonSource::Touch { finger_id, force }
|
||||
},
|
||||
@@ -620,7 +607,7 @@ impl WinitView {
|
||||
primary,
|
||||
position: Some(position),
|
||||
kind: if let UITouchType::Pencil = touch_type {
|
||||
PointerKind::TabletTool(TabletToolKind::Pencil)
|
||||
PointerKind::Unknown
|
||||
} else {
|
||||
PointerKind::Touch(finger_id)
|
||||
},
|
||||
@@ -634,32 +621,6 @@ impl WinitView {
|
||||
app_state::handle_nonuser_events(mtm, touch_events);
|
||||
}
|
||||
|
||||
fn tablet_tool_data_for_pencil(&self, touch: &UITouch) -> TabletToolData {
|
||||
let force = if available!(ios = 9.0, tvos = 9.0, visionos = 1.0) {
|
||||
let force_val = touch.force();
|
||||
let max_force = touch.maximumPossibleForce();
|
||||
Some(Force::Calibrated { force: force_val as _, max_possible_force: max_force as _ })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let angle = if available!(ios = 9.1, tvos = 9.0, visionos = 1.0) {
|
||||
let altitude = touch.altitudeAngle();
|
||||
let azimuth = touch.azimuthAngleInView(Some(self));
|
||||
Some(TabletToolAngle { altitude: altitude as _, azimuth: azimuth as _ })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
TabletToolData {
|
||||
force,
|
||||
tangential_force: None, // iOS doesn't provide barrel pressure
|
||||
twist: None, // iOS doesn't provide rotation/twist
|
||||
tilt: None, // Will be calculated from angle if needed
|
||||
angle,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_insert_text(&self, text: &NSString) {
|
||||
let window = self.window().unwrap();
|
||||
let window_id = window.id();
|
||||
|
||||
@@ -29,8 +29,8 @@ tracing.workspace = true
|
||||
winit-core.workspace = true
|
||||
|
||||
# Platform-specific
|
||||
ahash.workspace = true
|
||||
calloop.workspace = true
|
||||
foldhash.workspace = true
|
||||
libc.workspace = true
|
||||
memmap2.workspace = true
|
||||
rustix = { workspace = true, features = ["std", "system", "thread", "process", "event", "pipe"] }
|
||||
|
||||
@@ -169,6 +169,10 @@ impl EventLoop {
|
||||
Ok(event_loop)
|
||||
}
|
||||
|
||||
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
|
||||
self.run_app_on_demand(app)
|
||||
}
|
||||
|
||||
pub fn run_app_on_demand<A: ApplicationHandler>(
|
||||
&mut self,
|
||||
mut app: A,
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
//! * `wayland-csd-adwaita-crossfont`.
|
||||
//! * `wayland-csd-adwaita-notitle`.
|
||||
//! * `wayland-csd-adwaita-notitlebar`.
|
||||
|
||||
#![allow(clippy::mutable_key_type)]
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use foldhash::HashMap;
|
||||
use ahash::AHashMap;
|
||||
use sctk::reexports::client::backend::ObjectId;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_touch::WlTouch;
|
||||
@@ -43,7 +43,7 @@ pub struct WinitSeatState {
|
||||
touch: Option<WlTouch>,
|
||||
|
||||
/// The mapping from touched points to the surfaces they're present.
|
||||
touch_map: HashMap<i32, TouchPoint>,
|
||||
touch_map: AHashMap<i32, TouchPoint>,
|
||||
|
||||
/// Id of the first touch event.
|
||||
first_touch_id: Option<i32>,
|
||||
|
||||
@@ -150,7 +150,7 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
|
||||
// 6. Place cursor inside preedit text.
|
||||
|
||||
if let Some(DeleteSurroundingText { before, after }) =
|
||||
text_input_data.pending_delete.take()
|
||||
text_input_data.pending_delete
|
||||
{
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::DeleteSurrounding {
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::cell::RefCell;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use foldhash::HashMap;
|
||||
use ahash::AHashMap;
|
||||
use sctk::compositor::{CompositorHandler, CompositorState};
|
||||
use sctk::output::{OutputHandler, OutputState};
|
||||
use sctk::reexports::calloop::LoopHandle;
|
||||
@@ -62,10 +62,10 @@ pub struct WinitState {
|
||||
pub xdg_shell: XdgShell,
|
||||
|
||||
/// The currently present windows.
|
||||
pub windows: RefCell<HashMap<WindowId, Arc<Mutex<WindowState>>>>,
|
||||
pub windows: RefCell<AHashMap<WindowId, Arc<Mutex<WindowState>>>>,
|
||||
|
||||
/// The requests from the `Window` to EventLoop, such as close operations and redraw requests.
|
||||
pub window_requests: RefCell<HashMap<WindowId, Arc<WindowRequests>>>,
|
||||
pub window_requests: RefCell<AHashMap<WindowId, Arc<WindowRequests>>>,
|
||||
|
||||
/// The events that were generated directly from the window.
|
||||
pub window_events_sink: Arc<Mutex<EventSink>>,
|
||||
@@ -74,10 +74,10 @@ pub struct WinitState {
|
||||
pub window_compositor_updates: Vec<WindowCompositorUpdate>,
|
||||
|
||||
/// Currently handled seats.
|
||||
pub seats: HashMap<ObjectId, WinitSeatState>,
|
||||
pub seats: AHashMap<ObjectId, WinitSeatState>,
|
||||
|
||||
/// Currently present cursor surfaces.
|
||||
pub pointer_surfaces: HashMap<ObjectId, Arc<ThemedPointer<WinitPointerData>>>,
|
||||
pub pointer_surfaces: AHashMap<ObjectId, Arc<ThemedPointer<WinitPointerData>>>,
|
||||
|
||||
/// The state of the text input on the client.
|
||||
pub text_input_state: Option<TextInputState>,
|
||||
@@ -156,7 +156,7 @@ impl WinitState {
|
||||
|
||||
let seat_state = SeatState::new(globals, queue_handle);
|
||||
|
||||
let mut seats = HashMap::default();
|
||||
let mut seats = AHashMap::default();
|
||||
for seat in seat_state.seats() {
|
||||
seats.insert(seat.id(), WinitSeatState::new());
|
||||
}
|
||||
|
||||
@@ -21,13 +21,6 @@ use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_tool_v2::{
|
||||
ButtonState, Event as ToolEvent, Type as ToolType, ZwpTabletToolV2,
|
||||
};
|
||||
use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_v2::ZwpTabletV2;
|
||||
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_pad_dial_v2::ZwpTabletPadDialV2;
|
||||
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_pad_group_v2::{
|
||||
self, ZwpTabletPadGroupV2,
|
||||
};
|
||||
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2;
|
||||
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2;
|
||||
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_pad_v2;
|
||||
use winit_core::event::{
|
||||
ButtonSource, ElementState, Force, PointerKind, PointerSource, TabletToolButton,
|
||||
TabletToolData as CoreTabletToolData, TabletToolKind, TabletToolTilt, WindowEvent,
|
||||
@@ -331,10 +324,6 @@ impl Dispatch<ZwpTabletV2, (), WinitState> for TabletManager {
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTabletPadV2, (), WinitState> for TabletManager {
|
||||
event_created_child!(WinitState, ZwpTabletPadV2, [
|
||||
zwp_tablet_pad_v2::EVT_GROUP_OPCODE => (ZwpTabletPadGroupV2, Default::default()),
|
||||
]);
|
||||
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &ZwpTabletPadV2,
|
||||
@@ -346,67 +335,9 @@ impl Dispatch<ZwpTabletPadV2, (), WinitState> for TabletManager {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTabletPadGroupV2, (), WinitState> for TabletManager {
|
||||
event_created_child!(WinitState, ZwpTabletPadGroupV2, [
|
||||
zwp_tablet_pad_group_v2::EVT_RING_OPCODE => (ZwpTabletPadRingV2, Default::default()),
|
||||
zwp_tablet_pad_group_v2::EVT_STRIP_OPCODE => (ZwpTabletPadStripV2, Default::default()),
|
||||
zwp_tablet_pad_group_v2::EVT_DIAL_OPCODE => (ZwpTabletPadDialV2, Default::default()),
|
||||
]);
|
||||
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &ZwpTabletPadGroupV2,
|
||||
_: <ZwpTabletPadGroupV2 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTabletPadRingV2, (), WinitState> for TabletManager {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &ZwpTabletPadRingV2,
|
||||
_: <ZwpTabletPadRingV2 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTabletPadStripV2, (), WinitState> for TabletManager {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &ZwpTabletPadStripV2,
|
||||
_: <ZwpTabletPadStripV2 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTabletPadDialV2, (), WinitState> for TabletManager {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &ZwpTabletPadDialV2,
|
||||
_: <ZwpTabletPadDialV2 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [ZwpTabletManagerV2: GlobalData] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletManagerV2: ()] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletSeatV2: ()] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletV2: ()] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletToolV2: TabletToolData] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletPadV2: ()] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletPadGroupV2: ()] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletPadRingV2: ()] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletPadStripV2: ()] => TabletManager);
|
||||
delegate_dispatch!(WinitState: [ZwpTabletPadDialV2: ()] => TabletManager);
|
||||
|
||||
@@ -171,12 +171,6 @@ impl Window {
|
||||
Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
|
||||
}
|
||||
|
||||
// Apply resize increments.
|
||||
if let Some(increments) = attributes.surface_resize_increments {
|
||||
let increments = increments.to_logical(window_state.scale_factor());
|
||||
window_state.set_resize_increments(Some(increments));
|
||||
}
|
||||
|
||||
// Activate the window when the token is passed.
|
||||
if let (Some(xdg_activation), Some(token)) = (xdg_activation.as_ref(), activation_token) {
|
||||
xdg_activation.activate(token.into_raw(), &surface);
|
||||
@@ -376,18 +370,11 @@ impl CoreWindow for Window {
|
||||
}
|
||||
|
||||
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
window_state
|
||||
.resize_increments()
|
||||
.map(|size| super::logical_to_physical_rounded(size, scale_factor))
|
||||
None
|
||||
}
|
||||
|
||||
fn set_surface_resize_increments(&self, increments: Option<Size>) {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
let increments = increments.map(|size| size.to_logical(scale_factor));
|
||||
window_state.set_resize_increments(increments);
|
||||
fn set_surface_resize_increments(&self, _increments: Option<Size>) {
|
||||
warn!("`set_surface_resize_increments` is not implemented for Wayland");
|
||||
}
|
||||
|
||||
fn set_title(&self, title: &str) {
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::num::NonZeroU32;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use ahash::HashSet;
|
||||
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Size};
|
||||
use foldhash::HashSet;
|
||||
use sctk::compositor::{CompositorState, Region, SurfaceData, SurfaceDataExt};
|
||||
use sctk::globals::GlobalData;
|
||||
use sctk::reexports::client::backend::ObjectId;
|
||||
@@ -140,7 +140,6 @@ pub struct WindowState {
|
||||
/// Min size.
|
||||
min_surface_size: LogicalSize<u32>,
|
||||
max_surface_size: Option<LogicalSize<u32>>,
|
||||
resize_increments: Option<LogicalSize<u32>>,
|
||||
|
||||
/// The size of the window when no states were applied to it. The primary use for it
|
||||
/// is to fallback to original window size, before it was maximized, if the compositor
|
||||
@@ -224,7 +223,6 @@ impl WindowState {
|
||||
last_configure: None,
|
||||
max_surface_size: None,
|
||||
min_surface_size: MIN_WINDOW_SIZE,
|
||||
resize_increments: None,
|
||||
pointer_constraints,
|
||||
pointers: Default::default(),
|
||||
queue_handle: queue_handle.clone(),
|
||||
@@ -363,42 +361,6 @@ impl WindowState {
|
||||
.unwrap_or(new_size.height);
|
||||
}
|
||||
|
||||
// Apply size increments.
|
||||
//
|
||||
// We conditionally apply increments to avoid conflicts with the compositor's layout rules:
|
||||
// 1. If the window is floating (constrain == true), we snap to increments to ensure the
|
||||
// app's grid alignment.
|
||||
// 2. If the user is interactively resizing (is_resizing), we snap the size to provide
|
||||
// feedback.
|
||||
//
|
||||
// However, we MUST NOT snap if the compositor enforces a specific size (constrain == false,
|
||||
// or states like Maximized/Tiled). Snapping in these cases (e.g. corner tiling) would
|
||||
// shrink the window below the allocated area, creating visible gaps between valid
|
||||
// windows or screen edges.
|
||||
if (constrain || configure.is_resizing())
|
||||
&& !configure.is_maximized()
|
||||
&& !configure.is_fullscreen()
|
||||
&& !configure.is_tiled()
|
||||
{
|
||||
if let Some(increments) = self.resize_increments {
|
||||
// We use min size as a base size for the increments, similar to how X11 does it.
|
||||
//
|
||||
// This ensures that we can always reach the min size and the increments are
|
||||
// calculated from it.
|
||||
let (delta_width, delta_height) = (
|
||||
new_size.width.saturating_sub(self.min_surface_size.width),
|
||||
new_size.height.saturating_sub(self.min_surface_size.height),
|
||||
);
|
||||
|
||||
let width = self.min_surface_size.width
|
||||
+ (delta_width / increments.width) * increments.width;
|
||||
let height = self.min_surface_size.height
|
||||
+ (delta_height / increments.height) * increments.height;
|
||||
|
||||
new_size = (width, height).into();
|
||||
}
|
||||
}
|
||||
|
||||
let new_state = configure.state;
|
||||
let old_state = self.last_configure.as_ref().map(|configure| configure.state);
|
||||
|
||||
@@ -787,18 +749,6 @@ impl WindowState {
|
||||
self.selected_cursor = SelectedCursor::Custom(cursor);
|
||||
}
|
||||
|
||||
/// Set the resize increments of the window.
|
||||
pub fn set_resize_increments(&mut self, increments: Option<LogicalSize<u32>>) {
|
||||
self.resize_increments = increments;
|
||||
// NOTE: We don't update the window size here, because it will be done on the next resize
|
||||
// or configure event.
|
||||
}
|
||||
|
||||
/// Get the resize increments of the window.
|
||||
pub fn resize_increments(&self) -> Option<LogicalSize<u32>> {
|
||||
self.resize_increments
|
||||
}
|
||||
|
||||
fn apply_custom_cursor(&self, cursor: &CustomCursor) {
|
||||
self.apply_on_pointer(|pointer, data| {
|
||||
let surface = pointer.surface();
|
||||
|
||||
@@ -39,8 +39,32 @@ impl EventLoop {
|
||||
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn register_app<A: ApplicationHandler + 'static>(self, app: A) {
|
||||
self.elw.run(Box::new(app));
|
||||
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
|
||||
let app = Box::new(app);
|
||||
|
||||
// SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe
|
||||
// because this function will never return and all resources not cleaned up by the point we
|
||||
// `throw` will leak, making this actually `'static`.
|
||||
let app = unsafe {
|
||||
std::mem::transmute::<
|
||||
Box<dyn ApplicationHandler + '_>,
|
||||
Box<dyn ApplicationHandler + 'static>,
|
||||
>(app)
|
||||
};
|
||||
|
||||
self.elw.run(app, false);
|
||||
|
||||
// Throw an exception to break out of Rust execution and use unreachable to tell the
|
||||
// compiler this function won't return, giving it a return type of '!'
|
||||
backend::throw(
|
||||
"Using exceptions for control flow, don't mind me. This isn't actually an error!",
|
||||
);
|
||||
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
pub fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
|
||||
self.elw.run(Box::new(app), true);
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
|
||||
|
||||
@@ -48,6 +48,7 @@ struct Execution {
|
||||
exit: Cell<bool>,
|
||||
runner: RefCell<RunnerEnum>,
|
||||
suspended: Cell<bool>,
|
||||
event_loop_recreation: Cell<bool>,
|
||||
events: RefCell<VecDeque<Event>>,
|
||||
id: Cell<usize>,
|
||||
window: web_sys::Window,
|
||||
@@ -194,6 +195,7 @@ impl Shared {
|
||||
exit: Cell::new(false),
|
||||
runner: RefCell::new(RunnerEnum::Pending),
|
||||
suspended: Cell::new(false),
|
||||
event_loop_recreation: Cell::new(false),
|
||||
events: RefCell::new(VecDeque::new()),
|
||||
window,
|
||||
navigator,
|
||||
@@ -770,7 +772,9 @@ impl Shared {
|
||||
// * For each undropped `Window`:
|
||||
// * The `register_redraw_request` closure.
|
||||
// * The `destroy_fn` closure.
|
||||
EventLoop::allow_event_loop_recreation();
|
||||
if self.0.event_loop_recreation.get() {
|
||||
EventLoop::allow_event_loop_recreation();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the event loop is currently closed
|
||||
@@ -805,6 +809,10 @@ impl Shared {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event_loop_recreation(&self, allow: bool) {
|
||||
self.0.event_loop_recreation.set(allow)
|
||||
}
|
||||
|
||||
pub(crate) fn control_flow(&self) -> ControlFlow {
|
||||
self.0.control_flow.get()
|
||||
}
|
||||
|
||||
@@ -56,7 +56,8 @@ impl ActiveEventLoop {
|
||||
Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() }
|
||||
}
|
||||
|
||||
pub(crate) fn run(&self, app: Box<dyn ApplicationHandler>) {
|
||||
pub(crate) fn run(&self, app: Box<dyn ApplicationHandler>, event_loop_recreation: bool) {
|
||||
self.runner.event_loop_recreation(event_loop_recreation);
|
||||
self.runner.start(app, self.clone());
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ use std::task::{Context, Poll};
|
||||
use ::web_sys::HtmlCanvasElement;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use winit_core::application::ApplicationHandler;
|
||||
use winit_core::cursor::{CustomCursor, CustomCursorSource};
|
||||
use winit_core::error::NotSupportedError;
|
||||
use winit_core::event_loop::ActiveEventLoop;
|
||||
@@ -236,6 +237,30 @@ impl Default for WindowAttributesWeb {
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to the Web.
|
||||
pub trait EventLoopExtWeb {
|
||||
/// Initializes the winit event loop.
|
||||
///
|
||||
/// Unlike
|
||||
#[cfg_attr(target_feature = "exception-handling", doc = "`run_app()`")]
|
||||
#[cfg_attr(
|
||||
not(target_feature = "exception-handling"),
|
||||
doc = "[`run_app()`]"
|
||||
)]
|
||||
/// [^1], this returns immediately, and doesn't throw an exception in order to
|
||||
/// satisfy its [`!`] return type.
|
||||
///
|
||||
/// Once the event loop has been destroyed, it's possible to reinitialize another event loop
|
||||
/// by calling this function again. This can be useful if you want to recreate the event loop
|
||||
/// while the WebAssembly module is still loaded. For example, this can be used to recreate the
|
||||
/// event loop when switching between tabs on a single page application.
|
||||
#[rustfmt::skip]
|
||||
///
|
||||
#[cfg_attr(
|
||||
not(target_feature = "exception-handling"),
|
||||
doc = "[`run_app()`]: EventLoop::run_app()"
|
||||
)]
|
||||
/// [^1]: `run_app()` is _not_ available on Wasm when the target supports `exception-handling`.
|
||||
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A);
|
||||
|
||||
/// Sets the strategy for [`ControlFlow::Poll`].
|
||||
///
|
||||
/// See [`PollStrategy`].
|
||||
|
||||
@@ -25,6 +25,10 @@ pub use self::resize_scaling::ResizeScaleHandle;
|
||||
pub use self::safe_area::SafeAreaHandle;
|
||||
pub use self::schedule::Schedule;
|
||||
|
||||
pub fn throw(msg: &str) {
|
||||
wasm_bindgen::throw_str(msg);
|
||||
}
|
||||
|
||||
pub struct PageTransitionEventHandle {
|
||||
_show_listener: event_handle::EventListenerHandle<dyn FnMut(PageTransitionEvent)>,
|
||||
_hide_listener: event_handle::EventListenerHandle<dyn FnMut(PageTransitionEvent)>,
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::LazyLock;
|
||||
/// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode
|
||||
use std::{ffi::c_void, ptr};
|
||||
|
||||
use windows_sys::Win32::Foundation::{HWND, LPARAM, S_OK, WPARAM};
|
||||
use windows_sys::Win32::Foundation::{BOOL, HWND, LPARAM, S_OK, WPARAM};
|
||||
use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA};
|
||||
use windows_sys::Win32::UI::Accessibility::{HCF_HIGHCONTRASTON, HIGHCONTRASTA};
|
||||
use windows_sys::Win32::UI::Controls::SetWindowTheme;
|
||||
@@ -11,7 +11,7 @@ use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow;
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||
DefWindowProcW, SPI_GETHIGHCONTRAST, SystemParametersInfoA, WM_NCACTIVATE,
|
||||
};
|
||||
use windows_sys::core::{BOOL, PCSTR, PCWSTR};
|
||||
use windows_sys::core::{PCSTR, PCWSTR};
|
||||
use windows_sys::w;
|
||||
use winit_core::window::Theme;
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
use std::ffi::c_void;
|
||||
|
||||
use windows_sys::Win32::Foundation::{HWND, POINTL};
|
||||
use windows_sys::Win32::Foundation::{BOOL, HWND, POINTL};
|
||||
use windows_sys::Win32::System::Com::{FORMATETC, STGMEDIUM};
|
||||
use windows_sys::core::{BOOL, GUID, HRESULT};
|
||||
use windows_sys::core::{GUID, HRESULT};
|
||||
|
||||
pub type IUnknown = *mut c_void;
|
||||
pub type IAdviseSink = *mut c_void;
|
||||
|
||||
@@ -247,6 +247,10 @@ impl EventLoop {
|
||||
ActiveEventLoop::from_ref(&self.runner)
|
||||
}
|
||||
|
||||
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
|
||||
self.run_app_on_demand(app)
|
||||
}
|
||||
|
||||
pub fn run_app_on_demand<A: ApplicationHandler>(
|
||||
&mut self,
|
||||
mut app: A,
|
||||
@@ -528,11 +532,9 @@ fn main_thread_id() -> u32 {
|
||||
//
|
||||
// See: https://doc.rust-lang.org/stable/reference/abi.html#the-link_section-attribute
|
||||
#[unsafe(link_section = ".CRT$XCU")]
|
||||
static INIT_MAIN_THREAD_ID: unsafe extern "C" fn() = {
|
||||
unsafe extern "C" fn initer() {
|
||||
unsafe {
|
||||
MAIN_THREAD_ID = GetCurrentThreadId();
|
||||
}
|
||||
static INIT_MAIN_THREAD_ID: unsafe fn() = {
|
||||
unsafe fn initer() {
|
||||
unsafe { MAIN_THREAD_ID = GetCurrentThreadId() };
|
||||
}
|
||||
initer
|
||||
};
|
||||
@@ -1503,9 +1505,9 @@ unsafe fn public_window_callback_inner(
|
||||
},
|
||||
|
||||
WM_IME_SETCONTEXT => {
|
||||
// IME UI visibility flags are in lparam.
|
||||
let lparam = lparam & !(ISC_SHOWUICOMPOSITIONWINDOW as isize);
|
||||
result = ProcResult::Value(unsafe { DefWindowProcW(window, msg, wparam, lparam) });
|
||||
// Hide composing text drawn by IME.
|
||||
let wparam = wparam & (!ISC_SHOWUICOMPOSITIONWINDOW as usize);
|
||||
result = ProcResult::DefWindowProc(wparam);
|
||||
},
|
||||
|
||||
// this is necessary for us to maintain minimize/restore state
|
||||
|
||||
@@ -4,14 +4,13 @@ use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::{io, iter, mem, ptr};
|
||||
|
||||
use dpi::{PhysicalPosition, PhysicalSize};
|
||||
use windows_sys::Win32::Foundation::{HWND, LPARAM, POINT, RECT};
|
||||
use windows_sys::Win32::Foundation::{BOOL, HWND, LPARAM, POINT, RECT};
|
||||
use windows_sys::Win32::Graphics::Gdi::{
|
||||
DEVMODEW, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_PELSHEIGHT, DM_PELSWIDTH,
|
||||
ENUM_CURRENT_SETTINGS, EnumDisplayMonitors, EnumDisplaySettingsExW, GetMonitorInfoW, HDC,
|
||||
HMONITOR, MONITOR_DEFAULTTONEAREST, MONITOR_DEFAULTTOPRIMARY, MONITORINFO, MONITORINFOEXW,
|
||||
MonitorFromPoint, MonitorFromWindow,
|
||||
};
|
||||
use windows_sys::core::BOOL;
|
||||
use winit_core::monitor::{MonitorHandleProvider, VideoMode};
|
||||
|
||||
use super::util::decode_wide;
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::LazyLock;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{io, mem, ptr};
|
||||
|
||||
use windows_sys::Win32::Foundation::{HANDLE, HMODULE, HWND, NTSTATUS, POINT, RECT};
|
||||
use windows_sys::Win32::Foundation::{BOOL, HANDLE, HMODULE, HWND, NTSTATUS, POINT, RECT};
|
||||
use windows_sys::Win32::Graphics::Gdi::{ClientToScreen, HMONITOR};
|
||||
use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA};
|
||||
use windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW;
|
||||
@@ -23,7 +23,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||
SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SW_MAXIMIZE,
|
||||
ShowCursor, WINDOW_LONG_PTR_INDEX, WINDOWPLACEMENT,
|
||||
};
|
||||
use windows_sys::core::{BOOL, HRESULT, PCWSTR};
|
||||
use windows_sys::core::{HRESULT, PCWSTR};
|
||||
use winit_core::cursor::CursorIcon;
|
||||
use winit_core::event::DeviceId;
|
||||
|
||||
|
||||
@@ -427,6 +427,10 @@ impl EventLoop {
|
||||
&self.event_processor.target
|
||||
}
|
||||
|
||||
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
|
||||
self.run_app_on_demand(app)
|
||||
}
|
||||
|
||||
pub fn run_app_on_demand<A: ApplicationHandler>(
|
||||
&mut self,
|
||||
mut app: A,
|
||||
|
||||
@@ -50,13 +50,8 @@ impl ImeInner {
|
||||
}
|
||||
|
||||
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
|
||||
if !self.is_destroyed {
|
||||
if let Some(im) = &self.im {
|
||||
unsafe { close_im(&self.xconn, im.im) }?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
if !self.is_destroyed && self.im.is_some() {
|
||||
unsafe { close_im(&self.xconn, self.im.as_ref().unwrap().im) }.map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
@@ -60,18 +60,16 @@ impl Iterator for KeymapIter<'_> {
|
||||
|
||||
impl XConnection {
|
||||
pub fn query_keymap(&self) -> Keymap {
|
||||
let keys = self
|
||||
.xcb_connection()
|
||||
.query_keymap()
|
||||
.expect("Failed to query keymap")
|
||||
.reply()
|
||||
.expect("Missing reply")
|
||||
.keys;
|
||||
let mut keys = [0; 32];
|
||||
|
||||
unsafe {
|
||||
(self.xlib.XQueryKeymap)(self.display, keys.as_mut_ptr() as *mut c_char);
|
||||
}
|
||||
|
||||
Keymap { keys }
|
||||
}
|
||||
}
|
||||
|
||||
fn first_bit(b: u8) -> u8 {
|
||||
b & b.wrapping_neg()
|
||||
1 << b.trailing_zeros()
|
||||
}
|
||||
|
||||
@@ -26,9 +26,10 @@ use winit_core::window::{
|
||||
};
|
||||
use x11rb::connection::{Connection, RequestConnection};
|
||||
use x11rb::properties::{WmHints, WmSizeHints, WmSizeHintsSpecification};
|
||||
use x11rb::protocol::shape::{ConnectionExt as ShapeExt, SK, SO};
|
||||
use x11rb::protocol::shape::SK;
|
||||
use x11rb::protocol::sync::{ConnectionExt as _, Int64};
|
||||
use x11rb::protocol::xproto::{self, ClipOrdering, ConnectionExt as _, Rectangle};
|
||||
use x11rb::protocol::xfixes::{ConnectionExt, RegionWrapper};
|
||||
use x11rb::protocol::xproto::{self, ConnectionExt as _, Rectangle};
|
||||
use x11rb::protocol::{randr, xinput};
|
||||
|
||||
use crate::atoms::*;
|
||||
@@ -1966,15 +1967,6 @@ impl UnownedWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
|
||||
// Implement cursor hittest for X11 by either setting an empty or full window input shape.
|
||||
|
||||
// In X11, every window has two "shapes":
|
||||
// * Bounding shape: defines the visible outline of the window.
|
||||
// * Input shape: defines the region of the window that receives pointer/keyboard events.
|
||||
// If the input shape is the full window rectangle, the window behaves normally.
|
||||
// If the input shape is empty, the window is completely click‑through.
|
||||
// Here, we implement hit test by mapping `hittest = true` to "restore a full input shape"
|
||||
// and `hittest = false` to "clear the input shape" (empty list of rectangles).
|
||||
let mut rectangles: Vec<Rectangle> = Vec::new();
|
||||
if hittest {
|
||||
let size = self.surface_size();
|
||||
@@ -1985,17 +1977,11 @@ impl UnownedWindow {
|
||||
height: size.height as u16,
|
||||
})
|
||||
}
|
||||
let region = RegionWrapper::create_region(self.xconn.xcb_connection(), &rectangles)
|
||||
.map_err(|_e| RequestError::Ignored)?;
|
||||
self.xconn
|
||||
.xcb_connection()
|
||||
.shape_rectangles(
|
||||
SO::SET,
|
||||
SK::INPUT,
|
||||
ClipOrdering::UNSORTED,
|
||||
self.xwindow,
|
||||
0,
|
||||
0,
|
||||
&rectangles,
|
||||
)
|
||||
.xfixes_set_window_shape_region(self.xwindow, SK::INPUT, 0, 0, region.region())
|
||||
.map_err(|_e| RequestError::Ignored)?;
|
||||
self.shared_state_lock().cursor_hittest = Some(hittest);
|
||||
Ok(())
|
||||
|
||||
@@ -250,7 +250,9 @@ impl XConnection {
|
||||
// Store the timestamp in the slot if it's greater than the last one.
|
||||
let mut last_timestamp = self.timestamp.load(Ordering::Relaxed);
|
||||
loop {
|
||||
if (timestamp as i32).wrapping_sub(last_timestamp as i32) <= 0 {
|
||||
let wrapping_sub = |a: xproto::Timestamp, b: xproto::Timestamp| (a as i32) - (b as i32);
|
||||
|
||||
if wrapping_sub(timestamp, last_timestamp) <= 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,10 +76,12 @@ winit-core.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
image = { workspace = true, features = ["png"] }
|
||||
softbuffer.workspace = true
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
|
||||
softbuffer.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
winit-android.workspace = true
|
||||
|
||||
|
||||
@@ -7,18 +7,21 @@ use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::fmt::Debug;
|
||||
#[cfg(not(android_platform))]
|
||||
use std::num::NonZeroU32;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::{self, Receiver, Sender};
|
||||
#[cfg(not(web_platform))]
|
||||
#[cfg(all(not(android_platform), not(web_platform)))]
|
||||
use std::time::Instant;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use cursor_icon::CursorIcon;
|
||||
#[cfg(not(android_platform))]
|
||||
use rwh_06::{DisplayHandle, HasDisplayHandle};
|
||||
#[cfg(not(android_platform))]
|
||||
use softbuffer::{Context, Surface};
|
||||
use tracing::{error, info};
|
||||
#[cfg(web_platform)]
|
||||
#[cfg(all(web_platform, not(android_platform)))]
|
||||
use web_time::Instant;
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::cursor::{Cursor, CustomCursor, CustomCursorSource};
|
||||
@@ -94,12 +97,14 @@ struct Application {
|
||||
/// Drawing context.
|
||||
///
|
||||
/// With OpenGL it could be EGLDisplay.
|
||||
#[cfg(not(android_platform))]
|
||||
context: Option<Context<DisplayHandle<'static>>>,
|
||||
}
|
||||
|
||||
impl Application {
|
||||
fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self {
|
||||
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
|
||||
#[cfg(not(android_platform))]
|
||||
let context = Some(
|
||||
Context::new(unsafe {
|
||||
std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
|
||||
@@ -125,7 +130,15 @@ impl Application {
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
Self { receiver, sender, context, custom_cursors, icon, windows: Default::default() }
|
||||
Self {
|
||||
receiver,
|
||||
sender,
|
||||
#[cfg(not(android_platform))]
|
||||
context,
|
||||
custom_cursors,
|
||||
icon,
|
||||
windows: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_window(
|
||||
@@ -238,12 +251,6 @@ impl Application {
|
||||
Action::ToggleSimpleFullscreen => {
|
||||
window.window.set_simple_fullscreen(!window.window.simple_fullscreen());
|
||||
},
|
||||
#[cfg(macos_platform)]
|
||||
Action::ToggleBorderlessGame => {
|
||||
let current = window.window.is_borderless_game();
|
||||
window.window.set_borderless_game(!current);
|
||||
info!("Borderless game: {}", !current);
|
||||
},
|
||||
Action::ToggleMaximize => window.toggle_maximize(),
|
||||
Action::Minimize => window.minimize(),
|
||||
Action::NextCursor => window.next_cursor(),
|
||||
@@ -607,6 +614,7 @@ struct WindowState {
|
||||
/// Render surface.
|
||||
///
|
||||
/// NOTE: This surface must be dropped before the `Window`.
|
||||
#[cfg(not(android_platform))]
|
||||
surface: Surface<DisplayHandle<'static>, Arc<dyn Window>>,
|
||||
/// The actual winit Window.
|
||||
window: Arc<dyn Window>,
|
||||
@@ -615,6 +623,7 @@ struct WindowState {
|
||||
/// Fill the window with animated color
|
||||
animated_fill_color: bool,
|
||||
/// The application start time. Used for color fill animation
|
||||
#[cfg(not(android_platform))]
|
||||
start_time: Instant,
|
||||
/// Redraw continuously
|
||||
continuous_redraw: bool,
|
||||
@@ -650,6 +659,7 @@ impl WindowState {
|
||||
|
||||
// SAFETY: the surface is dropped before the `window` which provided it with handle, thus
|
||||
// it doesn't outlive it.
|
||||
#[cfg(not(android_platform))]
|
||||
let surface = Surface::new(app.context.as_ref().unwrap(), Arc::clone(&window))?;
|
||||
|
||||
let theme = window.theme().unwrap_or(Theme::Dark);
|
||||
@@ -664,12 +674,14 @@ impl WindowState {
|
||||
custom_idx: app.custom_cursors.as_ref().map(Vec::len).unwrap_or(1) - 1,
|
||||
cursor_grab: CursorGrabMode::None,
|
||||
named_idx,
|
||||
#[cfg(not(android_platform))]
|
||||
surface,
|
||||
window,
|
||||
theme,
|
||||
animated_fill_color: false,
|
||||
continuous_redraw: false,
|
||||
emit_surface_size: false,
|
||||
#[cfg(not(android_platform))]
|
||||
start_time: Instant::now(),
|
||||
cursor_position: Default::default(),
|
||||
cursor_hidden: Default::default(),
|
||||
@@ -841,12 +853,15 @@ impl WindowState {
|
||||
/// Resize the surface to the new size.
|
||||
fn resize(&mut self, size: PhysicalSize<u32>) {
|
||||
info!("Surface resized to {size:?}");
|
||||
let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) {
|
||||
(Some(width), Some(height)) => (width, height),
|
||||
_ => return,
|
||||
};
|
||||
self.surface.resize(width, height).expect("failed to resize inner buffer");
|
||||
|
||||
#[cfg(not(android_platform))]
|
||||
{
|
||||
let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height))
|
||||
{
|
||||
(Some(width), Some(height)) => (width, height),
|
||||
_ => return,
|
||||
};
|
||||
self.surface.resize(width, height).expect("failed to resize inner buffer");
|
||||
}
|
||||
self.window.request_redraw();
|
||||
}
|
||||
|
||||
@@ -931,6 +946,7 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Draw the window contents.
|
||||
#[cfg(not(android_platform))]
|
||||
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
if self.occluded {
|
||||
info!("Skipping drawing occluded window={:?}", self.window.id());
|
||||
@@ -976,6 +992,12 @@ impl WindowState {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(android_platform)]
|
||||
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
info!("Drawing but without rendering...");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Binding<T: Eq> {
|
||||
@@ -1005,8 +1027,6 @@ enum Action {
|
||||
ToggleFullscreen,
|
||||
#[cfg(macos_platform)]
|
||||
ToggleSimpleFullscreen,
|
||||
#[cfg(macos_platform)]
|
||||
ToggleBorderlessGame,
|
||||
ToggleMaximize,
|
||||
Minimize,
|
||||
NextCursor,
|
||||
@@ -1044,8 +1064,6 @@ impl Action {
|
||||
Action::ToggleFullscreen => "Toggle fullscreen",
|
||||
#[cfg(macos_platform)]
|
||||
Action::ToggleSimpleFullscreen => "Toggle simple fullscreen",
|
||||
#[cfg(macos_platform)]
|
||||
Action::ToggleBorderlessGame => "Toggle borderless game mode",
|
||||
Action::ToggleMaximize => "Maximize",
|
||||
Action::Minimize => "Minimize",
|
||||
Action::ToggleResizeIncrements => "Use resize increments when resizing window",
|
||||
@@ -1297,8 +1315,6 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
|
||||
Binding::new("T", ModifiersState::META, Action::CreateNewTab),
|
||||
#[cfg(macos_platform)]
|
||||
Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt),
|
||||
#[cfg(macos_platform)]
|
||||
Binding::new("B", ModifiersState::CONTROL, Action::ToggleBorderlessGame),
|
||||
Binding::new("S", ModifiersState::ALT, Action::EmitSurfaceSize),
|
||||
Binding::new("S", ModifiersState::CONTROL, Action::Message),
|
||||
];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
// Limit this example to only compatible platforms.
|
||||
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, orbital_platform))]
|
||||
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))]
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -93,13 +93,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
orbital_platform
|
||||
)))]
|
||||
#[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))]
|
||||
fn main() {
|
||||
println!("This example is not supported on this platform");
|
||||
}
|
||||
|
||||
@@ -7,109 +7,150 @@
|
||||
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
|
||||
//! also be used to fill the window buffer, but they are more complicated to use.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::num::NonZeroU32;
|
||||
#[cfg(not(web_platform))]
|
||||
use std::time::Instant;
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::cleanup_window;
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::fill_window;
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::fill_window_with_animated_color;
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::fill_window_with_color;
|
||||
|
||||
use softbuffer::{Context, Surface};
|
||||
#[cfg(web_platform)]
|
||||
use web_time::Instant;
|
||||
use winit::window::{Window, WindowId};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod platform {
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::num::NonZeroU32;
|
||||
#[cfg(all(not(android_platform), not(web_platform)))]
|
||||
use std::time::Instant;
|
||||
|
||||
thread_local! {
|
||||
// NOTE: You should never do things like that, create context and drop it before
|
||||
// you drop the event loop. We do this for brevity to not blow up examples. We use
|
||||
// ManuallyDrop to prevent destructors from running.
|
||||
//
|
||||
// A static, thread-local map of graphics contexts to open windows.
|
||||
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) };
|
||||
}
|
||||
use softbuffer::{Context, Surface};
|
||||
#[cfg(all(web_platform, not(android_platform)))]
|
||||
use web_time::Instant;
|
||||
use winit::window::{Window, WindowId};
|
||||
|
||||
/// The graphics context used to draw to a window.
|
||||
struct GraphicsContext {
|
||||
/// The global softbuffer context.
|
||||
context: RefCell<Context<&'static dyn Window>>,
|
||||
thread_local! {
|
||||
// NOTE: You should never do things like that, create context and drop it before
|
||||
// you drop the event loop. We do this for brevity to not blow up examples. We use
|
||||
// ManuallyDrop to prevent destructors from running.
|
||||
//
|
||||
// A static, thread-local map of graphics contexts to open windows.
|
||||
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) };
|
||||
}
|
||||
|
||||
/// The hash map of window IDs to surfaces.
|
||||
surfaces: HashMap<WindowId, Surface<&'static dyn Window, &'static dyn Window>>,
|
||||
}
|
||||
/// The graphics context used to draw to a window.
|
||||
struct GraphicsContext {
|
||||
/// The global softbuffer context.
|
||||
context: RefCell<Context<&'static dyn Window>>,
|
||||
|
||||
impl GraphicsContext {
|
||||
fn new(w: &dyn Window) -> Self {
|
||||
Self {
|
||||
context: RefCell::new(
|
||||
Context::new(unsafe { mem::transmute::<&'_ dyn Window, &'static dyn Window>(w) })
|
||||
/// The hash map of window IDs to surfaces.
|
||||
surfaces: HashMap<WindowId, Surface<&'static dyn Window, &'static dyn Window>>,
|
||||
}
|
||||
|
||||
impl GraphicsContext {
|
||||
fn new(w: &dyn Window) -> Self {
|
||||
Self {
|
||||
context: RefCell::new(
|
||||
Context::new(unsafe {
|
||||
mem::transmute::<&'_ dyn Window, &'static dyn Window>(w)
|
||||
})
|
||||
.expect("Failed to create a softbuffer context"),
|
||||
),
|
||||
surfaces: HashMap::new(),
|
||||
),
|
||||
surfaces: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_surface(
|
||||
&mut self,
|
||||
window: &dyn Window,
|
||||
) -> &mut Surface<&'static dyn Window, &'static dyn Window> {
|
||||
self.surfaces.entry(window.id()).or_insert_with(|| {
|
||||
Surface::new(&self.context.borrow(), unsafe {
|
||||
mem::transmute::<&'_ dyn Window, &'static dyn Window>(window)
|
||||
})
|
||||
.expect("Failed to create a softbuffer surface")
|
||||
})
|
||||
}
|
||||
|
||||
fn destroy_surface(&mut self, window: &dyn Window) {
|
||||
self.surfaces.remove(&window.id());
|
||||
}
|
||||
}
|
||||
|
||||
fn create_surface(
|
||||
&mut self,
|
||||
window: &dyn Window,
|
||||
) -> &mut Surface<&'static dyn Window, &'static dyn Window> {
|
||||
self.surfaces.entry(window.id()).or_insert_with(|| {
|
||||
Surface::new(&self.context.borrow(), unsafe {
|
||||
mem::transmute::<&'_ dyn Window, &'static dyn Window>(window)
|
||||
})
|
||||
.expect("Failed to create a softbuffer surface")
|
||||
pub fn fill_window_with_color(window: &dyn Window, color: u32) {
|
||||
GC.with(|gc| {
|
||||
let size = window.surface_size();
|
||||
let (Some(width), Some(height)) =
|
||||
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Either get the last context used or create a new one.
|
||||
let mut gc = gc.borrow_mut();
|
||||
let surface =
|
||||
gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
|
||||
|
||||
// Fill a buffer with a solid color
|
||||
|
||||
surface.resize(width, height).expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(color);
|
||||
buffer.present().expect("Failed to present the softbuffer buffer");
|
||||
})
|
||||
}
|
||||
|
||||
fn destroy_surface(&mut self, window: &dyn Window) {
|
||||
self.surfaces.remove(&window.id());
|
||||
#[allow(dead_code)]
|
||||
pub fn fill_window(window: &dyn Window) {
|
||||
fill_window_with_color(window, 0xff181818);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn fill_window_with_animated_color(window: &dyn Window, start: Instant) {
|
||||
let time = start.elapsed().as_secs_f32() * 1.5;
|
||||
let blue = (time.sin() * 255.0) as u32;
|
||||
let green = ((time.cos() * 255.0) as u32) << 8;
|
||||
let red = ((1.0 - time.sin() * 255.0) as u32) << 16;
|
||||
let color = red | green | blue;
|
||||
fill_window_with_color(window, color);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(window: &dyn Window) {
|
||||
GC.with(|gc| {
|
||||
let mut gc = gc.borrow_mut();
|
||||
if let Some(context) = gc.as_mut() {
|
||||
context.destroy_surface(window);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_window_with_color(window: &dyn Window, color: u32) {
|
||||
GC.with(|gc| {
|
||||
let size = window.surface_size();
|
||||
let (Some(width), Some(height)) =
|
||||
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
mod platform {
|
||||
#[allow(dead_code)]
|
||||
pub fn fill_window(_window: &dyn winit::window::Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
|
||||
// Either get the last context used or create a new one.
|
||||
let mut gc = gc.borrow_mut();
|
||||
let surface = gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
|
||||
#[allow(dead_code)]
|
||||
pub fn fill_window_with_color(_window: &dyn winit::window::Window, _color: u32) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
|
||||
// Fill a buffer with a solid color
|
||||
#[allow(dead_code)]
|
||||
pub fn fill_window_with_animated_color(
|
||||
_window: &dyn winit::window::Window,
|
||||
_start: std::time::Instant,
|
||||
) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
|
||||
surface.resize(width, height).expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(color);
|
||||
buffer.present().expect("Failed to present the softbuffer buffer");
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn fill_window(window: &dyn Window) {
|
||||
fill_window_with_color(window, 0xff181818);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn fill_window_with_animated_color(window: &dyn Window, start: Instant) {
|
||||
let time = start.elapsed().as_secs_f32() * 1.5;
|
||||
let blue = (time.sin() * 255.0) as u32;
|
||||
let green = ((time.cos() * 255.0) as u32) << 8;
|
||||
let red = ((1.0 - time.sin() * 255.0) as u32) << 16;
|
||||
let color = red | green | blue;
|
||||
fill_window_with_color(window, color);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(window: &dyn Window) {
|
||||
GC.with(|gc| {
|
||||
let mut gc = gc.borrow_mut();
|
||||
if let Some(context) = gc.as_mut() {
|
||||
context.destroy_surface(window);
|
||||
}
|
||||
});
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(_window: &dyn winit::window::Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Simple winit window example.
|
||||
|
||||
use std::error::Error;
|
||||
use std::time::Instant;
|
||||
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::WindowEvent;
|
||||
@@ -37,14 +36,8 @@ impl ApplicationHandler for App {
|
||||
}
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop: &dyn ActiveEventLoop,
|
||||
_: WindowId,
|
||||
timestamp: Instant,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
::tracing::info!("{:?}: {event:?}", timestamp.elapsed());
|
||||
fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _: WindowId, event: WindowEvent) {
|
||||
println!("{event:?}");
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
println!("Close was requested; stopping");
|
||||
@@ -69,21 +62,11 @@ impl ApplicationHandler for App {
|
||||
fill::fill_window(window.as_ref());
|
||||
|
||||
// For contiguous redraw loop you can request a redraw from here.
|
||||
window.request_redraw();
|
||||
// window.request_redraw();
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn device_event(
|
||||
&mut self,
|
||||
_event_loop: &dyn ActiveEventLoop,
|
||||
_device_id: Option<winit::event::DeviceId>,
|
||||
timestamp: Instant,
|
||||
event: winit::event::DeviceEvent,
|
||||
) {
|
||||
::tracing::info!("{:?}: {event:?}", timestamp.elapsed());
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
@@ -39,22 +39,3 @@ The migration guide could reference other migration examples in the current
|
||||
changelog entry.
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Add `keyboard` support for OpenHarmony.
|
||||
- On iOS, add Apple Pencil support with force, altitude, and azimuth data.
|
||||
- On Redox, add support for missing keyboard scancodes.
|
||||
- Implement `Send` and `Sync` for `OwnedDisplayHandle`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated `windows-sys` to `v0.61`.
|
||||
- On older macOS versions (tested up to 12.7.6), applications now receive mouse movement events for unfocused windows, matching the behavior on other platforms.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On Redox, handle `EINTR` when reading from `event_socket` instead of panicking.
|
||||
- On Wayland, switch from using the `ahash` hashing algorithm to `foldhash`.
|
||||
- On macOS, fix borderless game presentation options not sticking after switching spaces.
|
||||
- On macOS, fix IME being locked on (regardless of requests to disable) after being enabled once.
|
||||
|
||||
@@ -1,18 +1,3 @@
|
||||
## 0.30.13
|
||||
|
||||
### Added
|
||||
|
||||
- On Wayland, add `Window::set_resize_increments`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On macOS, fixed crash when dragging non-file content onto window.
|
||||
- On X11, fix `set_hittest` not working on some window managers.
|
||||
- On X11, fix debug mode overflow panic in `set_timestamp`.
|
||||
- On macOS, fix crash in `set_marked_text` when native Pinyin IME sends out-of-bounds `selected_range`.
|
||||
- On Windows, fix `WM_IME_SETCONTEXT` IME UI flag masking on `lParam`.
|
||||
- On Android, populate `KeyEvent::text` and `KeyEvent::text_with_all_modifiers` via `Key::to_text()`
|
||||
|
||||
## 0.30.12
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
## 0.31.0-beta.2
|
||||
|
||||
### Added
|
||||
|
||||
- Add `EventLoopExtRegister::register_app` for being explicit about how the event loop runs on Web.
|
||||
- Add `EventLoopExtNeverReturn::run_app_never_return` for being explicit about how the event loop runs on iOS.
|
||||
|
||||
### Changed
|
||||
|
||||
- On Web, avoid throwing an exception in `EventLoop::run_app`, instead preferring to return to the caller.
|
||||
This requires passing a `'static` application to ensure that the application state will live as long as necessary.
|
||||
- On Web, the event loop can now always be re-created once it has finished running.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed panic when calling `Window::set_ime_allowed`.
|
||||
|
||||
## 0.31.0-beta.1
|
||||
|
||||
### Added
|
||||
@@ -249,3 +232,4 @@
|
||||
- On Web, device events are emitted regardless of cursor type.
|
||||
- On Wayland, `axis_value120` scroll events now generate `MouseScrollDelta::LineDelta`
|
||||
- On X11, mouse scroll button events no longer cause duplicated `WindowEvent::MouseWheel` events.
|
||||
- On macOS, fixed crash when dragging non-file content onto window.
|
||||
|
||||
@@ -118,7 +118,7 @@ impl EventLoop {
|
||||
EventLoopBuilder { platform_specific: Default::default() }
|
||||
}
|
||||
|
||||
/// Run the event loop with the given application on the calling thread.
|
||||
/// Run the application with the event loop on the calling thread.
|
||||
///
|
||||
/// The `app` is dropped when the event loop is shut down.
|
||||
///
|
||||
@@ -173,74 +173,35 @@ impl EventLoop {
|
||||
/// [`ControlFlow::WaitUntil`] and life-cycle methods like [`ApplicationHandler::resumed`], but
|
||||
/// it should give you an idea of how things fit together.
|
||||
///
|
||||
/// ## Returns
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// The semantics of this function can be a bit confusing, because the way different platforms
|
||||
/// control their event loop varies significantly.
|
||||
/// - **iOS:** Will never return to the caller and so values not passed to this function will
|
||||
/// *not* be dropped before the process exits.
|
||||
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript
|
||||
/// exception (that Rust doesn't see) that will also mean that the rest of the function is
|
||||
/// never executed and any values not passed to this function will *not* be dropped.
|
||||
///
|
||||
/// On most platforms (Android, macOS, Orbital, X11, Wayland, Windows), this blocks the caller,
|
||||
/// runs the event loop internally, and then returns once [`ActiveEventLoop::exit`] is called.
|
||||
/// See [`run_app_on_demand`] for more detailed semantics.
|
||||
/// Web applications are recommended to use
|
||||
#[cfg_attr(
|
||||
web_platform,
|
||||
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
|
||||
)]
|
||||
#[cfg_attr(not(web_platform), doc = " `EventLoopExtWeb::spawn_app()`")]
|
||||
/// [^1] instead of [`run_app()`] to avoid the need for the Javascript exception trick, and to
|
||||
/// make it clearer that the event loop runs asynchronously (via the browser's own,
|
||||
/// internal, event loop) and doesn't block the current thread of execution like it does
|
||||
/// on other platforms.
|
||||
///
|
||||
/// On iOS, this will register the application handler, and then call [`UIApplicationMain`]
|
||||
/// (which is the only way to run the system event loop), which never returns to the caller
|
||||
/// (the process instead exits after the handler has been dropped). See also
|
||||
/// [`run_app_never_return`].
|
||||
/// This function won't be available with `target_feature = "exception-handling"`.
|
||||
///
|
||||
/// On the web, this works by registering the application handler, and then immediately
|
||||
/// returning to the caller. This is necessary because WebAssembly (and JavaScript) is always
|
||||
/// executed in the context of the browser's own (internal) event loop, and thus we need to
|
||||
/// return to avoid blocking that and allow events to later be delivered asynchronously. See
|
||||
/// also [`register_app`].
|
||||
/// [^1]: `spawn_app()` is only available on the Web platform.
|
||||
///
|
||||
/// If you call this function inside `fn main`, you usually do not need to think about these
|
||||
/// details.
|
||||
///
|
||||
/// [`UIApplicationMain`]: https://developer.apple.com/documentation/uikit/uiapplicationmain(_:_:_:_:)-1yub7?language=objc
|
||||
/// [`run_app_on_demand`]: crate::event_loop::run_on_demand::EventLoopExtRunOnDemand::run_app_on_demand
|
||||
/// [`run_app_never_return`]: crate::event_loop::never_return::EventLoopExtNeverReturn::run_app_never_return
|
||||
/// [`register_app`]: crate::event_loop::register::EventLoopExtRegister::register_app
|
||||
///
|
||||
/// ## Static
|
||||
///
|
||||
/// To alleviate the issues noted above, this function requires that you pass in a `'static`
|
||||
/// handler, to ensure that any state your application uses will be alive as long as the
|
||||
/// application is running.
|
||||
///
|
||||
/// To be clear, you should avoid doing e.g. `event_loop.run_app(&mut app)?`, and prefer
|
||||
/// `event_loop.run_app(app)?` instead.
|
||||
///
|
||||
/// If this requirement is prohibitive for you, consider using [`run_app_on_demand`] instead
|
||||
/// (though note that this is not available on iOS and web).
|
||||
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
|
||||
/// [`run_app()`]: Self::run_app()
|
||||
#[inline]
|
||||
#[allow(unused_mut)]
|
||||
pub fn run_app<A: ApplicationHandler + 'static>(
|
||||
mut self,
|
||||
mut app: A,
|
||||
) -> Result<(), EventLoopError> {
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
orbital_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
))]
|
||||
{
|
||||
let result = self.event_loop.run_app_on_demand(&mut app);
|
||||
// SAFETY: unsure that the state is dropped before the exit from the event loop.
|
||||
drop(app);
|
||||
result
|
||||
}
|
||||
#[cfg(web_platform)]
|
||||
{
|
||||
self.event_loop.register_app(app);
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(ios_platform)]
|
||||
{
|
||||
self.event_loop.run_app_never_return(app)
|
||||
}
|
||||
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
|
||||
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
|
||||
self.event_loop.run_app(app)
|
||||
}
|
||||
|
||||
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
|
||||
@@ -345,7 +306,6 @@ impl winit_core::event_loop::pump_events::EventLoopExtPumpEvents for EventLoop {
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
orbital_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
docsrs,
|
||||
@@ -356,13 +316,6 @@ impl winit_core::event_loop::run_on_demand::EventLoopExtRunOnDemand for EventLoo
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(web_platform, docsrs))]
|
||||
impl winit_core::event_loop::register::EventLoopExtRegister for EventLoop {
|
||||
fn register_app<A: ApplicationHandler + 'static>(self, app: A) {
|
||||
self.event_loop.register_app(app)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(android_platform)]
|
||||
impl winit_android::EventLoopExtAndroid for EventLoop {
|
||||
fn android_app(&self) -> &winit_android::activity::AndroidApp {
|
||||
@@ -432,6 +385,10 @@ impl winit_wayland::EventLoopBuilderExtWayland for EventLoopBuilder {
|
||||
|
||||
#[cfg(web_platform)]
|
||||
impl winit_web::EventLoopExtWeb for EventLoop {
|
||||
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
|
||||
self.event_loop.spawn_app(app);
|
||||
}
|
||||
|
||||
fn set_poll_strategy(&self, strategy: winit_web::PollStrategy) {
|
||||
self.event_loop.set_poll_strategy(strategy);
|
||||
}
|
||||
|
||||
@@ -147,6 +147,10 @@ impl EventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app))
|
||||
}
|
||||
|
||||
pub fn run_app_on_demand<A: ApplicationHandler>(
|
||||
&mut self,
|
||||
app: A,
|
||||
|
||||
@@ -28,8 +28,3 @@ fn custom_cursor_send() {
|
||||
needs_send::<winit::cursor::CustomCursorSource>();
|
||||
needs_send::<winit::cursor::CustomCursor>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn owned_display_handle_send() {
|
||||
needs_send::<winit::event_loop::OwnedDisplayHandle>();
|
||||
}
|
||||
|
||||
@@ -21,8 +21,3 @@ fn custom_cursor_sync() {
|
||||
needs_sync::<winit::cursor::CustomCursorSource>();
|
||||
needs_sync::<winit::cursor::CustomCursor>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn owned_display_handle_sync() {
|
||||
needs_sync::<winit::event_loop::OwnedDisplayHandle>();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user