From 1b5da400f39a4ac559f76c8d71a7336339a7f514 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Sat, 15 Jul 2023 08:12:42 -0700 Subject: [PATCH] Added ability to send messages to windows. Gooey is using this to send invalidation messages from the UI callbacks. --- src/lib.rs | 65 ++++++++++++++++-------------- src/private.rs | 14 +++---- src/window.rs | 105 ++++++++++++++++++++++++++++++++++--------------- 3 files changed, 117 insertions(+), 67 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c0d3fc1..4a2485e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,6 @@ use winit::error::OsError; use winit::window::WindowId; use std::collections::HashMap; -use std::marker::PhantomData; use std::sync::{mpsc, Arc, Mutex, PoisonError}; use winit::event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget}; use winit::{event::Event, event_loop::EventLoop}; @@ -33,8 +32,12 @@ where running: App, } -type BoxedEventCallback = - Box) -> ::Response>; +type BoxedEventCallback = Box< + dyn FnMut( + AppMessage, + &Windows<::Window>, + ) -> ::Response, +>; impl Default for PendingApp<()> { fn default() -> Self { @@ -59,7 +62,8 @@ 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 + 'static, + event_callback: impl FnMut(AppMessage, &Windows) -> AppMessage::Response + + 'static, ) -> Self { let event_loop = EventLoopBuilder::with_user_event().build(); let proxy = event_loop.create_proxy(); @@ -134,7 +138,7 @@ where AppMessage: Message, { proxy: EventLoopProxy>, - windows: Windows, + windows: Windows, } impl Clone for App @@ -154,6 +158,9 @@ pub trait Application: private::ApplicationSealed where AppMessage: Message, { + /// Returns a handle to the running application. + fn app(&self) -> App; + /// Sends an app message to the main event loop to be handled by the /// callback provided when the app was created. /// @@ -165,18 +172,25 @@ where /// A message with an associated response type. pub trait Message: Send + 'static { + /// The message type that is able to be sent to individual windows. + type Window: Send; /// The type returned when responding to this message. type Response: Send; } impl Message for () { type Response = (); + type Window = (); } impl Application for PendingApp where AppMessage: Message, { + fn app(&self) -> App { + self.running.clone() + } + fn send(&mut self, message: AppMessage) -> Option<::Response> { Some((self.message_callback)(message, &self.running.windows)) } @@ -186,14 +200,10 @@ impl private::ApplicationSealed for PendingApp App { - self.running.clone() - } - fn open( &self, - window: WindowAttributes, - sender: mpsc::SyncSender, + window: WindowAttributes, + sender: mpsc::SyncSender>, ) -> Result>, OsError> { self.running .windows @@ -203,33 +213,27 @@ where } /// A collection of open windows. -pub struct Windows { - data: Arc>>, - _message: PhantomData, +pub struct Windows { + data: Arc>>>, } -impl Default for Windows { +impl Default for Windows { fn default() -> Self { Self { data: Arc::default(), - _message: PhantomData, } } } -impl Clone for Windows { +impl Clone for Windows { fn clone(&self) -> Self { Self { data: self.data.clone(), - _message: PhantomData, } } } -impl Windows -where - AppMessage: Message, -{ +impl Windows { /// Gets an instance of the winit window for the given window id, if it is /// still open. pub fn get(&self, id: WindowId) -> Option> { @@ -238,12 +242,15 @@ where } #[allow(unsafe_code)] - fn open( + fn open( &self, target: &EventLoopWindowTarget>, - attrs: WindowAttributes, - sender: mpsc::SyncSender, - ) -> Result, OsError> { + attrs: WindowAttributes, + sender: mpsc::SyncSender>, + ) -> Result, OsError> + where + AppMessage: crate::Message, + { let mut builder = winit::window::WindowBuilder::new() .with_active(attrs.active) .with_resizable(attrs.resizable) @@ -297,7 +304,7 @@ where Ok(winit) } - fn send(&self, window: WindowId, message: WindowMessage) { + fn send(&self, window: WindowId, message: WindowMessage) { let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); if let Some(open_window) = data.get(&window) { match open_window.sender.try_send(message) { @@ -320,7 +327,7 @@ where } } -struct OpenWindow { +struct OpenWindow { winit: Arc, - sender: mpsc::SyncSender, + sender: mpsc::SyncSender>, } diff --git a/src/private.rs b/src/private.rs index 92b4748..ebf63ff 100644 --- a/src/private.rs +++ b/src/private.rs @@ -10,17 +10,16 @@ use winit::event::{ use winit::window::{Theme, WindowId}; use crate::window::WindowAttributes; -use crate::{App, Message}; +use crate::Message; pub trait ApplicationSealed where AppMessage: Message, { - fn app(&self) -> App; fn open( &self, - window: WindowAttributes, - sender: mpsc::SyncSender, + window: WindowAttributes, + sender: mpsc::SyncSender>, ) -> Result>, OsError>; } @@ -29,8 +28,8 @@ where AppMessage: Message, { OpenWindow { - attrs: WindowAttributes, - sender: mpsc::SyncSender, + attrs: WindowAttributes, + sender: mpsc::SyncSender>, open_sender: mpsc::SyncSender, OsError>>, }, CloseWindow(WindowId), @@ -42,8 +41,9 @@ where } #[derive(Debug)] -pub enum WindowMessage { +pub enum WindowMessage { Redraw, + User(User), Event(WindowEvent), } diff --git a/src/window.rs b/src/window.rs index 7b746ea..90be4f0 100644 --- a/src/window.rs +++ b/src/window.rs @@ -18,17 +18,35 @@ use crate::private::{self, WindowEvent}; use crate::{App, Application, EventLoopMessage, Message, PendingApp, WindowMessage, Windows}; /// A weak reference to a running window. -#[derive(Clone)] -pub struct Window { +#[derive(Debug, Clone)] +pub struct Window { id: WindowId, + sender: mpsc::SyncSender>, } -impl Window { +impl Window { /// Returns the winit id of the window. #[must_use] pub const fn id(&self) -> WindowId { self.id } + + /// Sends a message to the window. + /// + /// Returns `Ok` if the message was successfully sent. The message may not + /// be received even if this function returns `Ok`, if the window closes + /// between when the message was sent and when the message is received. + /// + /// # Errors + /// + /// If the window is already closed, this function returns `Err(message)`. + pub fn send(&self, message: Message) -> Result<(), Message> { + match self.sender.send(WindowMessage::User(message)) { + Ok(()) => Ok(()), + Err(mpsc::SendError(WindowMessage::User(message))) => Err(message), + _ => unreachable!("same input as output"), + } + } } /// A builder for a window. @@ -45,7 +63,7 @@ where { owner: &'a Application, context: Behavior::Context, - attributes: WindowAttributes, + attributes: WindowAttributes, } impl<'a, Behavior, Application, AppMessage> Deref for WindowBuilder<'a, Behavior, Application, AppMessage> @@ -53,7 +71,7 @@ where Behavior: self::WindowBehavior, AppMessage: Message, { - type Target = WindowAttributes; + type Target = WindowAttributes; fn deref(&self) -> &Self::Target { &self.attributes @@ -72,7 +90,7 @@ where } #[allow(clippy::struct_excessive_bools)] -pub struct WindowAttributes { +pub struct WindowAttributes { pub inner_size: Option, pub min_inner_size: Option, pub max_inner_size: Option, @@ -90,11 +108,11 @@ pub struct WindowAttributes { pub resize_increments: Option, pub content_protected: bool, pub window_level: WindowLevel, - pub parent_window: Option, + pub parent_window: Option>, pub active: bool, } -impl Default for WindowAttributes { +impl Default for WindowAttributes { fn default() -> Self { let defaults = winit::window::WindowAttributes::default(); Self { @@ -145,18 +163,21 @@ where /// /// The only errors this funciton can return arise from /// [`winit::window::WindowBuilder::build`]. - pub fn open(self) -> Result, winit::error::OsError> { + pub fn open(self) -> Result>, winit::error::OsError> { // The window's thread shouldn't ever block for long periods of time. To // avoid a "frozen" window causing massive memory allocations, we'll use // a fixed-size channel and be cautious to not block the main event loop // by always using try_send. let (sender, receiver) = mpsc::sync_channel(1024); - let Some(winit) = self.owner.open(self.attributes, sender)? else { + let Some(winit) = self.owner.open(self.attributes, sender.clone())? else { return Ok(None) }; - let window = Window { id: winit.id() }; + let window = Window { + id: winit.id(), + sender: sender.clone(), + }; let running_window = RunningWindow { - messages: receiver, + messages: (sender, receiver), responses: mpsc::sync_channel(1), app: self.owner.app(), occluded: winit.is_visible().unwrap_or(false), @@ -180,6 +201,8 @@ where } } +type SyncChannel = (mpsc::SyncSender, mpsc::Receiver); + /// A window that is running in its own thread. pub struct RunningWindow where @@ -187,11 +210,8 @@ where { window: Arc, next_redraw_target: Option, - messages: mpsc::Receiver, - responses: ( - mpsc::SyncSender, - mpsc::Receiver, - ), + messages: SyncChannel>, + responses: SyncChannel, app: App, inner_size: PhysicalSize, location: PhysicalPosition, @@ -216,6 +236,15 @@ where &self.window } + /// Returns a handle to this window. + #[must_use] + pub fn handle(&self) -> Window { + Window { + id: self.window.id(), + sender: self.messages.0.clone(), + } + } + /// Returns the target for when the window will be redrawn. #[must_use] pub const fn next_redraw_target(&self) -> Option { @@ -340,7 +369,7 @@ where // The scheduled redraw time has already elapsed, or we need to // redraw. Process messages that are already enqueued, but don't // block. - TimeUntilRedraw::None => match self.messages.try_recv() { + TimeUntilRedraw::None => match self.messages.1.try_recv() { Ok(message) => message, Err(mpsc::TryRecvError::Disconnected) => return false, Err(mpsc::TryRecvError::Empty) => return true, @@ -348,14 +377,14 @@ where // We have a scheduled time for the next frame, and it hasn't // elapsed yet. TimeUntilRedraw::Some(duration_remaining) => { - match self.messages.recv_timeout(duration_remaining) { + match self.messages.1.recv_timeout(duration_remaining) { Ok(message) => message, Err(mpsc::RecvTimeoutError::Timeout) => return true, Err(mpsc::RecvTimeoutError::Disconnected) => return false, } } // No scheduled redraw time, sleep until the next message. - TimeUntilRedraw::Indefinite => match self.messages.recv() { + TimeUntilRedraw::Indefinite => match self.messages.1.recv() { Ok(message) => message, Err(_) => return false, }, @@ -368,7 +397,11 @@ where } #[allow(clippy::too_many_lines)] // can't avoid the match - fn handle_message(&mut self, message: WindowMessage, behavior: &mut Behavior) -> bool + fn handle_message( + &mut self, + message: WindowMessage, + behavior: &mut Behavior, + ) -> bool where Behavior: self::WindowBehavior, { @@ -376,6 +409,7 @@ where WindowMessage::Redraw => { self.set_needs_redraw(); } + WindowMessage::User(user) => behavior.event(self, user), WindowMessage::Event(evt) => match evt { WindowEvent::CloseRequested => { if behavior.close_requested(self) { @@ -569,6 +603,10 @@ impl Application for RunningWindow where AppMessage: Message, { + fn app(&self) -> App { + self.app.clone() + } + fn send(&mut self, message: AppMessage) -> Option<::Response> { self.app .proxy @@ -585,14 +623,10 @@ impl private::ApplicationSealed for RunningWindow App { - self.app.clone() - } - fn open( &self, - attrs: WindowAttributes, - sender: mpsc::SyncSender, + attrs: WindowAttributes, + sender: mpsc::SyncSender>, ) -> Result>, OsError> { let (open_sender, open_receiver) = mpsc::sync_channel(1); if self @@ -688,7 +722,8 @@ where /// [`Application::send`]. Each time a message is received by the main event /// loop, `app_callback` will be invoked. fn run_with_event_callback( - app_callback: impl FnMut(AppMessage, &Windows) -> AppMessage::Response + 'static, + app_callback: impl FnMut(AppMessage, &Windows) -> AppMessage::Response + + 'static, ) -> ! where Self::Context: Default, @@ -708,7 +743,8 @@ where /// loop, `app_callback` will be invoked. fn run_with_context_and_event_callback( context: Self::Context, - app_callback: impl FnMut(AppMessage, &Windows) -> AppMessage::Response + 'static, + app_callback: impl FnMut(AppMessage, &Windows) -> AppMessage::Response + + 'static, ) -> ! { let app = PendingApp::new_with_event_callback(app_callback); Self::open_with(&app, context).expect("error opening initial window"); @@ -725,7 +761,7 @@ where /// /// The only errors this funciton can return arise from /// [`winit::window::WindowBuilder::build`]. - fn open(app: &App) -> Result, OsError> + fn open(app: &App) -> Result>, OsError> where App: Application, Self::Context: Default, @@ -743,7 +779,10 @@ where /// /// The only errors this funciton can return arise from /// [`winit::window::WindowBuilder::build`]. - fn open_with(app: &App, context: Self::Context) -> Result, OsError> + fn open_with( + app: &App, + context: Self::Context, + ) -> Result>, OsError> where App: Application, { @@ -919,6 +958,10 @@ where phase: TouchPhase, ) { } + + /// A user event has been received by the window. + #[allow(unused_variables)] + fn event(&mut self, window: &mut RunningWindow, event: AppMessage::Window) {} } pub trait Run: WindowBehavior<()> {