mirror of
https://github.com/danbulant/appit
synced 2026-05-26 21:32:02 +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`
|
- All `&Appplication` bounds now are `?Sized`, enabling `&dyn Application`
|
||||||
parameters.
|
parameters.
|
||||||
|
- Redraw requests from `winit` now block the event loop thread until the window
|
||||||
|
has been repainted.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
|
|
||||||
16
src/lib.rs
16
src/lib.rs
|
|
@ -10,9 +10,10 @@ mod window;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::sync::{mpsc, Arc, Mutex, PoisonError};
|
use std::sync::{mpsc, Arc, Mutex, PoisonError};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use private::{OpenedWindow, WindowSpawner};
|
use private::{OpenedWindow, WindowSpawner};
|
||||||
pub use window::{RunningWindow, Window, WindowAttributes, WindowBehavior, WindowBuilder};
|
pub use window::{Run, RunningWindow, Window, WindowAttributes, WindowBehavior, WindowBuilder};
|
||||||
pub use winit;
|
pub use winit;
|
||||||
use winit::application::ApplicationHandler;
|
use winit::application::ApplicationHandler;
|
||||||
use winit::error::{EventLoopError, OsError};
|
use winit::error::{EventLoopError, OsError};
|
||||||
|
|
@ -149,10 +150,13 @@ where
|
||||||
window_id: WindowId,
|
window_id: WindowId,
|
||||||
event: winit::event::WindowEvent,
|
event: winit::event::WindowEvent,
|
||||||
) {
|
) {
|
||||||
let event = WindowEvent::from(event);
|
let (event, waiter) = WindowEvent::from_winit(event);
|
||||||
self.running
|
self.running
|
||||||
.windows
|
.windows
|
||||||
.send(window_id, WindowMessage::Event(event));
|
.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>) {
|
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
|
/// Gets an instance of the winit window for the given window id, if it has
|
||||||
/// been opened and is still open.
|
/// been opened and is still open.
|
||||||
pub fn get(&self, id: WindowId) -> Option<Arc<winit::window::Window>> {
|
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())
|
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 winit = Arc::new(target.create_window(builder)?);
|
||||||
let id = winit.id();
|
let id = winit.id();
|
||||||
let winit = OpenedWindow(Arc::new(Mutex::new(Some(winit))));
|
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(
|
windows.insert(
|
||||||
id,
|
id,
|
||||||
OpenWindow {
|
OpenWindow {
|
||||||
|
|
@ -453,7 +457,7 @@ impl<Message> Windows<Message> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send(&self, window: WindowId, message: WindowMessage<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) {
|
if let Some(open_window) = data.get(&window) {
|
||||||
match open_window.sender.try_send(message) {
|
match open_window.sender.try_send(message) {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
|
|
@ -469,7 +473,7 @@ impl<Message> Windows<Message> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close(&self, window: WindowId) -> bool {
|
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.remove(&window);
|
||||||
data.is_empty()
|
data.is_empty()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{mpsc, Arc, Mutex, PoisonError};
|
use std::sync::{mpsc, Arc, Mutex, PoisonError};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use winit::dpi::{PhysicalPosition, PhysicalSize};
|
use winit::dpi::{PhysicalPosition, PhysicalSize};
|
||||||
use winit::error::OsError;
|
use winit::error::OsError;
|
||||||
|
|
@ -34,7 +35,7 @@ impl OpenedWindow {
|
||||||
pub fn winit(&self) -> Option<Arc<winit::window::Window>> {
|
pub fn winit(&self) -> Option<Arc<winit::window::Window>> {
|
||||||
self.0
|
self.0
|
||||||
.lock()
|
.lock()
|
||||||
.map_or_else(PoisonError::into_inner, |g| g)
|
.unwrap_or_else(PoisonError::into_inner)
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -63,9 +64,33 @@ pub enum WindowMessage<User> {
|
||||||
Event(WindowEvent),
|
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)]
|
#[derive(Debug)]
|
||||||
pub enum WindowEvent {
|
pub enum WindowEvent {
|
||||||
RedrawRequested,
|
RedrawRequested(RedrawGuard),
|
||||||
|
|
||||||
/// The size of the window has changed. Contains the client area's new dimensions.
|
/// The size of the window has changed. Contains the client area's new dimensions.
|
||||||
Resized(PhysicalSize<u32>),
|
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
|
#[allow(clippy::too_many_lines)] // it's a match statement
|
||||||
fn from(event: winit::event::WindowEvent) -> Self {
|
pub fn from_winit(event: winit::event::WindowEvent) -> (Self, Option<WaitForRedraw>) {
|
||||||
match event {
|
(
|
||||||
winit::event::WindowEvent::RedrawRequested => Self::RedrawRequested,
|
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::Resized(size) => Self::Resized(size),
|
||||||
winit::event::WindowEvent::Moved(pos) => Self::Moved(pos),
|
winit::event::WindowEvent::Moved(pos) => Self::Moved(pos),
|
||||||
winit::event::WindowEvent::CloseRequested => Self::CloseRequested,
|
winit::event::WindowEvent::CloseRequested => Self::CloseRequested,
|
||||||
|
|
@ -381,6 +410,8 @@ impl From<winit::event::WindowEvent> for WindowEvent {
|
||||||
winit::event::WindowEvent::ActivationTokenDone { serial, token } => {
|
winit::event::WindowEvent::ActivationTokenDone { serial, token } => {
|
||||||
Self::ActivationTokenDone { serial, token }
|
Self::ActivationTokenDone { serial, token }
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use winit::event::{
|
||||||
use winit::keyboard::PhysicalKey;
|
use winit::keyboard::PhysicalKey;
|
||||||
use winit::window::{Fullscreen, Icon, Theme, WindowButtons, WindowId, WindowLevel};
|
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::{
|
use crate::{
|
||||||
App, Application, AsApplication, EventLoopMessage, Message, PendingApp, WindowMessage, Windows,
|
App, Application, AsApplication, EventLoopMessage, Message, PendingApp, WindowMessage, Windows,
|
||||||
};
|
};
|
||||||
|
|
@ -34,7 +34,7 @@ impl<Message> Window<Message> {
|
||||||
self.opened
|
self.opened
|
||||||
.0
|
.0
|
||||||
.lock()
|
.lock()
|
||||||
.map_or_else(PoisonError::into_inner, |g| g)
|
.unwrap_or_else(PoisonError::into_inner)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|w| w.id())
|
.map(|w| w.id())
|
||||||
}
|
}
|
||||||
|
|
@ -264,6 +264,12 @@ where
|
||||||
type SyncArcChannel<T> = (Arc<mpsc::SyncSender<T>>, mpsc::Receiver<T>);
|
type SyncArcChannel<T> = (Arc<mpsc::SyncSender<T>>, mpsc::Receiver<T>);
|
||||||
type SyncChannel<T> = (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.
|
/// A window that is running in its own thread.
|
||||||
pub struct RunningWindow<AppMessage>
|
pub struct RunningWindow<AppMessage>
|
||||||
where
|
where
|
||||||
|
|
@ -442,9 +448,15 @@ where
|
||||||
// the entire app panics or not.
|
// the entire app panics or not.
|
||||||
let possible_panic = std::panic::catch_unwind(AssertUnwindSafe(move || {
|
let possible_panic = std::panic::catch_unwind(AssertUnwindSafe(move || {
|
||||||
let mut behavior = Behavior::initialize(&mut self, context);
|
let mut behavior = Behavior::initialize(&mut self, context);
|
||||||
while !self.close && self.process_messages_until_redraw(&mut behavior) {
|
while !self.close {
|
||||||
self.next_redraw_target = None;
|
match self.process_messages_until_redraw(&mut behavior) {
|
||||||
behavior.redraw(&mut self);
|
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
|
// Do not notify the main thread to close the window until after the
|
||||||
// behavior is dropped. This upholds the requirement for RawWindowHandle
|
// 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
|
where
|
||||||
Behavior: self::WindowBehavior<AppMessage>,
|
Behavior: self::WindowBehavior<AppMessage>,
|
||||||
{
|
{
|
||||||
|
|
@ -471,27 +486,29 @@ where
|
||||||
// block.
|
// block.
|
||||||
TimeUntilRedraw::None => match self.messages.1.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 Err(()),
|
||||||
Err(mpsc::TryRecvError::Empty) => return true,
|
Err(mpsc::TryRecvError::Empty) => return Ok(None),
|
||||||
},
|
},
|
||||||
// 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.1.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 Ok(None),
|
||||||
Err(mpsc::RecvTimeoutError::Disconnected) => return false,
|
Err(mpsc::RecvTimeoutError::Disconnected) => return Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// No scheduled redraw time, sleep until the next message.
|
// No scheduled redraw time, sleep until the next message.
|
||||||
TimeUntilRedraw::Indefinite => match self.messages.1.recv() {
|
TimeUntilRedraw::Indefinite => match self.messages.1.recv() {
|
||||||
Ok(message) => message,
|
Ok(message) => message,
|
||||||
Err(_) => return false,
|
Err(_) => return Err(()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if !self.handle_message(message, behavior) {
|
match self.handle_message(message, behavior) {
|
||||||
break false;
|
HandleMessageResult::Ok => {}
|
||||||
|
HandleMessageResult::RedrawRequired(guard) => return Ok(Some(guard)),
|
||||||
|
HandleMessageResult::Destroyed => return Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -501,15 +518,16 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
message: WindowMessage<AppMessage::Window>,
|
message: WindowMessage<AppMessage::Window>,
|
||||||
behavior: &mut Behavior,
|
behavior: &mut Behavior,
|
||||||
) -> bool
|
) -> HandleMessageResult
|
||||||
where
|
where
|
||||||
Behavior: self::WindowBehavior<AppMessage>,
|
Behavior: self::WindowBehavior<AppMessage>,
|
||||||
{
|
{
|
||||||
match message {
|
match message {
|
||||||
WindowMessage::User(user) => behavior.event(self, user),
|
WindowMessage::User(user) => behavior.event(self, user),
|
||||||
WindowMessage::Event(evt) => match evt {
|
WindowMessage::Event(evt) => match evt {
|
||||||
WindowEvent::RedrawRequested => {
|
WindowEvent::RedrawRequested(guard) => {
|
||||||
self.set_needs_redraw();
|
self.set_needs_redraw();
|
||||||
|
return HandleMessageResult::RedrawRequired(guard);
|
||||||
}
|
}
|
||||||
WindowEvent::CloseRequested => {
|
WindowEvent::CloseRequested => {
|
||||||
if behavior.close_requested(self) {
|
if behavior.close_requested(self) {
|
||||||
|
|
@ -546,7 +564,7 @@ where
|
||||||
self.position = position;
|
self.position = position;
|
||||||
}
|
}
|
||||||
WindowEvent::Destroyed => {
|
WindowEvent::Destroyed => {
|
||||||
return false;
|
return HandleMessageResult::Destroyed;
|
||||||
}
|
}
|
||||||
WindowEvent::ThemeChanged(theme) => {
|
WindowEvent::ThemeChanged(theme) => {
|
||||||
self.theme = theme;
|
self.theme = theme;
|
||||||
|
|
@ -667,7 +685,7 @@ where
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
HandleMessageResult::Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets this window to close as soon as possible.
|
/// 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) {}
|
fn event(&mut self, window: &mut RunningWindow<AppMessage>, event: AppMessage::Window) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A runnable window.
|
||||||
pub trait Run: WindowBehavior<()> {
|
pub trait Run: WindowBehavior<()> {
|
||||||
/// Runs a window with a default instance of this behavior's
|
/// Runs a window with a default instance of this behavior's
|
||||||
/// [`Context`](Self::Context).
|
/// [`Context`](Self::Context).
|
||||||
///
|
///
|
||||||
/// This function is shorthand for creating a [`PendingApp`], opening this
|
/// This function is shorthand for creating a [`PendingApp`], opening this
|
||||||
/// window inside of it, and running the pending app.
|
/// 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>
|
fn run() -> Result<(), EventLoopError>
|
||||||
where
|
where
|
||||||
Self::Context: Default,
|
Self::Context: Default,
|
||||||
|
|
@ -1109,6 +1133,11 @@ pub trait Run: WindowBehavior<()> {
|
||||||
///
|
///
|
||||||
/// This function is shorthand for creating a [`PendingApp`], opening this
|
/// This function is shorthand for creating a [`PendingApp`], opening this
|
||||||
/// window inside of it, and running the pending app.
|
/// 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> {
|
fn run_with(context: Self::Context) -> Result<(), EventLoopError> {
|
||||||
let mut app = PendingApp::new();
|
let mut app = PendingApp::new();
|
||||||
Self::open_with(&mut app, context).expect("error opening initial window");
|
Self::open_with(&mut app, context).expect("error opening initial window");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue