From ab760493cfc4f75bc6a3bbf2c578efa643017afd Mon Sep 17 00:00:00 2001 From: ForLoveOfCats Date: Tue, 9 Aug 2022 15:34:57 -0400 Subject: [PATCH 1/5] Route whether or not a window is fullscreen down into GPUI This still needs to be able to invalidate things to be useful but it's a good first cut at just making the information available to platform-agnostic code Co-authored-by: Mikayla --- crates/gpui/src/app.rs | 93 +++++++++++++++++++++++++- crates/gpui/src/platform.rs | 1 + crates/gpui/src/platform/mac/window.rs | 32 +++++++++ crates/gpui/src/platform/test.rs | 6 ++ 4 files changed, 129 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 8285fda213..532f59f008 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1248,6 +1248,13 @@ impl MutableAppContext { .map_or(false, |window| window.is_active) } + pub fn window_is_fullscreen(&self, window_id: usize) -> bool { + self.cx + .windows + .get(&window_id) + .map_or(false, |window| window.is_fullscreen) + } + pub fn render_view(&mut self, params: RenderParams) -> Result { let window_id = params.window_id; let view_id = params.view_id; @@ -1934,6 +1941,7 @@ impl MutableAppContext { focused_view_id: Some(root_view.id()), is_active: false, invalidation: None, + is_fullscreen: false, }, ); root_view.update(this, |view, cx| view.on_focus(cx)); @@ -2010,6 +2018,13 @@ impl MutableAppContext { })); } + { + let mut app = self.upgrade(); + window.on_fullscreen(Box::new(move |is_fullscreen| { + app.update(|cx| cx.window_was_fullscreen_changed(window_id, is_fullscreen)) + })); + } + { let mut app = self.upgrade(); window.on_close(Box::new(move || { @@ -2151,7 +2166,9 @@ impl MutableAppContext { subscription_id, callback, } => self.handle_subscription_effect(entity_id, subscription_id, callback), + Effect::Event { entity_id, payload } => self.emit_event(entity_id, payload), + Effect::GlobalSubscription { type_id, subscription_id, @@ -2161,21 +2178,27 @@ impl MutableAppContext { subscription_id, callback, ), + Effect::GlobalEvent { payload } => self.emit_global_event(payload), + Effect::Observation { entity_id, subscription_id, callback, } => self.handle_observation_effect(entity_id, subscription_id, callback), + Effect::ModelNotification { model_id } => { self.handle_model_notification_effect(model_id) } + Effect::ViewNotification { window_id, view_id } => { self.handle_view_notification_effect(window_id, view_id) } + Effect::GlobalNotification { type_id } => { self.handle_global_notification_effect(type_id) } + Effect::Deferred { callback, after_window_update, @@ -2186,15 +2209,19 @@ impl MutableAppContext { callback(self) } } + Effect::ModelRelease { model_id, model } => { self.handle_entity_release_effect(model_id, model.as_any()) } + Effect::ViewRelease { view_id, view } => { self.handle_entity_release_effect(view_id, view.as_any()) } + Effect::Focus { window_id, view_id } => { self.handle_focus_effect(window_id, view_id); } + Effect::FocusObservation { view_id, subscription_id, @@ -2202,6 +2229,7 @@ impl MutableAppContext { } => { self.handle_focus_observation_effect(view_id, subscription_id, callback) } + Effect::ResizeWindow { window_id } => { if let Some(window) = self.cx.windows.get_mut(&window_id) { window @@ -2209,6 +2237,7 @@ impl MutableAppContext { .get_or_insert(WindowInvalidation::default()); } } + Effect::WindowActivationObservation { window_id, subscription_id, @@ -2218,10 +2247,17 @@ impl MutableAppContext { subscription_id, callback, ), + Effect::ActivateWindow { window_id, is_active, } => self.handle_window_activation_effect(window_id, is_active), + + Effect::FullscreenWindow { + window_id, + is_fullscreen, + } => self.handle_fullscreen_effect(window_id, is_fullscreen), + Effect::RefreshWindows => { refreshing = true; } @@ -2294,6 +2330,13 @@ impl MutableAppContext { .push_back(Effect::ResizeWindow { window_id }); } + fn window_was_fullscreen_changed(&mut self, window_id: usize, is_fullscreen: bool) { + self.pending_effects.push_back(Effect::FullscreenWindow { + window_id, + is_fullscreen, + }); + } + fn window_changed_active_status(&mut self, window_id: usize, is_active: bool) { self.pending_effects.push_back(Effect::ActivateWindow { window_id, @@ -2608,13 +2651,36 @@ impl MutableAppContext { } } - fn handle_window_activation_effect(&mut self, window_id: usize, active: bool) { + fn handle_fullscreen_effect(&mut self, window_id: usize, is_fullscreen: bool) { + //Short circuit evaluation if we're already g2g if self .cx .windows .get(&window_id) - .map(|w| w.is_active) - .map_or(false, |cur_active| cur_active == active) + .map(|w| w.is_fullscreen == is_fullscreen) + .unwrap_or(false) + { + return; + } + + self.update(|this| { + let window = this.cx.windows.get_mut(&window_id)?; + window.is_fullscreen = is_fullscreen; + + // self.emit_event(entity_id, payload); + + Some(()) + }); + } + + fn handle_window_activation_effect(&mut self, window_id: usize, active: bool) { + //Short circuit evaluation if we're already g2g + if self + .cx + .windows + .get(&window_id) + .map(|w| w.is_active == active) + .unwrap_or(false) { return; } @@ -2622,6 +2688,8 @@ impl MutableAppContext { self.update(|this| { let window = this.cx.windows.get_mut(&window_id)?; window.is_active = active; + + //Handle focus let view_id = window.focused_view_id?; if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) { if active { @@ -2632,6 +2700,7 @@ impl MutableAppContext { this.cx.views.insert((window_id, view_id), view); } + //Deliver events let callbacks = this .window_activation_observations .lock() @@ -2640,6 +2709,7 @@ impl MutableAppContext { for (id, callback) in callbacks { if let Some(mut callback) = callback { let alive = callback(active, this); + //Put entry back if alive { match this .window_activation_observations @@ -3072,6 +3142,7 @@ struct Window { root_view: AnyViewHandle, focused_view_id: Option, is_active: bool, + is_fullscreen: bool, invalidation: Option, } @@ -3138,6 +3209,10 @@ pub enum Effect { ResizeWindow { window_id: usize, }, + FullscreenWindow { + window_id: usize, + is_fullscreen: bool, + }, ActivateWindow { window_id: usize, is_active: bool, @@ -3256,6 +3331,14 @@ impl Debug for Effect { .field("window_id", window_id) .field("is_active", is_active) .finish(), + Effect::FullscreenWindow { + window_id, + is_fullscreen, + } => f + .debug_struct("Effect::FullscreenWindow") + .field("window_id", window_id) + .field("is_fullscreen", is_fullscreen) + .finish(), Effect::RefreshWindows => f.debug_struct("Effect::FullViewRefresh").finish(), Effect::WindowShouldCloseSubscription { window_id, .. } => f .debug_struct("Effect::WindowShouldCloseSubscription") @@ -4032,6 +4115,10 @@ impl<'a, V: View> RenderContext<'a, V> { WeakViewHandle::new(self.window_id, self.view_id) } + pub fn window_id(&self) -> usize { + self.window_id + } + pub fn view_id(&self) -> usize { self.view_id } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 833912587c..df7727ee89 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -112,6 +112,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_fullscreen(&mut self, callback: Box); fn on_should_close(&mut self, callback: Box bool>); fn on_close(&mut self, callback: Box); fn set_input_handler(&mut self, input_handler: Box); diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 908efd451c..2168b07ffe 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -128,6 +128,14 @@ unsafe fn build_classes() { sel!(windowDidResize:), window_did_resize as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(windowDidEnterFullScreen:), + window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidExitFullScreen:), + window_did_exit_fullscreen as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(windowDidBecomeKey:), window_did_change_key_status as extern "C" fn(&Object, Sel, id), @@ -276,6 +284,7 @@ struct WindowState { event_callback: Option bool>>, activate_callback: Option>, resize_callback: Option>, + fullscreen_callback: Option>, should_close_callback: Option bool>>, close_callback: Option>, input_handler: Option>, @@ -368,6 +377,7 @@ impl Window { should_close_callback: None, close_callback: None, activate_callback: None, + fullscreen_callback: None, input_handler: None, pending_key_down: None, performed_key_equivalent: false, @@ -467,6 +477,10 @@ impl platform::Window for Window { self.0.as_ref().borrow_mut().resize_callback = Some(callback); } + fn on_fullscreen(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback); + } + fn on_should_close(&mut self, callback: Box bool>) { self.0.as_ref().borrow_mut().should_close_callback = Some(callback); } @@ -908,6 +922,24 @@ extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) { window_state.as_ref().borrow().move_traffic_light(); } +extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { + window_fullscreen_changed(this, true); +} + +extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) { + window_fullscreen_changed(this, false); +} + +fn window_fullscreen_changed(this: &Object, is_fullscreen: 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.fullscreen_callback.take() { + drop(window_state_borrow); + callback(is_fullscreen); + window_state.borrow_mut().fullscreen_callback = Some(callback); + } +} + extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) { let is_active = if selector == sel!(windowDidBecomeKey:) { true diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 8ea55b5e0d..3eb9b1e88e 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>, + fullscreen_handlers: Vec>, pub(crate) active_status_change_handlers: Vec>, pub(crate) should_close_handler: Option bool>>, pub(crate) title: Option, @@ -199,6 +200,7 @@ impl Window { close_handlers: Default::default(), should_close_handler: Default::default(), active_status_change_handlers: Default::default(), + fullscreen_handlers: Default::default(), scale_factor: 1.0, current_scene: None, title: None, @@ -253,6 +255,10 @@ impl super::Window for Window { self.active_status_change_handlers.push(callback); } + fn on_fullscreen(&mut self, callback: Box) { + self.fullscreen_handlers.push(callback) + } + fn on_resize(&mut self, callback: Box) { self.resize_handlers.push(callback); } From 46fef69b1a7423caa7278a79d25550a6cffd342c Mon Sep 17 00:00:00 2001 From: ForLoveOfCats Date: Tue, 9 Aug 2022 18:10:30 -0400 Subject: [PATCH 2/5] Refactor notification/observation callback pattern in MutableAppContext Pull out duplicate code and clarify some misc behavior. Some of this existing API feels like it's probably incorrect but that needs more thorough investigation Co-authored-by: Mikayla --- crates/gpui/src/app.rs | 531 +++++++++++++---------------------------- 1 file changed, 164 insertions(+), 367 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 532f59f008..a9467c9996 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -962,6 +962,92 @@ type WindowActivationCallback = Box b type DeserializeActionCallback = fn(json: &str) -> anyhow::Result>; type WindowShouldCloseSubscriptionCallback = Box bool>; +// type SubscriptionMappings = Arc>>>>; +struct SubscriptionMappings { + internal: Arc>>>>, +} + +impl Default for SubscriptionMappings { + fn default() -> Self { + SubscriptionMappings { + internal: Arc::new(Mutex::new(HashMap::new())), + } + } +} + +impl SubscriptionMappings { + fn clone_ref(&self) -> Self { + Self { + internal: self.internal.clone(), + } + } + + fn add_callback(&mut self, id: K, subscription_id: usize, callback: F) { + self.internal + .lock() + .entry(id) + .or_default() + .insert(subscription_id, Some(callback)); + } + + fn remove(&mut self, id: K) { + self.internal.lock().remove(&id); + } + + fn add_or_remove_callback(&mut self, id: K, subscription_id: usize, callback: F) { + match self + .internal + .lock() + .entry(id) + .or_default() + .entry(subscription_id) + { + btree_map::Entry::Vacant(entry) => { + entry.insert(Some(callback)); + } + + btree_map::Entry::Occupied(entry) => { + // TODO: This seems like it should never be called because no code + // should ever attempt to remove an existing callback + debug_assert!(entry.get().is_none()); + entry.remove(); + } + } + } + + fn emit_and_cleanup bool>( + &mut self, + id: K, + cx: &mut MutableAppContext, + mut call_callback: C, + ) { + let callbacks = self.internal.lock().remove(&id); + if let Some(callbacks) = callbacks { + for (subscription_id, callback) in callbacks { + if let Some(mut callback) = callback { + let alive = call_callback(&mut callback, cx); + if alive { + match self + .internal + .lock() + .entry(id) + .or_default() + .entry(subscription_id) + { + btree_map::Entry::Vacant(entry) => { + entry.insert(Some(callback)); + } + btree_map::Entry::Occupied(entry) => { + entry.remove(); + } + } + } + } + } + } + } +} + pub struct MutableAppContext { weak_self: Option>>, foreground_platform: Rc, @@ -976,18 +1062,17 @@ pub struct MutableAppContext { next_window_id: usize, next_subscription_id: usize, frame_count: usize, - subscriptions: Arc>>>>, - global_subscriptions: - Arc>>>>, - observations: Arc>>>>, - focus_observations: - Arc>>>>, - global_observations: - Arc>>>>, + + focus_observations: SubscriptionMappings, + global_subscriptions: SubscriptionMappings, + global_observations: SubscriptionMappings, + subscriptions: SubscriptionMappings, + observations: SubscriptionMappings, + window_activation_observations: SubscriptionMappings, + release_observations: Arc>>>, - window_activation_observations: - Arc>>>>, action_dispatch_observations: Arc>>, + presenters_and_platform_windows: HashMap>, Box)>, foreground: Rc, @@ -1395,7 +1480,7 @@ impl MutableAppContext { Subscription::GlobalSubscription { id: subscription_id, type_id, - subscriptions: Some(Arc::downgrade(&self.global_subscriptions)), + subscriptions: Some(Arc::downgrade(&self.global_subscriptions.internal)), } } @@ -1436,7 +1521,7 @@ impl MutableAppContext { Subscription::Subscription { id: subscription_id, entity_id: handle.id(), - subscriptions: Some(Arc::downgrade(&self.subscriptions)), + subscriptions: Some(Arc::downgrade(&self.subscriptions.internal)), } } @@ -1464,7 +1549,7 @@ impl MutableAppContext { Subscription::Observation { id: subscription_id, entity_id, - observations: Some(Arc::downgrade(&self.observations)), + observations: Some(Arc::downgrade(&self.observations.internal)), } } @@ -1476,6 +1561,7 @@ impl MutableAppContext { let subscription_id = post_inc(&mut self.next_subscription_id); let observed = handle.downgrade(); let view_id = handle.id(); + self.pending_effects.push_back(Effect::FocusObservation { view_id, subscription_id, @@ -1487,10 +1573,11 @@ impl MutableAppContext { } }), }); + Subscription::FocusObservation { id: subscription_id, view_id, - observations: Some(Arc::downgrade(&self.focus_observations)), + observations: Some(Arc::downgrade(&self.focus_observations.internal)), } } @@ -1502,20 +1589,16 @@ impl MutableAppContext { let type_id = TypeId::of::(); let id = post_inc(&mut self.next_subscription_id); - self.global_observations - .lock() - .entry(type_id) - .or_default() - .insert( - id, - Some(Box::new(move |cx: &mut MutableAppContext| observe(cx)) - as GlobalObservationCallback), - ); + self.global_observations.add_callback( + type_id, + id, + Box::new(move |cx: &mut MutableAppContext| observe(cx)), + ); Subscription::GlobalObservation { id, type_id, - observations: Some(Arc::downgrade(&self.global_observations)), + observations: Some(Arc::downgrade(&self.global_observations.internal)), } } @@ -1573,7 +1656,9 @@ impl MutableAppContext { Subscription::WindowActivationObservation { id: subscription_id, window_id, - observations: Some(Arc::downgrade(&self.window_activation_observations)), + observations: Some(Arc::downgrade( + &self.window_activation_observations.internal, + )), } } @@ -2107,8 +2192,8 @@ impl MutableAppContext { } for model_id in dropped_models { - self.subscriptions.lock().remove(&model_id); - self.observations.lock().remove(&model_id); + self.subscriptions.remove(model_id); + self.observations.remove(model_id); let mut model = self.cx.models.remove(&model_id).unwrap(); model.release(self); self.pending_effects @@ -2116,8 +2201,8 @@ impl MutableAppContext { } for (window_id, view_id) in dropped_views { - self.subscriptions.lock().remove(&view_id); - self.observations.lock().remove(&view_id); + self.subscriptions.remove(view_id); + self.observations.remove(view_id); let mut view = self.cx.views.remove(&(window_id, view_id)).unwrap(); view.release(self); let change_focus_to = self.cx.windows.get_mut(&window_id).and_then(|window| { @@ -2165,15 +2250,24 @@ impl MutableAppContext { entity_id, subscription_id, callback, - } => self.handle_subscription_effect(entity_id, subscription_id, callback), + } => self.subscriptions.add_or_remove_callback( + entity_id, + subscription_id, + callback, + ), - Effect::Event { entity_id, payload } => self.emit_event(entity_id, payload), + Effect::Event { entity_id, payload } => { + let mut subscriptions = self.subscriptions.clone_ref(); + subscriptions.emit_and_cleanup(entity_id, self, |callback, this| { + callback(payload.as_ref(), this) + }) + } Effect::GlobalSubscription { type_id, subscription_id, callback, - } => self.handle_global_subscription_effect( + } => self.global_subscriptions.add_or_remove_callback( type_id, subscription_id, callback, @@ -2185,10 +2279,16 @@ impl MutableAppContext { entity_id, subscription_id, callback, - } => self.handle_observation_effect(entity_id, subscription_id, callback), + } => self.observations.add_or_remove_callback( + entity_id, + subscription_id, + callback, + ), Effect::ModelNotification { model_id } => { - self.handle_model_notification_effect(model_id) + let mut observations = self.observations.clone_ref(); + observations + .emit_and_cleanup(model_id, self, |callback, this| callback(this)); } Effect::ViewNotification { window_id, view_id } => { @@ -2196,7 +2296,11 @@ impl MutableAppContext { } Effect::GlobalNotification { type_id } => { - self.handle_global_notification_effect(type_id) + let mut subscriptions = self.global_observations.clone_ref(); + subscriptions.emit_and_cleanup(type_id, self, |callback, this| { + callback(this); + true + }); } Effect::Deferred { @@ -2227,7 +2331,11 @@ impl MutableAppContext { subscription_id, callback, } => { - self.handle_focus_observation_effect(view_id, subscription_id, callback) + self.focus_observations.add_or_remove_callback( + view_id, + subscription_id, + callback, + ); } Effect::ResizeWindow { window_id } => { @@ -2242,7 +2350,7 @@ impl MutableAppContext { window_id, subscription_id, callback, - } => self.handle_window_activation_observation_effect( + } => self.window_activation_observations.add_or_remove_callback( window_id, subscription_id, callback, @@ -2369,182 +2477,14 @@ impl MutableAppContext { self.presenters_and_platform_windows = presenters; } - fn handle_subscription_effect( - &mut self, - entity_id: usize, - subscription_id: usize, - callback: SubscriptionCallback, - ) { - match self - .subscriptions - .lock() - .entry(entity_id) - .or_default() - .entry(subscription_id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - // Subscription was dropped before effect was processed - btree_map::Entry::Occupied(entry) => { - debug_assert!(entry.get().is_none()); - entry.remove(); - } - } - } - - fn emit_event(&mut self, entity_id: usize, payload: Box) { - let callbacks = self.subscriptions.lock().remove(&entity_id); - if let Some(callbacks) = callbacks { - for (id, callback) in callbacks { - if let Some(mut callback) = callback { - let alive = callback(payload.as_ref(), self); - if alive { - match self - .subscriptions - .lock() - .entry(entity_id) - .or_default() - .entry(id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - btree_map::Entry::Occupied(entry) => { - entry.remove(); - } - } - } - } - } - } - } - - fn handle_global_subscription_effect( - &mut self, - type_id: TypeId, - subscription_id: usize, - callback: GlobalSubscriptionCallback, - ) { - match self - .global_subscriptions - .lock() - .entry(type_id) - .or_default() - .entry(subscription_id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - // Subscription was dropped before effect was processed - btree_map::Entry::Occupied(entry) => { - debug_assert!(entry.get().is_none()); - entry.remove(); - } - } - } - fn emit_global_event(&mut self, payload: Box) { let type_id = (&*payload).type_id(); - let callbacks = self.global_subscriptions.lock().remove(&type_id); - if let Some(callbacks) = callbacks { - for (id, callback) in callbacks { - if let Some(mut callback) = callback { - callback(payload.as_ref(), self); - match self - .global_subscriptions - .lock() - .entry(type_id) - .or_default() - .entry(id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - btree_map::Entry::Occupied(entry) => { - entry.remove(); - } - } - } - } - } - } - fn handle_observation_effect( - &mut self, - entity_id: usize, - subscription_id: usize, - callback: ObservationCallback, - ) { - match self - .observations - .lock() - .entry(entity_id) - .or_default() - .entry(subscription_id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - // Observation was dropped before effect was processed - btree_map::Entry::Occupied(entry) => { - debug_assert!(entry.get().is_none()); - entry.remove(); - } - } - } - - fn handle_focus_observation_effect( - &mut self, - view_id: usize, - subscription_id: usize, - callback: FocusObservationCallback, - ) { - match self - .focus_observations - .lock() - .entry(view_id) - .or_default() - .entry(subscription_id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - // Observation was dropped before effect was processed - btree_map::Entry::Occupied(entry) => { - debug_assert!(entry.get().is_none()); - entry.remove(); - } - } - } - - fn handle_model_notification_effect(&mut self, observed_id: usize) { - let callbacks = self.observations.lock().remove(&observed_id); - if let Some(callbacks) = callbacks { - if self.cx.models.contains_key(&observed_id) { - for (id, callback) in callbacks { - if let Some(mut callback) = callback { - let alive = callback(self); - if alive { - match self - .observations - .lock() - .entry(observed_id) - .or_default() - .entry(id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - btree_map::Entry::Occupied(entry) => { - entry.remove(); - } - } - } - } - } - } - } + let mut subscriptions = self.global_subscriptions.clone_ref(); + subscriptions.emit_and_cleanup(type_id, self, |callback, this| { + callback(payload.as_ref(), this); + true //Always alive + }); } fn handle_view_notification_effect( @@ -2552,8 +2492,6 @@ impl MutableAppContext { observed_window_id: usize, observed_view_id: usize, ) { - let callbacks = self.observations.lock().remove(&observed_view_id); - if self .cx .views @@ -2567,54 +2505,8 @@ impl MutableAppContext { .insert(observed_view_id); } - if let Some(callbacks) = callbacks { - for (id, callback) in callbacks { - if let Some(mut callback) = callback { - let alive = callback(self); - if alive { - match self - .observations - .lock() - .entry(observed_view_id) - .or_default() - .entry(id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - btree_map::Entry::Occupied(entry) => { - entry.remove(); - } - } - } - } - } - } - } - } - - fn handle_global_notification_effect(&mut self, observed_type_id: TypeId) { - let callbacks = self.global_observations.lock().remove(&observed_type_id); - if let Some(callbacks) = callbacks { - for (id, callback) in callbacks { - if let Some(mut callback) = callback { - callback(self); - match self - .global_observations - .lock() - .entry(observed_type_id) - .or_default() - .entry(id) - { - collections::btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - collections::btree_map::Entry::Occupied(entry) => { - entry.remove(); - } - } - } - } + let mut observations = self.observations.clone_ref(); + observations.emit_and_cleanup(observed_view_id, self, |callback, this| callback(this)); } } @@ -2627,30 +2519,6 @@ impl MutableAppContext { } } - fn handle_window_activation_observation_effect( - &mut self, - window_id: usize, - subscription_id: usize, - callback: WindowActivationCallback, - ) { - match self - .window_activation_observations - .lock() - .entry(window_id) - .or_default() - .entry(subscription_id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - // Observation was dropped before effect was processed - btree_map::Entry::Occupied(entry) => { - debug_assert!(entry.get().is_none()); - entry.remove(); - } - } - } - fn handle_fullscreen_effect(&mut self, window_id: usize, is_fullscreen: bool) { //Short circuit evaluation if we're already g2g if self @@ -2667,8 +2535,6 @@ impl MutableAppContext { let window = this.cx.windows.get_mut(&window_id)?; window.is_fullscreen = is_fullscreen; - // self.emit_event(entity_id, payload); - Some(()) }); } @@ -2700,35 +2566,8 @@ impl MutableAppContext { this.cx.views.insert((window_id, view_id), view); } - //Deliver events - let callbacks = this - .window_activation_observations - .lock() - .remove(&window_id); - if let Some(callbacks) = callbacks { - for (id, callback) in callbacks { - if let Some(mut callback) = callback { - let alive = callback(active, this); - //Put entry back - if alive { - match this - .window_activation_observations - .lock() - .entry(window_id) - .or_default() - .entry(id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - btree_map::Entry::Occupied(entry) => { - entry.remove(); - } - } - } - } - } - } + let mut observations = this.window_activation_observations.clone_ref(); + observations.emit_and_cleanup(window_id, this, |callback, this| callback(active, this)); Some(()) }); @@ -2759,30 +2598,9 @@ impl MutableAppContext { blurred_view.on_blur(this, window_id, blurred_id); this.cx.views.insert((window_id, blurred_id), blurred_view); - let callbacks = this.focus_observations.lock().remove(&blurred_id); - if let Some(callbacks) = callbacks { - for (id, callback) in callbacks { - if let Some(mut callback) = callback { - let alive = callback(false, this); - if alive { - match this - .focus_observations - .lock() - .entry(blurred_id) - .or_default() - .entry(id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - btree_map::Entry::Occupied(entry) => { - entry.remove(); - } - } - } - } - } - } + let mut subscriptions = this.focus_observations.clone_ref(); + subscriptions + .emit_and_cleanup(blurred_id, this, |callback, this| callback(false, this)); } } @@ -2791,30 +2609,9 @@ impl MutableAppContext { focused_view.on_focus(this, window_id, focused_id); this.cx.views.insert((window_id, focused_id), focused_view); - let callbacks = this.focus_observations.lock().remove(&focused_id); - if let Some(callbacks) = callbacks { - for (id, callback) in callbacks { - if let Some(mut callback) = callback { - let alive = callback(true, this); - if alive { - match this - .focus_observations - .lock() - .entry(focused_id) - .or_default() - .entry(id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - btree_map::Entry::Occupied(entry) => { - entry.remove(); - } - } - } - } - } - } + let mut subscriptions = this.focus_observations.clone_ref(); + subscriptions + .emit_and_cleanup(focused_id, this, |callback, this| callback(true, this)); } } }) @@ -5768,8 +5565,8 @@ mod tests { }); assert_eq!(cx.cx.models.len(), 1); - assert!(cx.subscriptions.lock().is_empty()); - assert!(cx.observations.lock().is_empty()); + assert!(cx.subscriptions.internal.lock().is_empty()); + assert!(cx.observations.internal.lock().is_empty()); } #[crate::test(self)] @@ -6019,8 +5816,8 @@ mod tests { }); assert_eq!(cx.cx.views.len(), 2); - assert!(cx.subscriptions.lock().is_empty()); - assert!(cx.observations.lock().is_empty()); + assert!(cx.subscriptions.internal.lock().is_empty()); + assert!(cx.observations.internal.lock().is_empty()); } #[crate::test(self)] From 43b9f3beb3be382f63e3a10112320e4478c44fcc Mon Sep 17 00:00:00 2001 From: ForLoveOfCats Date: Tue, 9 Aug 2022 18:52:25 -0400 Subject: [PATCH 3/5] Invalidate view when entering/exiting fullscreen This is required for `render_titlebar` to eventually adapt to fullscreen status to affect the size of left padding on workspace name to account for the traffic lights presence/absence Co-authored-by: Mikayla --- crates/gpui/src/app.rs | 102 +++++++++++++++++++++++++++++- crates/workspace/src/workspace.rs | 2 + 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index a9467c9996..480a9a1bdf 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -959,6 +959,7 @@ type GlobalObservationCallback = Box; type ReleaseObservationCallback = Box; type ActionObservationCallback = Box; type WindowActivationCallback = Box bool>; +type WindowFullscreenCallback = Box bool>; type DeserializeActionCallback = fn(json: &str) -> anyhow::Result>; type WindowShouldCloseSubscriptionCallback = Box bool>; @@ -1069,7 +1070,8 @@ pub struct MutableAppContext { subscriptions: SubscriptionMappings, observations: SubscriptionMappings, window_activation_observations: SubscriptionMappings, - + window_fullscreen_observations: SubscriptionMappings, + release_observations: Arc>>>, action_dispatch_observations: Arc>>, @@ -1126,6 +1128,7 @@ impl MutableAppContext { release_observations: Default::default(), global_observations: Default::default(), window_activation_observations: Default::default(), + window_fullscreen_observations: Default::default(), action_dispatch_observations: Default::default(), presenters_and_platform_windows: HashMap::new(), foreground, @@ -1662,6 +1665,26 @@ impl MutableAppContext { } } + fn observe_fullscreen(&mut self, window_id: usize, callback: F) -> Subscription + where + F: 'static + FnMut(bool, &mut MutableAppContext) -> bool, + { + let subscription_id = post_inc(&mut self.next_subscription_id); + self.pending_effects + .push_back(Effect::WindowFullscreenObservation { + window_id, + subscription_id, + callback: Box::new(callback), + }); + Subscription::WindowFullscreenObservation { + id: subscription_id, + window_id, + observations: Some(Arc::downgrade( + &self.window_activation_observations.internal, + )), + } + } + pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut MutableAppContext)) { self.pending_effects.push_back(Effect::Deferred { callback: Box::new(callback), @@ -2361,6 +2384,16 @@ impl MutableAppContext { is_active, } => self.handle_window_activation_effect(window_id, is_active), + Effect::WindowFullscreenObservation { + window_id, + subscription_id, + callback, + } => self.window_fullscreen_observations.add_or_remove_callback( + window_id, + subscription_id, + callback, + ), + Effect::FullscreenWindow { window_id, is_fullscreen, @@ -2535,6 +2568,11 @@ impl MutableAppContext { let window = this.cx.windows.get_mut(&window_id)?; window.is_fullscreen = is_fullscreen; + let mut observations = this.window_fullscreen_observations.clone_ref(); + observations.emit_and_cleanup(window_id, this, |callback, this| { + callback(is_fullscreen, this) + }); + Some(()) }); } @@ -3019,6 +3057,11 @@ pub enum Effect { subscription_id: usize, callback: WindowActivationCallback, }, + WindowFullscreenObservation { + window_id: usize, + subscription_id: usize, + callback: WindowFullscreenCallback, + }, RefreshWindows, ActionDispatchNotification { action_id: TypeId, @@ -3136,6 +3179,15 @@ impl Debug for Effect { .field("window_id", window_id) .field("is_fullscreen", is_fullscreen) .finish(), + Effect::WindowFullscreenObservation { + window_id, + subscription_id, + callback: _, + } => f + .debug_struct("Effect::WindowFullscreenObservation") + .field("window_id", window_id) + .field("subscription_id", subscription_id) + .finish(), Effect::RefreshWindows => f.debug_struct("Effect::FullViewRefresh").finish(), Effect::WindowShouldCloseSubscription { window_id, .. } => f .debug_struct("Effect::WindowShouldCloseSubscription") @@ -3807,6 +3859,24 @@ impl<'a, T: View> ViewContext<'a, T> { }) } + pub fn observe_fullscreen(&mut self, mut callback: F) -> Subscription + where + F: 'static + FnMut(&mut T, bool, &mut ViewContext), + { + let observer = self.weak_handle(); + self.app + .observe_fullscreen(self.window_id(), move |active, cx| { + if let Some(observer) = observer.upgrade(cx) { + observer.update(cx, |observer, cx| { + callback(observer, active, cx); + }); + true + } else { + false + } + }) + } + pub fn emit(&mut self, payload: T::Event) { self.app.pending_effects.push_back(Effect::Event { entity_id: self.view_id, @@ -5128,6 +5198,12 @@ pub enum Subscription { observations: Option>>>>>, }, + WindowFullscreenObservation { + id: usize, + window_id: usize, + observations: + Option>>>>>, + }, } impl Subscription { @@ -5157,6 +5233,9 @@ impl Subscription { Subscription::WindowActivationObservation { observations, .. } => { observations.take(); } + Subscription::WindowFullscreenObservation { observations, .. } => { + observations.take(); + } } } } @@ -5291,6 +5370,27 @@ impl Drop for Subscription { } } } + Subscription::WindowFullscreenObservation { + id, + window_id, + observations, + } => { + if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) { + match observations + .lock() + .entry(*window_id) + .or_default() + .entry(*id) + { + btree_map::Entry::Vacant(entry) => { + entry.insert(None); + } + btree_map::Entry::Occupied(entry) => { + entry.remove(); + } + } + } + } } } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 045e9c6f90..302ebd65ee 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -823,6 +823,8 @@ enum FollowerItem { impl Workspace { pub fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { + cx.observe_fullscreen(|_, _, cx| cx.notify()).detach(); + cx.observe_window_activation(Self::on_window_activation_changed) .detach(); cx.observe(&project, |_, _, cx| cx.notify()).detach(); From 3d9821b430dde5ab583b2f571c86fadf992afcf4 Mon Sep 17 00:00:00 2001 From: ForLoveOfCats Date: Tue, 9 Aug 2022 19:03:03 -0400 Subject: [PATCH 4/5] Jankily adjust left padding on workspace title in fullscreen This could seriously be done better Co-authored-by: Mikayla --- crates/gpui/src/platform/mac/window.rs | 12 ++++++------ crates/workspace/src/workspace.rs | 13 ++++++++++++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 2168b07ffe..568356e938 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -129,12 +129,12 @@ unsafe fn build_classes() { window_did_resize as extern "C" fn(&Object, Sel, id), ); decl.add_method( - sel!(windowDidEnterFullScreen:), - window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id), + sel!(windowWillEnterFullScreen:), + window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id), ); decl.add_method( - sel!(windowDidExitFullScreen:), - window_did_exit_fullscreen as extern "C" fn(&Object, Sel, id), + sel!(windowWillExitFullScreen:), + window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id), ); decl.add_method( sel!(windowDidBecomeKey:), @@ -922,11 +922,11 @@ extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) { window_state.as_ref().borrow().move_traffic_light(); } -extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { +extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { window_fullscreen_changed(this, true); } -extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) { +extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) { window_fullscreen_changed(this, false); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 302ebd65ee..a27754aa13 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1858,6 +1858,17 @@ impl Workspace { worktree_root_names.push_str(name); } + // TODO: There should be a better system in place for this + // (https://github.com/zed-industries/zed/issues/1290) + let is_fullscreen = cx.window_is_fullscreen(cx.window_id()); + let container_theme = if is_fullscreen { + let mut container_theme = theme.workspace.titlebar.container; + container_theme.padding.left = container_theme.padding.right; + container_theme + } else { + theme.workspace.titlebar.container + }; + ConstrainedBox::new( Container::new( Stack::new() @@ -1885,7 +1896,7 @@ impl Workspace { ) .boxed(), ) - .with_style(theme.workspace.titlebar.container) + .with_style(container_theme) .boxed(), ) .with_height(theme.workspace.titlebar.height) From 45c0539de0becaa9e68f07e92d16ac424ccfff31 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 9 Aug 2022 16:59:13 -0700 Subject: [PATCH 5/5] changed name of subscription mapping and moved out to file Co-authored-by: Keith --- crates/gpui/src/app.rs | 208 ++++++--------------- crates/gpui/src/app/callback_collection.rs | 106 +++++++++++ crates/gpui/src/presenter.rs | 4 +- 3 files changed, 165 insertions(+), 153 deletions(-) create mode 100644 crates/gpui/src/app/callback_collection.rs diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 480a9a1bdf..cc4c1191f6 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1,4 +1,5 @@ pub mod action; +mod callback_collection; use crate::{ elements::ElementBox, @@ -13,7 +14,8 @@ use crate::{ }; pub use action::*; use anyhow::{anyhow, Context, Result}; -use collections::btree_map; +use callback_collection::CallbackCollection; +use collections::{btree_map, hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque}; use keymap::MatchResult; use lazy_static::lazy_static; use parking_lot::Mutex; @@ -24,7 +26,6 @@ use smol::prelude::*; use std::{ any::{type_name, Any, TypeId}, cell::RefCell, - collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque}, fmt::{self, Debug}, hash::{Hash, Hasher}, marker::PhantomData, @@ -37,6 +38,8 @@ use std::{ time::Duration, }; +use self::callback_collection::Mapping; + pub trait Entity: 'static { type Event; @@ -963,92 +966,6 @@ type WindowFullscreenCallback = Box b type DeserializeActionCallback = fn(json: &str) -> anyhow::Result>; type WindowShouldCloseSubscriptionCallback = Box bool>; -// type SubscriptionMappings = Arc>>>>; -struct SubscriptionMappings { - internal: Arc>>>>, -} - -impl Default for SubscriptionMappings { - fn default() -> Self { - SubscriptionMappings { - internal: Arc::new(Mutex::new(HashMap::new())), - } - } -} - -impl SubscriptionMappings { - fn clone_ref(&self) -> Self { - Self { - internal: self.internal.clone(), - } - } - - fn add_callback(&mut self, id: K, subscription_id: usize, callback: F) { - self.internal - .lock() - .entry(id) - .or_default() - .insert(subscription_id, Some(callback)); - } - - fn remove(&mut self, id: K) { - self.internal.lock().remove(&id); - } - - fn add_or_remove_callback(&mut self, id: K, subscription_id: usize, callback: F) { - match self - .internal - .lock() - .entry(id) - .or_default() - .entry(subscription_id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - - btree_map::Entry::Occupied(entry) => { - // TODO: This seems like it should never be called because no code - // should ever attempt to remove an existing callback - debug_assert!(entry.get().is_none()); - entry.remove(); - } - } - } - - fn emit_and_cleanup bool>( - &mut self, - id: K, - cx: &mut MutableAppContext, - mut call_callback: C, - ) { - let callbacks = self.internal.lock().remove(&id); - if let Some(callbacks) = callbacks { - for (subscription_id, callback) in callbacks { - if let Some(mut callback) = callback { - let alive = call_callback(&mut callback, cx); - if alive { - match self - .internal - .lock() - .entry(id) - .or_default() - .entry(subscription_id) - { - btree_map::Entry::Vacant(entry) => { - entry.insert(Some(callback)); - } - btree_map::Entry::Occupied(entry) => { - entry.remove(); - } - } - } - } - } - } - } -} - pub struct MutableAppContext { weak_self: Option>>, foreground_platform: Rc, @@ -1064,13 +981,13 @@ pub struct MutableAppContext { next_subscription_id: usize, frame_count: usize, - focus_observations: SubscriptionMappings, - global_subscriptions: SubscriptionMappings, - global_observations: SubscriptionMappings, - subscriptions: SubscriptionMappings, - observations: SubscriptionMappings, - window_activation_observations: SubscriptionMappings, - window_fullscreen_observations: SubscriptionMappings, + focus_observations: CallbackCollection, + global_subscriptions: CallbackCollection, + global_observations: CallbackCollection, + subscriptions: CallbackCollection, + observations: CallbackCollection, + window_activation_observations: CallbackCollection, + window_fullscreen_observations: CallbackCollection, release_observations: Arc>>>, action_dispatch_observations: Arc>>, @@ -1112,10 +1029,10 @@ impl MutableAppContext { font_cache, platform, }, - action_deserializers: HashMap::new(), - capture_actions: HashMap::new(), - actions: HashMap::new(), - global_actions: HashMap::new(), + action_deserializers: Default::default(), + capture_actions: Default::default(), + actions: Default::default(), + global_actions: Default::default(), keystroke_matcher: keymap::Matcher::default(), next_entity_id: 0, next_window_id: 0, @@ -1130,12 +1047,12 @@ impl MutableAppContext { window_activation_observations: Default::default(), window_fullscreen_observations: Default::default(), action_dispatch_observations: Default::default(), - presenters_and_platform_windows: HashMap::new(), + presenters_and_platform_windows: Default::default(), foreground, pending_effects: VecDeque::new(), pending_focus_index: None, - pending_notifications: HashSet::new(), - pending_global_notifications: HashSet::new(), + pending_notifications: Default::default(), + pending_global_notifications: Default::default(), pending_flushes: 0, flushing_effects: false, halt_action_dispatch: false, @@ -1480,10 +1397,11 @@ impl MutableAppContext { callback(payload, cx) }), }); + Subscription::GlobalSubscription { id: subscription_id, type_id, - subscriptions: Some(Arc::downgrade(&self.global_subscriptions.internal)), + subscriptions: Some(self.global_subscriptions.downgrade()), } } @@ -1524,7 +1442,7 @@ impl MutableAppContext { Subscription::Subscription { id: subscription_id, entity_id: handle.id(), - subscriptions: Some(Arc::downgrade(&self.subscriptions.internal)), + subscriptions: Some(self.subscriptions.downgrade()), } } @@ -1552,7 +1470,7 @@ impl MutableAppContext { Subscription::Observation { id: subscription_id, entity_id, - observations: Some(Arc::downgrade(&self.observations.internal)), + observations: Some(self.observations.downgrade()), } } @@ -1580,7 +1498,7 @@ impl MutableAppContext { Subscription::FocusObservation { id: subscription_id, view_id, - observations: Some(Arc::downgrade(&self.focus_observations.internal)), + observations: Some(self.focus_observations.downgrade()), } } @@ -1601,7 +1519,7 @@ impl MutableAppContext { Subscription::GlobalObservation { id, type_id, - observations: Some(Arc::downgrade(&self.global_observations.internal)), + observations: Some(self.global_observations.downgrade()), } } @@ -1659,9 +1577,7 @@ impl MutableAppContext { Subscription::WindowActivationObservation { id: subscription_id, window_id, - observations: Some(Arc::downgrade( - &self.window_activation_observations.internal, - )), + observations: Some(self.window_activation_observations.downgrade()), } } @@ -1679,9 +1595,7 @@ impl MutableAppContext { Subscription::WindowFullscreenObservation { id: subscription_id, window_id, - observations: Some(Arc::downgrade( - &self.window_activation_observations.internal, - )), + observations: Some(self.window_activation_observations.downgrade()), } } @@ -2280,7 +2194,7 @@ impl MutableAppContext { ), Effect::Event { entity_id, payload } => { - let mut subscriptions = self.subscriptions.clone_ref(); + let mut subscriptions = self.subscriptions.clone(); subscriptions.emit_and_cleanup(entity_id, self, |callback, this| { callback(payload.as_ref(), this) }) @@ -2309,7 +2223,7 @@ impl MutableAppContext { ), Effect::ModelNotification { model_id } => { - let mut observations = self.observations.clone_ref(); + let mut observations = self.observations.clone(); observations .emit_and_cleanup(model_id, self, |callback, this| callback(this)); } @@ -2319,7 +2233,7 @@ impl MutableAppContext { } Effect::GlobalNotification { type_id } => { - let mut subscriptions = self.global_observations.clone_ref(); + let mut subscriptions = self.global_observations.clone(); subscriptions.emit_and_cleanup(type_id, self, |callback, this| { callback(this); true @@ -2442,7 +2356,7 @@ impl MutableAppContext { } fn update_windows(&mut self) { - let mut invalidations = HashMap::new(); + let mut invalidations: HashMap<_, _> = Default::default(); for (window_id, window) in &mut self.cx.windows { if let Some(invalidation) = window.invalidation.take() { invalidations.insert(*window_id, invalidation); @@ -2513,7 +2427,7 @@ impl MutableAppContext { fn emit_global_event(&mut self, payload: Box) { let type_id = (&*payload).type_id(); - let mut subscriptions = self.global_subscriptions.clone_ref(); + let mut subscriptions = self.global_subscriptions.clone(); subscriptions.emit_and_cleanup(type_id, self, |callback, this| { callback(payload.as_ref(), this); true //Always alive @@ -2538,7 +2452,7 @@ impl MutableAppContext { .insert(observed_view_id); } - let mut observations = self.observations.clone_ref(); + let mut observations = self.observations.clone(); observations.emit_and_cleanup(observed_view_id, self, |callback, this| callback(this)); } } @@ -2568,7 +2482,7 @@ impl MutableAppContext { let window = this.cx.windows.get_mut(&window_id)?; window.is_fullscreen = is_fullscreen; - let mut observations = this.window_fullscreen_observations.clone_ref(); + let mut observations = this.window_fullscreen_observations.clone(); observations.emit_and_cleanup(window_id, this, |callback, this| { callback(is_fullscreen, this) }); @@ -2604,7 +2518,7 @@ impl MutableAppContext { this.cx.views.insert((window_id, view_id), view); } - let mut observations = this.window_activation_observations.clone_ref(); + let mut observations = this.window_activation_observations.clone(); observations.emit_and_cleanup(window_id, this, |callback, this| callback(active, this)); Some(()) @@ -2636,7 +2550,7 @@ impl MutableAppContext { blurred_view.on_blur(this, window_id, blurred_id); this.cx.views.insert((window_id, blurred_id), blurred_view); - let mut subscriptions = this.focus_observations.clone_ref(); + let mut subscriptions = this.focus_observations.clone(); subscriptions .emit_and_cleanup(blurred_id, this, |callback, this| callback(false, this)); } @@ -2647,7 +2561,7 @@ impl MutableAppContext { focused_view.on_focus(this, window_id, focused_id); this.cx.views.insert((window_id, focused_id), focused_view); - let mut subscriptions = this.focus_observations.clone_ref(); + let mut subscriptions = this.focus_observations.clone(); subscriptions .emit_and_cleanup(focused_id, this, |callback, this| callback(true, this)); } @@ -5153,35 +5067,39 @@ pub enum Subscription { Subscription { id: usize, entity_id: usize, - subscriptions: - Option>>>>>, + subscriptions: Option>>, }, GlobalSubscription { id: usize, type_id: TypeId, - subscriptions: Option< - Weak>>>>, - >, + subscriptions: Option>>, }, Observation { id: usize, entity_id: usize, - observations: - Option>>>>>, + observations: Option>>, }, GlobalObservation { id: usize, type_id: TypeId, - observations: Option< - Weak>>>>, - >, + observations: Option>>, }, FocusObservation { id: usize, view_id: usize, - observations: - Option>>>>>, + observations: Option>>, }, + WindowActivationObservation { + id: usize, + window_id: usize, + observations: Option>>, + }, + WindowFullscreenObservation { + id: usize, + window_id: usize, + observations: Option>>, + }, + ReleaseObservation { id: usize, entity_id: usize, @@ -5192,18 +5110,6 @@ pub enum Subscription { id: usize, observations: Option>>>, }, - WindowActivationObservation { - id: usize, - window_id: usize, - observations: - Option>>>>>, - }, - WindowFullscreenObservation { - id: usize, - window_id: usize, - observations: - Option>>>>>, - }, } impl Subscription { @@ -5665,8 +5571,8 @@ mod tests { }); assert_eq!(cx.cx.models.len(), 1); - assert!(cx.subscriptions.internal.lock().is_empty()); - assert!(cx.observations.internal.lock().is_empty()); + assert!(cx.subscriptions.is_empty()); + assert!(cx.observations.is_empty()); } #[crate::test(self)] @@ -5916,8 +5822,8 @@ mod tests { }); assert_eq!(cx.cx.views.len(), 2); - assert!(cx.subscriptions.internal.lock().is_empty()); - assert!(cx.observations.internal.lock().is_empty()); + assert!(cx.subscriptions.is_empty()); + assert!(cx.observations.is_empty()); } #[crate::test(self)] diff --git a/crates/gpui/src/app/callback_collection.rs b/crates/gpui/src/app/callback_collection.rs new file mode 100644 index 0000000000..4ec90fbac0 --- /dev/null +++ b/crates/gpui/src/app/callback_collection.rs @@ -0,0 +1,106 @@ +use std::sync::Arc; +use std::{hash::Hash, sync::Weak}; + +use parking_lot::Mutex; + +use collections::{btree_map, BTreeMap, HashMap}; + +use crate::MutableAppContext; + +pub type Mapping = Mutex>>>; + +pub struct CallbackCollection { + internal: Arc>, +} + +impl Clone for CallbackCollection { + fn clone(&self) -> Self { + Self { + internal: self.internal.clone(), + } + } +} + +impl Default for CallbackCollection { + fn default() -> Self { + CallbackCollection { + internal: Arc::new(Mutex::new(Default::default())), + } + } +} + +impl CallbackCollection { + pub fn downgrade(&self) -> Weak> { + Arc::downgrade(&self.internal) + } + + #[cfg(test)] + pub fn is_empty(&self) -> bool { + self.internal.lock().is_empty() + } + + pub fn add_callback(&mut self, id: K, subscription_id: usize, callback: F) { + self.internal + .lock() + .entry(id) + .or_default() + .insert(subscription_id, Some(callback)); + } + + pub fn remove(&mut self, id: K) { + self.internal.lock().remove(&id); + } + + pub fn add_or_remove_callback(&mut self, id: K, subscription_id: usize, callback: F) { + match self + .internal + .lock() + .entry(id) + .or_default() + .entry(subscription_id) + { + btree_map::Entry::Vacant(entry) => { + entry.insert(Some(callback)); + } + + btree_map::Entry::Occupied(entry) => { + // TODO: This seems like it should never be called because no code + // should ever attempt to remove an existing callback + debug_assert!(entry.get().is_none()); + entry.remove(); + } + } + } + + pub fn emit_and_cleanup bool>( + &mut self, + id: K, + cx: &mut MutableAppContext, + mut call_callback: C, + ) { + let callbacks = self.internal.lock().remove(&id); + if let Some(callbacks) = callbacks { + for (subscription_id, callback) in callbacks { + if let Some(mut callback) = callback { + let alive = call_callback(&mut callback, cx); + if alive { + match self + .internal + .lock() + .entry(id) + .or_default() + .entry(subscription_id) + { + btree_map::Entry::Vacant(entry) => { + entry.insert(Some(callback)); + } + btree_map::Entry::Occupied(entry) => { + entry.remove(); + } + } + } + } + } + } + } +} diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index d0af2473a6..a8aefad6e9 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -13,11 +13,11 @@ use crate::{ ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, WeakModelHandle, WeakViewHandle, }; +use collections::{HashMap, HashSet}; use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; use smallvec::SmallVec; use std::{ - collections::{HashMap, HashSet}, marker::PhantomData, ops::{Deref, DerefMut, Range}, sync::Arc, @@ -52,7 +52,7 @@ impl Presenter { Self { window_id, rendered_views: cx.render_views(window_id, titlebar_height), - parents: HashMap::new(), + parents: Default::default(), cursor_regions: Default::default(), mouse_regions: Default::default(), font_cache,