mirror of
https://github.com/danbulant/appit
synced 2026-06-15 12:21:20 +00:00
Added ability to send messages to windows.
Gooey is using this to send invalidation messages from the UI callbacks.
This commit is contained in:
parent
13e0864bcd
commit
1b5da400f3
3 changed files with 117 additions and 67 deletions
65
src/lib.rs
65
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<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>>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
||||
|
|
|
|||
105
src/window.rs
105
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<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<()> {
|
||||
|
|
|
|||
Loading…
Reference in a new issue