diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 8285fda213..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; @@ -959,6 +962,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>; @@ -976,18 +980,18 @@ 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: CallbackCollection, + global_subscriptions: CallbackCollection, + global_observations: CallbackCollection, + subscriptions: CallbackCollection, + observations: CallbackCollection, + window_activation_observations: CallbackCollection, + window_fullscreen_observations: CallbackCollection, + release_observations: Arc>>>, - window_activation_observations: - Arc>>>>, action_dispatch_observations: Arc>>, + presenters_and_platform_windows: HashMap>, Box)>, foreground: Rc, @@ -1025,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, @@ -1041,13 +1045,14 @@ 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(), + 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, @@ -1248,6 +1253,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; @@ -1385,10 +1397,11 @@ impl MutableAppContext { callback(payload, cx) }), }); + Subscription::GlobalSubscription { id: subscription_id, type_id, - subscriptions: Some(Arc::downgrade(&self.global_subscriptions)), + subscriptions: Some(self.global_subscriptions.downgrade()), } } @@ -1429,7 +1442,7 @@ impl MutableAppContext { Subscription::Subscription { id: subscription_id, entity_id: handle.id(), - subscriptions: Some(Arc::downgrade(&self.subscriptions)), + subscriptions: Some(self.subscriptions.downgrade()), } } @@ -1457,7 +1470,7 @@ impl MutableAppContext { Subscription::Observation { id: subscription_id, entity_id, - observations: Some(Arc::downgrade(&self.observations)), + observations: Some(self.observations.downgrade()), } } @@ -1469,6 +1482,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, @@ -1480,10 +1494,11 @@ impl MutableAppContext { } }), }); + Subscription::FocusObservation { id: subscription_id, view_id, - observations: Some(Arc::downgrade(&self.focus_observations)), + observations: Some(self.focus_observations.downgrade()), } } @@ -1495,20 +1510,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(self.global_observations.downgrade()), } } @@ -1566,7 +1577,25 @@ impl MutableAppContext { Subscription::WindowActivationObservation { id: subscription_id, window_id, - observations: Some(Arc::downgrade(&self.window_activation_observations)), + observations: Some(self.window_activation_observations.downgrade()), + } + } + + 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(self.window_activation_observations.downgrade()), } } @@ -1934,6 +1963,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 +2040,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 || { @@ -2092,8 +2129,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 @@ -2101,8 +2138,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| { @@ -2150,32 +2187,59 @@ impl MutableAppContext { entity_id, subscription_id, callback, - } => self.handle_subscription_effect(entity_id, subscription_id, callback), - Effect::Event { entity_id, payload } => self.emit_event(entity_id, payload), + } => self.subscriptions.add_or_remove_callback( + entity_id, + subscription_id, + callback, + ), + + Effect::Event { entity_id, payload } => { + let mut subscriptions = self.subscriptions.clone(); + 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, ), + Effect::GlobalEvent { payload } => self.emit_global_event(payload), + Effect::Observation { 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(); + observations + .emit_and_cleanup(model_id, self, |callback, this| callback(this)); } + 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) + let mut subscriptions = self.global_observations.clone(); + subscriptions.emit_and_cleanup(type_id, self, |callback, this| { + callback(this); + true + }); } + Effect::Deferred { callback, after_window_update, @@ -2186,22 +2250,31 @@ 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, 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 } => { if let Some(window) = self.cx.windows.get_mut(&window_id) { window @@ -2209,19 +2282,37 @@ impl MutableAppContext { .get_or_insert(WindowInvalidation::default()); } } + Effect::WindowActivationObservation { window_id, subscription_id, callback, - } => self.handle_window_activation_observation_effect( + } => self.window_activation_observations.add_or_remove_callback( window_id, subscription_id, callback, ), + Effect::ActivateWindow { window_id, 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, + } => self.handle_fullscreen_effect(window_id, is_fullscreen), + Effect::RefreshWindows => { refreshing = true; } @@ -2265,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); @@ -2294,6 +2385,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, @@ -2326,182 +2424,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(); + subscriptions.emit_and_cleanup(type_id, self, |callback, this| { + callback(payload.as_ref(), this); + true //Always alive + }); } fn handle_view_notification_effect( @@ -2509,8 +2439,6 @@ impl MutableAppContext { observed_window_id: usize, observed_view_id: usize, ) { - let callbacks = self.observations.lock().remove(&observed_view_id); - if self .cx .views @@ -2524,54 +2452,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(); + observations.emit_and_cleanup(observed_view_id, self, |callback, this| callback(this)); } } @@ -2584,37 +2466,39 @@ 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_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; + + let mut observations = this.window_fullscreen_observations.clone(); + observations.emit_and_cleanup(window_id, this, |callback, this| { + callback(is_fullscreen, this) + }); + + 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 +2506,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,33 +2518,8 @@ impl MutableAppContext { this.cx.views.insert((window_id, view_id), view); } - 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); - 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(); + observations.emit_and_cleanup(window_id, this, |callback, this| callback(active, this)); Some(()) }); @@ -2689,30 +2550,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(); + subscriptions + .emit_and_cleanup(blurred_id, this, |callback, this| callback(false, this)); } } @@ -2721,30 +2561,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(); + subscriptions + .emit_and_cleanup(focused_id, this, |callback, this| callback(true, this)); } } }) @@ -3072,6 +2891,7 @@ struct Window { root_view: AnyViewHandle, focused_view_id: Option, is_active: bool, + is_fullscreen: bool, invalidation: Option, } @@ -3138,6 +2958,10 @@ pub enum Effect { ResizeWindow { window_id: usize, }, + FullscreenWindow { + window_id: usize, + is_fullscreen: bool, + }, ActivateWindow { window_id: usize, is_active: bool, @@ -3147,6 +2971,11 @@ pub enum Effect { subscription_id: usize, callback: WindowActivationCallback, }, + WindowFullscreenObservation { + window_id: usize, + subscription_id: usize, + callback: WindowFullscreenCallback, + }, RefreshWindows, ActionDispatchNotification { action_id: TypeId, @@ -3256,6 +3085,23 @@ 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::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") @@ -3927,6 +3773,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, @@ -4032,6 +3896,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 } @@ -5199,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, @@ -5238,12 +5110,6 @@ pub enum Subscription { id: usize, observations: Option>>>, }, - WindowActivationObservation { - id: usize, - window_id: usize, - observations: - Option>>>>>, - }, } impl Subscription { @@ -5273,6 +5139,9 @@ impl Subscription { Subscription::WindowActivationObservation { observations, .. } => { observations.take(); } + Subscription::WindowFullscreenObservation { observations, .. } => { + observations.take(); + } } } } @@ -5407,6 +5276,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(); + } + } + } + } } } } @@ -5681,8 +5571,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.is_empty()); + assert!(cx.observations.is_empty()); } #[crate::test(self)] @@ -5932,8 +5822,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.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/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..568356e938 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!(windowWillEnterFullScreen:), + window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowWillExitFullScreen:), + window_will_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_will_enter_fullscreen(this: &Object, _: Sel, _: id) { + window_fullscreen_changed(this, true); +} + +extern "C" fn window_will_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); } 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, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 045e9c6f90..a27754aa13 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(); @@ -1856,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() @@ -1883,7 +1896,7 @@ impl Workspace { ) .boxed(), ) - .with_style(theme.workspace.titlebar.container) + .with_style(container_theme) .boxed(), ) .with_height(theme.workspace.titlebar.height)