diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 52e8e859ad..f5bfb39090 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -780,6 +780,7 @@ type GlobalObservationCallback = Box; type ReleaseObservationCallback = Box; type ActionObservationCallback = Box; type DeserializeActionCallback = fn(json: &str) -> anyhow::Result>; +type WindowShouldCloseSubscriptionCallback = Box bool>; pub struct MutableAppContext { weak_self: Option>>, @@ -2004,6 +2005,12 @@ impl MutableAppContext { Effect::ActionDispatchNotification { action_id } => { self.handle_action_dispatch_notification_effect(action_id) } + Effect::WindowShouldCloseSubscription { + window_id, + callback, + } => { + self.handle_window_should_close_subscription_effect(window_id, callback) + } } self.pending_notifications.clear(); self.remove_dropped_entities(); @@ -2451,6 +2458,17 @@ impl MutableAppContext { self.action_dispatch_observations.lock().extend(callbacks); } + fn handle_window_should_close_subscription_effect( + &mut self, + window_id: usize, + mut callback: WindowShouldCloseSubscriptionCallback, + ) { + let mut app = self.upgrade(); + if let Some((_, window)) = self.presenters_and_platform_windows.get_mut(&window_id) { + window.on_should_close(Box::new(move || app.update(|cx| callback(cx)))) + } + } + pub fn focus(&mut self, window_id: usize, view_id: Option) { if let Some(pending_focus_index) = self.pending_focus_index { self.pending_effects.remove(pending_focus_index); @@ -2828,6 +2846,10 @@ pub enum Effect { ActionDispatchNotification { action_id: TypeId, }, + WindowShouldCloseSubscription { + window_id: usize, + callback: WindowShouldCloseSubscriptionCallback, + }, } impl Debug for Effect { @@ -2921,6 +2943,10 @@ impl Debug for Effect { .field("is_active", is_active) .finish(), Effect::RefreshWindows => f.debug_struct("Effect::FullViewRefresh").finish(), + Effect::WindowShouldCloseSubscription { window_id, .. } => f + .debug_struct("Effect::WindowShouldCloseSubscription") + .field("window_id", window_id) + .finish(), } } } @@ -3346,6 +3372,25 @@ impl<'a, T: View> ViewContext<'a, T> { } } + pub fn on_window_should_close(&mut self, mut callback: F) + where + F: 'static + FnMut(&mut T, &mut ViewContext) -> bool, + { + let window_id = self.window_id(); + let view = self.weak_handle(); + self.pending_effects + .push_back(Effect::WindowShouldCloseSubscription { + window_id, + callback: Box::new(move |cx| { + if let Some(view) = view.upgrade(cx) { + view.update(cx, |view, cx| callback(view, cx)) + } else { + true + } + }), + }); + } + pub fn add_model(&mut self, build_model: F) -> ModelHandle where S: Entity, diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 6c259537ad..b4875df3f5 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -93,6 +93,7 @@ pub trait Window: WindowContext { fn on_event(&mut self, callback: Box bool>); fn on_active_status_change(&mut self, callback: Box); fn on_resize(&mut self, callback: Box); + fn on_should_close(&mut self, callback: Box bool>); fn on_close(&mut self, callback: Box); fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver; fn activate(&self); diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index e16bac5a0d..367a9d70c8 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -81,6 +81,10 @@ unsafe fn build_classes() { sel!(windowDidResignKey:), window_did_change_key_status as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(windowShouldClose:), + window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL, + ); decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel)); decl.register() }; @@ -167,6 +171,7 @@ struct WindowState { event_callback: Option bool>>, activate_callback: Option>, resize_callback: Option>, + should_close_callback: Option bool>>, close_callback: Option>, synthetic_drag_counter: usize, executor: Rc, @@ -242,6 +247,7 @@ impl Window { native_window, event_callback: None, resize_callback: None, + should_close_callback: None, close_callback: None, activate_callback: None, synthetic_drag_counter: 0, @@ -339,6 +345,10 @@ impl platform::Window for Window { self.0.as_ref().borrow_mut().resize_callback = Some(callback); } + fn on_should_close(&mut self, callback: Box bool>) { + self.0.as_ref().borrow_mut().should_close_callback = Some(callback); + } + fn on_close(&mut self, callback: Box) { self.0.as_ref().borrow_mut().close_callback = Some(callback); } @@ -674,6 +684,19 @@ extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) .detach(); } +extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL { + let window_state = unsafe { get_window_state(this) }; + let mut window_state_borrow = window_state.as_ref().borrow_mut(); + if let Some(mut callback) = window_state_borrow.should_close_callback.take() { + drop(window_state_borrow); + let should_close = callback(); + window_state.borrow_mut().should_close_callback = Some(callback); + should_close as BOOL + } else { + YES + } +} + extern "C" fn close_window(this: &Object, _: Sel) { unsafe { let close_callback = { diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index fd07ef80a2..9ffd95ea0e 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -37,6 +37,7 @@ pub struct Window { event_handlers: Vec bool>>, resize_handlers: Vec>, close_handlers: Vec>, + should_close_handler: Option bool>>, pub(crate) title: Option, pub(crate) edited: bool, pub(crate) pending_prompts: RefCell>>, @@ -186,9 +187,10 @@ impl Window { fn new(size: Vector2F) -> Self { Self { size, - event_handlers: Vec::new(), - resize_handlers: Vec::new(), - close_handlers: Vec::new(), + event_handlers: Default::default(), + resize_handlers: Default::default(), + close_handlers: Default::default(), + should_close_handler: Default::default(), scale_factor: 1.0, current_scene: None, title: None, @@ -264,6 +266,10 @@ impl super::Window for Window { fn set_edited(&mut self, edited: bool) { self.edited = edited; } + + fn on_should_close(&mut self, callback: Box bool>) { + self.should_close_handler = Some(callback); + } } pub fn platform() -> Platform {