mirror of
https://github.com/danbulant/appit
synced 2026-05-19 04:08:34 +00:00
Attempt to make resizes repaint in time
This introduces a RedrawGuard concept, which allows the event loop to block until the window has been repainted. This hopefully will resolve khonsulabs/cushy#114.
This commit is contained in:
parent
36b8c1fc8b
commit
ee4528cab2
4 changed files with 96 additions and 30 deletions
|
|
@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- All `&Appplication` bounds now are `?Sized`, enabling `&dyn Application`
|
||||
parameters.
|
||||
- Redraw requests from `winit` now block the event loop thread until the window
|
||||
has been repainted.
|
||||
|
||||
### Added
|
||||
|
||||
|
|
|
|||
16
src/lib.rs
16
src/lib.rs
|
|
@ -10,9 +10,10 @@ mod window;
|
|||
use std::collections::HashMap;
|
||||
use std::process::exit;
|
||||
use std::sync::{mpsc, Arc, Mutex, PoisonError};
|
||||
use std::time::Duration;
|
||||
|
||||
use private::{OpenedWindow, WindowSpawner};
|
||||
pub use window::{RunningWindow, Window, WindowAttributes, WindowBehavior, WindowBuilder};
|
||||
pub use window::{Run, RunningWindow, Window, WindowAttributes, WindowBehavior, WindowBuilder};
|
||||
pub use winit;
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::error::{EventLoopError, OsError};
|
||||
|
|
@ -149,10 +150,13 @@ where
|
|||
window_id: WindowId,
|
||||
event: winit::event::WindowEvent,
|
||||
) {
|
||||
let event = WindowEvent::from(event);
|
||||
let (event, waiter) = WindowEvent::from_winit(event);
|
||||
self.running
|
||||
.windows
|
||||
.send(window_id, WindowMessage::Event(event));
|
||||
if let Some(waiter) = waiter {
|
||||
waiter.wait(Duration::from_millis(16));
|
||||
}
|
||||
}
|
||||
|
||||
fn user_event(&mut self, event_loop: &ActiveEventLoop, message: EventLoopMessage<AppMessage>) {
|
||||
|
|
@ -379,7 +383,7 @@ impl<Message> Windows<Message> {
|
|||
/// Gets an instance of the winit window for the given window id, if it has
|
||||
/// been opened and is still open.
|
||||
pub fn get(&self, id: WindowId) -> Option<Arc<winit::window::Window>> {
|
||||
let windows = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
let windows = self.data.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
windows.get(&id).and_then(|w| w.winit.winit())
|
||||
}
|
||||
|
||||
|
|
@ -441,7 +445,7 @@ impl<Message> Windows<Message> {
|
|||
let winit = Arc::new(target.create_window(builder)?);
|
||||
let id = winit.id();
|
||||
let winit = OpenedWindow(Arc::new(Mutex::new(Some(winit))));
|
||||
let mut windows = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
let mut windows = self.data.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
windows.insert(
|
||||
id,
|
||||
OpenWindow {
|
||||
|
|
@ -453,7 +457,7 @@ impl<Message> Windows<Message> {
|
|||
}
|
||||
|
||||
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().unwrap_or_else(PoisonError::into_inner);
|
||||
if let Some(open_window) = data.get(&window) {
|
||||
match open_window.sender.try_send(message) {
|
||||
Ok(()) => {}
|
||||
|
|
@ -469,7 +473,7 @@ impl<Message> Windows<Message> {
|
|||
}
|
||||
|
||||
fn close(&self, window: WindowId) -> bool {
|
||||
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
let mut data = self.data.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
data.remove(&window);
|
||||
data.is_empty()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::path::PathBuf;
|
||||
use std::sync::{mpsc, Arc, Mutex, PoisonError};
|
||||
use std::time::Duration;
|
||||
|
||||
use winit::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use winit::error::OsError;
|
||||
|
|
@ -34,7 +35,7 @@ impl OpenedWindow {
|
|||
pub fn winit(&self) -> Option<Arc<winit::window::Window>> {
|
||||
self.0
|
||||
.lock()
|
||||
.map_or_else(PoisonError::into_inner, |g| g)
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
|
@ -63,9 +64,33 @@ pub enum WindowMessage<User> {
|
|||
Event(WindowEvent),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RedrawGuard(mpsc::SyncSender<()>);
|
||||
|
||||
impl Drop for RedrawGuard {
|
||||
fn drop(&mut self) {
|
||||
let _ignored = self.0.send(());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WaitForRedraw(mpsc::Receiver<()>);
|
||||
|
||||
impl WaitForRedraw {
|
||||
pub fn wait(self, timeout: Duration) {
|
||||
let _result = self.0.recv_timeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
impl RedrawGuard {
|
||||
pub fn new() -> (Self, WaitForRedraw) {
|
||||
let (sender, receiver) = mpsc::sync_channel(1);
|
||||
(Self(sender), WaitForRedraw(receiver))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WindowEvent {
|
||||
RedrawRequested,
|
||||
RedrawRequested(RedrawGuard),
|
||||
|
||||
/// The size of the window has changed. Contains the client area's new dimensions.
|
||||
Resized(PhysicalSize<u32>),
|
||||
|
|
@ -262,11 +287,15 @@ pub enum WindowEvent {
|
|||
},
|
||||
}
|
||||
|
||||
impl From<winit::event::WindowEvent> for WindowEvent {
|
||||
impl WindowEvent {
|
||||
#[allow(clippy::too_many_lines)] // it's a match statement
|
||||
fn from(event: winit::event::WindowEvent) -> Self {
|
||||
match event {
|
||||
winit::event::WindowEvent::RedrawRequested => Self::RedrawRequested,
|
||||
pub fn from_winit(event: winit::event::WindowEvent) -> (Self, Option<WaitForRedraw>) {
|
||||
(
|
||||
match event {
|
||||
winit::event::WindowEvent::RedrawRequested => {
|
||||
let (guard, wait) = RedrawGuard::new();
|
||||
return (Self::RedrawRequested(guard), Some(wait))
|
||||
},
|
||||
winit::event::WindowEvent::Resized(size) => Self::Resized(size),
|
||||
winit::event::WindowEvent::Moved(pos) => Self::Moved(pos),
|
||||
winit::event::WindowEvent::CloseRequested => Self::CloseRequested,
|
||||
|
|
@ -381,6 +410,8 @@ impl From<winit::event::WindowEvent> for WindowEvent {
|
|||
winit::event::WindowEvent::ActivationTokenDone { serial, token } => {
|
||||
Self::ActivationTokenDone { serial, token }
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use winit::event::{
|
|||
use winit::keyboard::PhysicalKey;
|
||||
use winit::window::{Fullscreen, Icon, Theme, WindowButtons, WindowId, WindowLevel};
|
||||
|
||||
use crate::private::{self, OpenedWindow, WindowEvent, WindowSpawner};
|
||||
use crate::private::{self, OpenedWindow, RedrawGuard, WindowEvent, WindowSpawner};
|
||||
use crate::{
|
||||
App, Application, AsApplication, EventLoopMessage, Message, PendingApp, WindowMessage, Windows,
|
||||
};
|
||||
|
|
@ -34,7 +34,7 @@ impl<Message> Window<Message> {
|
|||
self.opened
|
||||
.0
|
||||
.lock()
|
||||
.map_or_else(PoisonError::into_inner, |g| g)
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.as_ref()
|
||||
.map(|w| w.id())
|
||||
}
|
||||
|
|
@ -264,6 +264,12 @@ where
|
|||
type SyncArcChannel<T> = (Arc<mpsc::SyncSender<T>>, mpsc::Receiver<T>);
|
||||
type SyncChannel<T> = (mpsc::SyncSender<T>, mpsc::Receiver<T>);
|
||||
|
||||
enum HandleMessageResult {
|
||||
Ok,
|
||||
RedrawRequired(RedrawGuard),
|
||||
Destroyed,
|
||||
}
|
||||
|
||||
/// A window that is running in its own thread.
|
||||
pub struct RunningWindow<AppMessage>
|
||||
where
|
||||
|
|
@ -442,9 +448,15 @@ where
|
|||
// the entire app panics or not.
|
||||
let possible_panic = std::panic::catch_unwind(AssertUnwindSafe(move || {
|
||||
let mut behavior = Behavior::initialize(&mut self, context);
|
||||
while !self.close && self.process_messages_until_redraw(&mut behavior) {
|
||||
self.next_redraw_target = None;
|
||||
behavior.redraw(&mut self);
|
||||
while !self.close {
|
||||
match self.process_messages_until_redraw(&mut behavior) {
|
||||
Ok(guard) => {
|
||||
self.next_redraw_target = None;
|
||||
behavior.redraw(&mut self);
|
||||
drop(guard);
|
||||
}
|
||||
Err(()) => break,
|
||||
}
|
||||
}
|
||||
// Do not notify the main thread to close the window until after the
|
||||
// behavior is dropped. This upholds the requirement for RawWindowHandle
|
||||
|
|
@ -460,7 +472,10 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn process_messages_until_redraw<Behavior>(&mut self, behavior: &mut Behavior) -> bool
|
||||
fn process_messages_until_redraw<Behavior>(
|
||||
&mut self,
|
||||
behavior: &mut Behavior,
|
||||
) -> Result<Option<RedrawGuard>, ()>
|
||||
where
|
||||
Behavior: self::WindowBehavior<AppMessage>,
|
||||
{
|
||||
|
|
@ -471,27 +486,29 @@ where
|
|||
// block.
|
||||
TimeUntilRedraw::None => match self.messages.1.try_recv() {
|
||||
Ok(message) => message,
|
||||
Err(mpsc::TryRecvError::Disconnected) => return false,
|
||||
Err(mpsc::TryRecvError::Empty) => return true,
|
||||
Err(mpsc::TryRecvError::Disconnected) => return Err(()),
|
||||
Err(mpsc::TryRecvError::Empty) => return Ok(None),
|
||||
},
|
||||
// We have a scheduled time for the next frame, and it hasn't
|
||||
// elapsed yet.
|
||||
TimeUntilRedraw::Some(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,
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => return Ok(None),
|
||||
Err(mpsc::RecvTimeoutError::Disconnected) => return Err(()),
|
||||
}
|
||||
}
|
||||
// No scheduled redraw time, sleep until the next message.
|
||||
TimeUntilRedraw::Indefinite => match self.messages.1.recv() {
|
||||
Ok(message) => message,
|
||||
Err(_) => return false,
|
||||
Err(_) => return Err(()),
|
||||
},
|
||||
};
|
||||
|
||||
if !self.handle_message(message, behavior) {
|
||||
break false;
|
||||
match self.handle_message(message, behavior) {
|
||||
HandleMessageResult::Ok => {}
|
||||
HandleMessageResult::RedrawRequired(guard) => return Ok(Some(guard)),
|
||||
HandleMessageResult::Destroyed => return Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -501,15 +518,16 @@ where
|
|||
&mut self,
|
||||
message: WindowMessage<AppMessage::Window>,
|
||||
behavior: &mut Behavior,
|
||||
) -> bool
|
||||
) -> HandleMessageResult
|
||||
where
|
||||
Behavior: self::WindowBehavior<AppMessage>,
|
||||
{
|
||||
match message {
|
||||
WindowMessage::User(user) => behavior.event(self, user),
|
||||
WindowMessage::Event(evt) => match evt {
|
||||
WindowEvent::RedrawRequested => {
|
||||
WindowEvent::RedrawRequested(guard) => {
|
||||
self.set_needs_redraw();
|
||||
return HandleMessageResult::RedrawRequired(guard);
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
if behavior.close_requested(self) {
|
||||
|
|
@ -546,7 +564,7 @@ where
|
|||
self.position = position;
|
||||
}
|
||||
WindowEvent::Destroyed => {
|
||||
return false;
|
||||
return HandleMessageResult::Destroyed;
|
||||
}
|
||||
WindowEvent::ThemeChanged(theme) => {
|
||||
self.theme = theme;
|
||||
|
|
@ -667,7 +685,7 @@ where
|
|||
},
|
||||
}
|
||||
|
||||
true
|
||||
HandleMessageResult::Ok
|
||||
}
|
||||
|
||||
/// Sets this window to close as soon as possible.
|
||||
|
|
@ -1090,12 +1108,18 @@ where
|
|||
fn event(&mut self, window: &mut RunningWindow<AppMessage>, event: AppMessage::Window) {}
|
||||
}
|
||||
|
||||
/// A runnable window.
|
||||
pub trait Run: WindowBehavior<()> {
|
||||
/// Runs a window with a default instance of this behavior's
|
||||
/// [`Context`](Self::Context).
|
||||
///
|
||||
/// This function is shorthand for creating a [`PendingApp`], opening this
|
||||
/// window inside of it, and running the pending app.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an [`EventLoopError`] upon the loop exiting due to an error. See
|
||||
/// [`EventLoop::run`] for more information.
|
||||
fn run() -> Result<(), EventLoopError>
|
||||
where
|
||||
Self::Context: Default,
|
||||
|
|
@ -1109,6 +1133,11 @@ pub trait Run: WindowBehavior<()> {
|
|||
///
|
||||
/// This function is shorthand for creating a [`PendingApp`], opening this
|
||||
/// window inside of it, and running the pending app.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an [`EventLoopError`] upon the loop exiting due to an error. See
|
||||
/// [`EventLoop::run`] for more information.
|
||||
fn run_with(context: Self::Context) -> Result<(), EventLoopError> {
|
||||
let mut app = PendingApp::new();
|
||||
Self::open_with(&mut app, context).expect("error opening initial window");
|
||||
|
|
|
|||
Loading…
Reference in a new issue