mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
36 Commits
madsmtm/ve
...
v0.30.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2d4d20108 | ||
|
|
d8f4d8f1b7 | ||
|
|
a974640a66 | ||
|
|
3d7d766182 | ||
|
|
c73d8cff20 | ||
|
|
79aa95b212 | ||
|
|
ecd14688dc | ||
|
|
b512ed1e63 | ||
|
|
96388f4f6b | ||
|
|
1745b01502 | ||
|
|
54e974c090 | ||
|
|
b14d5c0c99 | ||
|
|
437747b966 | ||
|
|
21e266f3b7 | ||
|
|
3a0928af45 | ||
|
|
ad92b4f89d | ||
|
|
bf4445bb62 | ||
|
|
391a22217d | ||
|
|
fb4a674ee5 | ||
|
|
43f296b2b3 | ||
|
|
042667c5eb | ||
|
|
3206d105fe | ||
|
|
1afec3ca0d | ||
|
|
c4a8e9321d | ||
|
|
dee7a405fc | ||
|
|
a298b4d00e | ||
|
|
aebd5edc9e | ||
|
|
c801b69d3e | ||
|
|
ebd6454f8f | ||
|
|
4f2f0bc08f | ||
|
|
4b3c0655bf | ||
|
|
0812adc983 | ||
|
|
cd6ec19300 | ||
|
|
61bd8172bd | ||
|
|
c04c113e7e | ||
|
|
ce32a3024e |
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
@@ -226,3 +226,19 @@ jobs:
|
||||
command: check
|
||||
log-level: error
|
||||
arguments: --all-features --target ${{ matrix.platform.target }}
|
||||
|
||||
swc:
|
||||
name: Minimize JavaScript
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install SWC
|
||||
run: sudo npm i -g @swc/cli
|
||||
- name: Run SWC
|
||||
run: |
|
||||
swc src/platform_impl/web/web_sys/worker.js -o src/platform_impl/web/web_sys/worker.min.js
|
||||
- name: Check for diff
|
||||
run: |
|
||||
[[ -z $(git status -s) ]]
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,8 +3,5 @@ target/
|
||||
rls/
|
||||
.vscode/
|
||||
*~
|
||||
*.wasm
|
||||
*.ts
|
||||
*.js
|
||||
#*#
|
||||
.DS_Store
|
||||
|
||||
12
.swcrc
Normal file
12
.swcrc
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"minify": true,
|
||||
"jsc": {
|
||||
"target": "es2022",
|
||||
"minify": {
|
||||
"compress": {
|
||||
"unused": true
|
||||
},
|
||||
"mangle": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.30.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',
|
||||
]
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.30.0"
|
||||
winit = "0.30.3"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
|
||||
@@ -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))))]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![cfg(ios_platform)]
|
||||
#![allow(clippy::let_unit_value)]
|
||||
|
||||
mod app_delegate;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![cfg(wayland_platform)]
|
||||
|
||||
//! Winit's Wayland backend.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
@@ -434,7 +434,7 @@ impl EventProcessor {
|
||||
let flags = xev.data.get_long(1);
|
||||
let version = flags >> 24;
|
||||
self.dnd.version = Some(version);
|
||||
let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1;
|
||||
let has_more_types = flags - (flags & (c_long::MAX - 1)) == 1;
|
||||
if !has_more_types {
|
||||
let type_list = vec![
|
||||
xev.data.get_long(2) as xproto::Atom,
|
||||
|
||||
@@ -158,7 +158,9 @@ struct PreeditCallbacks {
|
||||
impl PreeditCallbacks {
|
||||
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
|
||||
let start_callback = create_xim_callback(client_data, unsafe {
|
||||
mem::transmute(preedit_start_callback as usize)
|
||||
mem::transmute::<usize, unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer)>(
|
||||
preedit_start_callback as usize,
|
||||
)
|
||||
});
|
||||
let done_callback = create_xim_callback(client_data, preedit_done_callback);
|
||||
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![cfg(x11_platform)]
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::ffi::CStr;
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
mod util;
|
||||
|
||||
mod app;
|
||||
mod app_delegate;
|
||||
mod app_state;
|
||||
mod cursor;
|
||||
mod event;
|
||||
mod event_handler;
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ pub struct WindowId {
|
||||
|
||||
impl WindowId {
|
||||
pub const fn dummy() -> Self {
|
||||
WindowId { fd: u64::max_value() }
|
||||
WindowId { fd: u64::MAX }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,12 @@ impl<T> Dispatcher<T> {
|
||||
// SAFETY: The `transmute` is necessary because `Closure` requires `'static`. This is
|
||||
// safe because this function won't return until `f` has finished executing. See
|
||||
// `Self::new()`.
|
||||
let closure = Closure(unsafe { std::mem::transmute(closure) });
|
||||
let closure = Closure(unsafe {
|
||||
std::mem::transmute::<
|
||||
Box<dyn FnOnce(&T) + Send>,
|
||||
Box<dyn FnOnce(&T) + Send + 'static>,
|
||||
>(closure)
|
||||
});
|
||||
|
||||
self.0.send(closure);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::future;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::task::Poll;
|
||||
@@ -6,13 +7,13 @@ use std::task::Poll;
|
||||
use super::super::main_thread::MainThreadMarker;
|
||||
use super::{AtomicWaker, Wrapper};
|
||||
|
||||
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, usize>);
|
||||
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, NonZeroUsize>);
|
||||
|
||||
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, usize>);
|
||||
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, NonZeroUsize>);
|
||||
|
||||
struct Handler<T> {
|
||||
value: T,
|
||||
handler: fn(&T, usize),
|
||||
handler: fn(&T, NonZeroUsize, bool),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -20,7 +21,11 @@ struct Sender(Arc<Inner>);
|
||||
|
||||
impl<T> WakerSpawner<T> {
|
||||
#[track_caller]
|
||||
pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, usize)) -> Option<Self> {
|
||||
pub fn new(
|
||||
main_thread: MainThreadMarker,
|
||||
value: T,
|
||||
handler: fn(&T, NonZeroUsize, bool),
|
||||
) -> Option<Self> {
|
||||
let inner = Arc::new(Inner {
|
||||
counter: AtomicUsize::new(0),
|
||||
waker: AtomicWaker::new(),
|
||||
@@ -37,7 +42,7 @@ impl<T> WakerSpawner<T> {
|
||||
|handler, count| {
|
||||
let handler = handler.borrow();
|
||||
let handler = handler.as_ref().unwrap();
|
||||
(handler.handler)(&handler.value, count);
|
||||
(handler.handler)(&handler.value, count, true);
|
||||
},
|
||||
{
|
||||
let inner = Arc::clone(&inner);
|
||||
@@ -46,29 +51,31 @@ impl<T> WakerSpawner<T> {
|
||||
while let Some(count) = future::poll_fn(|cx| {
|
||||
let count = inner.counter.swap(0, Ordering::Relaxed);
|
||||
|
||||
if count > 0 {
|
||||
Poll::Ready(Some(count))
|
||||
} else {
|
||||
inner.waker.register(cx.waker());
|
||||
match NonZeroUsize::new(count) {
|
||||
Some(count) => Poll::Ready(Some(count)),
|
||||
None => {
|
||||
inner.waker.register(cx.waker());
|
||||
|
||||
let count = inner.counter.swap(0, Ordering::Relaxed);
|
||||
let count = inner.counter.swap(0, Ordering::Relaxed);
|
||||
|
||||
if count > 0 {
|
||||
Poll::Ready(Some(count))
|
||||
} else {
|
||||
if inner.closed.load(Ordering::Relaxed) {
|
||||
return Poll::Ready(None);
|
||||
match NonZeroUsize::new(count) {
|
||||
Some(count) => Poll::Ready(Some(count)),
|
||||
None => {
|
||||
if inner.closed.load(Ordering::Relaxed) {
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
},
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
.await
|
||||
{
|
||||
let handler = handler.borrow();
|
||||
let handler = handler.as_ref().unwrap();
|
||||
(handler.handler)(&handler.value, count);
|
||||
(handler.handler)(&handler.value, count, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -107,7 +114,7 @@ impl<T> Drop for WakerSpawner<T> {
|
||||
|
||||
impl<T> Waker<T> {
|
||||
pub fn wake(&self) {
|
||||
self.0.send(1)
|
||||
self.0.send(NonZeroUsize::MIN)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -324,7 +324,11 @@ impl Inner {
|
||||
match &self.cursor {
|
||||
SelectedCursor::Icon(icon)
|
||||
| SelectedCursor::Loading { previous: Previous::Icon(icon), .. } => {
|
||||
self.style.set("cursor", icon.name())
|
||||
if let CursorIcon::Default = icon {
|
||||
self.style.remove("cursor")
|
||||
} else {
|
||||
self.style.set("cursor", icon.name())
|
||||
}
|
||||
},
|
||||
SelectedCursor::Loading { previous: Previous::Image(cursor), .. }
|
||||
| SelectedCursor::Image(cursor) => {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,21 @@ use crate::event::{
|
||||
WindowEvent,
|
||||
};
|
||||
use crate::event_loop::{ControlFlow, DeviceEvents};
|
||||
use crate::platform::web::PollStrategy;
|
||||
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
|
||||
use crate::platform_impl::platform::backend::EventListenerHandle;
|
||||
use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawner};
|
||||
use crate::platform_impl::platform::window::Inner;
|
||||
use crate::window::WindowId;
|
||||
|
||||
use js_sys::Function;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::iter;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::ops::Deref;
|
||||
use std::rc::{Rc, Weak};
|
||||
use wasm_bindgen::prelude::Closure;
|
||||
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent};
|
||||
use web_time::{Duration, Instant};
|
||||
|
||||
@@ -40,6 +43,7 @@ pub struct Execution {
|
||||
proxy_spawner: WakerSpawner<Weak<Self>>,
|
||||
control_flow: Cell<ControlFlow>,
|
||||
poll_strategy: Cell<PollStrategy>,
|
||||
wait_until_strategy: Cell<WaitUntilStrategy>,
|
||||
exit: Cell<bool>,
|
||||
runner: RefCell<RunnerEnum>,
|
||||
suspended: Cell<bool>,
|
||||
@@ -133,18 +137,20 @@ impl Shared {
|
||||
let document = window.document().expect("Failed to obtain document");
|
||||
|
||||
Shared(Rc::<Execution>::new_cyclic(|weak| {
|
||||
let proxy_spawner = WakerSpawner::new(main_thread, weak.clone(), |runner, count| {
|
||||
if let Some(runner) = runner.upgrade() {
|
||||
Shared(runner).send_events(iter::repeat(Event::UserEvent(())).take(count))
|
||||
}
|
||||
})
|
||||
.expect("`EventLoop` has to be created in the main thread");
|
||||
let proxy_spawner =
|
||||
WakerSpawner::new(main_thread, weak.clone(), |runner, count, local| {
|
||||
if let Some(runner) = runner.upgrade() {
|
||||
Shared(runner).send_user_events(count, local)
|
||||
}
|
||||
})
|
||||
.expect("`EventLoop` has to be created in the main thread");
|
||||
|
||||
Execution {
|
||||
main_thread,
|
||||
proxy_spawner,
|
||||
control_flow: Cell::new(ControlFlow::default()),
|
||||
poll_strategy: Cell::new(PollStrategy::default()),
|
||||
wait_until_strategy: Cell::new(WaitUntilStrategy::default()),
|
||||
exit: Cell::new(false),
|
||||
runner: RefCell::new(RunnerEnum::Pending),
|
||||
suspended: Cell::new(false),
|
||||
@@ -460,6 +466,51 @@ impl Shared {
|
||||
self.send_events(iter::once(event));
|
||||
}
|
||||
|
||||
// Add a series of user events to the event loop runner
|
||||
//
|
||||
// This will schedule the event loop to wake up instead of waking it up immediately if its not
|
||||
// running.
|
||||
pub(crate) fn send_user_events(&self, count: NonZeroUsize, local: bool) {
|
||||
// If the event loop is closed, it should discard any new events
|
||||
if self.is_closed() {
|
||||
return;
|
||||
}
|
||||
|
||||
if local {
|
||||
// If the loop is not running and triggered locally, queue on next microtick.
|
||||
if let Ok(RunnerEnum::Running(ref runner)) =
|
||||
self.0.runner.try_borrow().as_ref().map(Deref::deref)
|
||||
{
|
||||
// If we're currently polling let `send_events` do its job.
|
||||
if !matches!(runner.state, State::Poll { .. }) {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_name = queueMicrotask)]
|
||||
fn queue_microtask(task: Function);
|
||||
}
|
||||
|
||||
queue_microtask(
|
||||
Closure::once_into_js({
|
||||
let this = Rc::downgrade(&self.0);
|
||||
move || {
|
||||
if let Some(shared) = this.upgrade() {
|
||||
Shared(shared).send_events(
|
||||
iter::repeat(Event::UserEvent(())).take(count.get()),
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
.unchecked_into(),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.send_events(iter::repeat(Event::UserEvent(())).take(count.get()))
|
||||
}
|
||||
|
||||
// Add a series of events to the event loop runner
|
||||
//
|
||||
// It will determine if the event should be immediately sent to the user or buffered for later
|
||||
@@ -473,7 +524,7 @@ impl Shared {
|
||||
match self.0.runner.try_borrow().as_ref().map(Deref::deref) {
|
||||
Ok(RunnerEnum::Running(ref runner)) => {
|
||||
// If we're currently polling, queue this and wait for the poll() method to be
|
||||
// called
|
||||
// called.
|
||||
if let State::Poll { .. } = runner.state {
|
||||
process_immediately = false;
|
||||
}
|
||||
@@ -647,6 +698,7 @@ impl Shared {
|
||||
start,
|
||||
end,
|
||||
_timeout: backend::Schedule::new_with_duration(
|
||||
self.wait_until_strategy(),
|
||||
self.window(),
|
||||
move || cloned.resume_time_reached(start, end),
|
||||
delay,
|
||||
@@ -759,6 +811,14 @@ impl Shared {
|
||||
self.0.poll_strategy.get()
|
||||
}
|
||||
|
||||
pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
|
||||
self.0.wait_until_strategy.set(strategy)
|
||||
}
|
||||
|
||||
pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy {
|
||||
self.0.wait_until_strategy.get()
|
||||
}
|
||||
|
||||
pub(crate) fn waker(&self) -> Waker<Weak<Execution>> {
|
||||
self.0.proxy_spawner.waker()
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::event::{
|
||||
};
|
||||
use crate::event_loop::{ControlFlow, DeviceEvents};
|
||||
use crate::keyboard::ModifiersState;
|
||||
use crate::platform::web::{CustomCursorFuture, PollStrategy};
|
||||
use crate::platform::web::{CustomCursorFuture, PollStrategy, WaitUntilStrategy};
|
||||
use crate::platform_impl::platform::cursor::CustomCursor;
|
||||
use crate::platform_impl::platform::r#async::Waker;
|
||||
use crate::window::{
|
||||
@@ -682,6 +682,14 @@ impl ActiveEventLoop {
|
||||
self.runner.poll_strategy()
|
||||
}
|
||||
|
||||
pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
|
||||
self.runner.set_wait_until_strategy(strategy)
|
||||
}
|
||||
|
||||
pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy {
|
||||
self.runner.wait_until_strategy()
|
||||
}
|
||||
|
||||
pub(crate) fn waker(&self) -> Waker<Weak<Execution>> {
|
||||
self.runner.waker()
|
||||
}
|
||||
|
||||
@@ -427,8 +427,8 @@ impl Canvas {
|
||||
|
||||
pub(crate) fn on_resize_scale<S, R>(&mut self, scale_handler: S, size_handler: R)
|
||||
where
|
||||
S: 'static + FnMut(PhysicalSize<u32>, f64),
|
||||
R: 'static + FnMut(PhysicalSize<u32>),
|
||||
S: 'static + Fn(PhysicalSize<u32>, f64),
|
||||
R: 'static + Fn(PhysicalSize<u32>),
|
||||
{
|
||||
self.on_resize_scale = Some(ResizeScaleHandle::new(
|
||||
self.window().clone(),
|
||||
|
||||
@@ -16,7 +16,7 @@ use super::media_query_handle::MediaQueryListHandle;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct ResizeScaleHandle(Rc<RefCell<ResizeScaleInternal>>);
|
||||
pub struct ResizeScaleHandle(Rc<ResizeScaleInternal>);
|
||||
|
||||
impl ResizeScaleHandle {
|
||||
pub(crate) fn new<S, R>(
|
||||
@@ -28,8 +28,8 @@ impl ResizeScaleHandle {
|
||||
resize_handler: R,
|
||||
) -> Self
|
||||
where
|
||||
S: 'static + FnMut(PhysicalSize<u32>, f64),
|
||||
R: 'static + FnMut(PhysicalSize<u32>),
|
||||
S: 'static + Fn(PhysicalSize<u32>, f64),
|
||||
R: 'static + Fn(PhysicalSize<u32>),
|
||||
{
|
||||
Self(ResizeScaleInternal::new(
|
||||
window,
|
||||
@@ -42,7 +42,7 @@ impl ResizeScaleHandle {
|
||||
}
|
||||
|
||||
pub(crate) fn notify_resize(&self) {
|
||||
self.0.borrow_mut().notify()
|
||||
self.0.notify()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,11 +53,11 @@ struct ResizeScaleInternal {
|
||||
document: Document,
|
||||
canvas: HtmlCanvasElement,
|
||||
style: Style,
|
||||
mql: MediaQueryListHandle,
|
||||
mql: RefCell<MediaQueryListHandle>,
|
||||
observer: ResizeObserver,
|
||||
_observer_closure: Closure<dyn FnMut(Array, ResizeObserver)>,
|
||||
scale_handler: Box<dyn FnMut(PhysicalSize<u32>, f64)>,
|
||||
resize_handler: Box<dyn FnMut(PhysicalSize<u32>)>,
|
||||
scale_handler: Box<dyn Fn(PhysicalSize<u32>, f64)>,
|
||||
resize_handler: Box<dyn Fn(PhysicalSize<u32>)>,
|
||||
notify_scale: Cell<bool>,
|
||||
}
|
||||
|
||||
@@ -69,12 +69,12 @@ impl ResizeScaleInternal {
|
||||
style: Style,
|
||||
scale_handler: S,
|
||||
resize_handler: R,
|
||||
) -> Rc<RefCell<Self>>
|
||||
) -> Rc<Self>
|
||||
where
|
||||
S: 'static + FnMut(PhysicalSize<u32>, f64),
|
||||
R: 'static + FnMut(PhysicalSize<u32>),
|
||||
S: 'static + Fn(PhysicalSize<u32>, f64),
|
||||
R: 'static + Fn(PhysicalSize<u32>),
|
||||
{
|
||||
Rc::<RefCell<ResizeScaleInternal>>::new_cyclic(|weak_self| {
|
||||
Rc::<ResizeScaleInternal>::new_cyclic(|weak_self| {
|
||||
let mql = Self::create_mql(&window, {
|
||||
let weak_self = weak_self.clone();
|
||||
move |mql| {
|
||||
@@ -86,9 +86,7 @@ impl ResizeScaleInternal {
|
||||
|
||||
let weak_self = weak_self.clone();
|
||||
let observer_closure = Closure::new(move |entries: Array, _| {
|
||||
if let Some(rc_self) = weak_self.upgrade() {
|
||||
let mut this = rc_self.borrow_mut();
|
||||
|
||||
if let Some(this) = weak_self.upgrade() {
|
||||
let size = this.process_entry(entries);
|
||||
|
||||
if this.notify_scale.replace(false) {
|
||||
@@ -101,18 +99,18 @@ impl ResizeScaleInternal {
|
||||
});
|
||||
let observer = Self::create_observer(&canvas, observer_closure.as_ref());
|
||||
|
||||
RefCell::new(Self {
|
||||
Self {
|
||||
window,
|
||||
document,
|
||||
canvas,
|
||||
style,
|
||||
mql,
|
||||
mql: RefCell::new(mql),
|
||||
observer,
|
||||
_observer_closure: observer_closure,
|
||||
scale_handler: Box::new(scale_handler),
|
||||
resize_handler: Box::new(resize_handler),
|
||||
notify_scale: Cell::new(false),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -152,7 +150,7 @@ impl ResizeScaleInternal {
|
||||
observer
|
||||
}
|
||||
|
||||
fn notify(&mut self) {
|
||||
fn notify(&self) {
|
||||
if !self.document.contains(Some(&self.canvas)) || self.style.get("display") == "none" {
|
||||
let size = PhysicalSize::new(0, 0);
|
||||
|
||||
@@ -200,10 +198,9 @@ impl ResizeScaleInternal {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_scale(this: Rc<RefCell<Self>>, mql: &MediaQueryList) {
|
||||
let weak_self = Rc::downgrade(&this);
|
||||
let mut this = this.borrow_mut();
|
||||
let scale = super::scale_factor(&this.window);
|
||||
fn handle_scale(self: Rc<Self>, mql: &MediaQueryList) {
|
||||
let weak_self = Rc::downgrade(&self);
|
||||
let scale = super::scale_factor(&self.window);
|
||||
|
||||
// TODO: confirm/reproduce this problem, see:
|
||||
// <https://github.com/rust-windowing/winit/issues/2597>.
|
||||
@@ -217,15 +214,15 @@ impl ResizeScaleInternal {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_mql = Self::create_mql(&this.window, move |mql| {
|
||||
let new_mql = Self::create_mql(&self.window, move |mql| {
|
||||
if let Some(rc_self) = weak_self.upgrade() {
|
||||
Self::handle_scale(rc_self, mql);
|
||||
}
|
||||
});
|
||||
this.mql = new_mql;
|
||||
self.mql.replace(new_mql);
|
||||
|
||||
this.notify_scale.set(true);
|
||||
this.notify();
|
||||
self.notify_scale.set(true);
|
||||
self.notify();
|
||||
}
|
||||
|
||||
fn process_entry(&self, entries: Array) -> PhysicalSize<u32> {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use js_sys::{Function, Object, Promise, Reflect};
|
||||
use js_sys::{Array, Function, Object, Promise, Reflect};
|
||||
use std::cell::OnceCell;
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{AbortController, AbortSignal, MessageChannel, MessagePort};
|
||||
use web_sys::{
|
||||
AbortController, AbortSignal, Blob, BlobPropertyBag, MessageChannel, MessagePort, Url, Worker,
|
||||
};
|
||||
|
||||
use crate::platform::web::PollStrategy;
|
||||
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Schedule {
|
||||
@@ -29,6 +31,7 @@ enum Inner {
|
||||
port: MessagePort,
|
||||
_timeout_closure: Closure<dyn FnMut()>,
|
||||
},
|
||||
Worker(MessagePort),
|
||||
}
|
||||
|
||||
impl Schedule {
|
||||
@@ -45,14 +48,24 @@ impl Schedule {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_duration<F>(window: &web_sys::Window, f: F, duration: Duration) -> Schedule
|
||||
pub fn new_with_duration<F>(
|
||||
strategy: WaitUntilStrategy,
|
||||
window: &web_sys::Window,
|
||||
f: F,
|
||||
duration: Duration,
|
||||
) -> Schedule
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
if has_scheduler_support(window) {
|
||||
Self::new_scheduler(window, f, Some(duration))
|
||||
} else {
|
||||
Self::new_timeout(window.clone(), f, Some(duration))
|
||||
match strategy {
|
||||
WaitUntilStrategy::Scheduler => {
|
||||
if has_scheduler_support(window) {
|
||||
Self::new_scheduler(window, f, Some(duration))
|
||||
} else {
|
||||
Self::new_timeout(window.clone(), f, Some(duration))
|
||||
}
|
||||
},
|
||||
WaitUntilStrategy::Worker => Self::new_worker(f, duration),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +166,44 @@ impl Schedule {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn new_worker<F>(f: F, duration: Duration) -> Schedule
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
thread_local! {
|
||||
static URL: ScriptUrl = ScriptUrl::new(include_str!("worker.min.js"));
|
||||
static WORKER: Worker = URL.with(|url| Worker::new(&url.0)).expect("`new Worker()` is not expected to fail with a local script");
|
||||
}
|
||||
|
||||
let channel = MessageChannel::new().unwrap();
|
||||
let closure = Closure::new(f);
|
||||
let port_1 = channel.port1();
|
||||
port_1.set_onmessage(Some(closure.as_ref().unchecked_ref()));
|
||||
port_1.start();
|
||||
|
||||
// `Duration::as_millis()` always rounds down (because of truncation), we want to round
|
||||
// up instead. This makes sure that the we never wake up **before** the given time.
|
||||
let duration = duration
|
||||
.as_secs()
|
||||
.try_into()
|
||||
.ok()
|
||||
.and_then(|secs: u32| secs.checked_mul(1000))
|
||||
.and_then(|secs| secs.checked_add(duration_millis_ceil(duration)))
|
||||
.unwrap_or(u32::MAX);
|
||||
|
||||
WORKER
|
||||
.with(|worker| {
|
||||
let port_2 = channel.port2();
|
||||
worker.post_message_with_transfer(
|
||||
&Array::of2(&port_2, &duration.into()),
|
||||
&Array::of1(&port_2).into(),
|
||||
)
|
||||
})
|
||||
.expect("`Worker.postMessage()` is not expected to fail");
|
||||
|
||||
Schedule { _closure: closure, inner: Inner::Worker(port_1) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Schedule {
|
||||
@@ -165,6 +216,10 @@ impl Drop for Schedule {
|
||||
port.close();
|
||||
port.set_onmessage(None);
|
||||
},
|
||||
Inner::Worker(port) => {
|
||||
port.close();
|
||||
port.set_onmessage(None);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,6 +281,29 @@ fn has_idle_callback_support(window: &web_sys::Window) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
struct ScriptUrl(String);
|
||||
|
||||
impl ScriptUrl {
|
||||
fn new(script: &str) -> Self {
|
||||
let sequence = Array::of1(&script.into());
|
||||
let mut property = BlobPropertyBag::new();
|
||||
property.type_("text/javascript");
|
||||
let blob = Blob::new_with_str_sequence_and_options(&sequence, &property)
|
||||
.expect("`new Blob()` should never throw");
|
||||
|
||||
let url = Url::create_object_url_with_blob(&blob)
|
||||
.expect("`URL.createObjectURL()` should never throw");
|
||||
|
||||
Self(url)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ScriptUrl {
|
||||
fn drop(&mut self) {
|
||||
Url::revoke_object_url(&self.0).expect("`URL.revokeObjectURL()` should never throw");
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type WindowSupportExt;
|
||||
|
||||
10
src/platform_impl/web/web_sys/worker.js
Normal file
10
src/platform_impl/web/web_sys/worker.js
Normal file
@@ -0,0 +1,10 @@
|
||||
onmessage = event => {
|
||||
const [port, timeout] = event.data
|
||||
const f = () => port.postMessage(undefined)
|
||||
|
||||
if ('scheduler' in this) {
|
||||
scheduler.postTask(f, { delay: timeout })
|
||||
} else {
|
||||
setTimeout(f, timeout)
|
||||
}
|
||||
}
|
||||
1
src/platform_impl/web/web_sys/worker.min.js
vendored
Normal file
1
src/platform_impl/web/web_sys/worker.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
onmessage=e=>{let[s,t]=e.data,a=()=>s.postMessage(void 0);"scheduler"in this?scheduler.postTask(a,{delay:t}):setTimeout(a,t)};
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![cfg(windows_platform)]
|
||||
|
||||
use smol_str::SmolStr;
|
||||
use windows_sys::Win32::Foundation::{HANDLE, HWND};
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX};
|
||||
|
||||
@@ -393,7 +393,6 @@ impl WindowAttributes {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** This is an app-wide setting.
|
||||
/// - **Wayland:** This controls only CSD. When using `None` it'll try to use dbus to get the
|
||||
/// system preference. When explicit theme is used, this will avoid dbus all together.
|
||||
/// - **x11:** Build window with `_GTK_THEME_VARIANT` hint set to `dark` or `light`.
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user