mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-27 07:03:15 -04:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2d4d20108 | ||
|
|
d8f4d8f1b7 | ||
|
|
a974640a66 | ||
|
|
3d7d766182 | ||
|
|
c73d8cff20 | ||
|
|
79aa95b212 | ||
|
|
ecd14688dc | ||
|
|
b512ed1e63 | ||
|
|
96388f4f6b | ||
|
|
1745b01502 | ||
|
|
54e974c090 | ||
|
|
b14d5c0c99 |
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
@@ -226,3 +226,19 @@ jobs:
|
||||
command: check
|
||||
log-level: error
|
||||
arguments: --all-features --target ${{ matrix.platform.target }}
|
||||
|
||||
swc:
|
||||
name: Minimize JavaScript
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install SWC
|
||||
run: sudo npm i -g @swc/cli
|
||||
- name: Run SWC
|
||||
run: |
|
||||
swc src/platform_impl/web/web_sys/worker.js -o src/platform_impl/web/web_sys/worker.min.js
|
||||
- name: Check for diff
|
||||
run: |
|
||||
[[ -z $(git status -s) ]]
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,8 +3,5 @@ target/
|
||||
rls/
|
||||
.vscode/
|
||||
*~
|
||||
*.wasm
|
||||
*.ts
|
||||
*.js
|
||||
#*#
|
||||
.DS_Store
|
||||
|
||||
12
.swcrc
Normal file
12
.swcrc
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"minify": true,
|
||||
"jsc": {
|
||||
"target": "es2022",
|
||||
"minify": {
|
||||
"compress": {
|
||||
"unused": true
|
||||
},
|
||||
"mangle": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.30.1"
|
||||
version = "0.30.3"
|
||||
authors = [
|
||||
"The winit contributors",
|
||||
"Pierre Krieger <pierre.krieger1708@gmail.com>",
|
||||
@@ -125,6 +125,7 @@ features = [
|
||||
"NSDictionary",
|
||||
"NSDistributedNotificationCenter",
|
||||
"NSEnumerator",
|
||||
"NSKeyValueObserving",
|
||||
"NSNotification",
|
||||
"NSObjCRuntime",
|
||||
"NSPathUtilities",
|
||||
@@ -142,6 +143,7 @@ features = [
|
||||
"NSApplication",
|
||||
"NSBitmapImageRep",
|
||||
"NSButton",
|
||||
"NSColor",
|
||||
"NSControl",
|
||||
"NSCursor",
|
||||
"NSDragging",
|
||||
@@ -283,6 +285,7 @@ features = [
|
||||
'AbortController',
|
||||
'AbortSignal',
|
||||
'Blob',
|
||||
'BlobPropertyBag',
|
||||
'console',
|
||||
'CssStyleDeclaration',
|
||||
'Document',
|
||||
@@ -318,6 +321,7 @@ features = [
|
||||
'VisibilityState',
|
||||
'Window',
|
||||
'WheelEvent',
|
||||
'Worker',
|
||||
'Url',
|
||||
]
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.30.1"
|
||||
winit = "0.30.3"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
|
||||
@@ -212,6 +212,12 @@ impl Application {
|
||||
Action::PrintHelp => self.print_help(),
|
||||
#[cfg(macos_platform)]
|
||||
Action::CycleOptionAsAlt => window.cycle_option_as_alt(),
|
||||
Action::SetTheme(theme) => {
|
||||
window.window.set_theme(theme);
|
||||
// Get the resulting current theme to draw with
|
||||
let actual_theme = theme.or_else(|| window.window.theme()).unwrap_or(Theme::Dark);
|
||||
window.set_draw_theme(actual_theme);
|
||||
},
|
||||
#[cfg(macos_platform)]
|
||||
Action::CreateNewTab => {
|
||||
let tab_id = window.window.tabbing_identifier();
|
||||
@@ -334,7 +340,7 @@ impl ApplicationHandler<UserEvent> for Application {
|
||||
},
|
||||
WindowEvent::ThemeChanged(theme) => {
|
||||
info!("Theme changed to {theme:?}");
|
||||
window.set_theme(theme);
|
||||
window.set_draw_theme(theme);
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
if let Err(err) = window.draw() {
|
||||
@@ -733,8 +739,8 @@ impl WindowState {
|
||||
self.window.request_redraw();
|
||||
}
|
||||
|
||||
/// Change the theme.
|
||||
fn set_theme(&mut self, theme: Theme) {
|
||||
/// Change the theme that things are drawn in.
|
||||
fn set_draw_theme(&mut self, theme: Theme) {
|
||||
self.theme = theme;
|
||||
self.window.request_redraw();
|
||||
}
|
||||
@@ -884,6 +890,7 @@ enum Action {
|
||||
ShowWindowMenu,
|
||||
#[cfg(macos_platform)]
|
||||
CycleOptionAsAlt,
|
||||
SetTheme(Option<Theme>),
|
||||
#[cfg(macos_platform)]
|
||||
CreateNewTab,
|
||||
RequestResize,
|
||||
@@ -915,6 +922,9 @@ impl Action {
|
||||
Action::ShowWindowMenu => "Show window menu",
|
||||
#[cfg(macos_platform)]
|
||||
Action::CycleOptionAsAlt => "Cycle option as alt mode",
|
||||
Action::SetTheme(None) => "Change to the system theme",
|
||||
Action::SetTheme(Some(Theme::Light)) => "Change to a light theme",
|
||||
Action::SetTheme(Some(Theme::Dark)) => "Change to a dark theme",
|
||||
#[cfg(macos_platform)]
|
||||
Action::CreateNewTab => "Create new tab",
|
||||
Action::RequestResize => "Request a resize",
|
||||
@@ -1059,6 +1069,10 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
|
||||
Action::AnimationCustomCursor,
|
||||
),
|
||||
Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility),
|
||||
// K.
|
||||
Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)),
|
||||
Binding::new("K", ModifiersState::SUPER, Action::SetTheme(Some(Theme::Light))),
|
||||
Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))),
|
||||
#[cfg(macos_platform)]
|
||||
Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
|
||||
#[cfg(macos_platform)]
|
||||
|
||||
@@ -1,3 +1,34 @@
|
||||
## 0.30.3
|
||||
|
||||
### Added
|
||||
|
||||
- On Web, add `EventLoopExtWebSys::(set_)poll_strategy()` to allow setting
|
||||
control flow strategies before starting the event loop.
|
||||
- On Web, add `WaitUntilStrategy`, which allows to set different strategies for
|
||||
`ControlFlow::WaitUntil`. By default the Prioritized Task Scheduling API is
|
||||
used, with a fallback to `setTimeout()` with a trick to circumvent throttling
|
||||
to 4ms. But an option to use a Web worker to schedule the timer is available
|
||||
as well, which commonly prevents any throttling when the window is not focused.
|
||||
|
||||
### Changed
|
||||
|
||||
- On macOS, set the window theme on the `NSWindow` instead of application-wide.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On X11, build on arm platforms.
|
||||
- On macOS, fixed `WindowBuilder::with_theme` not having any effect on the window.
|
||||
|
||||
## 0.30.2
|
||||
|
||||
### Fixed
|
||||
|
||||
- On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately
|
||||
when not called from inside the event loop. Now queues a microtask instead.
|
||||
- On Web, stop overwriting default cursor with `CursorIcon::Default`.
|
||||
- On Web, prevent crash when using `InnerSizeWriter::request_inner_size()`.
|
||||
- On macOS, fix not working opacity for entire window.
|
||||
|
||||
## 0.30.1
|
||||
|
||||
### Added
|
||||
|
||||
@@ -389,6 +389,8 @@ pub enum WindowEvent {
|
||||
/// Applications might wish to react to this to change the theme of the content of the window
|
||||
/// when the system changes the window theme.
|
||||
///
|
||||
/// This only reports a change if the window theme was not overridden by [`Window::set_theme`].
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / X11 / Wayland / Orbital:** Unsupported.
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
|
||||
//! with `cargo apk`, then the minimal changes would be:
|
||||
//! 1. Remove `ndk-glue` from your `Cargo.toml`
|
||||
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.1",
|
||||
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.3",
|
||||
//! features = [ "android-native-activity" ] }`
|
||||
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc
|
||||
//! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
|
||||
|
||||
@@ -190,6 +190,34 @@ pub trait EventLoopExtWebSys {
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
|
||||
|
||||
/// Sets the strategy for [`ControlFlow::Poll`].
|
||||
///
|
||||
/// See [`PollStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
|
||||
fn set_poll_strategy(&self, strategy: PollStrategy);
|
||||
|
||||
/// Gets the strategy for [`ControlFlow::Poll`].
|
||||
///
|
||||
/// See [`PollStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
|
||||
fn poll_strategy(&self) -> PollStrategy;
|
||||
|
||||
/// Sets the strategy for [`ControlFlow::WaitUntil`].
|
||||
///
|
||||
/// See [`WaitUntilStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy);
|
||||
|
||||
/// Gets the strategy for [`ControlFlow::WaitUntil`].
|
||||
///
|
||||
/// See [`WaitUntilStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
fn wait_until_strategy(&self) -> WaitUntilStrategy;
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtWebSys for EventLoop<T> {
|
||||
@@ -207,6 +235,22 @@ impl<T> EventLoopExtWebSys for EventLoop<T> {
|
||||
{
|
||||
self.event_loop.spawn(event_handler)
|
||||
}
|
||||
|
||||
fn set_poll_strategy(&self, strategy: PollStrategy) {
|
||||
self.event_loop.set_poll_strategy(strategy);
|
||||
}
|
||||
|
||||
fn poll_strategy(&self) -> PollStrategy {
|
||||
self.event_loop.poll_strategy()
|
||||
}
|
||||
|
||||
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
|
||||
self.event_loop.set_wait_until_strategy(strategy);
|
||||
}
|
||||
|
||||
fn wait_until_strategy(&self) -> WaitUntilStrategy {
|
||||
self.event_loop.wait_until_strategy()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ActiveEventLoopExtWebSys {
|
||||
@@ -224,6 +268,20 @@ pub trait ActiveEventLoopExtWebSys {
|
||||
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
|
||||
fn poll_strategy(&self) -> PollStrategy;
|
||||
|
||||
/// Sets the strategy for [`ControlFlow::WaitUntil`].
|
||||
///
|
||||
/// See [`WaitUntilStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy);
|
||||
|
||||
/// Gets the strategy for [`ControlFlow::WaitUntil`].
|
||||
///
|
||||
/// See [`WaitUntilStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
fn wait_until_strategy(&self) -> WaitUntilStrategy;
|
||||
|
||||
/// Async version of [`ActiveEventLoop::create_custom_cursor()`] which waits until the
|
||||
/// cursor has completely finished loading.
|
||||
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture;
|
||||
@@ -244,6 +302,16 @@ impl ActiveEventLoopExtWebSys for ActiveEventLoop {
|
||||
fn poll_strategy(&self) -> PollStrategy {
|
||||
self.p.poll_strategy()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
|
||||
self.p.set_wait_until_strategy(strategy);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wait_until_strategy(&self) -> WaitUntilStrategy {
|
||||
self.p.wait_until_strategy()
|
||||
}
|
||||
}
|
||||
|
||||
/// Strategy used for [`ControlFlow::Poll`][crate::event_loop::ControlFlow::Poll].
|
||||
@@ -272,6 +340,29 @@ pub enum PollStrategy {
|
||||
Scheduler,
|
||||
}
|
||||
|
||||
/// Strategy used for [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil].
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum WaitUntilStrategy {
|
||||
/// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available
|
||||
/// this will fallback to [`setTimeout()`].
|
||||
///
|
||||
/// This strategy is commonly not affected by browser throttling unless the window is not
|
||||
/// focused.
|
||||
///
|
||||
/// This is the default strategy.
|
||||
///
|
||||
/// [Prioritized Task Scheduling API]: https://developer.mozilla.org/en-US/docs/Web/API/Prioritized_Task_Scheduling_API
|
||||
/// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout
|
||||
#[default]
|
||||
Scheduler,
|
||||
/// Equal to [`Scheduler`][Self::Scheduler] but wakes up the event loop from a [worker].
|
||||
///
|
||||
/// This strategy is commonly not affected by browser throttling regardless of window focus.
|
||||
///
|
||||
/// [worker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
|
||||
Worker,
|
||||
}
|
||||
|
||||
pub trait CustomCursorExtWebSys {
|
||||
/// Returns if this cursor is an animation.
|
||||
fn is_animation(&self) -> bool;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![cfg(android_platform)]
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::VecDeque;
|
||||
use std::hash::Hash;
|
||||
|
||||
@@ -798,7 +798,7 @@ impl EventLoopWaker {
|
||||
// future, but that gets changed to fire immediately in did_finish_launching
|
||||
let timer = CFRunLoopTimerCreate(
|
||||
ptr::null_mut(),
|
||||
std::f64::MAX,
|
||||
f64::MAX,
|
||||
0.000_000_1,
|
||||
0,
|
||||
0,
|
||||
@@ -812,11 +812,11 @@ impl EventLoopWaker {
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
|
||||
}
|
||||
|
||||
fn start(&mut self) {
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
|
||||
}
|
||||
|
||||
fn start_at(&mut self, instant: Instant) {
|
||||
|
||||
@@ -274,8 +274,7 @@ impl<T> EventLoopProxy<T> {
|
||||
cancel: None,
|
||||
perform: event_loop_proxy_handler,
|
||||
};
|
||||
let source =
|
||||
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
|
||||
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
|
||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
||||
CFRunLoopWakeUp(rl);
|
||||
|
||||
@@ -358,7 +357,7 @@ fn setup_control_flow_observers() {
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopAfterWaiting,
|
||||
1, // repeat = true
|
||||
CFIndex::min_value(),
|
||||
CFIndex::MIN,
|
||||
control_flow_begin_handler,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
@@ -378,7 +377,7 @@ fn setup_control_flow_observers() {
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
1, // repeat = true
|
||||
CFIndex::max_value(),
|
||||
CFIndex::MAX,
|
||||
control_flow_end_handler,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![cfg(ios_platform)]
|
||||
#![allow(clippy::let_unit_value)]
|
||||
|
||||
mod app_delegate;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![cfg(wayland_platform)]
|
||||
|
||||
//! Winit's Wayland backend.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
@@ -434,7 +434,7 @@ impl EventProcessor {
|
||||
let flags = xev.data.get_long(1);
|
||||
let version = flags >> 24;
|
||||
self.dnd.version = Some(version);
|
||||
let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1;
|
||||
let has_more_types = flags - (flags & (c_long::MAX - 1)) == 1;
|
||||
if !has_more_types {
|
||||
let type_list = vec![
|
||||
xev.data.get_long(2) as xproto::Atom,
|
||||
|
||||
@@ -158,7 +158,9 @@ struct PreeditCallbacks {
|
||||
impl PreeditCallbacks {
|
||||
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
|
||||
let start_callback = create_xim_callback(client_data, unsafe {
|
||||
mem::transmute(preedit_start_callback as usize)
|
||||
mem::transmute::<usize, unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer)>(
|
||||
preedit_start_callback as usize,
|
||||
)
|
||||
});
|
||||
let done_callback = create_xim_callback(client_data, preedit_done_callback);
|
||||
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![cfg(x11_platform)]
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::ffi::CStr;
|
||||
|
||||
@@ -482,8 +482,7 @@ impl<T> EventLoopProxy<T> {
|
||||
cancel: None,
|
||||
perform: event_loop_proxy_handler,
|
||||
};
|
||||
let source =
|
||||
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
|
||||
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
|
||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
||||
CFRunLoopWakeUp(rl);
|
||||
|
||||
|
||||
@@ -214,13 +214,13 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<Pani
|
||||
};
|
||||
run_loop.add_observer(
|
||||
kCFRunLoopAfterWaiting,
|
||||
CFIndex::min_value(),
|
||||
CFIndex::MIN,
|
||||
control_flow_begin_handler,
|
||||
&mut context as *mut _,
|
||||
);
|
||||
run_loop.add_observer(
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
CFIndex::max_value(),
|
||||
CFIndex::MAX,
|
||||
control_flow_end_handler,
|
||||
&mut context as *mut _,
|
||||
);
|
||||
@@ -260,7 +260,7 @@ impl EventLoopWaker {
|
||||
// future, but that gets changed to fire immediately in did_finish_launching
|
||||
let timer = CFRunLoopTimerCreate(
|
||||
ptr::null_mut(),
|
||||
std::f64::MAX,
|
||||
f64::MAX,
|
||||
0.000_000_1,
|
||||
0,
|
||||
0,
|
||||
@@ -275,14 +275,14 @@ impl EventLoopWaker {
|
||||
pub fn stop(&mut self) {
|
||||
if self.next_fire_date.is_some() {
|
||||
self.next_fire_date = None;
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
if self.next_fire_date != Some(self.start_instant) {
|
||||
self.next_fire_date = Some(self.start_instant);
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use core_graphics::display::{CGDisplay, CGPoint};
|
||||
@@ -9,18 +11,20 @@ use objc2::rc::{autoreleasepool, Retained};
|
||||
use objc2::runtime::{AnyObject, ProtocolObject};
|
||||
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
|
||||
use objc2_app_kit::{
|
||||
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSApplication,
|
||||
NSApplicationPresentationOptions, NSBackingStoreType, NSDraggingDestination,
|
||||
NSFilenamesPboardType, NSPasteboard, NSRequestUserAttentionType, NSScreen, NSView,
|
||||
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
|
||||
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
|
||||
NSWindowTabbingMode, NSWindowTitleVisibility,
|
||||
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
|
||||
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
|
||||
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
|
||||
NSRequestUserAttentionType, NSScreen, NSView, NSWindowButton, NSWindowDelegate,
|
||||
NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode,
|
||||
NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility,
|
||||
};
|
||||
use objc2_foundation::{
|
||||
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDistributedNotificationCenter,
|
||||
NSObject, NSObjectNSDelayedPerforming, NSObjectNSThreadPerformAdditions, NSObjectProtocol,
|
||||
NSPoint, NSRect, NSSize, NSString,
|
||||
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSKeyValueChangeKey,
|
||||
NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSObject,
|
||||
NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
|
||||
NSRect, NSSize, NSString,
|
||||
};
|
||||
use tracing::{trace, warn};
|
||||
|
||||
use super::app_state::ApplicationDelegate;
|
||||
use super::cursor::cursor_from_icon;
|
||||
@@ -79,8 +83,6 @@ pub(crate) struct State {
|
||||
|
||||
window: Retained<WinitWindow>,
|
||||
|
||||
current_theme: Cell<Option<Theme>>,
|
||||
|
||||
// During `windowDidResize`, we use this to only send Moved if the position changed.
|
||||
//
|
||||
// This is expressed in native screen coordinates.
|
||||
@@ -419,32 +421,66 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
// Key-Value Observing
|
||||
unsafe impl WindowDelegate {
|
||||
// Observe theme change
|
||||
#[method(effectiveAppearanceDidChange:)]
|
||||
fn effective_appearance_did_change(&self, sender: Option<&AnyObject>) {
|
||||
trace_scope!("effectiveAppearanceDidChange:");
|
||||
unsafe {
|
||||
self.performSelectorOnMainThread_withObject_waitUntilDone(
|
||||
sel!(effectiveAppearanceDidChangedOnMainThread:),
|
||||
sender,
|
||||
false,
|
||||
)
|
||||
};
|
||||
}
|
||||
#[method(observeValueForKeyPath:ofObject:change:context:)]
|
||||
fn observe_value(
|
||||
&self,
|
||||
key_path: Option<&NSString>,
|
||||
_object: Option<&AnyObject>,
|
||||
change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>,
|
||||
_context: *mut c_void,
|
||||
) {
|
||||
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")) {
|
||||
let change = change.expect("requested a change dictionary in `addObserver`, but none was provided");
|
||||
let old = change.get(unsafe { NSKeyValueChangeOldKey }).expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`");
|
||||
let new = change.get(unsafe { NSKeyValueChangeNewKey }).expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");
|
||||
|
||||
#[method(effectiveAppearanceDidChangedOnMainThread:)]
|
||||
fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let theme = get_ns_theme(mtm);
|
||||
let old_theme = self.ivars().current_theme.replace(Some(theme));
|
||||
if old_theme != Some(theme) {
|
||||
self.queue_event(WindowEvent::ThemeChanged(theme));
|
||||
// SAFETY: The value of `effectiveAppearance` is `NSAppearance`
|
||||
let old: *const AnyObject = old;
|
||||
let old: *const NSAppearance = old.cast();
|
||||
let old: &NSAppearance = unsafe { &*old };
|
||||
let new: *const AnyObject = new;
|
||||
let new: *const NSAppearance = new.cast();
|
||||
let new: &NSAppearance = unsafe { &*new };
|
||||
|
||||
trace!(old = %unsafe { old.name() }, new = %unsafe { new.name() }, "effectiveAppearance changed");
|
||||
|
||||
// Ignore the change if the window's theme is customized by the user (since in that
|
||||
// case the `effectiveAppearance` is only emitted upon said customization, and then
|
||||
// it's triggered directly by a user action, and we don't want to emit the event).
|
||||
if unsafe { self.window().appearance() }.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let old = appearance_to_theme(old);
|
||||
let new = appearance_to_theme(new);
|
||||
// Check that the theme changed in Winit's terms (the theme might have changed on
|
||||
// other parameters, such as level of contrast, but the event should not be emitted
|
||||
// in those cases).
|
||||
if old == new {
|
||||
return;
|
||||
}
|
||||
|
||||
self.queue_event(WindowEvent::ThemeChanged(new));
|
||||
} else {
|
||||
panic!("unknown observed keypath {key_path:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl Drop for WindowDelegate {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.window().removeObserver_forKeyPath(self, ns_string!("effectiveAppearance"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_window(
|
||||
app_delegate: &ApplicationDelegate,
|
||||
attrs: &WindowAttributes,
|
||||
@@ -613,6 +649,8 @@ fn new_window(
|
||||
|
||||
if attrs.transparent {
|
||||
window.setOpaque(false);
|
||||
// See `set_transparent` for details on why we do this.
|
||||
window.setBackgroundColor(unsafe { Some(&NSColor::clearColor()) });
|
||||
}
|
||||
|
||||
// register for drag and drop operations.
|
||||
@@ -666,15 +704,13 @@ impl WindowDelegate {
|
||||
|
||||
let scale_factor = window.backingScaleFactor() as _;
|
||||
|
||||
let current_theme = match attrs.preferred_theme {
|
||||
Some(theme) => Some(theme),
|
||||
None => Some(get_ns_theme(mtm)),
|
||||
};
|
||||
if let Some(appearance) = theme_to_appearance(attrs.preferred_theme) {
|
||||
unsafe { window.setAppearance(Some(&appearance)) };
|
||||
}
|
||||
|
||||
let delegate = mtm.alloc().set_ivars(State {
|
||||
app_delegate: app_delegate.retain(),
|
||||
window: window.retain(),
|
||||
current_theme: Cell::new(current_theme),
|
||||
previous_position: Cell::new(None),
|
||||
previous_scale_factor: Cell::new(scale_factor),
|
||||
resize_increments: Cell::new(resize_increments),
|
||||
@@ -700,14 +736,16 @@ impl WindowDelegate {
|
||||
}
|
||||
window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
||||
|
||||
// Enable theme change event
|
||||
let notification_center = unsafe { NSDistributedNotificationCenter::defaultCenter() };
|
||||
// Listen for theme change event.
|
||||
//
|
||||
// SAFETY: The observer is un-registered in the `Drop` of the delegate.
|
||||
unsafe {
|
||||
notification_center.addObserver_selector_name_object(
|
||||
window.addObserver_forKeyPath_options_context(
|
||||
&delegate,
|
||||
sel!(effectiveAppearanceDidChange:),
|
||||
Some(ns_string!("AppleInterfaceThemeChangedNotification")),
|
||||
None,
|
||||
ns_string!("effectiveAppearance"),
|
||||
NSKeyValueObservingOptions::NSKeyValueObservingOptionNew
|
||||
| NSKeyValueObservingOptions::NSKeyValueObservingOptionOld,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -821,7 +859,23 @@ impl WindowDelegate {
|
||||
}
|
||||
|
||||
pub fn set_transparent(&self, transparent: bool) {
|
||||
self.window().setOpaque(!transparent)
|
||||
// This is just a hint for Quartz, it doesn't actually speculate with window alpha.
|
||||
// Providing a wrong value here could result in visual artifacts, when the window is
|
||||
// transparent.
|
||||
self.window().setOpaque(!transparent);
|
||||
|
||||
// AppKit draws the window with a background color by default, which is usually really
|
||||
// nice, but gets in the way when we want to allow the contents of the window to be
|
||||
// transparent, as in that case, the transparent contents will just be drawn on top of
|
||||
// the background color. As such, to allow the window to be transparent, we must also set
|
||||
// the background color to one with an empty alpha channel.
|
||||
let color = if transparent {
|
||||
unsafe { NSColor::clearColor() }
|
||||
} else {
|
||||
unsafe { NSColor::windowBackgroundColor() }
|
||||
};
|
||||
|
||||
self.window().setBackgroundColor(Some(&color));
|
||||
}
|
||||
|
||||
pub fn set_blur(&self, blur: bool) {
|
||||
@@ -920,8 +974,8 @@ impl WindowDelegate {
|
||||
|
||||
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
|
||||
let dimensions = dimensions.unwrap_or(Size::Logical(LogicalSize {
|
||||
width: std::f32::MAX as f64,
|
||||
height: std::f32::MAX as f64,
|
||||
width: f32::MAX as f64,
|
||||
height: f32::MAX as f64,
|
||||
}));
|
||||
let scale_factor = self.scale_factor();
|
||||
let max_size = dimensions.to_logical::<CGFloat>(scale_factor);
|
||||
@@ -1597,20 +1651,24 @@ impl WindowDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn theme(&self) -> Option<Theme> {
|
||||
self.ivars().current_theme.get()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_focus(&self) -> bool {
|
||||
self.window().isKeyWindow()
|
||||
}
|
||||
|
||||
pub fn theme(&self) -> Option<Theme> {
|
||||
// Note: We could choose between returning the value of `effectiveAppearance` or
|
||||
// `appearance`, depending on what the user is asking about:
|
||||
// - "how should I render on this particular frame".
|
||||
// - "what is the configuration for this window".
|
||||
//
|
||||
// We choose the latter for consistency with the `set_theme` call, though it might also be
|
||||
// useful to expose the former.
|
||||
Some(appearance_to_theme(unsafe { &*self.window().appearance()? }))
|
||||
}
|
||||
|
||||
pub fn set_theme(&self, theme: Option<Theme>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
set_ns_theme(theme, mtm);
|
||||
self.ivars().current_theme.set(theme.or_else(|| Some(get_ns_theme(mtm))));
|
||||
unsafe { self.window().setAppearance(theme_to_appearance(theme).as_deref()) };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1769,34 +1827,39 @@ impl WindowExtMacOS for WindowDelegate {
|
||||
const DEFAULT_STANDARD_FRAME: NSRect =
|
||||
NSRect::new(NSPoint::new(50.0, 50.0), NSSize::new(800.0, 600.0));
|
||||
|
||||
pub(super) fn get_ns_theme(mtm: MainThreadMarker) -> Theme {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
if !app.respondsToSelector(sel!(effectiveAppearance)) {
|
||||
return Theme::Light;
|
||||
}
|
||||
let appearance = app.effectiveAppearance();
|
||||
let name = appearance
|
||||
.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
|
||||
NSString::from_str("NSAppearanceNameAqua"),
|
||||
NSString::from_str("NSAppearanceNameDarkAqua"),
|
||||
]))
|
||||
.unwrap();
|
||||
match &*name.to_string() {
|
||||
"NSAppearanceNameDarkAqua" => Theme::Dark,
|
||||
_ => Theme::Light,
|
||||
fn dark_appearance_name() -> &'static NSString {
|
||||
// Don't use the static `NSAppearanceNameDarkAqua` to allow linking on macOS < 10.14
|
||||
ns_string!("NSAppearanceNameDarkAqua")
|
||||
}
|
||||
|
||||
fn appearance_to_theme(appearance: &NSAppearance) -> Theme {
|
||||
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
|
||||
unsafe { NSAppearanceNameAqua.copy() },
|
||||
dark_appearance_name().copy(),
|
||||
]));
|
||||
if let Some(best_match) = best_match {
|
||||
if *best_match == *dark_appearance_name() {
|
||||
Theme::Dark
|
||||
} else {
|
||||
Theme::Light
|
||||
}
|
||||
} else {
|
||||
warn!(?appearance, "failed to determine the theme of the appearance");
|
||||
// Default to light in this case
|
||||
Theme::Light
|
||||
}
|
||||
}
|
||||
|
||||
fn set_ns_theme(theme: Option<Theme>, mtm: MainThreadMarker) {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
if app.respondsToSelector(sel!(effectiveAppearance)) {
|
||||
let appearance = theme.map(|t| {
|
||||
let name = match t {
|
||||
Theme::Dark => NSString::from_str("NSAppearanceNameDarkAqua"),
|
||||
Theme::Light => NSString::from_str("NSAppearanceNameAqua"),
|
||||
};
|
||||
NSAppearance::appearanceNamed(&name).unwrap()
|
||||
});
|
||||
app.setAppearance(appearance.as_ref().map(|a| a.as_ref()));
|
||||
fn theme_to_appearance(theme: Option<Theme>) -> Option<Retained<NSAppearance>> {
|
||||
let appearance = match theme? {
|
||||
Theme::Light => unsafe { NSAppearance::appearanceNamed(NSAppearanceNameAqua) },
|
||||
Theme::Dark => NSAppearance::appearanceNamed(dark_appearance_name()),
|
||||
};
|
||||
if let Some(appearance) = appearance {
|
||||
Some(appearance)
|
||||
} else {
|
||||
warn!(?theme, "could not find appearance for theme");
|
||||
// Assume system appearance in this case
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ pub struct WindowId {
|
||||
|
||||
impl WindowId {
|
||||
pub const fn dummy() -> Self {
|
||||
WindowId { fd: u64::max_value() }
|
||||
WindowId { fd: u64::MAX }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,12 @@ impl<T> Dispatcher<T> {
|
||||
// SAFETY: The `transmute` is necessary because `Closure` requires `'static`. This is
|
||||
// safe because this function won't return until `f` has finished executing. See
|
||||
// `Self::new()`.
|
||||
let closure = Closure(unsafe { std::mem::transmute(closure) });
|
||||
let closure = Closure(unsafe {
|
||||
std::mem::transmute::<
|
||||
Box<dyn FnOnce(&T) + Send>,
|
||||
Box<dyn FnOnce(&T) + Send + 'static>,
|
||||
>(closure)
|
||||
});
|
||||
|
||||
self.0.send(closure);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::future;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::task::Poll;
|
||||
@@ -6,13 +7,13 @@ use std::task::Poll;
|
||||
use super::super::main_thread::MainThreadMarker;
|
||||
use super::{AtomicWaker, Wrapper};
|
||||
|
||||
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, usize>);
|
||||
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, NonZeroUsize>);
|
||||
|
||||
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, usize>);
|
||||
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, NonZeroUsize>);
|
||||
|
||||
struct Handler<T> {
|
||||
value: T,
|
||||
handler: fn(&T, usize),
|
||||
handler: fn(&T, NonZeroUsize, bool),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -20,7 +21,11 @@ struct Sender(Arc<Inner>);
|
||||
|
||||
impl<T> WakerSpawner<T> {
|
||||
#[track_caller]
|
||||
pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, usize)) -> Option<Self> {
|
||||
pub fn new(
|
||||
main_thread: MainThreadMarker,
|
||||
value: T,
|
||||
handler: fn(&T, NonZeroUsize, bool),
|
||||
) -> Option<Self> {
|
||||
let inner = Arc::new(Inner {
|
||||
counter: AtomicUsize::new(0),
|
||||
waker: AtomicWaker::new(),
|
||||
@@ -37,7 +42,7 @@ impl<T> WakerSpawner<T> {
|
||||
|handler, count| {
|
||||
let handler = handler.borrow();
|
||||
let handler = handler.as_ref().unwrap();
|
||||
(handler.handler)(&handler.value, count);
|
||||
(handler.handler)(&handler.value, count, true);
|
||||
},
|
||||
{
|
||||
let inner = Arc::clone(&inner);
|
||||
@@ -46,29 +51,31 @@ impl<T> WakerSpawner<T> {
|
||||
while let Some(count) = future::poll_fn(|cx| {
|
||||
let count = inner.counter.swap(0, Ordering::Relaxed);
|
||||
|
||||
if count > 0 {
|
||||
Poll::Ready(Some(count))
|
||||
} else {
|
||||
inner.waker.register(cx.waker());
|
||||
match NonZeroUsize::new(count) {
|
||||
Some(count) => Poll::Ready(Some(count)),
|
||||
None => {
|
||||
inner.waker.register(cx.waker());
|
||||
|
||||
let count = inner.counter.swap(0, Ordering::Relaxed);
|
||||
let count = inner.counter.swap(0, Ordering::Relaxed);
|
||||
|
||||
if count > 0 {
|
||||
Poll::Ready(Some(count))
|
||||
} else {
|
||||
if inner.closed.load(Ordering::Relaxed) {
|
||||
return Poll::Ready(None);
|
||||
match NonZeroUsize::new(count) {
|
||||
Some(count) => Poll::Ready(Some(count)),
|
||||
None => {
|
||||
if inner.closed.load(Ordering::Relaxed) {
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
},
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
.await
|
||||
{
|
||||
let handler = handler.borrow();
|
||||
let handler = handler.as_ref().unwrap();
|
||||
(handler.handler)(&handler.value, count);
|
||||
(handler.handler)(&handler.value, count, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -107,7 +114,7 @@ impl<T> Drop for WakerSpawner<T> {
|
||||
|
||||
impl<T> Waker<T> {
|
||||
pub fn wake(&self) {
|
||||
self.0.send(1)
|
||||
self.0.send(NonZeroUsize::MIN)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -324,7 +324,11 @@ impl Inner {
|
||||
match &self.cursor {
|
||||
SelectedCursor::Icon(icon)
|
||||
| SelectedCursor::Loading { previous: Previous::Icon(icon), .. } => {
|
||||
self.style.set("cursor", icon.name())
|
||||
if let CursorIcon::Default = icon {
|
||||
self.style.remove("cursor")
|
||||
} else {
|
||||
self.style.set("cursor", icon.name())
|
||||
}
|
||||
},
|
||||
SelectedCursor::Loading { previous: Previous::Image(cursor), .. }
|
||||
| SelectedCursor::Image(cursor) => {
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::sync::mpsc::{self, Receiver, Sender};
|
||||
use crate::error::EventLoopError;
|
||||
use crate::event::Event;
|
||||
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
|
||||
use crate::platform::web::{ActiveEventLoopExtWebSys, PollStrategy, WaitUntilStrategy};
|
||||
|
||||
use super::{backend, device, window};
|
||||
|
||||
@@ -53,7 +54,11 @@ impl<T> EventLoop<T> {
|
||||
// SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe
|
||||
// because this function will never return and all resources not cleaned up by the point we
|
||||
// `throw` will leak, making this actually `'static`.
|
||||
let handler = unsafe { std::mem::transmute(handler) };
|
||||
let handler = unsafe {
|
||||
std::mem::transmute::<Box<dyn FnMut(Event<()>)>, Box<dyn FnMut(Event<()>) + 'static>>(
|
||||
handler,
|
||||
)
|
||||
};
|
||||
self.elw.p.run(handler, false);
|
||||
|
||||
// Throw an exception to break out of Rust execution and use unreachable to tell the
|
||||
@@ -95,4 +100,20 @@ impl<T> EventLoop<T> {
|
||||
pub fn window_target(&self) -> &RootActiveEventLoop {
|
||||
&self.elw
|
||||
}
|
||||
|
||||
pub fn set_poll_strategy(&self, strategy: PollStrategy) {
|
||||
self.elw.set_poll_strategy(strategy);
|
||||
}
|
||||
|
||||
pub fn poll_strategy(&self) -> PollStrategy {
|
||||
self.elw.poll_strategy()
|
||||
}
|
||||
|
||||
pub fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
|
||||
self.elw.set_wait_until_strategy(strategy);
|
||||
}
|
||||
|
||||
pub fn wait_until_strategy(&self) -> WaitUntilStrategy {
|
||||
self.elw.wait_until_strategy()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,21 @@ use crate::event::{
|
||||
WindowEvent,
|
||||
};
|
||||
use crate::event_loop::{ControlFlow, DeviceEvents};
|
||||
use crate::platform::web::PollStrategy;
|
||||
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
|
||||
use crate::platform_impl::platform::backend::EventListenerHandle;
|
||||
use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawner};
|
||||
use crate::platform_impl::platform::window::Inner;
|
||||
use crate::window::WindowId;
|
||||
|
||||
use js_sys::Function;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::iter;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::ops::Deref;
|
||||
use std::rc::{Rc, Weak};
|
||||
use wasm_bindgen::prelude::Closure;
|
||||
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent};
|
||||
use web_time::{Duration, Instant};
|
||||
|
||||
@@ -40,6 +43,7 @@ pub struct Execution {
|
||||
proxy_spawner: WakerSpawner<Weak<Self>>,
|
||||
control_flow: Cell<ControlFlow>,
|
||||
poll_strategy: Cell<PollStrategy>,
|
||||
wait_until_strategy: Cell<WaitUntilStrategy>,
|
||||
exit: Cell<bool>,
|
||||
runner: RefCell<RunnerEnum>,
|
||||
suspended: Cell<bool>,
|
||||
@@ -133,18 +137,20 @@ impl Shared {
|
||||
let document = window.document().expect("Failed to obtain document");
|
||||
|
||||
Shared(Rc::<Execution>::new_cyclic(|weak| {
|
||||
let proxy_spawner = WakerSpawner::new(main_thread, weak.clone(), |runner, count| {
|
||||
if let Some(runner) = runner.upgrade() {
|
||||
Shared(runner).send_events(iter::repeat(Event::UserEvent(())).take(count))
|
||||
}
|
||||
})
|
||||
.expect("`EventLoop` has to be created in the main thread");
|
||||
let proxy_spawner =
|
||||
WakerSpawner::new(main_thread, weak.clone(), |runner, count, local| {
|
||||
if let Some(runner) = runner.upgrade() {
|
||||
Shared(runner).send_user_events(count, local)
|
||||
}
|
||||
})
|
||||
.expect("`EventLoop` has to be created in the main thread");
|
||||
|
||||
Execution {
|
||||
main_thread,
|
||||
proxy_spawner,
|
||||
control_flow: Cell::new(ControlFlow::default()),
|
||||
poll_strategy: Cell::new(PollStrategy::default()),
|
||||
wait_until_strategy: Cell::new(WaitUntilStrategy::default()),
|
||||
exit: Cell::new(false),
|
||||
runner: RefCell::new(RunnerEnum::Pending),
|
||||
suspended: Cell::new(false),
|
||||
@@ -460,6 +466,51 @@ impl Shared {
|
||||
self.send_events(iter::once(event));
|
||||
}
|
||||
|
||||
// Add a series of user events to the event loop runner
|
||||
//
|
||||
// This will schedule the event loop to wake up instead of waking it up immediately if its not
|
||||
// running.
|
||||
pub(crate) fn send_user_events(&self, count: NonZeroUsize, local: bool) {
|
||||
// If the event loop is closed, it should discard any new events
|
||||
if self.is_closed() {
|
||||
return;
|
||||
}
|
||||
|
||||
if local {
|
||||
// If the loop is not running and triggered locally, queue on next microtick.
|
||||
if let Ok(RunnerEnum::Running(ref runner)) =
|
||||
self.0.runner.try_borrow().as_ref().map(Deref::deref)
|
||||
{
|
||||
// If we're currently polling let `send_events` do its job.
|
||||
if !matches!(runner.state, State::Poll { .. }) {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_name = queueMicrotask)]
|
||||
fn queue_microtask(task: Function);
|
||||
}
|
||||
|
||||
queue_microtask(
|
||||
Closure::once_into_js({
|
||||
let this = Rc::downgrade(&self.0);
|
||||
move || {
|
||||
if let Some(shared) = this.upgrade() {
|
||||
Shared(shared).send_events(
|
||||
iter::repeat(Event::UserEvent(())).take(count.get()),
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
.unchecked_into(),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.send_events(iter::repeat(Event::UserEvent(())).take(count.get()))
|
||||
}
|
||||
|
||||
// Add a series of events to the event loop runner
|
||||
//
|
||||
// It will determine if the event should be immediately sent to the user or buffered for later
|
||||
@@ -473,7 +524,7 @@ impl Shared {
|
||||
match self.0.runner.try_borrow().as_ref().map(Deref::deref) {
|
||||
Ok(RunnerEnum::Running(ref runner)) => {
|
||||
// If we're currently polling, queue this and wait for the poll() method to be
|
||||
// called
|
||||
// called.
|
||||
if let State::Poll { .. } = runner.state {
|
||||
process_immediately = false;
|
||||
}
|
||||
@@ -647,6 +698,7 @@ impl Shared {
|
||||
start,
|
||||
end,
|
||||
_timeout: backend::Schedule::new_with_duration(
|
||||
self.wait_until_strategy(),
|
||||
self.window(),
|
||||
move || cloned.resume_time_reached(start, end),
|
||||
delay,
|
||||
@@ -759,6 +811,14 @@ impl Shared {
|
||||
self.0.poll_strategy.get()
|
||||
}
|
||||
|
||||
pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
|
||||
self.0.wait_until_strategy.set(strategy)
|
||||
}
|
||||
|
||||
pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy {
|
||||
self.0.wait_until_strategy.get()
|
||||
}
|
||||
|
||||
pub(crate) fn waker(&self) -> Waker<Weak<Execution>> {
|
||||
self.0.proxy_spawner.waker()
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::event::{
|
||||
};
|
||||
use crate::event_loop::{ControlFlow, DeviceEvents};
|
||||
use crate::keyboard::ModifiersState;
|
||||
use crate::platform::web::{CustomCursorFuture, PollStrategy};
|
||||
use crate::platform::web::{CustomCursorFuture, PollStrategy, WaitUntilStrategy};
|
||||
use crate::platform_impl::platform::cursor::CustomCursor;
|
||||
use crate::platform_impl::platform::r#async::Waker;
|
||||
use crate::window::{
|
||||
@@ -682,6 +682,14 @@ impl ActiveEventLoop {
|
||||
self.runner.poll_strategy()
|
||||
}
|
||||
|
||||
pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
|
||||
self.runner.set_wait_until_strategy(strategy)
|
||||
}
|
||||
|
||||
pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy {
|
||||
self.runner.wait_until_strategy()
|
||||
}
|
||||
|
||||
pub(crate) fn waker(&self) -> Waker<Weak<Execution>> {
|
||||
self.runner.waker()
|
||||
}
|
||||
|
||||
@@ -427,8 +427,8 @@ impl Canvas {
|
||||
|
||||
pub(crate) fn on_resize_scale<S, R>(&mut self, scale_handler: S, size_handler: R)
|
||||
where
|
||||
S: 'static + FnMut(PhysicalSize<u32>, f64),
|
||||
R: 'static + FnMut(PhysicalSize<u32>),
|
||||
S: 'static + Fn(PhysicalSize<u32>, f64),
|
||||
R: 'static + Fn(PhysicalSize<u32>),
|
||||
{
|
||||
self.on_resize_scale = Some(ResizeScaleHandle::new(
|
||||
self.window().clone(),
|
||||
|
||||
@@ -16,7 +16,7 @@ use super::media_query_handle::MediaQueryListHandle;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct ResizeScaleHandle(Rc<RefCell<ResizeScaleInternal>>);
|
||||
pub struct ResizeScaleHandle(Rc<ResizeScaleInternal>);
|
||||
|
||||
impl ResizeScaleHandle {
|
||||
pub(crate) fn new<S, R>(
|
||||
@@ -28,8 +28,8 @@ impl ResizeScaleHandle {
|
||||
resize_handler: R,
|
||||
) -> Self
|
||||
where
|
||||
S: 'static + FnMut(PhysicalSize<u32>, f64),
|
||||
R: 'static + FnMut(PhysicalSize<u32>),
|
||||
S: 'static + Fn(PhysicalSize<u32>, f64),
|
||||
R: 'static + Fn(PhysicalSize<u32>),
|
||||
{
|
||||
Self(ResizeScaleInternal::new(
|
||||
window,
|
||||
@@ -42,7 +42,7 @@ impl ResizeScaleHandle {
|
||||
}
|
||||
|
||||
pub(crate) fn notify_resize(&self) {
|
||||
self.0.borrow_mut().notify()
|
||||
self.0.notify()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,11 +53,11 @@ struct ResizeScaleInternal {
|
||||
document: Document,
|
||||
canvas: HtmlCanvasElement,
|
||||
style: Style,
|
||||
mql: MediaQueryListHandle,
|
||||
mql: RefCell<MediaQueryListHandle>,
|
||||
observer: ResizeObserver,
|
||||
_observer_closure: Closure<dyn FnMut(Array, ResizeObserver)>,
|
||||
scale_handler: Box<dyn FnMut(PhysicalSize<u32>, f64)>,
|
||||
resize_handler: Box<dyn FnMut(PhysicalSize<u32>)>,
|
||||
scale_handler: Box<dyn Fn(PhysicalSize<u32>, f64)>,
|
||||
resize_handler: Box<dyn Fn(PhysicalSize<u32>)>,
|
||||
notify_scale: Cell<bool>,
|
||||
}
|
||||
|
||||
@@ -69,12 +69,12 @@ impl ResizeScaleInternal {
|
||||
style: Style,
|
||||
scale_handler: S,
|
||||
resize_handler: R,
|
||||
) -> Rc<RefCell<Self>>
|
||||
) -> Rc<Self>
|
||||
where
|
||||
S: 'static + FnMut(PhysicalSize<u32>, f64),
|
||||
R: 'static + FnMut(PhysicalSize<u32>),
|
||||
S: 'static + Fn(PhysicalSize<u32>, f64),
|
||||
R: 'static + Fn(PhysicalSize<u32>),
|
||||
{
|
||||
Rc::<RefCell<ResizeScaleInternal>>::new_cyclic(|weak_self| {
|
||||
Rc::<ResizeScaleInternal>::new_cyclic(|weak_self| {
|
||||
let mql = Self::create_mql(&window, {
|
||||
let weak_self = weak_self.clone();
|
||||
move |mql| {
|
||||
@@ -86,9 +86,7 @@ impl ResizeScaleInternal {
|
||||
|
||||
let weak_self = weak_self.clone();
|
||||
let observer_closure = Closure::new(move |entries: Array, _| {
|
||||
if let Some(rc_self) = weak_self.upgrade() {
|
||||
let mut this = rc_self.borrow_mut();
|
||||
|
||||
if let Some(this) = weak_self.upgrade() {
|
||||
let size = this.process_entry(entries);
|
||||
|
||||
if this.notify_scale.replace(false) {
|
||||
@@ -101,18 +99,18 @@ impl ResizeScaleInternal {
|
||||
});
|
||||
let observer = Self::create_observer(&canvas, observer_closure.as_ref());
|
||||
|
||||
RefCell::new(Self {
|
||||
Self {
|
||||
window,
|
||||
document,
|
||||
canvas,
|
||||
style,
|
||||
mql,
|
||||
mql: RefCell::new(mql),
|
||||
observer,
|
||||
_observer_closure: observer_closure,
|
||||
scale_handler: Box::new(scale_handler),
|
||||
resize_handler: Box::new(resize_handler),
|
||||
notify_scale: Cell::new(false),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -152,7 +150,7 @@ impl ResizeScaleInternal {
|
||||
observer
|
||||
}
|
||||
|
||||
fn notify(&mut self) {
|
||||
fn notify(&self) {
|
||||
if !self.document.contains(Some(&self.canvas)) || self.style.get("display") == "none" {
|
||||
let size = PhysicalSize::new(0, 0);
|
||||
|
||||
@@ -200,10 +198,9 @@ impl ResizeScaleInternal {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_scale(this: Rc<RefCell<Self>>, mql: &MediaQueryList) {
|
||||
let weak_self = Rc::downgrade(&this);
|
||||
let mut this = this.borrow_mut();
|
||||
let scale = super::scale_factor(&this.window);
|
||||
fn handle_scale(self: Rc<Self>, mql: &MediaQueryList) {
|
||||
let weak_self = Rc::downgrade(&self);
|
||||
let scale = super::scale_factor(&self.window);
|
||||
|
||||
// TODO: confirm/reproduce this problem, see:
|
||||
// <https://github.com/rust-windowing/winit/issues/2597>.
|
||||
@@ -217,15 +214,15 @@ impl ResizeScaleInternal {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_mql = Self::create_mql(&this.window, move |mql| {
|
||||
let new_mql = Self::create_mql(&self.window, move |mql| {
|
||||
if let Some(rc_self) = weak_self.upgrade() {
|
||||
Self::handle_scale(rc_self, mql);
|
||||
}
|
||||
});
|
||||
this.mql = new_mql;
|
||||
self.mql.replace(new_mql);
|
||||
|
||||
this.notify_scale.set(true);
|
||||
this.notify();
|
||||
self.notify_scale.set(true);
|
||||
self.notify();
|
||||
}
|
||||
|
||||
fn process_entry(&self, entries: Array) -> PhysicalSize<u32> {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use js_sys::{Function, Object, Promise, Reflect};
|
||||
use js_sys::{Array, Function, Object, Promise, Reflect};
|
||||
use std::cell::OnceCell;
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{AbortController, AbortSignal, MessageChannel, MessagePort};
|
||||
use web_sys::{
|
||||
AbortController, AbortSignal, Blob, BlobPropertyBag, MessageChannel, MessagePort, Url, Worker,
|
||||
};
|
||||
|
||||
use crate::platform::web::PollStrategy;
|
||||
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Schedule {
|
||||
@@ -29,6 +31,7 @@ enum Inner {
|
||||
port: MessagePort,
|
||||
_timeout_closure: Closure<dyn FnMut()>,
|
||||
},
|
||||
Worker(MessagePort),
|
||||
}
|
||||
|
||||
impl Schedule {
|
||||
@@ -45,14 +48,24 @@ impl Schedule {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_duration<F>(window: &web_sys::Window, f: F, duration: Duration) -> Schedule
|
||||
pub fn new_with_duration<F>(
|
||||
strategy: WaitUntilStrategy,
|
||||
window: &web_sys::Window,
|
||||
f: F,
|
||||
duration: Duration,
|
||||
) -> Schedule
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
if has_scheduler_support(window) {
|
||||
Self::new_scheduler(window, f, Some(duration))
|
||||
} else {
|
||||
Self::new_timeout(window.clone(), f, Some(duration))
|
||||
match strategy {
|
||||
WaitUntilStrategy::Scheduler => {
|
||||
if has_scheduler_support(window) {
|
||||
Self::new_scheduler(window, f, Some(duration))
|
||||
} else {
|
||||
Self::new_timeout(window.clone(), f, Some(duration))
|
||||
}
|
||||
},
|
||||
WaitUntilStrategy::Worker => Self::new_worker(f, duration),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +166,44 @@ impl Schedule {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn new_worker<F>(f: F, duration: Duration) -> Schedule
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
thread_local! {
|
||||
static URL: ScriptUrl = ScriptUrl::new(include_str!("worker.min.js"));
|
||||
static WORKER: Worker = URL.with(|url| Worker::new(&url.0)).expect("`new Worker()` is not expected to fail with a local script");
|
||||
}
|
||||
|
||||
let channel = MessageChannel::new().unwrap();
|
||||
let closure = Closure::new(f);
|
||||
let port_1 = channel.port1();
|
||||
port_1.set_onmessage(Some(closure.as_ref().unchecked_ref()));
|
||||
port_1.start();
|
||||
|
||||
// `Duration::as_millis()` always rounds down (because of truncation), we want to round
|
||||
// up instead. This makes sure that the we never wake up **before** the given time.
|
||||
let duration = duration
|
||||
.as_secs()
|
||||
.try_into()
|
||||
.ok()
|
||||
.and_then(|secs: u32| secs.checked_mul(1000))
|
||||
.and_then(|secs| secs.checked_add(duration_millis_ceil(duration)))
|
||||
.unwrap_or(u32::MAX);
|
||||
|
||||
WORKER
|
||||
.with(|worker| {
|
||||
let port_2 = channel.port2();
|
||||
worker.post_message_with_transfer(
|
||||
&Array::of2(&port_2, &duration.into()),
|
||||
&Array::of1(&port_2).into(),
|
||||
)
|
||||
})
|
||||
.expect("`Worker.postMessage()` is not expected to fail");
|
||||
|
||||
Schedule { _closure: closure, inner: Inner::Worker(port_1) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Schedule {
|
||||
@@ -165,6 +216,10 @@ impl Drop for Schedule {
|
||||
port.close();
|
||||
port.set_onmessage(None);
|
||||
},
|
||||
Inner::Worker(port) => {
|
||||
port.close();
|
||||
port.set_onmessage(None);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,6 +281,29 @@ fn has_idle_callback_support(window: &web_sys::Window) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
struct ScriptUrl(String);
|
||||
|
||||
impl ScriptUrl {
|
||||
fn new(script: &str) -> Self {
|
||||
let sequence = Array::of1(&script.into());
|
||||
let mut property = BlobPropertyBag::new();
|
||||
property.type_("text/javascript");
|
||||
let blob = Blob::new_with_str_sequence_and_options(&sequence, &property)
|
||||
.expect("`new Blob()` should never throw");
|
||||
|
||||
let url = Url::create_object_url_with_blob(&blob)
|
||||
.expect("`URL.createObjectURL()` should never throw");
|
||||
|
||||
Self(url)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ScriptUrl {
|
||||
fn drop(&mut self) {
|
||||
Url::revoke_object_url(&self.0).expect("`URL.revokeObjectURL()` should never throw");
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type WindowSupportExt;
|
||||
|
||||
10
src/platform_impl/web/web_sys/worker.js
Normal file
10
src/platform_impl/web/web_sys/worker.js
Normal file
@@ -0,0 +1,10 @@
|
||||
onmessage = event => {
|
||||
const [port, timeout] = event.data
|
||||
const f = () => port.postMessage(undefined)
|
||||
|
||||
if ('scheduler' in this) {
|
||||
scheduler.postTask(f, { delay: timeout })
|
||||
} else {
|
||||
setTimeout(f, timeout)
|
||||
}
|
||||
}
|
||||
1
src/platform_impl/web/web_sys/worker.min.js
vendored
Normal file
1
src/platform_impl/web/web_sys/worker.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
onmessage=e=>{let[s,t]=e.data,a=()=>s.postMessage(void 0);"scheduler"in this?scheduler.postTask(a,{delay:t}):setTimeout(a,t)};
|
||||
@@ -1,5 +1,3 @@
|
||||
#![cfg(windows_platform)]
|
||||
|
||||
use smol_str::SmolStr;
|
||||
use windows_sys::Win32::Foundation::{HANDLE, HWND};
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX};
|
||||
|
||||
@@ -393,7 +393,6 @@ impl WindowAttributes {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** This is an app-wide setting.
|
||||
/// - **Wayland:** This controls only CSD. When using `None` it'll try to use dbus to get the
|
||||
/// system preference. When explicit theme is used, this will avoid dbus all together.
|
||||
/// - **x11:** Build window with `_GTK_THEME_VARIANT` hint set to `dark` or `light`.
|
||||
@@ -942,8 +941,7 @@ impl Window {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** If you're not drawing to the window yourself, you might have to set the
|
||||
/// background color of the window to enable transparency.
|
||||
/// - **macOS:** This will reset the window's background color.
|
||||
/// - **Web / iOS / Android:** Unsupported.
|
||||
/// - **X11:** Can only be set while building the window, with
|
||||
/// [`WindowAttributes::with_transparent`].
|
||||
@@ -1355,11 +1353,12 @@ impl Window {
|
||||
self.window.maybe_queue_on_main(move |w| w.request_user_attention(request_type))
|
||||
}
|
||||
|
||||
/// Sets the current window theme. Use `None` to fallback to system default.
|
||||
/// Set or override the window theme.
|
||||
///
|
||||
/// Specify `None` to reset the theme to the system default.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** This is an app-wide setting.
|
||||
/// - **Wayland:** Sets the theme for the client side decorations. Using `None` will use dbus to
|
||||
/// get the system preference.
|
||||
/// - **X11:** Sets `_GTK_THEME_VARIANT` hint to `dark` or `light` and if `None` is used, it
|
||||
@@ -1375,12 +1374,14 @@ impl Window {
|
||||
self.window.maybe_queue_on_main(move |w| w.set_theme(theme))
|
||||
}
|
||||
|
||||
/// Returns the current window theme.
|
||||
/// Returns the current window theme override.
|
||||
///
|
||||
/// Returns `None` if the current theme is set as the system default, or if it cannot be
|
||||
/// determined on the current platform.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** This is an app-wide setting.
|
||||
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
|
||||
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported, returns `None`.
|
||||
#[inline]
|
||||
pub fn theme(&self) -> Option<Theme> {
|
||||
let _span = tracing::debug_span!("winit::Window::theme",).entered();
|
||||
|
||||
Reference in New Issue
Block a user