Add a bunch of docs
Co-Authored-By: Conrad <conrad@zed.dev> Co-Authored-By: Mikayla <mikayla@zed.dev>
This commit is contained in:
co-authored by
Conrad
Mikayla
parent
ab6cd1d93a
commit
59f41acb82
@@ -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 {
|
||||
|
||||
@@ -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<AppCell>,
|
||||
@@ -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<R>(&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<R>(&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<F, V>(&mut self, build_window: F) -> WindowHandle<V>
|
||||
where
|
||||
F: FnOnce(&mut ViewContext<V>) -> 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<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
|
||||
where
|
||||
F: FnOnce(&mut ViewContext<V>) -> V,
|
||||
@@ -156,18 +171,23 @@ impl TestAppContext {
|
||||
(view, Box::leak(cx))
|
||||
}
|
||||
|
||||
/// returns the TextSystem
|
||||
pub fn text_system(&self) -> &Arc<TextSystem> {
|
||||
&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<ClipboardItem> {
|
||||
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<std::path::PathBuf>,
|
||||
@@ -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<Pixels>) {
|
||||
self.test_window(window_handle).simulate_resize(size);
|
||||
}
|
||||
|
||||
/// Returns all windows open in the test.
|
||||
pub fn windows(&self) -> Vec<AnyWindowHandle> {
|
||||
self.app.borrow().windows().clone()
|
||||
}
|
||||
|
||||
/// Run the given task on the main thread.
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
|
||||
where
|
||||
Fut: Future<Output = R> + 'static,
|
||||
|
||||
@@ -32,6 +32,10 @@ pub struct ForegroundExecutor {
|
||||
not_send: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
/// 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<T> {
|
||||
@@ -40,10 +44,12 @@ pub enum Task<T> {
|
||||
}
|
||||
|
||||
impl<T> Task<T> {
|
||||
/// 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<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
|
||||
|
||||
type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
|
||||
|
||||
/// 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<dyn PlatformDispatcher>) -> 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<R>(&self, future: impl Future<Output = R>) -> 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<R>(&self, future: impl Future<Output = R>) -> 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<R>(
|
||||
&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<Output = ()> {
|
||||
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<usize>) {
|
||||
self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
|
||||
}
|
||||
}
|
||||
|
||||
/// ForegroundExecutor runs things on the main thread.
|
||||
impl ForegroundExecutor {
|
||||
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> 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<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
|
||||
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<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
|
||||
|
||||
@@ -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<T: 'static> futures::Stream for Observation<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// observe returns a stream of the change events from the given `View` or `Model`
|
||||
pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> {
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
let _subscription = cx.update(|cx| {
|
||||
|
||||
+117
-6
@@ -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<Arena> = RefCell::new(Arena::new(4 * 1024 * 1024));
|
||||
@@ -240,6 +247,7 @@ pub trait ManagedView: FocusableView + EventEmitter<DismissEvent> {}
|
||||
|
||||
impl<M: FocusableView + EventEmitter<DismissEvent>> 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<dyn Action>) {
|
||||
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<Emitter, E, Evt>(
|
||||
&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<AvailableSpace>) {
|
||||
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<Pixels> {
|
||||
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<Rc<dyn PlatformDisplay>> {
|
||||
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<Pixels>,
|
||||
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<usize> {
|
||||
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<Box<dyn Action>> {
|
||||
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<KeyBinding> {
|
||||
self.window
|
||||
.rendered_frame
|
||||
@@ -2279,6 +2314,10 @@ impl BorrowMut<Window> for WindowContext<'_> {
|
||||
|
||||
impl<T> BorrowWindow for T where T: BorrowMut<AppContext> + BorrowMut<Window> {}
|
||||
|
||||
/// 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::<V>::update], you're passed a `&mut V` and an `&mut ViewContext<V>`.
|
||||
pub struct ViewContext<'a, V> {
|
||||
window_cx: WindowContext<'a>,
|
||||
view: &'a View<V>,
|
||||
@@ -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<V> {
|
||||
self.view
|
||||
}
|
||||
|
||||
/// Get the model underlying this view.
|
||||
pub fn model(&self) -> &Model<V> {
|
||||
&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<V>) + '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<V2, E>(
|
||||
&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<V2, E, Evt>(
|
||||
&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<V2, E>(
|
||||
&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<V>) + '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<V>) + '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<V>] 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<Fut, R>(
|
||||
&mut self,
|
||||
f: impl FnOnce(WeakView<V>, 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<G, R>(&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<G: 'static>(
|
||||
&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<Event: 'static>(
|
||||
&mut self,
|
||||
handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + '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<Event: 'static>(
|
||||
&mut self,
|
||||
handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + '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<Evt>(&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<E>(
|
||||
&self,
|
||||
f: impl Fn(&mut V, &E, &mut ViewContext<V>) + '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<V> {
|
||||
#[deref]
|
||||
@@ -2844,6 +2920,8 @@ pub struct WindowHandle<V> {
|
||||
}
|
||||
|
||||
impl<V: 'static + Render> WindowHandle<V> {
|
||||
/// 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<V: 'static + Render> WindowHandle<V> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<C>(&self, cx: &mut C) -> Result<View<V>>
|
||||
where
|
||||
C: Context,
|
||||
@@ -2865,6 +2946,9 @@ impl<V: 'static + Render> WindowHandle<V> {
|
||||
}))
|
||||
}
|
||||
|
||||
/// 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<C, R>(
|
||||
&self,
|
||||
cx: &mut C,
|
||||
@@ -2881,6 +2965,9 @@ impl<V: 'static + Render> WindowHandle<V> {
|
||||
})?
|
||||
}
|
||||
|
||||
/// 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<V: 'static + Render> WindowHandle<V> {
|
||||
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<C, R>(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result<R>
|
||||
where
|
||||
C: Context,
|
||||
@@ -2904,6 +2994,9 @@ impl<V: 'static + Render> WindowHandle<V> {
|
||||
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<C>(&self, cx: &C) -> Result<View<V>>
|
||||
where
|
||||
C: Context,
|
||||
@@ -2911,6 +3004,9 @@ impl<V: 'static + Render> WindowHandle<V> {
|
||||
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<bool> {
|
||||
cx.windows
|
||||
.get(self.id)
|
||||
@@ -2946,6 +3042,7 @@ impl<V: 'static> From<WindowHandle<V>> 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<T: 'static>(&self) -> Option<WindowHandle<T>> {
|
||||
if TypeId::of::<T>() == 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<C, R>(
|
||||
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<T, C, R>(self, cx: &C, read: impl FnOnce(View<T>, &AppContext) -> R) -> Result<R>
|
||||
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<Pixels>,
|
||||
|
||||
Reference in New Issue
Block a user