Compare commits

..

11 Commits

Author SHA1 Message Date
John Nunley
19477d3f03 doc: Add installation instructions to docs
This commit adds installation instructions to the top-level
documentation for lib.rs. The main goal is to give the dependencies that
X11/Wayland use in a formal place, since it's not written down anywhere
to my knowledge.

Signed-off-by: John Nunley <dev@notgull.net>
2024-12-08 08:30:31 -08:00
Mads Marquart
e47081e385 docs: show less of the unstable docs in the README 2024-12-06 13:16:30 +03:00
Benjamin Brienen
171d53c042 Update smol_str (#3991) 2024-12-03 23:07:19 +01:00
Mads Marquart
35379f305a Remove feature description in FEATURES.md (#3479)
It easily becomes out of date.
2024-12-03 21:24:57 +01:00
Mads Marquart
4d2a0dd2b3 iOS: Never queue application-level events (#3905)
Events like `resumed`, `new_events`, `about_to_wait`, and so on will
never happen as a result of programmer action, so we'll never need to
queue those. This allows us to remove the need for the old `Event`
struct in the iOS backend.

Furthermore, we can now remove `InUserCallback`, since that state is
already stored inside `EventHandler`.

I've tried to otherwise keep the semantics as close to the original by
calling `handle_nonuser_events(mtm, [])`, which flushes pending events.
2024-12-03 19:53:29 +01:00
Mads Marquart
4a8b659228 Fix MonitorHandle PartialEq and Hash on iOS (#4013) 2024-12-03 19:02:53 +01:00
Mads Marquart
f314cd2b9a macOS: Fix crash when pressing Caps Lock (#4024)
Events emitted by `flagsChanged:` cannot access
`charactersIgnoringModifiers`. We were previously doing this because we
were trying to re-use the `create_key_event` function, but that is unsuited
for this purpose, so I have separated the `flagsChanged:` logic out from it.
2024-12-03 18:48:23 +01:00
Mads Marquart
3657506f6e macOS: Avoid redundant initial resize event (#3913)
The `NSViewFrameDidChangeNotification` that we listen to is emitted when
`-[NSWindow setContentView]` is called, since that sets the frame of the
view as well.

So now we register the notification later, so that it's not triggered at
window creation.

This behaviour is well described in the documentation:
https://developer.apple.com/documentation/appkit/nsview/postsframechangednotifications?language=objc
2024-12-03 18:35:04 +01:00
Mads Marquart
edca3ebc41 macOS: Align scancode conversions with Chromium and Firefox (#4019)
Also fix missing codes in physicalkey_to_scancode - This had become out of
sync with scancode_to_physicalkey.
2024-12-03 18:17:57 +01:00
Mads Marquart
ca46e29203 macOS: Fix surface position (#4027) 2024-12-03 17:31:32 +01:00
Mads Marquart
132fbe14d5 macOS: Fix safe area on macOS 10.14 and below (#4028) 2024-12-03 17:19:45 +01:00
40 changed files with 780 additions and 1031 deletions

View File

@@ -2,4 +2,3 @@
- [ ] Added an entry to the `changelog` module if knowledge of this change could be valuable to users - [ ] Added an entry to the `changelog` module if knowledge of this change could be valuable to users
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior - [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
- [ ] Created or updated an example program if it would help users understand this functionality - [ ] Created or updated an example program if it would help users understand this functionality
- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented

View File

@@ -81,7 +81,7 @@ cursor-icon = "1.1.0"
dpi = { version = "0.1.1", path = "dpi" } dpi = { version = "0.1.1", path = "dpi" }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] } rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] }
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }
smol_str = "0.2.0" smol_str = "0.3"
tracing = { version = "0.1.40", default-features = false } tracing = { version = "0.1.40", default-features = false }
[dev-dependencies] [dev-dependencies]

View File

@@ -47,201 +47,3 @@ through the implementation work necessary to function on all platforms. When one
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature. gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
If that gets accepted, the platform-specific functions get deprecated and become permanently If that gets accepted, the platform-specific functions get deprecated and become permanently
exposed through the core, cross-platform API. exposed through the core, cross-platform API.
# Features
## Extending this section
If your PR makes notable changes to Winit's features, please update this section as follows:
- If your PR adds a new feature, add a brief description to the relevant section. If the feature is a core
feature, add a row to the feature matrix and describe what platforms the feature has been implemented on.
- If your PR begins a new API rework, add a row to the `Pending API Reworks` table. If the PR implements the
API rework on all relevant platforms, please move it to the `Completed API Reworks` table.
- If your PR implements an already-existing feature on a new platform, either mark the feature as *completed*,
or mark it as *mostly completed* and link to an issue describing the problems with the implementation.
## Core
### Windowing
- **Window initialization**: Winit allows the creation of a window
- **Providing pointer to init OpenGL**: Winit provides the necessary pointers to initialize a working opengl context
- **Providing pointer to init Vulkan**: Same as OpenGL but for Vulkan
- **Window decorations**: The windows created by winit are properly decorated, and the decorations can
be deactivated
- **Window decorations toggle**: Decorations can be turned on or off after window creation
- **Window resizing**: The windows created by winit can be resized and generate the appropriate events
when they are. The application can precisely control its window size if desired.
- **Window resize increments**: When the window gets resized, the application can choose to snap the window's
size to specific values.
- **Window transparency**: Winit allows the creation of windows with a transparent background.
- **Window maximization**: The windows created by winit can be maximized upon creation.
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
creation.
- **Window minimization**: The windows created by winit can be minimized after creation.
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows and, if applicable, captures the monitor for exclusive
use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
get drawn above their owner.
### System Information
- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary.
- **Video mode query**: Monitors can be queried for their supported fullscreen video modes (consisting of resolution, refresh rate, and bit depth).
### Input Handling
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
- **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
- **Cursor icon**: Changing the cursor icon or hiding the cursor.
- **Cursor image**: Changing the cursor to your own image.
- **Cursor hittest**: Handle or ignore mouse events for a window.
- **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied.
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
## Platform
### Windows
* Setting the name of the internal window class
* Setting the taskbar icon
* Setting the parent window
* Setting a menu bar
* `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
* Changing a system-drawn backdrop
* Setting the window border color
* Setting the title bar background color
* Setting the title color
* Setting the corner rounding preference
### macOS
* Window activation policy
* Window movable by background
* Transparent titlebar
* Hidden titlebar
* Hidden titlebar buttons
* Full-size content view
* Accepts first mouse
* Set a preferred theme and get current theme.
### Unix
* Window urgency
* X11 Window Class
* X11 Override Redirect Flag
* GTK Theme Variant
* Base window size
* Setting the X11 parent window
### iOS
* Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor
* Valid orientations
* Home indicator visibility
* Status bar visibility and style
* Deferring system gestures
* Getting the preferred video mode
### Web
* Get if the systems preferred color scheme is "dark"
## Compatibility Matrix
Legend:
- ✔️: Works as intended
- ▢: Mostly works, but some bugs are known
- ❌: Missing feature or large bugs making it unusable
- **N/A**: Not applicable for this platform
- ❓: Unknown status
### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** |
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Window resize increments |✔️ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|**N/A** |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |▢[#720] |**N/A**|**N/A**|❓ |**N/A** |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|Resize with cursor |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
[#165]: https://github.com/rust-windowing/winit/issues/165
[#219]: https://github.com/rust-windowing/winit/issues/219
[#242]: https://github.com/rust-windowing/winit/issues/242
[#306]: https://github.com/rust-windowing/winit/issues/306
[#315]: https://github.com/rust-windowing/winit/issues/315
[#319]: https://github.com/rust-windowing/winit/issues/319
[#33]: https://github.com/rust-windowing/winit/issues/33
[#459]: https://github.com/rust-windowing/winit/issues/459
[#5]: https://github.com/rust-windowing/winit/issues/5
[#63]: https://github.com/rust-windowing/winit/issues/63
[#720]: https://github.com/rust-windowing/winit/issues/720
[#721]: https://github.com/rust-windowing/winit/issues/721
[#750]: https://github.com/rust-windowing/winit/issues/750
[#753]: https://github.com/rust-windowing/winit/issues/753
[#804]: https://github.com/rust-windowing/winit/issues/804

View File

@@ -2,7 +2,7 @@
[![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit) [![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit)
[![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit) [![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit)
[![Master Docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=master%20docs [![UNSTABLE docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=UNSTABLE%20docs
)](https://rust-windowing.github.io/winit/winit/index.html) )](https://rust-windowing.github.io/winit/winit/index.html)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions) [![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
@@ -66,4 +66,4 @@ same MSRV policy.
### Platform-specific usage ### Platform-specific usage
Check out the [`winit::platform`](https://rust-windowing.github.io/winit/winit/platform/index.html) module for platform-specific usage. Check out the [`winit::platform`](https://docs.rs/winit/latest/winit/platform/index.html) module for platform-specific usage.

View File

@@ -13,12 +13,26 @@ fn main() -> Result<(), impl std::error::Error> {
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[derive(Default)]
struct Application { struct Application {
parent_window_id: WindowId, parent_window_id: Option<WindowId>,
windows: HashMap<WindowId, Box<dyn Window>>, windows: HashMap<WindowId, Box<dyn Window>>,
} }
impl ApplicationHandler for Application { impl ApplicationHandler for Application {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let attributes = WindowAttributes::default()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
println!("Parent window id: {:?})", window.id());
self.parent_window_id = Some(window.id());
self.windows.insert(window.id(), window);
}
fn window_event( fn window_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
@@ -42,7 +56,7 @@ fn main() -> Result<(), impl std::error::Error> {
event: KeyEvent { state: ElementState::Pressed, .. }, event: KeyEvent { state: ElementState::Pressed, .. },
.. ..
} => { } => {
let parent_window = self.windows.get(&self.parent_window_id).unwrap(); let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window(parent_window.as_ref(), event_loop); let child_window = spawn_child_window(parent_window.as_ref(), event_loop);
let child_id = child_window.id(); let child_id = child_window.id();
println!("Child window created with id: {child_id:?}"); println!("Child window created with id: {child_id:?}");
@@ -58,20 +72,6 @@ fn main() -> Result<(), impl std::error::Error> {
} }
} }
fn init(event_loop: &dyn ActiveEventLoop) -> Application {
let attributes = WindowAttributes::default()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
println!("Parent window id: {:?})", window.id());
let parent_window_id = window.id();
let windows = HashMap::from([(window.id(), window)]);
Application { parent_window_id, windows }
}
fn spawn_child_window( fn spawn_child_window(
parent: &dyn Window, parent: &dyn Window,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
@@ -89,7 +89,7 @@ fn main() -> Result<(), impl std::error::Error> {
} }
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
event_loop.run(init) event_loop.run_app(Application::default())
} }
#[cfg(not(any(x11_platform, macos_platform, windows_platform)))] #[cfg(not(any(x11_platform, macos_platform, windows_platform)))]

View File

@@ -43,31 +43,16 @@ fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
event_loop.run(ControlFlowDemo::new) event_loop.run_app(ControlFlowDemo::default())
} }
#[derive(Default)]
struct ControlFlowDemo { struct ControlFlowDemo {
mode: Mode, mode: Mode,
request_redraw: bool, request_redraw: bool,
wait_cancelled: bool, wait_cancelled: bool,
close_requested: bool, close_requested: bool,
window: Box<dyn Window>, window: Option<Box<dyn Window>>,
}
impl ControlFlowDemo {
fn new(event_loop: &dyn ActiveEventLoop) -> Self {
let window_attributes = WindowAttributes::default().with_title(
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.",
);
let window = event_loop.create_window(window_attributes).unwrap();
Self {
mode: Mode::default(),
request_redraw: false,
wait_cancelled: false,
close_requested: false,
window,
}
}
} }
impl ApplicationHandler for ControlFlowDemo { impl ApplicationHandler for ControlFlowDemo {
@@ -80,6 +65,13 @@ impl ApplicationHandler for ControlFlowDemo {
} }
} }
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = WindowAttributes::default().with_title(
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.",
);
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
fn window_event( fn window_event(
&mut self, &mut self,
_event_loop: &dyn ActiveEventLoop, _event_loop: &dyn ActiveEventLoop,
@@ -120,8 +112,9 @@ impl ApplicationHandler for ControlFlowDemo {
_ => (), _ => (),
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
self.window.pre_present_notify(); let window = self.window.as_ref().unwrap();
fill::fill_window(&*self.window); window.pre_present_notify();
fill::fill_window(window.as_ref());
}, },
_ => (), _ => (),
} }
@@ -129,7 +122,7 @@ impl ApplicationHandler for ControlFlowDemo {
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
if self.request_redraw && !self.wait_cancelled && !self.close_requested { if self.request_redraw && !self.wait_cancelled && !self.close_requested {
self.window.request_redraw(); self.window.as_ref().unwrap().request_redraw();
} }
match self.mode { match self.mode {

View File

@@ -8,7 +8,7 @@ fn main() -> std::process::ExitCode {
use std::time::Duration; use std::time::Duration;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::{StartCause, WindowEvent}; use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}; use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
@@ -22,12 +22,9 @@ fn main() -> std::process::ExitCode {
} }
impl ApplicationHandler for PumpDemo { impl ApplicationHandler for PumpDemo {
fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) { fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
if matches!(cause, StartCause::Init) && self.window.is_none() { let window_attributes = WindowAttributes::default().with_title("A fantastic window!");
let window_attributes = self.window = Some(event_loop.create_window(window_attributes).unwrap());
WindowAttributes::default().with_title("A fantastic window!");
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
} }
fn window_event( fn window_event(

View File

@@ -21,8 +21,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
window: Option<Box<dyn Window>>, window: Option<Box<dyn Window>>,
} }
impl App { impl ApplicationHandler for App {
fn init_window(&mut self, event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
if let Some(window) = self.window.as_ref() {
window.request_redraw();
}
}
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = WindowAttributes::default() let window_attributes = WindowAttributes::default()
.with_title("Fantastic window number one!") .with_title("Fantastic window number one!")
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0)); .with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0));
@@ -30,14 +36,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
self.window_id = Some(window.id()); self.window_id = Some(window.id());
self.window = Some(window); self.window = Some(window);
} }
}
impl ApplicationHandler for App {
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
if let Some(window) = self.window.as_ref() {
window.request_redraw();
}
}
fn window_event( fn window_event(
&mut self, &mut self,
@@ -83,20 +81,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut event_loop = EventLoop::new().unwrap(); let mut event_loop = EventLoop::new().unwrap();
let mut app = App { idx: 1, ..Default::default() }; let mut app = App { idx: 1, ..Default::default() };
event_loop.run_on_demand(|event_loop| { event_loop.run_app_on_demand(&mut app)?;
app.init_window(event_loop);
&mut app
})?;
println!("--------------------------------------------------------- Finished first loop"); println!("--------------------------------------------------------- Finished first loop");
println!("--------------------------------------------------------- Waiting 5 seconds"); println!("--------------------------------------------------------- Waiting 5 seconds");
std::thread::sleep(Duration::from_secs(5)); std::thread::sleep(Duration::from_secs(5));
app.idx += 1; app.idx += 1;
event_loop.run_on_demand(|event_loop| { event_loop.run_app_on_demand(&mut app)?;
app.init_window(event_loop);
&mut app
})?;
println!("--------------------------------------------------------- Finished second loop"); println!("--------------------------------------------------------- Finished second loop");
Ok(()) Ok(())
} }

View File

@@ -70,7 +70,8 @@ fn main() -> Result<(), Box<dyn Error>> {
}); });
} }
Ok(event_loop.run(|event_loop| Application::new(event_loop, receiver, sender))?) let app = Application::new(&event_loop, receiver, sender);
Ok(event_loop.run_app(app)?)
} }
/// Application state and event handling. /// Application state and event handling.
@@ -87,24 +88,21 @@ struct Application {
/// ///
/// With OpenGL it could be EGLDisplay. /// With OpenGL it could be EGLDisplay.
#[cfg(not(android_platform))] #[cfg(not(android_platform))]
context: Context<DisplayHandle<'static>>, context: Option<Context<DisplayHandle<'static>>>,
} }
impl Application { impl Application {
fn new( fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self {
event_loop: &dyn ActiveEventLoop, // SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
receiver: Receiver<Action>,
sender: Sender<Action>,
) -> Self {
// SAFETY: The context is stored in the application, which is dropped right before the event
// loop is stopped, thus making it safe.
#[cfg(not(android_platform))] #[cfg(not(android_platform))]
let context = Context::new(unsafe { let context = Some(
std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>( Context::new(unsafe {
event_loop.display_handle().unwrap(), std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
) event_loop.display_handle().unwrap(),
}) )
.unwrap(); })
.unwrap(),
);
// You'll have to choose an icon size at your own discretion. On X11, the desired size // You'll have to choose an icon size at your own discretion. On X11, the desired size
// varies by WM, and on Windows, you still have to account for screen scaling. Here // varies by WM, and on Windows, you still have to account for screen scaling. Here
@@ -122,7 +120,7 @@ impl Application {
.into_iter() .into_iter()
.collect(); .collect();
let mut app = Self { Self {
receiver, receiver,
sender, sender,
#[cfg(not(android_platform))] #[cfg(not(android_platform))]
@@ -130,16 +128,7 @@ impl Application {
custom_cursors, custom_cursors,
icon, icon,
windows: Default::default(), windows: Default::default(),
}; }
app.dump_monitors(event_loop);
// Create initial window.
app.create_window(event_loop, None).expect("failed to create initial window");
app.print_help();
app
} }
fn create_window( fn create_window(
@@ -571,12 +560,14 @@ impl ApplicationHandler for Application {
info!("Device {device_id:?} event: {event:?}"); info!("Device {device_id:?} event: {event:?}");
} }
#[cfg(not(android_platform))] fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
fn can_create_surfaces(&mut self, _event_loop: &dyn ActiveEventLoop) { info!("Ready to create surfaces");
for window in self.windows.values_mut() { self.dump_monitors(event_loop);
window.surface = Some(Surface::new(&self.context, Arc::clone(&window.window)).unwrap());
window.resize(window.window.surface_size()); // Create initial window.
} self.create_window(event_loop, None).expect("failed to create initial window");
self.print_help();
} }
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
@@ -587,10 +578,9 @@ impl ApplicationHandler for Application {
} }
#[cfg(not(android_platform))] #[cfg(not(android_platform))]
fn destroy_surfaces(&mut self, _event_loop: &dyn ActiveEventLoop) { fn exiting(&mut self, _event_loop: &dyn ActiveEventLoop) {
for window in self.windows.values_mut() { // We must drop the context here.
window.surface = None; self.context = None;
}
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@@ -617,9 +607,9 @@ struct WindowState {
ime: bool, ime: bool,
/// Render surface. /// Render surface.
/// ///
/// `None` when not between `can_create_surfaces` and `destroy_surfaces`. /// NOTE: This surface must be dropped before the `Window`.
#[cfg(not(android_platform))] #[cfg(not(android_platform))]
surface: Option<Surface<DisplayHandle<'static>, Arc<dyn Window>>>, surface: Surface<DisplayHandle<'static>, Arc<dyn Window>>,
/// The actual winit Window. /// The actual winit Window.
window: Arc<dyn Window>, window: Arc<dyn Window>,
/// The window theme we're drawing with. /// The window theme we're drawing with.
@@ -652,6 +642,11 @@ impl WindowState {
fn new(app: &Application, window: Box<dyn Window>) -> Result<Self, Box<dyn Error>> { fn new(app: &Application, window: Box<dyn Window>) -> Result<Self, Box<dyn Error>> {
let window: Arc<dyn Window> = Arc::from(window); let window: Arc<dyn Window> = Arc::from(window);
// SAFETY: the surface is dropped before the `window` which provided it with handle, thus
// it doesn't outlive it.
#[cfg(not(android_platform))]
let surface = Surface::new(app.context.as_ref().unwrap(), Arc::clone(&window))?;
let theme = window.theme().unwrap_or(Theme::Dark); let theme = window.theme().unwrap_or(Theme::Dark);
info!("Theme: {theme:?}"); info!("Theme: {theme:?}");
let named_idx = 0; let named_idx = 0;
@@ -661,14 +656,15 @@ impl WindowState {
let ime = true; let ime = true;
window.set_ime_allowed(ime); window.set_ime_allowed(ime);
Ok(Self { let size = window.surface_size();
let mut state = Self {
#[cfg(macos_platform)] #[cfg(macos_platform)]
option_as_alt: window.option_as_alt(), option_as_alt: window.option_as_alt(),
custom_idx: app.custom_cursors.as_ref().map(Vec::len).unwrap_or(1) - 1, custom_idx: app.custom_cursors.as_ref().map(Vec::len).unwrap_or(1) - 1,
cursor_grab: CursorGrabMode::None, cursor_grab: CursorGrabMode::None,
named_idx, named_idx,
#[cfg(not(android_platform))] #[cfg(not(android_platform))]
surface: None, surface,
window, window,
theme, theme,
ime, ime,
@@ -679,7 +675,10 @@ impl WindowState {
rotated: Default::default(), rotated: Default::default(),
panned: Default::default(), panned: Default::default(),
zoom: Default::default(), zoom: Default::default(),
}) };
state.resize(size);
Ok(state)
} }
pub fn toggle_ime(&mut self) { pub fn toggle_ime(&mut self) {
@@ -853,11 +852,7 @@ impl WindowState {
(Some(width), Some(height)) => (width, height), (Some(width), Some(height)) => (width, height),
_ => return, _ => return,
}; };
self.surface self.surface.resize(width, height).expect("failed to resize inner buffer");
.as_mut()
.unwrap()
.resize(width, height)
.expect("failed to resize inner buffer");
} }
self.window.request_redraw(); self.window.request_redraw();
} }
@@ -950,7 +945,7 @@ impl WindowState {
return Ok(()); return Ok(());
} }
let mut buffer = self.surface.as_mut().unwrap().buffer_mut()?; let mut buffer = self.surface.buffer_mut()?;
// Draw a different color inside the safe area // Draw a different color inside the safe area
let surface_size = self.window.surface_size(); let surface_size = self.window.surface_size();

View File

@@ -13,28 +13,39 @@ fn main() -> Result<(), Box<dyn Error>> {
mod fill; mod fill;
pub struct XEmbedDemo { pub struct XEmbedDemo {
window: Box<dyn Window>, parent_window_id: u32,
window: Option<Box<dyn Window>>,
} }
impl ApplicationHandler for XEmbedDemo { impl ApplicationHandler for XEmbedDemo {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = WindowAttributes::default()
.with_title("An embedded window!")
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(self.parent_window_id);
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
fn window_event( fn window_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
_window_id: WindowId, _window_id: WindowId,
event: WindowEvent, event: WindowEvent,
) { ) {
let window = self.window.as_ref().unwrap();
match event { match event {
WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
self.window.pre_present_notify(); window.pre_present_notify();
fill::fill_window(&*self.window); fill::fill_window(window.as_ref());
}, },
_ => (), _ => (),
} }
} }
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
self.window.request_redraw(); self.window.as_ref().unwrap().request_redraw();
} }
} }
@@ -47,15 +58,7 @@ fn main() -> Result<(), Box<dyn Error>> {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
Ok(event_loop.run(|event_loop| { Ok(event_loop.run_app(XEmbedDemo { parent_window_id, window: None })?)
let window_attributes = WindowAttributes::default()
.with_title("An embedded window!")
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(parent_window_id);
let window = event_loop.create_window(window_attributes).unwrap();
XEmbedDemo { window }
})?)
} }
#[cfg(not(x11_platform))] #[cfg(not(x11_platform))]

View File

@@ -7,8 +7,6 @@ use crate::platform::macos::ApplicationHandlerExtMacOS;
use crate::window::WindowId; use crate::window::WindowId;
/// The handler of the application events. /// The handler of the application events.
///
/// This is [dropped][std::ops::Drop] when the event loop is being shut down.
pub trait ApplicationHandler { pub trait ApplicationHandler {
/// Emitted when new events arrive from the OS to be processed. /// Emitted when new events arrive from the OS to be processed.
/// ///
@@ -103,9 +101,7 @@ pub trait ApplicationHandler {
/// ///
/// [`can_create_surfaces()`]: Self::can_create_surfaces() /// [`can_create_surfaces()`]: Self::can_create_surfaces()
/// [`destroy_surfaces()`]: Self::destroy_surfaces() /// [`destroy_surfaces()`]: Self::destroy_surfaces()
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop);
let _ = event_loop;
}
/// Called after a wake up is requested using [`EventLoopProxy::wake_up()`]. /// Called after a wake up is requested using [`EventLoopProxy::wake_up()`].
/// ///
@@ -146,6 +142,8 @@ pub trait ApplicationHandler {
/// # ) { /// # ) {
/// # } /// # }
/// # /// #
/// # fn can_create_surfaces(&mut self, _event_loop: &dyn ActiveEventLoop) {}
/// #
/// fn proxy_wake_up(&mut self, _event_loop: &dyn ActiveEventLoop) { /// fn proxy_wake_up(&mut self, _event_loop: &dyn ActiveEventLoop) {
/// // Iterate current events, since wake-ups may have been merged. /// // Iterate current events, since wake-ups may have been merged.
/// // /// //
@@ -164,6 +162,8 @@ pub trait ApplicationHandler {
/// ///
/// let (sender, receiver) = mpsc::channel(); /// let (sender, receiver) = mpsc::channel();
/// ///
/// let mut app = MyApp { receiver };
///
/// // Send an event in a loop /// // Send an event in a loop
/// let proxy = event_loop.create_proxy(); /// let proxy = event_loop.create_proxy();
/// let background_thread = thread::spawn(move || { /// let background_thread = thread::spawn(move || {
@@ -182,8 +182,9 @@ pub trait ApplicationHandler {
/// } /// }
/// }); /// });
/// ///
/// event_loop.run(|_event_loop| MyApp { receiver })?; /// event_loop.run_app(&mut app)?;
/// ///
/// drop(app);
/// background_thread.join().unwrap(); /// background_thread.join().unwrap();
/// ///
/// Ok(()) /// Ok(())
@@ -285,13 +286,13 @@ pub trait ApplicationHandler {
/// with the [`onStop`] lifecycle event which typically results in the surface to be destroyed /// with the [`onStop`] lifecycle event which typically results in the surface to be destroyed
/// after the app becomes invisible. /// after the app becomes invisible.
/// ///
/// Applications that need to run on Android must be able to handle their underlying /// Applications that need to run on Android should assume their [`NativeWindow`] has been
/// [`SurfaceView`] being destroyed, which in turn indirectly invalidates any existing /// destroyed, which indirectly invalidates any existing render surfaces that may have been
/// render surfaces that may have been created outside of Winit (such as an `EGLSurface`, /// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]).
/// [`VkSurfaceKHR`] or [`wgpu::Surface`]).
/// ///
/// This means that in this method, you must drop all render surfaces before the event callback /// When receiving [`destroy_surfaces()`] Android applications should drop all render surfaces
/// completes, and only re-create them in or after [`can_create_surfaces()`] is next recieved. /// before the event callback completes, which may be re-created when the application next
/// receives [`can_create_surfaces()`].
/// ///
/// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window /// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window
/// [`Surface`]: https://developer.android.com/reference/android/view/Surface /// [`Surface`]: https://developer.android.com/reference/android/view/Surface
@@ -309,6 +310,14 @@ pub trait ApplicationHandler {
let _ = event_loop; let _ = event_loop;
} }
/// Emitted when the event loop is being shut down.
///
/// This is irreversible - if this method is called, it is guaranteed that the event loop
/// will exit right after.
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
/// Emitted when the application has received a memory warning. /// Emitted when the application has received a memory warning.
/// ///
/// ## Platform-specific /// ## Platform-specific
@@ -404,6 +413,11 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
(**self).destroy_surfaces(event_loop); (**self).destroy_surfaces(event_loop);
} }
#[inline]
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).exiting(event_loop);
}
#[inline] #[inline]
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) { fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).memory_warning(event_loop); (**self).memory_warning(event_loop);
@@ -473,6 +487,11 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
(**self).destroy_surfaces(event_loop); (**self).destroy_surfaces(event_loop);
} }
#[inline]
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).exiting(event_loop);
}
#[inline] #[inline]
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) { fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).memory_warning(event_loop); (**self).memory_warning(event_loop);

View File

@@ -75,8 +75,7 @@ changelog entry.
variables to test the respective modifiers of window creation. variables to test the respective modifiers of window creation.
- Added `Window::surface_position`, which is the position of the surface inside the window. - Added `Window::surface_position`, which is the position of the surface inside the window.
- Added `Window::safe_area`, which describes the area of the surface that is unobstructed. - Added `Window::safe_area`, which describes the area of the surface that is unobstructed.
- On X11 and Wayland, improved scancode conversions for more obscure key codes. - On X11, Wayland, Windows and macOS, improved scancode conversions for more obscure key codes.
- On Windows, improved scancode conversions for more obscure key codes.
### Changed ### Changed
@@ -164,6 +163,8 @@ changelog entry.
- On X11, use bottom-right corner for IME hotspot in `Window::set_ime_cursor_area`. - On X11, use bottom-right corner for IME hotspot in `Window::set_ime_cursor_area`.
- On macOS and iOS, no longer emit `ScaleFactorChanged` upon window creation. - On macOS and iOS, no longer emit `ScaleFactorChanged` upon window creation.
- On macOS, no longer emit `Focused` upon window creation. - On macOS, no longer emit `Focused` upon window creation.
- On iOS, emit more events immediately, instead of queuing them.
- Update `smol_str` to version `0.3`
### Removed ### Removed
@@ -210,3 +211,8 @@ changelog entry.
- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again. - On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again.
- On X11, fix XInput handling that prevented a new window from getting the focus in some cases. - On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
- On iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface. - On iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface.
- On macOS, fixed the scancode conversion for audio volume keys.
- On macOS, fixed the scancode conversion for `IntlBackslash`.
- On macOS, fixed redundant `SurfaceResized` event at window creation.
- On macOS, fix crash when pressing Caps Lock in certain configurations.
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.

View File

@@ -1,4 +1,39 @@
//! The event enums and assorted supporting types. //! The event enums and assorted supporting types.
//!
//! These are sent to the closure given to [`EventLoop::run_app(...)`], where they get
//! processed and used to modify the program state. For more details, see the root-level
//! documentation.
//!
//! Some of these events represent different "parts" of a traditional event-handling loop. You could
//! approximate the basic ordering loop of [`EventLoop::run_app(...)`] like this:
//!
//! ```rust,ignore
//! let mut start_cause = StartCause::Init;
//!
//! while !elwt.exiting() {
//! app.new_events(event_loop, start_cause);
//!
//! for event in (window events, user events, device events) {
//! // This will pick the right method on the application based on the event.
//! app.handle_event(event_loop, event);
//! }
//!
//! for window_id in (redraw windows) {
//! app.window_event(event_loop, window_id, RedrawRequested);
//! }
//!
//! app.about_to_wait(event_loop);
//! start_cause = wait_if_necessary();
//! }
//!
//! app.exiting(event_loop);
//! ```
//!
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
//! describes what happens in what order.
//!
//! [`EventLoop::run_app(...)`]: crate::event_loop::EventLoop::run_app
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Mutex, Weak}; use std::sync::{Mutex, Weak};
#[cfg(not(web_platform))] #[cfg(not(web_platform))]
@@ -50,6 +85,11 @@ pub(crate) enum Event {
/// [`ApplicationHandler::suspended()`]: crate::application::ApplicationHandler::suspended() /// [`ApplicationHandler::suspended()`]: crate::application::ApplicationHandler::suspended()
Suspended, Suspended,
/// See [`ApplicationHandler::can_create_surfaces()`] for details.
///
/// [`ApplicationHandler::can_create_surfaces()`]: crate::application::ApplicationHandler::can_create_surfaces()
CreateSurfaces,
/// See [`ApplicationHandler::resumed()`] for details. /// See [`ApplicationHandler::resumed()`] for details.
/// ///
/// [`ApplicationHandler::resumed()`]: crate::application::ApplicationHandler::resumed() /// [`ApplicationHandler::resumed()`]: crate::application::ApplicationHandler::resumed()
@@ -60,6 +100,11 @@ pub(crate) enum Event {
/// [`ApplicationHandler::about_to_wait()`]: crate::application::ApplicationHandler::about_to_wait() /// [`ApplicationHandler::about_to_wait()`]: crate::application::ApplicationHandler::about_to_wait()
AboutToWait, AboutToWait,
/// See [`ApplicationHandler::exiting()`] for details.
///
/// [`ApplicationHandler::exiting()`]: crate::application::ApplicationHandler::exiting()
LoopExiting,
/// See [`ApplicationHandler::memory_warning()`] for details. /// See [`ApplicationHandler::memory_warning()`] for details.
/// ///
/// [`ApplicationHandler::memory_warning()`]: crate::application::ApplicationHandler::memory_warning() /// [`ApplicationHandler::memory_warning()`]: crate::application::ApplicationHandler::memory_warning()
@@ -130,18 +175,23 @@ pub enum WindowEvent {
/// The window has been destroyed. /// The window has been destroyed.
Destroyed, Destroyed,
/// A file has been dropped into the window.
///
/// When the user drops multiple files at once, this event will be emitted for each file
/// separately.
DroppedFile(PathBuf),
/// A file is being hovered over the window. /// A file is being hovered over the window.
/// ///
/// When the user hovers multiple files at once, this event will be emitted for each file /// When the user hovers multiple files at once, this event will be emitted for each file
/// separately. /// separately.
HoveredFile(PathBuf), HoveredFile(PathBuf),
/// A file has been dropped into the window.
///
/// When the user drops multiple files at once, this event will be emitted for each file
/// separately.
///
/// The support for this is known to be incomplete, see [#720] for more
/// information.
///
/// [#720]: https://github.com/rust-windowing/winit/issues/720
DroppedFile(PathBuf),
/// A file was hovered, but has exited the window. /// A file was hovered, but has exited the window.
/// ///
/// There will be a single `HoveredFileCancelled` event triggered even if multiple files were /// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
@@ -162,6 +212,7 @@ pub enum WindowEvent {
/// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down, /// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down,
/// numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key /// numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key
/// events which are not marked as `is_synthetic`. /// events which are not marked as `is_synthetic`.
/// - **iOS:** Unsupported.
KeyboardInput { KeyboardInput {
device_id: Option<DeviceId>, device_id: Option<DeviceId>,
event: KeyEvent, event: KeyEvent,
@@ -365,10 +416,18 @@ pub enum WindowEvent {
/// Touchpad pressure event. /// Touchpad pressure event.
/// ///
/// At the moment, only supported on Apple forcetouch-capable macbooks. /// ## Platform-specific
/// The parameters are: pressure level (value between 0 and 1 representing how hard the ///
/// touchpad is being pressed) and stage (integer representing the click level). /// - **macOS**: Only supported on Apple forcetouch-capable macbooks.
TouchpadPressure { device_id: Option<DeviceId>, pressure: f32, stage: i64 }, /// - **Android / iOS / Wayland / X11 / Windows / Orbital / Web:** Unsupported.
TouchpadPressure {
device_id: Option<DeviceId>,
/// Value between 0 and 1 representing how hard the touchpad is being
/// pressed.
pressure: f32,
/// Represents the click level.
stage: i64,
},
/// The window's scale factor has changed. /// The window's scale factor has changed.
/// ///
@@ -1150,6 +1209,7 @@ mod tests {
let wid = WindowId::from_raw(0); let wid = WindowId::from_raw(0);
x(NewEvents(event::StartCause::Init)); x(NewEvents(event::StartCause::Init));
x(AboutToWait); x(AboutToWait);
x(LoopExiting);
x(Suspended); x(Suspended);
x(Resumed); x(Resumed);

View File

@@ -195,48 +195,10 @@ impl EventLoop {
} }
impl EventLoop { impl EventLoop {
/// Run the event loop on the current thread. /// Run the application with the event loop on the calling thread.
///
/// You pass in a closure that returns your application state. This closure has access to the
/// currently running event loop, allowing you to initialize your windows and surfaces in here.
/// ///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior. /// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
/// ///
/// ## Event loop flow
///
/// This function internally handles the different parts of a traditional event-handling loop.
/// You could imagine this method being implemented like this:
///
/// ```rust,ignore
/// // Initialize.
/// let mut app = init_closure(event_loop);
/// let mut start_cause = StartCause::Init;
///
/// // Run loop.
/// while !elwt.exiting() {
/// // Wake up.
/// app.new_events(event_loop, start_cause);
///
/// // Handle events by the user.
/// for (device_id, event) in incoming_device_events {
/// app.device_event(event_loop, device_id, event);
/// }
/// for (window_id, event) in incoming_window_events {
/// app.window_event(event_loop, window_id, event);
/// }
///
/// // Done handling events, wait until we're woken up again.
/// app.about_to_wait(event_loop);
/// start_cause = wait_if_necessary();
/// }
///
/// // Finished running, drop application state.
/// drop(app);
/// ```
///
/// This is of course a very coarse-grained overview, and leaves out timing details like
/// [`ControlFlow::WaitUntil`] and life-cycle methods like [`ApplicationHandler::resumed`].
///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **iOS:** Will never return to the caller and so values not passed to this function will /// - **iOS:** Will never return to the caller and so values not passed to this function will
@@ -251,7 +213,7 @@ impl EventLoop {
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]" doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
)] )]
#[cfg_attr(not(any(web_platform, docsrs)), doc = " `EventLoopExtWeb::spawn_app()`")] #[cfg_attr(not(any(web_platform, docsrs)), doc = " `EventLoopExtWeb::spawn_app()`")]
/// [^1] instead of [`run()`] to avoid the need for the Javascript exception trick, and to /// [^1] instead of [`run_app()`] to avoid the need for the Javascript exception trick, and to
/// make it clearer that the event loop runs asynchronously (via the browser's own, /// make it clearer that the event loop runs asynchronously (via the browser's own,
/// internal, event loop) and doesn't block the current thread of execution like it does /// internal, event loop) and doesn't block the current thread of execution like it does
/// on other platforms. /// on other platforms.
@@ -261,20 +223,11 @@ impl EventLoop {
/// [^1]: `spawn_app()` is only available on the Web platform. /// [^1]: `spawn_app()` is only available on the Web platform.
/// ///
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
/// [`run()`]: Self::run() /// [`run_app()`]: Self::run_app()
#[inline] #[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))] #[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run<A: ApplicationHandler>(
self,
init_closure: impl FnOnce(&dyn ActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
self.event_loop.run(init_closure)
}
/// Run the event loop with the given application state.
#[deprecated = "less flexible version of `run`"]
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> { pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
self.run(|_event_loop| app) self.event_loop.run_app(app)
} }
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events /// Creates an [`EventLoopProxy`] that can be used to dispatch user events
@@ -439,7 +392,9 @@ pub trait ActiveEventLoop: AsAny {
/// Gets the current [`ControlFlow`]. /// Gets the current [`ControlFlow`].
fn control_flow(&self) -> ControlFlow; fn control_flow(&self) -> ControlFlow;
/// This stops the event loop. /// This exits the event loop.
///
/// See [`exiting`][crate::application::ApplicationHandler::exiting].
fn exit(&self); fn exit(&self);
/// Returns if the [`EventLoop`] is about to stop. /// Returns if the [`EventLoop`] is about to stop.

View File

@@ -1,14 +1,78 @@
//! Winit is a cross-platform window creation and event loop management library. //! Winit is a cross-platform window creation and event loop management library.
//! //!
//! # Usage
//!
//! `winit` can be added to `Cargo.toml` as a dependency. It can be added via `cargo add`.
//!
//! ```bash
//! $ cargo add winit
//! ```
//!
//! To only enable the X11 backend on Free Unix[^unix] systems, disable default features
//! and enable the `x11` feature.
//!
//! ```bash
//! $ cargo add winit --no-default-features --features x11
//! ```
//!
//! To only enable the Wayland backend on Free Unix systems, disable default features
//! and enable the `wayland` feature.
//!
//! ```bash
//! $ cargo add winit --no-default-features --features wayland
//! ```
//!
//! These features have no effect on systems that are not Free Unix.
//!
//! ## Dependencies
//!
//! Dependencies on non-system libraries is managed through Cargo. For the X11
//! backend, the following Ubuntu packages or their equivalents must[^must] be installed.
//!
//! - `libx11-dev`
//! - `libxcb1-dev`
//! - `libxi-dev`
//! - `libxcbcommon-dev`
//! - `libxcbcommon-x11-dev`
//!
//! For the Wayland backend, the following Ubuntu packages or their equivalents
//! must be installed.
//!
//! - `libwayland-dev`
//! - `libxcbcommon-dev`
//! - `libfontconfig` (only with `sctk-adwaita` feature)
//! - `freetype` (only with `sctk-adwaita` feature)
//!
//! The "dev" packages are only needed for building binaries that use `winit`. On
//! deployed system the non-`dev` equivalents need to be installed.
//!
//! The other backends (Windows, macOS, etc) do not have any dependencies on system libraries
//! that don't already come with the operating system. However, note that the Windows backend
//! only supports Windows 10 and above, and the macOS backend only supports macOS
//! 10.14 and above.
//!
//! [^unix]: Unix systems outside of Android and Apple, like Linux or FreeBSD.
//! [^must]: This is not a "must" when the "dlopen" features are enabled
//!
//! # Building windows
//!
//! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with
//! the [`EventLoop::new()`] function.
//!
//! ```no_run
//! use winit::event_loop::EventLoop;
//!
//! # // Intentionally use `fn main` for clarity
//! fn main() {
//! let event_loop = EventLoop::new().unwrap();
//! // ...
//! }
//! ```
//!
//! Then you create a [`Window`] with [`create_window`].
//!
//! # Event handling //! # Event handling
//! //!
//! Basically all of the functionality that Winit exposes requires an [`ActiveEventLoop`], which you
//! can get access to by running an [`EventLoop`] using [`EventLoop::new()`] and
//! [`EventLoop::run()`].
//!
//! Once it's running, you can create your [`Window`]s with [`ActiveEventLoop::create_window()`] by
//! passing in the desired [`WindowAttributes`].
//!
//! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can //! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can
//! generate [`WindowEvent`]s when certain input events occur, such as a cursor moving over the //! generate [`WindowEvent`]s when certain input events occur, such as a cursor moving over the
//! window or a key getting pressed while the window is focused. Devices can generate //! window or a key getting pressed while the window is focused. Devices can generate
@@ -16,10 +80,9 @@
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a //! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`]. //! [`DeviceEvent`].
//! //!
//! You retrieve by implementing [`ApplicationHandler`] for a new type, which will be the state of //! You can retrieve events by calling [`EventLoop::run_app()`]. This function will
//! your application. The methods in this trait will continuously receive events until //! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! [`ActiveEventLoop::exit()`] is used, at which point your application state will be dropped, and //! will run until [`exit()`] is used, at which point [`exiting()`] is called.
//! the application shuts down.
//! //!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop //! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g Web, iOS) and works //! model, since that can't be implemented properly on some platforms (e.g Web, iOS) and works
@@ -40,20 +103,19 @@
//! use winit::application::ApplicationHandler; //! use winit::application::ApplicationHandler;
//! use winit::event::WindowEvent; //! use winit::event::WindowEvent;
//! use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; //! use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
//! use winit::window::{Window, WindowId}; //! use winit::window::{Window, WindowId, WindowAttributes};
//! //!
//! #[derive(Default)]
//! struct App { //! struct App {
//! window: Box<dyn Window>, //! window: Option<Box<dyn Window>>,
//! } //! }
//! //!
//! impl ApplicationHandler for App { //! impl ApplicationHandler for App {
//! fn window_event( //! fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
//! &mut self, //! self.window = Some(event_loop.create_window(WindowAttributes::default()).unwrap());
//! event_loop: &dyn ActiveEventLoop, //! }
//! id: WindowId, //!
//! event: WindowEvent, //! fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, id: WindowId, event: WindowEvent) {
//! ) {
//! // Called by `EventLoop::run` when a new event happens on the window.
//! match event { //! match event {
//! WindowEvent::CloseRequested => { //! WindowEvent::CloseRequested => {
//! println!("The close button was pressed; stopping"); //! println!("The close button was pressed; stopping");
@@ -73,19 +135,16 @@
//! // You only need to call this if you've determined that you need to redraw in //! // You only need to call this if you've determined that you need to redraw in
//! // applications which do not always need to. Applications that redraw continuously //! // applications which do not always need to. Applications that redraw continuously
//! // can render here instead. //! // can render here instead.
//! self.window.request_redraw(); //! self.window.as_ref().unwrap().request_redraw();
//! }, //! }
//! _ => (), //! _ => (),
//! } //! }
//! } //! }
//! } //! }
//! //!
//! # // Intentionally use `fn main` for clarity //! # // Intentionally use `fn main` for clarity
//! fn main() -> Result<(), Box<dyn std::error::Error>> { //! fn main() {
//! // Create a new event loop. //! let event_loop = EventLoop::new().unwrap();
//! let event_loop = EventLoop::new()?;
//!
//! // Configure settings before launching.
//! //!
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't //! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications. //! // dispatched any events. This is ideal for games and similar applications.
@@ -96,21 +155,8 @@
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll. //! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait); //! event_loop.set_control_flow(ControlFlow::Wait);
//! //!
//! // Launch and begin running our event loop. //! let mut app = App::default();
//! event_loop.run(|event_loop| { //! event_loop.run_app(&mut app);
//! // The event loop has launched, and we can initialize our UI state in this closure.
//!
//! // Create a simple window with default attributes.
//! let window = event_loop
//! .create_window(Window::default_attributes())
//! .expect("failed creating window");
//!
//! // Give our newly created application state to Winit, which will, when necessary, call
//! // the `ApplicationHandler` methods configured above.
//! App { window }
//! })?;
//!
//! Ok(())
//! } //! }
//! ``` //! ```
//! //!
@@ -258,21 +304,20 @@
//! |32-bit ARM Android |`arm-linux-androideabi` |Android | //! |32-bit ARM Android |`arm-linux-androideabi` |Android |
//! |64-bit SPARC Linux with glibc |`sparc64-unknown-linux-gnu` |X11, Wayland | //! |64-bit SPARC Linux with glibc |`sparc64-unknown-linux-gnu` |X11, Wayland |
//! //!
//! [`ActiveEventLoop`]: event_loop::ActiveEventLoop
//! [`EventLoop`]: event_loop::EventLoop //! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoop::new()`]: event_loop::EventLoop::new //! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [`EventLoop::run()`]: event_loop::EventLoop::run //! [`EventLoop::run_app()`]: event_loop::EventLoop::run_app
//! [`ActiveEventLoop::exit()`]: event_loop::ActiveEventLoop::exit //! [`exit()`]: event_loop::ActiveEventLoop::exit
//! [`Window`]: window::Window //! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId //! [`WindowId`]: window::WindowId
//! [`WindowAttributes`]: window::WindowAttributes //! [`WindowAttributes`]: window::WindowAttributes
//! [window_new]: window::Window::new //! [window_new]: window::Window::new
//! [`ActiveEventLoop::create_window()`]: event_loop::ActiveEventLoop::create_window //! [`create_window`]: event_loop::ActiveEventLoop::create_window
//! [`Window::id()`]: window::Window::id //! [`Window::id()`]: window::Window::id
//! [`WindowEvent`]: event::WindowEvent //! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent //! [`DeviceEvent`]: event::DeviceEvent
//! [`ApplicationHandler`]: application::ApplicationHandler
//! [`Event::UserEvent`]: event::Event::UserEvent //! [`Event::UserEvent`]: event::Event::UserEvent
//! [`exiting()`]: crate::application::ApplicationHandler::exiting
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle //! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland. //! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland.

View File

@@ -1,18 +1,12 @@
//! Types useful for interacting with a user's monitors. //! Types useful for interacting with a user's monitors.
//!
//! If you want to get basic information about a monitor, you can use the
//! [`MonitorHandle`] type. This is retrieved from one of the following
//! methods, which return an iterator of [`MonitorHandle`]:
//! - [`ActiveEventLoop::available_monitors`][crate::event_loop::ActiveEventLoop::available_monitors].
//! - [`Window::available_monitors`][crate::window::Window::available_monitors].
use std::num::{NonZeroU16, NonZeroU32}; use std::num::{NonZeroU16, NonZeroU32};
use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::platform_impl; use crate::platform_impl;
/// Describes a fullscreen video mode of a monitor. /// A handle to a fullscreen video mode of a specific monitor.
/// ///
/// Can be acquired with [`MonitorHandle::video_modes`]. /// This can be acquired with [`MonitorHandle::video_modes`].
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle { pub struct VideoModeHandle {
pub(crate) video_mode: platform_impl::VideoModeHandle, pub(crate) video_mode: platform_impl::VideoModeHandle,
@@ -92,7 +86,15 @@ impl std::fmt::Display for VideoModeHandle {
/// Handle to a monitor. /// Handle to a monitor.
/// ///
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation. /// Allows you to retrieve basic information and metadata about a monitor.
///
/// Can be used in [`Window`] creation to place the window on a specific
/// monitor.
///
/// This can be retrieved from one of the following methods, which return an
/// iterator of [`MonitorHandle`]s:
/// - [`ActiveEventLoop::available_monitors`](crate::event_loop::ActiveEventLoop::available_monitors).
/// - [`Window::available_monitors`](crate::window::Window::available_monitors).
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///

View File

@@ -65,7 +65,7 @@
//! let app = NSApplication::sharedApplication(mtm); //! let app = NSApplication::sharedApplication(mtm);
//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); //! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
//! //!
//! // event_loop.run(|event_loop| { ... })?; //! // event_loop.run_app(&mut my_app);
//! Ok(()) //! Ok(())
//! } //! }
//! ``` //! ```

View File

@@ -1,14 +1,16 @@
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::error::EventLoopError; use crate::error::EventLoopError;
use crate::event_loop::{ActiveEventLoop, EventLoop}; use crate::event_loop::EventLoop;
#[cfg(doc)] #[cfg(doc)]
use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window}; use crate::{
event_loop::ActiveEventLoop, platform::pump_events::EventLoopExtPumpEvents, window::Window,
};
/// Additional methods on [`EventLoop`] to return control flow to the caller. /// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand { pub trait EventLoopExtRunOnDemand {
/// Run the application with the event loop on the calling thread. /// Run the application with the event loop on the calling thread.
/// ///
/// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) /// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
/// closures and it is possible to return control back to the caller without /// closures and it is possible to return control back to the caller without
/// consuming the `EventLoop` (by using [`exit()`]) and /// consuming the `EventLoop` (by using [`exit()`]) and
/// so the event loop can be re-run after it has exit. /// so the event loop can be re-run after it has exit.
@@ -21,7 +23,7 @@ pub trait EventLoopExtRunOnDemand {
/// to while maintaining the full state of your application. (If you need something like this /// to while maintaining the full state of your application. (If you need something like this
/// you can look at the [`EventLoopExtPumpEvents::pump_app_events()`] API) /// you can look at the [`EventLoopExtPumpEvents::pump_app_events()`] API)
/// ///
/// Each time `run_on_demand` is called the startup sequence of `init`, followed by /// Each time `run_app_on_demand` is called the startup sequence of `init`, followed by
/// `resume` is being preserved. /// `resume` is being preserved.
/// ///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior. /// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
@@ -38,7 +40,7 @@ pub trait EventLoopExtRunOnDemand {
/// [^1] more than once instead). /// [^1] more than once instead).
/// - No [`Window`] state can be carried between separate runs of the event loop. /// - No [`Window`] state can be carried between separate runs of the event loop.
/// ///
/// You are strongly encouraged to use [`EventLoop::run()`] for portability, unless you /// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
/// specifically need the ability to re-run a single event loop more than once /// specifically need the ability to re-run a single event loop more than once
/// ///
/// # Supported Platforms /// # Supported Platforms
@@ -58,18 +60,12 @@ pub trait EventLoopExtRunOnDemand {
/// ///
/// [`exit()`]: ActiveEventLoop::exit() /// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_on_demand<A: ApplicationHandler>( fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError>;
&mut self,
init_closure: impl FnOnce(&dyn ActiveEventLoop) -> A,
) -> Result<(), EventLoopError>;
} }
impl EventLoopExtRunOnDemand for EventLoop { impl EventLoopExtRunOnDemand for EventLoop {
fn run_on_demand<A: ApplicationHandler>( fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError> {
&mut self, self.event_loop.run_app_on_demand(app)
init_closure: impl FnOnce(&dyn ActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
self.event_loop.run_on_demand(init_closure)
} }
} }

View File

@@ -190,10 +190,10 @@ pub trait EventLoopExtWeb {
/// Initializes the winit event loop. /// Initializes the winit event loop.
/// ///
/// Unlike /// Unlike
#[cfg_attr(all(web_platform, target_feature = "exception-handling"), doc = "`run()`")] #[cfg_attr(all(web_platform, target_feature = "exception-handling"), doc = "`run_app()`")]
#[cfg_attr( #[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")), not(all(web_platform, target_feature = "exception-handling")),
doc = "[`run()`]" doc = "[`run_app()`]"
)] )]
/// [^1], this returns immediately, and doesn't throw an exception in order to /// [^1], this returns immediately, and doesn't throw an exception in order to
/// satisfy its [`!`] return type. /// satisfy its [`!`] return type.
@@ -206,9 +206,9 @@ pub trait EventLoopExtWeb {
/// ///
#[cfg_attr( #[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")), not(all(web_platform, target_feature = "exception-handling")),
doc = "[`run()`]: EventLoop::run()" doc = "[`run_app()`]: EventLoop::run_app()"
)] )]
/// [^1]: `run()` is _not_ available on Wasm when the target supports `exception-handling`. /// [^1]: `run_app()` is _not_ available on Wasm when the target supports `exception-handling`.
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A); fn spawn_app<A: ApplicationHandler + 'static>(self, app: A);
/// Sets the strategy for [`ControlFlow::Poll`]. /// Sets the strategy for [`ControlFlow::Poll`].

View File

@@ -496,21 +496,15 @@ impl EventLoop {
input_status input_status
} }
pub fn run<A: ApplicationHandler>( pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
mut self, self.run_app_on_demand(app)
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
self.run_on_demand(init_closure)
} }
pub fn run_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A, mut app: A,
) -> Result<(), EventLoopError> { ) -> Result<(), EventLoopError> {
self.window_target.clear_exit(); self.window_target.clear_exit();
let mut app = init_closure(&self.window_target);
loop { loop {
match self.pump_app_events(None, &mut app) { match self.pump_app_events(None, &mut app) {
PumpStatus::Exit(0) => { PumpStatus::Exit(0) => {
@@ -552,6 +546,8 @@ impl EventLoop {
if self.exiting() { if self.exiting() {
self.loop_running = false; self.loop_running = false;
app.exiting(&self.window_target);
PumpStatus::Exit(0) PumpStatus::Exit(0)
} else { } else {
PumpStatus::Continue PumpStatus::Continue

View File

@@ -9,12 +9,12 @@ use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningAppli
use objc2_foundation::{MainThreadMarker, NSNotification}; use objc2_foundation::{MainThreadMarker, NSNotification};
use super::super::event_handler::EventHandler; use super::super::event_handler::EventHandler;
use super::event_loop::{stop_app_immediately, EventLoopProxy, PanicInfo}; use super::event_loop::{stop_app_immediately, ActiveEventLoop, EventLoopProxy, PanicInfo};
use super::menu; use super::menu;
use super::observer::{EventLoopWaker, RunLoop}; use super::observer::{EventLoopWaker, RunLoop};
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::event::{StartCause, WindowEvent}; use crate::event::{StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop, ControlFlow}; use crate::event_loop::ControlFlow;
use crate::window::WindowId; use crate::window::WindowId;
#[derive(Debug)] #[derive(Debug)]
@@ -163,25 +163,17 @@ impl AppState {
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) { pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
trace_scope!("NSApplicationWillTerminateNotification"); trace_scope!("NSApplicationWillTerminateNotification");
// TODO: Notify every window that it will be destroyed, like done in iOS? // TODO: Notify every window that it will be destroyed, like done in iOS?
self.with_handler(|app, event_loop| {
app.exiting(event_loop);
});
self.internal_exit(); self.internal_exit();
} }
/// Place the event handler in the application state for the duration /// Place the event handler in the application state for the duration
/// of the given closure. /// of the given closure.
pub fn set_init_closure<A: ApplicationHandler, R>( pub fn set_event_handler<R>(
&self, &self,
init_closure: impl FnOnce(&dyn ActiveEventLoop) -> A, handler: &mut dyn ApplicationHandler,
closure: impl FnOnce() -> R, closure: impl FnOnce() -> R,
) -> R { ) -> R {
let init_closure = Box::new( self.event_handler.set(handler, closure)
|active_event_loop: &'_ dyn ActiveEventLoop| -> Box<dyn ApplicationHandler + '_> {
Box::new(init_closure(active_event_loop))
},
);
self.event_handler.set(init_closure, closure)
} }
pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> { pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
@@ -216,6 +208,10 @@ impl AppState {
/// NOTE: that if the `NSApplication` has been launched then that state is preserved, /// 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. /// and we won't need to re-launch the app if subsequent EventLoops are run.
pub fn internal_exit(self: &Rc<Self>) { pub fn internal_exit(self: &Rc<Self>) {
self.with_handler(|app, event_loop| {
app.exiting(event_loop);
});
self.set_is_running(false); self.set_is_running(false);
self.set_stop_on_redraw(false); self.set_stop_on_redraw(false);
self.set_stop_before_wait(false); self.set_stop_before_wait(false);

View File

@@ -92,17 +92,12 @@ fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
/// Create `KeyEvent` for the given `NSEvent`. /// Create `KeyEvent` for the given `NSEvent`.
/// ///
/// This function shouldn't be called when the IME input is in process. /// This function shouldn't be called when the IME input is in process.
pub(crate) fn create_key_event( pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bool) -> KeyEvent {
ns_event: &NSEvent,
is_press: bool,
is_repeat: bool,
key_override: Option<PhysicalKey>,
) -> KeyEvent {
use ElementState::{Pressed, Released}; use ElementState::{Pressed, Released};
let state = if is_press { Pressed } else { Released }; let state = if is_press { Pressed } else { Released };
let scancode = unsafe { ns_event.keyCode() }; let scancode = unsafe { ns_event.keyCode() };
let mut physical_key = key_override.unwrap_or_else(|| scancode_to_physicalkey(scancode as u32)); let mut physical_key = scancode_to_physicalkey(scancode as u32);
// NOTE: The logical key should heed both SHIFT and ALT if possible. // NOTE: The logical key should heed both SHIFT and ALT if possible.
// For instance: // For instance:
@@ -111,20 +106,15 @@ pub(crate) fn create_key_event(
// * Pressing CTRL SHIFT A: logical key should also be "A" // * Pressing CTRL SHIFT A: logical key should also be "A"
// This is not easy to tease out of `NSEvent`, but we do our best. // This is not easy to tease out of `NSEvent`, but we do our best.
let text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() { let characters = unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
let text_with_all_modifiers = if characters.is_empty() {
None None
} else { } else {
let characters = if matches!(physical_key, PhysicalKey::Unidentified(_)) {
unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default(); // The key may be one of the funky function keys
if characters.is_empty() { physical_key = extra_function_key_to_code(scancode, &characters);
None
} else {
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
// The key may be one of the funky function keys
physical_key = extra_function_key_to_code(scancode, &characters);
}
Some(SmolStr::new(characters))
} }
Some(SmolStr::new(characters))
}; };
let key_from_code = code_to_key(physical_key, scancode); let key_from_code = code_to_key(physical_key, scancode);
@@ -377,6 +367,7 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::KeyX => Some(0x07), KeyCode::KeyX => Some(0x07),
KeyCode::KeyC => Some(0x08), KeyCode::KeyC => Some(0x08),
KeyCode::KeyV => Some(0x09), KeyCode::KeyV => Some(0x09),
KeyCode::IntlBackslash => Some(0x0a),
KeyCode::KeyB => Some(0x0b), KeyCode::KeyB => Some(0x0b),
KeyCode::KeyQ => Some(0x0c), KeyCode::KeyQ => Some(0x0c),
KeyCode::KeyW => Some(0x0d), KeyCode::KeyW => Some(0x0d),
@@ -422,18 +413,21 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::SuperRight => Some(0x36), KeyCode::SuperRight => Some(0x36),
KeyCode::SuperLeft => Some(0x37), KeyCode::SuperLeft => Some(0x37),
KeyCode::ShiftLeft => Some(0x38), KeyCode::ShiftLeft => Some(0x38),
KeyCode::CapsLock => Some(0x39),
KeyCode::AltLeft => Some(0x3a), KeyCode::AltLeft => Some(0x3a),
KeyCode::ControlLeft => Some(0x3b), KeyCode::ControlLeft => Some(0x3b),
KeyCode::ShiftRight => Some(0x3c), KeyCode::ShiftRight => Some(0x3c),
KeyCode::AltRight => Some(0x3d), KeyCode::AltRight => Some(0x3d),
KeyCode::ControlRight => Some(0x3e), KeyCode::ControlRight => Some(0x3e),
KeyCode::Fn => Some(0x3f),
KeyCode::F17 => Some(0x40), KeyCode::F17 => Some(0x40),
KeyCode::NumpadDecimal => Some(0x41), KeyCode::NumpadDecimal => Some(0x41),
KeyCode::NumpadMultiply => Some(0x43), KeyCode::NumpadMultiply => Some(0x43),
KeyCode::NumpadAdd => Some(0x45), KeyCode::NumpadAdd => Some(0x45),
KeyCode::NumLock => Some(0x47), KeyCode::NumLock => Some(0x47),
KeyCode::AudioVolumeUp => Some(0x49), KeyCode::AudioVolumeUp => Some(0x48),
KeyCode::AudioVolumeDown => Some(0x4a), KeyCode::AudioVolumeDown => Some(0x49),
KeyCode::AudioVolumeMute => Some(0x4a),
KeyCode::NumpadDivide => Some(0x4b), KeyCode::NumpadDivide => Some(0x4b),
KeyCode::NumpadEnter => Some(0x4c), KeyCode::NumpadEnter => Some(0x4c),
KeyCode::NumpadSubtract => Some(0x4e), KeyCode::NumpadSubtract => Some(0x4e),
@@ -452,17 +446,22 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::Numpad8 => Some(0x5b), KeyCode::Numpad8 => Some(0x5b),
KeyCode::Numpad9 => Some(0x5c), KeyCode::Numpad9 => Some(0x5c),
KeyCode::IntlYen => Some(0x5d), KeyCode::IntlYen => Some(0x5d),
KeyCode::IntlRo => Some(0x5e),
KeyCode::NumpadComma => Some(0x5f),
KeyCode::F5 => Some(0x60), KeyCode::F5 => Some(0x60),
KeyCode::F6 => Some(0x61), KeyCode::F6 => Some(0x61),
KeyCode::F7 => Some(0x62), KeyCode::F7 => Some(0x62),
KeyCode::F3 => Some(0x63), KeyCode::F3 => Some(0x63),
KeyCode::F8 => Some(0x64), KeyCode::F8 => Some(0x64),
KeyCode::F9 => Some(0x65), KeyCode::F9 => Some(0x65),
KeyCode::Lang2 => Some(0x66),
KeyCode::F11 => Some(0x67), KeyCode::F11 => Some(0x67),
KeyCode::Lang1 => Some(0x68),
KeyCode::F13 => Some(0x69), KeyCode::F13 => Some(0x69),
KeyCode::F16 => Some(0x6a), KeyCode::F16 => Some(0x6a),
KeyCode::F14 => Some(0x6b), KeyCode::F14 => Some(0x6b),
KeyCode::F10 => Some(0x6d), KeyCode::F10 => Some(0x6d),
KeyCode::ContextMenu => Some(0x6e),
KeyCode::F12 => Some(0x6f), KeyCode::F12 => Some(0x6f),
KeyCode::F15 => Some(0x71), KeyCode::F15 => Some(0x71),
KeyCode::Insert => Some(0x72), KeyCode::Insert => Some(0x72),
@@ -478,11 +477,26 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::ArrowRight => Some(0x7c), KeyCode::ArrowRight => Some(0x7c),
KeyCode::ArrowDown => Some(0x7d), KeyCode::ArrowDown => Some(0x7d),
KeyCode::ArrowUp => Some(0x7e), KeyCode::ArrowUp => Some(0x7e),
KeyCode::Power => Some(0x7f),
_ => None, _ => None,
} }
} }
pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// Follows what Chromium and Firefox do:
// https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h
//
// See also:
// Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
//
// Also see https://developer.apple.com/documentation/appkit/function-key-unicode-values:
//
// > the system handles some function keys at a lower level and your app never sees them.
// > Examples include the Volume Up key, Volume Down key, Volume Mute key, Eject key, and
// > Function key found on many Macs.
//
// So the handling of some of these is mostly for show.
PhysicalKey::Code(match scancode { PhysicalKey::Code(match scancode {
0x00 => KeyCode::KeyA, 0x00 => KeyCode::KeyA,
0x01 => KeyCode::KeyS, 0x01 => KeyCode::KeyS,
@@ -494,7 +508,11 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x07 => KeyCode::KeyX, 0x07 => KeyCode::KeyX,
0x08 => KeyCode::KeyC, 0x08 => KeyCode::KeyC,
0x09 => KeyCode::KeyV, 0x09 => KeyCode::KeyV,
// 0x0a => World 1, // This key is typically located near LeftShift key, roughly the same location as backquote
// (`) on Windows' US layout.
//
// The keycap varies on international keyboards.
0x0a => KeyCode::IntlBackslash,
0x0b => KeyCode::KeyB, 0x0b => KeyCode::KeyB,
0x0c => KeyCode::KeyQ, 0x0c => KeyCode::KeyQ,
0x0d => KeyCode::KeyW, 0x0d => KeyCode::KeyW,
@@ -536,7 +554,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x31 => KeyCode::Space, 0x31 => KeyCode::Space,
0x32 => KeyCode::Backquote, 0x32 => KeyCode::Backquote,
0x33 => KeyCode::Backspace, 0x33 => KeyCode::Backspace,
// 0x34 => unknown, // 0x34 => unknown, // kVK_Powerbook_KeypadEnter
0x35 => KeyCode::Escape, 0x35 => KeyCode::Escape,
0x36 => KeyCode::SuperRight, 0x36 => KeyCode::SuperRight,
0x37 => KeyCode::SuperLeft, 0x37 => KeyCode::SuperLeft,
@@ -555,15 +573,10 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 0x44 => unknown, // 0x44 => unknown,
0x45 => KeyCode::NumpadAdd, 0x45 => KeyCode::NumpadAdd,
// 0x46 => unknown, // 0x46 => unknown,
0x47 => KeyCode::NumLock, 0x47 => KeyCode::NumLock, // kVK_ANSI_KeypadClear
// 0x48 => KeyCode::NumpadClear, 0x48 => KeyCode::AudioVolumeUp,
0x49 => KeyCode::AudioVolumeDown,
// TODO: (Artur) for me, kVK_VolumeUp is 0x48 0x4a => KeyCode::AudioVolumeMute,
// macOS 10.11
// /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/
// Versions/A/Headers/Events.h
0x49 => KeyCode::AudioVolumeUp,
0x4a => KeyCode::AudioVolumeDown,
0x4b => KeyCode::NumpadDivide, 0x4b => KeyCode::NumpadDivide,
0x4c => KeyCode::NumpadEnter, 0x4c => KeyCode::NumpadEnter,
// 0x4d => unknown, // 0x4d => unknown,
@@ -583,23 +596,23 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x5b => KeyCode::Numpad8, 0x5b => KeyCode::Numpad8,
0x5c => KeyCode::Numpad9, 0x5c => KeyCode::Numpad9,
0x5d => KeyCode::IntlYen, 0x5d => KeyCode::IntlYen,
// 0x5e => JIS Ro, 0x5e => KeyCode::IntlRo,
// 0x5f => unknown, 0x5f => KeyCode::NumpadComma,
0x60 => KeyCode::F5, 0x60 => KeyCode::F5,
0x61 => KeyCode::F6, 0x61 => KeyCode::F6,
0x62 => KeyCode::F7, 0x62 => KeyCode::F7,
0x63 => KeyCode::F3, 0x63 => KeyCode::F3,
0x64 => KeyCode::F8, 0x64 => KeyCode::F8,
0x65 => KeyCode::F9, 0x65 => KeyCode::F9,
// 0x66 => JIS Eisuu (macOS), 0x66 => KeyCode::Lang2,
0x67 => KeyCode::F11, 0x67 => KeyCode::F11,
// 0x68 => JIS Kanna (macOS), 0x68 => KeyCode::Lang1,
0x69 => KeyCode::F13, 0x69 => KeyCode::F13,
0x6a => KeyCode::F16, 0x6a => KeyCode::F16,
0x6b => KeyCode::F14, 0x6b => KeyCode::F14,
// 0x6c => unknown, // 0x6c => unknown,
0x6d => KeyCode::F10, 0x6d => KeyCode::F10,
// 0x6e => unknown, 0x6e => KeyCode::ContextMenu,
0x6f => KeyCode::F12, 0x6f => KeyCode::F12,
// 0x70 => unknown, // 0x70 => unknown,
0x71 => KeyCode::F15, 0x71 => KeyCode::F15,
@@ -616,11 +629,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x7c => KeyCode::ArrowRight, 0x7c => KeyCode::ArrowRight,
0x7d => KeyCode::ArrowDown, 0x7d => KeyCode::ArrowDown,
0x7e => KeyCode::ArrowUp, 0x7e => KeyCode::ArrowUp,
// 0x7f => unknown, 0x7f => KeyCode::Power, // On 10.7 and 10.8 only
// 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as
// backquote (`) on Windows' US layout.
0xa => KeyCode::Backquote,
_ => return PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode as u16)), _ => return PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode as u16)),
}) })
} }

View File

@@ -274,23 +274,20 @@ impl EventLoop {
&self.window_target &self.window_target
} }
pub fn run<A: ApplicationHandler>( pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
mut self, self.run_app_on_demand(app)
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
self.run_on_demand(init_closure)
} }
// NB: we don't base this on `pump_events` because for `MacOs` we can't support // 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 // `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 // time and so a layered implementation would end up using a lot of CPU due to
// redundant wake ups. // redundant wake ups.
pub fn run_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A, mut app: A,
) -> Result<(), EventLoopError> { ) -> Result<(), EventLoopError> {
self.app_state.clear_exit(); self.app_state.clear_exit();
self.app_state.set_init_closure(init_closure, || { self.app_state.set_event_handler(&mut app, || {
autoreleasepool(|_| { autoreleasepool(|_| {
// clear / normalize pump_events state // clear / normalize pump_events state
self.app_state.set_wait_timeout(None); self.app_state.set_wait_timeout(None);

View File

@@ -4,30 +4,30 @@ use std::collections::{HashMap, VecDeque};
use std::ptr; use std::ptr;
use std::rc::Rc; use std::rc::Rc;
use objc2::rc::{Retained, WeakId}; use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel}; use objc2::runtime::{AnyObject, Sel};
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass}; use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{ use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView, NSViewFrameDidChangeNotification, NSTrackingRectTag, NSView,
}; };
use objc2_foundation::{ use objc2_foundation::{
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
NSMutableAttributedString, NSNotFound, NSNotificationCenter, NSObject, NSObjectProtocol, NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect,
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, NSSize, NSString, NSUInteger,
}; };
use super::app_state::AppState; use super::app_state::AppState;
use super::cursor::{default_cursor, invisible_cursor}; use super::cursor::{default_cursor, invisible_cursor};
use super::event::{ use super::event::{
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed, code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
scancode_to_physicalkey, scancode_to_physicalkey, KeyEventExtra,
}; };
use super::window::WinitWindow; use super::window::WinitWindow;
use crate::dpi::{LogicalPosition, LogicalSize}; use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{ use crate::event::{
DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, PointerKind, DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerSource, TouchPhase, WindowEvent, PointerKind, PointerSource, TouchPhase, WindowEvent,
}; };
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey}; use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use crate::platform::macos::OptionAsAlt; use crate::platform::macos::OptionAsAlt;
@@ -134,9 +134,6 @@ pub struct ViewState {
marked_text: RefCell<Retained<NSMutableAttributedString>>, marked_text: RefCell<Retained<NSMutableAttributedString>>,
accepts_first_mouse: bool, accepts_first_mouse: bool,
// Weak reference because the window keeps a strong reference to the view
_ns_window: WeakId<WinitWindow>,
/// The state of the `Option` as `Alt`. /// The state of the `Option` as `Alt`.
option_as_alt: Cell<OptionAsAlt>, option_as_alt: Cell<OptionAsAlt>,
} }
@@ -177,9 +174,10 @@ declare_class!(
self.ivars().tracking_rect.set(Some(tracking_rect)); self.ivars().tracking_rect.set(Some(tracking_rect));
} }
#[method(frameDidChange:)] // Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
fn frame_did_change(&self, _event: &NSEvent) { #[method(viewFrameDidChangeNotification:)]
trace_scope!("frameDidChange:"); fn frame_did_change(&self, _notification: Option<&AnyObject>) {
trace_scope!("NSViewFrameDidChangeNotification");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() { if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
self.removeTrackingRect(tracking_rect); self.removeTrackingRect(tracking_rect);
} }
@@ -203,10 +201,7 @@ declare_class!(
fn draw_rect(&self, _rect: NSRect) { fn draw_rect(&self, _rect: NSRect) {
trace_scope!("drawRect:"); trace_scope!("drawRect:");
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`. self.ivars().app_state.handle_redraw(self.window().id());
if let Some(window) = self.ivars()._ns_window.load() {
self.ivars().app_state.handle_redraw(window.id());
}
// This is a direct subclass of NSView, no need to call superclass' drawRect: // This is a direct subclass of NSView, no need to call superclass' drawRect:
} }
@@ -495,7 +490,7 @@ declare_class!(
}; };
if !had_ime_input || self.ivars().forward_key_to_app.get() { if !had_ime_input || self.ivars().forward_key_to_app.get() {
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None); let key_event = create_key_event(&event, true, unsafe { event.isARepeat() });
self.queue_event(WindowEvent::KeyboardInput { self.queue_event(WindowEvent::KeyboardInput {
device_id: None, device_id: None,
event: key_event, event: key_event,
@@ -518,7 +513,7 @@ declare_class!(
) { ) {
self.queue_event(WindowEvent::KeyboardInput { self.queue_event(WindowEvent::KeyboardInput {
device_id: None, device_id: None,
event: create_key_event(&event, false, false, None), event: create_key_event(&event, false, false),
is_synthetic: false, is_synthetic: false,
}); });
} }
@@ -565,7 +560,7 @@ declare_class!(
.expect("could not find current event"); .expect("could not find current event");
self.update_modifiers(&event, false); self.update_modifiers(&event, false);
let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None); let event = create_key_event(&event, true, unsafe { event.isARepeat() });
self.queue_event(WindowEvent::KeyboardInput { self.queue_event(WindowEvent::KeyboardInput {
device_id: None, device_id: None,
@@ -806,11 +801,10 @@ declare_class!(
impl WinitView { impl WinitView {
pub(super) fn new( pub(super) fn new(
app_state: &Rc<AppState>, app_state: &Rc<AppState>,
window: &WinitWindow,
accepts_first_mouse: bool, accepts_first_mouse: bool,
option_as_alt: OptionAsAlt, option_as_alt: OptionAsAlt,
mtm: MainThreadMarker,
) -> Retained<Self> { ) -> Retained<Self> {
let mtm = MainThreadMarker::from(window);
let this = mtm.alloc().set_ivars(ViewState { let this = mtm.alloc().set_ivars(ViewState {
app_state: Rc::clone(app_state), app_state: Rc::clone(app_state),
cursor_state: Default::default(), cursor_state: Default::default(),
@@ -825,34 +819,24 @@ impl WinitView {
forward_key_to_app: Default::default(), forward_key_to_app: Default::default(),
marked_text: Default::default(), marked_text: Default::default(),
accepts_first_mouse, accepts_first_mouse,
_ns_window: WeakId::new(&window.retain()),
option_as_alt: Cell::new(option_as_alt), option_as_alt: Cell::new(option_as_alt),
}); });
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] }; let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
this.setPostsFrameChangedNotifications(true);
let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
unsafe {
notification_center.addObserver_selector_name_object(
&this,
sel!(frameDidChange:),
Some(NSViewFrameDidChangeNotification),
Some(&this),
)
}
*this.ivars().input_source.borrow_mut() = this.current_input_source(); *this.ivars().input_source.borrow_mut() = this.current_input_source();
this this
} }
fn window(&self) -> Retained<WinitWindow> { fn window(&self) -> Retained<WinitWindow> {
// TODO: Simply use `window` property on `NSView`. let window = (**self).window().expect("view must be installed in a window");
// That only returns a window _after_ the view has been attached though!
// (which is incompatible with `frameDidChange:`) if !window.isKindOfClass(WinitWindow::class()) {
// unreachable!("view installed in non-WinitWindow");
// unsafe { msg_send_id![self, window] } }
self.ivars()._ns_window.load().expect("view to have a window")
// SAFETY: Just checked that the window is `WinitWindow`
unsafe { Retained::cast(window) }
} }
fn queue_event(&self, event: WindowEvent) { fn queue_event(&self, event: WindowEvent) {
@@ -962,22 +946,36 @@ impl WinitView {
let scancode = unsafe { ns_event.keyCode() }; let scancode = unsafe { ns_event.keyCode() };
let physical_key = scancode_to_physicalkey(scancode as u32); let physical_key = scancode_to_physicalkey(scancode as u32);
// We'll correct the `is_press` later. let logical_key = code_to_key(physical_key, scancode);
let mut event = create_key_event(ns_event, false, false, Some(physical_key));
let key = code_to_key(physical_key, scancode);
// Ignore processing of unknown modifiers because we can't determine whether // Ignore processing of unknown modifiers because we can't determine whether
// it was pressed or release reliably. // it was pressed or release reliably.
let Some(event_modifier) = key_to_modifier(&key) else { //
// Furthermore, sometimes normal keys are reported inside flagsChanged:, such as
// when holding Caps Lock while pressing another key, see:
// https://github.com/alacritty/alacritty/issues/8268
let Some(event_modifier) = key_to_modifier(&logical_key) else {
break 'send_event; break 'send_event;
}; };
event.physical_key = physical_key;
event.logical_key = key.clone(); let mut event = KeyEvent {
event.location = code_to_location(physical_key); location: code_to_location(physical_key),
logical_key: logical_key.clone(),
physical_key,
repeat: false,
// We'll correct this later.
state: Pressed,
text: None,
platform_specific: KeyEventExtra {
text_with_all_modifiers: None,
key_without_modifiers: logical_key.clone(),
},
};
let location_mask = ModLocationMask::from_location(event.location); let location_mask = ModLocationMask::from_location(event.location);
let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut(); let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();
let phys_mod = phys_mod_state.entry(key).or_insert(ModLocationMask::empty()); let phys_mod =
phys_mod_state.entry(logical_key).or_insert(ModLocationMask::empty());
let is_active = current_modifiers.state().contains(event_modifier); let is_active = current_modifiers.state().contains(event_modifier);
let mut events = VecDeque::with_capacity(2); let mut events = VecDeque::with_capacity(2);

View File

@@ -15,15 +15,15 @@ use objc2_app_kit::{
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSWindowButton, NSWindowDelegate, NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification,
NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
NSWindowToolbarStyle, NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle,
}; };
use objc2_foundation::{ use objc2_foundation::{
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets, ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets,
NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey,
NSKeyValueObservingOptions, NSObject, NSObjectNSDelayedPerforming, NSKeyValueObservingOptions, NSNotificationCenter, NSObject, NSObjectNSDelayedPerforming,
NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString,
}; };
use tracing::{trace, warn}; use tracing::{trace, warn};
@@ -170,7 +170,7 @@ declare_class!(
#[method(windowDidResize:)] #[method(windowDidResize:)]
fn window_did_resize(&self, _: Option<&AnyObject>) { fn window_did_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidResize:"); trace_scope!("windowDidResize:");
// NOTE: WindowEvent::SurfaceResized is reported in frameDidChange. // NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification.
self.emit_move_event(); self.emit_move_event();
} }
@@ -658,9 +658,9 @@ fn new_window(
let view = WinitView::new( let view = WinitView::new(
app_state, app_state,
&window,
attrs.platform_specific.accepts_first_mouse, attrs.platform_specific.accepts_first_mouse,
attrs.platform_specific.option_as_alt, attrs.platform_specific.option_as_alt,
mtm,
); );
// The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until // The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until
@@ -682,6 +682,23 @@ fn new_window(
window.setContentView(Some(&view)); window.setContentView(Some(&view));
window.setInitialFirstResponder(Some(&view)); window.setInitialFirstResponder(Some(&view));
// Configure the view to send notifications whenever its frame rectangle changes.
//
// We explicitly do this _after_ setting the view as the content view of the window, to
// avoid a resize event when creating the window.
view.setPostsFrameChangedNotifications(true);
// `setPostsFrameChangedNotifications` posts the notification immediately, so register the
// observer _after_, again so that the event isn't triggered initially.
let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
unsafe {
notification_center.addObserver_selector_name_object(
&view,
sel!(viewFrameDidChangeNotification:),
Some(NSViewFrameDidChangeNotification),
Some(&view),
)
}
if attrs.transparent { if attrs.transparent {
window.setOpaque(false); window.setOpaque(false);
// See `set_transparent` for details on why we do this. // See `set_transparent` for details on why we do this.
@@ -945,8 +962,21 @@ impl WindowDelegate {
} }
pub fn surface_position(&self) -> PhysicalPosition<i32> { pub fn surface_position(&self) -> PhysicalPosition<i32> {
let content_rect = self.window().contentRectForFrameRect(self.window().frame()); // The calculation here is a bit awkward because we've gotta reconcile the
let logical = LogicalPosition::new(content_rect.origin.x, content_rect.origin.y); // different origins (Winit prefers top-left vs. NSWindow's bottom-left),
// and I couldn't find a built-in way to do so.
// The position of the window and the view, both in Winit screen coordinates.
let window_position = flip_window_screen_coordinates(self.window().frame());
let view_position = flip_window_screen_coordinates(
self.window().contentRectForFrameRect(self.window().frame()),
);
// And use that to convert the view position to window coordinates.
let surface_position =
NSPoint::new(view_position.x - window_position.x, view_position.y - window_position.y);
let logical = LogicalPosition::new(surface_position.x, surface_position.y);
logical.to_physical(self.scale_factor()) logical.to_physical(self.scale_factor())
} }
@@ -980,19 +1010,24 @@ impl WindowDelegate {
// we've set it up with `additionalSafeAreaInsets`. // we've set it up with `additionalSafeAreaInsets`.
unsafe { self.view().safeAreaInsets() } unsafe { self.view().safeAreaInsets() }
} else { } else {
let content_rect = self.window().contentRectForFrameRect(self.window().frame()); // If `safeAreaInsets` is not available, we'll have to do the calculation ourselves.
// Includes NSWindowStyleMask::FullSizeContentView
// Convert from window coordinates to view coordinates let window_rect = unsafe {
let safe_rect = unsafe { self.window().convertRectFromScreen(
self.view().convertRect_fromView(self.window().contentLayoutRect(), None) self.window().contentRectForFrameRect(self.window().frame()),
)
}; };
// This includes NSWindowStyleMask::FullSizeContentView.
let layout_rect = unsafe { self.window().contentLayoutRect() };
// Calculate the insets from window coordinates in AppKit's coordinate system.
NSEdgeInsets { NSEdgeInsets {
top: safe_rect.origin.y - content_rect.origin.y, top: (window_rect.size.height + window_rect.origin.y)
left: safe_rect.origin.x - content_rect.origin.x, - (layout_rect.size.height + layout_rect.origin.y),
bottom: (content_rect.size.height + content_rect.origin.x) left: layout_rect.origin.x - window_rect.origin.x,
- (safe_rect.size.height + safe_rect.origin.x), bottom: layout_rect.origin.y - window_rect.origin.y,
right: (content_rect.size.width + content_rect.origin.y) right: (window_rect.size.width + window_rect.origin.x)
- (safe_rect.size.width + safe_rect.origin.y), - (layout_rect.size.width + layout_rect.origin.x),
} }
}; };
let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right); let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right);

View File

@@ -1,55 +1,33 @@
use std::cell::Cell; use std::cell::RefCell;
use std::{fmt, mem}; use std::{fmt, mem};
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::event_loop::ActiveEventLoop;
/// A helper type for storing a reference to `ApplicationHandler`, allowing interior mutable access /// A helper type for storing a reference to `ApplicationHandler`, allowing interior mutable access
/// to it within the execution of a closure. /// to it within the execution of a closure.
#[derive(Default)] #[derive(Default)]
pub(crate) struct EventHandler { pub(crate) struct EventHandler {
state: Cell<State>, /// This can be in the following states:
} /// - Not registered by the event loop (None).
/// - Present (Some(handler)).
type InitClosure<'handler> = /// - Currently executing the handler / in use (RefCell borrowed).
Box<dyn FnOnce(&dyn ActiveEventLoop) -> Box<dyn ApplicationHandler + 'handler> + 'handler>; inner: RefCell<Option<&'static mut dyn ApplicationHandler>>,
#[derive(Default)]
enum State {
/// Not registered by the event loop.
#[default]
NotRegistered,
/// The event is registered by the event loop.
Registered(InitClosure<'static>),
/// The application has been initialized, and we're ready to handle events.
Ready(Box<dyn ApplicationHandler + 'static>),
/// Currently executing the handler.
CurrentlyExecuting,
/// The application has been terminated.
Terminated,
// TODO: Invalid state?
} }
impl fmt::Debug for EventHandler { impl fmt::Debug for EventHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = self.state.replace(State::CurrentlyExecuting); let state = match self.inner.try_borrow().as_deref() {
// NOTE: We're very careful not to panic inside the "critial" section here. Ok(Some(_)) => "<available>",
let string = match &state { Ok(None) => "<not set>",
State::NotRegistered => "<not registered>", Err(_) => "<in use>",
State::Registered(_) => "<registered>",
State::Ready(_) => "<ready>",
State::CurrentlyExecuting => "<currently executing>",
State::Terminated => "<terminated>",
}; };
self.state.set(state); f.debug_struct("EventHandler").field("state", &state).finish_non_exhaustive()
f.debug_struct("EventHandler").field("state", &string).finish_non_exhaustive()
} }
} }
impl EventHandler { impl EventHandler {
pub(crate) const fn new() -> Self { pub(crate) fn new() -> Self {
Self { state: Cell::new(State::NotRegistered) } Self { inner: RefCell::new(None) }
} }
/// Set the event loop handler for the duration of the given closure. /// Set the event loop handler for the duration of the given closure.
@@ -59,7 +37,7 @@ impl EventHandler {
/// from within the closure. /// from within the closure.
pub(crate) fn set<'handler, R>( pub(crate) fn set<'handler, R>(
&self, &self,
init_closure: InitClosure<'handler>, app: &'handler mut dyn ApplicationHandler,
closure: impl FnOnce() -> R, closure: impl FnOnce() -> R,
) -> R { ) -> R {
// SAFETY: We extend the lifetime of the handler here so that we can // SAFETY: We extend the lifetime of the handler here so that we can
@@ -68,9 +46,14 @@ impl EventHandler {
// This is sound, since we make sure to unset the handler again at the // This is sound, since we make sure to unset the handler again at the
// end of this function, and as such the lifetime isn't actually // end of this function, and as such the lifetime isn't actually
// extended beyond `'handler`. // extended beyond `'handler`.
let handler = unsafe { mem::transmute::<InitClosure<'handler>, InitClosure<'static>>(app) }; let handler = unsafe {
mem::transmute::<
&'handler mut dyn ApplicationHandler,
&'static mut dyn ApplicationHandler,
>(app)
};
match self.state.try_borrow_mut().as_deref_mut() { match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(_)) => { Ok(Some(_)) => {
unreachable!("tried to set handler while another was already set"); unreachable!("tried to set handler while another was already set");
}, },
@@ -86,7 +69,7 @@ impl EventHandler {
impl Drop for ClearOnDrop<'_> { impl Drop for ClearOnDrop<'_> {
fn drop(&mut self) { fn drop(&mut self) {
match self.0.state.try_borrow_mut().as_deref_mut() { match self.0.inner.try_borrow_mut().as_deref_mut() {
Ok(data @ Some(_)) => { Ok(data @ Some(_)) => {
*data = None; *data = None;
}, },
@@ -118,16 +101,11 @@ impl EventHandler {
// soundness. // soundness.
} }
fn init(&self) {}
fn terminate(&self) {}
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub(crate) fn in_use(&self) -> bool { pub(crate) fn in_use(&self) -> bool {
self.inner.try_borrow().is_err() self.inner.try_borrow().is_err()
} }
#[cfg(target_os = "macos")]
pub(crate) fn ready(&self) -> bool { pub(crate) fn ready(&self) -> bool {
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_))) matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
} }
@@ -141,7 +119,7 @@ impl EventHandler {
// //
// If the handler unwinds, the `RefMut` will ensure that the // If the handler unwinds, the `RefMut` will ensure that the
// handler is no longer borrowed. // handler is no longer borrowed.
callback(user_app); callback(*user_app);
}, },
Ok(None) => { Ok(None) => {
// `NSApplication`, our app state and this handler are all // `NSApplication`, our app state and this handler are all

View File

@@ -27,8 +27,9 @@ use super::window::WinitUIWindow;
use super::{ActiveEventLoop, EventLoopProxy}; use super::{ActiveEventLoop, EventLoopProxy};
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::dpi::PhysicalSize; use crate::dpi::PhysicalSize;
use crate::event::{Event, StartCause, SurfaceSizeWriter, WindowEvent}; use crate::event::{StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::ControlFlow; use crate::event_loop::ControlFlow;
use crate::window::WindowId;
macro_rules! bug { macro_rules! bug {
($($msg:tt)*) => { ($($msg:tt)*) => {
@@ -67,25 +68,9 @@ fn get_handler(mtm: MainThreadMarker) -> &'static EventHandler {
GLOBAL.get(mtm).get_or_init(EventHandler::new) GLOBAL.get(mtm).get_or_init(EventHandler::new)
} }
fn handle_event(mtm: MainThreadMarker, event: Event) {
let event_loop = &ActiveEventLoop { mtm };
get_handler(mtm).handle(|app| 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::UserWakeUp => app.proxy_wake_up(event_loop),
Event::Suspended => app.suspended(event_loop),
Event::Resumed => app.resumed(event_loop),
Event::CreateSurfaces => app.can_create_surfaces(event_loop),
Event::AboutToWait => app.about_to_wait(event_loop),
Event::LoopExiting => app.exiting(event_loop),
Event::MemoryWarning => app.memory_warning(event_loop),
})
}
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum EventWrapper { pub(crate) enum EventWrapper {
StaticEvent(Event), Window { window_id: WindowId, event: WindowEvent },
ScaleFactorChanged(ScaleFactorChanged), ScaleFactorChanged(ScaleFactorChanged),
} }
@@ -96,14 +81,9 @@ pub struct ScaleFactorChanged {
pub(super) scale_factor: f64, pub(super) scale_factor: f64,
} }
enum UserCallbackTransitionResult<'a> { impl EventWrapper {
Success { active_control_flow: ControlFlow, processing_redraws: bool },
ReentrancyPrevented { queued_events: &'a mut Vec<EventWrapper> },
}
impl Event {
fn is_redraw(&self) -> bool { fn is_redraw(&self) -> bool {
matches!(self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. }) matches!(self, Self::Window { event: WindowEvent::RedrawRequested, .. })
} }
} }
@@ -112,18 +92,12 @@ impl Event {
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"] #[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl { enum AppStateImpl {
Initial { Initial {
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>, queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
}, },
ProcessingEvents { ProcessingEvents {
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>, queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
active_control_flow: ControlFlow, active_control_flow: ControlFlow,
}, },
// special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback {
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
ProcessingRedraws { ProcessingRedraws {
active_control_flow: ControlFlow, active_control_flow: ControlFlow,
}, },
@@ -140,6 +114,7 @@ pub(crate) struct AppState {
control_flow: ControlFlow, control_flow: ControlFlow,
waker: EventLoopWaker, waker: EventLoopWaker,
event_loop_proxy: Arc<EventLoopProxy>, event_loop_proxy: Arc<EventLoopProxy>,
queued_events: Vec<EventWrapper>,
} }
impl AppState { impl AppState {
@@ -158,13 +133,11 @@ impl AppState {
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) { fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() }); let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
**guard = Some(AppState { **guard = Some(AppState {
app_state: Some(AppStateImpl::Initial { app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }),
queued_events: Vec::new(),
queued_gpu_redraws: HashSet::new(),
}),
control_flow: ControlFlow::default(), control_flow: ControlFlow::default(),
waker, waker,
event_loop_proxy: Arc::new(EventLoopProxy::new()), event_loop_proxy: Arc::new(EventLoopProxy::new()),
queued_events: Vec::new(),
}); });
} }
init_guard(&mut guard); init_guard(&mut guard);
@@ -217,48 +190,34 @@ impl AppState {
matches!(self.state(), AppStateImpl::Terminated) matches!(self.state(), AppStateImpl::Terminated)
} }
fn did_finish_launching_transition(&mut self) -> Vec<EventWrapper> { fn did_finish_launching_transition(&mut self) {
let (events, queued_gpu_redraws) = match self.take_state() { let queued_gpu_redraws = match self.take_state() {
AppStateImpl::Initial { queued_events, queued_gpu_redraws } => { AppStateImpl::Initial { queued_gpu_redraws } => queued_gpu_redraws,
(queued_events, queued_gpu_redraws)
},
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingEvents { self.set_state(AppStateImpl::ProcessingEvents {
active_control_flow: self.control_flow, active_control_flow: self.control_flow,
queued_gpu_redraws, queued_gpu_redraws,
}); });
events
} }
fn wakeup_transition(&mut self) -> Option<EventWrapper> { fn wakeup_transition(&mut self) -> Option<StartCause> {
// before `AppState::did_finish_launching` is called, pretend there is no running // before `AppState::did_finish_launching` is called, pretend there is no running
// event loop. // event loop.
if !self.has_launched() || self.has_terminated() { if !self.has_launched() || self.has_terminated() {
return None; return None;
} }
let event = match (self.control_flow, self.take_state()) { let start_cause = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished) => { (ControlFlow::Poll, AppStateImpl::PollFinished) => StartCause::Poll,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll))
},
(ControlFlow::Wait, AppStateImpl::Waiting { start }) => { (ControlFlow::Wait, AppStateImpl::Waiting { start }) => {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { StartCause::WaitCancelled { start, requested_resume: None }
start,
requested_resume: None,
}))
}, },
(ControlFlow::WaitUntil(requested_resume), AppStateImpl::Waiting { start }) => { (ControlFlow::WaitUntil(requested_resume), AppStateImpl::Waiting { start }) => {
if Instant::now() >= requested_resume { if Instant::now() >= requested_resume {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached { StartCause::ResumeTimeReached { start, requested_resume }
start,
requested_resume,
}))
} else { } else {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { StartCause::WaitCancelled { start, requested_resume: Some(requested_resume) }
start,
requested_resume: Some(requested_resume),
}))
} }
}, },
s => bug!("`EventHandler` unexpectedly woke up {:?}", s), s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
@@ -268,55 +227,7 @@ impl AppState {
queued_gpu_redraws: Default::default(), queued_gpu_redraws: Default::default(),
active_control_flow: self.control_flow, active_control_flow: self.control_flow,
}); });
Some(event) Some(start_cause)
}
fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> {
// If we're not able to process an event due to recursion or `Init` not having been sent out
// yet, then queue the events up.
match self.state_mut() {
&mut AppStateImpl::Initial { ref mut queued_events, .. }
| &mut AppStateImpl::InUserCallback { ref mut queued_events, .. } => {
// A lifetime cast: early returns are not currently handled well with NLL, but
// polonius handles them well. This transmute is a safe workaround.
return unsafe {
mem::transmute::<
UserCallbackTransitionResult<'_>,
UserCallbackTransitionResult<'_>,
>(UserCallbackTransitionResult::ReentrancyPrevented {
queued_events,
})
};
},
&mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {},
s @ &mut AppStateImpl::PollFinished { .. }
| s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::Terminated => {
bug!("unexpected attempted to process an event {:?}", s)
},
}
let (queued_gpu_redraws, active_control_flow, processing_redraws) = match self.take_state()
{
AppStateImpl::Initial { .. } | AppStateImpl::InUserCallback { .. } => unreachable!(),
AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow } => {
(queued_gpu_redraws, active_control_flow, false)
},
AppStateImpl::ProcessingRedraws { active_control_flow } => {
(Default::default(), active_control_flow, true)
},
AppStateImpl::PollFinished { .. }
| AppStateImpl::Waiting { .. }
| AppStateImpl::Terminated => unreachable!(),
};
self.set_state(AppStateImpl::InUserCallback {
queued_events: Vec::new(),
queued_gpu_redraws,
});
UserCallbackTransitionResult::Success { active_control_flow, processing_redraws }
} }
fn main_events_cleared_transition(&mut self) -> HashSet<Retained<WinitUIWindow>> { fn main_events_cleared_transition(&mut self) -> HashSet<Retained<WinitUIWindow>> {
@@ -372,7 +283,7 @@ impl AppState {
fn terminated_transition(&mut self) { fn terminated_transition(&mut self) {
match self.replace_state(AppStateImpl::Terminated) { match self.replace_state(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { .. } => {}, AppStateImpl::ProcessingEvents { .. } => {},
s => bug!("`LoopExiting` happened while not processing events {:?}", s), s => bug!("terminated while not processing events {:?}", s),
} }
} }
@@ -393,8 +304,7 @@ pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained<W
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
match this.state_mut() { match this.state_mut() {
&mut AppStateImpl::Initial { ref mut queued_gpu_redraws, .. } &mut AppStateImpl::Initial { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. } | &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. } => {
| &mut AppStateImpl::InUserCallback { ref mut queued_gpu_redraws, .. } => {
let _ = queued_gpu_redraws.insert(window); let _ = queued_gpu_redraws.insert(window);
}, },
s @ &mut AppStateImpl::ProcessingRedraws { .. } s @ &mut AppStateImpl::ProcessingRedraws { .. }
@@ -418,27 +328,24 @@ pub fn did_finish_launching(mtm: MainThreadMarker) {
// have to drop RefMut because the window setup code below can trigger new events // have to drop RefMut because the window setup code below can trigger new events
drop(this); drop(this);
let events = AppState::get_mut(mtm).did_finish_launching_transition(); AppState::get_mut(mtm).did_finish_launching_transition();
let events = [ get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, StartCause::Init));
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Init)), get_handler(mtm).handle(|app| app.can_create_surfaces(&ActiveEventLoop { mtm }));
EventWrapper::StaticEvent(Event::CreateSurfaces), handle_nonuser_events(mtm, []);
]
.into_iter()
.chain(events);
handle_nonuser_events(mtm, events);
} }
// AppState::did_finish_launching handles the special transition `Init` // AppState::did_finish_launching handles the special transition `Init`
pub fn handle_wakeup_transition(mtm: MainThreadMarker) { pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
let wakeup_event = match this.wakeup_transition() { let cause = match this.wakeup_transition() {
None => return, None => return,
Some(wakeup_event) => wakeup_event, Some(cause) => cause,
}; };
drop(this); drop(this);
handle_nonuser_event(mtm, wakeup_event) get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, cause));
handle_nonuser_events(mtm, []);
} }
pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) { pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) {
@@ -454,132 +361,75 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
return; return;
} }
let (active_control_flow, processing_redraws) = match this.try_user_callback_transition() { if !get_handler(mtm).ready() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { // Prevent re-entrancy; queue the events up for once we're done handling the event instead.
queued_events.extend(events); this.queued_events.extend(events);
return; return;
}, }
UserCallbackTransitionResult::Success { active_control_flow, processing_redraws } => {
(active_control_flow, processing_redraws) let processing_redraws = matches!(this.state(), AppStateImpl::ProcessingRedraws { .. });
},
};
drop(this); drop(this);
for wrapper in events { for event in events {
match wrapper { if !processing_redraws && event.is_redraw() {
EventWrapper::StaticEvent(event) => { tracing::info!("processing `RedrawRequested` during the main event loop");
if !processing_redraws && event.is_redraw() { } else if processing_redraws && !event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop"); tracing::warn!(
} else if processing_redraws && !event.is_redraw() { "processing non `RedrawRequested` event after the main event loop: {:#?}",
tracing::warn!( event
"processing non `RedrawRequested` event after the main event loop: {:#?}", );
event
);
}
handle_event(mtm, event)
},
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
} }
handle_wrapped_event(mtm, event)
} }
loop { loop {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() { let queued_events = mem::take(&mut this.queued_events);
&mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
mem::take(queued_events)
},
s => bug!("unexpected state {:?}", s),
};
if queued_events.is_empty() { if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_gpu_redraws
},
_ => unreachable!(),
};
this.app_state = Some(if processing_redraws {
bug_assert!(
queued_gpu_redraws.is_empty(),
"redraw queued while processing redraws"
);
AppStateImpl::ProcessingRedraws { active_control_flow }
} else {
AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow }
});
break; break;
} }
drop(this); drop(this);
for wrapper in queued_events { for event in queued_events {
match wrapper { if !processing_redraws && event.is_redraw() {
EventWrapper::StaticEvent(event) => { tracing::info!("processing `RedrawRequested` during the main event loop");
if !processing_redraws && event.is_redraw() { } else if processing_redraws && !event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop"); tracing::warn!(
} else if processing_redraws && !event.is_redraw() { "processing non-`RedrawRequested` event after the main event loop: {:#?}",
tracing::warn!( event
"processing non-`RedrawRequested` event after the main event loop: \ );
{:#?}",
event
);
}
handle_event(mtm, event)
},
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
} }
handle_wrapped_event(mtm, event);
} }
} }
} }
fn handle_user_events(mtm: MainThreadMarker) { fn handle_user_events(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm); let this = AppState::get_mut(mtm);
let (active_control_flow, processing_redraws) = match this.try_user_callback_transition() { if matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }) {
UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
bug!("unexpected attempted to process an event")
},
UserCallbackTransitionResult::Success { active_control_flow, processing_redraws } => {
(active_control_flow, processing_redraws)
},
};
if processing_redraws {
bug!("user events attempted to be sent out while `ProcessingRedraws`"); bug!("user events attempted to be sent out while `ProcessingRedraws`");
} }
let event_loop_proxy = this.event_loop_proxy().clone(); let event_loop_proxy = this.event_loop_proxy().clone();
drop(this); drop(this);
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) { if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
handle_event(mtm, Event::UserWakeUp); get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
} }
loop { loop {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() { let queued_events = mem::take(&mut this.queued_events);
&mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
mem::take(queued_events)
},
s => bug!("unexpected state {:?}", s),
};
if queued_events.is_empty() { if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_gpu_redraws
},
_ => unreachable!(),
};
this.app_state =
Some(AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow });
break; break;
} }
drop(this); drop(this);
for wrapper in queued_events { for event in queued_events {
match wrapper { handle_wrapped_event(mtm, event);
EventWrapper::StaticEvent(event) => handle_event(mtm, event),
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
}
} }
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) { if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
handle_event(mtm, Event::UserWakeUp); get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
} }
} }
} }
@@ -597,10 +447,10 @@ pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, o
let ptr: *const WinitUIWindow = ptr.cast(); let ptr: *const WinitUIWindow = ptr.cast();
&*ptr &*ptr
}; };
events.push(EventWrapper::StaticEvent(Event::WindowEvent { events.push(EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::Occluded(occluded), event: WindowEvent::Occluded(occluded),
})); });
} }
} }
handle_nonuser_events(mtm, events); handle_nonuser_events(mtm, events);
@@ -623,23 +473,37 @@ pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let redraw_events: Vec<EventWrapper> = this let redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition() .main_events_cleared_transition()
.into_iter() .into_iter()
.map(|window| { .map(|window| EventWrapper::Window {
EventWrapper::StaticEvent(Event::WindowEvent { window_id: window.id(),
window_id: window.id(), event: WindowEvent::RedrawRequested,
event: WindowEvent::RedrawRequested,
})
}) })
.collect(); .collect();
drop(this); drop(this);
handle_nonuser_events(mtm, redraw_events); handle_nonuser_events(mtm, redraw_events);
handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait)); get_handler(mtm).handle(|app| app.about_to_wait(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
} }
pub fn handle_events_cleared(mtm: MainThreadMarker) { pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition(); AppState::get_mut(mtm).events_cleared_transition();
} }
pub(crate) fn handle_resumed(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.resumed(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn handle_suspended(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.suspended(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn handle_memory_warning(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.memory_warning(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn terminated(application: &UIApplication) { pub(crate) fn terminated(application: &UIApplication) {
let mtm = MainThreadMarker::from(application); let mtm = MainThreadMarker::from(application);
@@ -653,10 +517,10 @@ pub(crate) fn terminated(application: &UIApplication) {
let ptr: *const WinitUIWindow = ptr.cast(); let ptr: *const WinitUIWindow = ptr.cast();
&*ptr &*ptr
}; };
events.push(EventWrapper::StaticEvent(Event::WindowEvent { events.push(EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::Destroyed, event: WindowEvent::Destroyed,
})); });
} }
} }
handle_nonuser_events(mtm, events); handle_nonuser_events(mtm, events);
@@ -665,20 +529,26 @@ pub(crate) fn terminated(application: &UIApplication) {
this.terminated_transition(); this.terminated_transition();
drop(this); drop(this);
handle_event(mtm, Event::LoopExiting) get_handler(mtm).handle(|app| app.exiting(&ActiveEventLoop { mtm }));
}
fn handle_wrapped_event(mtm: MainThreadMarker, event: EventWrapper) {
match event {
EventWrapper::Window { window_id, event } => get_handler(mtm)
.handle(|app| app.window_event(&ActiveEventLoop { mtm }, window_id, event)),
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
}
} }
fn handle_hidpi_proxy(mtm: MainThreadMarker, event: ScaleFactorChanged) { fn handle_hidpi_proxy(mtm: MainThreadMarker, event: ScaleFactorChanged) {
let ScaleFactorChanged { suggested_size, scale_factor, window } = event; let ScaleFactorChanged { suggested_size, scale_factor, window } = event;
let new_surface_size = Arc::new(Mutex::new(suggested_size)); let new_surface_size = Arc::new(Mutex::new(suggested_size));
let event = Event::WindowEvent { get_handler(mtm).handle(|app| {
window_id: window.id(), app.window_event(&ActiveEventLoop { mtm }, window.id(), WindowEvent::ScaleFactorChanged {
event: WindowEvent::ScaleFactorChanged {
scale_factor, scale_factor,
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)), surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)),
}, });
}; });
handle_event(mtm, event);
let (view, screen_frame) = get_view_and_screen_frame(&window); let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_surface_size.lock().unwrap(); let physical_size = *new_surface_size.lock().unwrap();
drop(new_surface_size); drop(new_surface_size);

View File

@@ -23,11 +23,10 @@ use objc2_ui_kit::{
use rwh_06::HasDisplayHandle; use rwh_06::HasDisplayHandle;
use super::super::notification_center::create_observer; use super::super::notification_center::create_observer;
use super::app_state::{send_occluded_event_for_all_windows, AppState, EventWrapper}; use super::app_state::{send_occluded_event_for_all_windows, AppState};
use super::{app_state, monitor, MonitorHandle}; use super::{app_state, monitor, MonitorHandle};
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError, RequestError}; use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event::Event;
use crate::event_loop::{ use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider, EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
@@ -174,17 +173,13 @@ impl EventLoop {
&center, &center,
// `applicationDidBecomeActive:` // `applicationDidBecomeActive:`
unsafe { UIApplicationDidBecomeActiveNotification }, unsafe { UIApplicationDidBecomeActiveNotification },
move |_| { move |_| app_state::handle_resumed(mtm),
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed));
},
); );
let _will_resign_active_observer = create_observer( let _will_resign_active_observer = create_observer(
&center, &center,
// `applicationWillResignActive:` // `applicationWillResignActive:`
unsafe { UIApplicationWillResignActiveNotification }, unsafe { UIApplicationWillResignActiveNotification },
move |_| { move |_| app_state::handle_suspended(mtm),
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended));
},
); );
let _will_enter_foreground_observer = create_observer( let _will_enter_foreground_observer = create_observer(
&center, &center,
@@ -231,12 +226,7 @@ impl EventLoop {
&center, &center,
// `applicationDidReceiveMemoryWarning:` // `applicationDidReceiveMemoryWarning:`
unsafe { UIApplicationDidReceiveMemoryWarningNotification }, unsafe { UIApplicationDidReceiveMemoryWarningNotification },
move |_| { move |_| app_state::handle_memory_warning(mtm),
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::MemoryWarning),
);
},
); );
Ok(EventLoop { Ok(EventLoop {

View File

@@ -101,13 +101,20 @@ impl Clone for MonitorHandle {
impl hash::Hash for MonitorHandle { impl hash::Hash for MonitorHandle {
fn hash<H: hash::Hasher>(&self, state: &mut H) { fn hash<H: hash::Hasher>(&self, state: &mut H) {
(self as *const Self).hash(state); // SAFETY: Only getting the pointer.
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)).hash(state);
} }
} }
impl PartialEq for MonitorHandle { impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
ptr::eq(self, other) // SAFETY: Only getting the pointer.
let mtm = unsafe { MainThreadMarker::new_unchecked() };
ptr::eq(
Retained::as_ptr(self.ui_screen.get(mtm)),
Retained::as_ptr(other.ui_screen.get(mtm)),
)
} }
} }
@@ -121,8 +128,10 @@ impl PartialOrd for MonitorHandle {
impl Ord for MonitorHandle { impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering { fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// SAFETY: Only getting the pointer.
// TODO: Make a better ordering // TODO: Make a better ordering
(self as *const Self).cmp(&(other as *const Self)) let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)).cmp(&Retained::as_ptr(other.ui_screen.get(mtm)))
} }
} }
@@ -240,3 +249,27 @@ pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
#[allow(deprecated)] #[allow(deprecated)]
UIScreen::screens(mtm).into_iter().map(MonitorHandle::new).collect() UIScreen::screens(mtm).into_iter().map(MonitorHandle::new).collect()
} }
#[cfg(test)]
mod tests {
use objc2_foundation::NSSet;
use super::*;
// Test that UIScreen pointer comparisons are correct.
#[test]
#[allow(deprecated)]
fn screen_comparisons() {
// Test code, doesn't matter that it's not thread safe
let mtm = unsafe { MainThreadMarker::new_unchecked() };
assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm)));
let main = UIScreen::mainScreen(mtm);
assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(screen, &*main)));
assert!(unsafe {
NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm))
});
}
}

View File

@@ -17,8 +17,8 @@ use super::app_state::{self, EventWrapper};
use super::window::WinitUIWindow; use super::window::WinitUIWindow;
use crate::dpi::PhysicalPosition; use crate::dpi::PhysicalPosition;
use crate::event::{ use crate::event::{
ButtonSource, ElementState, Event, FingerId, Force, KeyEvent, PointerKind, PointerSource, ButtonSource, ElementState, FingerId, Force, KeyEvent, PointerKind, PointerSource, TouchPhase,
TouchPhase, WindowEvent, WindowEvent,
}; };
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey}; use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey};
use crate::platform_impl::KeyEventExtra; use crate::platform_impl::KeyEventExtra;
@@ -60,10 +60,10 @@ declare_class!(
let window = self.window().unwrap(); let window = self.window().unwrap();
app_state::handle_nonuser_event( app_state::handle_nonuser_event(
mtm, mtm,
EventWrapper::StaticEvent(Event::WindowEvent { EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::RedrawRequested, event: WindowEvent::RedrawRequested,
}), },
); );
let _: () = unsafe { msg_send![super(self), drawRect: rect] }; let _: () = unsafe { msg_send![super(self), drawRect: rect] };
} }
@@ -84,10 +84,10 @@ declare_class!(
let window = self.window().unwrap(); let window = self.window().unwrap();
app_state::handle_nonuser_event( app_state::handle_nonuser_event(
mtm, mtm,
EventWrapper::StaticEvent(Event::WindowEvent { EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::SurfaceResized(size), event: WindowEvent::SurfaceResized(size),
}), },
); );
} }
@@ -131,12 +131,11 @@ declare_class!(
suggested_size: size.to_physical(scale_factor), suggested_size: size.to_physical(scale_factor),
}, },
)) ))
.chain(std::iter::once(EventWrapper::StaticEvent( .chain(std::iter::once(EventWrapper::Window {
Event::WindowEvent {
window_id, window_id,
event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)), event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)),
}, },
))), )),
); );
} }
@@ -192,14 +191,14 @@ declare_class!(
state => panic!("unexpected recognizer state: {state:?}"), state => panic!("unexpected recognizer state: {state:?}"),
}; };
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { let gesture_event = EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::PinchGesture { event: WindowEvent::PinchGesture {
device_id: None, device_id: None,
delta: delta as f64, delta: delta as f64,
phase, phase,
}, },
}); };
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event); app_state::handle_nonuser_event(mtm, gesture_event);
@@ -210,12 +209,12 @@ declare_class!(
let window = self.window().unwrap(); let window = self.window().unwrap();
if recognizer.state() == UIGestureRecognizerState::Ended { if recognizer.state() == UIGestureRecognizerState::Ended {
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { let gesture_event = EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::DoubleTapGesture { event: WindowEvent::DoubleTapGesture {
device_id: None, device_id: None,
}, },
}); };
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event); app_state::handle_nonuser_event(mtm, gesture_event);
@@ -252,14 +251,14 @@ declare_class!(
}; };
// Make delta negative to match macos, convert to degrees // Make delta negative to match macos, convert to degrees
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { let gesture_event = EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::RotationGesture { event: WindowEvent::RotationGesture {
device_id: None, device_id: None,
delta: -delta.to_degrees() as _, delta: -delta.to_degrees() as _,
phase, phase,
}, },
}); };
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event); app_state::handle_nonuser_event(mtm, gesture_event);
@@ -303,14 +302,14 @@ declare_class!(
}; };
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { let gesture_event = EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::PanGesture { event: WindowEvent::PanGesture {
device_id: None, device_id: None,
delta: PhysicalPosition::new(dx as _, dy as _), delta: PhysicalPosition::new(dx as _, dy as _),
phase, phase,
}, },
}); };
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event); app_state::handle_nonuser_event(mtm, gesture_event);
@@ -538,7 +537,7 @@ impl WinitView {
} }
}; };
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { touch_events.push(EventWrapper::Window {
window_id, window_id,
event: WindowEvent::PointerEntered { event: WindowEvent::PointerEntered {
device_id: None, device_id: None,
@@ -550,8 +549,8 @@ impl WinitView {
PointerKind::Touch(finger_id) PointerKind::Touch(finger_id)
}, },
}, },
})); });
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { touch_events.push(EventWrapper::Window {
window_id, window_id,
event: WindowEvent::PointerButton { event: WindowEvent::PointerButton {
device_id: None, device_id: None,
@@ -564,7 +563,7 @@ impl WinitView {
ButtonSource::Touch { finger_id, force } ButtonSource::Touch { finger_id, force }
}, },
}, },
})); });
}, },
UITouchPhase::Moved => { UITouchPhase::Moved => {
let (primary, source) = if let UITouchType::Pencil = touch_type { let (primary, source) = if let UITouchType::Pencil = touch_type {
@@ -576,7 +575,7 @@ impl WinitView {
}) })
}; };
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { touch_events.push(EventWrapper::Window {
window_id, window_id,
event: WindowEvent::PointerMoved { event: WindowEvent::PointerMoved {
device_id: None, device_id: None,
@@ -584,7 +583,7 @@ impl WinitView {
position, position,
source, source,
}, },
})); });
}, },
// 2 is UITouchPhase::Stationary and is not expected here // 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended | UITouchPhase::Cancelled => { UITouchPhase::Ended | UITouchPhase::Cancelled => {
@@ -600,7 +599,7 @@ impl WinitView {
}; };
if let UITouchPhase::Ended = phase { if let UITouchPhase::Ended = phase {
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { touch_events.push(EventWrapper::Window {
window_id, window_id,
event: WindowEvent::PointerButton { event: WindowEvent::PointerButton {
device_id: None, device_id: None,
@@ -613,10 +612,10 @@ impl WinitView {
ButtonSource::Touch { finger_id, force } ButtonSource::Touch { finger_id, force }
}, },
}, },
})); });
} }
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { touch_events.push(EventWrapper::Window {
window_id, window_id,
event: WindowEvent::PointerLeft { event: WindowEvent::PointerLeft {
device_id: None, device_id: None,
@@ -628,7 +627,7 @@ impl WinitView {
PointerKind::Touch(finger_id) PointerKind::Touch(finger_id)
}, },
}, },
})); });
}, },
_ => panic!("unexpected touch phase: {phase:?}"), _ => panic!("unexpected touch phase: {phase:?}"),
} }
@@ -647,29 +646,25 @@ impl WinitView {
text.to_string().chars().flat_map(|c| { text.to_string().chars().flat_map(|c| {
let text = smol_str::SmolStr::from_iter([c]); let text = smol_str::SmolStr::from_iter([c]);
// Emit both press and release events // Emit both press and release events
[ElementState::Pressed, ElementState::Released].map(|state| { [ElementState::Pressed, ElementState::Released].map(|state| EventWrapper::Window {
EventWrapper::StaticEvent(Event::WindowEvent { window_id,
window_id, event: WindowEvent::KeyboardInput {
event: WindowEvent::KeyboardInput { device_id: None,
event: KeyEvent { event: KeyEvent {
text: if state == ElementState::Pressed { text: if state == ElementState::Pressed {
Some(text.clone()) Some(text.clone())
} else { } else {
None None
},
state,
location: KeyLocation::Standard,
repeat: false,
logical_key: Key::Character(text.clone()),
physical_key: PhysicalKey::Unidentified(
NativeKeyCode::Unidentified,
),
platform_specific: KeyEventExtra {},
}, },
is_synthetic: false, state,
device_id: None, location: KeyLocation::Standard,
repeat: false,
logical_key: Key::Character(text.clone()),
physical_key: PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
platform_specific: KeyEventExtra {},
}, },
}) is_synthetic: false,
},
}) })
}), }),
); );
@@ -681,23 +676,21 @@ impl WinitView {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events( app_state::handle_nonuser_events(
mtm, mtm,
[ElementState::Pressed, ElementState::Released].map(|state| { [ElementState::Pressed, ElementState::Released].map(|state| EventWrapper::Window {
EventWrapper::StaticEvent(Event::WindowEvent { window_id,
window_id, event: WindowEvent::KeyboardInput {
event: WindowEvent::KeyboardInput { device_id: None,
device_id: None, event: KeyEvent {
event: KeyEvent { state,
state, logical_key: Key::Named(NamedKey::Backspace),
logical_key: Key::Named(NamedKey::Backspace), physical_key: PhysicalKey::Code(KeyCode::Backspace),
physical_key: PhysicalKey::Code(KeyCode::Backspace), platform_specific: KeyEventExtra {},
platform_specific: KeyEventExtra {}, repeat: false,
repeat: false, location: KeyLocation::Standard,
location: KeyLocation::Standard, text: None,
text: None,
},
is_synthetic: false,
}, },
}) is_synthetic: false,
},
}), }),
); );
} }

View File

@@ -23,7 +23,7 @@ use crate::dpi::{
Position, Size, Position, Size,
}; };
use crate::error::{NotSupportedError, RequestError}; use crate::error::{NotSupportedError, RequestError};
use crate::event::{Event, WindowEvent}; use crate::event::WindowEvent;
use crate::icon::Icon; use crate::icon::Icon;
use crate::monitor::MonitorHandle as CoreMonitorHandle; use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations}; use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
@@ -51,10 +51,10 @@ declare_class!(
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event( app_state::handle_nonuser_event(
mtm, mtm,
EventWrapper::StaticEvent(Event::WindowEvent { EventWrapper::Window {
window_id: self.id(), window_id: self.id(),
event: WindowEvent::Focused(true), event: WindowEvent::Focused(true),
}), },
); );
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] }; let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
} }
@@ -64,10 +64,10 @@ declare_class!(
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event( app_state::handle_nonuser_event(
mtm, mtm,
EventWrapper::StaticEvent(Event::WindowEvent { EventWrapper::Window {
window_id: self.id(), window_id: self.id(),
event: WindowEvent::Focused(false), event: WindowEvent::Focused(false),
}), },
); );
let _: () = unsafe { msg_send![super(self), resignKeyWindow] }; let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
} }

View File

@@ -360,18 +360,15 @@ impl EventLoop {
} }
} }
pub fn run<A: ApplicationHandler>( pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
self, x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app))
init_closure: impl FnOnce(&dyn ActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(init_closure))
} }
pub fn run_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
init_closure: impl FnOnce(&dyn ActiveEventLoop) -> A, app: A,
) -> Result<(), EventLoopError> { ) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(init_closure)) x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app_on_demand(app))
} }
pub fn pump_app_events<A: ApplicationHandler>( pub fn pump_app_events<A: ApplicationHandler>(

View File

@@ -146,21 +146,15 @@ impl EventLoop {
Ok(event_loop) Ok(event_loop)
} }
pub fn run<A: ApplicationHandler>( pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
mut self, self.run_app_on_demand(app)
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
self.run_on_demand(init_closure)
} }
pub fn run_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A, mut app: A,
) -> Result<(), EventLoopError> { ) -> Result<(), EventLoopError> {
self.active_event_loop.clear_exit(); self.active_event_loop.clear_exit();
let mut app = init_closure(&self.active_event_loop);
let exit = loop { let exit = loop {
match self.pump_app_events(None, &mut app) { match self.pump_app_events(None, &mut app) {
PumpStatus::Exit(0) => { PumpStatus::Exit(0) => {
@@ -204,6 +198,8 @@ impl EventLoop {
if let Some(code) = self.exit_code() { if let Some(code) = self.exit_code() {
self.loop_running = false; self.loop_running = false;
app.exiting(&self.active_event_loop);
PumpStatus::Exit(code) PumpStatus::Exit(code)
} else { } else {
PumpStatus::Continue PumpStatus::Continue

View File

@@ -373,21 +373,15 @@ impl EventLoop {
&self.event_processor.target &self.event_processor.target
} }
pub fn run<A: ApplicationHandler>( pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
mut self, self.run_app_on_demand(app)
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
self.run_on_demand(init_closure)
} }
pub fn run_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A, mut app: A,
) -> Result<(), EventLoopError> { ) -> Result<(), EventLoopError> {
self.event_processor.target.clear_exit(); self.event_processor.target.clear_exit();
let mut app = init_closure(&self.event_processor.target);
let exit = loop { let exit = loop {
match self.pump_app_events(None, &mut app) { match self.pump_app_events(None, &mut app) {
PumpStatus::Exit(0) => { PumpStatus::Exit(0) => {
@@ -435,6 +429,8 @@ impl EventLoop {
if let Some(code) = self.exit_code() { if let Some(code) = self.exit_code() {
self.loop_running = false; self.loop_running = false;
app.exiting(self.window_target());
PumpStatus::Exit(code) PumpStatus::Exit(code)
} else { } else {
PumpStatus::Continue PumpStatus::Continue

View File

@@ -483,11 +483,7 @@ impl EventLoop {
} }
} }
pub fn run<A: ApplicationHandler>( pub fn run_app<A: ApplicationHandler>(mut self, mut app: A) -> Result<(), EventLoopError> {
mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
let mut app = init_closure(&self.window_target);
let mut start_cause = StartCause::Init; let mut start_cause = StartCause::Init;
loop { loop {
app.new_events(&self.window_target, start_cause); app.new_events(&self.window_target, start_cause);
@@ -658,6 +654,8 @@ impl EventLoop {
} }
} }
app.exiting(&self.window_target);
Ok(()) Ok(())
} }

View File

@@ -96,6 +96,7 @@ fn handle_event<A: ApplicationHandler>(app: &mut A, target: &ActiveEventLoop, ev
Event::Resumed => app.resumed(target), Event::Resumed => app.resumed(target),
Event::CreateSurfaces => app.can_create_surfaces(target), Event::CreateSurfaces => app.can_create_surfaces(target),
Event::AboutToWait => app.about_to_wait(target), Event::AboutToWait => app.about_to_wait(target),
Event::LoopExiting => app.exiting(target),
Event::MemoryWarning => app.memory_warning(target), Event::MemoryWarning => app.memory_warning(target),
} }
} }

View File

@@ -609,7 +609,7 @@ impl Shared {
self.apply_control_flow(); self.apply_control_flow();
// We don't call `handle_loop_destroyed` here because we don't need to // We don't call `handle_loop_destroyed` here because we don't need to
// perform cleanup when the Web browser is going to destroy the page. // perform cleanup when the Web browser is going to destroy the page.
todo!("drop the application handler"); self.handle_event(Event::LoopExiting);
} }
// handle_event takes in events and either queues them or applies a callback // handle_event takes in events and either queues them or applies a callback
@@ -713,7 +713,7 @@ impl Shared {
} }
fn handle_loop_destroyed(&self) { fn handle_loop_destroyed(&self) {
todo!("drop the application handler"); self.handle_event(Event::LoopExiting);
let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut()); let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut());
*self.0.page_transition_event_handle.borrow_mut() = None; *self.0.page_transition_event_handle.borrow_mut() = None;
*self.0.on_mouse_move.borrow_mut() = None; *self.0.on_mouse_move.borrow_mut() = None;

View File

@@ -220,21 +220,15 @@ impl EventLoop {
&self.window_target &self.window_target
} }
pub fn run<A: ApplicationHandler>( pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
mut self, self.run_app_on_demand(app)
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A,
) -> Result<(), EventLoopError> {
self.run_on_demand(init_closure)
} }
pub fn run_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
init_closure: impl FnOnce(&dyn RootActiveEventLoop) -> A, mut app: A,
) -> Result<(), EventLoopError> { ) -> Result<(), EventLoopError> {
self.window_target.clear_exit(); self.window_target.clear_exit();
let mut app = init_closure(&self.window_target);
{ {
let runner = &self.window_target.runner_shared; let runner = &self.window_target.runner_shared;
@@ -256,6 +250,7 @@ impl EventLoop {
Event::Resumed => app.resumed(event_loop_windows_ref), Event::Resumed => app.resumed(event_loop_windows_ref),
Event::CreateSurfaces => app.can_create_surfaces(event_loop_windows_ref), Event::CreateSurfaces => app.can_create_surfaces(event_loop_windows_ref),
Event::AboutToWait => app.about_to_wait(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), Event::MemoryWarning => app.memory_warning(event_loop_windows_ref),
}); });
} }
@@ -322,6 +317,7 @@ impl EventLoop {
Event::Resumed => app.resumed(event_loop_windows_ref), Event::Resumed => app.resumed(event_loop_windows_ref),
Event::CreateSurfaces => app.can_create_surfaces(event_loop_windows_ref), Event::CreateSurfaces => app.can_create_surfaces(event_loop_windows_ref),
Event::AboutToWait => app.about_to_wait(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), Event::MemoryWarning => app.memory_warning(event_loop_windows_ref),
}); });
@@ -352,10 +348,8 @@ impl EventLoop {
PumpStatus::Continue PumpStatus::Continue
}; };
// We wait until we've checked for an exit status before clearing the application callback, // We wait until we've checked for an exit status before clearing the
// in case any of the methods above ends up triggering an event. // application callback, in case we need to dispatch a LoopExiting event
//
// This drops the user's `ApplicationHandler`.
// //
// # Safety // # Safety
// This pairs up with our call to `runner.set_event_handler` and ensures // This pairs up with our call to `runner.set_event_handler` and ensures

View File

@@ -248,8 +248,9 @@ impl EventLoopRunner {
} }
} }
/// Dispatch control flow events (`new_events`, `about_to_wait`) as necessary to bring the /// Dispatch control flow events (`NewEvents`, `AboutToWait`, and
/// internal `RunnerState` to the new runner state. /// `LoopExiting`) as necessary to bring the internal `RunnerState` to the
/// new runner state.
/// ///
/// The state transitions are defined as follows: /// The state transitions are defined as follows:
/// ///
@@ -293,6 +294,7 @@ impl EventLoopRunner {
self.call_new_events(true); self.call_new_events(true);
self.call_event_handler(Event::AboutToWait); self.call_event_handler(Event::AboutToWait);
self.last_events_cleared.set(Instant::now()); self.last_events_cleared.set(Instant::now());
self.call_event_handler(Event::LoopExiting);
}, },
(_, Uninitialized) => panic!("cannot move state to Uninitialized"), (_, Uninitialized) => panic!("cannot move state to Uninitialized"),
@@ -300,7 +302,9 @@ impl EventLoopRunner {
(Idle, HandlingMainEvents) => { (Idle, HandlingMainEvents) => {
self.call_new_events(false); self.call_new_events(false);
}, },
(Idle, Destroyed) => {}, (Idle, Destroyed) => {
self.call_event_handler(Event::LoopExiting);
},
(HandlingMainEvents, Idle) => { (HandlingMainEvents, Idle) => {
// This is always the last event we dispatch before waiting for new events // This is always the last event we dispatch before waiting for new events
@@ -310,6 +314,7 @@ impl EventLoopRunner {
(HandlingMainEvents, Destroyed) => { (HandlingMainEvents, Destroyed) => {
self.call_event_handler(Event::AboutToWait); self.call_event_handler(Event::AboutToWait);
self.last_events_cleared.set(Instant::now()); self.last_events_cleared.set(Instant::now());
self.call_event_handler(Event::LoopExiting);
}, },
(Destroyed, _) => panic!("cannot move state from Destroyed"), (Destroyed, _) => panic!("cannot move state from Destroyed"),

View File

@@ -909,7 +909,7 @@ pub trait Window: AsAny + Send + Sync {
/// - **Web / iOS / Android:** Unsupported. Always returns [`WindowButtons::all`]. /// - **Web / iOS / Android:** Unsupported. Always returns [`WindowButtons::all`].
fn enabled_buttons(&self) -> WindowButtons; fn enabled_buttons(&self) -> WindowButtons;
/// Sets the window to minimized or back /// Minimize the window, or put it back from the minimized state.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
@@ -945,7 +945,7 @@ pub trait Window: AsAny + Send + Sync {
/// - **iOS / Android / Web:** Unsupported. /// - **iOS / Android / Web:** Unsupported.
fn is_maximized(&self) -> bool; fn is_maximized(&self) -> bool;
/// Sets the window to fullscreen or back. /// Set the window's fullscreen state.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
@@ -1433,6 +1433,9 @@ impl From<ResizeDirection> for CursorIcon {
/// Fullscreen modes. /// Fullscreen modes.
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Fullscreen { pub enum Fullscreen {
/// This changes the video mode of the monitor for fullscreen windows and,
/// if applicable, captures the monitor for exclusive use by this
/// application.
Exclusive(VideoModeHandle), Exclusive(VideoModeHandle),
/// Providing `None` to `Borderless` will fullscreen on the current monitor. /// Providing `None` to `Borderless` will fullscreen on the current monitor.