Exposing event loop details

This commit is contained in:
Jonathan Johnson 2024-09-06 10:41:14 -07:00
parent 2b7829ac56
commit 516401f48c
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
4 changed files with 213 additions and 25 deletions

View file

@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Breaking Changes
- Several functions now accept an `ExecutingApp` parameter instead of a
`Windows` parameter. This new type exposes access to other information from
the event loop such as monitor information. The affected APIs are:
- `PendingApp::new_with_event_callback`
- `WindowBehavior::run_witH_event_callback`
- `WindowBehavior::run_witH_context_and_event_callback`
### Added
- `PendingApp::on_startup` accepts a callback that will be invoked once the
event loop is executing.
### Changed
- `AsApplication` is now explicitly implemented for `App` and `PendingApp`
rather than implemented using a blanket implementation. This allows downstream
crates to create wrappers of these types that can implement `AsApplication`.
## v0.3.2 (2024-08-28)
### Fixed

View file

@ -8,6 +8,7 @@ mod private;
mod window;
use std::collections::HashMap;
use std::ops::Deref;
use std::process::exit;
use std::sync::{mpsc, Arc, Mutex, PoisonError};
use std::time::Duration;
@ -17,11 +18,116 @@ pub use window::{Run, RunningWindow, Window, WindowAttributes, WindowBehavior, W
pub use winit;
use winit::application::ApplicationHandler;
use winit::error::{EventLoopError, OsError};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy};
use winit::event_loop::{
ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy, OwnedDisplayHandle,
};
use winit::monitor::MonitorHandle;
use winit::window::WindowId;
use crate::private::{EventLoopMessage, WindowEvent, WindowMessage};
/// A reference to an executing application.
pub struct ExecutingApp<'a, AppMessage>(ExecutingAppHandle<'a, AppMessage>)
where
AppMessage: Message;
impl<'a, AppMessage> ExecutingApp<'a, AppMessage>
where
AppMessage: Message,
{
fn new(
windows: &'a Windows<<AppMessage as Message>::Window>,
winit: impl Into<WinitHandle<'a, AppMessage>>,
) -> Self {
Self(ExecutingAppHandle {
windows,
winit: winit.into(),
})
}
/// Returns the list of available monitors.
///
/// This function will return an empty `Vec` if invoked before the
/// application has begun executing. This can occur if an app message is
/// sent before a `PendingApp` is run.
#[must_use]
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
match &self.0.winit {
WinitHandle::Owned(_) => Vec::new(),
WinitHandle::Active(winit) => winit.available_monitors().collect(),
}
}
/// Returns a handle to the primary monitor.
///
/// This function will return None if:
///
/// - The application hasn't begun executing.
/// - The platform does not support determining a primary monitor.
#[must_use]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
match &self.0.winit {
WinitHandle::Owned(_) => None,
WinitHandle::Active(winit) => winit.primary_monitor(),
}
}
/// Returns a handle to the underlying display.
#[must_use]
pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
match &self.0.winit {
WinitHandle::Owned(winit) => winit.owned_display_handle(),
WinitHandle::Active(winit) => winit.owned_display_handle(),
}
}
}
impl<AppMessage> Deref for ExecutingApp<'_, AppMessage>
where
AppMessage: Message,
{
type Target = Windows<AppMessage::Window>;
fn deref(&self) -> &Self::Target {
self.0.windows
}
}
struct ExecutingAppHandle<'a, AppMessage>
where
AppMessage: Message,
{
windows: &'a Windows<<AppMessage as Message>::Window>,
winit: WinitHandle<'a, AppMessage>,
}
enum WinitHandle<'a, AppMessage>
where
AppMessage: Message,
{
Owned(&'a EventLoop<EventLoopMessage<AppMessage>>),
Active(&'a ActiveEventLoop),
}
impl<'a, AppMessage> From<&'a ActiveEventLoop> for WinitHandle<'a, AppMessage>
where
AppMessage: Message,
{
fn from(handle: &'a ActiveEventLoop) -> Self {
Self::Active(handle)
}
}
impl<'a, AppMessage> From<&'a EventLoop<EventLoopMessage<AppMessage>>>
for WinitHandle<'a, AppMessage>
where
AppMessage: Message,
{
fn from(handle: &'a EventLoop<EventLoopMessage<AppMessage>>) -> Self {
Self::Owned(handle)
}
}
/// An application that is not yet running.
pub struct PendingApp<AppMessage>
where
@ -42,12 +148,8 @@ where
spawner: WindowSpawner,
}
type BoxedEventCallback<AppMessage> = Box<
dyn FnMut(
AppMessage,
&Windows<<AppMessage as Message>::Window>,
) -> <AppMessage as Message>::Response,
>;
type BoxedEventCallback<AppMessage> =
Box<dyn FnMut(AppMessage, ExecutingApp<'_, AppMessage>) -> <AppMessage as Message>::Response>;
impl Default for PendingApp<()> {
fn default() -> Self {
@ -72,7 +174,7 @@ where
/// app is run, the app will immediately close.
#[must_use]
pub fn new_with_event_callback(
event_callback: impl FnMut(AppMessage, &Windows<AppMessage::Window>) -> AppMessage::Response
event_callback: impl FnMut(AppMessage, ExecutingApp<'_, AppMessage>) -> AppMessage::Response
+ 'static,
) -> Self {
let event_loop = EventLoop::with_user_event()
@ -90,6 +192,19 @@ where
}
}
/// Executes `on_startup` once the app event loop has started.
///
/// This is useful because some information provided by winit is only
/// available after the event loop has started. For example, to enter an
/// exclusive full screen mode, monitor information must be accessed which
/// requires the event loop to have been started.
pub fn on_startup<F>(&self, on_startup: F)
where
F: FnOnce(ExecutingApp<'_, AppMessage>) + Send + 'static,
{
self.running.push_on_startup(Box::new(on_startup));
}
/// Begins running the application.
///
/// Internally this runs the [`EventLoop`].
@ -161,6 +276,9 @@ where
fn user_event(&mut self, event_loop: &ActiveEventLoop, message: EventLoopMessage<AppMessage>) {
match message {
EventLoopMessage::Execute(closure) => {
closure(ExecutingApp::new(&self.running.windows, event_loop));
}
EventLoopMessage::CloseWindow(window_id) => {
if self.running.windows.close(window_id) {
exit(0)
@ -187,13 +305,17 @@ where
message,
response_sender,
} => {
let _result =
response_sender.send((self.message_callback)(message, &self.running.windows));
let _result = response_sender.send((self.message_callback)(
message,
ExecutingApp::new(&self.running.windows, event_loop),
));
}
}
}
}
type StartupClosure<AppMessage> = dyn FnOnce(ExecutingApp<'_, AppMessage>) + Send;
/// A reference to a multi-window application.
pub struct App<AppMessage>
where
@ -203,6 +325,32 @@ where
windows: Windows<AppMessage::Window>,
}
impl<AppMessage> App<AppMessage>
where
AppMessage: Message,
{
/// Sends an app message to the main event loop to be handled by the
/// callback provided when the app was created.
///
/// This function will return None if the main event loop is no longer
/// running. Otherwise, this function will block until the result of the
/// callback has been received.
pub fn send(&self, message: AppMessage) -> Option<AppMessage::Response> {
let (response_sender, response_receiver) = mpsc::sync_channel(1);
self.proxy
.send_event(EventLoopMessage::User {
message,
response_sender,
})
.ok()?;
response_receiver.recv().ok()
}
fn push_on_startup(&self, on_startup: Box<StartupClosure<AppMessage>>) {
let _ = self.proxy.send_event(EventLoopMessage::Execute(on_startup));
}
}
impl<AppMessage> Clone for App<AppMessage>
where
AppMessage: Message,
@ -245,9 +393,27 @@ pub trait AsApplication<AppMessage> {
AppMessage: Message;
}
impl<T, AppMessage> AsApplication<AppMessage> for T
impl<AppMessage> AsApplication<AppMessage> for App<AppMessage>
where
AppMessage: Message,
{
fn as_application(&self) -> &dyn Application<AppMessage>
where
AppMessage: Message,
{
self
}
fn as_application_mut(&mut self) -> &mut dyn Application<AppMessage>
where
AppMessage: Message,
{
self
}
}
impl<AppMessage> AsApplication<AppMessage> for PendingApp<AppMessage>
where
T: Application<AppMessage>,
AppMessage: Message,
{
fn as_application(&self) -> &dyn Application<AppMessage>
@ -287,7 +453,10 @@ where
}
fn send(&mut self, message: AppMessage) -> Option<<AppMessage as Message>::Response> {
Some((self.message_callback)(message, &self.running.windows))
Some((self.message_callback)(
message,
ExecutingApp::new(&self.running.windows, &self.event_loop),
))
}
}
@ -319,14 +488,8 @@ where
}
fn send(&mut self, message: AppMessage) -> Option<<AppMessage as Message>::Response> {
let (response_sender, response_receiver) = mpsc::sync_channel(1);
self.proxy
.send_event(EventLoopMessage::User {
message,
response_sender,
})
.ok()?;
response_receiver.recv().ok()
let this: &Self = self;
this.send(message)
}
}

View file

@ -12,7 +12,7 @@ use winit::event_loop::AsyncRequestSerial;
use winit::window::{ActivationToken, Theme, WindowId};
use crate::window::WindowAttributes;
use crate::Message;
use crate::{Message, StartupClosure};
pub type WindowSpawner = Box<dyn FnOnce(OpenedWindow) + Send + 'static>;
@ -48,6 +48,7 @@ pub enum EventLoopMessage<AppMessage>
where
AppMessage: Message,
{
Execute(Box<StartupClosure<AppMessage>>),
OpenWindow {
attrs: WindowAttributes,
sender: Arc<mpsc::SyncSender<WindowMessage<AppMessage::Window>>>,

View file

@ -17,7 +17,8 @@ use winit::window::{Fullscreen, Icon, Theme, WindowButtons, WindowId, WindowLeve
use crate::private::{self, OpenedWindow, RedrawGuard, WindowEvent, WindowSpawner};
use crate::{
App, Application, AsApplication, EventLoopMessage, Message, PendingApp, WindowMessage, Windows,
App, Application, AsApplication, EventLoopMessage, ExecutingApp, Message, PendingApp,
WindowMessage,
};
/// A weak reference to a running window.
@ -869,7 +870,7 @@ where
/// Returns an [`EventLoopError`] upon the loop exiting due to an error. See
/// [`EventLoop::run`] for more information.
fn run_with_event_callback(
app_callback: impl FnMut(AppMessage, &Windows<AppMessage::Window>) -> AppMessage::Response
app_callback: impl FnMut(AppMessage, ExecutingApp<'_, AppMessage>) -> AppMessage::Response
+ 'static,
) -> Result<(), EventLoopError>
where
@ -895,7 +896,7 @@ where
/// [`EventLoop::run`] for more information.
fn run_with_context_and_event_callback(
context: Self::Context,
app_callback: impl FnMut(AppMessage, &Windows<AppMessage::Window>) -> AppMessage::Response
app_callback: impl FnMut(AppMessage, ExecutingApp<'_, AppMessage>) -> AppMessage::Response
+ 'static,
) -> Result<(), EventLoopError> {
let mut app = PendingApp::new_with_event_callback(app_callback);