diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index 17f6e47ddfec85de1c82b1fc0b8cd6ffc285aa32..0b213b20f769975e22761f8294251051e39e2753 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -19,7 +19,10 @@ use std::{ #[cfg(any(test, feature = "test-support"))] use collections::HashMap; -slotmap::new_key_type! { pub struct EntityId; } +slotmap::new_key_type! { + /// A unique identifier for a model or view across the application. + pub struct EntityId; +} impl EntityId { pub fn as_u64(self) -> u64 { diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index a530aaf69b94ce65d35feee015738f306b3a79c3..587d3f3fec4b5f6d278b4bd1e7272e5b6bf3d356 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -9,6 +9,8 @@ use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}; +/// A TestAppContext is provided to tests created with `#[gpui::test]`, it provides +/// an implementation of `Context` with additional methods that are useful in tests. #[derive(Clone)] pub struct TestAppContext { pub app: Rc, @@ -95,38 +97,47 @@ impl TestAppContext { } } + /// returns a new `TestAppContext` re-using the same executors to interleave tasks. pub fn new_app(&self) -> TestAppContext { Self::new(self.dispatcher.clone()) } + /// Simulates quitting the app. pub fn quit(&self) { self.app.borrow_mut().shutdown(); } + /// Schedules all windows to be redrawn on the next effect cycle. pub fn refresh(&mut self) -> Result<()> { let mut app = self.app.borrow_mut(); app.refresh(); Ok(()) } + /// Returns an executor (for running tasks in the background) pub fn executor(&self) -> BackgroundExecutor { self.background_executor.clone() } + /// Returns an executor (for running tasks on the main thread) pub fn foreground_executor(&self) -> &ForegroundExecutor { &self.foreground_executor } + /// Gives you an `&mut AppContext` for the duration of the closure pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { let mut cx = self.app.borrow_mut(); cx.update(f) } + /// Gives you an `&AppContext` for the duration of the closure pub fn read(&self, f: impl FnOnce(&AppContext) -> R) -> R { let cx = self.app.borrow(); f(&*cx) } + // Adds a new window. The Window will always be backed by a `TestWindow` which + // can be retrieved with `self.test_window(handle)` pub fn add_window(&mut self, build_window: F) -> WindowHandle where F: FnOnce(&mut ViewContext) -> V, @@ -136,12 +147,16 @@ impl TestAppContext { cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window)) } + // Adds a new window with no content. pub fn add_empty_window(&mut self) -> AnyWindowHandle { let mut cx = self.app.borrow_mut(); cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {})) .any_handle } + /// Adds a new window, and returns its root view and a `VisualTestContext` which can be used + /// as a `WindowContext` for the rest of the test. Typically you would shadow this context with + /// the returned one. `let (view, cx) = cx.add_window_view(...);` pub fn add_window_view(&mut self, build_window: F) -> (View, &mut VisualTestContext) where F: FnOnce(&mut ViewContext) -> V, @@ -156,18 +171,23 @@ impl TestAppContext { (view, Box::leak(cx)) } + /// returns the TextSystem pub fn text_system(&self) -> &Arc { &self.text_system } + /// Simulates writing to the platform clipboard pub fn write_to_clipboard(&self, item: ClipboardItem) { self.test_platform.write_to_clipboard(item) } + /// Simulates reading from the platform clipboard. + /// This will return the most recent value from `write_to_clipboard`. pub fn read_from_clipboard(&self) -> Option { self.test_platform.read_from_clipboard() } + /// Simulates choosing a File in the platform's "Open" dialog. pub fn simulate_new_path_selection( &self, select_path: impl FnOnce(&std::path::Path) -> Option, @@ -175,22 +195,27 @@ impl TestAppContext { self.test_platform.simulate_new_path_selection(select_path); } + /// Simulates clicking a button in an platform-level alert dialog. pub fn simulate_prompt_answer(&self, button_ix: usize) { self.test_platform.simulate_prompt_answer(button_ix); } + /// Returns true if there's an alert dialog open. pub fn has_pending_prompt(&self) -> bool { self.test_platform.has_pending_prompt() } + /// Simulates the user resizing the window to the new size. pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size) { self.test_window(window_handle).simulate_resize(size); } + /// Returns all windows open in the test. pub fn windows(&self) -> Vec { self.app.borrow().windows().clone() } + /// Run the given task on the main thread. pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 589493b4bb431586ed004e88e59d0d8f329334fc..4be1ffbf0fdf970353d6f2402eda02ab2b0faac8 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -32,6 +32,10 @@ pub struct ForegroundExecutor { not_send: PhantomData>, } +/// Task is a primitive that allows work to happen in the background. +/// It implements Future so you can `.await` on it. +/// If you drop a task it will be cancelled immediately. Calling `.detach()` allows +/// the task to continue running in the background, but with no way to return a value. #[must_use] #[derive(Debug)] pub enum Task { @@ -40,10 +44,12 @@ pub enum Task { } impl Task { + /// Create a new task that will resolve with the value pub fn ready(val: T) -> Self { Task::Ready(Some(val)) } + /// Detaching a task runs it to completion in the background pub fn detach(self) { match self { Task::Ready(_) => {} @@ -57,6 +63,8 @@ where T: 'static, E: 'static + Debug, { + /// Run the task to completion in the background and log any + /// errors that occur. #[track_caller] pub fn detach_and_log_err(self, cx: &mut AppContext) { let location = core::panic::Location::caller(); @@ -97,6 +105,10 @@ type AnyLocalFuture = Pin>>; type AnyFuture = Pin>>; +/// BackgroundExecutor lets you run things on background threads. +/// In production this is a thread pool with no ordering guarantees. +/// In tests this is simalated by running tasks one by one in a deterministic +/// (but arbitrary) order controlled by the `SEED` environment variable. impl BackgroundExecutor { pub fn new(dispatcher: Arc) -> Self { Self { dispatcher } @@ -135,6 +147,7 @@ impl BackgroundExecutor { Task::Spawned(task) } + /// Used by the test harness to run an async test in a syncronous fashion. #[cfg(any(test, feature = "test-support"))] #[track_caller] pub fn block_test(&self, future: impl Future) -> R { @@ -145,6 +158,8 @@ impl BackgroundExecutor { } } + /// Block the current thread until the given future resolves. + /// Consider using `block_with_timeout` instead. pub fn block(&self, future: impl Future) -> R { if let Ok(value) = self.block_internal(true, future, usize::MAX) { value @@ -206,6 +221,8 @@ impl BackgroundExecutor { } } + /// Block the current thread until the given future resolves + /// or `duration` has elapsed. pub fn block_with_timeout( &self, duration: Duration, @@ -238,6 +255,8 @@ impl BackgroundExecutor { } } + /// Scoped lets you start a number of tasks and waits + /// for all of them to complete before returning. pub async fn scoped<'scope, F>(&self, scheduler: F) where F: FnOnce(&mut Scope<'scope>), @@ -253,6 +272,9 @@ impl BackgroundExecutor { } } + /// Returns a task that will complete after the given duration. + /// Depending on other concurrent tasks the elapsed duration may be longer + /// than reqested. pub fn timer(&self, duration: Duration) -> Task<()> { let (runnable, task) = async_task::spawn(async move {}, { let dispatcher = self.dispatcher.clone(); @@ -262,65 +284,81 @@ impl BackgroundExecutor { Task::Spawned(task) } + /// in tests, start_waiting lets you indicate which task is waiting (for debugging only) #[cfg(any(test, feature = "test-support"))] pub fn start_waiting(&self) { self.dispatcher.as_test().unwrap().start_waiting(); } + /// in tests, removes the debugging data added by start_waiting #[cfg(any(test, feature = "test-support"))] pub fn finish_waiting(&self) { self.dispatcher.as_test().unwrap().finish_waiting(); } + /// in tests, run an arbitrary number of tasks (determined by the SEED environment variable) #[cfg(any(test, feature = "test-support"))] pub fn simulate_random_delay(&self) -> impl Future { self.dispatcher.as_test().unwrap().simulate_random_delay() } + /// in tests, indicate that a given task from `spawn_labeled` should run after everything else #[cfg(any(test, feature = "test-support"))] pub fn deprioritize(&self, task_label: TaskLabel) { self.dispatcher.as_test().unwrap().deprioritize(task_label) } + /// in tests, move time forward. This does not run any tasks, but does make `timer`s ready. #[cfg(any(test, feature = "test-support"))] pub fn advance_clock(&self, duration: Duration) { self.dispatcher.as_test().unwrap().advance_clock(duration) } + /// in tests, run one task. #[cfg(any(test, feature = "test-support"))] pub fn tick(&self) -> bool { self.dispatcher.as_test().unwrap().tick(false) } + /// in tests, run all tasks that are ready to run. If after doing so + /// the test still has outstanding tasks, this will panic. (See also `allow_parking`) #[cfg(any(test, feature = "test-support"))] pub fn run_until_parked(&self) { self.dispatcher.as_test().unwrap().run_until_parked() } + /// in tests, prevents `run_until_parked` from panicking if there are outstanding tasks. + /// This is useful when you are integrating other (non-GPUI) futures, like disk access, that + /// do take real async time to run. #[cfg(any(test, feature = "test-support"))] pub fn allow_parking(&self) { self.dispatcher.as_test().unwrap().allow_parking(); } + /// in tests, returns the rng used by the dispatcher and seeded by the `SEED` environment variable #[cfg(any(test, feature = "test-support"))] pub fn rng(&self) -> StdRng { self.dispatcher.as_test().unwrap().rng() } + /// How many CPUs are available to the dispatcher pub fn num_cpus(&self) -> usize { num_cpus::get() } + /// Whether we're on the main thread. pub fn is_main_thread(&self) -> bool { self.dispatcher.is_main_thread() } #[cfg(any(test, feature = "test-support"))] + /// in tests, control the number of ticks that `block_with_timeout` will run before timing out. pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive) { self.dispatcher.as_test().unwrap().set_block_on_ticks(range); } } +/// ForegroundExecutor runs things on the main thread. impl ForegroundExecutor { pub fn new(dispatcher: Arc) -> Self { Self { @@ -329,8 +367,7 @@ impl ForegroundExecutor { } } - /// Enqueues the given closure to be run on any thread. The closure returns - /// a future which will be run to completion on any available thread. + /// Enqueues the given Task to run on the main thread at some point in the future. pub fn spawn(&self, future: impl Future + 'static) -> Task where R: 'static, @@ -350,6 +387,7 @@ impl ForegroundExecutor { } } +/// Scope manages a set of tasks that are enqueued and waited on together. See `BackgroundExecutor#scoped` pub struct Scope<'a> { executor: BackgroundExecutor, futures: Vec + Send + 'static>>>, diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index 5a21576fb26178ff67c750ca7ee63652690f1700..1771f29c67b13639fbb7b87029840c435b6a681d 100644 --- a/crates/gpui/src/test.rs +++ b/crates/gpui/src/test.rs @@ -1,3 +1,30 @@ +//! Test support for GPUI. +//! +//! GPUI provides first-class support for testing, which includes a macro to run test that rely on having a context, +//! and a test implementation of the `ForegroundExecutor` and `BackgroundExecutor` which ensure that your tests run +//! deterministically even in the face of arbitrary parallelism. +//! +//! The output of the `gpui::test` macro is understood by other rust test runners, so you can use it with `cargo test` +//! or `cargo-nextest`, or another runner of your choice. +//! +//! To make it possible to test collaborative user interfaces (like Zed) you can ask for as many different contexts +//! as you need. +//! +//! ## Example +//! +//! ``` +//! use gpui; +//! +//! #[gpui::test] +//! async fn test_example(cx: &TestAppContext) { +//! assert!(true) +//! } +//! +//! #[gpui::test] +//! async fn test_collaboration_example(cx_a: &TestAppContext, cx_b: &TestAppContext) { +//! assert!(true) +//! } +//! ``` use crate::{Entity, Subscription, TestAppContext, TestDispatcher}; use futures::StreamExt as _; use rand::prelude::*; @@ -68,6 +95,7 @@ impl futures::Stream for Observation { } } +/// observe returns a stream of the change events from the given `View` or `Model` pub fn observe(entity: &impl Entity, cx: &mut TestAppContext) -> Observation<()> { let (tx, rx) = smol::channel::unbounded(); let _subscription = cx.update(|cx| { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7e4c5f93f95e6ea770d404a63a3e6795d9a4be7d..dd567a83f6baee9ae65b57def9d7576b84bad89f 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1,3 +1,5 @@ +#![deny(missing_docs)] + use crate::{ px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, ArenaBox, ArenaRef, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, @@ -85,10 +87,12 @@ pub enum DispatchPhase { } impl DispatchPhase { + /// Returns true if this represents the "bubble" phase. pub fn bubble(self) -> bool { self == DispatchPhase::Bubble } + /// Returns true if this represents the "capture" phase. pub fn capture(self) -> bool { self == DispatchPhase::Capture } @@ -103,7 +107,10 @@ struct FocusEvent { current_focus_path: SmallVec<[FocusId; 8]>, } -slotmap::new_key_type! { pub struct FocusId; } +slotmap::new_key_type! { + /// A globally unique identifier for a focusable element. + pub struct FocusId; +} thread_local! { pub(crate) static ELEMENT_ARENA: RefCell = RefCell::new(Arena::new(4 * 1024 * 1024)); @@ -240,6 +247,7 @@ pub trait ManagedView: FocusableView + EventEmitter {} impl> ManagedView for M {} +/// Emitted by implementers of [ManagedView] to indicate the view should be dismissed, such as when a view is presented as a modal. pub struct DismissEvent; // Holds the state for a specific window. @@ -525,11 +533,13 @@ impl<'a> WindowContext<'a> { self.notify(); } + /// Blur the window and don't allow anything in it to be focused again. pub fn disable_focus(&mut self) { self.blur(); self.window.focus_enabled = false; } + /// Dispatch the given action on the currently focused element. pub fn dispatch_action(&mut self, action: Box) { let focus_handle = self.focused(); @@ -591,6 +601,9 @@ impl<'a> WindowContext<'a> { }); } + /// Subscribe to events emitted by a model or view. + /// The entity to which you're subscribing must implement the [EventEmitter] trait. + /// The callback will be invoked a handle to the emitting entity (either a [View] or [Model]), the event, and a window context for the current window. pub fn subscribe( &mut self, entity: &E, @@ -754,6 +767,9 @@ impl<'a> WindowContext<'a> { .request_measured_layout(style, rem_size, measure) } + /// Compute the layout for the given id within the given available space. + /// This method is called for its side effect, typically by the framework prior to painting. + /// After calling it, you can request the bounds of the given layout node id or any descendant. pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size) { let mut layout_engine = self.window.layout_engine.take().unwrap(); layout_engine.compute_layout(layout_id, available_space, self); @@ -788,30 +804,37 @@ impl<'a> WindowContext<'a> { .retain(&(), |callback| callback(self)); } + /// Returns the bounds of the current window in the global coordinate space, which could span across multiple displays. pub fn window_bounds(&self) -> WindowBounds { self.window.bounds } + /// Returns the size of the drawable area within the window. pub fn viewport_size(&self) -> Size { self.window.viewport_size } + /// Returns whether this window is focused by the operating system (receiving key events). pub fn is_window_active(&self) -> bool { self.window.active } + /// Toggle zoom on the window. pub fn zoom_window(&self) { self.window.platform_window.zoom(); } + /// Update the window's title at the platform level. pub fn set_window_title(&mut self, title: &str) { self.window.platform_window.set_title(title); } + /// Mark the window as dirty at the platform level. pub fn set_window_edited(&mut self, edited: bool) { self.window.platform_window.set_edited(edited); } + /// Determine the display on which the window is visible. pub fn display(&self) -> Option> { self.platform .displays() @@ -819,6 +842,7 @@ impl<'a> WindowContext<'a> { .find(|display| display.id() == self.window.display_id) } + /// Show the platform character palette. pub fn show_character_palette(&self) { self.window.platform_window.show_character_palette(); } @@ -936,6 +960,7 @@ impl<'a> WindowContext<'a> { .on_action(action_type, ArenaRef::from(listener)); } + /// Determine whether the given action is available along the dispatch path to the currently focused element. pub fn is_action_available(&self, action: &dyn Action) -> bool { let target = self .focused() @@ -962,6 +987,7 @@ impl<'a> WindowContext<'a> { self.window.modifiers } + /// Update the cursor style at the platform level. pub fn set_cursor_style(&mut self, style: CursorStyle) { self.window.requested_cursor_style = Some(style) } @@ -991,7 +1017,7 @@ impl<'a> WindowContext<'a> { true } - pub fn was_top_layer_under_active_drag( + pub(crate) fn was_top_layer_under_active_drag( &self, point: &Point, level: &StackingOrder, @@ -1649,6 +1675,7 @@ impl<'a> WindowContext<'a> { self.dispatch_keystroke_observers(event, None); } + /// Determine whether a potential multi-stroke key binding is in progress on this window. pub fn has_pending_keystrokes(&self) -> bool { self.window .rendered_frame @@ -1715,27 +1742,34 @@ impl<'a> WindowContext<'a> { subscription } + /// Focus the current window and bring it to the foreground at the platform level. pub fn activate_window(&self) { self.window.platform_window.activate(); } + /// Minimize the current window at the platform level. pub fn minimize_window(&self) { self.window.platform_window.minimize(); } + /// Toggle full screen status on the current window at the platform level. pub fn toggle_full_screen(&self) { self.window.platform_window.toggle_full_screen(); } + /// Present a platform dialog. + /// The provided message will be presented, along with buttons for each answer. + /// When a button is clicked, the returned Receiver will receive the index of the clicked button. pub fn prompt( &self, level: PromptLevel, - msg: &str, + message: &str, answers: &[&str], ) -> oneshot::Receiver { - self.window.platform_window.prompt(level, msg, answers) + self.window.platform_window.prompt(level, message, answers) } + /// Returns all available actions for the focused element. pub fn available_actions(&self) -> Vec> { let node_id = self .window @@ -1754,6 +1788,7 @@ impl<'a> WindowContext<'a> { .available_actions(node_id) } + /// Returns any key bindings that invoke the given action. pub fn bindings_for_action(&self, action: &dyn Action) -> Vec { self.window .rendered_frame @@ -2279,6 +2314,10 @@ impl BorrowMut for WindowContext<'_> { impl BorrowWindow for T where T: BorrowMut + BorrowMut {} +/// Provides access to application state that is specialized for a particular [View]. +/// Allows you to interact with focus, emit events, etc. +/// ViewContext also derefs to [WindowContext], giving you access to all of its methods as well. +/// When you call [View::::update], you're passed a `&mut V` and an `&mut ViewContext`. pub struct ViewContext<'a, V> { window_cx: WindowContext<'a>, view: &'a View, @@ -2316,14 +2355,17 @@ impl<'a, V: 'static> ViewContext<'a, V> { } } + /// Get the entity_id of this view. pub fn entity_id(&self) -> EntityId { self.view.entity_id() } + /// Get the view pointer underlying this context. pub fn view(&self) -> &View { self.view } + /// Get the model underlying this view. pub fn model(&self) -> &Model { &self.view.model } @@ -2333,6 +2375,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { &mut self.window_cx } + /// Set a given callback to be run on the next frame. pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext) + 'static) where V: 'static, @@ -2350,6 +2393,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Observe another model or view for changes to it's state, as tracked by the [AppContext::notify] pub fn observe( &mut self, entity: &E, @@ -2383,6 +2427,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Subscribe to events emitted by another model or view. + /// The entity to which you're subscribing must implement the [EventEmitter] trait. + /// The callback will be invoked with a reference to the current view, a handle to the emitting entity (either a [View] or [Model]), the event, and a view context for the current view. pub fn subscribe( &mut self, entity: &E, @@ -2440,6 +2487,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Register a callback to be invoked when the given Model or View is released. pub fn observe_release( &mut self, entity: &E, @@ -2466,6 +2514,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Indicate that this view has changed, which will invoke any observers and also mark the window as dirty. + /// If this view or any of its ancestors are *cached*, notifying it will cause it or its ancestors to be redrawn. pub fn notify(&mut self) { if !self.window.drawing { self.window_cx.notify(); @@ -2475,6 +2525,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } } + /// Register a callback to be invoked when the window is resized. pub fn observe_window_bounds( &mut self, mut callback: impl FnMut(&mut V, &mut ViewContext) + 'static, @@ -2488,6 +2539,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Register a callback to be invoked when the window is activated or deactivated. pub fn observe_window_activation( &mut self, mut callback: impl FnMut(&mut V, &mut ViewContext) + 'static, @@ -2620,6 +2672,10 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Schedule a future to be run asynchronously. + /// The given callback is invoked with a [WeakView] to avoid leaking the view for a long-running process. + /// It's also given an `AsyncWindowContext`, which can be used to access the state of the view across await points. + /// The returned future will be polled on the main thread. pub fn spawn( &mut self, f: impl FnOnce(WeakView, AsyncWindowContext) -> Fut, @@ -2632,6 +2688,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.window_cx.spawn(|cx| f(view, cx)) } + /// Update the global state of the given type. pub fn update_global(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R where G: 'static, @@ -2642,6 +2699,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { result } + /// Register a callback to be invoked when the given global state changes. pub fn observe_global( &mut self, mut f: impl FnMut(&mut V, &mut ViewContext<'_, V>) + 'static, @@ -2660,6 +2718,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Add a listener for any mouse event that occurs in the window. + /// This is a fairly low level method. + /// Typically, you'll want to use methods on UI elements, which perform bounds checking etc. pub fn on_mouse_event( &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, @@ -2672,6 +2733,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Register a callback to be invoked when the given Key Event is dispatched to the window. pub fn on_key_event( &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, @@ -2684,6 +2746,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Register a callback to be invoked when the given Action type is dispatched to the window. pub fn on_action( &mut self, action_type: TypeId, @@ -2698,6 +2761,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Emit an event to be handled any other views that have subscribed via [ViewContext::subscribe]. pub fn emit(&mut self, event: Evt) where Evt: 'static, @@ -2711,6 +2775,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Move focus to the current view, assuming it implements [FocusableView]. pub fn focus_self(&mut self) where V: FocusableView, @@ -2718,6 +2783,11 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.defer(|view, cx| view.focus_handle(cx).focus(cx)) } + /// Convenience method for accessing view state in an event callback. + /// + /// Many GPUI callbacks take the form of `Fn(&E, &mut WindowContext)`, + /// but it's often useful to be able to access view state in these + /// callbacks. This method provides a convenient way to do so. pub fn listener( &self, f: impl Fn(&mut V, &E, &mut ViewContext) + 'static, @@ -2827,14 +2897,20 @@ impl<'a, V> std::ops::DerefMut for ViewContext<'a, V> { } // #[derive(Clone, Copy, Eq, PartialEq, Hash)] -slotmap::new_key_type! { pub struct WindowId; } +slotmap::new_key_type! { + /// A unique identifier for a window. + pub struct WindowId; +} impl WindowId { + /// Converts this window ID to a `u64`. pub fn as_u64(&self) -> u64 { self.0.as_ffi() } } +/// A handle to a window with a specific root view type. +/// Note that this does not keep the window alive on its own. #[derive(Deref, DerefMut)] pub struct WindowHandle { #[deref] @@ -2844,6 +2920,8 @@ pub struct WindowHandle { } impl WindowHandle { + /// Create a new handle from a window ID. + /// This does not check if the root type of the window is `V`. pub fn new(id: WindowId) -> Self { WindowHandle { any_handle: AnyWindowHandle { @@ -2854,6 +2932,9 @@ impl WindowHandle { } } + /// Get the root view out of this window. + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn root(&self, cx: &mut C) -> Result> where C: Context, @@ -2865,6 +2946,9 @@ impl WindowHandle { })) } + /// Update the root view of this window. + /// + /// This will fail if the window has been closed or if the root view's type does not match pub fn update( &self, cx: &mut C, @@ -2881,6 +2965,9 @@ impl WindowHandle { })? } + /// Read the root view out of this window. + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn read<'a>(&self, cx: &'a AppContext) -> Result<&'a V> { let x = cx .windows @@ -2897,6 +2984,9 @@ impl WindowHandle { Ok(x.read(cx)) } + /// Read the root view out of this window, with a callback + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn read_with(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result where C: Context, @@ -2904,6 +2994,9 @@ impl WindowHandle { cx.read_window(self, |root_view, cx| read_with(root_view.read(cx), cx)) } + /// Read the root view pointer off of this window. + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn root_view(&self, cx: &C) -> Result> where C: Context, @@ -2911,6 +3004,9 @@ impl WindowHandle { cx.read_window(self, |root_view, _cx| root_view.clone()) } + /// Check if this window is 'active'. + /// + /// Will return `None` if the window is closed. pub fn is_active(&self, cx: &AppContext) -> Option { cx.windows .get(self.id) @@ -2946,6 +3042,7 @@ impl From> for AnyWindowHandle { } } +/// A handle to a window with any root view type, which can be downcast to a window with a specific root view type. #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct AnyWindowHandle { pub(crate) id: WindowId, @@ -2953,10 +3050,13 @@ pub struct AnyWindowHandle { } impl AnyWindowHandle { + /// Get the ID of this window. pub fn window_id(&self) -> WindowId { self.id } + /// Attempt to convert this handle to a window handle with a specific root view type. + /// If the types do not match, this will return `None`. pub fn downcast(&self) -> Option> { if TypeId::of::() == self.state_type { Some(WindowHandle { @@ -2968,6 +3068,9 @@ impl AnyWindowHandle { } } + /// Update the state of the root view of this window. + /// + /// This will fail if the window has been closed. pub fn update( self, cx: &mut C, @@ -2979,6 +3082,9 @@ impl AnyWindowHandle { cx.update_window(self, update) } + /// Read the state of the root view of this window. + /// + /// This will fail if the window has been closed. pub fn read(self, cx: &C, read: impl FnOnce(View, &AppContext) -> R) -> Result where C: Context, @@ -2999,6 +3105,10 @@ impl AnyWindowHandle { // } // } +/// An identifier for an [Element]. +/// +/// Can be constructed with a string, a number, or both, as well +/// as other internal representations. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ElementId { View(EntityId), @@ -3074,7 +3184,8 @@ impl From<(&'static str, u64)> for ElementId { } } -/// A rectangle, to be rendered on the screen by GPUI at the given position and size. +/// A rectangle to be rendered in the window at the given position and size. +/// Passed as an argument [WindowContext::paint_quad]. #[derive(Clone)] pub struct PaintQuad { bounds: Bounds,