use std::{ any::{type_name, TypeId}, cell::{Ref, RefCell, RefMut}, marker::PhantomData, mem, ops::{Deref, DerefMut}, path::{Path, PathBuf}, rc::{Rc, Weak}, sync::{atomic::Ordering::SeqCst, Arc}, time::Duration,}; use anyhow::{anyhow, Result};use derive_more::{Deref, DerefMut};use futures::{ channel::oneshot, future::{LocalBoxFuture, Shared}, Future, FutureExt,};use parking_lot::RwLock;use slotmap::SlotMap; pub use async_context::*;use collections::{FxHashMap, FxHashSet, HashMap, VecDeque};pub use context::*;pub use entity_map::*;use http_client::HttpClient;#[cfg(any(test, feature = "test-support"))]pub use test_context::*;use util::ResultExt; use crate::{ current_platform, hash, init_app_menus, Action, ActionBuildError, ActionRegistry, Any, AnyView, AnyWindowHandle, AppContext, Asset, AssetSource, BackgroundExecutor, Bounds, ClipboardItem, DispatchPhase, DisplayId, EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator,}; mod async_context;mod context;mod entity_map;#[cfg(any(test, feature = "test-support"))]mod test_context; /// The duration for which futures returned from [Context::on_app_quit] can run before the application fully quits.pub const SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(100); /// Temporary(?) wrapper around [`RefCell<App>`] to help us debug any double borrows./// Strongly consider removing after stabilization.#[doc(hidden)]pub struct AppCell { app: RefCell<App>,} impl AppCell { #[doc(hidden)] #[track_caller] pub fn borrow(&self) -> AppRef { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("borrowed {thread_id:?}"); } AppRef(self.app.borrow()) } #[doc(hidden)] #[track_caller] pub fn borrow_mut(&self) -> AppRefMut { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("borrowed {thread_id:?}"); } AppRefMut(self.app.borrow_mut()) }} #[doc(hidden)]#[derive(Deref, DerefMut)]pub struct AppRef<'a>(Ref<'a, App>); impl Drop for AppRef<'_> { fn drop(&mut self) { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("dropped borrow from {thread_id:?}"); } }} #[doc(hidden)]#[derive(Deref, DerefMut)]pub struct AppRefMut<'a>(RefMut<'a, App>); impl Drop for AppRefMut<'_> { fn drop(&mut self) { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("dropped {thread_id:?}"); } }} /// A reference to a GPUI application, typically constructed in the `main` function of your app./// You won't interact with this type much outside of initial configuration and startup.pub struct Application(Rc<AppCell>); /// Represents an application before it is fully launched. Once your app is/// configured, you'll start the app with `App::run`.impl Application { /// Builds an app with the given asset source. #[allow(clippy::new_without_default)] pub fn new() -> Self { #[cfg(any(test, feature = "test-support"))] log::info!("GPUI was compiled in test mode"); Self(App::new_app( current_platform(false), Arc::new(()), Arc::new(NullHttpClient), )) } /// Build an app in headless mode. This prevents opening windows, /// but makes it possible to run an application in an context like /// SSH, where GUI applications are not allowed. pub fn headless() -> Self { Self(App::new_app( current_platform(true), Arc::new(()), Arc::new(NullHttpClient), )) } /// Assign pub fn with_assets(self, asset_source: impl AssetSource) -> Self { let mut context_lock = self.0.borrow_mut(); let asset_source = Arc::new(asset_source); context_lock.asset_source = asset_source.clone(); context_lock.svg_renderer = SvgRenderer::new(asset_source); drop(context_lock); self } /// Sets the HTTP client for the application. pub fn with_http_client(self, http_client: Arc<dyn HttpClient>) -> Self { let mut context_lock = self.0.borrow_mut(); context_lock.http_client = http_client; drop(context_lock); self } /// Start the application. The provided callback will be called once the /// app is fully launched. pub fn run<F>(self, on_finish_launching: F) where F: 'static + FnOnce(&mut App), { let this = self.0.clone(); let platform = self.0.borrow().platform.clone(); platform.run(Box::new(move || { let cx = &mut *this.borrow_mut(); on_finish_launching(cx); })); } /// Register a handler to be invoked when the platform instructs the application /// to open one or more URLs. pub fn on_open_urls<F>(&self, mut callback: F) -> &Self where F: 'static + FnMut(Vec<String>), { self.0.borrow().platform.on_open_urls(Box::new(callback)); self } /// Invokes a handler when an already-running application is launched. /// On macOS, this can occur when the application icon is double-clicked or the app is launched via the dock. pub fn on_reopen<F>(&self, mut callback: F) -> &Self where F: 'static + FnMut(&mut App), { let this = Rc::downgrade(&self.0); self.0.borrow_mut().platform.on_reopen(Box::new(move || { if let Some(app) = this.upgrade() { callback(&mut app.borrow_mut()); } })); self } /// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background. pub fn background_executor(&self) -> BackgroundExecutor { self.0.borrow().background_executor.clone() } /// Returns a handle to the [`ForegroundExecutor`] associated with this app, which can be used to spawn futures in the foreground. pub fn foreground_executor(&self) -> ForegroundExecutor { self.0.borrow().foreground_executor.clone() } /// Returns a reference to the [`TextSystem`] associated with this app. pub fn text_system(&self) -> Arc<TextSystem> { self.0.borrow().text_system.clone() } /// Returns the file URL of the executable with the specified name in the application bundle pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> { self.0.borrow().path_for_auxiliary_executable(name) }} type Handler = Box<dyn FnMut(&mut App) -> bool + 'static>;type Listener = Box<dyn FnMut(&dyn Any, &mut App) -> bool + 'static>;pub(crate) type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut Window, &mut App) -> bool + 'static>;type QuitHandler = Box<dyn FnOnce(&mut App) -> LocalBoxFuture<'static, ()> + 'static>;type WindowClosedHandler = Box<dyn FnMut(&mut App)>;type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut App) + 'static>;type NewEntityListener = Box<dyn FnMut(AnyEntity, &mut Option<&mut Window>, &mut App) + 'static>; /// Contains the state of the full application, and passed as a reference to a variety of callbacks./// Other [Context] derefs to this type./// You need a reference to an `App` to access the state of a [Entity].pub struct App { pub(crate) this: Weak<AppCell>, pub(crate) platform: Rc<dyn Platform>, text_system: Arc<TextSystem>, flushing_effects: bool, pending_updates: usize, pub(crate) actions: Rc<ActionRegistry>, pub(crate) active_drag: Option<AnyDrag>, pub(crate) background_executor: BackgroundExecutor, pub(crate) foreground_executor: ForegroundExecutor, pub(crate) loading_assets: FxHashMap<(TypeId, u64), Box<dyn Any>>, asset_source: Arc<dyn AssetSource>, pub(crate) svg_renderer: SvgRenderer, http_client: Arc<dyn HttpClient>, pub(crate) globals_by_type: FxHashMap<TypeId, Box<dyn Any>>, pub(crate) entities: EntityMap, pub(crate) window_update_stack: Vec<WindowId>, pub(crate) new_entity_observers: SubscriberSet<TypeId, NewEntityListener>, pub(crate) windows: SlotMap<WindowId, Option<Window>>, pub(crate) window_handles: FxHashMap<WindowId, AnyWindowHandle>, pub(crate) focus_handles: Arc<FocusMap>, pub(crate) keymap: Rc<RefCell<Keymap>>, pub(crate) keyboard_layout: SharedString, pub(crate) global_action_listeners: FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>, pending_effects: VecDeque<Effect>, pub(crate) pending_notifications: FxHashSet<EntityId>, pub(crate) pending_global_notifications: FxHashSet<TypeId>, pub(crate) observers: SubscriberSet<EntityId, Handler>, // TypeId is the type of the event that the listener callback expects pub(crate) event_listeners: SubscriberSet<EntityId, (TypeId, Listener)>, pub(crate) keystroke_observers: SubscriberSet<(), KeystrokeObserver>, pub(crate) keyboard_layout_observers: SubscriberSet<(), Handler>, pub(crate) release_listeners: SubscriberSet<EntityId, ReleaseListener>, pub(crate) global_observers: SubscriberSet<TypeId, Handler>, pub(crate) quit_observers: SubscriberSet<(), QuitHandler>, pub(crate) window_closed_observers: SubscriberSet<(), WindowClosedHandler>, pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests. pub(crate) propagate_event: bool, pub(crate) prompt_builder: Option<PromptBuilder>, pub(crate) window_invalidators_by_entity: FxHashMap<EntityId, FxHashMap<WindowId, WindowInvalidator>>, pub(crate) tracked_entities: FxHashMap<WindowId, FxHashSet<EntityId>>, #[cfg(any(test, feature = "test-support", debug_assertions))] pub(crate) name: Option<&'static str>,} impl App { #[allow(clippy::new_ret_no_self)] pub(crate) fn new_app( platform: Rc<dyn Platform>, asset_source: Arc<dyn AssetSource>, http_client: Arc<dyn HttpClient>, ) -> Rc<AppCell> { let executor = platform.background_executor(); let foreground_executor = platform.foreground_executor(); assert!( executor.is_main_thread(), "must construct App on main thread" ); let text_system = Arc::new(TextSystem::new(platform.text_system())); let entities = EntityMap::new(); let keyboard_layout = SharedString::from(platform.keyboard_layout()); let app = Rc::new_cyclic(|this| AppCell { app: RefCell::new(App { this: this.clone(), platform: platform.clone(), text_system, actions: Rc::new(ActionRegistry::default()), flushing_effects: false, pending_updates: 0, active_drag: None, background_executor: executor, foreground_executor, svg_renderer: SvgRenderer::new(asset_source.clone()), loading_assets: Default::default(), asset_source, http_client, globals_by_type: FxHashMap::default(), entities, new_entity_observers: SubscriberSet::new(), windows: SlotMap::with_key(), window_update_stack: Vec::new(), window_handles: FxHashMap::default(), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), keymap: Rc::new(RefCell::new(Keymap::default())), keyboard_layout, global_action_listeners: FxHashMap::default(), pending_effects: VecDeque::new(), pending_notifications: FxHashSet::default(), pending_global_notifications: FxHashSet::default(), observers: SubscriberSet::new(), tracked_entities: FxHashMap::default(), window_invalidators_by_entity: FxHashMap::default(), event_listeners: SubscriberSet::new(), release_listeners: SubscriberSet::new(), keystroke_observers: SubscriberSet::new(), keyboard_layout_observers: SubscriberSet::new(), global_observers: SubscriberSet::new(), quit_observers: SubscriberSet::new(), window_closed_observers: SubscriberSet::new(), layout_id_buffer: Default::default(), propagate_event: true, prompt_builder: Some(PromptBuilder::Default), #[cfg(any(test, feature = "test-support", debug_assertions))] name: None, }), }); init_app_menus(platform.as_ref(), &mut app.borrow_mut()); platform.on_keyboard_layout_change(Box::new({ let app = Rc::downgrade(&app); move || { if let Some(app) = app.upgrade() { let cx = &mut app.borrow_mut(); cx.keyboard_layout = SharedString::from(cx.platform.keyboard_layout()); cx.keyboard_layout_observers .clone() .retain(&(), move |callback| (callback)(cx)); } } })); platform.on_quit(Box::new({ let cx = app.clone(); move || { cx.borrow_mut().shutdown(); } })); app } /// Quit the application gracefully. Handlers registered with [`Context::on_app_quit`] /// will be given 100ms to complete before exiting. pub fn shutdown(&mut self) { let mut futures = Vec::new(); for observer in self.quit_observers.remove(&()) { futures.push(observer(self)); } self.windows.clear(); self.window_handles.clear(); self.flush_effects(); let futures = futures::future::join_all(futures); if self .background_executor .block_with_timeout(SHUTDOWN_TIMEOUT, futures) .is_err() { log::error!("timed out waiting on app_will_quit"); } } /// Get the id of the current keyboard layout pub fn keyboard_layout(&self) -> &SharedString { &self.keyboard_layout } /// Invokes a handler when the current keyboard layout changes pub fn on_keyboard_layout_change<F>(&self, mut callback: F) -> Subscription where F: 'static + FnMut(&mut App), { let (subscription, activate) = self.keyboard_layout_observers.insert( (), Box::new(move |cx| { callback(cx); true }), ); activate(); subscription } /// Gracefully quit the application via the platform's standard routine. pub fn quit(&self) { self.platform.quit(); } /// Schedules all windows in the application to be redrawn. This can be called /// multiple times in an update cycle and still result in a single redraw. pub fn refresh_windows(&mut self) { self.pending_effects.push_back(Effect::RefreshWindows); } pub(crate) fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R { self.pending_updates += 1; let result = update(self); if !self.flushing_effects && self.pending_updates == 1 { self.flushing_effects = true; self.flush_effects(); self.flushing_effects = false; } self.pending_updates -= 1; result } /// Arrange a callback to be invoked when the given entity calls `notify` on its respective context. pub fn observe<W>( &mut self, entity: &Entity<W>, mut on_notify: impl FnMut(Entity<W>, &mut App) + 'static, ) -> Subscription where W: 'static, { self.observe_internal(entity, move |e, cx| { on_notify(e, cx); true }) } pub(crate) fn detect_accessed_entities<R>( &mut self, callback: impl FnOnce(&mut App) -> R, ) -> (R, FxHashSet<EntityId>) { let accessed_entities_start = self.entities.accessed_entities.borrow().clone(); let result = callback(self); let accessed_entities_end = self.entities.accessed_entities.borrow().clone(); let entities_accessed_in_callback = accessed_entities_end .difference(&accessed_entities_start) .copied() .collect::<FxHashSet<EntityId>>(); (result, entities_accessed_in_callback) } pub(crate) fn record_entities_accessed( &mut self, window_handle: AnyWindowHandle, invalidator: WindowInvalidator, entities: &FxHashSet<EntityId>, ) { let mut tracked_entities = std::mem::take(self.tracked_entities.entry(window_handle.id).or_default()); for entity in tracked_entities.iter() { self.window_invalidators_by_entity .entry(*entity) .and_modify(|windows| { windows.remove(&window_handle.id); }); } for entity in entities.iter() { self.window_invalidators_by_entity .entry(*entity) .or_default() .insert(window_handle.id, invalidator.clone()); } tracked_entities.clear(); tracked_entities.extend(entities.iter().copied()); self.tracked_entities .insert(window_handle.id, tracked_entities); } pub(crate) fn new_observer(&mut self, key: EntityId, value: Handler) -> Subscription { let (subscription, activate) = self.observers.insert(key, value); self.defer(move |_| activate()); subscription } pub(crate) fn observe_internal<W>( &mut self, entity: &Entity<W>, mut on_notify: impl FnMut(Entity<W>, &mut App) -> bool + 'static, ) -> Subscription where W: 'static,Lines 1–500 of 1844. Load more
use std::{ any::{type_name, TypeId}, cell::{Ref, RefCell, RefMut}, marker::PhantomData, mem, ops::{Deref, DerefMut}, path::{Path, PathBuf}, rc::{Rc, Weak}, sync::{atomic::Ordering::SeqCst, Arc}, time::Duration, }; use anyhow::{anyhow, Result}; use derive_more::{Deref, DerefMut}; use futures::{ channel::oneshot, future::{LocalBoxFuture, Shared}, Future, FutureExt, }; use parking_lot::RwLock; use slotmap::SlotMap; pub use async_context::*; use collections::{FxHashMap, FxHashSet, HashMap, VecDeque}; pub use context::*; pub use entity_map::*; use http_client::HttpClient; #[cfg(any(test, feature = "test-support"))] pub use test_context::*; use util::ResultExt; use crate::{ current_platform, hash, init_app_menus, Action, ActionBuildError, ActionRegistry, Any, AnyView, AnyWindowHandle, AppContext, Asset, AssetSource, BackgroundExecutor, Bounds, ClipboardItem, DispatchPhase, DisplayId, EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator, }; mod async_context; mod context; mod entity_map; #[cfg(any(test, feature = "test-support"))] mod test_context; /// The duration for which futures returned from [Context::on_app_quit] can run before the application fully quits. pub const SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(100); /// Temporary(?) wrapper around [`RefCell<App>`] to help us debug any double borrows. /// Strongly consider removing after stabilization. #[doc(hidden)] pub struct AppCell { app: RefCell<App>, } impl AppCell { #[doc(hidden)] #[track_caller] pub fn borrow(&self) -> AppRef { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("borrowed {thread_id:?}"); } AppRef(self.app.borrow()) } #[doc(hidden)] #[track_caller] pub fn borrow_mut(&self) -> AppRefMut { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("borrowed {thread_id:?}"); } AppRefMut(self.app.borrow_mut()) } } #[doc(hidden)] #[derive(Deref, DerefMut)] pub struct AppRef<'a>(Ref<'a, App>); impl Drop for AppRef<'_> { fn drop(&mut self) { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("dropped borrow from {thread_id:?}"); } } } #[doc(hidden)] #[derive(Deref, DerefMut)] pub struct AppRefMut<'a>(RefMut<'a, App>); impl Drop for AppRefMut<'_> { fn drop(&mut self) { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("dropped {thread_id:?}"); } } } /// A reference to a GPUI application, typically constructed in the `main` function of your app. /// You won't interact with this type much outside of initial configuration and startup. pub struct Application(Rc<AppCell>); /// Represents an application before it is fully launched. Once your app is /// configured, you'll start the app with `App::run`. impl Application { /// Builds an app with the given asset source. #[allow(clippy::new_without_default)] pub fn new() -> Self { #[cfg(any(test, feature = "test-support"))] log::info!("GPUI was compiled in test mode"); Self(App::new_app( current_platform(false), Arc::new(()), Arc::new(NullHttpClient), )) } /// Build an app in headless mode. This prevents opening windows, /// but makes it possible to run an application in an context like /// SSH, where GUI applications are not allowed. pub fn headless() -> Self { Self(App::new_app( current_platform(true), Arc::new(()), Arc::new(NullHttpClient), )) } /// Assign pub fn with_assets(self, asset_source: impl AssetSource) -> Self { let mut context_lock = self.0.borrow_mut(); let asset_source = Arc::new(asset_source); context_lock.asset_source = asset_source.clone(); context_lock.svg_renderer = SvgRenderer::new(asset_source); drop(context_lock); self } /// Sets the HTTP client for the application. pub fn with_http_client(self, http_client: Arc<dyn HttpClient>) -> Self { let mut context_lock = self.0.borrow_mut(); context_lock.http_client = http_client; drop(context_lock); self } /// Start the application. The provided callback will be called once the /// app is fully launched. pub fn run<F>(self, on_finish_launching: F) where F: 'static + FnOnce(&mut App), { let this = self.0.clone(); let platform = self.0.borrow().platform.clone(); platform.run(Box::new(move || { let cx = &mut *this.borrow_mut(); on_finish_launching(cx); })); } /// Register a handler to be invoked when the platform instructs the application /// to open one or more URLs. pub fn on_open_urls<F>(&self, mut callback: F) -> &Self where F: 'static + FnMut(Vec<String>), { self.0.borrow().platform.on_open_urls(Box::new(callback)); self } /// Invokes a handler when an already-running application is launched. /// On macOS, this can occur when the application icon is double-clicked or the app is launched via the dock. pub fn on_reopen<F>(&self, mut callback: F) -> &Self where F: 'static + FnMut(&mut App), { let this = Rc::downgrade(&self.0); self.0.borrow_mut().platform.on_reopen(Box::new(move || { if let Some(app) = this.upgrade() { callback(&mut app.borrow_mut()); } })); self } /// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background. pub fn background_executor(&self) -> BackgroundExecutor { self.0.borrow().background_executor.clone() } /// Returns a handle to the [`ForegroundExecutor`] associated with this app, which can be used to spawn futures in the foreground. pub fn foreground_executor(&self) -> ForegroundExecutor { self.0.borrow().foreground_executor.clone() } /// Returns a reference to the [`TextSystem`] associated with this app. pub fn text_system(&self) -> Arc<TextSystem> { self.0.borrow().text_system.clone() } /// Returns the file URL of the executable with the specified name in the application bundle pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> { self.0.borrow().path_for_auxiliary_executable(name) } } type Handler = Box<dyn FnMut(&mut App) -> bool + 'static>; type Listener = Box<dyn FnMut(&dyn Any, &mut App) -> bool + 'static>; pub(crate) type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut Window, &mut App) -> bool + 'static>; type QuitHandler = Box<dyn FnOnce(&mut App) -> LocalBoxFuture<'static, ()> + 'static>; type WindowClosedHandler = Box<dyn FnMut(&mut App)>; type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut App) + 'static>; type NewEntityListener = Box<dyn FnMut(AnyEntity, &mut Option<&mut Window>, &mut App) + 'static>; /// Contains the state of the full application, and passed as a reference to a variety of callbacks. /// Other [Context] derefs to this type. /// You need a reference to an `App` to access the state of a [Entity]. pub struct App { pub(crate) this: Weak<AppCell>, pub(crate) platform: Rc<dyn Platform>, text_system: Arc<TextSystem>, flushing_effects: bool, pending_updates: usize, pub(crate) actions: Rc<ActionRegistry>, pub(crate) active_drag: Option<AnyDrag>, pub(crate) background_executor: BackgroundExecutor, pub(crate) foreground_executor: ForegroundExecutor, pub(crate) loading_assets: FxHashMap<(TypeId, u64), Box<dyn Any>>, asset_source: Arc<dyn AssetSource>, pub(crate) svg_renderer: SvgRenderer, http_client: Arc<dyn HttpClient>, pub(crate) globals_by_type: FxHashMap<TypeId, Box<dyn Any>>, pub(crate) entities: EntityMap, pub(crate) window_update_stack: Vec<WindowId>, pub(crate) new_entity_observers: SubscriberSet<TypeId, NewEntityListener>, pub(crate) windows: SlotMap<WindowId, Option<Window>>, pub(crate) window_handles: FxHashMap<WindowId, AnyWindowHandle>, pub(crate) focus_handles: Arc<FocusMap>, pub(crate) keymap: Rc<RefCell<Keymap>>, pub(crate) keyboard_layout: SharedString, pub(crate) global_action_listeners: FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>, pending_effects: VecDeque<Effect>, pub(crate) pending_notifications: FxHashSet<EntityId>, pub(crate) pending_global_notifications: FxHashSet<TypeId>, pub(crate) observers: SubscriberSet<EntityId, Handler>, // TypeId is the type of the event that the listener callback expects pub(crate) event_listeners: SubscriberSet<EntityId, (TypeId, Listener)>, pub(crate) keystroke_observers: SubscriberSet<(), KeystrokeObserver>, pub(crate) keyboard_layout_observers: SubscriberSet<(), Handler>, pub(crate) release_listeners: SubscriberSet<EntityId, ReleaseListener>, pub(crate) global_observers: SubscriberSet<TypeId, Handler>, pub(crate) quit_observers: SubscriberSet<(), QuitHandler>, pub(crate) window_closed_observers: SubscriberSet<(), WindowClosedHandler>, pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests. pub(crate) propagate_event: bool, pub(crate) prompt_builder: Option<PromptBuilder>, pub(crate) window_invalidators_by_entity: FxHashMap<EntityId, FxHashMap<WindowId, WindowInvalidator>>, pub(crate) tracked_entities: FxHashMap<WindowId, FxHashSet<EntityId>>, #[cfg(any(test, feature = "test-support", debug_assertions))] pub(crate) name: Option<&'static str>, } impl App { #[allow(clippy::new_ret_no_self)] pub(crate) fn new_app( platform: Rc<dyn Platform>, asset_source: Arc<dyn AssetSource>, http_client: Arc<dyn HttpClient>, ) -> Rc<AppCell> { let executor = platform.background_executor(); let foreground_executor = platform.foreground_executor(); assert!( executor.is_main_thread(), "must construct App on main thread" ); let text_system = Arc::new(TextSystem::new(platform.text_system())); let entities = EntityMap::new(); let keyboard_layout = SharedString::from(platform.keyboard_layout()); let app = Rc::new_cyclic(|this| AppCell { app: RefCell::new(App { this: this.clone(), platform: platform.clone(), text_system, actions: Rc::new(ActionRegistry::default()), flushing_effects: false, pending_updates: 0, active_drag: None, background_executor: executor, foreground_executor, svg_renderer: SvgRenderer::new(asset_source.clone()), loading_assets: Default::default(), asset_source, http_client, globals_by_type: FxHashMap::default(), entities, new_entity_observers: SubscriberSet::new(), windows: SlotMap::with_key(), window_update_stack: Vec::new(), window_handles: FxHashMap::default(), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), keymap: Rc::new(RefCell::new(Keymap::default())), keyboard_layout, global_action_listeners: FxHashMap::default(), pending_effects: VecDeque::new(), pending_notifications: FxHashSet::default(), pending_global_notifications: FxHashSet::default(), observers: SubscriberSet::new(), tracked_entities: FxHashMap::default(), window_invalidators_by_entity: FxHashMap::default(), event_listeners: SubscriberSet::new(), release_listeners: SubscriberSet::new(), keystroke_observers: SubscriberSet::new(), keyboard_layout_observers: SubscriberSet::new(), global_observers: SubscriberSet::new(), quit_observers: SubscriberSet::new(), window_closed_observers: SubscriberSet::new(), layout_id_buffer: Default::default(), propagate_event: true, prompt_builder: Some(PromptBuilder::Default), #[cfg(any(test, feature = "test-support", debug_assertions))] name: None, }), }); init_app_menus(platform.as_ref(), &mut app.borrow_mut()); platform.on_keyboard_layout_change(Box::new({ let app = Rc::downgrade(&app); move || { if let Some(app) = app.upgrade() { let cx = &mut app.borrow_mut(); cx.keyboard_layout = SharedString::from(cx.platform.keyboard_layout()); cx.keyboard_layout_observers .clone() .retain(&(), move |callback| (callback)(cx)); } } })); platform.on_quit(Box::new({ let cx = app.clone(); move || { cx.borrow_mut().shutdown(); } })); app } /// Quit the application gracefully. Handlers registered with [`Context::on_app_quit`] /// will be given 100ms to complete before exiting. pub fn shutdown(&mut self) { let mut futures = Vec::new(); for observer in self.quit_observers.remove(&()) { futures.push(observer(self)); } self.windows.clear(); self.window_handles.clear(); self.flush_effects(); let futures = futures::future::join_all(futures); if self .background_executor .block_with_timeout(SHUTDOWN_TIMEOUT, futures) .is_err() { log::error!("timed out waiting on app_will_quit"); } } /// Get the id of the current keyboard layout pub fn keyboard_layout(&self) -> &SharedString { &self.keyboard_layout } /// Invokes a handler when the current keyboard layout changes pub fn on_keyboard_layout_change<F>(&self, mut callback: F) -> Subscription where F: 'static + FnMut(&mut App), { let (subscription, activate) = self.keyboard_layout_observers.insert( (), Box::new(move |cx| { callback(cx); true }), ); activate(); subscription } /// Gracefully quit the application via the platform's standard routine. pub fn quit(&self) { self.platform.quit(); } /// Schedules all windows in the application to be redrawn. This can be called /// multiple times in an update cycle and still result in a single redraw. pub fn refresh_windows(&mut self) { self.pending_effects.push_back(Effect::RefreshWindows); } pub(crate) fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R { self.pending_updates += 1; let result = update(self); if !self.flushing_effects && self.pending_updates == 1 { self.flushing_effects = true; self.flush_effects(); self.flushing_effects = false; } self.pending_updates -= 1; result } /// Arrange a callback to be invoked when the given entity calls `notify` on its respective context. pub fn observe<W>( &mut self, entity: &Entity<W>, mut on_notify: impl FnMut(Entity<W>, &mut App) + 'static, ) -> Subscription where W: 'static, { self.observe_internal(entity, move |e, cx| { on_notify(e, cx); true }) } pub(crate) fn detect_accessed_entities<R>( &mut self, callback: impl FnOnce(&mut App) -> R, ) -> (R, FxHashSet<EntityId>) { let accessed_entities_start = self.entities.accessed_entities.borrow().clone(); let result = callback(self); let accessed_entities_end = self.entities.accessed_entities.borrow().clone(); let entities_accessed_in_callback = accessed_entities_end .difference(&accessed_entities_start) .copied() .collect::<FxHashSet<EntityId>>(); (result, entities_accessed_in_callback) } pub(crate) fn record_entities_accessed( &mut self, window_handle: AnyWindowHandle, invalidator: WindowInvalidator, entities: &FxHashSet<EntityId>, ) { let mut tracked_entities = std::mem::take(self.tracked_entities.entry(window_handle.id).or_default()); for entity in tracked_entities.iter() { self.window_invalidators_by_entity .entry(*entity) .and_modify(|windows| { windows.remove(&window_handle.id); }); } for entity in entities.iter() { self.window_invalidators_by_entity .entry(*entity) .or_default() .insert(window_handle.id, invalidator.clone()); } tracked_entities.clear(); tracked_entities.extend(entities.iter().copied()); self.tracked_entities .insert(window_handle.id, tracked_entities); } pub(crate) fn new_observer(&mut self, key: EntityId, value: Handler) -> Subscription { let (subscription, activate) = self.observers.insert(key, value); self.defer(move |_| activate()); subscription } pub(crate) fn observe_internal<W>( &mut self, entity: &Entity<W>, mut on_notify: impl FnMut(Entity<W>, &mut App) -> bool + 'static, ) -> Subscription where W: 'static,