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 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<AppMessage>,
}
type BoxedEventCallback<AppMessage> =
Box<dyn FnMut(AppMessage, &Windows<AppMessage>) -> <AppMessage as Message>::Response>;
type BoxedEventCallback<AppMessage> = Box<
dyn FnMut(
AppMessage,
&Windows<<AppMessage as Message>::Window>,
) -> <AppMessage as Message>::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>) -> AppMessage::Response + 'static,
event_callback: impl FnMut(AppMessage, &Windows<AppMessage::Window>) -> 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<EventLoopMessage<AppMessage>>,
windows: Windows<AppMessage>,
windows: Windows<AppMessage::Window>,
}
impl<AppMessage> Clone for App<AppMessage>
@ -154,6 +158,9 @@ pub trait Application<AppMessage>: private::ApplicationSealed<AppMessage>
where
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
/// 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<AppMessage> Application<AppMessage> for PendingApp<AppMessage>
where
AppMessage: Message,
{
fn app(&self) -> App<AppMessage> {
self.running.clone()
}
fn send(&mut self, message: AppMessage) -> Option<<AppMessage as Message>::Response> {
Some((self.message_callback)(message, &self.running.windows))
}
@ -186,14 +200,10 @@ impl<AppMessage> private::ApplicationSealed<AppMessage> for PendingApp<AppMessag
where
AppMessage: Message,
{
fn app(&self) -> App<AppMessage> {
self.running.clone()
}
fn open(
&self,
window: WindowAttributes,
sender: mpsc::SyncSender<WindowMessage>,
window: WindowAttributes<AppMessage::Window>,
sender: mpsc::SyncSender<WindowMessage<AppMessage::Window>>,
) -> Result<Option<Arc<winit::window::Window>>, OsError> {
self.running
.windows
@ -203,33 +213,27 @@ where
}
/// A collection of open windows.
pub struct Windows<AppMessage> {
data: Arc<Mutex<HashMap<WindowId, OpenWindow>>>,
_message: PhantomData<AppMessage>,
pub struct Windows<Message> {
data: Arc<Mutex<HashMap<WindowId, OpenWindow<Message>>>>,
}
impl<AppMessage> Default for Windows<AppMessage> {
impl<Message> Default for Windows<Message> {
fn default() -> Self {
Self {
data: Arc::default(),
_message: PhantomData,
}
}
}
impl<AppMessage> Clone for Windows<AppMessage> {
impl<Message> Clone for Windows<Message> {
fn clone(&self) -> Self {
Self {
data: self.data.clone(),
_message: PhantomData,
}
}
}
impl<AppMessage> Windows<AppMessage>
where
AppMessage: Message,
{
impl<Message> Windows<Message> {
/// Gets an instance of the winit window for the given window id, if it is
/// still open.
pub fn get(&self, id: WindowId) -> Option<Arc<winit::window::Window>> {
@ -238,12 +242,15 @@ where
}
#[allow(unsafe_code)]
fn open(
fn open<AppMessage>(
&self,
target: &EventLoopWindowTarget<EventLoopMessage<AppMessage>>,
attrs: WindowAttributes,
sender: mpsc::SyncSender<WindowMessage>,
) -> Result<Arc<winit::window::Window>, OsError> {
attrs: WindowAttributes<Message>,
sender: mpsc::SyncSender<WindowMessage<Message>>,
) -> Result<Arc<winit::window::Window>, OsError>
where
AppMessage: crate::Message<Window = 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<Message>) {
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<User> {
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 crate::window::WindowAttributes;
use crate::{App, Message};
use crate::Message;
pub trait ApplicationSealed<AppMessage>
where
AppMessage: Message,
{
fn app(&self) -> App<AppMessage>;
fn open(
&self,
window: WindowAttributes,
sender: mpsc::SyncSender<WindowMessage>,
window: WindowAttributes<AppMessage::Window>,
sender: mpsc::SyncSender<WindowMessage<AppMessage::Window>>,
) -> Result<Option<Arc<winit::window::Window>>, OsError>;
}
@ -29,8 +28,8 @@ where
AppMessage: Message,
{
OpenWindow {
attrs: WindowAttributes,
sender: mpsc::SyncSender<WindowMessage>,
attrs: WindowAttributes<AppMessage::Window>,
sender: mpsc::SyncSender<WindowMessage<AppMessage::Window>>,
open_sender: mpsc::SyncSender<Result<Arc<winit::window::Window>, OsError>>,
},
CloseWindow(WindowId),
@ -42,8 +41,9 @@ where
}
#[derive(Debug)]
pub enum WindowMessage {
pub enum WindowMessage<User> {
Redraw,
User(User),
Event(WindowEvent),
}

View file

@ -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<Message> {
id: WindowId,
sender: mpsc::SyncSender<WindowMessage<Message>>,
}
impl Window {
impl<Message> Window<Message> {
/// 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<AppMessage::Window>,
}
impl<'a, Behavior, Application, AppMessage> Deref
for WindowBuilder<'a, Behavior, Application, AppMessage>
@ -53,7 +71,7 @@ where
Behavior: self::WindowBehavior<AppMessage>,
AppMessage: Message,
{
type Target = WindowAttributes;
type Target = WindowAttributes<AppMessage::Window>;
fn deref(&self) -> &Self::Target {
&self.attributes
@ -72,7 +90,7 @@ where
}
#[allow(clippy::struct_excessive_bools)]
pub struct WindowAttributes {
pub struct WindowAttributes<ParentWindowEvent> {
pub inner_size: Option<Size>,
pub min_inner_size: Option<Size>,
pub max_inner_size: Option<Size>,
@ -90,11 +108,11 @@ pub struct WindowAttributes {
pub resize_increments: Option<Size>,
pub content_protected: bool,
pub window_level: WindowLevel,
pub parent_window: Option<Window>,
pub parent_window: Option<Window<ParentWindowEvent>>,
pub active: bool,
}
impl Default for WindowAttributes {
impl<User> Default for WindowAttributes<User> {
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<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
// 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<T> = (mpsc::SyncSender<T>, mpsc::Receiver<T>);
/// A window that is running in its own thread.
pub struct RunningWindow<AppMessage>
where
@ -187,11 +210,8 @@ where
{
window: Arc<winit::window::Window>,
next_redraw_target: Option<RedrawTarget>,
messages: mpsc::Receiver<WindowMessage>,
responses: (
mpsc::SyncSender<AppMessage::Response>,
mpsc::Receiver<AppMessage::Response>,
),
messages: SyncChannel<WindowMessage<AppMessage::Window>>,
responses: SyncChannel<AppMessage::Response>,
app: App<AppMessage>,
inner_size: PhysicalSize<u32>,
location: PhysicalPosition<i32>,
@ -216,6 +236,15 @@ where
&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.
#[must_use]
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
// 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<Behavior>(&mut self, message: WindowMessage, behavior: &mut Behavior) -> bool
fn handle_message<Behavior>(
&mut self,
message: WindowMessage<AppMessage::Window>,
behavior: &mut Behavior,
) -> bool
where
Behavior: self::WindowBehavior<AppMessage>,
{
@ -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<AppMessage> Application<AppMessage> for RunningWindow<AppMessage>
where
AppMessage: Message,
{
fn app(&self) -> App<AppMessage> {
self.app.clone()
}
fn send(&mut self, message: AppMessage) -> Option<<AppMessage as Message>::Response> {
self.app
.proxy
@ -585,14 +623,10 @@ impl<AppMessage> private::ApplicationSealed<AppMessage> for RunningWindow<AppMes
where
AppMessage: Message,
{
fn app(&self) -> App<AppMessage> {
self.app.clone()
}
fn open(
&self,
attrs: WindowAttributes,
sender: mpsc::SyncSender<WindowMessage>,
attrs: WindowAttributes<AppMessage::Window>,
sender: mpsc::SyncSender<WindowMessage<AppMessage::Window>>,
) -> Result<Option<Arc<winit::window::Window>>, 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>) -> AppMessage::Response + 'static,
app_callback: impl FnMut(AppMessage, &Windows<AppMessage::Window>) -> 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>) -> AppMessage::Response + 'static,
app_callback: impl FnMut(AppMessage, &Windows<AppMessage::Window>) -> 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: &App) -> Result<Option<Window>, OsError>
fn open<App>(app: &App) -> Result<Option<Window<AppMessage::Window>>, OsError>
where
App: Application<AppMessage>,
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: &App, context: Self::Context) -> Result<Option<Window>, OsError>
fn open_with<App>(
app: &App,
context: Self::Context,
) -> Result<Option<Window<AppMessage::Window>>, OsError>
where
App: Application<AppMessage>,
{
@ -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<AppMessage>, event: AppMessage::Window) {}
}
pub trait Run: WindowBehavior<()> {