ZIm/crates/gpui/src/app.rs
Piotr Osiewicz 88af35fe47
collab: Add screen selector (#31506)
Instead of selecting a screen to share arbitrarily, we'll now allow user
to select the screen to share. Note that sharing multiple screens at the
time is still not supported (though prolly not too far-fetched).

Related to #4666

![image](https://github.com/user-attachments/assets/1afb664f-3cdb-4e0a-bb29-9d7093d87fa5)

Release Notes:

- Added screen selector dropdown to screen share button

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Cole Miller <cole@zed.dev>
2025-07-21 13:44:51 +02:00

2109 lines
73 KiB
Rust

use std::{
any::{TypeId, type_name},
cell::{BorrowMutError, Ref, RefCell, RefMut},
marker::PhantomData,
mem,
ops::{Deref, DerefMut},
path::{Path, PathBuf},
rc::{Rc, Weak},
sync::{Arc, atomic::Ordering::SeqCst},
time::Duration,
};
use anyhow::{Context as _, Result, anyhow};
use derive_more::{Deref, DerefMut};
use futures::{
Future, FutureExt,
channel::oneshot,
future::{LocalBoxFuture, Shared},
};
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, Url};
use smallvec::SmallVec;
#[cfg(any(test, feature = "test-support"))]
pub use test_context::*;
use util::{ResultExt, debug_panic};
#[cfg(any(feature = "inspector", debug_assertions))]
use crate::InspectorElementRegistry;
use crate::{
Action, ActionBuildError, ActionRegistry, Any, AnyView, AnyWindowHandle, AppContext, Asset,
AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId,
EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext,
Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptButton, PromptHandle,
PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource,
SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance,
WindowHandle, WindowId, WindowInvalidator,
colors::{Colors, GlobalColors},
current_platform, hash, init_app_menus,
};
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)]
#[track_caller]
pub fn try_borrow_mut(&self) -> Result<AppRefMut<'_>, BorrowMutError> {
if option_env!("TRACK_THREAD_BORROWS").is_some() {
let thread_id = std::thread::current().id();
eprintln!("borrowed {thread_id:?}");
}
Ok(AppRefMut(self.app.try_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: Box<dyn PlatformKeyboardLayout>,
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) keystroke_interceptors: 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(feature = "inspector", debug_assertions))]
pub(crate) inspector_renderer: Option<crate::InspectorRenderer>,
#[cfg(any(feature = "inspector", debug_assertions))]
pub(crate) inspector_element_registry: InspectorElementRegistry,
#[cfg(any(test, feature = "test-support", debug_assertions))]
pub(crate) name: Option<&'static str>,
quitting: bool,
}
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 = 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(),
keystroke_interceptors: 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(feature = "inspector", debug_assertions))]
inspector_renderer: None,
#[cfg(any(feature = "inspector", debug_assertions))]
inspector_element_registry: InspectorElementRegistry::default(),
quitting: false,
#[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 = 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();
self.quitting = true;
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");
}
self.quitting = false;
}
/// Get the id of the current keyboard layout
pub fn keyboard_layout(&self) -> &dyn PlatformKeyboardLayout {
self.keyboard_layout.as_ref()
}
/// 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.start_update();
let result = update(self);
self.finish_update();
result
}
pub(crate) fn start_update(&mut self) {
self.pending_updates += 1;
}
pub(crate) fn finish_update(&mut self) {
if !self.flushing_effects && self.pending_updates == 1 {
self.flushing_effects = true;
self.flush_effects();
self.flushing_effects = false;
}
self.pending_updates -= 1;
}
/// 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,
{
let entity_id = entity.entity_id();
let handle = entity.downgrade();
self.new_observer(
entity_id,
Box::new(move |cx| {
if let Some(entity) = handle.upgrade() {
on_notify(entity, cx)
} else {
false
}
}),
)
}
/// Arrange for the given callback to be invoked whenever the given entity emits an event of a given type.
/// The callback is provided a handle to the emitting entity and a reference to the emitted event.
pub fn subscribe<T, Event>(
&mut self,
entity: &Entity<T>,
mut on_event: impl FnMut(Entity<T>, &Event, &mut App) + 'static,
) -> Subscription
where
T: 'static + EventEmitter<Event>,
Event: 'static,
{
self.subscribe_internal(entity, move |entity, event, cx| {
on_event(entity, event, cx);
true
})
}
pub(crate) fn new_subscription(
&mut self,
key: EntityId,
value: (TypeId, Listener),
) -> Subscription {
let (subscription, activate) = self.event_listeners.insert(key, value);
self.defer(move |_| activate());
subscription
}
pub(crate) fn subscribe_internal<T, Evt>(
&mut self,
entity: &Entity<T>,
mut on_event: impl FnMut(Entity<T>, &Evt, &mut App) -> bool + 'static,
) -> Subscription
where
T: 'static + EventEmitter<Evt>,
Evt: 'static,
{
let entity_id = entity.entity_id();
let handle = entity.downgrade();
self.new_subscription(
entity_id,
(
TypeId::of::<Evt>(),
Box::new(move |event, cx| {
let event: &Evt = event.downcast_ref().expect("invalid event type");
if let Some(entity) = handle.upgrade() {
on_event(entity, event, cx)
} else {
false
}
}),
),
)
}
/// Returns handles to all open windows in the application.
/// Each handle could be downcast to a handle typed for the root view of that window.
/// To find all windows of a given type, you could filter on
pub fn windows(&self) -> Vec<AnyWindowHandle> {
self.windows
.keys()
.flat_map(|window_id| self.window_handles.get(&window_id).copied())
.collect()
}
/// Returns the window handles ordered by their appearance on screen, front to back.
///
/// The first window in the returned list is the active/topmost window of the application.
///
/// This method returns None if the platform doesn't implement the method yet.
pub fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
self.platform.window_stack()
}
/// Returns a handle to the window that is currently focused at the platform level, if one exists.
pub fn active_window(&self) -> Option<AnyWindowHandle> {
self.platform.active_window()
}
/// Opens a new window with the given option and the root view returned by the given function.
/// The function is invoked with a `Window`, which can be used to interact with window-specific
/// functionality.
pub fn open_window<V: 'static + Render>(
&mut self,
options: crate::WindowOptions,
build_root_view: impl FnOnce(&mut Window, &mut App) -> Entity<V>,
) -> anyhow::Result<WindowHandle<V>> {
self.update(|cx| {
let id = cx.windows.insert(None);
let handle = WindowHandle::new(id);
match Window::new(handle.into(), options, cx) {
Ok(mut window) => {
cx.window_update_stack.push(id);
let root_view = build_root_view(&mut window, cx);
cx.window_update_stack.pop();
window.root.replace(root_view.into());
window.defer(cx, |window: &mut Window, cx| window.appearance_changed(cx));
cx.window_handles.insert(id, window.handle);
cx.windows.get_mut(id).unwrap().replace(window);
Ok(handle)
}
Err(e) => {
cx.windows.remove(id);
Err(e)
}
}
})
}
/// Instructs the platform to activate the application by bringing it to the foreground.
pub fn activate(&self, ignoring_other_apps: bool) {
self.platform.activate(ignoring_other_apps);
}
/// Hide the application at the platform level.
pub fn hide(&self) {
self.platform.hide();
}
/// Hide other applications at the platform level.
pub fn hide_other_apps(&self) {
self.platform.hide_other_apps();
}
/// Unhide other applications at the platform level.
pub fn unhide_other_apps(&self) {
self.platform.unhide_other_apps();
}
/// Returns the list of currently active displays.
pub fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
self.platform.displays()
}
/// Returns the primary display that will be used for new windows.
pub fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
self.platform.primary_display()
}
/// Returns whether `screen_capture_sources` may work.
pub fn is_screen_capture_supported(&self) -> bool {
self.platform.is_screen_capture_supported()
}
/// Returns a list of available screen capture sources.
pub fn screen_capture_sources(
&self,
) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
self.platform.screen_capture_sources()
}
/// Returns the display with the given ID, if one exists.
pub fn find_display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
self.displays()
.iter()
.find(|display| display.id() == id)
.cloned()
}
/// Returns the appearance of the application's windows.
pub fn window_appearance(&self) -> WindowAppearance {
self.platform.window_appearance()
}
/// Writes data to the primary selection buffer.
/// Only available on Linux.
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub fn write_to_primary(&self, item: ClipboardItem) {
self.platform.write_to_primary(item)
}
/// Writes data to the platform clipboard.
pub fn write_to_clipboard(&self, item: ClipboardItem) {
self.platform.write_to_clipboard(item)
}
/// Reads data from the primary selection buffer.
/// Only available on Linux.
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub fn read_from_primary(&self) -> Option<ClipboardItem> {
self.platform.read_from_primary()
}
/// Reads data from the platform clipboard.
pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.platform.read_from_clipboard()
}
/// Writes credentials to the platform keychain.
pub fn write_credentials(
&self,
url: &str,
username: &str,
password: &[u8],
) -> Task<Result<()>> {
self.platform.write_credentials(url, username, password)
}
/// Reads credentials from the platform keychain.
pub fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
self.platform.read_credentials(url)
}
/// Deletes credentials from the platform keychain.
pub fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
self.platform.delete_credentials(url)
}
/// Directs the platform's default browser to open the given URL.
pub fn open_url(&self, url: &str) {
self.platform.open_url(url);
}
/// Registers the given URL scheme (e.g. `zed` for `zed://` urls) to be
/// opened by the current app.
///
/// On some platforms (e.g. macOS) you may be able to register URL schemes
/// as part of app distribution, but this method exists to let you register
/// schemes at runtime.
pub fn register_url_scheme(&self, scheme: &str) -> Task<Result<()>> {
self.platform.register_url_scheme(scheme)
}
/// Returns the full pathname of the current app bundle.
///
/// Returns an error if the app is not being run from a bundle.
pub fn app_path(&self) -> Result<PathBuf> {
self.platform.app_path()
}
/// On Linux, returns the name of the compositor in use.
///
/// Returns an empty string on other platforms.
pub fn compositor_name(&self) -> &'static str {
self.platform.compositor_name()
}
/// 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.platform.path_for_auxiliary_executable(name)
}
/// Displays a platform modal for selecting paths.
///
/// When one or more paths are selected, they'll be relayed asynchronously via the returned oneshot channel.
/// If cancelled, a `None` will be relayed instead.
/// May return an error on Linux if the file picker couldn't be opened.
pub fn prompt_for_paths(
&self,
options: PathPromptOptions,
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>> {
self.platform.prompt_for_paths(options)
}
/// Displays a platform modal for selecting a new path where a file can be saved.
///
/// The provided directory will be used to set the initial location.
/// When a path is selected, it is relayed asynchronously via the returned oneshot channel.
/// If cancelled, a `None` will be relayed instead.
/// May return an error on Linux if the file picker couldn't be opened.
pub fn prompt_for_new_path(
&self,
directory: &Path,
) -> oneshot::Receiver<Result<Option<PathBuf>>> {
self.platform.prompt_for_new_path(directory)
}
/// Reveals the specified path at the platform level, such as in Finder on macOS.
pub fn reveal_path(&self, path: &Path) {
self.platform.reveal_path(path)
}
/// Opens the specified path with the system's default application.
pub fn open_with_system(&self, path: &Path) {
self.platform.open_with_system(path)
}
/// Returns whether the user has configured scrollbars to auto-hide at the platform level.
pub fn should_auto_hide_scrollbars(&self) -> bool {
self.platform.should_auto_hide_scrollbars()
}
/// Restarts the application.
pub fn restart(&self, binary_path: Option<PathBuf>) {
self.platform.restart(binary_path)
}
/// Returns the HTTP client for the application.
pub fn http_client(&self) -> Arc<dyn HttpClient> {
self.http_client.clone()
}
/// Sets the HTTP client for the application.
pub fn set_http_client(&mut self, new_client: Arc<dyn HttpClient>) {
self.http_client = new_client;
}
/// Returns the SVG renderer used by the application.
pub fn svg_renderer(&self) -> SvgRenderer {
self.svg_renderer.clone()
}
pub(crate) fn push_effect(&mut self, effect: Effect) {
match &effect {
Effect::Notify { emitter } => {
if !self.pending_notifications.insert(*emitter) {
return;
}
}
Effect::NotifyGlobalObservers { global_type } => {
if !self.pending_global_notifications.insert(*global_type) {
return;
}
}
_ => {}
};
self.pending_effects.push_back(effect);
}
/// Called at the end of [`App::update`] to complete any side effects
/// such as notifying observers, emitting events, etc. Effects can themselves
/// cause effects, so we continue looping until all effects are processed.
fn flush_effects(&mut self) {
loop {
self.release_dropped_entities();
self.release_dropped_focus_handles();
if let Some(effect) = self.pending_effects.pop_front() {
match effect {
Effect::Notify { emitter } => {
self.apply_notify_effect(emitter);
}
Effect::Emit {
emitter,
event_type,
event,
} => self.apply_emit_effect(emitter, event_type, event),
Effect::RefreshWindows => {
self.apply_refresh_effect();
}
Effect::NotifyGlobalObservers { global_type } => {
self.apply_notify_global_observers_effect(global_type);
}
Effect::Defer { callback } => {
self.apply_defer_effect(callback);
}
Effect::EntityCreated {
entity,
tid,
window,
} => {
self.apply_entity_created_effect(entity, tid, window);
}
}
} else {
#[cfg(any(test, feature = "test-support"))]
for window in self
.windows
.values()
.filter_map(|window| {
let window = window.as_ref()?;
window.invalidator.is_dirty().then_some(window.handle)
})
.collect::<Vec<_>>()
{
self.update_window(window, |_, window, cx| window.draw(cx).clear())
.unwrap();
}
if self.pending_effects.is_empty() {
break;
}
}
}
}
/// Repeatedly called during `flush_effects` to release any entities whose
/// reference count has become zero. We invoke any release observers before dropping
/// each entity.
fn release_dropped_entities(&mut self) {
loop {
let dropped = self.entities.take_dropped();
if dropped.is_empty() {
break;
}
for (entity_id, mut entity) in dropped {
self.observers.remove(&entity_id);
self.event_listeners.remove(&entity_id);
for release_callback in self.release_listeners.remove(&entity_id) {
release_callback(entity.as_mut(), self);
}
}
}
}
/// Repeatedly called during `flush_effects` to handle a focused handle being dropped.
fn release_dropped_focus_handles(&mut self) {
self.focus_handles
.clone()
.write()
.retain(|handle_id, focus| {
if focus.ref_count.load(SeqCst) == 0 {
for window_handle in self.windows() {
window_handle
.update(self, |_, window, _| {
if window.focus == Some(handle_id) {
window.blur();
}
})
.unwrap();
}
false
} else {
true
}
});
}
fn apply_notify_effect(&mut self, emitter: EntityId) {
self.pending_notifications.remove(&emitter);
self.observers
.clone()
.retain(&emitter, |handler| handler(self));
}
fn apply_emit_effect(&mut self, emitter: EntityId, event_type: TypeId, event: Box<dyn Any>) {
self.event_listeners
.clone()
.retain(&emitter, |(stored_type, handler)| {
if *stored_type == event_type {
handler(event.as_ref(), self)
} else {
true
}
});
}
fn apply_refresh_effect(&mut self) {
for window in self.windows.values_mut() {
if let Some(window) = window.as_mut() {
window.refreshing = true;
window.invalidator.set_dirty(true);
}
}
}
fn apply_notify_global_observers_effect(&mut self, type_id: TypeId) {
self.pending_global_notifications.remove(&type_id);
self.global_observers
.clone()
.retain(&type_id, |observer| observer(self));
}
fn apply_defer_effect(&mut self, callback: Box<dyn FnOnce(&mut Self) + 'static>) {
callback(self);
}
fn apply_entity_created_effect(
&mut self,
entity: AnyEntity,
tid: TypeId,
window: Option<WindowId>,
) {
self.new_entity_observers.clone().retain(&tid, |observer| {
if let Some(id) = window {
self.update_window_id(id, {
let entity = entity.clone();
|_, window, cx| (observer)(entity, &mut Some(window), cx)
})
.expect("All windows should be off the stack when flushing effects");
} else {
(observer)(entity.clone(), &mut None, self)
}
true
});
}
fn update_window_id<T, F>(&mut self, id: WindowId, update: F) -> Result<T>
where
F: FnOnce(AnyView, &mut Window, &mut App) -> T,
{
self.update(|cx| {
let mut window = cx
.windows
.get_mut(id)
.context("window not found")?
.take()
.context("window not found")?;
let root_view = window.root.clone().unwrap();
cx.window_update_stack.push(window.handle.id);
let result = update(root_view, &mut window, cx);
cx.window_update_stack.pop();
if window.removed {
cx.window_handles.remove(&id);
cx.windows.remove(id);
cx.window_closed_observers.clone().retain(&(), |callback| {
callback(cx);
true
});
} else {
cx.windows
.get_mut(id)
.context("window not found")?
.replace(window);
}
Ok(result)
})
}
/// Creates an `AsyncApp`, which can be cloned and has a static lifetime
/// so it can be held across `await` points.
pub fn to_async(&self) -> AsyncApp {
AsyncApp {
app: self.this.clone(),
background_executor: self.background_executor.clone(),
foreground_executor: self.foreground_executor.clone(),
}
}
/// Obtains a reference to the executor, which can be used to spawn futures.
pub fn background_executor(&self) -> &BackgroundExecutor {
&self.background_executor
}
/// Obtains a reference to the executor, which can be used to spawn futures.
pub fn foreground_executor(&self) -> &ForegroundExecutor {
if self.quitting {
panic!("Can't spawn on main thread after on_app_quit")
};
&self.foreground_executor
}
/// Spawns the future returned by the given function on the main thread. The closure will be invoked
/// with [AsyncApp], which allows the application state to be accessed across await points.
#[track_caller]
pub fn spawn<AsyncFn, R>(&self, f: AsyncFn) -> Task<R>
where
AsyncFn: AsyncFnOnce(&mut AsyncApp) -> R + 'static,
R: 'static,
{
if self.quitting {
debug_panic!("Can't spawn on main thread after on_app_quit")
};
let mut cx = self.to_async();
self.foreground_executor
.spawn(async move { f(&mut cx).await })
}
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
/// that are currently on the stack to be returned to the app.
pub fn defer(&mut self, f: impl FnOnce(&mut App) + 'static) {
self.push_effect(Effect::Defer {
callback: Box::new(f),
});
}
/// Accessor for the application's asset source, which is provided when constructing the `App`.
pub fn asset_source(&self) -> &Arc<dyn AssetSource> {
&self.asset_source
}
/// Accessor for the text system.
pub fn text_system(&self) -> &Arc<TextSystem> {
&self.text_system
}
/// Check whether a global of the given type has been assigned.
pub fn has_global<G: Global>(&self) -> bool {
self.globals_by_type.contains_key(&TypeId::of::<G>())
}
/// Access the global of the given type. Panics if a global for that type has not been assigned.
#[track_caller]
pub fn global<G: Global>(&self) -> &G {
self.globals_by_type
.get(&TypeId::of::<G>())
.map(|any_state| any_state.downcast_ref::<G>().unwrap())
.with_context(|| format!("no state of type {} exists", type_name::<G>()))
.unwrap()
}
/// Access the global of the given type if a value has been assigned.
pub fn try_global<G: Global>(&self) -> Option<&G> {
self.globals_by_type
.get(&TypeId::of::<G>())
.map(|any_state| any_state.downcast_ref::<G>().unwrap())
}
/// Access the global of the given type mutably. Panics if a global for that type has not been assigned.
#[track_caller]
pub fn global_mut<G: Global>(&mut self) -> &mut G {
let global_type = TypeId::of::<G>();
self.push_effect(Effect::NotifyGlobalObservers { global_type });
self.globals_by_type
.get_mut(&global_type)
.and_then(|any_state| any_state.downcast_mut::<G>())
.with_context(|| format!("no state of type {} exists", type_name::<G>()))
.unwrap()
}
/// Access the global of the given type mutably. A default value is assigned if a global of this type has not
/// yet been assigned.
pub fn default_global<G: Global + Default>(&mut self) -> &mut G {
let global_type = TypeId::of::<G>();
self.push_effect(Effect::NotifyGlobalObservers { global_type });
self.globals_by_type
.entry(global_type)
.or_insert_with(|| Box::<G>::default())
.downcast_mut::<G>()
.unwrap()
}
/// Sets the value of the global of the given type.
pub fn set_global<G: Global>(&mut self, global: G) {
let global_type = TypeId::of::<G>();
self.push_effect(Effect::NotifyGlobalObservers { global_type });
self.globals_by_type.insert(global_type, Box::new(global));
}
/// Clear all stored globals. Does not notify global observers.
#[cfg(any(test, feature = "test-support"))]
pub fn clear_globals(&mut self) {
self.globals_by_type.drain();
}
/// Remove the global of the given type from the app context. Does not notify global observers.
pub fn remove_global<G: Global>(&mut self) -> G {
let global_type = TypeId::of::<G>();
self.push_effect(Effect::NotifyGlobalObservers { global_type });
*self
.globals_by_type
.remove(&global_type)
.unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::<G>()))
.downcast()
.unwrap()
}
/// Register a callback to be invoked when a global of the given type is updated.
pub fn observe_global<G: Global>(
&mut self,
mut f: impl FnMut(&mut Self) + 'static,
) -> Subscription {
let (subscription, activate) = self.global_observers.insert(
TypeId::of::<G>(),
Box::new(move |cx| {
f(cx);
true
}),
);
self.defer(move |_| activate());
subscription
}
/// Move the global of the given type to the stack.
#[track_caller]
pub(crate) fn lease_global<G: Global>(&mut self) -> GlobalLease<G> {
GlobalLease::new(
self.globals_by_type
.remove(&TypeId::of::<G>())
.with_context(|| format!("no global registered of type {}", type_name::<G>()))
.unwrap(),
)
}
/// Restore the global of the given type after it is moved to the stack.
pub(crate) fn end_global_lease<G: Global>(&mut self, lease: GlobalLease<G>) {
let global_type = TypeId::of::<G>();
self.push_effect(Effect::NotifyGlobalObservers { global_type });
self.globals_by_type.insert(global_type, lease.global);
}
pub(crate) fn new_entity_observer(
&self,
key: TypeId,
value: NewEntityListener,
) -> Subscription {
let (subscription, activate) = self.new_entity_observers.insert(key, value);
activate();
subscription
}
/// Arrange for the given function to be invoked whenever a view of the specified type is created.
/// The function will be passed a mutable reference to the view along with an appropriate context.
pub fn observe_new<T: 'static>(
&self,
on_new: impl 'static + Fn(&mut T, Option<&mut Window>, &mut Context<T>),
) -> Subscription {
self.new_entity_observer(
TypeId::of::<T>(),
Box::new(
move |any_entity: AnyEntity, window: &mut Option<&mut Window>, cx: &mut App| {
any_entity
.downcast::<T>()
.unwrap()
.update(cx, |entity_state, cx| {
on_new(entity_state, window.as_deref_mut(), cx)
})
},
),
)
}
/// Observe the release of a entity. The callback is invoked after the entity
/// has no more strong references but before it has been dropped.
pub fn observe_release<T>(
&self,
handle: &Entity<T>,
on_release: impl FnOnce(&mut T, &mut App) + 'static,
) -> Subscription
where
T: 'static,
{
let (subscription, activate) = self.release_listeners.insert(
handle.entity_id(),
Box::new(move |entity, cx| {
let entity = entity.downcast_mut().expect("invalid entity type");
on_release(entity, cx)
}),
);
activate();
subscription
}
/// Observe the release of a entity. The callback is invoked after the entity
/// has no more strong references but before it has been dropped.
pub fn observe_release_in<T>(
&self,
handle: &Entity<T>,
window: &Window,
on_release: impl FnOnce(&mut T, &mut Window, &mut App) + 'static,
) -> Subscription
where
T: 'static,
{
let window_handle = window.handle;
self.observe_release(&handle, move |entity, cx| {
let _ = window_handle.update(cx, |_, window, cx| on_release(entity, window, cx));
})
}
/// Register a callback to be invoked when a keystroke is received by the application
/// in any window. Note that this fires after all other action and event mechanisms have resolved
/// and that this API will not be invoked if the event's propagation is stopped.
pub fn observe_keystrokes(
&mut self,
mut f: impl FnMut(&KeystrokeEvent, &mut Window, &mut App) + 'static,
) -> Subscription {
fn inner(
keystroke_observers: &SubscriberSet<(), KeystrokeObserver>,
handler: KeystrokeObserver,
) -> Subscription {
let (subscription, activate) = keystroke_observers.insert((), handler);
activate();
subscription
}
inner(
&mut self.keystroke_observers,
Box::new(move |event, window, cx| {
f(event, window, cx);
true
}),
)
}
/// Register a callback to be invoked when a keystroke is received by the application
/// in any window. Note that this fires _before_ all other action and event mechanisms have resolved
/// unlike [`App::observe_keystrokes`] which fires after. This means that `cx.stop_propagation` calls
/// within interceptors will prevent action dispatch
pub fn intercept_keystrokes(
&mut self,
mut f: impl FnMut(&KeystrokeEvent, &mut Window, &mut App) + 'static,
) -> Subscription {
fn inner(
keystroke_interceptors: &SubscriberSet<(), KeystrokeObserver>,
handler: KeystrokeObserver,
) -> Subscription {
let (subscription, activate) = keystroke_interceptors.insert((), handler);
activate();
subscription
}
inner(
&mut self.keystroke_interceptors,
Box::new(move |event, window, cx| {
f(event, window, cx);
true
}),
)
}
/// Register key bindings.
pub fn bind_keys(&mut self, bindings: impl IntoIterator<Item = KeyBinding>) {
self.keymap.borrow_mut().add_bindings(bindings);
self.pending_effects.push_back(Effect::RefreshWindows);
}
/// Clear all key bindings in the app.
pub fn clear_key_bindings(&mut self) {
self.keymap.borrow_mut().clear();
self.pending_effects.push_back(Effect::RefreshWindows);
}
/// Get all key bindings in the app.
pub fn key_bindings(&self) -> Rc<RefCell<Keymap>> {
self.keymap.clone()
}
/// Register a global handler for actions invoked via the keyboard. These handlers are run at
/// the end of the bubble phase for actions, and so will only be invoked if there are no other
/// handlers or if they called `cx.propagate()`.
pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + 'static) {
self.global_action_listeners
.entry(TypeId::of::<A>())
.or_default()
.push(Rc::new(move |action, phase, cx| {
if phase == DispatchPhase::Bubble {
let action = action.downcast_ref().unwrap();
listener(action, cx)
}
}));
}
/// Event handlers propagate events by default. Call this method to stop dispatching to
/// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
/// the opposite of [`Self::propagate`]. It's also possible to cancel a call to [`Self::propagate`] by
/// calling this method before effects are flushed.
pub fn stop_propagation(&mut self) {
self.propagate_event = false;
}
/// Action handlers stop propagation by default during the bubble phase of action dispatch
/// dispatching to action handlers higher in the element tree. This is the opposite of
/// [`Self::stop_propagation`]. It's also possible to cancel a call to [`Self::stop_propagation`] by calling
/// this method before effects are flushed.
pub fn propagate(&mut self) {
self.propagate_event = true;
}
/// Build an action from some arbitrary data, typically a keymap entry.
pub fn build_action(
&self,
name: &str,
data: Option<serde_json::Value>,
) -> std::result::Result<Box<dyn Action>, ActionBuildError> {
self.actions.build_action(name, data)
}
/// Get all action names that have been registered. Note that registration only allows for
/// actions to be built dynamically, and is unrelated to binding actions in the element tree.
pub fn all_action_names(&self) -> &[&'static str] {
self.actions.all_action_names()
}
/// Returns key bindings that invoke the given action on the currently focused element, without
/// checking context. Bindings are returned in the order they were added. For display, the last
/// binding should take precedence.
pub fn all_bindings_for_input(&self, input: &[Keystroke]) -> Vec<KeyBinding> {
RefCell::borrow(&self.keymap).all_bindings_for_input(input)
}
/// Get all non-internal actions that have been registered, along with their schemas.
pub fn action_schemas(
&self,
generator: &mut schemars::SchemaGenerator,
) -> Vec<(&'static str, Option<schemars::Schema>)> {
self.actions.action_schemas(generator)
}
/// Get a map from a deprecated action name to the canonical name.
pub fn deprecated_actions_to_preferred_actions(&self) -> &HashMap<&'static str, &'static str> {
self.actions.deprecated_aliases()
}
/// Get a map from an action name to the deprecation messages.
pub fn action_deprecation_messages(&self) -> &HashMap<&'static str, &'static str> {
self.actions.deprecation_messages()
}
/// Get a map from an action name to the documentation.
pub fn action_documentation(&self) -> &HashMap<&'static str, &'static str> {
self.actions.documentation()
}
/// Register a callback to be invoked when the application is about to quit.
/// It is not possible to cancel the quit event at this point.
pub fn on_app_quit<Fut>(
&self,
mut on_quit: impl FnMut(&mut App) -> Fut + 'static,
) -> Subscription
where
Fut: 'static + Future<Output = ()>,
{
let (subscription, activate) = self.quit_observers.insert(
(),
Box::new(move |cx| {
let future = on_quit(cx);
future.boxed_local()
}),
);
activate();
subscription
}
/// Register a callback to be invoked when a window is closed
/// The window is no longer accessible at the point this callback is invoked.
pub fn on_window_closed(&self, mut on_closed: impl FnMut(&mut App) + 'static) -> Subscription {
let (subscription, activate) = self.window_closed_observers.insert((), Box::new(on_closed));
activate();
subscription
}
pub(crate) fn clear_pending_keystrokes(&mut self) {
for window in self.windows() {
window
.update(self, |_, window, _| {
window.clear_pending_keystrokes();
})
.ok();
}
}
/// Checks if the given action is bound in the current context, as defined by the app's current focus,
/// the bindings in the element tree, and any global action listeners.
pub fn is_action_available(&mut self, action: &dyn Action) -> bool {
let mut action_available = false;
if let Some(window) = self.active_window() {
if let Ok(window_action_available) =
window.update(self, |_, window, cx| window.is_action_available(action, cx))
{
action_available = window_action_available;
}
}
action_available
|| self
.global_action_listeners
.contains_key(&action.as_any().type_id())
}
/// Sets the menu bar for this application. This will replace any existing menu bar.
pub fn set_menus(&self, menus: Vec<Menu>) {
self.platform.set_menus(menus, &self.keymap.borrow());
}
/// Gets the menu bar for this application.
pub fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
self.platform.get_menus()
}
/// Sets the right click menu for the app icon in the dock
pub fn set_dock_menu(&self, menus: Vec<MenuItem>) {
self.platform.set_dock_menu(menus, &self.keymap.borrow())
}
/// Performs the action associated with the given dock menu item, only used on Windows for now.
pub fn perform_dock_menu_action(&self, action: usize) {
self.platform.perform_dock_menu_action(action);
}
/// Adds given path to the bottom of the list of recent paths for the application.
/// The list is usually shown on the application icon's context menu in the dock,
/// and allows to open the recent files via that context menu.
/// If the path is already in the list, it will be moved to the bottom of the list.
pub fn add_recent_document(&self, path: &Path) {
self.platform.add_recent_document(path);
}
/// Updates the jump list with the updated list of recent paths for the application, only used on Windows for now.
/// Note that this also sets the dock menu on Windows.
pub fn update_jump_list(
&self,
menus: Vec<MenuItem>,
entries: Vec<SmallVec<[PathBuf; 2]>>,
) -> Vec<SmallVec<[PathBuf; 2]>> {
self.platform.update_jump_list(menus, entries)
}
/// Dispatch an action to the currently active window or global action handler
/// See [`crate::Action`] for more information on how actions work
pub fn dispatch_action(&mut self, action: &dyn Action) {
if let Some(active_window) = self.active_window() {
active_window
.update(self, |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx)
})
.log_err();
} else {
self.dispatch_global_action(action);
}
}
fn dispatch_global_action(&mut self, action: &dyn Action) {
self.propagate_event = true;
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
{
for listener in &global_listeners {
listener(action.as_any(), DispatchPhase::Capture, self);
if !self.propagate_event {
break;
}
}
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
}
if self.propagate_event {
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
{
for listener in global_listeners.iter().rev() {
listener(action.as_any(), DispatchPhase::Bubble, self);
if !self.propagate_event {
break;
}
}
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
}
}
}
/// Is there currently something being dragged?
pub fn has_active_drag(&self) -> bool {
self.active_drag.is_some()
}
/// Gets the cursor style of the currently active drag operation.
pub fn active_drag_cursor_style(&self) -> Option<CursorStyle> {
self.active_drag.as_ref().and_then(|drag| drag.cursor_style)
}
/// Stops active drag and clears any related effects.
pub fn stop_active_drag(&mut self, window: &mut Window) -> bool {
if self.active_drag.is_some() {
self.active_drag = None;
window.refresh();
true
} else {
false
}
}
/// Sets the cursor style for the currently active drag operation.
pub fn set_active_drag_cursor_style(
&mut self,
cursor_style: CursorStyle,
window: &mut Window,
) -> bool {
if let Some(ref mut drag) = self.active_drag {
drag.cursor_style = Some(cursor_style);
window.refresh();
true
} else {
false
}
}
/// Set the prompt renderer for GPUI. This will replace the default or platform specific
/// prompts with this custom implementation.
pub fn set_prompt_builder(
&mut self,
renderer: impl Fn(
PromptLevel,
&str,
Option<&str>,
&[PromptButton],
PromptHandle,
&mut Window,
&mut App,
) -> RenderablePromptHandle
+ 'static,
) {
self.prompt_builder = Some(PromptBuilder::Custom(Box::new(renderer)));
}
/// Reset the prompt builder to the default implementation.
pub fn reset_prompt_builder(&mut self) {
self.prompt_builder = Some(PromptBuilder::Default);
}
/// Remove an asset from GPUI's cache
pub fn remove_asset<A: Asset>(&mut self, source: &A::Source) {
let asset_id = (TypeId::of::<A>(), hash(source));
self.loading_assets.remove(&asset_id);
}
/// Asynchronously load an asset, if the asset hasn't finished loading this will return None.
///
/// Note that the multiple calls to this method will only result in one `Asset::load` call at a
/// time, and the results of this call will be cached
pub fn fetch_asset<A: Asset>(&mut self, source: &A::Source) -> (Shared<Task<A::Output>>, bool) {
let asset_id = (TypeId::of::<A>(), hash(source));
let mut is_first = false;
let task = self
.loading_assets
.remove(&asset_id)
.map(|boxed_task| *boxed_task.downcast::<Shared<Task<A::Output>>>().unwrap())
.unwrap_or_else(|| {
is_first = true;
let future = A::load(source.clone(), self);
let task = self.background_executor().spawn(future).shared();
task
});
self.loading_assets.insert(asset_id, Box::new(task.clone()));
(task, is_first)
}
/// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus
/// for elements rendered within this window.
#[track_caller]
pub fn focus_handle(&self) -> FocusHandle {
FocusHandle::new(&self.focus_handles)
}
/// Tell GPUI that an entity has changed and observers of it should be notified.
pub fn notify(&mut self, entity_id: EntityId) {
let window_invalidators = mem::take(
self.window_invalidators_by_entity
.entry(entity_id)
.or_default(),
);
if window_invalidators.is_empty() {
if self.pending_notifications.insert(entity_id) {
self.pending_effects
.push_back(Effect::Notify { emitter: entity_id });
}
} else {
for invalidator in window_invalidators.values() {
invalidator.invalidate_view(entity_id, self);
}
}
self.window_invalidators_by_entity
.insert(entity_id, window_invalidators);
}
/// Returns the name for this [`App`].
#[cfg(any(test, feature = "test-support", debug_assertions))]
pub fn get_name(&self) -> Option<&'static str> {
self.name
}
/// Returns `true` if the platform file picker supports selecting a mix of files and directories.
pub fn can_select_mixed_files_and_dirs(&self) -> bool {
self.platform.can_select_mixed_files_and_dirs()
}
/// Removes an image from the sprite atlas on all windows.
///
/// If the current window is being updated, it will be removed from `App.windows`, you can use `current_window` to specify the current window.
/// This is a no-op if the image is not in the sprite atlas.
pub fn drop_image(&mut self, image: Arc<RenderImage>, current_window: Option<&mut Window>) {
// remove the texture from all other windows
for window in self.windows.values_mut().flatten() {
_ = window.drop_image(image.clone());
}
// remove the texture from the current window
if let Some(window) = current_window {
_ = window.drop_image(image);
}
}
/// Sets the renderer for the inspector.
#[cfg(any(feature = "inspector", debug_assertions))]
pub fn set_inspector_renderer(&mut self, f: crate::InspectorRenderer) {
self.inspector_renderer = Some(f);
}
/// Registers a renderer specific to an inspector state.
#[cfg(any(feature = "inspector", debug_assertions))]
pub fn register_inspector_element<T: 'static, R: crate::IntoElement>(
&mut self,
f: impl 'static + Fn(crate::InspectorElementId, &T, &mut Window, &mut App) -> R,
) {
self.inspector_element_registry.register(f);
}
/// Initializes gpui's default colors for the application.
///
/// These colors can be accessed through `cx.default_colors()`.
pub fn init_colors(&mut self) {
self.set_global(GlobalColors(Arc::new(Colors::default())));
}
}
impl AppContext for App {
type Result<T> = T;
/// Builds an entity that is owned by the application.
///
/// The given function will be invoked with a [`Context`] and must return an object representing the entity. An
/// [`Entity`] handle will be returned, which can be used to access the entity in a context.
fn new<T: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Entity<T> {
self.update(|cx| {
let slot = cx.entities.reserve();
let handle = slot.clone();
let entity = build_entity(&mut Context::new_context(cx, slot.downgrade()));
cx.push_effect(Effect::EntityCreated {
entity: handle.clone().into_any(),
tid: TypeId::of::<T>(),
window: cx.window_update_stack.last().cloned(),
});
cx.entities.insert(slot, entity);
handle
})
}
fn reserve_entity<T: 'static>(&mut self) -> Self::Result<Reservation<T>> {
Reservation(self.entities.reserve())
}
fn insert_entity<T: 'static>(
&mut self,
reservation: Reservation<T>,
build_entity: impl FnOnce(&mut Context<T>) -> T,
) -> Self::Result<Entity<T>> {
self.update(|cx| {
let slot = reservation.0;
let entity = build_entity(&mut Context::new_context(cx, slot.downgrade()));
cx.entities.insert(slot, entity)
})
}
/// Updates the entity referenced by the given handle. The function is passed a mutable reference to the
/// entity along with a `Context` for the entity.
fn update_entity<T: 'static, R>(
&mut self,
handle: &Entity<T>,
update: impl FnOnce(&mut T, &mut Context<T>) -> R,
) -> R {
self.update(|cx| {
let mut entity = cx.entities.lease(handle);
let result = update(
&mut entity,
&mut Context::new_context(cx, handle.downgrade()),
);
cx.entities.end_lease(entity);
result
})
}
fn as_mut<'a, T>(&'a mut self, handle: &Entity<T>) -> GpuiBorrow<'a, T>
where
T: 'static,
{
GpuiBorrow::new(handle.clone(), self)
}
fn read_entity<T, R>(
&self,
handle: &Entity<T>,
read: impl FnOnce(&T, &App) -> R,
) -> Self::Result<R>
where
T: 'static,
{
let entity = self.entities.read(handle);
read(entity, self)
}
fn update_window<T, F>(&mut self, handle: AnyWindowHandle, update: F) -> Result<T>
where
F: FnOnce(AnyView, &mut Window, &mut App) -> T,
{
self.update_window_id(handle.id, update)
}
fn read_window<T, R>(
&self,
window: &WindowHandle<T>,
read: impl FnOnce(Entity<T>, &App) -> R,
) -> Result<R>
where
T: 'static,
{
let window = self
.windows
.get(window.id)
.context("window not found")?
.as_ref()
.expect("attempted to read a window that is already on the stack");
let root_view = window.root.clone().unwrap();
let view = root_view
.downcast::<T>()
.map_err(|_| anyhow!("root view's type has changed"))?;
Ok(read(view, self))
}
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
self.background_executor.spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
where
G: Global,
{
let mut g = self.global::<G>();
callback(&g, self)
}
}
/// These effects are processed at the end of each application update cycle.
pub(crate) enum Effect {
Notify {
emitter: EntityId,
},
Emit {
emitter: EntityId,
event_type: TypeId,
event: Box<dyn Any>,
},
RefreshWindows,
NotifyGlobalObservers {
global_type: TypeId,
},
Defer {
callback: Box<dyn FnOnce(&mut App) + 'static>,
},
EntityCreated {
entity: AnyEntity,
tid: TypeId,
window: Option<WindowId>,
},
}
impl std::fmt::Debug for Effect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Effect::Notify { emitter } => write!(f, "Notify({})", emitter),
Effect::Emit { emitter, .. } => write!(f, "Emit({:?})", emitter),
Effect::RefreshWindows => write!(f, "RefreshWindows"),
Effect::NotifyGlobalObservers { global_type } => {
write!(f, "NotifyGlobalObservers({:?})", global_type)
}
Effect::Defer { .. } => write!(f, "Defer(..)"),
Effect::EntityCreated { entity, .. } => write!(f, "EntityCreated({:?})", entity),
}
}
}
/// Wraps a global variable value during `update_global` while the value has been moved to the stack.
pub(crate) struct GlobalLease<G: Global> {
global: Box<dyn Any>,
global_type: PhantomData<G>,
}
impl<G: Global> GlobalLease<G> {
fn new(global: Box<dyn Any>) -> Self {
GlobalLease {
global,
global_type: PhantomData,
}
}
}
impl<G: Global> Deref for GlobalLease<G> {
type Target = G;
fn deref(&self) -> &Self::Target {
self.global.downcast_ref().unwrap()
}
}
impl<G: Global> DerefMut for GlobalLease<G> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.global.downcast_mut().unwrap()
}
}
/// Contains state associated with an active drag operation, started by dragging an element
/// within the window or by dragging into the app from the underlying platform.
pub struct AnyDrag {
/// The view used to render this drag
pub view: AnyView,
/// The value of the dragged item, to be dropped
pub value: Arc<dyn Any>,
/// This is used to render the dragged item in the same place
/// on the original element that the drag was initiated
pub cursor_offset: Point<Pixels>,
/// The cursor style to use while dragging
pub cursor_style: Option<CursorStyle>,
}
/// Contains state associated with a tooltip. You'll only need this struct if you're implementing
/// tooltip behavior on a custom element. Otherwise, use [Div::tooltip].
#[derive(Clone)]
pub struct AnyTooltip {
/// The view used to display the tooltip
pub view: AnyView,
/// The absolute position of the mouse when the tooltip was deployed.
pub mouse_position: Point<Pixels>,
/// Given the bounds of the tooltip, checks whether the tooltip should still be visible and
/// updates its state accordingly. This is needed atop the hovered element's mouse move handler
/// to handle the case where the element is not painted (e.g. via use of `visible_on_hover`).
pub check_visible_and_update: Rc<dyn Fn(Bounds<Pixels>, &mut Window, &mut App) -> bool>,
}
/// A keystroke event, and potentially the associated action
#[derive(Debug)]
pub struct KeystrokeEvent {
/// The keystroke that occurred
pub keystroke: Keystroke,
/// The action that was resolved for the keystroke, if any
pub action: Option<Box<dyn Action>>,
/// The context stack at the time
pub context_stack: Vec<KeyContext>,
}
struct NullHttpClient;
impl HttpClient for NullHttpClient {
fn send(
&self,
_req: http_client::Request<http_client::AsyncBody>,
) -> futures::future::BoxFuture<
'static,
anyhow::Result<http_client::Response<http_client::AsyncBody>>,
> {
async move {
anyhow::bail!("No HttpClient available");
}
.boxed()
}
fn proxy(&self) -> Option<&Url> {
None
}
fn type_name(&self) -> &'static str {
type_name::<Self>()
}
}
/// A mutable reference to an entity owned by GPUI
pub struct GpuiBorrow<'a, T> {
inner: Option<Lease<T>>,
app: &'a mut App,
}
impl<'a, T: 'static> GpuiBorrow<'a, T> {
fn new(inner: Entity<T>, app: &'a mut App) -> Self {
app.start_update();
let lease = app.entities.lease(&inner);
Self {
inner: Some(lease),
app,
}
}
}
impl<'a, T: 'static> std::borrow::Borrow<T> for GpuiBorrow<'a, T> {
fn borrow(&self) -> &T {
self.inner.as_ref().unwrap().borrow()
}
}
impl<'a, T: 'static> std::borrow::BorrowMut<T> for GpuiBorrow<'a, T> {
fn borrow_mut(&mut self) -> &mut T {
self.inner.as_mut().unwrap().borrow_mut()
}
}
impl<'a, T> Drop for GpuiBorrow<'a, T> {
fn drop(&mut self) {
let lease = self.inner.take().unwrap();
self.app.notify(lease.id);
self.app.entities.end_lease(lease);
self.app.finish_update();
}
}
#[cfg(test)]
mod test {
use std::{cell::RefCell, rc::Rc};
use crate::{AppContext, TestAppContext};
#[test]
fn test_gpui_borrow() {
let cx = TestAppContext::single();
let observation_count = Rc::new(RefCell::new(0));
let state = cx.update(|cx| {
let state = cx.new(|_| false);
cx.observe(&state, {
let observation_count = observation_count.clone();
move |_, _| {
let mut count = observation_count.borrow_mut();
*count += 1;
}
})
.detach();
state
});
cx.update(|cx| {
// Calling this like this so that we don't clobber the borrow_mut above
*std::borrow::BorrowMut::borrow_mut(&mut state.as_mut(cx)) = true;
});
cx.update(|cx| {
state.write(cx, false);
});
assert_eq!(*observation_count.borrow(), 2);
}
}