App::prevent_shutdown()

This commit is contained in:
Jonathan Johnson 2024-09-12 07:37:14 -07:00
parent 93479b8111
commit 331bfdd353
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
3 changed files with 76 additions and 8 deletions

View file

@ -32,6 +32,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `WindowBehavior::moved` is called when the window moves. - `WindowBehavior::moved` is called when the window moves.
- `RunningWindow::outer_size` is a new function that returns the window's - `RunningWindow::outer_size` is a new function that returns the window's
current size including decorations. current size including decorations.
- `App::prevent_shutdown()` returns a guard that prevents the application from
closing automatically when the final window is closed.
### Changed ### Changed

View file

@ -325,6 +325,14 @@ where
ExecutingApp::new(&self.running.windows, event_loop), ExecutingApp::new(&self.running.windows, event_loop),
)); ));
} }
EventLoopMessage::PreventShutdown => {
self.running.windows.prevent_shutdown();
}
EventLoopMessage::AllowShutdown => {
if self.running.windows.allow_shutdown() {
exit(0)
}
}
} }
} }
} }
@ -365,6 +373,20 @@ where
.ok()?; .ok()?;
response_receiver.recv().ok() response_receiver.recv().ok()
} }
/// Creates a guard that prevents this app from shutting down.
///
/// If the app is not currently running, this function returns None.
///
/// Once a guard is allocated the app will not be closed automatically when
/// the final window is closed. If the final shutdown guard is dropped while
/// no windows are open, the app will be closed.
pub fn prevent_shutdown(&self) -> Option<ShutdownGuard<AppMessage>> {
self.proxy
.send_event(EventLoopMessage::PreventShutdown)
.ok()
.map(|()| ShutdownGuard { app: self.clone() })
}
} }
impl<AppMessage> Clone for App<AppMessage> impl<AppMessage> Clone for App<AppMessage>
@ -540,13 +562,27 @@ where
/// A collection of open windows. /// A collection of open windows.
pub struct Windows<Message> { pub struct Windows<Message> {
data: Arc<Mutex<HashMap<WindowId, OpenWindow<Message>>>>, data: Arc<Mutex<WindowsData<Message>>>,
}
struct WindowsData<Message> {
open: HashMap<WindowId, OpenWindow<Message>>,
guards: usize,
}
impl<Message> WindowsData<Message> {
fn should_shutdown(&self) -> bool {
self.open.is_empty() && self.guards == 0
}
} }
impl<Message> Default for Windows<Message> { impl<Message> Default for Windows<Message> {
fn default() -> Self { fn default() -> Self {
Self { Self {
data: Arc::default(), data: Arc::new(Mutex::new(WindowsData {
open: HashMap::new(),
guards: 0,
})),
} }
} }
} }
@ -564,7 +600,7 @@ impl<Message> Windows<Message> {
/// 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().unwrap_or_else(PoisonError::into_inner); let windows = self.data.lock().unwrap_or_else(PoisonError::into_inner);
windows.get(&id).and_then(|w| w.winit.winit()) windows.open.get(&id).and_then(|w| w.winit.winit())
} }
#[allow(unsafe_code)] #[allow(unsafe_code)]
@ -626,7 +662,7 @@ impl<Message> Windows<Message> {
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().unwrap_or_else(PoisonError::into_inner); let mut windows = self.data.lock().unwrap_or_else(PoisonError::into_inner);
windows.insert( windows.open.insert(
id, id,
OpenWindow { OpenWindow {
winit: winit.clone(), winit: winit.clone(),
@ -638,7 +674,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().unwrap_or_else(PoisonError::into_inner); 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.open.get(&window) {
match open_window.sender.try_send(message) { match open_window.sender.try_send(message) {
Ok(()) => {} Ok(()) => {}
Err(mpsc::TrySendError::Full(_)) => { Err(mpsc::TrySendError::Full(_)) => {
@ -646,7 +682,7 @@ impl<Message> Windows<Message> {
} }
Err(mpsc::TrySendError::Disconnected(_)) => { Err(mpsc::TrySendError::Disconnected(_)) => {
// Window no longer active, remove it. // Window no longer active, remove it.
data.remove(&window); data.open.remove(&window);
} }
} }
} }
@ -654,10 +690,21 @@ impl<Message> Windows<Message> {
fn close(&self, window: WindowId) -> bool { fn close(&self, window: WindowId) -> bool {
let mut data = self.data.lock().unwrap_or_else(PoisonError::into_inner); let mut data = self.data.lock().unwrap_or_else(PoisonError::into_inner);
if let Some(closed) = data.remove(&window) { if let Some(closed) = data.open.remove(&window) {
closed.winit.close(); closed.winit.close();
} }
data.is_empty() data.should_shutdown()
}
fn prevent_shutdown(&self) {
let mut data = self.data.lock().unwrap_or_else(PoisonError::into_inner);
data.guards += 1;
}
fn allow_shutdown(&self) -> bool {
let mut data = self.data.lock().unwrap_or_else(PoisonError::into_inner);
data.guards -= 1;
data.should_shutdown()
} }
} }
@ -665,3 +712,20 @@ struct OpenWindow<User> {
winit: OpenedWindow, winit: OpenedWindow,
sender: Arc<mpsc::SyncSender<WindowMessage<User>>>, sender: Arc<mpsc::SyncSender<WindowMessage<User>>>,
} }
/// A guard preventing an [`App`] from shutting down.
pub struct ShutdownGuard<Message>
where
Message: crate::Message,
{
app: App<Message>,
}
impl<Message> Drop for ShutdownGuard<Message>
where
Message: crate::Message,
{
fn drop(&mut self) {
let _ = self.app.proxy.send_event(EventLoopMessage::AllowShutdown);
}
}

View file

@ -60,6 +60,8 @@ where
message: AppMessage, message: AppMessage,
response_sender: mpsc::SyncSender<AppMessage::Response>, response_sender: mpsc::SyncSender<AppMessage::Response>,
}, },
PreventShutdown,
AllowShutdown,
} }
#[derive(Debug)] #[derive(Debug)]