mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
1 Commits
dependabot
...
madsmtm/ui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a2b140d65 |
27
.github/workflows/ci.yml
vendored
27
.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@v6
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
|
||||
path: |
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
|
||||
- name: Generate lockfile
|
||||
# Also updates the crates.io index
|
||||
run: cargo generate-lockfile && cargo update -p smol_str --precise 0.3.2 && cargo update -p unicode-segmentation --precise 1.12.0 && cargo update -p wayland-protocols --precise 0.32.12
|
||||
run: cargo generate-lockfile && cargo update -p smol_str --precise 0.3.2
|
||||
|
||||
- name: Install GCC Multilib
|
||||
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
- name: Cache cargo-apk
|
||||
if: contains(matrix.platform.target, 'android')
|
||||
id: cargo-apk-cache
|
||||
uses: actions/cache@v6
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.cargo/bin/cargo-apk
|
||||
# Change this key if we update the required cargo-apk version
|
||||
@@ -281,7 +281,7 @@ jobs:
|
||||
|
||||
# See restore step above
|
||||
- name: Save cache of cargo folder
|
||||
uses: actions/cache/save@v6
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index/
|
||||
@@ -290,14 +290,31 @@ jobs:
|
||||
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
|
||||
|
||||
cargo-deny:
|
||||
name: Run cargo-deny
|
||||
name: Run cargo-deny on ${{ matrix.platform.name }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- { name: 'Android', target: aarch64-linux-android }
|
||||
- { name: 'iOS', target: aarch64-apple-ios }
|
||||
- { name: 'Linux', target: x86_64-unknown-linux-gnu }
|
||||
- { name: 'macOS', target: aarch64-apple-darwin }
|
||||
- { name: 'Redox OS', target: x86_64-unknown-redox }
|
||||
- { name: 'Web', target: wasm32-unknown-unknown }
|
||||
- { name: 'Windows GNU', target: x86_64-pc-windows-gnu }
|
||||
- { name: 'Windows MSVC', target: x86_64-pc-windows-msvc }
|
||||
|
||||
steps:
|
||||
- uses: taiki-e/checkout-action@v1
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
with:
|
||||
command: check
|
||||
log-level: error
|
||||
manifest-path: winit/Cargo.toml
|
||||
arguments: --all-features --target ${{ matrix.platform.target }}
|
||||
|
||||
eslint:
|
||||
name: ESLint
|
||||
|
||||
8
.github/workflows/docs.yml
vendored
8
.github/workflows/docs.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=serde,mint,android-native-activity
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v6
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Fix permissions
|
||||
run: |
|
||||
@@ -41,10 +41,10 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: target/doc
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v5
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
# Using allow-invalid because this is platform-specific code
|
||||
disallowed-macros = [
|
||||
{ path = "std::print", reason = "works badly on web", replacement = "tracing::info" },
|
||||
{ path = "std::println", reason = "works badly on web", replacement = "tracing::info" },
|
||||
{ path = "std::eprint", reason = "works badly on web", replacement = "tracing::error" },
|
||||
{ path = "std::eprintln", reason = "works badly on web", replacement = "tracing::error" },
|
||||
{ path = "std::dbg", reason = "leftover debugging aid, remove it or use tracing" },
|
||||
]
|
||||
disallowed-methods = [
|
||||
{ allow-invalid = true, path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
|
||||
{ allow-invalid = true, path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
|
||||
|
||||
82
deny.toml
82
deny.toml
@@ -1,25 +1,27 @@
|
||||
# https://embarkstudios.github.io/cargo-deny
|
||||
# cargo install cargo-deny
|
||||
# cargo update && cargo deny check
|
||||
# cargo update && cargo deny --target aarch64-apple-ios check
|
||||
# Note: running just `cargo deny check` without a `--target` will result in
|
||||
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
|
||||
[graph]
|
||||
all-features = true
|
||||
exclude-dev = true
|
||||
targets = [
|
||||
"aarch64-apple-darwin",
|
||||
"aarch64-apple-ios",
|
||||
"aarch64-linux-android",
|
||||
"i686-pc-windows-gnu",
|
||||
"i686-pc-windows-msvc",
|
||||
"i686-unknown-linux-gnu",
|
||||
{ triple = "aarch64-apple-darwin" },
|
||||
{ triple = "aarch64-apple-ios" },
|
||||
{ triple = "aarch64-linux-android" },
|
||||
{ triple = "i686-pc-windows-gnu" },
|
||||
{ triple = "i686-pc-windows-msvc" },
|
||||
{ triple = "i686-unknown-linux-gnu" },
|
||||
{ triple = "wasm32-unknown-unknown", features = [
|
||||
"atomics",
|
||||
] },
|
||||
"x86_64-apple-darwin",
|
||||
"x86_64-apple-ios",
|
||||
"x86_64-pc-windows-gnu",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-unknown-redox",
|
||||
{ triple = "x86_64-apple-darwin" },
|
||||
{ triple = "x86_64-apple-ios" },
|
||||
{ triple = "x86_64-pc-windows-gnu" },
|
||||
{ triple = "x86_64-pc-windows-msvc" },
|
||||
{ triple = "x86_64-unknown-linux-gnu" },
|
||||
{ triple = "x86_64-unknown-redox" },
|
||||
]
|
||||
|
||||
[licenses]
|
||||
@@ -31,7 +33,6 @@ allow = [
|
||||
"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
|
||||
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/
|
||||
]
|
||||
confidence-threshold = 1.0
|
||||
private = { ignore = true }
|
||||
@@ -39,23 +40,46 @@ private = { ignore = true }
|
||||
[bans]
|
||||
multiple-versions = "deny"
|
||||
skip = [
|
||||
{ crate = "jni-sys@0.3", reason = "uses the semver trick to depend on v0.4, but `ndk` hasn't been updated to v0.4 yet" },
|
||||
{ crate = "thiserror@1.0", reason = "dep of `ndk` crate, yet to be updated" },
|
||||
{ crate = "thiserror-impl@1.0", reason = "dep of `thiserror`" },
|
||||
{ crate = "objc2@0.5", reason = "used by crossfont" },
|
||||
{ crate = "objc2-foundation@0.2", reason = "used by crossfont" },
|
||||
]
|
||||
skip-tree = [
|
||||
{ crate = "windows-sys", reason = "foundational but bumps fairly often, nothing we can do about it not having a shared version" },
|
||||
{ crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" },
|
||||
{ crate = "rustix@0.38", reason = "the ecosystem is in the process of migrating" },
|
||||
{ crate = "linux-raw-sys@0.4", reason = "the ecosystem is in the process of migrating" },
|
||||
]
|
||||
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
|
||||
|
||||
[bans.build]
|
||||
bypass = [
|
||||
{ crate = "android-activity", allow-globs = ["android-games-sdk/import-games-sdk.sh"] },
|
||||
{ crate = "freetype-sys", allow-globs = ["freetype2/*"] },
|
||||
# `crossfont` still depends (partially transitively) on `winapi`.
|
||||
{ crate = "winapi-i686-pc-windows-gnu", allow-globs = ["lib/lib*.a"] },
|
||||
{ crate = "winapi-x86_64-pc-windows-gnu", allow-globs = ["lib/lib*.a"] },
|
||||
]
|
||||
include-archives = true
|
||||
interpreted = "deny"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow = [
|
||||
{ path = "generate-bindings.sh", checksum = "268ec23248218d779e33853cdc60e2985e70214ff004716cd734270de1f6b561" },
|
||||
]
|
||||
crate = "android-activity"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["ci/*", "githooks/*"]
|
||||
crate = "zerocopy"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["cherry-pick-stable.sh"]
|
||||
crate = "libc"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["freetype2/*"]
|
||||
crate = "freetype-sys"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["lib/*.a"]
|
||||
crate = "windows_i686_gnu"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["lib/*.lib"]
|
||||
crate = "windows_i686_msvc"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["lib/*.a"]
|
||||
crate = "windows_x86_64_gnu"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["lib/*.lib"]
|
||||
crate = "windows_x86_64_msvc"
|
||||
|
||||
@@ -109,7 +109,6 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
|
||||
Keycode::MediaStop => KeyCode::MediaStop,
|
||||
Keycode::MediaNext => KeyCode::MediaTrackNext,
|
||||
Keycode::MediaPrevious => KeyCode::MediaTrackPrevious,
|
||||
Keycode::MediaEject => KeyCode::Eject,
|
||||
|
||||
Keycode::Plus => KeyCode::Equal,
|
||||
Keycode::Minus => KeyCode::Minus,
|
||||
@@ -132,11 +131,7 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
|
||||
// These are exactly the same
|
||||
Keycode::ScrollLock => KeyCode::ScrollLock,
|
||||
|
||||
Keycode::Eisu => KeyCode::Lang2,
|
||||
Keycode::Muhenkan => KeyCode::NonConvert,
|
||||
Keycode::Henkan => KeyCode::Convert,
|
||||
Keycode::Yen => KeyCode::IntlYen,
|
||||
Keycode::Ro => KeyCode::IntlRo,
|
||||
Keycode::Kana => KeyCode::Lang1,
|
||||
Keycode::KatakanaHiragana => KeyCode::KanaMode,
|
||||
|
||||
@@ -159,14 +154,6 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
|
||||
Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep?
|
||||
Keycode::Wakeup => KeyCode::WakeUp,
|
||||
|
||||
Keycode::CapsLock => KeyCode::CapsLock,
|
||||
Keycode::Help => KeyCode::Help,
|
||||
|
||||
Keycode::Back => KeyCode::BrowserBack,
|
||||
Keycode::Forward => KeyCode::BrowserForward,
|
||||
Keycode::Refresh => KeyCode::BrowserRefresh,
|
||||
Keycode::Search => KeyCode::BrowserSearch,
|
||||
|
||||
keycode => return PhysicalKey::Unidentified(NativeKeyCode::Android(keycode.into())),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -51,7 +51,6 @@ objc2-app-kit = { workspace = true, features = [
|
||||
"NSScreen",
|
||||
"NSTextInputClient",
|
||||
"NSTextInputContext",
|
||||
"NSTrackingArea",
|
||||
"NSToolbar",
|
||||
"NSView",
|
||||
"NSWindow",
|
||||
@@ -107,7 +106,7 @@ objc2-foundation = { workspace = true, features = [
|
||||
"NSThread",
|
||||
"NSValue",
|
||||
] }
|
||||
winit-common = { workspace = true, features = ["core-foundation", "event-handler", "foundation"] }
|
||||
winit-common = { workspace = true, features = ["core-foundation", "event-handler"] }
|
||||
|
||||
[dev-dependencies]
|
||||
winit.workspace = true
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::{mem, ptr};
|
||||
|
||||
use dispatch2::MainThreadBound;
|
||||
use objc2::runtime::{Imp, Sel};
|
||||
use objc2::sel;
|
||||
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType};
|
||||
use objc2_foundation::MainThreadMarker;
|
||||
use tracing::trace_span;
|
||||
use winit_core::event::{DeviceEvent, ElementState};
|
||||
|
||||
use super::app_state::AppState;
|
||||
@@ -22,10 +21,6 @@ static ORIGINAL: MainThreadBound<Cell<Option<SendEvent>>> = {
|
||||
};
|
||||
|
||||
extern "C-unwind" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) {
|
||||
// This can be a bit noisy, since `event` is fairly large. Note that you can use
|
||||
// `RUST_LOG='trace,winit_appkit::app=warn'` if you're debugging and want TRACE-level logs but
|
||||
// not this.
|
||||
let _entered = trace_span!("sendEvent:", ?event).entered();
|
||||
let mtm = MainThreadMarker::from(app);
|
||||
|
||||
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
|
||||
@@ -80,7 +75,9 @@ pub(crate) fn override_send_event(global_app: &NSApplication) {
|
||||
let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) };
|
||||
|
||||
// If we've already overridden the method, don't do anything.
|
||||
if ptr::fn_addr_eq(overridden, method.implementation()) {
|
||||
// FIXME(madsmtm): Use `std::ptr::fn_addr_eq` (Rust 1.85) once available in MSRV.
|
||||
#[allow(unknown_lints, unpredictable_function_pointer_comparisons)]
|
||||
if overridden == method.implementation() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ impl AppState {
|
||||
// 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) {
|
||||
trace_scope!("NSApplicationDidFinishLaunchingNotification");
|
||||
self.is_launched.set(true);
|
||||
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
@@ -153,6 +154,7 @@ impl AppState {
|
||||
}
|
||||
|
||||
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
|
||||
trace_scope!("NSApplicationWillTerminateNotification");
|
||||
let app = NSApplication::sharedApplication(self.mtm);
|
||||
notify_windows_of_exit(&app);
|
||||
self.event_handler.terminate();
|
||||
|
||||
@@ -5,10 +5,7 @@ use std::sync::OnceLock;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{AllocAnyThread, ClassType, available, msg_send, sel};
|
||||
use objc2_app_kit::{
|
||||
NSBitmapImageRep, NSCursor, NSCursorFrameResizeDirections, NSCursorFrameResizePosition,
|
||||
NSDeviceRGBColorSpace, NSImage,
|
||||
};
|
||||
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
|
||||
use objc2_foundation::{
|
||||
NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString, ns_string,
|
||||
};
|
||||
@@ -207,155 +204,23 @@ pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
|
||||
CursorIcon::NotAllowed | CursorIcon::NoDrop => NSCursor::operationNotAllowedCursor(),
|
||||
CursorIcon::ContextMenu => NSCursor::contextualMenuCursor(),
|
||||
CursorIcon::Crosshair => NSCursor::crosshairCursor(),
|
||||
CursorIcon::EResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::frameResizeCursorFromPosition_inDirections(
|
||||
NSCursorFrameResizePosition::Right,
|
||||
NSCursorFrameResizeDirections::Outward,
|
||||
)
|
||||
} else {
|
||||
NSCursor::resizeRightCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::NResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::frameResizeCursorFromPosition_inDirections(
|
||||
NSCursorFrameResizePosition::Top,
|
||||
NSCursorFrameResizeDirections::Outward,
|
||||
)
|
||||
} else {
|
||||
NSCursor::resizeUpCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::WResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::frameResizeCursorFromPosition_inDirections(
|
||||
NSCursorFrameResizePosition::Left,
|
||||
NSCursorFrameResizeDirections::Outward,
|
||||
)
|
||||
} else {
|
||||
NSCursor::resizeLeftCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::SResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::frameResizeCursorFromPosition_inDirections(
|
||||
NSCursorFrameResizePosition::Bottom,
|
||||
NSCursorFrameResizeDirections::Outward,
|
||||
)
|
||||
} else {
|
||||
NSCursor::resizeDownCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::EwResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::frameResizeCursorFromPosition_inDirections(
|
||||
NSCursorFrameResizePosition::Right,
|
||||
NSCursorFrameResizeDirections::All,
|
||||
)
|
||||
} else {
|
||||
NSCursor::resizeLeftRightCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::NsResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::frameResizeCursorFromPosition_inDirections(
|
||||
NSCursorFrameResizePosition::Top,
|
||||
NSCursorFrameResizeDirections::All,
|
||||
)
|
||||
} else {
|
||||
NSCursor::resizeUpDownCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::NeResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::frameResizeCursorFromPosition_inDirections(
|
||||
NSCursorFrameResizePosition::TopRight,
|
||||
NSCursorFrameResizeDirections::Outward,
|
||||
)
|
||||
} else {
|
||||
_windowResizeNorthEastCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::NwResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::frameResizeCursorFromPosition_inDirections(
|
||||
NSCursorFrameResizePosition::TopLeft,
|
||||
NSCursorFrameResizeDirections::Outward,
|
||||
)
|
||||
} else {
|
||||
_windowResizeNorthWestCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::SeResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::frameResizeCursorFromPosition_inDirections(
|
||||
NSCursorFrameResizePosition::BottomRight,
|
||||
NSCursorFrameResizeDirections::Outward,
|
||||
)
|
||||
} else {
|
||||
_windowResizeSouthEastCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::SwResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::frameResizeCursorFromPosition_inDirections(
|
||||
NSCursorFrameResizePosition::BottomLeft,
|
||||
NSCursorFrameResizeDirections::Outward,
|
||||
)
|
||||
} else {
|
||||
_windowResizeSouthWestCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::NeswResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::frameResizeCursorFromPosition_inDirections(
|
||||
NSCursorFrameResizePosition::TopRight,
|
||||
NSCursorFrameResizeDirections::All,
|
||||
)
|
||||
} else {
|
||||
_windowResizeNorthEastSouthWestCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::NwseResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::frameResizeCursorFromPosition_inDirections(
|
||||
NSCursorFrameResizePosition::TopLeft,
|
||||
NSCursorFrameResizeDirections::All,
|
||||
)
|
||||
} else {
|
||||
_windowResizeNorthWestSouthEastCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::ColResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::columnResizeCursor()
|
||||
} else {
|
||||
NSCursor::resizeLeftRightCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::RowResize => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::rowResizeCursor()
|
||||
} else {
|
||||
NSCursor::resizeUpDownCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::ZoomIn => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::zoomInCursor()
|
||||
} else {
|
||||
_zoomInCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::ZoomOut => {
|
||||
if available!(macos = 15.0) {
|
||||
NSCursor::zoomOutCursor()
|
||||
} else {
|
||||
_zoomOutCursor()
|
||||
}
|
||||
},
|
||||
CursorIcon::EResize => NSCursor::resizeRightCursor(),
|
||||
CursorIcon::NResize => NSCursor::resizeUpCursor(),
|
||||
CursorIcon::WResize => NSCursor::resizeLeftCursor(),
|
||||
CursorIcon::SResize => NSCursor::resizeDownCursor(),
|
||||
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(),
|
||||
CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
|
||||
CursorIcon::Help => _helpCursor(),
|
||||
CursorIcon::ZoomIn if available!(macos = 15.0) => NSCursor::zoomInCursor(),
|
||||
CursorIcon::ZoomIn => _zoomInCursor(),
|
||||
CursorIcon::ZoomOut if available!(macos = 15.0) => NSCursor::zoomOutCursor(),
|
||||
CursorIcon::ZoomOut => _zoomOutCursor(),
|
||||
CursorIcon::NeResize => _windowResizeNorthEastCursor(),
|
||||
CursorIcon::NwResize => _windowResizeNorthWestCursor(),
|
||||
CursorIcon::SeResize => _windowResizeSouthEastCursor(),
|
||||
CursorIcon::SwResize => _windowResizeSouthWestCursor(),
|
||||
CursorIcon::NeswResize => _windowResizeNorthEastSouthWestCursor(),
|
||||
CursorIcon::NwseResize => _windowResizeNorthWestSouthEastCursor(),
|
||||
// This is the wrong semantics for `Wait`, but it's the same as
|
||||
// what's used in Safari and Chrome.
|
||||
CursorIcon::Wait | CursorIcon::Progress => busyButClickableCursor(),
|
||||
|
||||
@@ -12,9 +12,7 @@ use objc2_app_kit::{
|
||||
use objc2_core_foundation::{CFIndex, CFRunLoopActivity, kCFRunLoopCommonModes};
|
||||
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
|
||||
use rwh_06::HasDisplayHandle;
|
||||
use tracing::debug_span;
|
||||
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
|
||||
use winit_common::foundation::create_observer;
|
||||
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};
|
||||
@@ -31,6 +29,7 @@ use super::app_state::AppState;
|
||||
use super::cursor::CustomCursor;
|
||||
use super::event::dummy_event;
|
||||
use super::monitor;
|
||||
use super::notification_center::create_observer;
|
||||
use crate::ActivationPolicy;
|
||||
use crate::window::Window;
|
||||
|
||||
@@ -153,7 +152,6 @@ pub struct EventLoop {
|
||||
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
|
||||
_tracing_observers: Option<(MainRunLoopObserver, MainRunLoopObserver)>,
|
||||
_before_waiting_observer: MainRunLoopObserver,
|
||||
_after_waiting_observer: MainRunLoopObserver,
|
||||
}
|
||||
@@ -205,7 +203,6 @@ impl EventLoop {
|
||||
// `applicationDidFinishLaunching:`
|
||||
unsafe { NSApplicationDidFinishLaunchingNotification },
|
||||
move |notification| {
|
||||
let _entered = debug_span!("NSApplicationDidFinishLaunchingNotification").entered();
|
||||
if let Some(app_state) = weak_app_state.upgrade() {
|
||||
app_state.did_finish_launching(notification);
|
||||
}
|
||||
@@ -218,7 +215,6 @@ impl EventLoop {
|
||||
// `applicationWillTerminate:`
|
||||
unsafe { NSApplicationWillTerminateNotification },
|
||||
move |notification| {
|
||||
let _entered = debug_span!("NSApplicationWillTerminateNotification").entered();
|
||||
if let Some(app_state) = weak_app_state.upgrade() {
|
||||
app_state.will_terminate(notification);
|
||||
}
|
||||
@@ -228,20 +224,14 @@ impl EventLoop {
|
||||
let main_loop = MainRunLoop::get(mtm);
|
||||
let mode = unsafe { kCFRunLoopCommonModes }.unwrap();
|
||||
|
||||
// Tracing observers have the lowest and highest orderings.
|
||||
let _tracing_observers = tracing_observers(mtm).inspect(|(start, end)| {
|
||||
main_loop.add_observer(start, mode);
|
||||
main_loop.add_observer(end, mode);
|
||||
});
|
||||
|
||||
let app_state_clone = Rc::clone(&app_state);
|
||||
let _before_waiting_observer = MainRunLoopObserver::new(
|
||||
mtm,
|
||||
CFRunLoopActivity::BeforeWaiting,
|
||||
true,
|
||||
// Queued with the second-lowest priority (tracing observers use the lowest) to ensure
|
||||
// it is processed after other observers.
|
||||
CFIndex::MAX - 1,
|
||||
// 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);
|
||||
@@ -251,9 +241,8 @@ impl EventLoop {
|
||||
mtm,
|
||||
CFRunLoopActivity::AfterWaiting,
|
||||
true,
|
||||
// Queued with the second-highest priority (tracing observers use the highest) to
|
||||
// ensure it is processed before other observers.
|
||||
CFIndex::MIN + 1,
|
||||
// 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);
|
||||
@@ -264,7 +253,6 @@ impl EventLoop {
|
||||
window_target: ActiveEventLoop { app_state, mtm },
|
||||
_did_finish_launching_observer,
|
||||
_will_terminate_observer,
|
||||
_tracing_observers,
|
||||
_before_waiting_observer,
|
||||
_after_waiting_observer,
|
||||
})
|
||||
|
||||
@@ -76,6 +76,7 @@ mod event_loop;
|
||||
mod ffi;
|
||||
mod menu;
|
||||
mod monitor;
|
||||
mod notification_center;
|
||||
mod observer;
|
||||
mod view;
|
||||
mod window;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// NOTE: This is symlinked to be contained in both the AppKit and UIKit implementations.
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use block2::RcBlock;
|
||||
@@ -11,7 +12,7 @@ use objc2_foundation::{
|
||||
///
|
||||
/// This is used in Winit as an alternative to declaring an application delegate, as we want to
|
||||
/// give the user full control over those.
|
||||
pub fn create_observer(
|
||||
pub(crate) fn create_observer(
|
||||
center: &NSNotificationCenter,
|
||||
name: &NSNotificationName,
|
||||
handler: impl Fn(&NSNotification) + 'static,
|
||||
@@ -1,10 +1,37 @@
|
||||
use objc2_core_graphics::CGError;
|
||||
use tracing::trace;
|
||||
use winit_core::error::OsError;
|
||||
|
||||
macro_rules! os_error {
|
||||
($error:expr) => {{ winit_core::error::OsError::new(line!(), file!(), $error) }};
|
||||
}
|
||||
|
||||
macro_rules! trace_scope {
|
||||
($s:literal) => {
|
||||
let _crate = $crate::util::TraceGuard::new(module_path!(), $s);
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) struct TraceGuard {
|
||||
module_path: &'static str,
|
||||
called_from_fn: &'static str,
|
||||
}
|
||||
|
||||
impl TraceGuard {
|
||||
#[inline]
|
||||
pub(crate) fn new(module_path: &'static str, called_from_fn: &'static str) -> Self {
|
||||
trace!(target = module_path, "Triggered `{}`", called_from_fn);
|
||||
Self { module_path, called_from_fn }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TraceGuard {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
trace!(target = self.module_path, "Completed `{}`", self.called_from_fn);
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn cgerr(err: CGError) -> Result<(), OsError> {
|
||||
if err == CGError::Success { Ok(()) } else { Err(os_error!(format!("CGError {err:?}"))) }
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
|
||||
use dpi::{LogicalPosition, PhysicalSize};
|
||||
use dpi::{LogicalPosition, LogicalSize};
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::{AnyObject, Sel};
|
||||
use objc2::{AnyThread, DefinedClass, MainThreadMarker, 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, NSViewLayerContentsRedrawPolicy, 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::{debug_span, trace_span};
|
||||
use winit_core::event::{
|
||||
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
|
||||
PointerKind, PointerSource, TouchPhase, WindowEvent,
|
||||
@@ -119,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>,
|
||||
|
||||
@@ -130,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,
|
||||
|
||||
@@ -151,35 +153,49 @@ 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>) {
|
||||
let _entered = debug_span!("NSViewFrameDidChangeNotification").entered();
|
||||
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).
|
||||
self.surface_resized();
|
||||
// During live resize, AppKit may not let the normal event loop reach its next redraw
|
||||
// point before stretching the current layer contents. Redraw immediately after the
|
||||
// app has observed the new surface size.
|
||||
self.redraw_during_live_resize();
|
||||
}
|
||||
|
||||
#[unsafe(method(viewDidChangeBackingProperties))]
|
||||
fn view_did_change_backing_properties(&self) {
|
||||
let _entered = debug_span!("viewDidChangeBackingProperties").entered();
|
||||
// Moving between displays or changing scale can alter the drawable backing size
|
||||
// without a matching frame-size change.
|
||||
self.surface_resized();
|
||||
self.redraw_during_live_resize();
|
||||
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));
|
||||
}
|
||||
|
||||
#[unsafe(method(drawRect:))]
|
||||
fn draw_rect(&self, _rect: NSRect) {
|
||||
let _entered = debug_span!("drawRect:").entered();
|
||||
trace_scope!("drawRect:");
|
||||
|
||||
self.ivars().app_state.handle_redraw(window_id(&self.window()));
|
||||
|
||||
@@ -188,7 +204,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(acceptsFirstResponder))]
|
||||
fn accepts_first_responder(&self) -> bool {
|
||||
let _entered = trace_span!("acceptsFirstResponder").entered();
|
||||
trace_scope!("acceptsFirstResponder");
|
||||
true
|
||||
}
|
||||
|
||||
@@ -202,13 +218,13 @@ define_class!(
|
||||
// extension for using `NSTouchBar`
|
||||
#[unsafe(method_id(touchBar))]
|
||||
fn touch_bar(&self) -> Option<Retained<NSObject>> {
|
||||
let _entered = debug_span!("touchBar").entered();
|
||||
trace_scope!("touchBar");
|
||||
None
|
||||
}
|
||||
|
||||
#[unsafe(method(resetCursorRects))]
|
||||
fn reset_cursor_rects(&self) {
|
||||
let _entered = debug_span!("resetCursorRects").entered();
|
||||
trace_scope!("resetCursorRects");
|
||||
let bounds = self.bounds();
|
||||
let cursor_state = self.ivars().cursor_state.borrow();
|
||||
// We correctly invoke `addCursorRect` only from inside `resetCursorRects`
|
||||
@@ -223,13 +239,13 @@ define_class!(
|
||||
unsafe impl NSTextInputClient for WinitView {
|
||||
#[unsafe(method(hasMarkedText))]
|
||||
fn has_marked_text(&self) -> bool {
|
||||
let _entered = debug_span!("hasMarkedText").entered();
|
||||
trace_scope!("hasMarkedText");
|
||||
self.ivars().marked_text.borrow().length() > 0
|
||||
}
|
||||
|
||||
#[unsafe(method(markedRange))]
|
||||
fn marked_range(&self) -> NSRange {
|
||||
let _entered = debug_span!("markedRange").entered();
|
||||
trace_scope!("markedRange");
|
||||
let length = self.ivars().marked_text.borrow().length();
|
||||
if length > 0 {
|
||||
NSRange::new(0, length)
|
||||
@@ -241,7 +257,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(selectedRange))]
|
||||
fn selected_range(&self) -> NSRange {
|
||||
let _entered = debug_span!("selectedRange").entered();
|
||||
trace_scope!("selectedRange");
|
||||
// Documented to return `{NSNotFound, 0}` if there is no selection.
|
||||
NSRange::new(NSNotFound as NSUInteger, 0)
|
||||
}
|
||||
@@ -254,7 +270,7 @@ define_class!(
|
||||
_replacement_range: NSRange,
|
||||
) {
|
||||
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
|
||||
let _entered = debug_span!("setMarkedText:selectedRange:replacementRange:").entered();
|
||||
trace_scope!("setMarkedText:selectedRange:replacementRange:");
|
||||
|
||||
let (marked_text, string) = if let Some(string) =
|
||||
string.downcast_ref::<NSAttributedString>()
|
||||
@@ -283,31 +299,32 @@ define_class!(
|
||||
self.ivars().ime_state.set(ImeState::Ground);
|
||||
}
|
||||
|
||||
let string = string.to_string();
|
||||
let cursor_range = if string.is_empty() {
|
||||
// An empty string basically means that there's no preedit, so indicate that by
|
||||
// sending a `None` cursor range.
|
||||
None
|
||||
} else {
|
||||
// Convert the selected range from UTF-16 code unit indices to UTF-8 byte
|
||||
// offsets. `utf16_to_utf8_offset` is defensive: it snaps an offset that would
|
||||
// split a surrogate pair down to the character boundary and clamps an
|
||||
// out-of-bounds offset to the string length, so no `NSRangeException` is
|
||||
// possible and the resulting range can never be inverted (`lower <= upper`).
|
||||
// IMEs are known to send both mid-surrogate and out-of-bounds offsets (e.g.
|
||||
// native Pinyin, see https://github.com/alacritty/alacritty/issues/8791).
|
||||
let lowerbound_utf8 = utf16_to_utf8_offset(&string, selected_range.location);
|
||||
let upperbound_utf8 = utf16_to_utf8_offset(&string, selected_range.end());
|
||||
// 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 lowerbound_utf8 = sub_string_a.len();
|
||||
let upperbound_utf8 = sub_string_b.len();
|
||||
Some((lowerbound_utf8, upperbound_utf8))
|
||||
};
|
||||
|
||||
// Send WindowEvent for updating marked text
|
||||
self.queue_event(WindowEvent::Ime(Ime::Preedit(string, cursor_range)));
|
||||
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
|
||||
}
|
||||
|
||||
#[unsafe(method(unmarkText))]
|
||||
fn unmark_text(&self) {
|
||||
let _entered = debug_span!("unmarkText").entered();
|
||||
trace_scope!("unmarkText");
|
||||
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
|
||||
|
||||
let input_context = self.inputContext().expect("input context");
|
||||
@@ -324,7 +341,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method_id(validAttributesForMarkedText))]
|
||||
fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
|
||||
let _entered = trace_span!("validAttributesForMarkedText").entered();
|
||||
trace_scope!("validAttributesForMarkedText");
|
||||
NSArray::new()
|
||||
}
|
||||
|
||||
@@ -334,14 +351,13 @@ define_class!(
|
||||
_range: NSRange,
|
||||
_actual_range: *mut NSRange,
|
||||
) -> Option<Retained<NSAttributedString>> {
|
||||
let _entered =
|
||||
trace_span!("attributedSubstringForProposedRange:actualRange:").entered();
|
||||
trace_scope!("attributedSubstringForProposedRange:actualRange:");
|
||||
None
|
||||
}
|
||||
|
||||
#[unsafe(method(characterIndexForPoint:))]
|
||||
fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
|
||||
let _entered = debug_span!("characterIndexForPoint:").entered();
|
||||
trace_scope!("characterIndexForPoint:");
|
||||
0
|
||||
}
|
||||
|
||||
@@ -351,7 +367,7 @@ define_class!(
|
||||
_range: NSRange,
|
||||
_actual_range: *mut NSRange,
|
||||
) -> NSRect {
|
||||
let _entered = debug_span!("firstRectForCharacterRange:actualRange:").entered();
|
||||
trace_scope!("firstRectForCharacterRange:actualRange:");
|
||||
|
||||
// Guard when the view is no longer in a window during teardown.
|
||||
let Some(window) = (**self).window() else {
|
||||
@@ -367,7 +383,7 @@ define_class!(
|
||||
#[unsafe(method(insertText:replacementRange:))]
|
||||
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
|
||||
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
|
||||
let _entered = debug_span!("insertText:replacementRange:").entered();
|
||||
trace_scope!("insertText:replacementRange:");
|
||||
|
||||
let string = if let Some(string) = string.downcast_ref::<NSAttributedString>() {
|
||||
string.string().to_string()
|
||||
@@ -392,7 +408,7 @@ define_class!(
|
||||
// "human readable" character happens, i.e. newlines, tabs, and Ctrl+C.
|
||||
#[unsafe(method(doCommandBySelector:))]
|
||||
fn do_command_by_selector(&self, command: Sel) {
|
||||
let _entered = debug_span!("doCommandBySelector:").entered();
|
||||
trace_scope!("doCommandBySelector:");
|
||||
|
||||
// We shouldn't forward any character from just committed text, since we'll end up
|
||||
// sending it twice with some IMEs like Korean one. We'll also always send
|
||||
@@ -431,7 +447,7 @@ define_class!(
|
||||
impl WinitView {
|
||||
#[unsafe(method(keyDown:))]
|
||||
fn key_down(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("keyDown:").entered();
|
||||
trace_scope!("keyDown:");
|
||||
{
|
||||
let mut prev_input_source = self.ivars().input_source.borrow_mut();
|
||||
let current_input_source = self.current_input_source();
|
||||
@@ -490,7 +506,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(keyUp:))]
|
||||
fn key_up(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("keyUp:").entered();
|
||||
trace_scope!("keyUp:");
|
||||
|
||||
let event = replace_event(event, self.option_as_alt());
|
||||
self.update_modifiers(&event, false);
|
||||
@@ -507,14 +523,14 @@ define_class!(
|
||||
|
||||
#[unsafe(method(flagsChanged:))]
|
||||
fn flags_changed(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("flagsChanged:").entered();
|
||||
trace_scope!("flagsChanged:");
|
||||
|
||||
self.update_modifiers(event, true);
|
||||
}
|
||||
|
||||
#[unsafe(method(insertTab:))]
|
||||
fn insert_tab(&self, _sender: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("insertTab:").entered();
|
||||
trace_scope!("insertTab:");
|
||||
let window = self.window();
|
||||
if let Some(first_responder) = window.firstResponder() {
|
||||
if *first_responder == ***self {
|
||||
@@ -525,7 +541,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(insertBackTab:))]
|
||||
fn insert_back_tab(&self, _sender: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("insertBackTab:").entered();
|
||||
trace_scope!("insertBackTab:");
|
||||
let window = self.window();
|
||||
if let Some(first_responder) = window.firstResponder() {
|
||||
if *first_responder == ***self {
|
||||
@@ -539,7 +555,7 @@ define_class!(
|
||||
#[unsafe(method(cancelOperation:))]
|
||||
fn cancel_operation(&self, _sender: Option<&AnyObject>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let _entered = debug_span!("cancelOperation:").entered();
|
||||
trace_scope!("cancelOperation:");
|
||||
|
||||
let event = NSApplication::sharedApplication(mtm)
|
||||
.currentEvent()
|
||||
@@ -568,73 +584,71 @@ define_class!(
|
||||
|
||||
#[unsafe(method(mouseDown:))]
|
||||
fn mouse_down(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("mouseDown:").entered();
|
||||
trace_scope!("mouseDown:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Pressed);
|
||||
}
|
||||
|
||||
#[unsafe(method(mouseUp:))]
|
||||
fn mouse_up(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("mouseUp:").entered();
|
||||
trace_scope!("mouseUp:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Released);
|
||||
}
|
||||
|
||||
#[unsafe(method(rightMouseDown:))]
|
||||
fn right_mouse_down(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("rightMouseDown:").entered();
|
||||
trace_scope!("rightMouseDown:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Pressed);
|
||||
}
|
||||
|
||||
#[unsafe(method(rightMouseUp:))]
|
||||
fn right_mouse_up(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("rightMouseUp:").entered();
|
||||
trace_scope!("rightMouseUp:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Released);
|
||||
}
|
||||
|
||||
#[unsafe(method(otherMouseDown:))]
|
||||
fn other_mouse_down(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("otherMouseDown:").entered();
|
||||
trace_scope!("otherMouseDown:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Pressed);
|
||||
}
|
||||
|
||||
#[unsafe(method(otherMouseUp:))]
|
||||
fn other_mouse_up(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("otherMouseUp:").entered();
|
||||
trace_scope!("otherMouseUp:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Released);
|
||||
}
|
||||
|
||||
// No tracing on these because that would be overly verbose
|
||||
|
||||
#[unsafe(method(mouseMoved:))]
|
||||
fn mouse_moved(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("mouseMoved:").entered();
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[unsafe(method(mouseDragged:))]
|
||||
fn mouse_dragged(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("mouseDragged:").entered();
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[unsafe(method(rightMouseDragged:))]
|
||||
fn right_mouse_dragged(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("rightMouseDragged:").entered();
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[unsafe(method(otherMouseDragged:))]
|
||||
fn other_mouse_dragged(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("otherMouseDragged:").entered();
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[unsafe(method(mouseEntered:))]
|
||||
fn mouse_entered(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("mouseEntered:").entered();
|
||||
trace_scope!("mouseEntered:");
|
||||
|
||||
let position = self.mouse_view_point(event).to_physical(self.scale_factor());
|
||||
|
||||
@@ -648,7 +662,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(mouseExited:))]
|
||||
fn mouse_exited(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("mouseExited:").entered();
|
||||
trace_scope!("mouseExited:");
|
||||
|
||||
let position = self.mouse_view_point(event).to_physical(self.scale_factor());
|
||||
|
||||
@@ -662,7 +676,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(scrollWheel:))]
|
||||
fn scroll_wheel(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("scrollWheel:").entered();
|
||||
trace_scope!("scrollWheel:");
|
||||
|
||||
self.mouse_motion(event);
|
||||
|
||||
@@ -701,7 +715,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(magnifyWithEvent:))]
|
||||
fn magnify_with_event(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("magnifyWithEvent:").entered();
|
||||
trace_scope!("magnifyWithEvent:");
|
||||
|
||||
self.mouse_motion(event);
|
||||
|
||||
@@ -723,7 +737,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(smartMagnifyWithEvent:))]
|
||||
fn smart_magnify_with_event(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("smartMagnifyWithEvent:").entered();
|
||||
trace_scope!("smartMagnifyWithEvent:");
|
||||
|
||||
self.mouse_motion(event);
|
||||
|
||||
@@ -732,7 +746,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(rotateWithEvent:))]
|
||||
fn rotate_with_event(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("rotateWithEvent:").entered();
|
||||
trace_scope!("rotateWithEvent:");
|
||||
|
||||
self.mouse_motion(event);
|
||||
|
||||
@@ -754,7 +768,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(pressureChangeWithEvent:))]
|
||||
fn pressure_change_with_event(&self, event: &NSEvent) {
|
||||
let _entered = debug_span!("pressureChangeWithEvent:").entered();
|
||||
trace_scope!("pressureChangeWithEvent:");
|
||||
|
||||
self.queue_event(WindowEvent::TouchpadPressure {
|
||||
device_id: None,
|
||||
@@ -768,13 +782,13 @@ define_class!(
|
||||
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
|
||||
#[unsafe(method(_wantsKeyDownForEvent:))]
|
||||
fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool {
|
||||
let _entered = debug_span!("_wantsKeyDownForEvent:").entered();
|
||||
trace_scope!("_wantsKeyDownForEvent:");
|
||||
true
|
||||
}
|
||||
|
||||
#[unsafe(method(acceptsFirstMouse:))]
|
||||
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
|
||||
let _entered = debug_span!("acceptsFirstMouse:").entered();
|
||||
trace_scope!("acceptsFirstMouse:");
|
||||
self.ivars().accepts_first_mouse
|
||||
}
|
||||
}
|
||||
@@ -794,6 +808,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(),
|
||||
@@ -803,56 +818,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();
|
||||
|
||||
// Ask AppKit to redisplay the layer while the view is being resized so layer-backed
|
||||
// surfaces keep painting.
|
||||
this.setLayerContentsRedrawPolicy(NSViewLayerContentsRedrawPolicy::DuringViewResize);
|
||||
|
||||
// `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
|
||||
}
|
||||
|
||||
@@ -867,37 +835,6 @@ impl WinitView {
|
||||
});
|
||||
}
|
||||
|
||||
fn surface_resized(&self) {
|
||||
let Some(window) = (**self).window() else {
|
||||
return;
|
||||
};
|
||||
let size = self.surface_size();
|
||||
let window_id = window_id(&window);
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.window_event(event_loop, window_id, WindowEvent::SurfaceResized(size));
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the drawable size from the view's backing-coordinate bounds.
|
||||
pub(super) fn surface_size(&self) -> PhysicalSize<u32> {
|
||||
// The view bounds are authoritative for full-size content views and during live resize.
|
||||
// Deriving this from the window frame can exclude custom titlebar content or be stale.
|
||||
let backing_bounds = self.convertRectToBacking(self.bounds());
|
||||
PhysicalSize::new(
|
||||
backing_bounds.size.width.round().max(0.0) as u32,
|
||||
backing_bounds.size.height.round().max(0.0) as u32,
|
||||
)
|
||||
}
|
||||
|
||||
fn redraw_during_live_resize(&self) {
|
||||
let Some(window) = (**self).window() else {
|
||||
return;
|
||||
};
|
||||
if window.inLiveResize() {
|
||||
self.ivars().app_state.handle_redraw(window_id(&window));
|
||||
}
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f64 {
|
||||
self.window().backingScaleFactor() as f64
|
||||
}
|
||||
@@ -1214,92 +1151,3 @@ fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained<NSEven
|
||||
event.copy()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a UTF-16 code unit offset into the corresponding UTF-8 byte offset within `s`.
|
||||
///
|
||||
/// IMEs are not required to send well-formed offsets, so this is defensive: an offset that
|
||||
/// would split a surrogate pair is snapped down to the start of that character, and an
|
||||
/// out-of-bounds offset is clamped to the end of the string (e.g. native Pinyin sends
|
||||
/// out-of-bounds indices, see <https://github.com/alacritty/alacritty/issues/8791>).
|
||||
///
|
||||
/// The mapping is monotone non-decreasing, so applying it to the location and end of an
|
||||
/// `NSRange` (where `location <= end`) can never produce an inverted byte range.
|
||||
fn utf16_to_utf8_offset(s: &str, utf16_offset: usize) -> usize {
|
||||
let mut utf16_pos = 0;
|
||||
for (utf8_pos, ch) in s.char_indices() {
|
||||
if utf16_pos >= utf16_offset {
|
||||
return utf8_pos;
|
||||
}
|
||||
utf16_pos += ch.len_utf16();
|
||||
// The target offset lands strictly inside this character's UTF-16 representation,
|
||||
// i.e. it splits a surrogate pair: snap down to the character boundary.
|
||||
if utf16_pos > utf16_offset {
|
||||
return utf8_pos;
|
||||
}
|
||||
}
|
||||
s.len()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Apply the UTF-16 -> UTF-8 conversion to both ends of a `selectedRange {loc, len}`,
|
||||
/// mirroring what `set_marked_text` does for the emitted `Ime::Preedit` cursor range.
|
||||
fn convert(s: &str, loc: usize, len: usize) -> (usize, usize) {
|
||||
(utf16_to_utf8_offset(s, loc), utf16_to_utf8_offset(s, loc + len))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mid_surrogate_offset_snaps_down() {
|
||||
// "😀a": 😀 is one char = 2 UTF-16 units = 4 UTF-8 bytes; offset 1 is mid-pair.
|
||||
assert_eq!(utf16_to_utf8_offset("\u{1F600}a", 1), 0);
|
||||
// Offset 2 is the boundary just after the pair.
|
||||
assert_eq!(utf16_to_utf8_offset("\u{1F600}a", 2), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_longer_inverted() {
|
||||
// "a😀b" with selectedRange {1,1}: previously emitted (1, 0) -- lower > upper, a
|
||||
// slice-panic vector. The boundary-snapping conversion keeps lower <= upper.
|
||||
assert_eq!(convert("a\u{1F600}b", 1, 1), (1, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefix_preserved_on_mid_pair_collapse() {
|
||||
// "a😀b" with selectedRange {2,0}: previously collapsed to (0, 0), discarding the
|
||||
// valid "a" prefix; now snaps to the char boundary after "a".
|
||||
assert_eq!(convert("a\u{1F600}b", 2, 0), (1, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_bounds_clamps_to_len() {
|
||||
// Subsumes the #4494 `.min(len)` clamp: an out-of-bounds offset maps to the string
|
||||
// length instead of triggering an NSRangeException.
|
||||
assert_eq!(convert("\u{1F600}a", 99, 0), (5, 5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn well_formed_inputs_are_identity() {
|
||||
// The common case (well-formed boundary indices) must be byte-for-byte unchanged.
|
||||
assert_eq!(convert("a\u{1F600}b", 3, 0), (5, 5));
|
||||
assert_eq!(convert("a\u{1F600}b", 4, 0), (6, 6));
|
||||
// BMP multi-byte (Japanese): each char is 1 UTF-16 unit and 3 UTF-8 bytes.
|
||||
assert_eq!(convert("\u{3053}\u{3093}", 1, 1), (3, 6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn monotone_non_decreasing() {
|
||||
// Sweep every UTF-16 offset (including out-of-bounds) over a string mixing BMP and
|
||||
// non-BMP characters and assert the conversion never goes backwards, which is what
|
||||
// guarantees `lower <= upper` for any `NSRange`.
|
||||
let s = "a\u{1F600}b\u{3053}\u{1F4A9}c";
|
||||
let mut prev = 0;
|
||||
for off in 0..=20 {
|
||||
let cur = utf16_to_utf8_offset(s, off);
|
||||
assert!(cur >= prev, "non-monotone at offset {off}: {cur} < {prev}");
|
||||
assert!(cur <= s.len(), "offset {off} mapped past end: {cur} > {}", s.len());
|
||||
prev = cur;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use objc2::rc::{Retained, autoreleasepool};
|
||||
use objc2::{MainThreadMarker, Message, define_class};
|
||||
use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
|
||||
use objc2_foundation::NSObject;
|
||||
use tracing::trace_span;
|
||||
use winit_core::cursor::Cursor;
|
||||
use winit_core::error::RequestError;
|
||||
use winit_core::icon::Icon;
|
||||
@@ -351,13 +350,13 @@ define_class!(
|
||||
impl WinitWindow {
|
||||
#[unsafe(method(canBecomeMainWindow))]
|
||||
fn can_become_main_window(&self) -> bool {
|
||||
let _entered = trace_span!("canBecomeMainWindow").entered();
|
||||
trace_scope!("canBecomeMainWindow");
|
||||
true
|
||||
}
|
||||
|
||||
#[unsafe(method(canBecomeKeyWindow))]
|
||||
fn can_become_key_window(&self) -> bool {
|
||||
let _entered = trace_span!("canBecomeKeyWindow").entered();
|
||||
trace_scope!("canBecomeKeyWindow");
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -375,7 +374,7 @@ define_class!(
|
||||
// it doesn't if window doesn't have NSWindowStyleMask::Titled
|
||||
#[unsafe(method(canBecomeKeyWindow))]
|
||||
fn can_become_key_window(&self) -> bool {
|
||||
let _entered = trace_span!("canBecomeKeyWindow").entered();
|
||||
trace_scope!("canBecomeKeyWindow");
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ use objc2_foundation::{
|
||||
NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
|
||||
NSRect, NSSize, NSString, ns_string,
|
||||
};
|
||||
use tracing::{debug_span, trace, warn};
|
||||
use tracing::{trace, warn};
|
||||
use winit_common::core_foundation::MainRunLoop;
|
||||
use winit_core::cursor::Cursor;
|
||||
use winit_core::error::{NotSupportedError, RequestError};
|
||||
@@ -121,14 +121,14 @@ define_class!(
|
||||
unsafe impl NSWindowDelegate for WindowDelegate {
|
||||
#[unsafe(method(windowShouldClose:))]
|
||||
fn window_should_close(&self, _: Option<&AnyObject>) -> bool {
|
||||
let _entered = debug_span!("windowShouldClose:").entered();
|
||||
trace_scope!("windowShouldClose:");
|
||||
self.queue_event(WindowEvent::CloseRequested);
|
||||
false
|
||||
}
|
||||
|
||||
#[unsafe(method(windowWillClose:))]
|
||||
fn window_will_close(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowWillClose:").entered();
|
||||
trace_scope!("windowWillClose:");
|
||||
// `setDelegate:` retains the previous value and then autoreleases it
|
||||
autoreleasepool(|_| {
|
||||
// Since El Capitan, we need to be careful that delegate methods can't
|
||||
@@ -140,14 +140,14 @@ define_class!(
|
||||
|
||||
#[unsafe(method(windowDidResize:))]
|
||||
fn window_did_resize(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowDidResize:").entered();
|
||||
trace_scope!("windowDidResize:");
|
||||
// NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification.
|
||||
self.emit_move_event();
|
||||
}
|
||||
|
||||
#[unsafe(method(windowWillStartLiveResize:))]
|
||||
fn window_will_start_live_resize(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowWillStartLiveResize:").entered();
|
||||
trace_scope!("windowWillStartLiveResize:");
|
||||
|
||||
let increments = self.ivars().surface_resize_increments.get();
|
||||
self.set_resize_increments_inner(increments);
|
||||
@@ -155,20 +155,20 @@ define_class!(
|
||||
|
||||
#[unsafe(method(windowDidEndLiveResize:))]
|
||||
fn window_did_end_live_resize(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowDidEndLiveResize:").entered();
|
||||
trace_scope!("windowDidEndLiveResize:");
|
||||
self.set_resize_increments_inner(NSSize::new(1., 1.));
|
||||
}
|
||||
|
||||
// This won't be triggered if the move was part of a resize.
|
||||
#[unsafe(method(windowDidMove:))]
|
||||
fn window_did_move(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowDidMove:").entered();
|
||||
trace_scope!("windowDidMove:");
|
||||
self.emit_move_event();
|
||||
}
|
||||
|
||||
#[unsafe(method(windowDidChangeBackingProperties:))]
|
||||
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowDidChangeBackingProperties:").entered();
|
||||
trace_scope!("windowDidChangeBackingProperties:");
|
||||
let scale_factor = self.scale_factor();
|
||||
if scale_factor == self.ivars().previous_scale_factor.get() {
|
||||
return;
|
||||
@@ -184,7 +184,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(windowDidBecomeKey:))]
|
||||
fn window_did_become_key(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowDidBecomeKey:").entered();
|
||||
trace_scope!("windowDidBecomeKey:");
|
||||
// TODO: center the cursor if the window had mouse grab when it
|
||||
// lost focus
|
||||
self.queue_event(WindowEvent::Focused(true));
|
||||
@@ -192,7 +192,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(windowDidResignKey:))]
|
||||
fn window_did_resign_key(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowDidResignKey:").entered();
|
||||
trace_scope!("windowDidResignKey:");
|
||||
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
|
||||
// NSWindowDelegate will receive a didResignKey event despite no event
|
||||
// being received when the modifiers are released. This is because
|
||||
@@ -208,7 +208,7 @@ define_class!(
|
||||
/// Invoked when before enter fullscreen
|
||||
#[unsafe(method(windowWillEnterFullScreen:))]
|
||||
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowWillEnterFullScreen:").entered();
|
||||
trace_scope!("windowWillEnterFullScreen:");
|
||||
|
||||
self.ivars().maximized.set(self.is_zoomed());
|
||||
let mut fullscreen = self.ivars().fullscreen.borrow_mut();
|
||||
@@ -236,7 +236,7 @@ define_class!(
|
||||
/// Invoked when before exit fullscreen
|
||||
#[unsafe(method(windowWillExitFullScreen:))]
|
||||
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowWillExitFullScreen:").entered();
|
||||
trace_scope!("windowWillExitFullScreen:");
|
||||
|
||||
self.ivars().in_fullscreen_transition.set(true);
|
||||
}
|
||||
@@ -247,7 +247,7 @@ define_class!(
|
||||
_: Option<&AnyObject>,
|
||||
proposed_options: NSApplicationPresentationOptions,
|
||||
) -> NSApplicationPresentationOptions {
|
||||
let _entered = debug_span!("window:willUseFullScreenPresentationOptions:").entered();
|
||||
trace_scope!("window:willUseFullScreenPresentationOptions:");
|
||||
// Generally, games will want to disable the menu bar and the dock. Ideally,
|
||||
// this would be configurable by the user. Unfortunately because of our
|
||||
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
|
||||
@@ -270,7 +270,7 @@ define_class!(
|
||||
/// Invoked when entered fullscreen
|
||||
#[unsafe(method(windowDidEnterFullScreen:))]
|
||||
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowDidEnterFullScreen:").entered();
|
||||
trace_scope!("windowDidEnterFullScreen:");
|
||||
self.ivars().initial_fullscreen.set(false);
|
||||
self.ivars().in_fullscreen_transition.set(false);
|
||||
if let Some(target_fullscreen) = self.ivars().target_fullscreen.take() {
|
||||
@@ -281,7 +281,7 @@ define_class!(
|
||||
/// Invoked when exited fullscreen
|
||||
#[unsafe(method(windowDidExitFullScreen:))]
|
||||
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowDidExitFullScreen:").entered();
|
||||
trace_scope!("windowDidExitFullScreen:");
|
||||
|
||||
self.restore_state_from_fullscreen();
|
||||
self.ivars().in_fullscreen_transition.set(false);
|
||||
@@ -308,7 +308,7 @@ define_class!(
|
||||
/// work you may have done to prepare to enter full-screen mode.
|
||||
#[unsafe(method(windowDidFailToEnterFullScreen:))]
|
||||
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowDidFailToEnterFullScreen:").entered();
|
||||
trace_scope!("windowDidFailToEnterFullScreen:");
|
||||
self.ivars().in_fullscreen_transition.set(false);
|
||||
self.ivars().target_fullscreen.replace(None);
|
||||
if self.ivars().initial_fullscreen.get() {
|
||||
@@ -327,7 +327,7 @@ define_class!(
|
||||
// Invoked when the occlusion state of the window changes
|
||||
#[unsafe(method(windowDidChangeOcclusionState:))]
|
||||
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowDidChangeOcclusionState:").entered();
|
||||
trace_scope!("windowDidChangeOcclusionState:");
|
||||
let visible = self.window().occlusionState().contains(NSWindowOcclusionState::Visible);
|
||||
self.queue_event(WindowEvent::Occluded(!visible));
|
||||
|
||||
@@ -348,7 +348,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(windowDidChangeScreen:))]
|
||||
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
|
||||
let _entered = debug_span!("windowDidChangeScreen:").entered();
|
||||
trace_scope!("windowDidChangeScreen:");
|
||||
let is_simple_fullscreen = self.ivars().is_simple_fullscreen.get();
|
||||
if is_simple_fullscreen {
|
||||
if let Some(screen) = self.window().screen() {
|
||||
@@ -362,7 +362,7 @@ define_class!(
|
||||
/// Invoked when the dragged image enters destination bounds or frame
|
||||
#[unsafe(method(draggingEntered:))]
|
||||
fn dragging_entered(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
|
||||
let _entered = debug_span!("draggingEntered:").entered();
|
||||
trace_scope!("draggingEntered:");
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -393,7 +393,7 @@ define_class!(
|
||||
|
||||
#[unsafe(method(wantsPeriodicDraggingUpdates))]
|
||||
fn wants_periodic_dragging_updates(&self) -> bool {
|
||||
let _entered = debug_span!("wantsPeriodicDraggingUpdates:").entered();
|
||||
trace_scope!("wantsPeriodicDraggingUpdates:");
|
||||
true
|
||||
}
|
||||
|
||||
@@ -401,7 +401,7 @@ define_class!(
|
||||
/// modification of the dragging operation or mouse-pointer position.
|
||||
#[unsafe(method(draggingUpdated:))]
|
||||
fn dragging_updated(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
|
||||
let _entered = debug_span!("draggingUpdated:").entered();
|
||||
trace_scope!("draggingUpdated:");
|
||||
|
||||
let dl = sender.draggingLocation();
|
||||
let dl = self.view().convertPoint_fromView(dl, None);
|
||||
@@ -416,14 +416,14 @@ define_class!(
|
||||
/// Invoked when the image is released
|
||||
#[unsafe(method(prepareForDragOperation:))]
|
||||
fn prepare_for_drag_operation(&self, _sender: &NSObject) -> bool {
|
||||
let _entered = debug_span!("prepareForDragOperation:").entered();
|
||||
trace_scope!("prepareForDragOperation:");
|
||||
true
|
||||
}
|
||||
|
||||
/// Invoked after the released image has been removed from the screen
|
||||
#[unsafe(method(performDragOperation:))]
|
||||
fn perform_drag_operation(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
|
||||
let _entered = debug_span!("performDragOperation:").entered();
|
||||
trace_scope!("performDragOperation:");
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -455,13 +455,13 @@ define_class!(
|
||||
/// Invoked when the dragging operation is complete
|
||||
#[unsafe(method(concludeDragOperation:))]
|
||||
fn conclude_drag_operation(&self, _sender: Option<&NSObject>) {
|
||||
let _entered = debug_span!("concludeDragOperation:").entered();
|
||||
trace_scope!("concludeDragOperation:");
|
||||
}
|
||||
|
||||
/// Invoked when the dragging operation is cancelled
|
||||
#[unsafe(method(draggingExited:))]
|
||||
fn dragging_exited(&self, sender: Option<&ProtocolObject<dyn NSDraggingInfo>>) {
|
||||
let _entered = debug_span!("draggingExited:").entered();
|
||||
trace_scope!("draggingExited:");
|
||||
|
||||
let position = sender.map(|sender| {
|
||||
let dl = sender.draggingLocation();
|
||||
@@ -483,7 +483,7 @@ define_class!(
|
||||
change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>,
|
||||
_context: *mut c_void,
|
||||
) {
|
||||
let _entered = debug_span!("observeValueForKeyPath:ofObject:change:context:").entered();
|
||||
trace_scope!("observeValueForKeyPath:ofObject:change:context:");
|
||||
// NOTE: We don't _really_ need to check the key path, as there should only be one, but
|
||||
// in the future we might want to observe other key paths.
|
||||
if key_path == Some(ns_string!("effectiveAppearance")) {
|
||||
@@ -912,7 +912,10 @@ impl WindowDelegate {
|
||||
fn handle_scale_factor_changed(&self, scale_factor: CGFloat) {
|
||||
let window = self.window();
|
||||
|
||||
let suggested_size = self.view().surface_size();
|
||||
let content_size = window.contentRectForFrameRect(window.frame()).size;
|
||||
let content_size = LogicalSize::new(content_size.width, content_size.height);
|
||||
|
||||
let suggested_size = content_size.to_physical(scale_factor);
|
||||
let new_surface_size = Arc::new(Mutex::new(suggested_size));
|
||||
self.queue_event(WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
@@ -1037,7 +1040,9 @@ impl WindowDelegate {
|
||||
|
||||
#[inline]
|
||||
pub fn surface_size(&self) -> PhysicalSize<u32> {
|
||||
self.view().surface_size()
|
||||
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
|
||||
let logical = LogicalSize::new(content_rect.size.width, content_rect.size.height);
|
||||
logical.to_physical(self.scale_factor())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -20,9 +20,6 @@ xkb = ["dep:xkbcommon-dl", "dep:smol_str"]
|
||||
# CoreFoundation
|
||||
core-foundation = ["dep:block2", "dep:objc2", "dep:objc2-core-foundation"]
|
||||
|
||||
# Foundation
|
||||
foundation = ["dep:block2", "dep:objc2", "dep:objc2-foundation"]
|
||||
|
||||
[dependencies]
|
||||
smol_str = { workspace = true, optional = true }
|
||||
tracing.workspace = true
|
||||
@@ -33,7 +30,7 @@ memmap2 = { workspace = true, optional = true }
|
||||
x11-dl = { workspace = true, optional = true }
|
||||
xkbcommon-dl = { workspace = true, optional = true }
|
||||
|
||||
# Foundation / CoreFoundation
|
||||
# CoreFoundation
|
||||
block2 = { workspace = true, optional = true }
|
||||
objc2 = { workspace = true, optional = true }
|
||||
objc2-core-foundation = { workspace = true, optional = true, features = [
|
||||
@@ -43,13 +40,6 @@ objc2-core-foundation = { workspace = true, optional = true, features = [
|
||||
"CFRunLoop",
|
||||
"CFString",
|
||||
] }
|
||||
objc2-foundation = { workspace = true, optional = true, features = [
|
||||
"std",
|
||||
"block2",
|
||||
"NSNotification",
|
||||
"NSString",
|
||||
"NSOperation",
|
||||
] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::cell::Cell;
|
||||
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{CFRetained, CFRunLoop, CFRunLoopMode, kCFRunLoopDefaultMode};
|
||||
use tracing::{Span, error};
|
||||
use tracing::error;
|
||||
|
||||
use super::MainRunLoopObserver;
|
||||
|
||||
@@ -49,8 +49,7 @@ impl MainRunLoop {
|
||||
/// 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:]` to queue a closure to run
|
||||
/// the next time runloop sources are processed.
|
||||
/// 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
|
||||
@@ -74,17 +73,9 @@ impl MainRunLoop {
|
||||
/// 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) {
|
||||
// We use this to run a closure asynchronously at a later point, so it also makes sense to
|
||||
// re-enter the current span when running the queued closure.
|
||||
let span = Span::current();
|
||||
|
||||
// Convert `FnOnce()` to `Block<dyn Fn()>`.
|
||||
let closure = Cell::new(Some(closure));
|
||||
let block = block2::RcBlock::new(move || {
|
||||
// Running this block happens inside a `CFRunLoopSource`, but the spans that we emit for
|
||||
// that are (intentionally) overwritten by entering the span from before.
|
||||
let _enter = span.enter();
|
||||
|
||||
debug_assert!(MainThreadMarker::new().is_some());
|
||||
if let Some(closure) = closure.take() {
|
||||
closure()
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
mod event_loop_proxy;
|
||||
mod main_run_loop;
|
||||
mod main_run_loop_observer;
|
||||
mod tracing_observers;
|
||||
|
||||
pub use self::event_loop_proxy::*;
|
||||
pub use self::main_run_loop::*;
|
||||
pub use self::main_run_loop_observer::*;
|
||||
pub use self::tracing_observers::*;
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{CFIndex, CFRunLoop, CFRunLoopActivity, kCFRunLoopDefaultMode};
|
||||
use tracing::span::EnteredSpan;
|
||||
use tracing::{Level, error, field, span_enabled, trace_span};
|
||||
|
||||
use crate::core_foundation::MainRunLoopObserver;
|
||||
|
||||
/// Create two run loop observers that add TRACE-level [spans][tracing::span].
|
||||
///
|
||||
/// This is useful when debugging run loops, it makes it easier to see in which run loop activity an
|
||||
/// event is triggered inside (if any).
|
||||
///
|
||||
/// When debugging these interactions, it can also be useful to configure your tracing subscriber
|
||||
/// with `.with_span_events(tracing_subscriber::fmt::format::FmtSpan::ACTIVE)` to emit events upon
|
||||
/// entering and exiting these stages.
|
||||
pub fn tracing_observers(
|
||||
mtm: MainThreadMarker,
|
||||
) -> Option<(MainRunLoopObserver, MainRunLoopObserver)> {
|
||||
// Observers are a bit costly, so don't create them if the tracing-level for this module is
|
||||
// configured to disable them.
|
||||
if !span_enabled!(Level::TRACE) {
|
||||
return None;
|
||||
}
|
||||
|
||||
/// The state that we think the runloop is currently in.
|
||||
///
|
||||
/// The order of activities we observe if waiting twice looks something like:
|
||||
/// - CFRunLoopActivity::Entry
|
||||
/// - CFRunLoopActivity::BeforeTimers
|
||||
/// - CFRunLoopActivity::BeforeSources
|
||||
/// - CFRunLoopActivity::BeforeWaiting
|
||||
/// - CFRunLoopActivity::AfterWaiting
|
||||
/// - CFRunLoopActivity::BeforeTimers
|
||||
/// - CFRunLoopActivity::BeforeSources
|
||||
/// - CFRunLoopActivity::BeforeWaiting
|
||||
/// - CFRunLoopActivity::AfterWaiting
|
||||
/// - CFRunLoopActivity::Exit
|
||||
///
|
||||
/// And if not waiting, it looks something like:
|
||||
/// - CFRunLoopActivity::Entry
|
||||
/// - CFRunLoopActivity::BeforeTimers
|
||||
/// - CFRunLoopActivity::BeforeSources
|
||||
/// - CFRunLoopActivity::Exit
|
||||
#[derive(Default)]
|
||||
#[allow(unused)] // EnteredSpans are kept around
|
||||
enum RunLoopState {
|
||||
/// Currently processing `Entry`/`Exit` observers.
|
||||
#[default]
|
||||
Entered,
|
||||
/// Currently processing timers or `BeforeTimers` observers.
|
||||
Timers(EnteredSpan),
|
||||
/// Currently processing sources or `BeforeSources` observers.
|
||||
Sources(EnteredSpan),
|
||||
/// Currently waiting or processing `BeforeWaiting`/`AfterWaiting` observers.
|
||||
Waiting(EnteredSpan),
|
||||
}
|
||||
|
||||
// A list of currently entered (outer) spans and their state.
|
||||
//
|
||||
// This is a list because runloops can be run recursively.
|
||||
let spans: Rc<RefCell<Vec<(EnteredSpan, RunLoopState)>>> = Rc::new(RefCell::new(Vec::new()));
|
||||
let spans_clone = Rc::clone(&spans);
|
||||
|
||||
// An observer at the start of run loop activities.
|
||||
let activities = CFRunLoopActivity::Entry
|
||||
| CFRunLoopActivity::BeforeTimers
|
||||
| CFRunLoopActivity::BeforeSources
|
||||
| CFRunLoopActivity::BeforeWaiting;
|
||||
let start = MainRunLoopObserver::new(mtm, activities, true, CFIndex::MIN, move |activity| {
|
||||
match activity {
|
||||
// Add an outer span for each runloop iteration.
|
||||
CFRunLoopActivity::Entry => {
|
||||
let span = trace_span!("inside runloop", mode = field::Empty);
|
||||
|
||||
// Get the mode dynamically, the observer may added to multiple different modes.
|
||||
let mode = CFRunLoop::current().unwrap().current_mode().unwrap();
|
||||
// Mode isn't interesting if it's the default mode.
|
||||
if &*mode != unsafe { kCFRunLoopDefaultMode }.unwrap() {
|
||||
span.record("mode", field::display(mode));
|
||||
}
|
||||
|
||||
let entered = span.entered();
|
||||
spans.borrow_mut().push((entered, RunLoopState::Entered));
|
||||
},
|
||||
|
||||
// Add inner spans that help inspecting the state the runloop is in.
|
||||
CFRunLoopActivity::BeforeTimers => {
|
||||
if let Some((_, state)) = spans.borrow_mut().last_mut() {
|
||||
*state = RunLoopState::Entered; // Drop any previous spans.
|
||||
*state = RunLoopState::Timers(trace_span!("processing timers").entered());
|
||||
} else {
|
||||
error!("unbalanced observer invocations");
|
||||
}
|
||||
},
|
||||
CFRunLoopActivity::BeforeSources => {
|
||||
if let Some((_, state)) = spans.borrow_mut().last_mut() {
|
||||
*state = RunLoopState::Entered; // Drop any previous spans.
|
||||
*state = RunLoopState::Sources(trace_span!("processing sources").entered());
|
||||
} else {
|
||||
error!("unbalanced observer invocations");
|
||||
}
|
||||
},
|
||||
CFRunLoopActivity::BeforeWaiting => {
|
||||
if let Some((_, state)) = spans.borrow_mut().last_mut() {
|
||||
*state = RunLoopState::Entered; // Drop any previous spans.
|
||||
*state = RunLoopState::Waiting(trace_span!("waiting").entered());
|
||||
} else {
|
||||
error!("unbalanced observer invocations");
|
||||
}
|
||||
},
|
||||
|
||||
activity => unreachable!("unexpected activity: {activity:?}"),
|
||||
}
|
||||
});
|
||||
|
||||
// An observer at the end of run loop activities.
|
||||
let activities = CFRunLoopActivity::AfterWaiting | CFRunLoopActivity::Exit;
|
||||
let end = MainRunLoopObserver::new(mtm, activities, true, CFIndex::MAX, move |activity| {
|
||||
match activity {
|
||||
CFRunLoopActivity::AfterWaiting => {
|
||||
if let Some((_, state)) = spans_clone.borrow_mut().last_mut() {
|
||||
// Transition from the waiting state to the initial state.
|
||||
*state = RunLoopState::Entered;
|
||||
} else {
|
||||
error!("unbalanced observer invocations");
|
||||
}
|
||||
},
|
||||
|
||||
CFRunLoopActivity::Exit => {
|
||||
if let Some((span, state)) = spans_clone.borrow_mut().pop() {
|
||||
drop(state); // Explicitly exit and drop inner span.
|
||||
drop(span); // Explicitly exit and drop outer span.
|
||||
} else {
|
||||
error!("unbalanced observer invocations");
|
||||
}
|
||||
},
|
||||
|
||||
activity => unreachable!("unexpected activity: {activity:?}"),
|
||||
}
|
||||
});
|
||||
|
||||
Some((start, end))
|
||||
}
|
||||
@@ -79,10 +79,6 @@ impl EventHandler {
|
||||
// Allowed, happens if the handler was cleared manually
|
||||
// elsewhere (such as in `applicationWillTerminate:`).
|
||||
},
|
||||
// We use `eprintln!` here over `tracing::error!`, since we're going to abort
|
||||
// immediately after this, and it'd be annoying for the user if they didn't get
|
||||
// any feedback on that if they don't have a tracing subscriber.
|
||||
#[allow(clippy::disallowed_macros)]
|
||||
Err(_) => {
|
||||
// Note: This is not expected to ever happen, this
|
||||
// module generally controls the `RefCell`, and
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
mod notification_center;
|
||||
|
||||
pub use self::notification_center::*;
|
||||
@@ -4,7 +4,5 @@
|
||||
pub mod core_foundation;
|
||||
#[cfg(feature = "event-handler")]
|
||||
pub mod event_handler;
|
||||
#[cfg(feature = "foundation")]
|
||||
pub mod foundation;
|
||||
#[cfg(feature = "xkb")]
|
||||
pub mod xkb;
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -269,20 +263,6 @@ pub enum WindowEvent {
|
||||
button: ButtonSource,
|
||||
},
|
||||
|
||||
/// Multi-finger hold gesture on the touchpad or touchscreen without movement.
|
||||
///
|
||||
/// The `phase` field indicates the lifecycle of the hold gesture:
|
||||
/// - `Started`: One or more fingers are in contact with the touchpad/touchscreen.
|
||||
/// - `Ended`: All fingers have been lifted from the touchpad/touchscreen.
|
||||
/// - `Cancelled`: The hold gesture was interrupted, for example when another finger touches the
|
||||
/// touchpad (causing a new `Started` event with more fingers), or when movement begins and
|
||||
/// transitions to other gestures like pinch, pan, or rotation.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - Only available on **Wayland**.
|
||||
HoldGesture { device_id: Option<DeviceId>, phase: TouchPhase },
|
||||
|
||||
/// Two-finger pinch gesture, often used for magnification.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
@@ -1020,17 +1000,9 @@ pub enum Ime {
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum TouchPhase {
|
||||
/// Initial touch contact or gesture start, for example when one or more fingers touch the
|
||||
/// screen or touchpad.
|
||||
Started,
|
||||
/// The touch contact point changed, for example without lifting the finger.
|
||||
Moved,
|
||||
/// All touch contact points have been lifted from the touchscreen or touchpad.
|
||||
///
|
||||
/// This event is important as it should clear any state or event in flight that was
|
||||
/// generated by the preceding `Started` and `Moved` events.
|
||||
Ended,
|
||||
/// The event was cancelled and should cancel any event in flight and clear state.
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
|
||||
@@ -752,10 +752,11 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
|
||||
/// The inset area of the surface that is unobstructed.
|
||||
///
|
||||
/// On some devices, especially mobile devices, the screen is not a perfect rectangle, and may
|
||||
/// have rounded corners, notches, bezels, and so on. When drawing your content, you usually
|
||||
/// want to draw your background and other such unimportant content on the entire surface, while
|
||||
/// you will want to restrict important content such as text, interactable or visual indicators
|
||||
/// to the part of the screen that is actually visible; for this, you use the safe area.
|
||||
/// have rounded corners, notches, bezels, and so on. Additionally, a soft keyboard may be open.
|
||||
/// When drawing your content, you usually want to draw your background and other such
|
||||
/// unimportant content on the entire surface, while you will want to restrict important content
|
||||
/// such as text, interactable or visual indicators to the part of the screen that is actually
|
||||
/// visible; for this, you use the safe area.
|
||||
///
|
||||
/// The safe area is a rectangle that is defined relative to the origin at the top-left corner
|
||||
/// of the surface, and the size extending downwards to the right. The area will not extend
|
||||
@@ -880,8 +881,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS / X11 / Web / Windows:** Unsupported.
|
||||
/// - **Wayland:** Only works with `org_kde_kwin_blur_manager` or
|
||||
/// `ext_background_effect_manager_v1` protocol.
|
||||
/// - **Wayland:** Only works with org_kde_kwin_blur_manager protocol.
|
||||
fn set_blur(&self, blur: bool);
|
||||
|
||||
/// Modifies the window's visibility.
|
||||
|
||||
@@ -32,6 +32,7 @@ objc2-core-foundation = { workspace = true, features = [
|
||||
"CFRunLoop",
|
||||
"CFString",
|
||||
] }
|
||||
objc2-core-graphics = { workspace = true, features = ["std", "CGGeometry"] }
|
||||
objc2-foundation = { workspace = true, features = [
|
||||
"std",
|
||||
"block2",
|
||||
@@ -47,14 +48,13 @@ objc2-foundation = { workspace = true, features = [
|
||||
] }
|
||||
objc2-ui-kit = { workspace = true, features = [
|
||||
"std",
|
||||
"block2",
|
||||
"objc2-core-foundation",
|
||||
"UIApplication",
|
||||
"UIDevice",
|
||||
"UIEvent",
|
||||
"UIGeometry",
|
||||
"UIGestureRecognizer",
|
||||
"UITextInput",
|
||||
"UITextInputTraits",
|
||||
"UIOrientation",
|
||||
"UIPanGestureRecognizer",
|
||||
"UIPinchGestureRecognizer",
|
||||
@@ -63,13 +63,17 @@ objc2-ui-kit = { workspace = true, features = [
|
||||
"UIScreen",
|
||||
"UIScreenMode",
|
||||
"UITapGestureRecognizer",
|
||||
"UITextInput",
|
||||
"UITextInputTraits",
|
||||
"UITouch",
|
||||
"UITraitCollection",
|
||||
"UIView",
|
||||
"UIViewAnimating",
|
||||
"UIViewController",
|
||||
"UIViewPropertyAnimator",
|
||||
"UIWindow",
|
||||
] }
|
||||
winit-common = { workspace = true, features = ["core-foundation", "event-handler", "foundation"] }
|
||||
winit-common = { workspace = true, features = ["core-foundation", "event-handler"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -309,7 +309,7 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
|
||||
|
||||
for event in events {
|
||||
if !processing_redraws && event.is_redraw() {
|
||||
tracing::info!("processing `RedrawRequested` during the main event loop");
|
||||
// tracing::info!("processing `RedrawRequested` during the main event loop");
|
||||
} else if processing_redraws && !event.is_redraw() {
|
||||
tracing::warn!(
|
||||
"processing non `RedrawRequested` event after the main event loop: {:#?}",
|
||||
@@ -327,7 +327,7 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
|
||||
|
||||
for event in queued_events {
|
||||
if !processing_redraws && event.is_redraw() {
|
||||
tracing::info!("processing `RedrawRequested` during the main event loop");
|
||||
// tracing::info!("processing `RedrawRequested` during the main event loop");
|
||||
} else if processing_redraws && !event.is_redraw() {
|
||||
tracing::warn!(
|
||||
"processing non-`RedrawRequested` event after the main event loop: {:#?}",
|
||||
|
||||
@@ -12,9 +12,7 @@ use objc2_ui_kit::{
|
||||
UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen,
|
||||
};
|
||||
use rwh_06::HasDisplayHandle;
|
||||
use tracing::debug_span;
|
||||
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
|
||||
use winit_common::foundation::create_observer;
|
||||
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};
|
||||
@@ -26,6 +24,7 @@ use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
|
||||
use winit_core::window::{Theme, Window as CoreWindow};
|
||||
|
||||
use super::app_state::{AppState, send_occluded_event_for_all_windows};
|
||||
use super::notification_center::create_observer;
|
||||
use crate::monitor::MonitorHandle;
|
||||
use crate::window::Window;
|
||||
use crate::{app_state, monitor};
|
||||
@@ -135,7 +134,6 @@ pub struct EventLoop {
|
||||
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
|
||||
_tracing_observers: Option<(MainRunLoopObserver, MainRunLoopObserver)>,
|
||||
_wakeup_observer: MainRunLoopObserver,
|
||||
_main_events_cleared_observer: MainRunLoopObserver,
|
||||
_events_cleared_observer: MainRunLoopObserver,
|
||||
@@ -161,7 +159,6 @@ impl EventLoop {
|
||||
// `application:didFinishLaunchingWithOptions:`
|
||||
unsafe { UIApplicationDidFinishLaunchingNotification },
|
||||
move |_| {
|
||||
let _entered = debug_span!("UIApplicationDidFinishLaunchingNotification").entered();
|
||||
app_state::did_finish_launching(mtm);
|
||||
},
|
||||
);
|
||||
@@ -169,27 +166,19 @@ impl EventLoop {
|
||||
¢er,
|
||||
// `applicationDidBecomeActive:`
|
||||
unsafe { UIApplicationDidBecomeActiveNotification },
|
||||
move |_| {
|
||||
let _entered = debug_span!("UIApplicationDidBecomeActiveNotification").entered();
|
||||
app_state::handle_resumed(mtm)
|
||||
},
|
||||
move |_| app_state::handle_resumed(mtm),
|
||||
);
|
||||
let _will_resign_active_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationWillResignActive:`
|
||||
unsafe { UIApplicationWillResignActiveNotification },
|
||||
move |_| {
|
||||
let _entered = debug_span!("UIApplicationWillResignActiveNotification").entered();
|
||||
app_state::handle_suspended(mtm)
|
||||
},
|
||||
move |_| app_state::handle_suspended(mtm),
|
||||
);
|
||||
let _will_enter_foreground_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationWillEnterForeground:`
|
||||
unsafe { UIApplicationWillEnterForegroundNotification },
|
||||
move |notification| {
|
||||
let _entered =
|
||||
debug_span!("UIApplicationWillEnterForegroundNotification").entered();
|
||||
let app = notification.object().expect(
|
||||
"UIApplicationWillEnterForegroundNotification to have application object",
|
||||
);
|
||||
@@ -204,7 +193,6 @@ impl EventLoop {
|
||||
// `applicationDidEnterBackground:`
|
||||
unsafe { UIApplicationDidEnterBackgroundNotification },
|
||||
move |notification| {
|
||||
let _entered = debug_span!("UIApplicationDidEnterBackgroundNotification").entered();
|
||||
let app = notification.object().expect(
|
||||
"UIApplicationDidEnterBackgroundNotification to have application object",
|
||||
);
|
||||
@@ -219,7 +207,6 @@ impl EventLoop {
|
||||
// `applicationWillTerminate:`
|
||||
unsafe { UIApplicationWillTerminateNotification },
|
||||
move |notification| {
|
||||
let _entered = debug_span!("UIApplicationWillTerminateNotification").entered();
|
||||
let app = notification
|
||||
.object()
|
||||
.expect("UIApplicationWillTerminateNotification to have application object");
|
||||
@@ -233,29 +220,18 @@ impl EventLoop {
|
||||
¢er,
|
||||
// `applicationDidReceiveMemoryWarning:`
|
||||
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
|
||||
move |_| {
|
||||
let _entered =
|
||||
debug_span!("UIApplicationDidReceiveMemoryWarningNotification").entered();
|
||||
app_state::handle_memory_warning(mtm)
|
||||
},
|
||||
move |_| app_state::handle_memory_warning(mtm),
|
||||
);
|
||||
|
||||
let main_loop = MainRunLoop::get(mtm);
|
||||
let mode = unsafe { kCFRunLoopDefaultMode }.unwrap();
|
||||
|
||||
// Tracing observers have the lowest and highest orderings.
|
||||
let _tracing_observers = tracing_observers(mtm).inspect(|(start, end)| {
|
||||
main_loop.add_observer(start, mode);
|
||||
main_loop.add_observer(end, mode);
|
||||
});
|
||||
|
||||
let _wakeup_observer = MainRunLoopObserver::new(
|
||||
mtm,
|
||||
CFRunLoopActivity::AfterWaiting,
|
||||
true,
|
||||
// Queued with the second-highest priority (tracing observers use the highest) to
|
||||
// ensure it is processed before other observers.
|
||||
CFIndex::MIN + 1,
|
||||
// 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);
|
||||
@@ -265,17 +241,17 @@ impl EventLoop {
|
||||
CFRunLoopActivity::BeforeWaiting,
|
||||
true,
|
||||
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
|
||||
// `CA::Transaction::ensure_implicit` with a priority of `2000000`. We set the
|
||||
// `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 `2000000` was determined by inspecting stack traces and the associated
|
||||
// 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 `2000000` on iPhone 8, iOS 13 beta 4.
|
||||
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
|
||||
0,
|
||||
move |_| app_state::handle_main_events_cleared(mtm),
|
||||
);
|
||||
@@ -285,9 +261,8 @@ impl EventLoop {
|
||||
mtm,
|
||||
CFRunLoopActivity::BeforeWaiting,
|
||||
true,
|
||||
// Queued with the second-lowest priority (tracing observers use the lowest) to ensure
|
||||
// it is processed after other observers.
|
||||
CFIndex::MAX - 1,
|
||||
// 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);
|
||||
@@ -302,7 +277,6 @@ impl EventLoop {
|
||||
_did_enter_background_observer,
|
||||
_will_terminate_observer,
|
||||
_did_receive_memory_warning_observer,
|
||||
_tracing_observers,
|
||||
_wakeup_observer,
|
||||
_main_events_cleared_observer,
|
||||
_events_cleared_observer,
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
mod app_state;
|
||||
mod event_loop;
|
||||
mod monitor;
|
||||
mod notification_center;
|
||||
mod view;
|
||||
mod view_controller;
|
||||
mod window;
|
||||
|
||||
1
winit-uikit/src/notification_center.rs
Symbolic link
1
winit-uikit/src/notification_center.rs
Symbolic link
@@ -0,0 +1 @@
|
||||
../../winit-appkit/src/notification_center.rs
|
||||
@@ -13,7 +13,7 @@ use objc2_ui_kit::{
|
||||
UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, UITextInputTraits, UITouch,
|
||||
UITouchPhase, UITouchType, UITraitEnvironment, UIView,
|
||||
};
|
||||
use tracing::{debug, debug_span, trace_span};
|
||||
use tracing::debug;
|
||||
use winit_core::event::{
|
||||
ButtonSource, ElementState, FingerId, Force, KeyEvent, PointerKind, PointerSource,
|
||||
TabletToolAngle, TabletToolButton, TabletToolData, TabletToolKind, TouchPhase, WindowEvent,
|
||||
@@ -48,7 +48,6 @@ define_class!(
|
||||
impl WinitView {
|
||||
#[unsafe(method(drawRect:))]
|
||||
fn draw_rect(&self, rect: CGRect) {
|
||||
let _entered = debug_span!("drawRect:").entered();
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let window = self.window().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
|
||||
@@ -60,7 +59,6 @@ define_class!(
|
||||
|
||||
#[unsafe(method(layoutSubviews))]
|
||||
fn layout_subviews(&self) {
|
||||
let _entered = debug_span!("layoutSubviews").entered();
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
|
||||
|
||||
@@ -81,7 +79,6 @@ define_class!(
|
||||
|
||||
#[unsafe(method(setContentScaleFactor:))]
|
||||
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
|
||||
let _entered = debug_span!("setContentScaleFactor:").entered();
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
let _: () =
|
||||
unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] };
|
||||
@@ -127,39 +124,33 @@ define_class!(
|
||||
|
||||
#[unsafe(method(safeAreaInsetsDidChange))]
|
||||
fn safe_area_changed(&self) {
|
||||
let _entered = debug_span!("safeAreaInsetsDidChange").entered();
|
||||
debug!("safeAreaInsetsDidChange was called, requesting redraw");
|
||||
println!("safeAreaInsetsDidChange was called, requesting redraw");
|
||||
// When the safe area changes we want to make sure to emit a redraw event
|
||||
self.setNeedsDisplay();
|
||||
}
|
||||
|
||||
#[unsafe(method(touchesBegan:withEvent:))]
|
||||
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
let _entered = debug_span!("touchesBegan:withEvent:").entered();
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[unsafe(method(touchesMoved:withEvent:))]
|
||||
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
let _entered = debug_span!("touchesMoved:withEvent:").entered();
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[unsafe(method(touchesEnded:withEvent:))]
|
||||
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
let _entered = debug_span!("touchesEnded:withEvent:").entered();
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[unsafe(method(touchesCancelled:withEvent:))]
|
||||
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
let _entered = debug_span!("touchesCancelled:withEvent:").entered();
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[unsafe(method(pinchGesture:))]
|
||||
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
|
||||
let _entered = debug_span!("pinchGesture:").entered();
|
||||
let window = self.window().unwrap();
|
||||
|
||||
let (phase, delta) = match recognizer.state() {
|
||||
@@ -194,7 +185,6 @@ define_class!(
|
||||
|
||||
#[unsafe(method(doubleTapGesture:))]
|
||||
fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) {
|
||||
let _entered = debug_span!("doubleTapGesture:").entered();
|
||||
let window = self.window().unwrap();
|
||||
|
||||
if recognizer.state() == UIGestureRecognizerState::Ended {
|
||||
@@ -210,7 +200,6 @@ define_class!(
|
||||
|
||||
#[unsafe(method(rotationGesture:))]
|
||||
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
|
||||
let _entered = debug_span!("rotationGesture:").entered();
|
||||
let window = self.window().unwrap();
|
||||
|
||||
let (phase, delta) = match recognizer.state() {
|
||||
@@ -255,7 +244,6 @@ define_class!(
|
||||
|
||||
#[unsafe(method(panGesture:))]
|
||||
fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) {
|
||||
let _entered = debug_span!("panGesture:").entered();
|
||||
let window = self.window().unwrap();
|
||||
|
||||
let translation = recognizer.translationInView(Some(self));
|
||||
@@ -308,7 +296,6 @@ define_class!(
|
||||
|
||||
#[unsafe(method(canBecomeFirstResponder))]
|
||||
fn can_become_first_responder(&self) -> bool {
|
||||
let _entered = trace_span!("canBecomeFirstResponder").entered();
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -322,10 +309,6 @@ define_class!(
|
||||
_gesture_recognizer: &UIGestureRecognizer,
|
||||
_other_gesture_recognizer: &UIGestureRecognizer,
|
||||
) -> bool {
|
||||
let _entered = trace_span!(
|
||||
"gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:"
|
||||
)
|
||||
.entered();
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -335,19 +318,16 @@ define_class!(
|
||||
unsafe impl UIKeyInput for WinitView {
|
||||
#[unsafe(method(hasText))]
|
||||
fn has_text(&self) -> bool {
|
||||
let _entered = debug_span!("hasText").entered();
|
||||
true
|
||||
}
|
||||
|
||||
#[unsafe(method(insertText:))]
|
||||
fn insert_text(&self, text: &NSString) {
|
||||
let _entered = debug_span!("insertText:").entered();
|
||||
self.handle_insert_text(text)
|
||||
}
|
||||
|
||||
#[unsafe(method(deleteBackward))]
|
||||
fn delete_backward(&self) {
|
||||
let _entered = debug_span!("deleteBackward").entered();
|
||||
self.handle_delete_backward()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
use std::cell::Cell;
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{DefinedClass, MainThreadMarker, available, define_class, msg_send};
|
||||
use objc2_foundation::NSObject;
|
||||
use objc2_ui_kit::{
|
||||
UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle,
|
||||
UIUserInterfaceIdiom, UIView, UIViewController,
|
||||
use block2::RcBlock;
|
||||
use objc2::rc::{Retained, Weak};
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2::{
|
||||
DefinedClass, MainThreadMarker, MainThreadOnly, Message, available, define_class, msg_send,
|
||||
};
|
||||
use objc2_core_foundation::{CFTimeInterval, CGRect};
|
||||
use objc2_core_graphics::CGRectIntersection;
|
||||
use objc2_foundation::{
|
||||
NSNotification, NSNotificationCenter, NSNumber, NSObject, NSObjectProtocol, NSValue,
|
||||
};
|
||||
use objc2_ui_kit::{
|
||||
UICoordinateSpace, UIDevice, UIEdgeInsets, UIInterfaceOrientationMask,
|
||||
UIKeyboardAnimationCurveUserInfoKey, UIKeyboardAnimationDurationUserInfoKey,
|
||||
UIKeyboardFrameBeginUserInfoKey, UIKeyboardFrameEndUserInfoKey,
|
||||
UIKeyboardWillChangeFrameNotification, UIRectEdge, UIResponder, UIScreen, UIStatusBarStyle,
|
||||
UIUserInterfaceIdiom, UIView, UIViewAnimating, UIViewAnimationCurve, UIViewAnimationOptions,
|
||||
UIViewController, UIViewPropertyAnimator,
|
||||
};
|
||||
use tracing::trace_span;
|
||||
|
||||
use crate::notification_center::create_observer;
|
||||
use crate::{ScreenEdge, StatusBarStyle, ValidOrientations, WindowAttributesIos};
|
||||
|
||||
pub struct ViewControllerState {
|
||||
@@ -17,6 +29,9 @@ pub struct ViewControllerState {
|
||||
prefers_home_indicator_auto_hidden: Cell<bool>,
|
||||
supported_orientations: Cell<UIInterfaceOrientationMask>,
|
||||
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
|
||||
// Keep observer around (deallocating it stops notifications being posted).
|
||||
keyboard_will_change_frame_observer:
|
||||
Cell<Option<Retained<ProtocolObject<dyn NSObjectProtocol>>>>,
|
||||
}
|
||||
|
||||
define_class!(
|
||||
@@ -29,37 +44,31 @@ define_class!(
|
||||
impl WinitViewController {
|
||||
#[unsafe(method(shouldAutorotate))]
|
||||
fn should_autorotate(&self) -> bool {
|
||||
let _entered = trace_span!("shouldAutorotate").entered();
|
||||
true
|
||||
}
|
||||
|
||||
#[unsafe(method(prefersStatusBarHidden))]
|
||||
fn prefers_status_bar_hidden(&self) -> bool {
|
||||
let _entered = trace_span!("prefersStatusBarHidden").entered();
|
||||
self.ivars().prefers_status_bar_hidden.get()
|
||||
}
|
||||
|
||||
#[unsafe(method(preferredStatusBarStyle))]
|
||||
fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
|
||||
let _entered = trace_span!("preferredStatusBarStyle").entered();
|
||||
self.ivars().preferred_status_bar_style.get()
|
||||
}
|
||||
|
||||
#[unsafe(method(prefersHomeIndicatorAutoHidden))]
|
||||
fn prefers_home_indicator_auto_hidden(&self) -> bool {
|
||||
let _entered = trace_span!("prefersHomeIndicatorAutoHidden").entered();
|
||||
self.ivars().prefers_home_indicator_auto_hidden.get()
|
||||
}
|
||||
|
||||
#[unsafe(method(supportedInterfaceOrientations))]
|
||||
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
|
||||
let _entered = trace_span!("supportedInterfaceOrientations").entered();
|
||||
self.ivars().supported_orientations.get()
|
||||
}
|
||||
|
||||
#[unsafe(method(preferredScreenEdgesDeferringSystemGestures))]
|
||||
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
|
||||
let _entered = trace_span!("preferredScreenEdgesDeferringSystemGestures").entered();
|
||||
self.ivars().preferred_screen_edges_deferring_system_gestures.get()
|
||||
}
|
||||
}
|
||||
@@ -145,6 +154,7 @@ impl WinitViewController {
|
||||
prefers_home_indicator_auto_hidden: Cell::new(false),
|
||||
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
|
||||
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::empty()),
|
||||
keyboard_will_change_frame_observer: Cell::new(None),
|
||||
});
|
||||
let this: Retained<Self> = unsafe { msg_send![super(this), init] };
|
||||
|
||||
@@ -160,8 +170,115 @@ impl WinitViewController {
|
||||
ios_attributes.preferred_screen_edges_deferring_system_gestures,
|
||||
);
|
||||
|
||||
let center = NSNotificationCenter::defaultCenter();
|
||||
|
||||
this.setView(Some(view));
|
||||
|
||||
// Set up an observer that will make the `safeAreaRect` of the view update based on the soft
|
||||
// keyboard's presence (in addition to everything else that the safe area depends on).
|
||||
let controller_weak = Weak::from_retained(&this);
|
||||
this.ivars().keyboard_will_change_frame_observer.set(Some(create_observer(
|
||||
¢er,
|
||||
unsafe { UIKeyboardWillChangeFrameNotification },
|
||||
move |notification| {
|
||||
eprintln!("UIKeyboardWillChangeFrameNotification");
|
||||
if let Some(controller) = controller_weak.load() {
|
||||
keyboard_will_change_frame(&controller, notification);
|
||||
}
|
||||
},
|
||||
)));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// The current keyboard frame, in the view's coordinate space.
|
||||
pub(crate) fn current_keyboard_frame(&self) -> CGRect {
|
||||
// TODO: Combine start_frame and end_frame with `animator.fractionComplete()` to produce
|
||||
// current frame
|
||||
|
||||
// Convert keyboard frame to view coordinates.
|
||||
let keyboard_frame = self
|
||||
.view()
|
||||
.unwrap()
|
||||
.convertRect_fromCoordinateSpace(frame, &keyboard_screen.coordinateSpace());
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
fn keyboard_will_change_frame(controller: &WinitViewController, notification: &NSNotification) {
|
||||
let mtm = controller.mtm();
|
||||
let controller = controller.retain();
|
||||
let view = controller.view().unwrap();
|
||||
|
||||
// The notification's object is the screen the keyboard appears on (since iOS 16).
|
||||
let keyboard_screen = notification
|
||||
.object()
|
||||
.map(|s| s.downcast::<UIScreen>().unwrap())
|
||||
.unwrap_or_else(|| view.window().unwrap().screen());
|
||||
|
||||
let user_info = notification.userInfo().unwrap();
|
||||
let begin_frame = user_info
|
||||
.objectForKey(unsafe { UIKeyboardFrameBeginUserInfoKey })
|
||||
.unwrap()
|
||||
.downcast::<NSValue>()
|
||||
.unwrap()
|
||||
.get_rect()
|
||||
.unwrap();
|
||||
let end_frame = user_info
|
||||
.objectForKey(unsafe { UIKeyboardFrameEndUserInfoKey })
|
||||
.unwrap()
|
||||
.downcast::<NSValue>()
|
||||
.unwrap()
|
||||
.get_rect()
|
||||
.unwrap();
|
||||
let duration: CFTimeInterval = user_info
|
||||
.objectForKey(unsafe { UIKeyboardAnimationDurationUserInfoKey })
|
||||
.unwrap()
|
||||
.downcast::<NSNumber>()
|
||||
.unwrap()
|
||||
.doubleValue();
|
||||
let curve_raw = user_info
|
||||
.objectForKey(unsafe { UIKeyboardAnimationCurveUserInfoKey })
|
||||
.unwrap()
|
||||
.downcast::<NSNumber>()
|
||||
.unwrap()
|
||||
.integerValue();
|
||||
let curve = UIViewAnimationCurve(curve_raw);
|
||||
|
||||
// If OS version is high enough, set up a `UIViewPropertyAnimator` to track the position of the
|
||||
// keyboard.
|
||||
if available!(ios = 10.0, tvos = 10.0, visionos = 1.0) {
|
||||
let animator = UIViewPropertyAnimator::initWithDuration_curve_animations(
|
||||
UIViewPropertyAnimator::alloc(mtm),
|
||||
duration,
|
||||
curve,
|
||||
None,
|
||||
);
|
||||
|
||||
animator.addCompletion(&RcBlock::new(move |_| {
|
||||
controller.setAdditionalSafeAreaInsets(todo!());
|
||||
// TODO: Might need to do further work to update the safe area when we
|
||||
// move the view?
|
||||
|
||||
view.layoutIfNeeded();
|
||||
// Safe area changed -> request redraw.
|
||||
view.setNeedsDisplay();
|
||||
}));
|
||||
|
||||
animator.startAnimation();
|
||||
} else {
|
||||
// Update immediately.
|
||||
todo!()
|
||||
}
|
||||
|
||||
// Not sufficient, `setAdditionalSafeAreaInsets` only updates at the start, it doesn't change
|
||||
// `safeAreaInsets` continously during the keyboard open animation.
|
||||
//
|
||||
// UIView::animateWithDuration_delay_options_animations_completion(
|
||||
// duration,
|
||||
// 0.0,
|
||||
// options,
|
||||
// None,
|
||||
// mtm,
|
||||
// );
|
||||
}
|
||||
|
||||
@@ -11,12 +11,13 @@ use dpi::{
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{MainThreadMarker, available, class, define_class, msg_send};
|
||||
use objc2_core_foundation::{CGFloat, CGPoint, CGRect, CGSize};
|
||||
use objc2_core_graphics::CGRectIntersection;
|
||||
use objc2_foundation::{NSObject, NSObjectProtocol};
|
||||
use objc2_ui_kit::{
|
||||
UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen,
|
||||
UIScreenOverscanCompensation, UIViewController, UIWindow,
|
||||
};
|
||||
use tracing::{debug, debug_span, warn};
|
||||
use tracing::{debug, warn};
|
||||
use winit_core::cursor::Cursor;
|
||||
use winit_core::error::{NotSupportedError, RequestError};
|
||||
use winit_core::event::WindowEvent;
|
||||
@@ -46,7 +47,6 @@ define_class!(
|
||||
impl WinitUIWindow {
|
||||
#[unsafe(method(becomeKeyWindow))]
|
||||
fn become_key_window(&self) {
|
||||
let _entered = debug_span!("becomeKeyWindow").entered();
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
|
||||
window_id: self.id(),
|
||||
@@ -57,7 +57,6 @@ define_class!(
|
||||
|
||||
#[unsafe(method(resignKeyWindow))]
|
||||
fn resign_key_window(&self) {
|
||||
let _entered = debug_span!("resignKeyWindow").entered();
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
|
||||
window_id: self.id(),
|
||||
@@ -204,7 +203,7 @@ impl Inner {
|
||||
}
|
||||
|
||||
pub fn safe_area(&self) -> PhysicalInsets<u32> {
|
||||
let insets = if available!(ios = 11.0, tvos = 11.0, visionos = 1.0) {
|
||||
let device_insets = if available!(ios = 11.0, tvos = 11.0, visionos = 1.0) {
|
||||
self.view.safeAreaInsets()
|
||||
} else {
|
||||
// Assume the status bar frame is the only thing that obscures the view
|
||||
@@ -213,7 +212,17 @@ impl Inner {
|
||||
let status_bar_frame = app.statusBarFrame();
|
||||
UIEdgeInsets { top: status_bar_frame.size.height, left: 0.0, bottom: 0.0, right: 0.0 }
|
||||
};
|
||||
let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right);
|
||||
|
||||
let keyboard_frame = self.view_controller.current_keyboard_frame();
|
||||
let intersection = CGRectIntersection(self.view.bounds(), keyboard_frame);
|
||||
|
||||
let insets = LogicalInsets::new(
|
||||
device_insets.top,
|
||||
device_insets.left,
|
||||
// Assume that the keyboard appears from the bottom.
|
||||
device_insets.bottom + intersection.size.height,
|
||||
device_insets.right,
|
||||
);
|
||||
insets.to_physical(self.scale_factor())
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ sctk = { package = "smithay-client-toolkit", version = "0.20.0", default-feature
|
||||
sctk-adwaita = { version = "0.11.0", default-features = false, optional = true }
|
||||
wayland-backend = { version = "0.3.10", default-features = false, features = ["client_system"] }
|
||||
wayland-client = "0.31.10"
|
||||
wayland-protocols = { version = "0.32.11", features = ["staging", "unstable"] }
|
||||
wayland-protocols = { version = "0.32.8", features = ["staging"] }
|
||||
wayland-protocols-plasma = { version = "0.3.8", features = ["client"] }
|
||||
winit-common = { workspace = true, features = ["xkb", "wayland"] }
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::
|
||||
use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
|
||||
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
|
||||
use tracing::warn;
|
||||
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_hold_v1::ZwpPointerGestureHoldV1;
|
||||
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1;
|
||||
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_seat_v2::ZwpTabletSeatV2;
|
||||
use winit_core::event::WindowEvent;
|
||||
@@ -61,9 +60,6 @@ pub struct WinitSeatState {
|
||||
/// The pinch pointer gesture bound on the seat.
|
||||
pointer_gesture_pinch: Option<ZwpPointerGesturePinchV1>,
|
||||
|
||||
/// The hold pointer gesture bound on the seat.
|
||||
pointer_gesture_hold: Option<ZwpPointerGestureHoldV1>,
|
||||
|
||||
/// The keyboard bound on the seat.
|
||||
keyboard_state: Option<KeyboardState>,
|
||||
|
||||
@@ -145,14 +141,6 @@ impl SeatHandler for WinitState {
|
||||
)
|
||||
});
|
||||
|
||||
seat_state.pointer_gesture_hold = self.pointer_gestures.as_ref().map(|manager| {
|
||||
manager.get_hold_gesture(
|
||||
themed_pointer.pointer(),
|
||||
queue_handle,
|
||||
PointerGestureData::default(),
|
||||
)
|
||||
});
|
||||
|
||||
let themed_pointer = Arc::new(themed_pointer);
|
||||
|
||||
// Register cursor surface.
|
||||
@@ -221,10 +209,6 @@ impl SeatHandler for WinitState {
|
||||
pointer_gesture_pinch.destroy();
|
||||
}
|
||||
|
||||
if let Some(pointer_gesture_hold) = seat_state.pointer_gesture_hold.take() {
|
||||
pointer_gesture_hold.destroy();
|
||||
}
|
||||
|
||||
if let Some(pointer) = seat_state.pointer.take() {
|
||||
let pointer_data = pointer.pointer().winit_data();
|
||||
|
||||
|
||||
@@ -7,12 +7,9 @@ use sctk::globals::GlobalData;
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, delegate_dispatch};
|
||||
use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::{
|
||||
Event as PinchEvent, ZwpPointerGesturePinchV1,
|
||||
Event, ZwpPointerGesturePinchV1,
|
||||
};
|
||||
use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gestures_v1::ZwpPointerGesturesV1;
|
||||
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_hold_v1::{
|
||||
Event as HoldEvent, ZwpPointerGestureHoldV1,
|
||||
};
|
||||
use winit_core::event::{TouchPhase, WindowEvent};
|
||||
use winit_core::window::WindowId;
|
||||
|
||||
@@ -30,7 +27,7 @@ impl PointerGesturesState {
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let pointer_gestures = globals.bind(queue_handle, 3..=3, GlobalData)?;
|
||||
let pointer_gestures = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self { pointer_gestures })
|
||||
}
|
||||
}
|
||||
@@ -73,49 +70,6 @@ impl Dispatch<ZwpPointerGesturesV1, GlobalData, WinitState> for PointerGesturesS
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpPointerGestureHoldV1, PointerGestureData, WinitState> for PointerGesturesState {
|
||||
fn event(
|
||||
state: &mut WinitState,
|
||||
_proxy: &ZwpPointerGestureHoldV1,
|
||||
event: <ZwpPointerGestureHoldV1 as wayland_client::Proxy>::Event,
|
||||
data: &PointerGestureData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
let mut pointer_gesture_data = data.inner.lock().unwrap();
|
||||
let (window_id, phase) = match event {
|
||||
HoldEvent::Begin { surface, fingers, .. } => {
|
||||
if fingers < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
let window_id = crate::make_wid(&surface);
|
||||
pointer_gesture_data.window_id = Some(window_id);
|
||||
|
||||
(window_id, TouchPhase::Started)
|
||||
},
|
||||
HoldEvent::End { cancelled, .. } => {
|
||||
let window_id = match pointer_gesture_data.window_id {
|
||||
Some(window_id) => window_id,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// Reset the state.
|
||||
*pointer_gesture_data = Default::default();
|
||||
|
||||
let phase = if cancelled == 0 { TouchPhase::Ended } else { TouchPhase::Cancelled };
|
||||
|
||||
(window_id, phase)
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::HoldGesture { device_id: None, phase }, window_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for PointerGesturesState {
|
||||
fn event(
|
||||
state: &mut WinitState,
|
||||
@@ -127,7 +81,7 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
|
||||
) {
|
||||
let mut pointer_gesture_data = data.inner.lock().unwrap();
|
||||
let (window_id, phase, pan_delta, pinch_delta, rotation_delta) = match event {
|
||||
PinchEvent::Begin { surface, fingers, .. } => {
|
||||
Event::Begin { surface, fingers, .. } => {
|
||||
// We only support two fingers for now.
|
||||
if fingers != 2 {
|
||||
return;
|
||||
@@ -146,7 +100,7 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
|
||||
|
||||
(window_id, TouchPhase::Started, PhysicalPosition::new(0., 0.), 0., 0.)
|
||||
},
|
||||
PinchEvent::Update { dx, dy, scale: pinch, rotation, .. } => {
|
||||
Event::Update { dx, dy, scale: pinch, rotation, .. } => {
|
||||
let window_id = match pointer_gesture_data.window_id {
|
||||
Some(window_id) => window_id,
|
||||
_ => return,
|
||||
@@ -167,7 +121,7 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
|
||||
let rotation_delta = -rotation as f32;
|
||||
(window_id, TouchPhase::Moved, pan_delta, pinch_delta, rotation_delta)
|
||||
},
|
||||
PinchEvent::End { cancelled, .. } => {
|
||||
Event::End { cancelled, .. } => {
|
||||
let window_id = match pointer_gesture_data.window_id {
|
||||
Some(window_id) => window_id,
|
||||
_ => return,
|
||||
@@ -201,4 +155,3 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
|
||||
|
||||
delegate_dispatch!(WinitState: [ZwpPointerGesturesV1: GlobalData] => PointerGesturesState);
|
||||
delegate_dispatch!(WinitState: [ZwpPointerGesturePinchV1: PointerGestureData] => PointerGesturesState);
|
||||
delegate_dispatch!(WinitState: [ZwpPointerGestureHoldV1: PointerGestureData] => PointerGesturesState);
|
||||
|
||||
@@ -29,7 +29,7 @@ use crate::seat::{
|
||||
PointerConstraintsState, PointerGesturesState, RelativePointerState, TextInputState,
|
||||
WinitPointerData, WinitPointerDataExt, WinitSeatState,
|
||||
};
|
||||
use crate::types::bgr_effects::BgrEffectManager;
|
||||
use crate::types::kwin_blur::KWinBlurManager;
|
||||
use crate::types::wp_fractional_scaling::FractionalScalingManager;
|
||||
use crate::types::wp_tablet_input_v2::TabletManager;
|
||||
use crate::types::wp_viewporter::ViewporterState;
|
||||
@@ -116,8 +116,8 @@ pub struct WinitState {
|
||||
/// Fractional scaling manager.
|
||||
pub fractional_scaling_manager: Option<FractionalScalingManager>,
|
||||
|
||||
/// Blur manager.
|
||||
pub blur_manager: Option<BgrEffectManager>,
|
||||
/// KWin blur manager.
|
||||
pub kwin_blur_manager: Option<KWinBlurManager>,
|
||||
|
||||
/// Loop handle to re-register event sources, such as keyboard repeat.
|
||||
pub loop_handle: LoopHandle<'static, Self>,
|
||||
@@ -192,7 +192,7 @@ impl WinitState {
|
||||
window_events_sink: Default::default(),
|
||||
viewporter_state,
|
||||
fractional_scaling_manager,
|
||||
blur_manager: BgrEffectManager::new(globals, queue_handle).ok(),
|
||||
kwin_blur_manager: KWinBlurManager::new(globals, queue_handle).ok(),
|
||||
|
||||
seats,
|
||||
text_input_state: TextInputState::new(globals, queue_handle).ok(),
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
use sctk::compositor::Region;
|
||||
use sctk::reexports::client::QueueHandle;
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::protocols::ext::background_effect::v1::client::ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1;
|
||||
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
|
||||
|
||||
use crate::state::WinitState;
|
||||
use crate::types::ext_background_effect::ExtBackgroundEffectManager;
|
||||
use crate::types::kwin_blur::KWinBlurManager;
|
||||
|
||||
/// Wrapper around various background effects for [`WlSurface`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BgrEffectManager {
|
||||
Ext(ExtBackgroundEffectManager),
|
||||
KWin(KWinBlurManager),
|
||||
}
|
||||
|
||||
impl BgrEffectManager {
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
ExtBackgroundEffectManager::new(globals, queue_handle)
|
||||
.map(Self::Ext)
|
||||
.or_else(|_| KWinBlurManager::new(globals, queue_handle).map(Self::KWin))
|
||||
}
|
||||
|
||||
/// Creates a new blur effect for the surface.
|
||||
pub fn new_blur_effect(
|
||||
&mut self,
|
||||
surface: &WlSurface,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> SurfaceBlurEffect {
|
||||
match self {
|
||||
BgrEffectManager::Ext(mgr) => SurfaceBlurEffect::Ext(mgr.blur(surface, queue_handle)),
|
||||
BgrEffectManager::KWin(mgr) => SurfaceBlurEffect::Kwin(
|
||||
mgr.blur(surface, queue_handle),
|
||||
mgr.clone(),
|
||||
surface.clone(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SurfaceBlurEffect {
|
||||
Ext(ExtBackgroundEffectSurfaceV1),
|
||||
Kwin(OrgKdeKwinBlur, KWinBlurManager, WlSurface),
|
||||
}
|
||||
|
||||
impl SurfaceBlurEffect {
|
||||
/// Returns `true` if the main surface commit is required.
|
||||
///
|
||||
/// `None` clears the blur.
|
||||
#[must_use]
|
||||
pub fn set_blur(&self, region: Option<&Region>) -> bool {
|
||||
let region = region.map(|region| region.wl_region());
|
||||
match self {
|
||||
SurfaceBlurEffect::Ext(surface) => {
|
||||
surface.set_blur_region(region);
|
||||
true
|
||||
},
|
||||
SurfaceBlurEffect::Kwin(blur, ..) => {
|
||||
blur.set_region(region);
|
||||
blur.commit();
|
||||
true
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SurfaceBlurEffect {
|
||||
fn drop(&mut self) {
|
||||
match self {
|
||||
SurfaceBlurEffect::Ext(surface) => surface.destroy(),
|
||||
SurfaceBlurEffect::Kwin(blur, mgr, wl_surface) => {
|
||||
blur.set_region(None);
|
||||
blur.commit();
|
||||
blur.release();
|
||||
mgr.unset(wl_surface);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
use sctk::globals::GlobalData;
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, delegate_dispatch};
|
||||
use wayland_protocols::ext::background_effect::v1::client::ext_background_effect_manager_v1::ExtBackgroundEffectManagerV1;
|
||||
use wayland_protocols::ext::background_effect::v1::client::ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1;
|
||||
|
||||
use crate::state::WinitState;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExtBackgroundEffectManager {
|
||||
manager: ExtBackgroundEffectManagerV1,
|
||||
}
|
||||
|
||||
impl ExtBackgroundEffectManager {
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self { manager })
|
||||
}
|
||||
|
||||
pub fn blur(
|
||||
&mut self,
|
||||
surface: &WlSurface,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> ExtBackgroundEffectSurfaceV1 {
|
||||
self.manager.get_background_effect(surface, queue_handle, ())
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ExtBackgroundEffectManagerV1, GlobalData, WinitState> for ExtBackgroundEffectManager {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &ExtBackgroundEffectManagerV1,
|
||||
_: <ExtBackgroundEffectManagerV1 as Proxy>::Event,
|
||||
_: &GlobalData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ExtBackgroundEffectSurfaceV1, (), WinitState> for ExtBackgroundEffectManager {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &ExtBackgroundEffectSurfaceV1,
|
||||
_: <ExtBackgroundEffectSurfaceV1 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
// There is no event
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [ExtBackgroundEffectManagerV1: GlobalData] => ExtBackgroundEffectManager);
|
||||
delegate_dispatch!(WinitState: [ExtBackgroundEffectSurfaceV1: ()] => ExtBackgroundEffectManager);
|
||||
@@ -1,8 +1,6 @@
|
||||
//! Wayland protocol implementation boilerplate.
|
||||
|
||||
pub mod bgr_effects;
|
||||
pub mod cursor;
|
||||
pub mod ext_background_effect;
|
||||
pub mod kwin_blur;
|
||||
pub mod wp_fractional_scaling;
|
||||
pub mod wp_tablet_input_v2;
|
||||
|
||||
@@ -127,8 +127,7 @@ impl Window {
|
||||
// Set transparency hint.
|
||||
window_state.set_transparent(attributes.transparent);
|
||||
|
||||
// Set blur.
|
||||
let _ = window_state.set_blur(attributes.blur);
|
||||
window_state.set_blur(attributes.blur);
|
||||
|
||||
// Set the decorations hint.
|
||||
window_state.set_decorate(attributes.decorations);
|
||||
@@ -499,9 +498,7 @@ impl CoreWindow for Window {
|
||||
|
||||
#[inline]
|
||||
fn set_blur(&self, blur: bool) {
|
||||
if self.window_state.lock().unwrap().set_blur(blur) {
|
||||
self.request_redraw();
|
||||
}
|
||||
self.window_state.lock().unwrap().set_blur(blur);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -29,6 +29,7 @@ use sctk::shm::slot::SlotPool;
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
use tracing::{info, warn};
|
||||
use wayland_protocols::xdg::toplevel_icon::v1::client::xdg_toplevel_icon_manager_v1::XdgToplevelIconManagerV1;
|
||||
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
|
||||
use winit_core::cursor::{CursorIcon, CustomCursor as CoreCustomCursor};
|
||||
use winit_core::error::{NotSupportedError, RequestError};
|
||||
use winit_core::window::{
|
||||
@@ -42,8 +43,8 @@ use crate::seat::{
|
||||
ZwpTextInputV3Ext,
|
||||
};
|
||||
use crate::state::{WindowCompositorUpdate, WinitState};
|
||||
use crate::types::bgr_effects::{BgrEffectManager, SurfaceBlurEffect};
|
||||
use crate::types::cursor::{CustomCursor, SelectedCursor, WaylandCustomCursor};
|
||||
use crate::types::kwin_blur::KWinBlurManager;
|
||||
use crate::types::xdg_toplevel_icon_manager::ToplevelIcon;
|
||||
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
@@ -155,8 +156,8 @@ pub struct WindowState {
|
||||
|
||||
viewport: Option<WpViewport>,
|
||||
fractional_scale: Option<WpFractionalScaleV1>,
|
||||
blur: Option<SurfaceBlurEffect>,
|
||||
blur_manager: Option<BgrEffectManager>,
|
||||
blur: Option<OrgKdeKwinBlur>,
|
||||
blur_manager: Option<KWinBlurManager>,
|
||||
|
||||
/// Whether the client side decorations have pending move operations.
|
||||
///
|
||||
@@ -205,7 +206,7 @@ impl WindowState {
|
||||
toplevel_icon: None,
|
||||
xdg_toplevel_icon_manager,
|
||||
blur: None,
|
||||
blur_manager: winit_state.blur_manager.clone(),
|
||||
blur_manager: winit_state.kwin_blur_manager.clone(),
|
||||
compositor,
|
||||
handle,
|
||||
csd_fails: false,
|
||||
@@ -741,13 +742,6 @@ impl WindowState {
|
||||
// Set surface size without the borders.
|
||||
viewport.set_destination(self.size.width as _, self.size.height as _);
|
||||
}
|
||||
|
||||
// Update blur region with new size.
|
||||
if self.blur.is_some() {
|
||||
// NOTE: either user resized or configure, in both cases
|
||||
// the redraw scheduling is done on the caller side.
|
||||
let _ = self.set_blur(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the scale factor of the window.
|
||||
@@ -1119,37 +1113,20 @@ impl WindowState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Make window background blurred.
|
||||
///
|
||||
/// Returns `true` if redraw is required.
|
||||
#[must_use]
|
||||
pub fn set_blur(&mut self, blurred: bool) -> bool {
|
||||
if !blurred {
|
||||
self.blur = None;
|
||||
return true;
|
||||
}
|
||||
|
||||
let mgr = match self.blur_manager.as_mut() {
|
||||
Some(mgr) => mgr,
|
||||
None => {
|
||||
info!("Blur manager unavailable, unable to change blur");
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
let blur = match self.blur.as_ref() {
|
||||
Some(blur) => blur,
|
||||
None => {
|
||||
self.blur = Some(mgr.new_blur_effect(self.window.wl_surface(), &self.queue_handle));
|
||||
self.blur.as_ref().unwrap()
|
||||
},
|
||||
};
|
||||
|
||||
if let Ok(region) = Region::new(&*self.compositor) {
|
||||
region.add(0, 0, i32::MAX, i32::MAX);
|
||||
blur.set_blur(Some(®ion))
|
||||
} else {
|
||||
false
|
||||
/// Make window background blurred
|
||||
#[inline]
|
||||
pub fn set_blur(&mut self, blurred: bool) {
|
||||
if blurred && self.blur.is_none() {
|
||||
if let Some(blur_manager) = self.blur_manager.as_ref() {
|
||||
let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle);
|
||||
blur.commit();
|
||||
self.blur = Some(blur);
|
||||
} else {
|
||||
info!("Blur manager unavailable, unable to change blur")
|
||||
}
|
||||
} else if !blurred && self.blur.is_some() {
|
||||
self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface());
|
||||
self.blur.take().unwrap().release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1247,6 +1224,10 @@ impl WindowState {
|
||||
|
||||
impl Drop for WindowState {
|
||||
fn drop(&mut self) {
|
||||
if let Some(blur) = self.blur.take() {
|
||||
blur.release();
|
||||
}
|
||||
|
||||
if let Some(fs) = self.fractional_scale.take() {
|
||||
fs.destroy();
|
||||
}
|
||||
|
||||
@@ -526,7 +526,7 @@ impl MonitorHandler {
|
||||
},
|
||||
};
|
||||
|
||||
// Notifying `Future`s is not dependent on the lifetime of the runner,
|
||||
// Notifying `Future`s is not dependant on the lifetime of the runner,
|
||||
// because they can outlive it.
|
||||
if let Some(runner) = runner.upgrade() {
|
||||
if let Some(details) = details {
|
||||
@@ -761,7 +761,7 @@ impl MonitorPermissionFuture {
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match future.await {
|
||||
Ok(details) => {
|
||||
// Notifying `Future`s is not dependent on the lifetime of the runner, because
|
||||
// Notifying `Future`s is not dependant on the lifetime of the runner, because
|
||||
// they can outlive it.
|
||||
notifier.notify(Ok(()));
|
||||
|
||||
|
||||
@@ -51,12 +51,12 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||
SystemParametersInfoW, TranslateMessage, WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE,
|
||||
WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO,
|
||||
WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION,
|
||||
WM_INPUT, WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP,
|
||||
WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL,
|
||||
WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT,
|
||||
WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR,
|
||||
WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP,
|
||||
WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WMSZ_BOTTOM,
|
||||
WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
|
||||
WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE,
|
||||
WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN,
|
||||
WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS,
|
||||
WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH,
|
||||
WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WMSZ_BOTTOM,
|
||||
WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT,
|
||||
WMSZ_TOPRIGHT, WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW,
|
||||
WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
|
||||
@@ -1508,21 +1508,6 @@ unsafe fn public_window_callback_inner(
|
||||
result = ProcResult::Value(unsafe { DefWindowProcW(window, msg, wparam, lparam) });
|
||||
},
|
||||
|
||||
WM_INPUTLANGCHANGE => {
|
||||
// Refresh the cached keyboard layout for the newly activated input
|
||||
// language. This message is sent (by Windows or by layout switchers
|
||||
// such as Punto Switcher) after the layout changes. Refreshing the
|
||||
// cache here prevents a freeze that otherwise occurs when switching
|
||||
// layout via such tools. We still defer to `DefWindowProc` so the
|
||||
// message keeps propagating to first-level child windows, as the
|
||||
// Win32 documentation requires.
|
||||
{
|
||||
let mut layouts = LAYOUT_CACHE.lock().unwrap();
|
||||
layouts.get_current_layout();
|
||||
}
|
||||
result = ProcResult::DefWindowProc(wparam);
|
||||
},
|
||||
|
||||
// this is necessary for us to maintain minimize/restore state
|
||||
WM_SYSCOMMAND => {
|
||||
if wparam == SC_RESTORE as usize {
|
||||
|
||||
@@ -387,7 +387,11 @@ impl LayoutCache {
|
||||
let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id);
|
||||
let key = match unicode {
|
||||
ToUnicodeResult::Str(str) => Key::Character(SmolStr::new(str)),
|
||||
ToUnicodeResult::Dead(dead_char) => Key::Dead(dead_char),
|
||||
ToUnicodeResult::Dead(dead_char) => {
|
||||
// println!("{:?} - {:?} produced dead {:?}", key_code, mod_state,
|
||||
// dead_char);
|
||||
Key::Dead(dead_char)
|
||||
},
|
||||
ToUnicodeResult::None => {
|
||||
let has_alt = mod_state.contains(WindowsModifiers::ALT);
|
||||
let has_ctrl = mod_state.contains(WindowsModifiers::CONTROL);
|
||||
|
||||
@@ -1757,7 +1757,7 @@ impl EventProcessor {
|
||||
.find(|prev_monitor| prev_monitor.name == new_monitor.name)
|
||||
.map(|prev_monitor| prev_monitor.scale_factor);
|
||||
if Some(new_monitor.scale_factor) != maybe_prev_scale_factor {
|
||||
for window in self.target.windows.borrow().values().filter_map(|w| w.upgrade()) {
|
||||
for window in self.target.windows.borrow().iter().filter_map(|(_, w)| w.upgrade()) {
|
||||
window.refresh_dpi_for_monitor(
|
||||
&new_monitor,
|
||||
maybe_prev_scale_factor,
|
||||
|
||||
@@ -66,6 +66,7 @@ impl XConnection {
|
||||
// All util functions that abstract an async function will return a `Flusher`.
|
||||
pub fn flush_requests(&self) -> Result<(), XError> {
|
||||
unsafe { (self.xlib.XFlush)(self.display) };
|
||||
// println!("XFlush");
|
||||
// This isn't necessarily a useful time to check for errors (since our request hasn't
|
||||
// necessarily been processed yet)
|
||||
self.check_errors()
|
||||
@@ -73,6 +74,7 @@ impl XConnection {
|
||||
|
||||
pub fn sync_with_server(&self) -> Result<(), XError> {
|
||||
unsafe { (self.xlib.XSync)(self.display, ffi::False) };
|
||||
// println!("XSync");
|
||||
self.check_errors()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,13 +76,9 @@ 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"] }
|
||||
# Launching a window without drawing to it has unpredictable results varying from platform to
|
||||
# platform. We use the `softbuffer` crate in our examples because of its ease of use to avoid
|
||||
# confusion around this. `glutin` or `wgpu` could also be used to fill the window buffer, but they
|
||||
# are more complicated to set up.
|
||||
softbuffer.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
winit-android.workspace = true
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use cfg_aliases::cfg_aliases;
|
||||
|
||||
// Only relevant for examples and Winit, our usage of println! is fine here.
|
||||
#[allow(clippy::disallowed_macros)]
|
||||
fn main() {
|
||||
// Dummy invocation to enable change-tracking in build scripts.
|
||||
// The script doesn't depend on our code.
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// Setup cfg aliases.
|
||||
|
||||
@@ -15,6 +15,7 @@ use std::time::Instant;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use cursor_icon::CursorIcon;
|
||||
use rwh_06::{DisplayHandle, HasDisplayHandle};
|
||||
use softbuffer::{Context, Surface};
|
||||
use tracing::{error, info};
|
||||
#[cfg(web_platform)]
|
||||
@@ -24,7 +25,7 @@ use winit::cursor::{Cursor, CustomCursor, CustomCursorSource};
|
||||
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
|
||||
use winit::error::RequestError;
|
||||
use winit::event::{DeviceEvent, DeviceId, MouseButton, MouseScrollDelta, WindowEvent};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::icon::{Icon, RgbaIcon};
|
||||
use winit::keyboard::{Key, ModifiersState};
|
||||
use winit::monitor::Fullscreen;
|
||||
@@ -44,6 +45,9 @@ use winit_core::application::macos::ApplicationHandlerExtMacOS;
|
||||
#[path = "util/tracing.rs"]
|
||||
mod tracing_init;
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
/// The amount of points to around the window for drag resize direction calculations.
|
||||
const BORDER_SIZE: f64 = 20.;
|
||||
|
||||
@@ -90,12 +94,20 @@ struct Application {
|
||||
/// Drawing context.
|
||||
///
|
||||
/// With OpenGL it could be EGLDisplay.
|
||||
context: Context<OwnedDisplayHandle>,
|
||||
context: Option<Context<DisplayHandle<'static>>>,
|
||||
}
|
||||
|
||||
impl Application {
|
||||
fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self {
|
||||
let context = Context::new(event_loop.owned_display_handle()).unwrap();
|
||||
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
|
||||
let context = Some(
|
||||
Context::new(unsafe {
|
||||
std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
|
||||
event_loop.display_handle().unwrap(),
|
||||
)
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
// You'll have to choose an icon size at your own discretion. On X11, the desired size
|
||||
// varies by WM, and on Windows, you still have to account for screen scaling. Here
|
||||
@@ -529,7 +541,16 @@ impl ApplicationHandler for Application {
|
||||
WindowEvent::DoubleTapGesture { .. } => {
|
||||
info!("Smart zoom");
|
||||
},
|
||||
_ => (),
|
||||
WindowEvent::TouchpadPressure { .. }
|
||||
| WindowEvent::DragLeft { .. }
|
||||
| WindowEvent::KeyboardInput { .. }
|
||||
| WindowEvent::PointerEntered { .. }
|
||||
| WindowEvent::DragEntered { .. }
|
||||
| WindowEvent::DragMoved { .. }
|
||||
| WindowEvent::DragDropped { .. }
|
||||
| WindowEvent::Destroyed
|
||||
| WindowEvent::Ime(_)
|
||||
| WindowEvent::Moved(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,7 +605,9 @@ impl ApplicationHandlerExtMacOS for Application {
|
||||
/// State of the window.
|
||||
struct WindowState {
|
||||
/// Render surface.
|
||||
surface: Surface<OwnedDisplayHandle, Arc<dyn Window>>,
|
||||
///
|
||||
/// NOTE: This surface must be dropped before the `Window`.
|
||||
surface: Surface<DisplayHandle<'static>, Arc<dyn Window>>,
|
||||
/// The actual winit Window.
|
||||
window: Arc<dyn Window>,
|
||||
/// The window theme we're drawing with.
|
||||
@@ -625,7 +648,9 @@ impl WindowState {
|
||||
fn new(app: &Application, window: Box<dyn Window>) -> Result<Self, Box<dyn Error>> {
|
||||
let window: Arc<dyn Window> = Arc::from(window);
|
||||
|
||||
let surface = Surface::new(&app.context, Arc::clone(&window))?;
|
||||
// SAFETY: the surface is dropped before the `window` which provided it with handle, thus
|
||||
// it doesn't outlive it.
|
||||
let surface = Surface::new(app.context.as_ref().unwrap(), Arc::clone(&window))?;
|
||||
|
||||
let theme = window.theme().unwrap_or(Theme::Dark);
|
||||
info!("Theme: {theme:?}");
|
||||
@@ -912,40 +937,35 @@ impl WindowState {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.animated_fill_color {
|
||||
fill::fill_window_with_animated_color(&*self.window, self.start_time);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut buffer = self.surface.buffer_mut()?;
|
||||
|
||||
if self.animated_fill_color {
|
||||
// Fill the entire buffer with a single color.
|
||||
let time = self.start_time.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;
|
||||
buffer.fill(color);
|
||||
} else {
|
||||
// Draw a different color inside the safe area
|
||||
let surface_size = self.window.surface_size();
|
||||
let insets = self.window.safe_area();
|
||||
for y in 0..surface_size.height {
|
||||
for x in 0..surface_size.width {
|
||||
let index = y as usize * surface_size.width as usize + x as usize;
|
||||
if insets.left <= x
|
||||
&& x <= (surface_size.width - insets.right)
|
||||
&& insets.top <= y
|
||||
&& y <= (surface_size.height - insets.bottom)
|
||||
{
|
||||
// In safe area
|
||||
buffer[index] = match self.theme {
|
||||
Theme::Light => 0xffe8e8e8, // Light gray
|
||||
Theme::Dark => 0xff525252, // Medium gray
|
||||
};
|
||||
} else {
|
||||
// Outside safe area
|
||||
buffer[index] = match self.theme {
|
||||
Theme::Light => 0xffffffff, // White
|
||||
Theme::Dark => 0xff181818, // Dark gray
|
||||
};
|
||||
}
|
||||
// Draw a different color inside the safe area
|
||||
let surface_size = self.window.surface_size();
|
||||
let insets = self.window.safe_area();
|
||||
for y in 0..surface_size.height {
|
||||
for x in 0..surface_size.width {
|
||||
let index = y as usize * surface_size.width as usize + x as usize;
|
||||
if insets.left <= x
|
||||
&& x <= (surface_size.width - insets.right)
|
||||
&& insets.top <= y
|
||||
&& y <= (surface_size.height - insets.bottom)
|
||||
{
|
||||
// In safe area
|
||||
buffer[index] = match self.theme {
|
||||
Theme::Light => 0xffe8e8e8, // Light gray
|
||||
Theme::Dark => 0xff525252, // Medium gray
|
||||
};
|
||||
} else {
|
||||
// Outside safe area
|
||||
buffer[index] = match self.theme {
|
||||
Theme::Light => 0xffffffff, // White
|
||||
Theme::Dark => 0xff181818, // Dark gray
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use softbuffer::{Context, Surface};
|
||||
use tracing::info;
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::dpi::{LogicalPosition, LogicalSize, Position};
|
||||
use winit::event::{ElementState, KeyEvent, WindowEvent};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::raw_window_handle::HasRawWindowHandle;
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
@@ -17,20 +15,18 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct WindowData {
|
||||
surface: Surface<OwnedDisplayHandle, Box<dyn Window>>,
|
||||
window: Box<dyn Window>,
|
||||
color: u32,
|
||||
}
|
||||
|
||||
impl WindowData {
|
||||
fn new(context: &Context<OwnedDisplayHandle>, window: Box<dyn Window>, color: u32) -> Self {
|
||||
let surface = Surface::new(context, window).unwrap();
|
||||
Self { surface, color }
|
||||
fn new(window: Box<dyn Window>, color: u32) -> Self {
|
||||
Self { window, color }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Default, Debug)]
|
||||
struct Application {
|
||||
context: Context<OwnedDisplayHandle>,
|
||||
parent_window_id: Option<WindowId>,
|
||||
windows: HashMap<WindowId, WindowData>,
|
||||
}
|
||||
@@ -42,10 +38,10 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
|
||||
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
|
||||
let window = event_loop.create_window(attributes).unwrap();
|
||||
info!("Parent window id: {:?})", window.id());
|
||||
println!("Parent window id: {:?})", window.id());
|
||||
self.parent_window_id = Some(window.id());
|
||||
|
||||
self.windows.insert(window.id(), WindowData::new(&self.context, window, 0xffbbbbbb));
|
||||
self.windows.insert(window.id(), WindowData::new(window, 0xffbbbbbb));
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
@@ -60,12 +56,12 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
event_loop.exit();
|
||||
},
|
||||
WindowEvent::PointerEntered { device_id: _, .. } => {
|
||||
// On x11, log when the cursor entered in a window even if the child window
|
||||
// On x11, println when the cursor entered in a window even if the child window
|
||||
// is created by some key inputs.
|
||||
// the child windows are always placed at (0, 0) with size (200, 200) in the
|
||||
// parent window, so we also can see this log when we move
|
||||
// the cursor around (200, 200) in parent window.
|
||||
info!("cursor entered in the window {window_id:?}");
|
||||
println!("cursor entered in the window {window_id:?}");
|
||||
},
|
||||
WindowEvent::KeyboardInput {
|
||||
event: KeyEvent { state: ElementState::Pressed, .. },
|
||||
@@ -76,24 +72,18 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
0xff000000 + 3_u32.pow((child_index + 2).rem_euclid(16) as u32);
|
||||
|
||||
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
|
||||
let child_window = spawn_child_window(
|
||||
parent_window.surface.window().as_ref(),
|
||||
event_loop,
|
||||
child_index,
|
||||
);
|
||||
let child_window =
|
||||
spawn_child_window(parent_window.window.as_ref(), event_loop, child_index);
|
||||
let child_id = child_window.id();
|
||||
info!("Child window created with id: {child_id:?}");
|
||||
self.windows.insert(
|
||||
child_id,
|
||||
WindowData::new(&self.context, child_window, child_color),
|
||||
);
|
||||
println!("Child window created with id: {child_id:?}");
|
||||
self.windows.insert(child_id, WindowData::new(child_window, child_color));
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
if let Some(window) = self.windows.get_mut(&window_id) {
|
||||
if let Some(window) = self.windows.get(&window_id) {
|
||||
if window_id == self.parent_window_id.unwrap() {
|
||||
fill::fill(&mut window.surface);
|
||||
fill::fill_window(window.window.as_ref());
|
||||
} else {
|
||||
fill::fill_with_color(&mut window.surface, window.color);
|
||||
fill::fill_window_with_color(window.window.as_ref(), window.color);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -127,8 +117,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
}
|
||||
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let context = Context::new(event_loop.owned_display_handle()).unwrap();
|
||||
event_loop.run_app(Application { context, parent_window_id: None, windows: HashMap::new() })
|
||||
event_loop.run_app(Application::default())
|
||||
}
|
||||
|
||||
#[cfg(not(any(x11_platform, macos_platform, windows_platform)))]
|
||||
|
||||
@@ -4,13 +4,12 @@ use std::thread;
|
||||
#[cfg(not(web_platform))]
|
||||
use std::time;
|
||||
|
||||
use softbuffer::{Context, Surface};
|
||||
use tracing::{info, warn};
|
||||
use ::tracing::{info, warn};
|
||||
#[cfg(web_platform)]
|
||||
use web_time as time;
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent};
|
||||
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, OwnedDisplayHandle};
|
||||
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
|
||||
use winit::keyboard::{Key, NamedKey};
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
@@ -53,7 +52,7 @@ struct ControlFlowDemo {
|
||||
request_redraw: bool,
|
||||
wait_cancelled: bool,
|
||||
close_requested: bool,
|
||||
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
|
||||
window: Option<Box<dyn Window>>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler for ControlFlowDemo {
|
||||
@@ -70,10 +69,7 @@ impl ApplicationHandler for ControlFlowDemo {
|
||||
let window_attributes = WindowAttributes::default().with_title(
|
||||
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.",
|
||||
);
|
||||
let window = event_loop.create_window(window_attributes).unwrap();
|
||||
let context = Context::new(event_loop.owned_display_handle()).unwrap();
|
||||
let surface = Surface::new(&context, window).unwrap();
|
||||
self.surface = Some(surface);
|
||||
self.window = Some(event_loop.create_window(window_attributes).unwrap());
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
@@ -116,9 +112,9 @@ impl ApplicationHandler for ControlFlowDemo {
|
||||
_ => (),
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
let surface = self.surface.as_mut().unwrap();
|
||||
surface.window().pre_present_notify();
|
||||
fill::fill(surface);
|
||||
let window = self.window.as_ref().unwrap();
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(window.as_ref());
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
@@ -126,7 +122,7 @@ impl ApplicationHandler for ControlFlowDemo {
|
||||
|
||||
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
if self.request_redraw && !self.wait_cancelled && !self.close_requested {
|
||||
self.surface.as_ref().unwrap().window().request_redraw();
|
||||
self.window.as_ref().unwrap().request_redraw();
|
||||
}
|
||||
|
||||
match self.mode {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::error::Error;
|
||||
|
||||
use softbuffer::{Context, Surface};
|
||||
use tracing::info;
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::WindowEvent;
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
@@ -17,24 +15,27 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let event_loop = EventLoop::new()?;
|
||||
|
||||
let app = Application::default();
|
||||
let app = Application::new();
|
||||
Ok(event_loop.run_app(app)?)
|
||||
}
|
||||
|
||||
/// Application state and event handling.
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Debug)]
|
||||
struct Application {
|
||||
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
|
||||
window: Option<Box<dyn Window>>,
|
||||
}
|
||||
|
||||
impl Application {
|
||||
fn new() -> Self {
|
||||
Self { window: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationHandler for Application {
|
||||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let window_attributes =
|
||||
WindowAttributes::default().with_title("Drag and drop files on me!");
|
||||
let window = event_loop.create_window(window_attributes).unwrap();
|
||||
let context = Context::new(event_loop.owned_display_handle()).unwrap();
|
||||
let surface = Surface::new(&context, window).unwrap();
|
||||
self.surface = Some(surface);
|
||||
self.window = Some(event_loop.create_window(window_attributes).unwrap());
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
@@ -48,12 +49,12 @@ impl ApplicationHandler for Application {
|
||||
| WindowEvent::DragEntered { .. }
|
||||
| WindowEvent::DragMoved { .. }
|
||||
| WindowEvent::DragDropped { .. } => {
|
||||
info!("{event:?}");
|
||||
println!("{event:?}");
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
let surface = self.surface.as_mut().unwrap();
|
||||
surface.window().pre_present_notify();
|
||||
fill::fill(surface);
|
||||
let window = self.window.as_ref().unwrap();
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(window.as_ref());
|
||||
},
|
||||
WindowEvent::CloseRequested => {
|
||||
event_loop.exit();
|
||||
|
||||
@@ -9,11 +9,10 @@ use std::cmp;
|
||||
use std::error::Error;
|
||||
|
||||
use dpi::{LogicalPosition, PhysicalSize};
|
||||
use softbuffer::{Context, Surface};
|
||||
use tracing::{error, info};
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::{Ime, WindowEvent};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::keyboard::{Key, ModifiersState, NamedKey};
|
||||
#[cfg(web_platform)]
|
||||
use winit::platform::web::WindowAttributesWeb;
|
||||
@@ -31,7 +30,7 @@ const IME_CURSOR_SIZE: PhysicalSize<u32> = PhysicalSize::new(20, 20);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct App {
|
||||
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
|
||||
window: Option<Box<dyn Window>>,
|
||||
input_state: TextInputState,
|
||||
modifiers: ModifiersState,
|
||||
}
|
||||
@@ -74,12 +73,14 @@ impl ApplicationHandler for App {
|
||||
#[cfg(web_platform)]
|
||||
let window_attributes = WindowAttributes::default()
|
||||
.with_platform_attributes(Box::new(WindowAttributesWeb::default().with_append(true)));
|
||||
let window = event_loop.create_window(window_attributes).expect("failed creating window");
|
||||
|
||||
let context =
|
||||
Context::new(event_loop.owned_display_handle()).expect("failed creating context");
|
||||
let surface = Surface::new(&context, window).expect("failed creating surface");
|
||||
self.surface = Some(surface);
|
||||
self.window = match event_loop.create_window(window_attributes) {
|
||||
Ok(window) => Some(window),
|
||||
Err(err) => {
|
||||
eprintln!("error creating window: {err}");
|
||||
event_loop.exit();
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
// Allow IME out of the box.
|
||||
let enable_request = ImeEnableRequest::new(
|
||||
@@ -100,13 +101,11 @@ impl ApplicationHandler for App {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
info!("Close was requested; stopping");
|
||||
self.surface = None;
|
||||
self.window = None;
|
||||
event_loop.exit();
|
||||
},
|
||||
WindowEvent::SurfaceResized(surface_size) => {
|
||||
let surface = self.surface.as_mut().expect("resize event without a surface");
|
||||
fill::resize(surface, surface_size);
|
||||
surface.window().request_redraw();
|
||||
WindowEvent::SurfaceResized(_) => {
|
||||
self.window.as_ref().expect("resize event without a window").request_redraw();
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
// Redraw the application.
|
||||
@@ -115,13 +114,13 @@ impl ApplicationHandler for App {
|
||||
// this event rather than in AboutToWait, since rendering in here allows
|
||||
// the program to gracefully handle redraws requested by the OS.
|
||||
|
||||
let surface = self.surface.as_mut().expect("redraw event without a surface");
|
||||
let window = self.window.as_ref().expect("redraw request without a window");
|
||||
|
||||
// Notify that you're about to draw.
|
||||
surface.window().pre_present_notify();
|
||||
window.pre_present_notify();
|
||||
|
||||
// Draw.
|
||||
fill::fill(surface);
|
||||
fill::fill_window(window.as_ref());
|
||||
|
||||
// For contiguous redraw loop you can request a redraw from here.
|
||||
// window.request_redraw();
|
||||
@@ -215,14 +214,14 @@ impl App {
|
||||
}
|
||||
|
||||
fn handle_ime_event(&mut self, event: Ime) {
|
||||
let surface = self.surface.as_ref().expect("IME request without a window");
|
||||
let window = self.window.as_ref().expect("IME request without a window");
|
||||
match event {
|
||||
Ime::Enabled => info!("IME enabled for Window={:?}", surface.window().id()),
|
||||
Ime::Enabled => info!("IME enabled for Window={:?}", window.id()),
|
||||
Ime::Preedit(text, caret_pos) => info!("Preedit: {text}, with caret at {caret_pos:?}"),
|
||||
Ime::Commit(text) => {
|
||||
self.input_state.append_text(&text);
|
||||
let request_data = self.get_ime_update();
|
||||
surface.window().request_ime_update(ImeRequest::Update(request_data)).unwrap();
|
||||
window.request_ime_update(ImeRequest::Update(request_data)).unwrap();
|
||||
self.print_input_state();
|
||||
},
|
||||
Ime::DeleteSurrounding { before_bytes, after_bytes } => {
|
||||
@@ -247,7 +246,7 @@ impl App {
|
||||
error!("Buggy IME tried to delete with indices not on char boundary.");
|
||||
}
|
||||
},
|
||||
Ime::Disabled => info!("IME disabled for Window={:?}", surface.window().id()),
|
||||
Ime::Disabled => info!("IME disabled for Window={:?}", window.id()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,7 +312,7 @@ impl App {
|
||||
}
|
||||
|
||||
fn window(&self) -> &dyn Window {
|
||||
self.surface.as_ref().unwrap().window().as_ref()
|
||||
self.window.as_ref().unwrap().as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +346,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let event_loop = EventLoop::new()?;
|
||||
|
||||
info!(
|
||||
println!(
|
||||
r#"This showcases the use of an input method engine (IME) by emulating a text edit field.
|
||||
Use CTRL+i to toggle IME support.
|
||||
Use CTRL+p to cycle content purpose values.
|
||||
@@ -356,7 +355,7 @@ Use CTRL+h to cycle content hint permutations.
|
||||
);
|
||||
|
||||
let app = App {
|
||||
surface: None,
|
||||
window: None,
|
||||
input_state: TextInputState {
|
||||
ime_enabled: true,
|
||||
contents: String::new(),
|
||||
|
||||
@@ -7,31 +7,24 @@ fn main() -> std::process::ExitCode {
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
use softbuffer::{Context, Surface};
|
||||
use tracing::info;
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::WindowEvent;
|
||||
use winit::event_loop::pump_events::{EventLoopExtPumpEvents, PumpStatus};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
#[path = "util/tracing.rs"]
|
||||
mod tracing;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct PumpDemo {
|
||||
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
|
||||
window: Option<Box<dyn Window>>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler for PumpDemo {
|
||||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let window_attributes = WindowAttributes::default().with_title("A fantastic window!");
|
||||
let window = event_loop.create_window(window_attributes).unwrap();
|
||||
|
||||
let context = Context::new(event_loop.owned_display_handle()).unwrap();
|
||||
self.surface = Some(Surface::new(&context, window).unwrap());
|
||||
self.window = Some(event_loop.create_window(window_attributes).unwrap());
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
@@ -40,28 +33,28 @@ fn main() -> std::process::ExitCode {
|
||||
_window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
info!("{event:?}");
|
||||
println!("{event:?}");
|
||||
|
||||
let surface = match self.surface.as_mut() {
|
||||
Some(surface) => surface,
|
||||
let window = match self.window.as_ref() {
|
||||
Some(window) => window,
|
||||
None => return,
|
||||
};
|
||||
|
||||
match event {
|
||||
WindowEvent::CloseRequested => event_loop.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill(surface);
|
||||
surface.window().request_redraw();
|
||||
fill::fill_window(window.as_ref());
|
||||
window.request_redraw();
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::init();
|
||||
|
||||
let mut event_loop = EventLoop::new().unwrap();
|
||||
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let mut app = PumpDemo::default();
|
||||
|
||||
loop {
|
||||
@@ -76,12 +69,12 @@ fn main() -> std::process::ExitCode {
|
||||
//
|
||||
// Since `pump_events` doesn't block it will be important to
|
||||
// throttle the loop in the app somehow.
|
||||
info!("Update()");
|
||||
println!("Update()");
|
||||
sleep(Duration::from_millis(16));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(ios_platform, web_platform, orbital_platform))]
|
||||
fn main() {
|
||||
panic!("This platform doesn't support pump_events.")
|
||||
println!("This platform doesn't support pump_events.");
|
||||
}
|
||||
|
||||
@@ -5,43 +5,36 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use std::time::Duration;
|
||||
|
||||
use softbuffer::{Context, Surface};
|
||||
use tracing::info;
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::WindowEvent;
|
||||
use winit::event_loop::run_on_demand::EventLoopExtRunOnDemand;
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
#[path = "util/tracing.rs"]
|
||||
mod tracing;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Default, Debug)]
|
||||
struct App {
|
||||
context: Context<OwnedDisplayHandle>,
|
||||
idx: usize,
|
||||
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
|
||||
window_id: Option<WindowId>,
|
||||
window: Option<Box<dyn Window>>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler for App {
|
||||
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
|
||||
if let Some(surface) = self.surface.as_ref() {
|
||||
surface.window().request_redraw();
|
||||
if let Some(window) = self.window.as_ref() {
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let window_attributes = WindowAttributes::default()
|
||||
.with_title(format!("Fantastic window number {}!", self.idx))
|
||||
.with_title("Fantastic window number one!")
|
||||
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0));
|
||||
let window = event_loop.create_window(window_attributes).unwrap();
|
||||
self.window_id = Some(window.id());
|
||||
|
||||
let surface = Surface::new(&self.context, window).unwrap();
|
||||
self.surface = Some(surface);
|
||||
self.window = Some(window);
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
@@ -51,44 +44,52 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
event: WindowEvent,
|
||||
) {
|
||||
if event == WindowEvent::Destroyed && self.window_id == Some(window_id) {
|
||||
info!("Window {} Destroyed", self.idx);
|
||||
println!(
|
||||
"--------------------------------------------------------- Window {} Destroyed",
|
||||
self.idx
|
||||
);
|
||||
self.window_id = None;
|
||||
event_loop.exit();
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(surface) = self.surface.as_mut() else {
|
||||
return;
|
||||
let window = match self.window.as_mut() {
|
||||
Some(window) => window,
|
||||
None => return,
|
||||
};
|
||||
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
info!("Window {} CloseRequested", self.idx);
|
||||
self.surface = None;
|
||||
println!(
|
||||
"--------------------------------------------------------- Window {} \
|
||||
CloseRequested",
|
||||
self.idx
|
||||
);
|
||||
fill::cleanup_window(window.as_ref());
|
||||
self.window = None;
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
fill::fill(surface);
|
||||
fill::fill_window(window.as_ref());
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::init();
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let mut event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let context = Context::new(event_loop.owned_display_handle()).unwrap();
|
||||
let mut app = App { context, idx: 1, surface: None, window_id: None };
|
||||
let mut app = App { idx: 1, ..Default::default() };
|
||||
event_loop.run_app_on_demand(&mut app)?;
|
||||
|
||||
info!("Finished first loop");
|
||||
info!("Waiting 5 seconds");
|
||||
println!("--------------------------------------------------------- Finished first loop");
|
||||
println!("--------------------------------------------------------- Waiting 5 seconds");
|
||||
std::thread::sleep(Duration::from_secs(5));
|
||||
|
||||
app.idx += 1;
|
||||
event_loop.run_app_on_demand(&mut app)?;
|
||||
info!("Finished second loop");
|
||||
println!("--------------------------------------------------------- Finished second loop");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -100,5 +101,5 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
orbital_platform
|
||||
)))]
|
||||
fn main() {
|
||||
panic!("This example is not supported on this platform")
|
||||
println!("This example is not supported on this platform");
|
||||
}
|
||||
|
||||
@@ -1,43 +1,115 @@
|
||||
//! Fill the window buffer with a solid color.
|
||||
//!
|
||||
//! Launching a window without drawing to it has unpredictable results varying from platform to
|
||||
//! platform. In order to have well-defined examples, this module provides an easy way to
|
||||
//! fill the window buffer with a solid color.
|
||||
//!
|
||||
//! 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;
|
||||
|
||||
use rwh_06::{HasDisplayHandle, HasWindowHandle};
|
||||
use softbuffer::Surface;
|
||||
use winit::window::Window;
|
||||
use softbuffer::{Context, Surface};
|
||||
#[cfg(web_platform)]
|
||||
use web_time::Instant;
|
||||
use winit::window::{Window, WindowId};
|
||||
|
||||
/// Resize the surface.
|
||||
pub fn resize(
|
||||
surface: &mut Surface<impl HasDisplayHandle, impl HasWindowHandle>,
|
||||
surface_size: dpi::PhysicalSize<u32>,
|
||||
) {
|
||||
// Handle zero-sized buffers.
|
||||
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.
|
||||
//
|
||||
// FIXME(madsmtm): This should be done by softbuffer internally in the future:
|
||||
// https://github.com/rust-windowing/softbuffer/issues/238
|
||||
let (Some(width), Some(height)) =
|
||||
(NonZeroU32::new(surface_size.width), NonZeroU32::new(surface_size.height))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
surface.resize(width, height).expect("Failed to resize the softbuffer surface");
|
||||
// A static, thread-local map of graphics contexts to open windows.
|
||||
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) };
|
||||
}
|
||||
|
||||
/// Fill the window buffer with a solid color.
|
||||
pub fn fill_with_color(
|
||||
surface: &mut Surface<impl HasDisplayHandle, impl HasWindowHandle + AsRef<dyn Window>>,
|
||||
color: u32,
|
||||
) {
|
||||
let surface_size = surface.window().as_ref().surface_size();
|
||||
resize(surface, surface_size);
|
||||
/// The graphics context used to draw to a window.
|
||||
struct GraphicsContext {
|
||||
/// The global softbuffer context.
|
||||
context: RefCell<Context<&'static dyn Window>>,
|
||||
|
||||
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");
|
||||
/// 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(),
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn fill(
|
||||
surface: &mut Surface<impl HasDisplayHandle, impl HasWindowHandle + AsRef<dyn Window>>,
|
||||
) {
|
||||
fill_with_color(surface, 0xff181818);
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,6 +23,3 @@ pub fn init() {
|
||||
)
|
||||
.init();
|
||||
}
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use ::tracing::*;
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
use softbuffer::{Context, Surface};
|
||||
use tracing::info;
|
||||
use dpi::PhysicalInsets;
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::WindowEvent;
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
#[cfg(web_platform)]
|
||||
use winit::platform::web::WindowAttributesWeb;
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
use winit::window::{
|
||||
ImeCapabilities, ImeEnableRequest, ImeRequest, ImeRequestData, Window, WindowAttributes,
|
||||
WindowId,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
@@ -18,7 +20,8 @@ mod tracing;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct App {
|
||||
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
|
||||
window: Option<Box<dyn Window>>,
|
||||
prev_safe_area: PhysicalInsets<u32>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler for App {
|
||||
@@ -28,49 +31,68 @@ impl ApplicationHandler for App {
|
||||
#[cfg(web_platform)]
|
||||
let window_attributes = WindowAttributes::default()
|
||||
.with_platform_attributes(Box::new(WindowAttributesWeb::default().with_append(true)));
|
||||
let window = event_loop.create_window(window_attributes).expect("failed creating window");
|
||||
self.window = match event_loop.create_window(window_attributes) {
|
||||
Ok(window) => Some(window),
|
||||
Err(err) => {
|
||||
eprintln!("error creating window: {err}");
|
||||
event_loop.exit();
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let context =
|
||||
Context::new(event_loop.owned_display_handle()).expect("failed creating context");
|
||||
let surface = Surface::new(&context, window).expect("failed creating surface");
|
||||
self.surface = Some(surface);
|
||||
// Allow IME out of the box.
|
||||
let enable_request =
|
||||
ImeEnableRequest::new(ImeCapabilities::new(), ImeRequestData::default()).unwrap();
|
||||
let enable_ime = ImeRequest::Enable(enable_request);
|
||||
|
||||
// Initial update
|
||||
self.window.as_ref().unwrap().request_ime_update(enable_ime).unwrap();
|
||||
}
|
||||
|
||||
fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _: WindowId, event: WindowEvent) {
|
||||
info!("{event:?}");
|
||||
let current_safe_area = self.window.as_ref().unwrap().safe_area();
|
||||
if self.prev_safe_area != current_safe_area {
|
||||
println!("safe area changed from {:?} to {:?}", self.prev_safe_area, current_safe_area);
|
||||
self.prev_safe_area = current_safe_area;
|
||||
}
|
||||
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
info!("Close was requested; stopping");
|
||||
println!("Close was requested; stopping");
|
||||
event_loop.exit();
|
||||
},
|
||||
WindowEvent::SurfaceResized(surface_size) => {
|
||||
let surface = self.surface.as_mut().expect("resize event without a surface");
|
||||
fill::resize(surface, surface_size);
|
||||
surface.window().request_redraw();
|
||||
WindowEvent::SurfaceResized(_) => {
|
||||
self.window.as_ref().expect("resize event without a window").request_redraw();
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
println!("redraw");
|
||||
// Redraw the application.
|
||||
//
|
||||
// It's preferable for applications that do not render continuously to render in
|
||||
// this event rather than in AboutToWait, since rendering in here allows
|
||||
// the program to gracefully handle redraws requested by the OS.
|
||||
|
||||
let surface = self.surface.as_mut().expect("redraw event without a surface");
|
||||
let window = self.window.as_ref().expect("redraw request without a window");
|
||||
|
||||
// Notify that you're about to draw.
|
||||
surface.window().pre_present_notify();
|
||||
window.pre_present_notify();
|
||||
|
||||
// Draw.
|
||||
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(0xff181818);
|
||||
buffer.present().expect("Failed to present the softbuffer buffer");
|
||||
fill::fill_window(window.as_ref());
|
||||
|
||||
// For contiguous redraw loop you can request a redraw from here.
|
||||
// window.request_redraw();
|
||||
},
|
||||
_ => (),
|
||||
_ => {
|
||||
println!("{event:?}");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||
let window = self.window.as_ref().expect("redraw request without a window");
|
||||
// window.request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
@@ -3,22 +3,19 @@ use std::error::Error;
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
use softbuffer::{Context, Surface};
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::WindowEvent;
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||
use winit::platform::x11::WindowAttributesX11;
|
||||
use winit::window::{Window, WindowAttributes, WindowId};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
#[path = "util/tracing.rs"]
|
||||
mod tracing;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XEmbedDemo {
|
||||
parent_window_id: u32,
|
||||
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
|
||||
window: Option<Box<dyn Window>>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler for XEmbedDemo {
|
||||
@@ -30,10 +27,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
WindowAttributesX11::default().with_embed_parent_window(self.parent_window_id);
|
||||
window_attributes = window_attributes.with_platform_attributes(Box::new(x11_attrs));
|
||||
|
||||
let window = event_loop.create_window(window_attributes).unwrap();
|
||||
let context = Context::new(event_loop.owned_display_handle()).unwrap();
|
||||
let surface = Surface::new(&context, window).unwrap();
|
||||
self.surface = Some(surface);
|
||||
self.window = Some(event_loop.create_window(window_attributes).unwrap());
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
@@ -42,19 +36,19 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
_window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
let window = self.window.as_ref().unwrap();
|
||||
match event {
|
||||
WindowEvent::CloseRequested => event_loop.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
let surface = self.surface.as_mut().unwrap();
|
||||
surface.window().pre_present_notify();
|
||||
fill::fill(surface);
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(window.as_ref());
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
|
||||
self.surface.as_ref().unwrap().window().request_redraw();
|
||||
self.window.as_ref().unwrap().request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,13 +58,14 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
.ok_or("Expected a 32-bit X11 window ID as the first argument.")?
|
||||
.parse::<u32>()?;
|
||||
|
||||
tracing::init();
|
||||
tracing_subscriber::fmt::init();
|
||||
let event_loop = EventLoop::new()?;
|
||||
|
||||
Ok(event_loop.run_app(XEmbedDemo { parent_window_id, surface: None })?)
|
||||
Ok(event_loop.run_app(XEmbedDemo { parent_window_id, window: None })?)
|
||||
}
|
||||
|
||||
#[cfg(not(x11_platform))]
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
panic!("This example is only supported on X11 platforms.")
|
||||
println!("This example is only supported on X11 platforms.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -46,24 +46,14 @@ changelog entry.
|
||||
- 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`.
|
||||
- Use new macOS 15 cursors for resize icons.
|
||||
- On Android, added scancode conversions for more obscure key codes.
|
||||
- On Wayland, added `HoldGesture` event for multi-finger hold gestures
|
||||
- On Wayland, added ext-background-effect-v1 support.
|
||||
|
||||
### 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 Windows, fix a freeze that occurs when the keyboard layout is switched by
|
||||
tools such as Punto Switcher. The `WM_INPUTLANGCHANGE` message is now handled
|
||||
to refresh the cached keyboard layout, while still deferring to
|
||||
`DefWindowProc` for normal propagation.
|
||||
- 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.
|
||||
- On macOS, fix a panic and incorrect cursor position in Ime::Preedit when the preedit string contains special characters (ie. emojis) caused by incorrect UTF-16 to UTF-8 offset conversion.
|
||||
|
||||
@@ -29,7 +29,7 @@ use crate::platform_impl;
|
||||
///
|
||||
/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs.
|
||||
///
|
||||
/// Note that this cannot be shared across threads (due to platform-dependent logic
|
||||
/// Note that this cannot be shared across threads (due to platform-dependant logic
|
||||
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access,
|
||||
/// the [`Window`] created from this _can_ be sent to an other thread, and the
|
||||
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
|
||||
@@ -88,7 +88,7 @@ impl EventLoopBuilder {
|
||||
)]
|
||||
#[inline]
|
||||
pub fn build(&mut self) -> Result<EventLoop, EventLoopError> {
|
||||
let _entered = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
|
||||
let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
|
||||
|
||||
// Certain platforms accept a mutable reference in their API.
|
||||
#[allow(clippy::unnecessary_mut_passed)]
|
||||
@@ -262,7 +262,7 @@ impl EventLoop {
|
||||
///
|
||||
/// [`DeviceEvent`]: crate::event::DeviceEvent
|
||||
pub fn listen_device_events(&self, allowed: DeviceEvents) {
|
||||
let _entered = tracing::debug_span!(
|
||||
let _span = tracing::debug_span!(
|
||||
"winit::EventLoop::listen_device_events",
|
||||
allowed = ?allowed
|
||||
)
|
||||
|
||||
0
winit/src/platform_impl/apple/appkit/mod.rs
Normal file
0
winit/src/platform_impl/apple/appkit/mod.rs
Normal file
Reference in New Issue
Block a user