diff --git a/CHANGELOG.md b/CHANGELOG.md index a8b2144..d197b3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/lib.rs b/src/lib.rs index 63d8731..26cb17f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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<::Window>, + winit: impl Into>, + ) -> 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 { + 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 { + 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 Deref for ExecutingApp<'_, AppMessage> +where + AppMessage: Message, +{ + type Target = Windows; + + fn deref(&self) -> &Self::Target { + self.0.windows + } +} + +struct ExecutingAppHandle<'a, AppMessage> +where + AppMessage: Message, +{ + windows: &'a Windows<::Window>, + winit: WinitHandle<'a, AppMessage>, +} + +enum WinitHandle<'a, AppMessage> +where + AppMessage: Message, +{ + Owned(&'a EventLoop>), + 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>> + for WinitHandle<'a, AppMessage> +where + AppMessage: Message, +{ + fn from(handle: &'a EventLoop>) -> Self { + Self::Owned(handle) + } +} + /// An application that is not yet running. pub struct PendingApp where @@ -42,12 +148,8 @@ where spawner: WindowSpawner, } -type BoxedEventCallback = Box< - dyn FnMut( - AppMessage, - &Windows<::Window>, - ) -> ::Response, ->; +type BoxedEventCallback = + Box) -> ::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::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(&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) { 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 = dyn FnOnce(ExecutingApp<'_, AppMessage>) + Send; + /// A reference to a multi-window application. pub struct App where @@ -203,6 +325,32 @@ where windows: Windows, } +impl App +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 { + 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>) { + let _ = self.proxy.send_event(EventLoopMessage::Execute(on_startup)); + } +} + impl Clone for App where AppMessage: Message, @@ -245,9 +393,27 @@ pub trait AsApplication { AppMessage: Message; } -impl AsApplication for T +impl AsApplication for App +where + AppMessage: Message, +{ + fn as_application(&self) -> &dyn Application + where + AppMessage: Message, + { + self + } + + fn as_application_mut(&mut self) -> &mut dyn Application + where + AppMessage: Message, + { + self + } +} + +impl AsApplication for PendingApp where - T: Application, AppMessage: Message, { fn as_application(&self) -> &dyn Application @@ -287,7 +453,10 @@ where } fn send(&mut self, message: AppMessage) -> Option<::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<::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) } } diff --git a/src/private.rs b/src/private.rs index 7018d23..1402d4c 100644 --- a/src/private.rs +++ b/src/private.rs @@ -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; @@ -48,6 +48,7 @@ pub enum EventLoopMessage where AppMessage: Message, { + Execute(Box>), OpenWindow { attrs: WindowAttributes, sender: Arc>>, diff --git a/src/window.rs b/src/window.rs index 6e528e9..c7c0ae5 100644 --- a/src/window.rs +++ b/src/window.rs @@ -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::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::Response + app_callback: impl FnMut(AppMessage, ExecutingApp<'_, AppMessage>) -> AppMessage::Response + 'static, ) -> Result<(), EventLoopError> { let mut app = PendingApp::new_with_event_callback(app_callback);