Added ability to send messages to windows.

Gooey is using this to send invalidation messages from the UI callbacks.
This commit is contained in:
Jonathan Johnson 2023-07-15 08:12:42 -07:00
parent 13e0864bcd
commit 1b5da400f3
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
3 changed files with 117 additions and 67 deletions

View file

@ -15,7 +15,6 @@ use winit::error::OsError;
use winit::window::WindowId; use winit::window::WindowId;
use std::collections::HashMap; use std::collections::HashMap;
use std::marker::PhantomData;
use std::sync::{mpsc, Arc, Mutex, PoisonError}; use std::sync::{mpsc, Arc, Mutex, PoisonError};
use winit::event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget}; use winit::event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget};
use winit::{event::Event, event_loop::EventLoop}; use winit::{event::Event, event_loop::EventLoop};
@ -33,8 +32,12 @@ where
running: App<AppMessage>, running: App<AppMessage>,
} }
type BoxedEventCallback<AppMessage> = type BoxedEventCallback<AppMessage> = Box<
Box<dyn FnMut(AppMessage, &Windows<AppMessage>) -> <AppMessage as Message>::Response>; dyn FnMut(
AppMessage,
&Windows<<AppMessage as Message>::Window>,
) -> <AppMessage as Message>::Response,
>;
impl Default for PendingApp<()> { impl Default for PendingApp<()> {
fn default() -> Self { fn default() -> Self {
@ -59,7 +62,8 @@ where
/// app is run, the app will immediately close. /// app is run, the app will immediately close.
#[must_use] #[must_use]
pub fn new_with_event_callback( pub fn new_with_event_callback(
event_callback: impl FnMut(AppMessage, &Windows<AppMessage>) -> AppMessage::Response + 'static, event_callback: impl FnMut(AppMessage, &Windows<AppMessage::Window>) -> AppMessage::Response
+ 'static,
) -> Self { ) -> Self {
let event_loop = EventLoopBuilder::with_user_event().build(); let event_loop = EventLoopBuilder::with_user_event().build();
let proxy = event_loop.create_proxy(); let proxy = event_loop.create_proxy();
@ -134,7 +138,7 @@ where
AppMessage: Message, AppMessage: Message,
{ {
proxy: EventLoopProxy<EventLoopMessage<AppMessage>>, proxy: EventLoopProxy<EventLoopMessage<AppMessage>>,
windows: Windows<AppMessage>, windows: Windows<AppMessage::Window>,
} }
impl<AppMessage> Clone for App<AppMessage> impl<AppMessage> Clone for App<AppMessage>
@ -154,6 +158,9 @@ pub trait Application<AppMessage>: private::ApplicationSealed<AppMessage>
where where
AppMessage: Message, AppMessage: Message,
{ {
/// Returns a handle to the running application.
fn app(&self) -> App<AppMessage>;
/// Sends an app message to the main event loop to be handled by the /// Sends an app message to the main event loop to be handled by the
/// callback provided when the app was created. /// callback provided when the app was created.
/// ///
@ -165,18 +172,25 @@ where
/// A message with an associated response type. /// A message with an associated response type.
pub trait Message: Send + 'static { 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. /// The type returned when responding to this message.
type Response: Send; type Response: Send;
} }
impl Message for () { impl Message for () {
type Response = (); type Response = ();
type Window = ();
} }
impl<AppMessage> Application<AppMessage> for PendingApp<AppMessage> impl<AppMessage> Application<AppMessage> for PendingApp<AppMessage>
where where
AppMessage: Message, AppMessage: Message,
{ {
fn app(&self) -> App<AppMessage> {
self.running.clone()
}
fn send(&mut self, message: AppMessage) -> Option<<AppMessage as Message>::Response> { fn send(&mut self, message: AppMessage) -> Option<<AppMessage as Message>::Response> {
Some((self.message_callback)(message, &self.running.windows)) Some((self.message_callback)(message, &self.running.windows))
} }
@ -186,14 +200,10 @@ impl<AppMessage> private::ApplicationSealed<AppMessage> for PendingApp<AppMessag
where where
AppMessage: Message, AppMessage: Message,
{ {
fn app(&self) -> App<AppMessage> {
self.running.clone()
}
fn open( fn open(
&self, &self,
window: WindowAttributes, window: WindowAttributes<AppMessage::Window>,
sender: mpsc::SyncSender<WindowMessage>, sender: mpsc::SyncSender<WindowMessage<AppMessage::Window>>,
) -> Result<Option<Arc<winit::window::Window>>, OsError> { ) -> Result<Option<Arc<winit::window::Window>>, OsError> {
self.running self.running
.windows .windows
@ -203,33 +213,27 @@ where
} }
/// A collection of open windows. /// A collection of open windows.
pub struct Windows<AppMessage> { pub struct Windows<Message> {
data: Arc<Mutex<HashMap<WindowId, OpenWindow>>>, data: Arc<Mutex<HashMap<WindowId, OpenWindow<Message>>>>,
_message: PhantomData<AppMessage>,
} }
impl<AppMessage> Default for Windows<AppMessage> { impl<Message> Default for Windows<Message> {
fn default() -> Self { fn default() -> Self {
Self { Self {
data: Arc::default(), data: Arc::default(),
_message: PhantomData,
} }
} }
} }
impl<AppMessage> Clone for Windows<AppMessage> { impl<Message> Clone for Windows<Message> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
data: self.data.clone(), data: self.data.clone(),
_message: PhantomData,
} }
} }
} }
impl<AppMessage> Windows<AppMessage> impl<Message> Windows<Message> {
where
AppMessage: Message,
{
/// Gets an instance of the winit window for the given window id, if it is /// Gets an instance of the winit window for the given window id, if it is
/// still open. /// still open.
pub fn get(&self, id: WindowId) -> Option<Arc<winit::window::Window>> { pub fn get(&self, id: WindowId) -> Option<Arc<winit::window::Window>> {
@ -238,12 +242,15 @@ where
} }
#[allow(unsafe_code)] #[allow(unsafe_code)]
fn open( fn open<AppMessage>(
&self, &self,
target: &EventLoopWindowTarget<EventLoopMessage<AppMessage>>, target: &EventLoopWindowTarget<EventLoopMessage<AppMessage>>,
attrs: WindowAttributes, attrs: WindowAttributes<Message>,
sender: mpsc::SyncSender<WindowMessage>, sender: mpsc::SyncSender<WindowMessage<Message>>,
) -> Result<Arc<winit::window::Window>, OsError> { ) -> Result<Arc<winit::window::Window>, OsError>
where
AppMessage: crate::Message<Window = Message>,
{
let mut builder = winit::window::WindowBuilder::new() let mut builder = winit::window::WindowBuilder::new()
.with_active(attrs.active) .with_active(attrs.active)
.with_resizable(attrs.resizable) .with_resizable(attrs.resizable)
@ -297,7 +304,7 @@ where
Ok(winit) Ok(winit)
} }
fn send(&self, window: WindowId, message: WindowMessage) { fn send(&self, window: WindowId, message: WindowMessage<Message>) {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
if let Some(open_window) = data.get(&window) { if let Some(open_window) = data.get(&window) {
match open_window.sender.try_send(message) { match open_window.sender.try_send(message) {
@ -320,7 +327,7 @@ where
} }
} }
struct OpenWindow { struct OpenWindow<User> {
winit: Arc<winit::window::Window>, winit: Arc<winit::window::Window>,
sender: mpsc::SyncSender<WindowMessage>, sender: mpsc::SyncSender<WindowMessage<User>>,
} }

View file

@ -10,17 +10,16 @@ use winit::event::{
use winit::window::{Theme, WindowId}; use winit::window::{Theme, WindowId};
use crate::window::WindowAttributes; use crate::window::WindowAttributes;
use crate::{App, Message}; use crate::Message;
pub trait ApplicationSealed<AppMessage> pub trait ApplicationSealed<AppMessage>
where where
AppMessage: Message, AppMessage: Message,
{ {
fn app(&self) -> App<AppMessage>;
fn open( fn open(
&self, &self,
window: WindowAttributes, window: WindowAttributes<AppMessage::Window>,
sender: mpsc::SyncSender<WindowMessage>, sender: mpsc::SyncSender<WindowMessage<AppMessage::Window>>,
) -> Result<Option<Arc<winit::window::Window>>, OsError>; ) -> Result<Option<Arc<winit::window::Window>>, OsError>;
} }
@ -29,8 +28,8 @@ where
AppMessage: Message, AppMessage: Message,
{ {
OpenWindow { OpenWindow {
attrs: WindowAttributes, attrs: WindowAttributes<AppMessage::Window>,
sender: mpsc::SyncSender<WindowMessage>, sender: mpsc::SyncSender<WindowMessage<AppMessage::Window>>,
open_sender: mpsc::SyncSender<Result<Arc<winit::window::Window>, OsError>>, open_sender: mpsc::SyncSender<Result<Arc<winit::window::Window>, OsError>>,
}, },
CloseWindow(WindowId), CloseWindow(WindowId),
@ -42,8 +41,9 @@ where
} }
#[derive(Debug)] #[derive(Debug)]
pub enum WindowMessage { pub enum WindowMessage<User> {
Redraw, Redraw,
User(User),
Event(WindowEvent), Event(WindowEvent),
} }

View file

@ -18,17 +18,35 @@ use crate::private::{self, WindowEvent};
use crate::{App, Application, EventLoopMessage, Message, PendingApp, WindowMessage, Windows}; use crate::{App, Application, EventLoopMessage, Message, PendingApp, WindowMessage, Windows};
/// A weak reference to a running window. /// A weak reference to a running window.
#[derive(Clone)] #[derive(Debug, Clone)]
pub struct Window { pub struct Window<Message> {
id: WindowId, id: WindowId,
sender: mpsc::SyncSender<WindowMessage<Message>>,
} }
impl Window { impl<Message> Window<Message> {
/// Returns the winit id of the window. /// Returns the winit id of the window.
#[must_use] #[must_use]
pub const fn id(&self) -> WindowId { pub const fn id(&self) -> WindowId {
self.id 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. /// A builder for a window.
@ -45,7 +63,7 @@ where
{ {
owner: &'a Application, owner: &'a Application,
context: Behavior::Context, context: Behavior::Context,
attributes: WindowAttributes, attributes: WindowAttributes<AppMessage::Window>,
} }
impl<'a, Behavior, Application, AppMessage> Deref impl<'a, Behavior, Application, AppMessage> Deref
for WindowBuilder<'a, Behavior, Application, AppMessage> for WindowBuilder<'a, Behavior, Application, AppMessage>
@ -53,7 +71,7 @@ where
Behavior: self::WindowBehavior<AppMessage>, Behavior: self::WindowBehavior<AppMessage>,
AppMessage: Message, AppMessage: Message,
{ {
type Target = WindowAttributes; type Target = WindowAttributes<AppMessage::Window>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.attributes &self.attributes
@ -72,7 +90,7 @@ where
} }
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
pub struct WindowAttributes { pub struct WindowAttributes<ParentWindowEvent> {
pub inner_size: Option<Size>, pub inner_size: Option<Size>,
pub min_inner_size: Option<Size>, pub min_inner_size: Option<Size>,
pub max_inner_size: Option<Size>, pub max_inner_size: Option<Size>,
@ -90,11 +108,11 @@ pub struct WindowAttributes {
pub resize_increments: Option<Size>, pub resize_increments: Option<Size>,
pub content_protected: bool, pub content_protected: bool,
pub window_level: WindowLevel, pub window_level: WindowLevel,
pub parent_window: Option<Window>, pub parent_window: Option<Window<ParentWindowEvent>>,
pub active: bool, pub active: bool,
} }
impl Default for WindowAttributes { impl<User> Default for WindowAttributes<User> {
fn default() -> Self { fn default() -> Self {
let defaults = winit::window::WindowAttributes::default(); let defaults = winit::window::WindowAttributes::default();
Self { Self {
@ -145,18 +163,21 @@ where
/// ///
/// The only errors this funciton can return arise from /// The only errors this funciton can return arise from
/// [`winit::window::WindowBuilder::build`]. /// [`winit::window::WindowBuilder::build`].
pub fn open(self) -> Result<Option<Window>, winit::error::OsError> { pub fn open(self) -> Result<Option<Window<AppMessage::Window>>, winit::error::OsError> {
// The window's thread shouldn't ever block for long periods of time. To // 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 // 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 // a fixed-size channel and be cautious to not block the main event loop
// by always using try_send. // by always using try_send.
let (sender, receiver) = mpsc::sync_channel(1024); 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) return Ok(None)
}; };
let window = Window { id: winit.id() }; let window = Window {
id: winit.id(),
sender: sender.clone(),
};
let running_window = RunningWindow { let running_window = RunningWindow {
messages: receiver, messages: (sender, receiver),
responses: mpsc::sync_channel(1), responses: mpsc::sync_channel(1),
app: self.owner.app(), app: self.owner.app(),
occluded: winit.is_visible().unwrap_or(false), occluded: winit.is_visible().unwrap_or(false),
@ -180,6 +201,8 @@ where
} }
} }
type SyncChannel<T> = (mpsc::SyncSender<T>, mpsc::Receiver<T>);
/// A window that is running in its own thread. /// A window that is running in its own thread.
pub struct RunningWindow<AppMessage> pub struct RunningWindow<AppMessage>
where where
@ -187,11 +210,8 @@ where
{ {
window: Arc<winit::window::Window>, window: Arc<winit::window::Window>,
next_redraw_target: Option<RedrawTarget>, next_redraw_target: Option<RedrawTarget>,
messages: mpsc::Receiver<WindowMessage>, messages: SyncChannel<WindowMessage<AppMessage::Window>>,
responses: ( responses: SyncChannel<AppMessage::Response>,
mpsc::SyncSender<AppMessage::Response>,
mpsc::Receiver<AppMessage::Response>,
),
app: App<AppMessage>, app: App<AppMessage>,
inner_size: PhysicalSize<u32>, inner_size: PhysicalSize<u32>,
location: PhysicalPosition<i32>, location: PhysicalPosition<i32>,
@ -216,6 +236,15 @@ where
&self.window &self.window
} }
/// Returns a handle to this window.
#[must_use]
pub fn handle(&self) -> Window<AppMessage::Window> {
Window {
id: self.window.id(),
sender: self.messages.0.clone(),
}
}
/// Returns the target for when the window will be redrawn. /// Returns the target for when the window will be redrawn.
#[must_use] #[must_use]
pub const fn next_redraw_target(&self) -> Option<RedrawTarget> { pub const fn next_redraw_target(&self) -> Option<RedrawTarget> {
@ -340,7 +369,7 @@ where
// The scheduled redraw time has already elapsed, or we need to // The scheduled redraw time has already elapsed, or we need to
// redraw. Process messages that are already enqueued, but don't // redraw. Process messages that are already enqueued, but don't
// block. // block.
TimeUntilRedraw::None => match self.messages.try_recv() { TimeUntilRedraw::None => match self.messages.1.try_recv() {
Ok(message) => message, Ok(message) => message,
Err(mpsc::TryRecvError::Disconnected) => return false, Err(mpsc::TryRecvError::Disconnected) => return false,
Err(mpsc::TryRecvError::Empty) => return true, Err(mpsc::TryRecvError::Empty) => return true,
@ -348,14 +377,14 @@ where
// We have a scheduled time for the next frame, and it hasn't // We have a scheduled time for the next frame, and it hasn't
// elapsed yet. // elapsed yet.
TimeUntilRedraw::Some(duration_remaining) => { TimeUntilRedraw::Some(duration_remaining) => {
match self.messages.recv_timeout(duration_remaining) { match self.messages.1.recv_timeout(duration_remaining) {
Ok(message) => message, Ok(message) => message,
Err(mpsc::RecvTimeoutError::Timeout) => return true, Err(mpsc::RecvTimeoutError::Timeout) => return true,
Err(mpsc::RecvTimeoutError::Disconnected) => return false, Err(mpsc::RecvTimeoutError::Disconnected) => return false,
} }
} }
// No scheduled redraw time, sleep until the next message. // 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, Ok(message) => message,
Err(_) => return false, Err(_) => return false,
}, },
@ -368,7 +397,11 @@ where
} }
#[allow(clippy::too_many_lines)] // can't avoid the match #[allow(clippy::too_many_lines)] // can't avoid the match
fn handle_message<Behavior>(&mut self, message: WindowMessage, behavior: &mut Behavior) -> bool fn handle_message<Behavior>(
&mut self,
message: WindowMessage<AppMessage::Window>,
behavior: &mut Behavior,
) -> bool
where where
Behavior: self::WindowBehavior<AppMessage>, Behavior: self::WindowBehavior<AppMessage>,
{ {
@ -376,6 +409,7 @@ where
WindowMessage::Redraw => { WindowMessage::Redraw => {
self.set_needs_redraw(); self.set_needs_redraw();
} }
WindowMessage::User(user) => behavior.event(self, user),
WindowMessage::Event(evt) => match evt { WindowMessage::Event(evt) => match evt {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
if behavior.close_requested(self) { if behavior.close_requested(self) {
@ -569,6 +603,10 @@ impl<AppMessage> Application<AppMessage> for RunningWindow<AppMessage>
where where
AppMessage: Message, AppMessage: Message,
{ {
fn app(&self) -> App<AppMessage> {
self.app.clone()
}
fn send(&mut self, message: AppMessage) -> Option<<AppMessage as Message>::Response> { fn send(&mut self, message: AppMessage) -> Option<<AppMessage as Message>::Response> {
self.app self.app
.proxy .proxy
@ -585,14 +623,10 @@ impl<AppMessage> private::ApplicationSealed<AppMessage> for RunningWindow<AppMes
where where
AppMessage: Message, AppMessage: Message,
{ {
fn app(&self) -> App<AppMessage> {
self.app.clone()
}
fn open( fn open(
&self, &self,
attrs: WindowAttributes, attrs: WindowAttributes<AppMessage::Window>,
sender: mpsc::SyncSender<WindowMessage>, sender: mpsc::SyncSender<WindowMessage<AppMessage::Window>>,
) -> Result<Option<Arc<winit::window::Window>>, OsError> { ) -> Result<Option<Arc<winit::window::Window>>, OsError> {
let (open_sender, open_receiver) = mpsc::sync_channel(1); let (open_sender, open_receiver) = mpsc::sync_channel(1);
if self if self
@ -688,7 +722,8 @@ where
/// [`Application::send`]. Each time a message is received by the main event /// [`Application::send`]. Each time a message is received by the main event
/// loop, `app_callback` will be invoked. /// loop, `app_callback` will be invoked.
fn run_with_event_callback( fn run_with_event_callback(
app_callback: impl FnMut(AppMessage, &Windows<AppMessage>) -> AppMessage::Response + 'static, app_callback: impl FnMut(AppMessage, &Windows<AppMessage::Window>) -> AppMessage::Response
+ 'static,
) -> ! ) -> !
where where
Self::Context: Default, Self::Context: Default,
@ -708,7 +743,8 @@ where
/// loop, `app_callback` will be invoked. /// loop, `app_callback` will be invoked.
fn run_with_context_and_event_callback( fn run_with_context_and_event_callback(
context: Self::Context, context: Self::Context,
app_callback: impl FnMut(AppMessage, &Windows<AppMessage>) -> AppMessage::Response + 'static, app_callback: impl FnMut(AppMessage, &Windows<AppMessage::Window>) -> AppMessage::Response
+ 'static,
) -> ! { ) -> ! {
let app = PendingApp::new_with_event_callback(app_callback); let app = PendingApp::new_with_event_callback(app_callback);
Self::open_with(&app, context).expect("error opening initial window"); Self::open_with(&app, context).expect("error opening initial window");
@ -725,7 +761,7 @@ where
/// ///
/// The only errors this funciton can return arise from /// The only errors this funciton can return arise from
/// [`winit::window::WindowBuilder::build`]. /// [`winit::window::WindowBuilder::build`].
fn open<App>(app: &App) -> Result<Option<Window>, OsError> fn open<App>(app: &App) -> Result<Option<Window<AppMessage::Window>>, OsError>
where where
App: Application<AppMessage>, App: Application<AppMessage>,
Self::Context: Default, Self::Context: Default,
@ -743,7 +779,10 @@ where
/// ///
/// The only errors this funciton can return arise from /// The only errors this funciton can return arise from
/// [`winit::window::WindowBuilder::build`]. /// [`winit::window::WindowBuilder::build`].
fn open_with<App>(app: &App, context: Self::Context) -> Result<Option<Window>, OsError> fn open_with<App>(
app: &App,
context: Self::Context,
) -> Result<Option<Window<AppMessage::Window>>, OsError>
where where
App: Application<AppMessage>, App: Application<AppMessage>,
{ {
@ -919,6 +958,10 @@ where
phase: TouchPhase, phase: TouchPhase,
) { ) {
} }
/// A user event has been received by the window.
#[allow(unused_variables)]
fn event(&mut self, window: &mut RunningWindow<AppMessage>, event: AppMessage::Window) {}
} }
pub trait Run: WindowBehavior<()> { pub trait Run: WindowBehavior<()> {