Compare commits

..

36 Commits

Author SHA1 Message Date
Kirill Chibisov
d2d4d20108 Winit version 0.30.3 2024-06-21 20:16:14 +03:00
daxpedda
d8f4d8f1b7 Web: implement WaitUntilStrategy (#3739) 2024-06-21 20:16:14 +03:00
daxpedda
a974640a66 Web: set control flow strategies on EventLoop (#3740) 2024-06-21 20:16:14 +03:00
Mads Marquart
3d7d766182 macOS: set the theme on the NSWindow, instead of application-wide
This new implementation uses:
- The NSAppearanceCustomization protocol for retrieving the appearance
  of the window, instead of using the application-wide
  `-[NSApplication effectiveAppearance]`.
- Key-Value observing for observing the `effectiveAppearance` to compute
  the `ThemeChanged` event, instead of using the undocumented
  `AppleInterfaceThemeChangedNotification` notification.

This also fixes `WindowBuilder::with_theme` not having any effect, and
the conversion between `Theme` and `NSAppearance` is made a bit more
robust.
2024-06-21 20:16:14 +03:00
Kirill Chibisov
c73d8cff20 x11: fix build on arm
The c_char type, which was used under the hood is different depending
on arch, thus use it directly instead of i8.

Fixes #3735.
2024-06-21 20:16:14 +03:00
Kirill Chibisov
79aa95b212 Winit version 0.30.2 2024-06-15 20:00:04 +03:00
Kirill Chibisov
ecd14688dc Revert: Web: don't wait for polling when sending events
This is a breaking change, thus revert it for patch series.
2024-06-15 20:00:04 +03:00
Kirill Chibisov
b512ed1e63 macOS: fix opacity handling
Not using `NSColor::clearColor()` results in Quartz thinking that the
window is not transparent at all, which results in artifacts.

However, not setting the `windowBackgroundColor` in
`Window::set_transparent` results in border not properly rendered.

Fixes: 94664ff687 (Don't set the background color)
2024-06-15 20:00:04 +03:00
Kirill Chibisov
96388f4f6b chore: address 1.79 clippy lints 2024-06-15 20:00:04 +03:00
daxpedda
1745b01502 Web: fix crash InnerSizeWriter::request_inner_size() (#3727) 2024-06-15 20:00:04 +03:00
daxpedda
54e974c090 Web: don't overwrite cursor with CursorIcon::Default (#3729) 2024-06-15 20:00:04 +03:00
daxpedda
b14d5c0c99 Web: queue EventLoopProxy::send_event() to microtask 2024-06-15 20:00:04 +03:00
John Nunley
437747b966 Winit version 0.30.1
Signed-off-by: John Nunley <dev@notgull.net>
2024-06-10 18:40:33 +03:00
Mads Marquart
21e266f3b7 macOS/iOS: Various refactorings in application state (#3720)
I'm preparing to get rid of our application delegate in favour of registering
notification observers, to do so I'm renaming `app_delegate.rs` to
`app_state.rs`, and moving the functionality out of the Objective-C method
into a normal method.

Additionally, `AppState` previously implemented `Default`, but really, this
was a hack done because someone (probably myself) was too lazy to write out
the full initialization in `AppDelegate::new`.
2024-06-10 18:40:33 +03:00
Mads Marquart
3a0928af45 macOS: Improve event queuing (#3708)
* Use AppKit's internal queuing mechanisms

This allows events to be queued in a more consistent order, they're now
interleaved with events that we handle immediately (like redraw events),
instead of being handled afterwards.

* Only queue events if necessary

This makes the call stack / backtraces easier to understand whenever
possible, and generally improves upon the order in which events are
delivered.
2024-06-10 18:40:33 +03:00
Philippe Renon
ad92b4f89d doc: clarify Window::pre_present_notify availability
Fixes #3703.
2024-06-10 18:40:33 +03:00
ShikiSuen
bf4445bb62 Handle _selected_range sent to NSTextInputClient.setMarkedText(). (#3619)
Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-06-10 18:40:33 +03:00
Mads Marquart
391a22217d Implement ApplicationHandler for &mut A and Box<A> (#3709) 2024-06-10 18:40:33 +03:00
Mads Marquart
fb4a674ee5 Update objc2 to v0.2.2 (#3702)
- Use new `bitflags!` support.
- Use `objc2-ui-kit`.
- Change usage of `Id` to `Retained`.
2024-06-10 18:40:33 +03:00
Diggory Hardy
43f296b2b3 event_loop: add is_x11 and is_wayland on EventLoop 2024-06-10 18:40:33 +03:00
Golden Water
042667c5eb Resize when size changes on scale change on macOS
This fixes an issue where the window glitched due to resize
when the user doesn't actually change the window, but macOS
function to update window size was still called.
2024-06-10 18:40:33 +03:00
Kirill Chibisov
3206d105fe chore: explicitly use cfg_aliases 0.2.1
This correctly handles recent nightly lint that requires to explicitly
define the CFG guards.
2024-06-10 18:40:33 +03:00
Kevin Müller
1afec3ca0d bugfix: Replace pointer dereference with read_unaligned
On Raspberry Pi, using the Rust crate eframe caused the program to crash on
mouse movement. The Backtrace lead to this specific line of code, and the exact
error was a "misaligned pointer dereference: address must be a multiple of 0x8
but is xxxx"

The edit has been tested with the Raspberry Pi, which works now.
2024-06-10 18:40:33 +03:00
Ryan Burleson
c4a8e9321d fix doc typo in application.rs (#3676) 2024-06-10 18:40:33 +03:00
linkmauve
dee7a405fc Reexport older versions of raw-window-handle
When the user decides to use an older version of raw-window-handle,
through the rwh_04 or rwh_05 features, it makes sense to reexport the
crate so they don’t have to depend on it manually and can instead use
winit::raw_window_handle.
2024-06-10 18:40:33 +03:00
Mads Marquart
a298b4d00e Reduce usage of direct msg_send! 2024-06-10 18:40:33 +03:00
Mads Marquart
aebd5edc9e macOS: Move util::EMPTY_RANGE to usage spot (#3685) 2024-06-10 18:40:33 +03:00
Mads Marquart
c801b69d3e Retain ApplicationDelegate in NSWindowDelegate and NSView
The delegate is only weakly referenced by NSApplication, so getting it
from there may fail if the event loop has been dropped.

Fixes #3668.
2024-06-10 18:40:33 +03:00
Mads Marquart
ebd6454f8f Use rustc-check-cfg (#3682) 2024-06-10 18:40:33 +03:00
daxpedda
4f2f0bc08f Web: fix Clippy v1.78 FPs (#3678) 2024-06-10 18:40:33 +03:00
Kirill Chibisov
4b3c0655bf Winit version 0.30.0 2024-04-27 19:00:38 +04:00
Joshua Pedrick
0812adc983 Add UIGestureRecognizerDelegate and PanGestureRecogniser (#3597)
- Allow all gestures simultaneously recognized.
- Add PanGestureRecogniser with min/max number of touches.
- Fix sending delta values relative to Update instead to match macOS.
- Fix rotation gesture units from iOS to be in degrees instead of radians.

Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-04-27 19:00:38 +04:00
Mads Marquart
cd6ec19300 Don't set the background color when initializing with transparency (#3657)
Setting the background color changes how the window title bar appears,
which is something that the application should customize itself if it
wants this behaviour (and also, it wasn't set when calling
`set_transparent`, so the behaviour wasn't consistent).
2024-04-27 19:00:38 +04:00
growfrow
61bd8172bd chore: fix some typos in comments (#3635)
Signed-off-by: growfrow <growfrow@outlook.com>
2024-04-27 19:00:38 +04:00
Kirill Chibisov
c04c113e7e chore: ensure that .cargo config is not published
Just in case, so the correct changelog will be rendered when pulling
the crate from the crates.io as archive and trying to build it.
2024-04-27 19:00:38 +04:00
Marijn Suijten
ce32a3024e android: bump to ndk 0.9.0 and android-activity 0.6.0 2024-04-27 19:00:38 +04:00
50 changed files with 1545 additions and 1125 deletions

View File

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

@@ -3,8 +3,5 @@ target/
rls/
.vscode/
*~
*.wasm
*.ts
*.js
#*#
.DS_Store

12
.swcrc Normal file
View File

@@ -0,0 +1,12 @@
{
"minify": true,
"jsc": {
"target": "es2022",
"minify": {
"compress": {
"unused": true
},
"mangle": true
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.30.0"
version = "0.30.3"
authors = [
"The winit contributors",
"Pierre Krieger <pierre.krieger1708@gmail.com>",
@@ -112,10 +112,12 @@ objc2 = "0.5.2"
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1"
block2 = "0.5.1"
[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation]
version = "0.2.2"
features = [
"block2",
"dispatch",
"NSArray",
"NSAttributedString",
@@ -123,6 +125,7 @@ features = [
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
"NSPathUtilities",
@@ -140,6 +143,7 @@ features = [
"NSApplication",
"NSBitmapImageRep",
"NSButton",
"NSColor",
"NSControl",
"NSCursor",
"NSDragging",
@@ -281,6 +285,7 @@ features = [
'AbortController',
'AbortSignal',
'Blob',
'BlobPropertyBag',
'console',
'CssStyleDeclaration',
'Document',
@@ -316,6 +321,7 @@ features = [
'VisibilityState',
'Window',
'WheelEvent',
'Worker',
'Url',
]

View File

@@ -8,7 +8,7 @@
```toml
[dependencies]
winit = "0.30.0"
winit = "0.30.3"
```
## [Documentation](https://docs.rs/winit)

View File

@@ -3,75 +3,15 @@
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap;
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, LogicalSize, Position};
use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::raw_window_handle::HasRawWindowHandle;
use winit::window::{Window, WindowId};
use winit::window::Window;
#[path = "util/fill.rs"]
mod fill;
#[derive(Default)]
struct Application {
parent_window_id: Option<WindowId>,
windows: HashMap<WindowId, Window>,
}
impl ApplicationHandler for Application {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let attributes = Window::default_attributes()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
println!("Parent window id: {:?})", window.id());
self.parent_window_id = Some(window.id());
self.windows.insert(window.id(), window);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: winit::window::WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::CloseRequested => {
self.windows.clear();
event_loop.exit();
},
WindowEvent::CursorEntered { device_id: _ } => {
// 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.
println!("cursor entered in the window {window_id:?}");
},
WindowEvent::KeyboardInput {
event: KeyEvent { state: ElementState::Pressed, .. },
..
} => {
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window(parent_window, event_loop);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
self.windows.insert(child_id, child_window);
},
WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get(&window_id) {
fill::fill_window(window);
}
},
_ => (),
}
}
}
fn spawn_child_window(parent: &Window, event_loop: &ActiveEventLoop) -> Window {
let parent = parent.raw_window_handle().unwrap();
let mut window_attributes = Window::default_attributes()
@@ -85,9 +25,58 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.create_window(window_attributes).unwrap()
}
let mut windows = HashMap::new();
let event_loop: EventLoop<()> = EventLoop::new().unwrap();
let mut app = Application::default();
event_loop.run_app(&mut app)
let mut parent_window_id = None;
event_loop.run(move |event: Event<()>, event_loop| {
match event {
Event::Resumed => {
let attributes = Window::default_attributes()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
parent_window_id = Some(window.id());
println!("Parent window id: {parent_window_id:?})");
windows.insert(window.id(), window);
},
Event::WindowEvent { window_id, event } => match event {
WindowEvent::CloseRequested => {
windows.clear();
event_loop.exit();
},
WindowEvent::CursorEntered { device_id: _ } => {
// 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.
println!("cursor entered in the window {window_id:?}");
},
WindowEvent::KeyboardInput {
event: KeyEvent { state: ElementState::Pressed, .. },
..
} => {
let parent_window = windows.get(&parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window(parent_window, event_loop);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
windows.insert(child_id, child_window);
},
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
},
_ => (),
},
_ => (),
}
})
}
#[cfg(all(feature = "rwh_06", not(any(x11_platform, macos_platform, windows_platform))))]

View File

@@ -17,9 +17,7 @@ use softbuffer::{Context, Surface};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::event::{
DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, StartCause, WindowEvent,
};
use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState};
use winit::window::{
@@ -214,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();
@@ -305,12 +309,6 @@ impl Application {
}
impl ApplicationHandler<UserEvent> for Application {
fn new_events(&mut self, _event_loop: &ActiveEventLoop, start_cause: StartCause) {
if let StartCause::Init = start_cause {
info!("Started the event loop");
}
}
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
info!("User event: {event:?}");
}
@@ -328,7 +326,6 @@ impl ApplicationHandler<UserEvent> for Application {
match event {
WindowEvent::Resized(size) => {
info!("Resized({size:?})");
window.resize(size);
},
WindowEvent::Focused(focused) => {
@@ -343,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() {
@@ -742,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();
}
@@ -893,6 +890,7 @@ enum Action {
ShowWindowMenu,
#[cfg(macos_platform)]
CycleOptionAsAlt,
SetTheme(Option<Theme>),
#[cfg(macos_platform)]
CreateNewTab,
RequestResize,
@@ -924,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",
@@ -1068,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)]

View File

@@ -12,7 +12,6 @@ on how to add them:
### Added
- Add `Window::turbo()`, implemented on X11, Wayland, and Web.
- Add traits `EventLoopExtWayland` and `EventLoopExtX11`, providing methods `is_wayland` and `is_x11` on `EventLoop`.
- On X11, add `Window::some_rare_api`.
- On X11, add `Window::even_more_rare_api`.
- On Wayland, add `Window::common_api`.
@@ -40,19 +39,3 @@ The migration guide could reference other migration examples in the current
changelog entry.
## Unreleased
### Added
- Reexport `raw-window-handle` versions 0.4 and 0.5 as `raw_window_handle_04` and `raw_window_handle_05`.
- Implement `ApplicationHandler` for `&mut` references and heap allocations to something that implements `ApplicationHandler`.
### Removed
- Remove `EventLoop::run`.
- Remove `EventLoopExtRunOnDemand::run_on_demand`.
- Remove `EventLoopExtPumpEvents::pump_events`.
### Fixed
- On macOS, fix panic on exit when dropping windows outside the event loop.
- On macOS, fix window dragging glitches when dragging across a monitor boundary with different scale factor.

View File

@@ -1,3 +1,49 @@
## 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
- Reexport `raw-window-handle` versions 0.4 and 0.5 as `raw_window_handle_04` and `raw_window_handle_05`.
- Implement `ApplicationHandler` for `&mut` references and heap allocations to something that implements `ApplicationHandler`.
### Fixed
- On macOS, fix panic on exit when dropping windows outside the event loop.
- On macOS, fix window dragging glitches when dragging across a monitor boundary with different scale factor.
- On macOS, fix the range in `Ime::Preedit`.
- On macOS, use the system's internal mechanisms for queuing events.
- On macOS, handle events directly instead of queuing when possible.
## 0.30.0
### Added
@@ -65,7 +111,7 @@
`Fn`. The semantics are mostly the same, given that the capture list of the
closure is your new `State`. Consider the following code:
```rust,no_run,ignore
```rust,no_run
use winit::event::Event;
use winit::event_loop::EventLoop;
use winit::window::Window;

View File

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

View File

@@ -20,6 +20,7 @@ use web_time::{Duration, Instant};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, OsError};
use crate::event::Event;
use crate::monitor::MonitorHandle;
use crate::platform_impl;
use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
@@ -151,7 +152,6 @@ impl fmt::Debug for ActiveEventLoop {
/// Defaults to [`Wait`].
///
/// [`Wait`]: Self::Wait
/// [`Event::AboutToWait`]: crate::event::Event::AboutToWait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
@@ -209,110 +209,6 @@ impl EventLoop<()> {
}
}
#[cfg(debug_assertions)]
pub(crate) fn ensure_event_order<'a, T: 'static>(
handler: impl ApplicationHandler<T> + 'a,
) -> impl ApplicationHandler<T> + 'a {
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use crate::window::WindowId;
#[derive(Default, Debug, PartialEq, Eq, Clone)]
enum State {
#[default]
NotRunning,
Suspended,
Running,
Waiting,
}
impl State {
#[track_caller]
fn expect(&self, expected: State) {
if *self != expected {
tracing::error!("expected state to be {expected:?}, found {self:?}");
}
}
#[track_caller]
fn transition(&mut self, from: State, to: State) {
if *self != from {
tracing::error!(
"invalid state transition to {to:?}. Expected {from:?}, found {self:?}"
);
}
*self = to;
}
}
struct EnsureEventOrder<A> {
inner: A,
state: State,
}
impl<A: ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for EnsureEventOrder<A> {
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
match cause {
StartCause::Init => self.state.transition(State::NotRunning, State::Suspended),
_ => self.state.transition(State::Waiting, State::Running),
}
self.inner.new_events(event_loop, cause);
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
self.state.transition(State::Suspended, State::Running);
self.inner.resumed(event_loop);
}
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
self.state.transition(State::Running, State::Suspended);
self.inner.suspended(event_loop);
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
self.state.transition(State::Running, State::Waiting);
self.inner.about_to_wait(event_loop);
}
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
self.state.transition(State::Suspended, State::NotRunning);
self.inner.exiting(event_loop);
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
self.state.expect(State::Running);
self.inner.user_event(event_loop, event);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
self.state.expect(State::Running);
self.inner.window_event(event_loop, window_id, event);
}
fn device_event(
&mut self,
event_loop: &ActiveEventLoop,
device_id: DeviceId,
event: DeviceEvent,
) {
self.state.expect(State::Running);
self.inner.device_event(event_loop, device_id, event);
}
fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
// TODO: What states are allowed when receiving this?
self.inner.memory_warning(event_loop);
}
}
EnsureEventOrder { inner: handler, state: State::NotRunning }
}
impl<T> EventLoop<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
@@ -320,6 +216,21 @@ impl<T> EventLoop<T> {
EventLoopBuilder { platform_specific: Default::default(), _p: PhantomData }
}
/// See [`run_app`].
///
/// [`run_app`]: Self::run_app
#[inline]
#[deprecated = "use `EventLoop::run_app` instead"]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &ActiveEventLoop),
{
let _span = tracing::debug_span!("winit::EventLoop::run").entered();
self.event_loop.run(event_handler)
}
/// Run the application with the event loop on the calling thread.
///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
@@ -351,9 +262,7 @@ impl<T> EventLoop<T> {
#[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> Result<(), EventLoopError> {
#[cfg(debug_assertions)]
let app = &mut ensure_event_order(app);
self.event_loop.run_app(app)
self.event_loop.run(|event, event_loop| dispatch_event_for_app(app, event_loop, event))
}
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
@@ -540,7 +449,7 @@ impl ActiveEventLoop {
/// This exits the event loop.
///
/// See [`LoopExiting`][crate::event::Event::LoopExiting].
/// See [`LoopExiting`][Event::LoopExiting].
pub fn exit(&self) {
let _span = tracing::debug_span!("winit::ActiveEventLoop::exit",).entered();
@@ -709,3 +618,23 @@ impl AsyncRequestSerial {
Self { serial }
}
}
/// Shim for various run APIs.
#[inline(always)]
pub(crate) fn dispatch_event_for_app<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
event_loop: &ActiveEventLoop,
event: Event<T>,
) {
match event {
Event::NewEvents(cause) => app.new_events(event_loop, cause),
Event::WindowEvent { window_id, event } => app.window_event(event_loop, window_id, event),
Event::DeviceEvent { device_id, event } => app.device_event(event_loop, device_id, event),
Event::UserEvent(event) => app.user_event(event_loop, event),
Event::Suspended => app.suspended(event_loop),
Event::Resumed => app.resumed(event_loop),
Event::AboutToWait => app.about_to_wait(event_loop),
Event::LoopExiting => app.exiting(event_loop),
Event::MemoryWarning => app.memory_warning(event_loop),
}
}

View File

@@ -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.0",
//! 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

View File

@@ -1,13 +1,12 @@
use std::time::Duration;
use crate::application::ApplicationHandler;
use crate::event_loop::EventLoop;
use crate::event::Event;
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
/// Additional methods on [`EventLoop`] for pumping events within an external event loop
pub trait EventLoopExtPumpEvents {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
///
/// [`Event::UserEvent`]: crate::event::Event::UserEvent
type UserEvent: 'static;
/// Pump the `EventLoop` to check for and dispatch pending events.
@@ -108,18 +107,30 @@ pub trait EventLoopExtPumpEvents {
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus;
) -> PumpStatus {
#[allow(deprecated)]
self.pump_events(timeout, |event, event_loop| {
event_loop::dispatch_event_for_app(app, event_loop, event)
})
}
/// See [`pump_app_events`].
///
/// [`pump_app_events`]: Self::pump_app_events
#[deprecated = "use EventLoopExtPumpEvents::pump_app_events"]
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
}
impl<T> EventLoopExtPumpEvents for EventLoop<T> {
type UserEvent = T;
fn pump_app_events<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
self.event_loop.pump_app_events(timeout, app)
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.pump_events(timeout, event_handler)
}
}

View File

@@ -1,6 +1,7 @@
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::event::Event;
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
#[cfg(doc)]
use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window};
@@ -8,10 +9,16 @@ use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window};
/// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
///
/// [`Event::UserEvent`]: crate::event::Event::UserEvent
type UserEvent: 'static;
/// See [`run_app_on_demand`].
///
/// [`run_app_on_demand`]: Self::run_app_on_demand
#[deprecated = "use EventLoopExtRunOnDemand::run_app_on_demand"]
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
/// Run the application with the event loop on the calling thread.
///
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
@@ -61,18 +68,23 @@ pub trait EventLoopExtRunOnDemand {
fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError>;
) -> Result<(), EventLoopError> {
#[allow(deprecated)]
self.run_on_demand(|event, event_loop| {
event_loop::dispatch_event_for_app(app, event_loop, event)
})
}
}
impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
type UserEvent = T;
fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.window_target().clear_exit();
self.event_loop.run_app_on_demand(app)
self.event_loop.run_on_demand(event_handler)
}
}

View File

@@ -55,7 +55,8 @@ use web_sys::HtmlCanvasElement;
use crate::application::ApplicationHandler;
use crate::cursor::CustomCursorSource;
use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::event::Event;
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
#[cfg(web_platform)]
use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture;
use crate::platform_impl::PlatformCustomCursorSource;
@@ -155,9 +156,7 @@ impl WindowAttributesExtWebSys for WindowAttributes {
/// Additional methods on `EventLoop` that are specific to the web.
pub trait EventLoopExtWebSys {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
///
/// [`Event::UserEvent`]: crate::event::Event::UserEvent
/// A type provided by the user that can be passed through `Event::UserEvent`.
type UserEvent: 'static;
/// Initializes the winit event loop.
@@ -183,13 +182,74 @@ pub trait EventLoopExtWebSys {
)]
/// [^1]: `run_app()` is _not_ available on WASM when the target supports `exception-handling`.
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, app: A);
/// See [`spawn_app`].
///
/// [`spawn_app`]: Self::spawn_app
#[deprecated = "use EventLoopExtWebSys::spawn_app"]
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> {
type UserEvent = T;
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, app: A) {
self.event_loop.spawn_app(app);
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, mut app: A) {
self.event_loop.spawn(move |event, event_loop| {
event_loop::dispatch_event_for_app(&mut app, event_loop, event)
});
}
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
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()
}
}
@@ -208,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;
@@ -228,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].
@@ -256,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;

View File

@@ -1,5 +1,3 @@
#![cfg(android_platform)]
use std::cell::Cell;
use std::collections::VecDeque;
use std::hash::Hash;
@@ -14,13 +12,12 @@ use android_activity::{
};
use tracing::{debug, trace, warn};
use crate::application::ApplicationHandler;
use crate::cursor::Cursor;
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error;
use crate::error::EventLoopError;
use crate::event::{self, Force, InnerSizeWriter, StartCause};
use crate::event_loop::{self, ControlFlow, DeviceEvents};
use crate::event_loop::{self, ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Fullscreen;
use crate::window::{
@@ -198,28 +195,27 @@ impl<T: 'static> EventLoop<T> {
})
}
fn single_iteration<A: ApplicationHandler<T>>(
&mut self,
main_event: Option<MainEvent<'_>>,
app: &mut A,
) {
fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F)
where
F: FnMut(event::Event<T>, &RootAEL),
{
trace!("Mainloop iteration");
let cause = self.cause;
let mut pending_redraw = self.pending_redraw;
let mut resized = false;
app.new_events(self.window_target(), cause);
callback(event::Event::NewEvents(cause), self.window_target());
if let Some(event) = main_event {
trace!("Handling main event {:?}", event);
match event {
MainEvent::InitWindow { .. } => {
app.resumed(self.window_target());
callback(event::Event::Resumed, self.window_target());
},
MainEvent::TerminateWindow { .. } => {
app.suspended(self.window_target());
callback(event::Event::Suspended, self.window_target());
},
MainEvent::WindowResized { .. } => resized = true,
MainEvent::RedrawNeeded { .. } => pending_redraw = true,
@@ -228,15 +224,23 @@ impl<T: 'static> EventLoop<T> {
},
MainEvent::GainedFocus => {
HAS_FOCUS.store(true, Ordering::Relaxed);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Focused(true);
app.window_event(self.window_target(), window_id, event);
callback(
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Focused(true),
},
self.window_target(),
);
},
MainEvent::LostFocus => {
HAS_FOCUS.store(false, Ordering::Relaxed);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Focused(false);
app.window_event(self.window_target(), window_id, event);
callback(
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Focused(false),
},
self.window_target(),
);
},
MainEvent::ConfigChanged { .. } => {
let monitor = MonitorHandle::new(self.android_app.clone());
@@ -246,19 +250,20 @@ impl<T: 'static> EventLoop<T> {
let new_inner_size = Arc::new(Mutex::new(
MonitorHandle::new(self.android_app.clone()).size(),
));
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::ScaleFactorChanged {
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
scale_factor,
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::ScaleFactorChanged {
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
scale_factor,
},
};
app.window_event(self.window_target(), window_id, event);
callback(event, self.window_target());
}
},
MainEvent::LowMemory => {
app.memory_warning(self.window_target());
callback(event::Event::MemoryWarning, self.window_target());
},
MainEvent::Start => {
// XXX: how to forward this state to applications?
@@ -306,7 +311,7 @@ impl<T: 'static> EventLoop<T> {
match android_app.input_events_iter() {
Ok(mut input_iter) => loop {
let read_event =
input_iter.next(|event| self.handle_input_event(&android_app, event, app));
input_iter.next(|event| self.handle_input_event(&android_app, event, callback));
if !read_event {
break;
@@ -320,7 +325,7 @@ impl<T: 'static> EventLoop<T> {
// Empty the user event buffer
{
while let Ok(event) = self.user_events_receiver.try_recv() {
app.user_event(self.window_target(), event);
callback(crate::event::Event::UserEvent(event), self.window_target());
}
}
@@ -333,32 +338,39 @@ impl<T: 'static> EventLoop<T> {
} else {
PhysicalSize::new(0, 0)
};
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Resized(size);
app.window_event(self.window_target(), window_id, event);
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Resized(size),
};
callback(event, self.window_target());
}
pending_redraw |= self.redraw_flag.get_and_reset();
if pending_redraw {
pending_redraw = false;
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::RedrawRequested;
app.window_event(self.window_target(), window_id, event);
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::RedrawRequested,
};
callback(event, self.window_target());
}
}
// This is always the last event we dispatch before poll again
app.about_to_wait(self.window_target());
callback(event::Event::AboutToWait, self.window_target());
self.pending_redraw = pending_redraw;
}
fn handle_input_event<A: ApplicationHandler<T>>(
fn handle_input_event<F>(
&mut self,
android_app: &AndroidApp,
event: &InputEvent<'_>,
app: &mut A,
) -> InputStatus {
callback: &mut F,
) -> InputStatus
where
F: FnMut(event::Event<T>, &RootAEL),
{
let mut input_status = InputStatus::Handled;
match event {
InputEvent::MotionEvent(motion_event) => {
@@ -396,16 +408,17 @@ impl<T: 'static> EventLoop<T> {
"Input event {device_id:?}, {phase:?}, loc={location:?}, \
pointer={pointer:?}"
);
let event = event::WindowEvent::Touch(event::Touch {
device_id,
phase,
location,
id: pointer.pointer_id() as u64,
force: Some(Force::Normalized(pointer.pressure() as f64)),
});
app.window_event(self.window_target(), window_id, event);
let event = event::Event::WindowEvent {
window_id,
event: event::WindowEvent::Touch(event::Touch {
device_id,
phase,
location,
id: pointer.pointer_id() as u64,
force: Some(Force::Normalized(pointer.pressure() as f64)),
}),
};
callback(event, self.window_target());
}
}
},
@@ -433,22 +446,23 @@ impl<T: 'static> EventLoop<T> {
&mut self.combining_accent,
);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId(key.device_id())),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_key(keycode),
logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
platform_specific: KeyEventExtra {},
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId(key.device_id())),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_key(keycode),
logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
},
is_synthetic: false,
};
app.window_event(self.window_target(), window_id, event);
callback(event, self.window_target());
},
}
},
@@ -460,16 +474,19 @@ impl<T: 'static> EventLoop<T> {
input_status
}
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{
self.run_on_demand(event_handler)
}
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{
loop {
match self.pump_app_events(None, app) {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
},
@@ -483,11 +500,10 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(event::Event<T>, &RootAEL),
{
if !self.loop_running {
self.loop_running = true;
@@ -498,18 +514,18 @@ impl<T: 'static> EventLoop<T> {
self.cause = StartCause::Init;
// run the initial loop iteration
self.single_iteration(None, app);
self.single_iteration(None, &mut callback);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit
if !self.exiting() {
self.poll_events_with_timeout(timeout, app);
self.poll_events_with_timeout(timeout, &mut callback);
}
if self.exiting() {
self.loop_running = false;
app.exiting(self.window_target());
callback(event::Event::LoopExiting, self.window_target());
PumpStatus::Exit(0)
} else {
@@ -517,11 +533,10 @@ impl<T: 'static> EventLoop<T> {
}
}
fn poll_events_with_timeout<A: ApplicationHandler<T>>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(event::Event<T>, &RootAEL),
{
let start = Instant::now();
self.pending_redraw |= self.redraw_flag.get_and_reset();
@@ -542,8 +557,8 @@ impl<T: 'static> EventLoop<T> {
min_timeout(control_flow_timeout, timeout)
};
let android_app = self.android_app.clone(); // Don't borrow self as part of poll expression
android_app.poll_events(timeout, |poll_event| {
let app = self.android_app.clone(); // Don't borrow self as part of poll expression
app.poll_events(timeout, |poll_event| {
let mut main_event = None;
match poll_event {
@@ -584,7 +599,7 @@ impl<T: 'static> EventLoop<T> {
},
};
self.single_iteration(main_event, app);
self.single_iteration(main_event, &mut callback);
});
}

View File

@@ -1,11 +1,9 @@
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
use objc2_ui_kit::{UIApplication, UIWindow};
use objc2_foundation::{MainThreadMarker, NSObject};
use objc2_ui_kit::UIApplication;
use super::app_state::{self, EventWrapper};
use super::window::WinitUIWindow;
use crate::event::{Event, WindowEvent};
use crate::window::WindowId as RootWindowId;
use super::app_state::{self, send_occluded_event_for_all_windows, EventWrapper};
use crate::event::Event;
declare_class!(
pub struct AppDelegate;
@@ -40,35 +38,17 @@ declare_class!(
#[method(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, false);
send_occluded_event_for_all_windows(application, false);
}
#[method(applicationDidEnterBackground:)]
fn did_enter_background(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, true);
send_occluded_event_for_all_windows(application, true);
}
#[method(applicationWillTerminate:)]
fn will_terminate(&self, application: &UIApplication) {
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Destroyed,
}));
}
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, events);
app_state::terminated(mtm);
app_state::terminated(application);
}
#[method(applicationDidReceiveMemoryWarning:)]
@@ -78,26 +58,3 @@ declare_class!(
}
}
);
impl AppDelegate {
fn send_occluded_event_for_all_windows(&self, application: &UIApplication, occluded: bool) {
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Occluded(occluded),
}));
}
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, events);
}
}

View File

@@ -20,7 +20,7 @@ use objc2_foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
NSProcessInfo,
};
use objc2_ui_kit::{UICoordinateSpace, UIView};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
use super::window::WinitUIWindow;
use crate::dpi::PhysicalSize;
@@ -662,6 +662,28 @@ fn handle_user_events(mtm: MainThreadMarker) {
}
}
pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, occluded: bool) {
let mtm = MainThreadMarker::from(application);
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Occluded(occluded),
}));
}
}
handle_nonuser_events(mtm, events);
}
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
if !this.has_launched() || this.has_terminated() {
@@ -696,7 +718,27 @@ pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition();
}
pub fn terminated(mtm: MainThreadMarker) {
pub(crate) fn terminated(application: &UIApplication) {
let mtm = MainThreadMarker::from(application);
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Destroyed,
}));
}
}
handle_nonuser_events(mtm, events);
let mut this = AppState::get_mut(mtm);
let mut handler = this.terminated_transition();
drop(this);
@@ -756,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,
@@ -770,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) {

View File

@@ -16,7 +16,6 @@ use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSString};
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIDevice, UIScreen, UIUserInterfaceIdiom};
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::Event;
use crate::event_loop::{
@@ -109,28 +108,17 @@ impl OwnedDisplayHandle {
}
}
fn map_user_event<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootActiveEventLoop),
receiver: mpsc::Receiver<T>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + '_ {
move |event, window_target| match event {
Event::NewEvents(cause) => app.new_events(window_target, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
Event::UserEvent(_) => {
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) {
move |event, window_target| match event.map_nonuser_event() {
Ok(event) => (handler)(event, window_target),
Err(_) => {
for event in receiver.try_iter() {
app.user_event(window_target, event);
(handler)(Event::UserEvent(event), window_target);
}
},
Event::Suspended => app.suspended(window_target),
Event::Resumed => app.resumed(window_target),
Event::AboutToWait => app.about_to_wait(window_target),
Event::LoopExiting => app.exiting(window_target),
Event::MemoryWarning => app.memory_warning(window_target),
}
}
@@ -173,7 +161,10 @@ impl<T: 'static> EventLoop<T> {
})
}
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> ! {
pub fn run<F>(self, handler: F) -> !
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let application: Option<Retained<UIApplication>> =
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
assert!(
@@ -183,7 +174,7 @@ impl<T: 'static> EventLoop<T> {
`EventLoop::run_app` calls `UIApplicationMain` on iOS",
);
let handler = map_user_event(app, self.receiver);
let handler = map_user_event(handler, self.receiver);
let handler = unsafe {
std::mem::transmute::<
@@ -283,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);
@@ -367,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(),
);
@@ -387,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(),
);

View File

@@ -1,4 +1,3 @@
#![cfg(ios_platform)]
#![allow(clippy::let_unit_value)]
mod app_delegate;

View File

@@ -11,8 +11,6 @@ use std::{env, fmt};
#[cfg(x11_platform)]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
use crate::application::ApplicationHandler;
use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)]
use crate::utils::Lazy;
use smol_str::SmolStr;
@@ -21,9 +19,12 @@ use smol_str::SmolStr;
use self::x11::{X11Error, XConnection, XError, XNotSupported};
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event_loop::{AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed};
use crate::event_loop::{
ActiveEventLoop as RootELW, AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed,
};
use crate::icon::Icon;
use crate::keyboard::Key;
use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)]
use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
use crate::window::{
@@ -798,23 +799,25 @@ impl<T: 'static> EventLoop<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
}
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app))
pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
self.run_on_demand(callback)
}
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app_on_demand(app))
pub fn run_on_demand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(callback))
}
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_app_events(timeout, app))
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback))
}
pub fn window_target(&self) -> &crate::event_loop::ActiveEventLoop {

View File

@@ -14,7 +14,6 @@ use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::{globals, Connection, QueueHandle};
use crate::application::ApplicationHandler;
use crate::cursor::OnlyCursorImage;
use crate::dpi::LogicalSize;
use crate::error::{EventLoopError, OsError as RootOsError};
@@ -174,16 +173,12 @@ impl<T: 'static> EventLoop<T> {
Ok(event_loop)
}
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let exit = loop {
match self.pump_app_events(None, app) {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
},
@@ -205,27 +200,26 @@ impl<T: 'static> EventLoop<T> {
exit
}
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
if !self.loop_running {
self.loop_running = true;
// Run the initial loop iteration.
self.single_iteration(app, StartCause::Init);
self.single_iteration(&mut callback, StartCause::Init);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit.
if !self.exiting() {
self.poll_events_with_timeout(timeout, app);
self.poll_events_with_timeout(timeout, &mut callback);
}
if let Some(code) = self.exit_code() {
self.loop_running = false;
app.exiting(&self.window_target);
callback(Event::LoopExiting, self.window_target());
PumpStatus::Exit(code)
} else {
@@ -233,11 +227,10 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn poll_events_with_timeout<A: ApplicationHandler<T>>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let cause = loop {
let start = Instant::now();
@@ -299,10 +292,13 @@ impl<T: 'static> EventLoop<T> {
break cause;
};
self.single_iteration(app, cause);
self.single_iteration(&mut callback, cause);
}
fn single_iteration<A: ApplicationHandler<T>>(&mut self, app: &mut A, cause: StartCause) {
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
// NOTE currently just indented to simplify the diff
// We retain these grow-only scratch buffers as part of the EventLoop
@@ -313,18 +309,18 @@ impl<T: 'static> EventLoop<T> {
let mut buffer_sink = std::mem::take(&mut self.buffer_sink);
let mut window_ids = std::mem::take(&mut self.window_ids);
app.new_events(&self.window_target, cause);
callback(Event::NewEvents(cause), &self.window_target);
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
// applications don't themselves have a formal suspend/resume lifecycle.
if cause == StartCause::Init {
app.resumed(&self.window_target);
callback(Event::Resumed, &self.window_target);
}
// Handle pending user events. We don't need back buffer, since we can't dispatch
// user events indirectly via callback to the user.
for user_event in self.pending_user_events.borrow_mut().drain(..) {
app.user_event(&self.window_target, user_event);
callback(Event::UserEvent(user_event), &self.window_target);
}
// Drain the pending compositor updates.
@@ -345,13 +341,18 @@ impl<T: 'static> EventLoop<T> {
let old_physical_size = physical_size;
let new_inner_size = Arc::new(Mutex::new(physical_size));
let root_window_id = crate::window::WindowId(window_id);
let event = WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
};
app.window_event(&self.window_target, root_window_id, event);
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
},
},
&self.window_target,
);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
@@ -394,14 +395,23 @@ impl<T: 'static> EventLoop<T> {
size
});
let window_id = crate::window::WindowId(window_id);
let event = WindowEvent::Resized(physical_size);
app.window_event(&self.window_target, window_id, event);
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
);
}
if compositor_update.close_window {
let window_id = crate::window::WindowId(window_id);
app.window_event(&self.window_target, window_id, WindowEvent::CloseRequested);
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
);
}
}
@@ -410,15 +420,8 @@ impl<T: 'static> EventLoop<T> {
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
});
for event in buffer_sink.drain() {
match event {
Event::WindowEvent { window_id, event } => {
app.window_event(&self.window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.window_target, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
let event = event.map_nonuser_event().unwrap();
callback(event, &self.window_target);
}
// Handle non-synthetic events.
@@ -426,15 +429,8 @@ impl<T: 'static> EventLoop<T> {
buffer_sink.append(&mut state.events_sink);
});
for event in buffer_sink.drain() {
match event {
Event::WindowEvent { window_id, event } => {
app.window_event(&self.window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.window_target, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
let event = event.map_nonuser_event().unwrap();
callback(event, &self.window_target);
}
// Collect the window ids
@@ -470,8 +466,10 @@ impl<T: 'static> EventLoop<T> {
});
if let Some(event) = event {
let window_id = crate::window::WindowId(*window_id);
app.window_event(&self.window_target, window_id, event);
callback(
Event::WindowEvent { window_id: crate::window::WindowId(*window_id), event },
&self.window_target,
);
}
}
@@ -481,7 +479,7 @@ impl<T: 'static> EventLoop<T> {
});
// This is always the last event we dispatch before poll again
app.about_to_wait(&self.window_target);
callback(Event::AboutToWait, &self.window_target);
// Update the window frames and schedule redraws.
let mut wake_up = false;

View File

@@ -1,5 +1,3 @@
#![cfg(wayland_platform)]
//! Winit's Wayland backend.
use std::fmt::Display;

View File

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

View File

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

View File

@@ -1,5 +1,3 @@
#![cfg(x11_platform)]
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet, VecDeque};
use std::ffi::CStr;
@@ -27,7 +25,6 @@ use x11rb::protocol::xproto::{self, ConnectionExt as _};
use x11rb::x11_utils::X11Error as LogicalError;
use x11rb::xcb_ffi::ReplyOrIdError;
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, OsError as RootOsError};
use crate::event::{Event, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents, EventLoopClosed};
@@ -380,16 +377,12 @@ impl<T: 'static> EventLoop<T> {
&self.event_processor.target
}
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootAEL),
{
let exit = loop {
match self.pump_app_events(None, app) {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
},
@@ -414,27 +407,26 @@ impl<T: 'static> EventLoop<T> {
exit
}
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootAEL),
{
if !self.loop_running {
self.loop_running = true;
// run the initial loop iteration
self.single_iteration(app, StartCause::Init);
self.single_iteration(&mut callback, StartCause::Init);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit.
if !self.exiting() {
self.poll_events_with_timeout(timeout, app);
self.poll_events_with_timeout(timeout, &mut callback);
}
if let Some(code) = self.exit_code() {
self.loop_running = false;
app.exiting(self.window_target());
callback(Event::LoopExiting, self.window_target());
PumpStatus::Exit(code)
} else {
@@ -448,11 +440,10 @@ impl<T: 'static> EventLoop<T> {
|| self.redraw_receiver.has_incoming()
}
pub fn poll_events_with_timeout<A: ApplicationHandler<T>>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(Event<T>, &RootAEL),
{
let start = Instant::now();
let has_pending = self.has_pending();
@@ -510,20 +501,23 @@ impl<T: 'static> EventLoop<T> {
return;
}
self.single_iteration(app, cause);
self.single_iteration(&mut callback, cause);
}
fn single_iteration<A: ApplicationHandler<T>>(&mut self, app: &mut A, cause: StartCause) {
app.new_events(&self.event_processor.target, cause);
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootAEL),
{
callback(Event::NewEvents(cause), &self.event_processor.target);
// NB: For consistency all platforms must emit a 'resumed' event even though X11
// applications don't themselves have a formal suspend/resume lifecycle.
if cause == StartCause::Init {
app.resumed(&self.event_processor.target)
callback(Event::Resumed, &self.event_processor.target);
}
// Process all pending events
self.drain_events(app);
self.drain_events(callback);
// Empty activation tokens.
while let Ok((window_id, serial)) = self.activation_receiver.try_recv() {
@@ -533,12 +527,14 @@ impl<T: 'static> EventLoop<T> {
match token {
Some(Ok(token)) => {
let window_id = crate::window::WindowId(window_id);
let event = WindowEvent::ActivationTokenDone {
serial,
token: crate::window::ActivationToken::_new(token),
let event = Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ActivationTokenDone {
serial,
token: crate::window::ActivationToken::_new(token),
},
};
app.window_event(&self.event_processor.target, window_id, event);
callback(event, &self.event_processor.target)
},
Some(Err(e)) => {
tracing::error!("Failed to get activation token: {}", e);
@@ -550,7 +546,7 @@ impl<T: 'static> EventLoop<T> {
// Empty the user event buffer
{
while let Ok(event) = self.user_receiver.try_recv() {
app.user_event(&self.event_processor.target, event);
callback(Event::UserEvent(event), &self.event_processor.target);
}
}
@@ -564,24 +560,28 @@ impl<T: 'static> EventLoop<T> {
for window_id in windows {
let window_id = crate::window::WindowId(window_id);
app.window_event(
callback(
Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested },
&self.event_processor.target,
window_id,
WindowEvent::RedrawRequested,
);
}
}
// This is always the last event we dispatch before poll again
app.about_to_wait(&self.event_processor.target);
{
callback(Event::AboutToWait, &self.event_processor.target);
}
}
fn drain_events<A: ApplicationHandler<T>>(&mut self, app: &mut A) {
fn drain_events<F>(&mut self, callback: &mut F)
where
F: FnMut(Event<T>, &RootAEL),
{
let mut xev = MaybeUninit::uninit();
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
self.event_processor.process_event(&mut xev, |window_target, event: Event<T>| {
self.event_processor.process_event(&mut xev, |window_target, event| {
if let Event::WindowEvent {
window_id: crate::window::WindowId(wid),
event: WindowEvent::RedrawRequested,
@@ -590,15 +590,7 @@ impl<T: 'static> EventLoop<T> {
let window_target = EventProcessor::window_target(window_target);
window_target.redraw_sender.send(wid).unwrap();
} else {
match event {
Event::WindowEvent { window_id, event } => {
app.window_event(window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
callback(event, window_target);
}
});
}

View File

@@ -4,7 +4,7 @@ use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use objc2_foundation::{MainThreadMarker, NSObject};
use super::app_delegate::ApplicationDelegate;
use super::app_state::ApplicationDelegate;
use crate::event::{DeviceEvent, ElementState};
declare_class!(
@@ -57,25 +57,27 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
let delta_y = unsafe { event.deltaY() } as f64;
if delta_x != 0.0 {
delegate.queue_device_event(DeviceEvent::Motion { axis: 0, value: delta_x });
delegate.maybe_queue_device_event(DeviceEvent::Motion { axis: 0, value: delta_x });
}
if delta_y != 0.0 {
delegate.queue_device_event(DeviceEvent::Motion { axis: 1, value: delta_y })
delegate.maybe_queue_device_event(DeviceEvent::Motion { axis: 1, value: delta_y })
}
if delta_x != 0.0 || delta_y != 0.0 {
delegate.queue_device_event(DeviceEvent::MouseMotion { delta: (delta_x, delta_y) });
delegate.maybe_queue_device_event(DeviceEvent::MouseMotion {
delta: (delta_x, delta_y),
});
}
},
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
delegate.queue_device_event(DeviceEvent::Button {
delegate.maybe_queue_device_event(DeviceEvent::Button {
button: unsafe { event.buttonNumber() } as u32,
state: ElementState::Pressed,
});
},
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
delegate.queue_device_event(DeviceEvent::Button {
delegate.maybe_queue_device_event(DeviceEvent::Button {
button: unsafe { event.buttonNumber() } as u32,
state: ElementState::Released,
});

View File

@@ -1,40 +1,27 @@
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::mem;
use std::rc::Weak;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol, NSSize};
use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol};
use super::event_handler::EventHandler;
use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo};
use super::observer::{EventLoopWaker, RunLoop};
use super::window::WinitWindow;
use super::{menu, WindowId, DEVICE_ID};
use crate::dpi::PhysicalSize;
use crate::event::{DeviceEvent, Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event::{DeviceEvent, Event, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow};
use crate::window::WindowId as RootWindowId;
#[derive(Debug)]
struct Policy(NSApplicationActivationPolicy);
impl Default for Policy {
fn default() -> Self {
Self(NSApplicationActivationPolicy::Regular)
}
}
#[derive(Debug, Default)]
pub(super) struct State {
activation_policy: Policy,
pub(super) struct AppState {
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
activate_ignoring_other_apps: bool,
run_loop: RunLoop,
event_handler: EventHandler,
stop_on_launch: Cell<bool>,
stop_before_wait: Cell<bool>,
@@ -50,7 +37,6 @@ pub(super) struct State {
waker: RefCell<EventLoopWaker>,
start_time: Cell<Option<Instant>>,
wait_timeout: Cell<Option<Instant>>,
pending_events: RefCell<VecDeque<QueuedEvent>>,
pending_redraw: RefCell<Vec<WindowId>>,
// NOTE: This is strongly referenced by our `NSWindowDelegate` and our `NSView` subclass, and
// as such should be careful to not add fields that, in turn, strongly reference those.
@@ -67,62 +53,20 @@ declare_class!(
}
impl DeclaredClass for ApplicationDelegate {
type Ivars = State;
type Ivars = AppState;
}
unsafe impl NSObjectProtocol for ApplicationDelegate {}
unsafe impl NSApplicationDelegate for ApplicationDelegate {
// NOTE: This will, globally, only be run once, no matter how many
// `EventLoop`s the user creates.
#[method(applicationDidFinishLaunching:)]
fn did_finish_launching(&self, _sender: Option<&AnyObject>) {
trace_scope!("applicationDidFinishLaunching:");
self.ivars().is_launched.set(true);
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
app.setActivationPolicy(self.ivars().activation_policy.0);
window_activation_hack(&app);
#[allow(deprecated)]
app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps);
if self.ivars().default_menu {
// The menubar initialization should be before the `NewEvents` event, to allow
// overriding of the default menu even if it's created
menu::initialize(&app);
}
self.ivars().waker.borrow_mut().start();
self.set_is_running(true);
self.dispatch_init_events();
// If the application is being launched via `EventLoop::pump_app_events()` then we'll
// want to stop the app once it is launched (and return to the external loop)
//
// In this case we still want to consider Winit's `EventLoop` to be "running",
// so we call `start_running()` above.
if self.ivars().stop_on_launch.get() {
// NOTE: the original idea had been to only stop the underlying `RunLoop`
// for the app but that didn't work as expected (`-[NSApplication run]`
// effectively ignored the attempt to stop the RunLoop and re-started it).
//
// So we return from `pump_events` by stopping the application.
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
}
fn app_did_finish_launching(&self, notification: &NSNotification) {
self.did_finish_launching(notification)
}
#[method(applicationWillTerminate:)]
fn will_terminate(&self, _sender: Option<&AnyObject>) {
trace_scope!("applicationWillTerminate:");
// TODO: Notify every window that it will be destroyed, like done in iOS?
self.internal_exit();
fn app_will_terminate(&self, notification: &NSNotification) {
self.will_terminate(notification)
}
}
);
@@ -134,15 +78,78 @@ impl ApplicationDelegate {
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Retained<Self> {
let this = mtm.alloc().set_ivars(State {
activation_policy: Policy(activation_policy),
let this = mtm.alloc().set_ivars(AppState {
activation_policy,
default_menu,
activate_ignoring_other_apps,
..Default::default()
run_loop: RunLoop::main(mtm),
event_handler: EventHandler::new(),
stop_on_launch: Cell::new(false),
stop_before_wait: Cell::new(false),
stop_after_wait: Cell::new(false),
stop_on_redraw: Cell::new(false),
is_launched: Cell::new(false),
is_running: Cell::new(false),
exit: Cell::new(false),
control_flow: Cell::new(ControlFlow::default()),
waker: RefCell::new(EventLoopWaker::new()),
start_time: Cell::new(None),
wait_timeout: Cell::new(None),
pending_redraw: RefCell::new(vec![]),
});
unsafe { msg_send_id![super(this), init] }
}
// NOTE: This will, globally, only be run once, no matter how many
// `EventLoop`s the user creates.
fn did_finish_launching(&self, _notification: &NSNotification) {
trace_scope!("applicationDidFinishLaunching:");
self.ivars().is_launched.set(true);
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
app.setActivationPolicy(self.ivars().activation_policy);
window_activation_hack(&app);
#[allow(deprecated)]
app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps);
if self.ivars().default_menu {
// The menubar initialization should be before the `NewEvents` event, to allow
// overriding of the default menu even if it's created
menu::initialize(&app);
}
self.ivars().waker.borrow_mut().start();
self.set_is_running(true);
self.dispatch_init_events();
// If the application is being launched via `EventLoop::pump_app_events()` then we'll
// want to stop the app once it is launched (and return to the external loop)
//
// In this case we still want to consider Winit's `EventLoop` to be "running",
// so we call `start_running()` above.
if self.ivars().stop_on_launch.get() {
// NOTE: the original idea had been to only stop the underlying `RunLoop`
// for the app but that didn't work as expected (`-[NSApplication run]`
// effectively ignored the attempt to stop the RunLoop and re-started it).
//
// So we return from `pump_events` by stopping the application.
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
}
}
fn will_terminate(&self, _notification: &NSNotification) {
trace_scope!("applicationWillTerminate:");
// TODO: Notify every window that it will be destroyed, like done in iOS?
self.internal_exit();
}
pub fn get(mtm: MainThreadMarker) -> Retained<Self> {
let app = NSApplication::sharedApplication(mtm);
let delegate =
@@ -193,7 +200,6 @@ impl ApplicationDelegate {
/// NOTE: that if the `NSApplication` has been launched then that state is preserved,
/// and we won't need to re-launch the app if subsequent EventLoops are run.
pub fn internal_exit(&self) {
self.handle_event(Event::Suspended);
self.handle_event(Event::LoopExiting);
self.set_is_running(false);
@@ -235,28 +241,16 @@ impl ApplicationDelegate {
self.ivars().control_flow.get()
}
pub fn queue_window_event(&self, window_id: WindowId, event: WindowEvent) {
self.ivars()
.pending_events
.borrow_mut()
.push_back(QueuedEvent::WindowEvent(window_id, event));
pub fn maybe_queue_window_event(&self, window_id: WindowId, event: WindowEvent) {
self.maybe_queue_event(Event::WindowEvent { window_id: RootWindowId(window_id), event });
}
pub fn queue_device_event(&self, event: DeviceEvent) {
self.ivars().pending_events.borrow_mut().push_back(QueuedEvent::DeviceEvent(event));
pub fn handle_window_event(&self, window_id: WindowId, event: WindowEvent) {
self.handle_event(Event::WindowEvent { window_id: RootWindowId(window_id), event });
}
pub fn queue_static_scale_factor_changed_event(
&self,
window: Retained<WinitWindow>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
) {
self.ivars().pending_events.borrow_mut().push_back(QueuedEvent::ScaleFactorChanged {
window,
suggested_size,
scale_factor,
});
pub fn maybe_queue_device_event(&self, event: DeviceEvent) {
self.maybe_queue_event(Event::DeviceEvent { device_id: DEVICE_ID, event });
}
pub fn handle_redraw(&self, window_id: WindowId) {
@@ -284,9 +278,27 @@ impl ApplicationDelegate {
if !pending_redraw.contains(&window_id) {
pending_redraw.push(window_id);
}
unsafe { RunLoop::get() }.wakeup();
self.ivars().run_loop.wakeup();
}
#[track_caller]
fn maybe_queue_event(&self, event: Event<HandlePendingUserEvents>) {
// Most programmer actions in AppKit (e.g. change window fullscreen, set focused, etc.)
// result in an event being queued, and applied at a later point.
//
// However, it is not documented which actions do this, and which ones are done immediately,
// so to make sure that we don't encounter re-entrancy issues, we first check if we're
// currently handling another event, and if we are, we queue the event instead.
if !self.ivars().event_handler.in_use() {
self.handle_event(event);
} else {
tracing::debug!(?event, "had to queue event since another is currently being handled");
let this = self.retain();
self.ivars().run_loop.queue_closure(move || this.handle_event(event));
}
}
#[track_caller]
fn handle_event(&self, event: Event<HandlePendingUserEvents>) {
self.ivars().event_handler.handle_event(event, &ActiveEventLoop::new_root(self.retain()))
}
@@ -348,49 +360,6 @@ impl ApplicationDelegate {
self.handle_event(Event::UserEvent(HandlePendingUserEvents));
let events = mem::take(&mut *self.ivars().pending_events.borrow_mut());
for event in events {
match event {
QueuedEvent::WindowEvent(window_id, event) => {
self.handle_event(Event::WindowEvent {
window_id: RootWindowId(window_id),
event,
});
},
QueuedEvent::DeviceEvent(event) => {
self.handle_event(Event::DeviceEvent { device_id: DEVICE_ID, event });
},
QueuedEvent::ScaleFactorChanged { window, suggested_size, scale_factor } => {
let new_inner_size = Arc::new(Mutex::new(suggested_size));
let scale_factor_changed_event = Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
},
};
self.handle_event(scale_factor_changed_event);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
if physical_size != suggested_size {
let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
}
let resized_event = Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Resized(physical_size),
};
self.handle_event(resized_event);
},
}
}
let redraw = mem::take(&mut *self.ivars().pending_redraw.borrow_mut());
for window_id in redraw {
self.handle_event(Event::WindowEvent {
@@ -421,17 +390,6 @@ impl ApplicationDelegate {
}
}
#[derive(Debug)]
pub(crate) enum QueuedEvent {
WindowEvent(WindowId, WindowEvent),
DeviceEvent(DeviceEvent),
ScaleFactorChanged {
window: Retained<WinitWindow>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
},
}
#[derive(Debug)]
pub(crate) struct HandlePendingUserEvents;

View File

@@ -1,7 +1,7 @@
use std::cell::RefCell;
use std::{fmt, mem};
use super::app_delegate::HandlePendingUserEvents;
use super::app_state::HandlePendingUserEvents;
use crate::event::Event;
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
@@ -16,7 +16,7 @@ impl fmt::Debug for EventHandlerData {
}
}
#[derive(Debug, Default)]
#[derive(Debug)]
pub(crate) struct EventHandler {
/// This can be in the following states:
/// - Not registered by the event loop (None).
@@ -26,6 +26,10 @@ pub(crate) struct EventHandler {
}
impl EventHandler {
pub(crate) const fn new() -> Self {
Self { inner: RefCell::new(None) }
}
/// Set the event loop handler for the duration of the given closure.
///
/// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates

View File

@@ -21,11 +21,10 @@ use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow};
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
use super::app::WinitApplication;
use super::app_delegate::{ApplicationDelegate, HandlePendingUserEvents};
use super::app_state::{ApplicationDelegate, HandlePendingUserEvents};
use super::event::dummy_event;
use super::monitor::{self, MonitorHandle};
use super::observer::setup_control_flow_observers;
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::Event;
use crate::event_loop::{
@@ -156,28 +155,17 @@ impl ActiveEventLoop {
}
}
fn map_user_event<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootWindowTarget),
receiver: Rc<mpsc::Receiver<T>>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootWindowTarget) + '_ {
move |event, window_target| match event {
Event::NewEvents(cause) => app.new_events(window_target, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
Event::UserEvent(_) => {
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootWindowTarget) {
move |event, window_target| match event.map_nonuser_event() {
Ok(event) => (handler)(event, window_target),
Err(_) => {
for event in receiver.try_iter() {
app.user_event(window_target, event);
(handler)(Event::UserEvent(event), window_target);
}
},
Event::Suspended => app.suspended(window_target),
Event::Resumed => app.resumed(window_target),
Event::AboutToWait => app.about_to_wait(window_target),
Event::LoopExiting => app.exiting(window_target),
Event::MemoryWarning => app.memory_warning(window_target),
}
}
@@ -252,7 +240,7 @@ impl<T> EventLoop<T> {
});
let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(Rc::downgrade(&panic_info));
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
let (sender, receiver) = mpsc::channel();
Ok(EventLoop {
@@ -272,19 +260,22 @@ impl<T> EventLoop<T> {
&self.window_target
}
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
pub fn run<F>(mut self, handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootWindowTarget),
{
self.run_on_demand(handler)
}
// NB: we don't base this on `pump_events` because for `MacOs` we can't support
// `pump_events` elegantly (we just ask to run the loop for a "short" amount of
// time and so a layered implementation would end up using a lot of CPU due to
// redundant wake ups.
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
let handler = map_user_event(app, self.receiver.clone());
pub fn run_on_demand<F>(&mut self, handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootWindowTarget),
{
let handler = map_user_event(handler, self.receiver.clone());
self.delegate.set_event_handler(handler, || {
autoreleasepool(|_| {
@@ -319,12 +310,11 @@ impl<T> EventLoop<T> {
Ok(())
}
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
let handler = map_user_event(app, self.receiver.clone());
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, handler: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootWindowTarget),
{
let handler = map_user_event(handler, self.receiver.clone());
self.delegate.set_event_handler(handler, || {
autoreleasepool(|_| {
@@ -492,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);

View File

@@ -2,7 +2,7 @@
mod util;
mod app;
mod app_delegate;
mod app_state;
mod cursor;
mod event;
mod event_handler;

View File

@@ -1,21 +1,28 @@
//! Utilities for working with `CFRunLoop`.
//!
//! See Apple's documentation on Run Loops for details:
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
use std::cell::Cell;
use std::ffi::c_void;
use std::panic::{AssertUnwindSafe, UnwindSafe};
use std::ptr;
use std::rc::Weak;
use std::time::Instant;
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease};
use block2::Block;
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef};
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopExit,
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate,
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
};
use objc2_foundation::MainThreadMarker;
use tracing::error;
use super::app_delegate::ApplicationDelegate;
use super::app_state::ApplicationDelegate;
use super::event_loop::{stop_app_on_panic, PanicInfo};
use super::ffi;
@@ -84,10 +91,20 @@ extern "C" fn control_flow_end_handler(
}
}
#[derive(Debug)]
pub struct RunLoop(CFRunLoopRef);
impl Default for RunLoop {
fn default() -> Self {
Self(ptr::null_mut())
}
}
impl RunLoop {
pub unsafe fn get() -> Self {
pub fn main(mtm: MainThreadMarker) -> Self {
// SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so
// scheduling (and scheduling a non-`Send` block) to that thread is allowed.
let _ = mtm;
RunLoop(unsafe { CFRunLoopGetMain() })
}
@@ -114,9 +131,79 @@ impl RunLoop {
};
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
}
/// Submit a closure to run on the main thread as the next step in the run loop, before other
/// event sources are processed.
///
/// This is used for running event handlers, as those are not allowed to run re-entrantly.
///
/// # Implementation
///
/// This queuing could be implemented in the following several ways with subtle differences in
/// timing. This list is sorted in rough order in which they are run:
///
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`.
///
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
/// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop).
///
/// a. `atStart = true`.
///
/// b. `atStart = false`.
///
/// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not
/// respect the ordering that runloop events have.
///
/// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we
/// want the event to be queued in a way that preserves the order the events originally arrived
/// in.
///
/// As an example, let's assume that we receive two events from the user, a mouse click which we
/// handled by queuing it, and a window resize which we handled immediately. If we allowed
/// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of
/// the queue, and the events would appear out of order to the user of Winit. So we must instead
/// put the event at the very front of the queue, to be handled as soon as possible after
/// handling whatever event it's currently handling.
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
extern "C" {
fn CFRunLoopPerformBlock(rl: CFRunLoopRef, mode: CFTypeRef, block: &Block<dyn Fn()>);
}
// Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || {
if let Some(closure) = closure.take() {
closure()
} else {
error!("tried to execute queued closure on main thread twice");
}
});
// There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa:
// - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`.
// - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window.
// - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop.
// - `NSConnectionReplyMode`: TODO.
//
// We only want to run event handlers in the default mode, as we support running a blocking
// modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and
// resizing such panel window enters the event tracking run loop mode, so we can't directly
// trigger events inside that mode either.
//
// Any events that are queued while running a modal or when live-resizing will instead wait,
// and be delivered to the application afterwards.
//
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
let mode = unsafe { kCFRunLoopDefaultMode as CFTypeRef };
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
unsafe { CFRunLoopPerformBlock(self.0, mode, &block) }
}
}
pub fn setup_control_flow_observers(panic_info: Weak<PanicInfo>) {
pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<PanicInfo>) {
let run_loop = RunLoop::main(mtm);
unsafe {
let mut context = CFRunLoopObserverContext {
info: Weak::into_raw(panic_info) as *mut _,
@@ -125,16 +212,15 @@ pub fn setup_control_flow_observers(panic_info: Weak<PanicInfo>) {
release: None,
copyDescription: None,
};
let run_loop = RunLoop::get();
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 _,
);
@@ -165,8 +251,8 @@ impl Drop for EventLoopWaker {
}
}
impl Default for EventLoopWaker {
fn default() -> EventLoopWaker {
impl EventLoopWaker {
pub(crate) fn new() -> Self {
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe {
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
@@ -174,7 +260,7 @@ impl Default for 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,
@@ -182,23 +268,21 @@ impl Default for EventLoopWaker {
ptr::null_mut(),
);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
EventLoopWaker { timer, start_instant: Instant::now(), next_fire_date: None }
Self { timer, start_instant: Instant::now(), next_fire_date: None }
}
}
}
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) }
}
}

View File

@@ -16,7 +16,7 @@ use objc2_foundation::{
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
};
use super::app_delegate::ApplicationDelegate;
use super::app_state::ApplicationDelegate;
use super::cursor::{default_cursor, invisible_cursor};
use super::event::{
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
@@ -270,19 +270,20 @@ declare_class!(
fn set_marked_text(
&self,
string: &NSObject,
_selected_range: NSRange,
selected_range: NSRange,
_replacement_range: NSRange,
) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("setMarkedText:selectedRange:replacementRange:");
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
let (marked_text, preedit_string) = if string.is_kind_of::<NSAttributedString>() {
let (marked_text, string) = if string.is_kind_of::<NSAttributedString>() {
let string: *const NSObject = string;
let string: *const NSAttributedString = string.cast();
let string = unsafe { &*string };
(
NSMutableAttributedString::from_attributed_nsstring(string),
string.string().to_string(),
string.string(),
)
} else {
let string: *const NSObject = string;
@@ -290,7 +291,7 @@ declare_class!(
let string = unsafe { &*string };
(
NSMutableAttributedString::from_nsstring(string),
string.to_string(),
string.copy(),
)
};
@@ -310,16 +311,21 @@ declare_class!(
self.ivars().ime_state.set(ImeState::Ground);
}
// Empty string basically means that there's no preedit, so indicate that by sending
// `None` cursor range.
let cursor_range = if preedit_string.is_empty() {
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 {
Some((preedit_string.len(), preedit_string.len()))
// Convert the selected range from UTF-16 indices to UTF-8 indices.
let sub_string_a = unsafe { string.substringToIndex(selected_range.location) };
let sub_string_b = unsafe { string.substringToIndex(selected_range.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(preedit_string, cursor_range)));
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
}
#[method(unmarkText)]
@@ -379,6 +385,7 @@ declare_class!(
#[method(insertText:replacementRange:)]
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("insertText:replacementRange:");
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
@@ -679,7 +686,7 @@ declare_class!(
self.update_modifiers(event, false);
self.queue_device_event(DeviceEvent::MouseWheel { delta });
self.ivars().app_delegate.maybe_queue_device_event(DeviceEvent::MouseWheel { delta });
self.queue_event(WindowEvent::MouseWheel {
device_id: DEVICE_ID,
delta,
@@ -823,11 +830,7 @@ impl WinitView {
}
fn queue_event(&self, event: WindowEvent) {
self.ivars().app_delegate.queue_window_event(self.window().id(), event);
}
fn queue_device_event(&self, event: DeviceEvent) {
self.ivars().app_delegate.queue_device_event(event);
self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event);
}
fn scale_factor(&self) -> f64 {

View File

@@ -1,6 +1,9 @@
#![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};
use monitor::VideoModeHandle;
@@ -8,28 +11,31 @@ 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_delegate::ApplicationDelegate;
use super::app_state::ApplicationDelegate;
use super::cursor::cursor_from_icon;
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
use super::observer::RunLoop;
use super::view::WinitView;
use super::window::WinitWindow;
use super::{ffi, Fullscreen, MonitorHandle, OsError, WindowId};
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::WindowEvent;
use crate::event::{InnerSizeWriter, WindowEvent};
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
use crate::window::{
Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@@ -77,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.
@@ -184,7 +188,17 @@ declare_class!(
#[method(windowDidChangeBackingProperties:)]
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeBackingProperties:");
self.queue_static_scale_factor_changed_event();
let scale_factor = self.scale_factor();
if scale_factor == self.ivars().previous_scale_factor.get() {
return;
};
self.ivars().previous_scale_factor.set(scale_factor);
let mtm = MainThreadMarker::from(self);
let this = self.retain();
RunLoop::main(mtm).queue_closure(move || {
this.handle_scale_factor_changed(scale_factor);
});
}
#[method(windowDidBecomeKey:)]
@@ -407,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,
@@ -601,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.
@@ -654,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),
@@ -681,18 +729,23 @@ impl WindowDelegate {
let delegate: Retained<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] };
if scale_factor != 1.0 {
delegate.queue_static_scale_factor_changed_event();
let delegate = delegate.clone();
RunLoop::main(mtm).queue_closure(move || {
delegate.handle_scale_factor_changed(scale_factor);
});
}
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(),
)
};
@@ -754,24 +807,31 @@ impl WindowDelegate {
}
pub(crate) fn queue_event(&self, event: WindowEvent) {
self.ivars().app_delegate.queue_window_event(self.window().id(), event);
self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event);
}
fn queue_static_scale_factor_changed_event(&self) {
let scale_factor = self.scale_factor();
if scale_factor == self.ivars().previous_scale_factor.get() {
return;
};
fn handle_scale_factor_changed(&self, scale_factor: CGFloat) {
let app_delegate = &self.ivars().app_delegate;
let window = self.window();
self.ivars().previous_scale_factor.set(scale_factor);
let content_size = self.window().contentRectForFrameRect(self.window().frame()).size;
let content_size = window.contentRectForFrameRect(window.frame()).size;
let content_size = LogicalSize::new(content_size.width, content_size.height);
self.ivars().app_delegate.queue_static_scale_factor_changed_event(
self.window().retain(),
content_size.to_physical(scale_factor),
let suggested_size = content_size.to_physical(scale_factor);
let new_inner_size = Arc::new(Mutex::new(suggested_size));
app_delegate.handle_window_event(window.id(), WindowEvent::ScaleFactorChanged {
scale_factor,
);
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
});
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
if physical_size != suggested_size {
let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
}
app_delegate.handle_window_event(window.id(), WindowEvent::Resized(physical_size));
}
fn emit_move_event(&self) {
@@ -799,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) {
@@ -898,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);
@@ -1575,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]
@@ -1747,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
}
}

View File

@@ -12,7 +12,6 @@ use orbclient::{
};
use smol_str::SmolStr;
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::{self, Ime, Modifiers, StartCause};
use crate::event_loop::{self, ControlFlow, DeviceEvents};
@@ -323,13 +322,14 @@ impl<T: 'static> EventLoop<T> {
})
}
fn process_event<A: ApplicationHandler<T>>(
fn process_event<F>(
window_id: WindowId,
event_option: EventOption,
event_state: &mut EventState,
window_target: &event_loop::ActiveEventLoop,
app: &mut A,
) {
mut event_handler: F,
) where
F: FnMut(event::Event<T>),
{
match event_option {
EventOption::Key(KeyEvent { character, scancode, pressed }) => {
// Convert scancode
@@ -371,128 +371,125 @@ impl<T: 'static> EventLoop<T> {
key_without_modifiers = logical_key.clone();
}
let window_id = RootWindowId(window_id);
let event = event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
event: event::KeyEvent {
logical_key,
physical_key,
location: KeyLocation::Standard,
state: element_state(pressed),
repeat: false,
text,
platform_specific: KeyEventExtra {
key_without_modifiers,
text_with_all_modifiers,
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
event: event::KeyEvent {
logical_key,
physical_key,
location: KeyLocation::Standard,
state: element_state(pressed),
repeat: false,
text,
platform_specific: KeyEventExtra {
key_without_modifiers,
text_with_all_modifiers,
},
},
is_synthetic: false,
},
is_synthetic: false,
};
app.window_event(window_target, window_id, event);
});
// If the state of the modifiers has changed, send the event.
if modifiers_before != event_state.keyboard {
app.window_event(
window_target,
window_id,
event::WindowEvent::ModifiersChanged(event_state.modifiers()),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::ModifiersChanged(event_state.modifiers()),
})
}
},
EventOption::TextInput(TextInputEvent { character }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Ime(Ime::Preedit("".into(), None)),
);
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Ime(Ime::Commit(character.into())),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Ime(Ime::Preedit("".into(), None)),
});
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Ime(Ime::Commit(character.into())),
});
},
EventOption::Mouse(MouseEvent { x, y }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::CursorMoved {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CursorMoved {
device_id: event::DeviceId(DeviceId),
position: (x, y).into(),
},
);
});
},
EventOption::MouseRelative(MouseRelativeEvent { dx, dy }) => {
app.device_event(
window_target,
event::DeviceId(DeviceId),
event::DeviceEvent::MouseMotion { delta: (dx as f64, dy as f64) },
);
event_handler(event::Event::DeviceEvent {
device_id: event::DeviceId(DeviceId),
event: event::DeviceEvent::MouseMotion { delta: (dx as f64, dy as f64) },
});
},
EventOption::Button(ButtonEvent { left, middle, right }) => {
while let Some((button, state)) = event_state.mouse(left, middle, right) {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::MouseInput {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::MouseInput {
device_id: event::DeviceId(DeviceId),
state,
button,
},
);
});
}
},
EventOption::Scroll(ScrollEvent { x, y }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::MouseWheel {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::MouseWheel {
device_id: event::DeviceId(DeviceId),
delta: event::MouseScrollDelta::LineDelta(x as f32, y as f32),
phase: event::TouchPhase::Moved,
},
);
});
},
EventOption::Quit(QuitEvent {}) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::CloseRequested,
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CloseRequested,
});
},
EventOption::Focus(FocusEvent { focused }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Focused(focused),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Focused(focused),
});
},
EventOption::Move(MoveEvent { x, y }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Moved((x, y).into()),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Moved((x, y).into()),
});
},
EventOption::Resize(ResizeEvent { width, height }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Resized((width, height).into()),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Resized((width, height).into()),
});
// Acknowledge resize after event loop.
event_state.resize_opt = Some((width, height));
},
// TODO: Screen, Clipboard, Drop
EventOption::Hover(HoverEvent { entered }) => {
let event = if entered {
event::WindowEvent::CursorEntered { device_id: event::DeviceId(DeviceId) }
if entered {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CursorEntered {
device_id: event::DeviceId(DeviceId),
},
});
} else {
event::WindowEvent::CursorLeft { device_id: event::DeviceId(DeviceId) }
};
app.window_event(window_target, RootWindowId(window_id), event);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CursorLeft {
device_id: event::DeviceId(DeviceId),
},
});
}
},
other => {
tracing::warn!("unhandled event: {:?}", other);
@@ -500,13 +497,22 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
pub fn run<F>(mut self, mut event_handler_inner: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{
let mut event_handler =
move |event: event::Event<T>, window_target: &event_loop::ActiveEventLoop| {
event_handler_inner(event, window_target);
};
let mut start_cause = StartCause::Init;
loop {
app.new_events(&self.window_target, start_cause);
event_handler(event::Event::NewEvents(start_cause), &self.window_target);
if start_cause == StartCause::Init {
app.resumed(&self.window_target);
event_handler(event::Event::Resumed, &self.window_target);
}
// Handle window creates.
@@ -522,15 +528,23 @@ impl<T: 'static> EventLoop<T> {
self.windows.push((window, EventState::default()));
let window_id = RootWindowId(window_id);
// Send resize event on create to indicate first size.
let event = event::WindowEvent::Resized((properties.w, properties.h).into());
app.window_event(&self.window_target, window_id, event);
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Resized((properties.w, properties.h).into()),
},
&self.window_target,
);
// Send moved event on create to indicate first position.
let event = event::WindowEvent::Moved((properties.x, properties.y).into());
app.window_event(&self.window_target, window_id, event);
// Send resize event on create to indicate first position.
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Moved((properties.x, properties.y).into()),
},
&self.window_target,
);
}
// Handle window destroys.
@@ -538,8 +552,14 @@ impl<T: 'static> EventLoop<T> {
let mut destroys = self.window_target.p.destroys.lock().unwrap();
destroys.pop_front()
} {
let window_id = RootWindowId(destroy_id);
app.window_event(&self.window_target, window_id, event::WindowEvent::Destroyed);
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(destroy_id),
event: event::WindowEvent::Destroyed,
},
&self.window_target,
);
self.windows.retain(|(window, _event_state)| window.fd as u64 != destroy_id.fd);
}
@@ -566,8 +586,7 @@ impl<T: 'static> EventLoop<T> {
window_id,
orbital_event.to_option(),
event_state,
&self.window_target,
app,
|event| event_handler(event, &self.window_target),
);
}
@@ -595,7 +614,7 @@ impl<T: 'static> EventLoop<T> {
}
while let Ok(event) = self.user_events_receiver.try_recv() {
app.user_event(&self.window_target, event);
event_handler(event::Event::UserEvent(event), &self.window_target);
}
// To avoid deadlocks the redraws lock is not held during event processing.
@@ -603,14 +622,16 @@ impl<T: 'static> EventLoop<T> {
let mut redraws = self.window_target.p.redraws.lock().unwrap();
redraws.pop_front()
} {
app.window_event(
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::RedrawRequested,
},
&self.window_target,
RootWindowId(window_id),
event::WindowEvent::RedrawRequested,
);
}
app.about_to_wait(&self.window_target);
event_handler(event::Event::AboutToWait, &self.window_target);
if self.window_target.p.exiting() {
break;
@@ -674,7 +695,7 @@ impl<T: 'static> EventLoop<T> {
}
}
app.exiting(&self.window_target);
event_handler(event::Event::LoopExiting, &self.window_target);
Ok(())
}

View File

@@ -103,7 +103,7 @@ pub struct WindowId {
impl WindowId {
pub const fn dummy() -> Self {
WindowId { fd: u64::max_value() }
WindowId { fd: u64::MAX }
}
}

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
use std::marker::PhantomData;
use std::sync::mpsc::{self, Receiver, Sender};
use crate::application::ApplicationHandler;
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};
@@ -32,17 +32,33 @@ impl<T> EventLoop<T> {
Ok(EventLoop { elw, user_event_sender, user_event_receiver })
}
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> ! {
pub fn run<F>(self, mut event_handler: F) -> !
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let target = RootActiveEventLoop { p: self.elw.p.clone(), _marker: PhantomData };
// SAFETY: Don't use `move` to make sure we leak the `event_handler` and `target`.
let handler: Box<dyn FnMut(Event<()>)> =
Box::new(|event| handle_event(app, &target, &self.user_event_receiver, event));
let handler: Box<dyn FnMut(Event<()>)> = Box::new(|event| {
let event = match event.map_nonuser_event() {
Ok(event) => event,
Err(Event::UserEvent(())) => Event::UserEvent(
self.user_event_receiver
.try_recv()
.expect("handler woken up without user event"),
),
Err(_) => unreachable!(),
};
event_handler(event, &target)
});
// 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
@@ -54,12 +70,24 @@ impl<T> EventLoop<T> {
unreachable!();
}
pub fn spawn_app<A: ApplicationHandler<T> + 'static>(self, mut app: A) {
pub fn spawn<F>(self, mut event_handler: F)
where
F: 'static + FnMut(Event<T>, &RootActiveEventLoop),
{
let target = RootActiveEventLoop { p: self.elw.p.clone(), _marker: PhantomData };
self.elw.p.run(
Box::new(move |event| {
handle_event(&mut app, &target, &self.user_event_receiver, event)
let event = match event.map_nonuser_event() {
Ok(event) => event,
Err(Event::UserEvent(())) => Event::UserEvent(
self.user_event_receiver
.try_recv()
.expect("handler woken up without user event"),
),
Err(_) => unreachable!(),
};
event_handler(event, &target)
}),
true,
);
@@ -72,27 +100,20 @@ impl<T> EventLoop<T> {
pub fn window_target(&self) -> &RootActiveEventLoop {
&self.elw
}
}
fn handle_event<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
target: &RootActiveEventLoop,
user_event_receiver: &Receiver<T>,
event: Event<()>,
) {
match event {
Event::NewEvents(cause) => app.new_events(target, cause),
Event::WindowEvent { window_id, event } => app.window_event(target, window_id, event),
Event::DeviceEvent { device_id, event } => app.device_event(target, device_id, event),
Event::UserEvent(_) => {
let event =
user_event_receiver.try_recv().expect("user event signaled but not received");
app.user_event(target, event);
},
Event::Suspended => app.suspended(target),
Event::Resumed => app.resumed(target),
Event::AboutToWait => app.about_to_wait(target),
Event::LoopExiting => app.exiting(target),
Event::MemoryWarning => app.memory_warning(target),
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()
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

View 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)};

View File

@@ -57,7 +57,6 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
};
use crate::application::ApplicationHandler;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::EventLoopError;
use crate::event::{
@@ -216,14 +215,17 @@ impl<T: 'static> EventLoop<T> {
&self.window_target
}
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootAEL),
{
self.run_on_demand(event_handler)
}
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootAEL),
{
{
let runner = &self.window_target.p.runner_shared;
@@ -234,32 +236,21 @@ impl<T: 'static> EventLoop<T> {
// returning
unsafe {
runner.set_event_handler(move |event| {
match event {
Event::NewEvents(cause) => app.new_events(event_loop_windows_ref, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(event_loop_windows_ref, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(event_loop_windows_ref, device_id, event)
},
// The shared `EventLoopRunner` is not parameterized
// `EventLoopProxy::send_event()` calls `PostMessage`
// to wakeup and dispatch a placeholder `UserEvent`,
// when we received the placeholder event here, the
// real UserEvent(T) should already be put in the
// mpsc channel and ready to be pulled
Event::UserEvent(_) => {
let event = user_event_receiver
// the shared `EventLoopRunner` is not parameterized
// `EventLoopProxy::send_event()` calls `PostMessage`
// to wakeup and dispatch a placeholder `UserEvent`,
// when we received the placeholder event here, the
// real UserEvent(T) should already be put in the
// mpsc channel and ready to be pulled
let event = match event.map_nonuser_event() {
Ok(non_user_event) => non_user_event,
Err(_user_event_placeholder) => Event::UserEvent(
user_event_receiver
.try_recv()
.expect("user event signaled but not received");
app.user_event(event_loop_windows_ref, event);
},
Event::Suspended => app.suspended(event_loop_windows_ref),
Event::Resumed => app.resumed(event_loop_windows_ref),
Event::AboutToWait => app.about_to_wait(event_loop_windows_ref),
Event::LoopExiting => app.exiting(event_loop_windows_ref),
Event::MemoryWarning => app.memory_warning(event_loop_windows_ref),
}
.expect("user event signaled but not received"),
),
};
event_handler(event, event_loop_windows_ref)
});
}
}
@@ -293,11 +284,10 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut event_handler: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootAEL),
{
{
let runner = &self.window_target.p.runner_shared;
let event_loop_windows_ref = &self.window_target;
@@ -312,34 +302,16 @@ impl<T: 'static> EventLoop<T> {
// event handler.
unsafe {
runner.set_event_handler(move |event| {
match event {
Event::NewEvents(cause) => app.new_events(event_loop_windows_ref, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(event_loop_windows_ref, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(event_loop_windows_ref, device_id, event)
},
// The shared `EventLoopRunner` is not parameterized
// `EventLoopProxy::send_event()` calls `PostMessage`
// to wakeup and dispatch a placeholder `UserEvent`,
// when we received the placeholder event here, the
// real UserEvent(T) should already be put in the
// mpsc channel and ready to be pulled
Event::UserEvent(_) => {
let event = user_event_receiver
.try_recv()
.expect("user event signaled but not received");
app.user_event(event_loop_windows_ref, event);
},
Event::Suspended => app.suspended(event_loop_windows_ref),
Event::Resumed => app.resumed(event_loop_windows_ref),
Event::AboutToWait => app.about_to_wait(event_loop_windows_ref),
Event::LoopExiting => app.exiting(event_loop_windows_ref),
Event::MemoryWarning => app.memory_warning(event_loop_windows_ref),
}
let event = match event.map_nonuser_event() {
Ok(non_user_event) => non_user_event,
Err(_user_event_placeholder) => Event::UserEvent(
user_event_receiver
.recv()
.expect("user event signaled but not received"),
),
};
event_handler(event, event_loop_windows_ref)
});
runner.wakeup();
}
}

View File

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

View File

@@ -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`.
@@ -632,7 +631,8 @@ impl Window {
///
/// ## Platform-specific
///
/// **Wayland:** - schedules a frame callback to throttle [`WindowEvent::RedrawRequested`].
/// - **Android / iOS / X11 / Web / Windows / macOS / Orbital:** Unsupported.
/// - **Wayland:** Schedules a frame callback to throttle [`WindowEvent::RedrawRequested`].
///
/// [`WindowEvent::RedrawRequested`]: crate::event::WindowEvent::RedrawRequested
#[inline]
@@ -941,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`].
@@ -1354,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
@@ -1374,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();