Merge remote-tracking branch 'origin/main' into cache

# Conflicts:
#	crates/copilot/src/sign_in.rs
#	crates/gpui/src/window.rs
#	crates/workspace/src/pane_group.rs
This commit is contained in:
Antonio Scandurra 2024-01-10 22:57:47 +01:00
commit 1c260e6dfd
216 changed files with 4218 additions and 2389 deletions

View file

@ -104,7 +104,7 @@ pub struct ActionData {
}
/// This constant must be public to be accessible from other crates.
/// But it's existence is an implementation detail and should not be used directly.
/// But its existence is an implementation detail and should not be used directly.
#[doc(hidden)]
#[linkme::distributed_slice]
pub static __GPUI_ACTIONS: [MacroActionBuilder];
@ -114,14 +114,26 @@ impl ActionRegistry {
pub(crate) fn load_actions(&mut self) {
for builder in __GPUI_ACTIONS {
let action = builder();
//todo(remove)
let name: SharedString = action.name.into();
self.builders_by_name.insert(name.clone(), action.build);
self.names_by_type_id.insert(action.type_id, name.clone());
self.all_names.push(name);
self.insert_action(action);
}
}
#[cfg(test)]
pub(crate) fn load_action<A: Action>(&mut self) {
self.insert_action(ActionData {
name: A::debug_name(),
type_id: TypeId::of::<A>(),
build: A::build,
});
}
fn insert_action(&mut self, action: ActionData) {
let name: SharedString = action.name.into();
self.builders_by_name.insert(name.clone(), action.build);
self.names_by_type_id.insert(action.type_id, name.clone());
self.all_names.push(name);
}
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
pub fn build_action_type(&self, type_id: &TypeId) -> Result<Box<dyn Action>> {
let name = self
@ -203,7 +215,6 @@ macro_rules! __impl_action {
)
}
// todo!() why is this needed in addition to name?
fn debug_name() -> &'static str
where
Self: ::std::marker::Sized

View file

@ -45,11 +45,13 @@ use util::{
/// Temporary(?) wrapper around [`RefCell<AppContext>`] to help us debug any double borrows.
/// Strongly consider removing after stabilization.
#[doc(hidden)]
pub struct AppCell {
app: RefCell<AppContext>,
}
impl AppCell {
#[doc(hidden)]
#[track_caller]
pub fn borrow(&self) -> AppRef {
if option_env!("TRACK_THREAD_BORROWS").is_some() {
@ -59,6 +61,7 @@ impl AppCell {
AppRef(self.app.borrow())
}
#[doc(hidden)]
#[track_caller]
pub fn borrow_mut(&self) -> AppRefMut {
if option_env!("TRACK_THREAD_BORROWS").is_some() {
@ -69,6 +72,7 @@ impl AppCell {
}
}
#[doc(hidden)]
#[derive(Deref, DerefMut)]
pub struct AppRef<'a>(Ref<'a, AppContext>);
@ -81,6 +85,7 @@ impl<'a> Drop for AppRef<'a> {
}
}
#[doc(hidden)]
#[derive(Deref, DerefMut)]
pub struct AppRefMut<'a>(RefMut<'a, AppContext>);
@ -93,6 +98,8 @@ impl<'a> Drop for AppRefMut<'a> {
}
}
/// 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 App(Rc<AppCell>);
/// Represents an application before it is fully launched. Once your app is
@ -136,6 +143,8 @@ impl App {
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 AppContext),
@ -149,18 +158,22 @@ impl App {
self
}
/// Returns metadata associated with the application
pub fn metadata(&self) -> AppMetadata {
self.0.borrow().app_metadata.clone()
}
/// 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()
}
@ -174,12 +187,6 @@ type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()
type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
// struct FrameConsumer {
// next_frame_callbacks: Vec<FrameCallback>,
// task: Task<()>,
// display_linker
// }
pub struct AppContext {
pub(crate) this: Weak<AppCell>,
pub(crate) platform: Rc<dyn Platform>,
@ -292,7 +299,7 @@ impl AppContext {
app
}
/// Quit the application gracefully. Handlers registered with `ModelContext::on_app_quit`
/// Quit the application gracefully. Handlers registered with [`ModelContext::on_app_quit`]
/// will be given 100ms to complete before exiting.
pub fn shutdown(&mut self) {
let mut futures = Vec::new();
@ -314,10 +321,12 @@ impl AppContext {
}
}
/// Gracefully quit the application via the platform's standard routine.
pub fn quit(&mut self) {
self.platform.quit();
}
/// Get metadata about the app and platform.
pub fn app_metadata(&self) -> AppMetadata {
self.app_metadata.clone()
}
@ -340,6 +349,7 @@ impl AppContext {
result
}
/// Arrange a callback to be invoked when the given model or view calls `notify` on its respective context.
pub fn observe<W, E>(
&mut self,
entity: &E,
@ -355,7 +365,7 @@ impl AppContext {
})
}
pub fn observe_internal<W, E>(
pub(crate) fn observe_internal<W, E>(
&mut self,
entity: &E,
mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static,
@ -380,15 +390,17 @@ impl AppContext {
subscription
}
pub fn subscribe<T, E, Evt>(
/// Arrange for the given callback to be invoked whenever the given model or view 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, E, Event>(
&mut self,
entity: &E,
mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static,
mut on_event: impl FnMut(E, &Event, &mut AppContext) + 'static,
) -> Subscription
where
T: 'static + EventEmitter<Evt>,
T: 'static + EventEmitter<Event>,
E: Entity<T>,
Evt: 'static,
Event: 'static,
{
self.subscribe_internal(entity, move |entity, event, cx| {
on_event(entity, event, cx);
@ -426,6 +438,9 @@ impl AppContext {
subscription
}
/// 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
.values()
@ -565,7 +580,7 @@ impl AppContext {
self.pending_effects.push_back(effect);
}
/// Called at the end of AppContext::update to complete any side effects
/// Called at the end of [`AppContext::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) {

View file

@ -82,6 +82,7 @@ impl Context for AsyncAppContext {
}
impl AsyncAppContext {
/// Schedules all windows in the application to be redrawn.
pub fn refresh(&mut self) -> Result<()> {
let app = self
.app
@ -92,14 +93,17 @@ impl AsyncAppContext {
Ok(())
}
/// Get an executor which can be used to spawn futures in the background.
pub fn background_executor(&self) -> &BackgroundExecutor {
&self.background_executor
}
/// Get an executor which can be used to spawn futures in the foreground.
pub fn foreground_executor(&self) -> &ForegroundExecutor {
&self.foreground_executor
}
/// Invoke the given function in the context of the app, then flush any effects produced during its invocation.
pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result<R> {
let app = self
.app
@ -109,6 +113,7 @@ impl AsyncAppContext {
Ok(f(&mut lock))
}
/// Open a window with the given options based on the root view returned by the given function.
pub fn open_window<V>(
&self,
options: crate::WindowOptions,
@ -125,6 +130,7 @@ impl AsyncAppContext {
Ok(lock.open_window(options, build_root_view))
}
/// Schedule a future to be polled in the background.
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
where
Fut: Future<Output = R> + 'static,

View file

@ -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 From<u64> for EntityId {
fn from(value: u64) -> Self {

View file

@ -1,3 +1,5 @@
#![deny(missing_docs)]
use crate::{
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
@ -9,14 +11,21 @@ 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 {
#[doc(hidden)]
pub app: Rc<AppCell>,
#[doc(hidden)]
pub background_executor: BackgroundExecutor,
#[doc(hidden)]
pub foreground_executor: ForegroundExecutor,
#[doc(hidden)]
pub dispatcher: TestDispatcher,
pub test_platform: Rc<TestPlatform>,
test_platform: Rc<TestPlatform>,
text_system: Arc<TextSystem>,
fn_name: Option<&'static str>,
}
impl Context for TestAppContext {
@ -76,7 +85,8 @@ impl Context for TestAppContext {
}
impl TestAppContext {
pub fn new(dispatcher: TestDispatcher) -> Self {
/// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you.
pub fn new(dispatcher: TestDispatcher, fn_name: Option<&'static str>) -> Self {
let arc_dispatcher = Arc::new(dispatcher.clone());
let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
@ -92,41 +102,61 @@ impl TestAppContext {
dispatcher: dispatcher.clone(),
test_platform: platform,
text_system,
fn_name,
}
}
pub fn new_app(&self) -> TestAppContext {
Self::new(self.dispatcher.clone())
/// The name of the test function that created this `TestAppContext`
pub fn test_function_name(&self) -> Option<&'static str> {
self.fn_name
}
/// Checks whether there have been any new path prompts received by the platform.
pub fn did_prompt_for_new_path(&self) -> bool {
self.test_platform.did_prompt_for_new_path()
}
/// returns a new `TestAppContext` re-using the same executors to interleave tasks.
pub fn new_app(&self) -> TestAppContext {
Self::new(self.dispatcher.clone(), self.fn_name)
}
/// 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 +166,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,
@ -152,22 +186,28 @@ impl TestAppContext {
drop(cx);
let view = window.root_view(self).unwrap();
let cx = Box::new(VisualTestContext::from_window(*window.deref(), self));
cx.run_until_parked();
// it might be nice to try and cleanup these at the end of each test.
(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 +215,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,
@ -199,16 +244,20 @@ impl TestAppContext {
self.foreground_executor.spawn(f(self.to_async()))
}
/// true if the given global is defined
pub fn has_global<G: 'static>(&self) -> bool {
let app = self.app.borrow();
app.has_global::<G>()
}
/// runs the given closure with a reference to the global
/// panics if `has_global` would return false.
pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
let app = self.app.borrow();
read(app.global(), &app)
}
/// runs the given closure with a reference to the global (if set)
pub fn try_read_global<G: 'static, R>(
&self,
read: impl FnOnce(&G, &AppContext) -> R,
@ -217,11 +266,13 @@ impl TestAppContext {
Some(read(lock.try_global()?, &lock))
}
/// sets the global in this context.
pub fn set_global<G: 'static>(&mut self, global: G) {
let mut lock = self.app.borrow_mut();
lock.set_global(global);
}
/// updates the global in this context. (panics if `has_global` would return false)
pub fn update_global<G: 'static, R>(
&mut self,
update: impl FnOnce(&mut G, &mut AppContext) -> R,
@ -230,6 +281,8 @@ impl TestAppContext {
lock.update_global(update)
}
/// Returns an `AsyncAppContext` which can be used to run tasks that expect to be on a background
/// thread on the current thread in tests.
pub fn to_async(&self) -> AsyncAppContext {
AsyncAppContext {
app: Rc::downgrade(&self.app),
@ -238,6 +291,12 @@ impl TestAppContext {
}
}
/// Wait until there are no more pending tasks.
pub fn run_until_parked(&mut self) {
self.background_executor.run_until_parked()
}
/// Simulate dispatching an action to the currently focused node in the window.
pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
where
A: Action,
@ -251,7 +310,8 @@ impl TestAppContext {
/// simulate_keystrokes takes a space-separated list of keys to type.
/// cx.simulate_keystrokes("cmd-shift-p b k s p enter")
/// will run backspace on the current editor through the command palette.
/// in Zed, this will run backspace on the current editor through the command palette.
/// This will also run the background executor until it's parked.
pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
for keystroke in keystrokes
.split(" ")
@ -266,7 +326,8 @@ impl TestAppContext {
/// simulate_input takes a string of text to type.
/// cx.simulate_input("abc")
/// will type abc into your current editor.
/// will type abc into your current editor
/// This will also run the background executor until it's parked.
pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
self.dispatch_keystroke(window, keystroke.into(), false);
@ -275,6 +336,7 @@ impl TestAppContext {
self.background_executor.run_until_parked()
}
/// dispatches a single Keystroke (see also `simulate_keystrokes` and `simulate_input`)
pub fn dispatch_keystroke(
&mut self,
window: AnyWindowHandle,
@ -285,6 +347,7 @@ impl TestAppContext {
.simulate_keystroke(keystroke, is_held)
}
/// Returns the `TestWindow` backing the given handle.
pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
self.app
.borrow_mut()
@ -299,6 +362,7 @@ impl TestAppContext {
.clone()
}
/// Returns a stream of notifications whenever the View or Model is updated.
pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
let (tx, rx) = futures::channel::mpsc::unbounded();
self.update(|cx| {
@ -315,6 +379,7 @@ impl TestAppContext {
rx
}
/// Retuens a stream of events emitted by the given Model.
pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
&mut self,
entity: &Model<T>,
@ -333,6 +398,8 @@ impl TestAppContext {
rx
}
/// Runs until the given condition becomes true. (Prefer `run_until_parked` if you
/// don't need to jump in at a specific time).
pub async fn condition<T: 'static>(
&mut self,
model: &Model<T>,
@ -362,6 +429,7 @@ impl TestAppContext {
}
impl<T: Send> Model<T> {
/// Block until the next event is emitted by the model, then return it.
pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
where
Evt: Send + Clone + 'static,
@ -391,6 +459,7 @@ impl<T: Send> Model<T> {
}
impl<V: 'static> View<V> {
/// Returns a future that resolves when the view is next updated.
pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
use postage::prelude::{Sink as _, Stream as _};
@ -417,6 +486,7 @@ impl<V: 'static> View<V> {
}
impl<V> View<V> {
/// Returns a future that resolves when the condition becomes true.
pub fn condition<Evt>(
&self,
cx: &TestAppContext,
@ -429,7 +499,7 @@ impl<V> View<V> {
use postage::prelude::{Sink as _, Stream as _};
let (tx, mut rx) = postage::mpsc::channel(1024);
let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration();
let timeout_duration = Duration::from_millis(100);
let mut cx = cx.app.borrow_mut();
let subscriptions = (
@ -467,12 +537,11 @@ impl<V> View<V> {
}
}
// todo!(start_waiting)
// cx.borrow().foreground_executor().start_waiting();
cx.borrow().background_executor().start_waiting();
rx.recv()
.await
.expect("view dropped with pending condition");
// cx.borrow().foreground_executor().finish_waiting();
cx.borrow().background_executor().finish_waiting();
}
})
.await
@ -484,18 +553,25 @@ impl<V> View<V> {
use derive_more::{Deref, DerefMut};
#[derive(Deref, DerefMut, Clone)]
/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to
/// run window-specific test code.
pub struct VisualTestContext {
#[deref]
#[deref_mut]
cx: TestAppContext,
/// cx is the original TestAppContext (you can more easily access this using Deref)
pub cx: TestAppContext,
window: AnyWindowHandle,
}
impl<'a> VisualTestContext {
/// Provides the `WindowContext` for the duration of the closure.
pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
}
/// Create a new VisualTestContext. You would typically shadow the passed in
/// TestAppContext with this, as this is typically more useful.
/// `let cx = VisualTestContext::from_window(window, cx);`
pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {
Self {
cx: cx.clone(),
@ -503,10 +579,12 @@ impl<'a> VisualTestContext {
}
}
/// Wait until there are no more pending tasks.
pub fn run_until_parked(&self) {
self.cx.background_executor.run_until_parked();
}
/// Dispatch the action to the currently focused node.
pub fn dispatch_action<A>(&mut self, action: A)
where
A: Action,
@ -514,24 +592,32 @@ impl<'a> VisualTestContext {
self.cx.dispatch_action(self.window, action)
}
/// Read the title off the window (set by `WindowContext#set_window_title`)
pub fn window_title(&mut self) -> Option<String> {
self.cx.test_window(self.window).0.lock().title.clone()
}
/// Simulate a sequence of keystrokes `cx.simulate_keystrokes("cmd-p escape")`
/// Automatically runs until parked.
pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
self.cx.simulate_keystrokes(self.window, keystrokes)
}
/// Simulate typing text `cx.simulate_input("hello")`
/// Automatically runs until parked.
pub fn simulate_input(&mut self, input: &str) {
self.cx.simulate_input(self.window, input)
}
/// Simulates the user blurring the window.
pub fn deactivate_window(&mut self) {
if Some(self.window) == self.test_platform.active_window() {
self.test_platform.set_active_window(None)
}
self.background_executor.run_until_parked();
}
/// Simulates the user closing the window.
/// Returns true if the window was closed.
pub fn simulate_close(&mut self) -> bool {
let handler = self
@ -668,6 +754,7 @@ impl VisualContext for VisualTestContext {
}
impl AnyWindowHandle {
/// Creates the given view in this window.
pub fn build_view<V: Render + 'static>(
&self,
cx: &mut TestAppContext,
@ -677,6 +764,7 @@ impl AnyWindowHandle {
}
}
/// An EmptyView for testing.
pub struct EmptyView {}
impl Render for EmptyView {

View file

@ -66,18 +66,19 @@ impl Arena {
}
unsafe {
let layout = alloc::Layout::new::<T>().pad_to_align();
let next_offset = self.offset.add(layout.size());
assert!(next_offset <= self.end);
let layout = alloc::Layout::new::<T>();
let offset = self.offset.add(self.offset.align_offset(layout.align()));
let next_offset = offset.add(layout.size());
assert!(next_offset <= self.end, "not enough space in Arena");
let result = ArenaBox {
ptr: self.offset.cast(),
ptr: offset.cast(),
valid: self.valid.clone(),
};
inner_writer(result.ptr, f);
self.elements.push(ArenaElement {
value: self.offset,
value: offset,
drop: drop::<T>,
});
self.offset = next_offset;
@ -199,4 +200,43 @@ mod tests {
arena.clear();
assert!(dropped.get());
}
#[test]
#[should_panic(expected = "not enough space in Arena")]
fn test_arena_overflow() {
let mut arena = Arena::new(16);
arena.alloc(|| 1u64);
arena.alloc(|| 2u64);
// This should panic.
arena.alloc(|| 3u64);
}
#[test]
fn test_arena_alignment() {
let mut arena = Arena::new(256);
let x1 = arena.alloc(|| 1u8);
let x2 = arena.alloc(|| 2u16);
let x3 = arena.alloc(|| 3u32);
let x4 = arena.alloc(|| 4u64);
let x5 = arena.alloc(|| 5u64);
assert_eq!(*x1, 1);
assert_eq!(*x2, 2);
assert_eq!(*x3, 3);
assert_eq!(*x4, 4);
assert_eq!(*x5, 5);
assert_eq!(x1.ptr.align_offset(std::mem::align_of_val(&*x1)), 0);
assert_eq!(x2.ptr.align_offset(std::mem::align_of_val(&*x2)), 0);
}
#[test]
#[should_panic(expected = "attempted to dereference an ArenaRef after its Arena was cleared")]
fn test_arena_use_after_clear() {
let mut arena = Arena::new(16);
let value = arena.alloc(|| 1u64);
arena.clear();
let _read_value = *value;
}
}

View file

@ -321,7 +321,7 @@ impl Hsla {
///
/// Assumptions:
/// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent.
/// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value.
/// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing its own alpha value.
/// - RGB color components are contained in the range [0, 1].
/// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined.
pub fn blend(self, other: Hsla) -> Hsla {

View file

@ -31,14 +31,14 @@ pub trait IntoElement: Sized {
/// The specific type of element into which the implementing type is converted.
type Element: Element;
/// The [ElementId] of self once converted into an [Element].
/// The [`ElementId`] of self once converted into an [`Element`].
/// If present, the resulting element's state will be carried across frames.
fn element_id(&self) -> Option<ElementId>;
/// Convert self into a type that implements [Element].
/// Convert self into a type that implements [`Element`].
fn into_element(self) -> Self::Element;
/// Convert self into a dynamically-typed [AnyElement].
/// Convert self into a dynamically-typed [`AnyElement`].
fn into_any_element(self) -> AnyElement {
self.into_element().into_any()
}
@ -115,7 +115,7 @@ pub trait Render: 'static + Sized {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement;
}
/// You can derive [IntoElement] on any type that implements this trait.
/// You can derive [`IntoElement`] on any type that implements this trait.
/// It is used to allow views to be expressed in terms of abstract data.
pub trait RenderOnce: 'static {
fn render(self, cx: &mut WindowContext) -> impl IntoElement;
@ -224,7 +224,7 @@ enum ElementDrawPhase<S> {
},
}
/// A wrapper around an implementer of [Element] that allows it to be drawn in a window.
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
impl<E: Element> DrawableElement<E> {
fn new(element: E) -> Self {
DrawableElement {

View file

@ -1003,7 +1003,7 @@ impl Interactivity {
if let Some(text) = cx
.text_system()
.shape_text(
&element_id,
element_id.into(),
FONT_SIZE,
&[cx.text_style().to_run(str_len)],
None,
@ -1055,22 +1055,11 @@ impl Interactivity {
};
eprintln!(
"This element is created at:\n{}:{}:{}",
location.file(),
"This element was created at:\n{}:{}:{}",
dir.join(location.file()).to_string_lossy(),
location.line(),
location.column()
);
std::process::Command::new("zed")
.arg(format!(
"{}/{}:{}:{}",
dir.to_string_lossy(),
location.file(),
location.line(),
location.column()
))
.spawn()
.ok();
}
}
});

View file

@ -2,7 +2,7 @@ use std::sync::Arc;
use crate::{
point, size, BorrowWindow, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, Size,
InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUrl, Size,
StyleRefinement, Styled, WindowContext,
};
use futures::FutureExt;
@ -12,13 +12,13 @@ use util::ResultExt;
#[derive(Clone, Debug)]
pub enum ImageSource {
/// Image content will be loaded from provided URI at render time.
Uri(SharedString),
Uri(SharedUrl),
Data(Arc<ImageData>),
Surface(CVImageBuffer),
}
impl From<SharedString> for ImageSource {
fn from(value: SharedString) -> Self {
impl From<SharedUrl> for ImageSource {
fn from(value: SharedUrl) -> Self {
Self::Uri(value)
}
}

View file

@ -14,9 +14,8 @@ pub struct Overlay {
children: SmallVec<[AnyElement; 2]>,
anchor_corner: AnchorCorner,
fit_mode: OverlayFitMode,
// todo!();
anchor_position: Option<Point<Pixels>>,
// position_mode: OverlayPositionMode,
position_mode: OverlayPositionMode,
}
/// overlay gives you a floating element that will avoid overflowing the window bounds.
@ -27,6 +26,7 @@ pub fn overlay() -> Overlay {
anchor_corner: AnchorCorner::TopLeft,
fit_mode: OverlayFitMode::SwitchAnchor,
anchor_position: None,
position_mode: OverlayPositionMode::Window,
}
}
@ -44,6 +44,14 @@ impl Overlay {
self
}
/// Sets the position mode for this overlay. Local will have this
/// interpret its [`Overlay::position`] as relative to the parent element.
/// While Window will have it interpret the position as relative to the window.
pub fn position_mode(mut self, mode: OverlayPositionMode) -> Self {
self.position_mode = mode;
self
}
/// Snap to window edge instead of switching anchor corner when an overflow would occur.
pub fn snap_to_window(mut self) -> Self {
self.fit_mode = OverlayFitMode::SnapToWindow;
@ -100,9 +108,14 @@ impl Element for Overlay {
child_max = child_max.max(&child_bounds.lower_right());
}
let size: Size<Pixels> = (child_max - child_min).into();
let origin = self.anchor_position.unwrap_or(bounds.origin);
let mut desired = self.anchor_corner.get_bounds(origin, size);
let (origin, mut desired) = self.position_mode.get_position_and_bounds(
self.anchor_position,
self.anchor_corner,
size,
bounds,
);
let limits = Bounds {
origin: Point::default(),
size: cx.viewport_size(),
@ -184,6 +197,35 @@ pub enum OverlayFitMode {
SwitchAnchor,
}
#[derive(Copy, Clone, PartialEq)]
pub enum OverlayPositionMode {
Window,
Local,
}
impl OverlayPositionMode {
fn get_position_and_bounds(
&self,
anchor_position: Option<Point<Pixels>>,
anchor_corner: AnchorCorner,
size: Size<Pixels>,
bounds: Bounds<Pixels>,
) -> (Point<Pixels>, Bounds<Pixels>) {
match self {
OverlayPositionMode::Window => {
let anchor_position = anchor_position.unwrap_or_else(|| bounds.origin);
let bounds = anchor_corner.get_bounds(anchor_position, size);
(anchor_position, bounds)
}
OverlayPositionMode::Local => {
let anchor_position = anchor_position.unwrap_or_default();
let bounds = anchor_corner.get_bounds(bounds.origin + anchor_position, size);
(anchor_position, bounds)
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum AnchorCorner {
TopLeft,

View file

@ -202,7 +202,10 @@ impl TextState {
let Some(lines) = cx
.text_system()
.shape_text(
&text, font_size, &runs, wrap_width, // Wrap if we know the width.
text.clone(),
font_size,
&runs,
wrap_width, // Wrap if we know the width.
)
.log_err()
else {

View file

@ -1,7 +1,7 @@
use crate::{
point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element,
ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
Pixels, Point, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
Pixels, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
};
use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@ -64,40 +64,19 @@ pub struct UniformList {
}
#[derive(Clone, Default)]
pub struct UniformListScrollHandle(Rc<RefCell<Option<ScrollHandleState>>>);
#[derive(Clone, Debug)]
struct ScrollHandleState {
item_height: Pixels,
list_height: Pixels,
scroll_offset: Rc<RefCell<Point<Pixels>>>,
pub struct UniformListScrollHandle {
deferred_scroll_to_item: Rc<RefCell<Option<usize>>>,
}
impl UniformListScrollHandle {
pub fn new() -> Self {
Self(Rc::new(RefCell::new(None)))
}
pub fn scroll_to_item(&self, ix: usize) {
if let Some(state) = &*self.0.borrow() {
let mut scroll_offset = state.scroll_offset.borrow_mut();
let item_top = state.item_height * ix;
let item_bottom = item_top + state.item_height;
let scroll_top = -scroll_offset.y;
if item_top < scroll_top {
scroll_offset.y = -item_top;
} else if item_bottom > scroll_top + state.list_height {
scroll_offset.y = -(item_bottom - state.list_height);
}
Self {
deferred_scroll_to_item: Rc::new(RefCell::new(None)),
}
}
pub fn scroll_top(&self) -> Pixels {
if let Some(state) = &*self.0.borrow() {
-state.scroll_offset.borrow().y
} else {
Pixels::ZERO
}
pub fn scroll_to_item(&mut self, ix: usize) {
self.deferred_scroll_to_item.replace(Some(ix));
}
}
@ -190,18 +169,14 @@ impl Element for UniformList {
let shared_scroll_offset = element_state
.interactive
.scroll_offset
.get_or_insert_with(|| {
if let Some(scroll_handle) = self.scroll_handle.as_ref() {
if let Some(scroll_handle) = scroll_handle.0.borrow().as_ref() {
return scroll_handle.scroll_offset.clone();
}
}
Rc::default()
})
.get_or_insert_with(|| Rc::default())
.clone();
let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
let shared_scroll_to_item = self
.scroll_handle
.as_mut()
.and_then(|handle| handle.deferred_scroll_to_item.take());
self.interactivity.paint(
bounds,
@ -228,12 +203,18 @@ impl Element for UniformList {
scroll_offset.y = min_scroll_offset;
}
if let Some(scroll_handle) = self.scroll_handle.clone() {
scroll_handle.0.borrow_mut().replace(ScrollHandleState {
item_height,
list_height: padded_bounds.size.height,
scroll_offset: shared_scroll_offset,
});
if let Some(ix) = shared_scroll_to_item {
let list_height = padded_bounds.size.height;
let mut updated_scroll_offset = shared_scroll_offset.borrow_mut();
let item_top = item_height * ix;
let item_bottom = item_top + item_height;
let scroll_top = -updated_scroll_offset.y;
if item_top < scroll_top {
updated_scroll_offset.y = -item_top;
} else if item_bottom > scroll_top + list_height {
updated_scroll_offset.y = -(item_bottom - list_height);
}
scroll_offset = *updated_scroll_offset;
}
let first_visible_element_ix =

View file

@ -32,6 +32,12 @@ 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 [`Task::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 +46,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 +65,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 +107,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 +149,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 +160,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 +223,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 +257,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 +274,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 +286,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 +369,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 +389,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>>>,

View file

@ -18,6 +18,7 @@ mod platform;
pub mod prelude;
mod scene;
mod shared_string;
mod shared_url;
mod style;
mod styled;
mod subscription;
@ -67,6 +68,7 @@ pub use refineable::*;
pub use scene::*;
use seal::Sealed;
pub use shared_string::*;
pub use shared_url::*;
pub use smol::Timer;
pub use style::*;
pub use styled::*;

View file

@ -1,4 +1,4 @@
use crate::{ImageData, ImageId, SharedString};
use crate::{ImageData, ImageId, SharedUrl};
use collections::HashMap;
use futures::{
future::{BoxFuture, Shared},
@ -44,7 +44,7 @@ impl From<ImageError> for Error {
pub struct ImageCache {
client: Arc<dyn HttpClient>,
images: Arc<Mutex<HashMap<SharedString, FetchImageFuture>>>,
images: Arc<Mutex<HashMap<SharedUrl, FetchImageFuture>>>,
}
type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
@ -59,7 +59,7 @@ impl ImageCache {
pub fn get(
&self,
uri: impl Into<SharedString>,
uri: impl Into<SharedUrl>,
) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
let uri = uri.into();
let mut images = self.images.lock();

View file

@ -34,7 +34,7 @@ pub trait InputHandler: 'static + Sized {
) -> Option<Bounds<Pixels>>;
}
/// The canonical implementation of `PlatformInputHandler`. Call `WindowContext::handle_input`
/// The canonical implementation of [`PlatformInputHandler`]. Call [`WindowContext::handle_input`]
/// with an instance during your element's paint.
pub struct ElementInputHandler<V> {
view: View<V>,

View file

@ -178,6 +178,20 @@ impl ScrollDelta {
ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
}
}
pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
match (self, other) {
(ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => {
ScrollDelta::Pixels(px_a + px_b)
}
(ScrollDelta::Lines(lines_a), ScrollDelta::Lines(lines_b)) => {
ScrollDelta::Lines(lines_a + lines_b)
}
_ => other,
}
}
}
#[derive(Clone, Debug, Default)]

View file

@ -269,8 +269,8 @@ impl DispatchTree {
keymap
.bindings_for_action(action)
.filter(|binding| {
for i in 1..context_stack.len() {
let context = &context_stack[0..i];
for i in 0..context_stack.len() {
let context = &context_stack[0..=i];
if keymap.binding_enabled(binding, context) {
return true;
}
@ -374,3 +374,76 @@ impl DispatchTree {
*self.node_stack.last().unwrap()
}
}
#[cfg(test)]
mod tests {
use std::{rc::Rc, sync::Arc};
use parking_lot::Mutex;
use crate::{Action, ActionRegistry, DispatchTree, KeyBinding, KeyContext, Keymap};
#[derive(PartialEq, Eq)]
struct TestAction;
impl Action for TestAction {
fn name(&self) -> &'static str {
"test::TestAction"
}
fn debug_name() -> &'static str
where
Self: ::std::marker::Sized,
{
"test::TestAction"
}
fn partial_eq(&self, action: &dyn Action) -> bool {
action
.as_any()
.downcast_ref::<Self>()
.map_or(false, |a| self == a)
}
fn boxed_clone(&self) -> std::boxed::Box<dyn Action> {
Box::new(TestAction)
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn Action>>
where
Self: Sized,
{
Ok(Box::new(TestAction))
}
}
#[test]
fn test_keybinding_for_action_bounds() {
let keymap = Keymap::new(vec![KeyBinding::new(
"cmd-n",
TestAction,
Some("ProjectPanel"),
)]);
let mut registry = ActionRegistry::default();
registry.load_action::<TestAction>();
let keymap = Arc::new(Mutex::new(keymap));
let tree = DispatchTree::new(keymap, Rc::new(registry));
let contexts = vec![
KeyContext::parse("Workspace").unwrap(),
KeyContext::parse("ProjectPanel").unwrap(),
];
let keybinding = tree.bindings_for_action(&TestAction, &contexts);
assert!(keybinding[0].action.partial_eq(&TestAction))
}
}

View file

@ -14,12 +14,12 @@ pub struct MacDisplay(pub(crate) CGDirectDisplayID);
unsafe impl Send for MacDisplay {}
impl MacDisplay {
/// Get the screen with the given [DisplayId].
/// Get the screen with the given [`DisplayId`].
pub fn find_by_id(id: DisplayId) -> Option<Self> {
Self::all().find(|screen| screen.id() == id)
}
/// Get the screen with the given persistent [Uuid].
/// Get the screen with the given persistent [`Uuid`].
pub fn find_by_uuid(uuid: Uuid) -> Option<Self> {
Self::all().find(|screen| screen.uuid().ok() == Some(uuid))
}

View file

@ -337,6 +337,7 @@ struct MacWindowState {
ime_state: ImeState,
// Retains the last IME Text
ime_text: Option<String>,
external_files_dragged: bool,
}
impl MacWindowState {
@ -565,6 +566,7 @@ impl MacWindow {
previous_modifiers_changed_event: None,
ime_state: ImeState::None,
ime_text: None,
external_files_dragged: false,
})));
(*native_window).set_ivar(
@ -1230,15 +1232,20 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
..
},
) => {
lock.synthetic_drag_counter += 1;
let executor = lock.executor.clone();
executor
.spawn(synthetic_drag(
weak_window_state,
lock.synthetic_drag_counter,
event.clone(),
))
.detach();
// Synthetic drag is used for selecting long buffer contents while buffer is being scrolled.
// External file drag and drop is able to emit its own synthetic mouse events which will conflict
// with these ones.
if !lock.external_files_dragged {
lock.synthetic_drag_counter += 1;
let executor = lock.executor.clone();
executor
.spawn(synthetic_drag(
weak_window_state,
lock.synthetic_drag_counter,
event.clone(),
))
.detach();
}
}
InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
@ -1679,6 +1686,7 @@ extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDr
let paths = external_paths_from_event(dragging_info);
InputEvent::FileDrop(FileDropEvent::Entered { position, paths })
}) {
window_state.lock().external_files_dragged = true;
NSDragOperationCopy
} else {
NSDragOperationNone
@ -1701,6 +1709,7 @@ extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDr
extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
let window_state = unsafe { get_window_state(this) };
send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited));
window_state.lock().external_files_dragged = false;
}
extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL {

View file

@ -32,7 +32,7 @@ impl PlatformDisplay for TestDisplay {
}
fn as_any(&self) -> &dyn std::any::Any {
todo!()
unimplemented!()
}
fn bounds(&self) -> crate::Bounds<crate::GlobalPixels> {

View file

@ -14,6 +14,7 @@ use std::{
time::Duration,
};
/// TestPlatform implements the Platform trait for use in tests.
pub struct TestPlatform {
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
@ -100,9 +101,12 @@ impl TestPlatform {
})
.detach();
}
pub(crate) fn did_prompt_for_new_path(&self) -> bool {
self.prompts.borrow().new_path.len() > 0
}
}
// todo!("implement out what our tests needed in GPUI 1")
impl Platform for TestPlatform {
fn background_executor(&self) -> BackgroundExecutor {
self.background_executor.clone()
@ -276,8 +280,7 @@ impl Platform for TestPlatform {
}
fn should_auto_hide_scrollbars(&self) -> bool {
// todo()
true
false
}
fn write_to_clipboard(&self, item: ClipboardItem) {

View file

@ -0,0 +1,25 @@
use derive_more::{Deref, DerefMut};
use crate::SharedString;
/// A [`SharedString`] containing a URL.
#[derive(Deref, DerefMut, Default, PartialEq, Eq, Hash, Clone)]
pub struct SharedUrl(SharedString);
impl std::fmt::Debug for SharedUrl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::fmt::Display for SharedUrl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.as_ref())
}
}
impl<T: Into<SharedString>> From<T> for SharedUrl {
fn from(value: T) -> Self {
Self(value.into())
}
}

View file

@ -165,7 +165,8 @@ impl Default for TextStyle {
fn default() -> Self {
TextStyle {
color: black(),
font_family: "Helvetica".into(), // todo!("Get a font we know exists on the system")
// Helvetica is a web safe font, so it should be available
font_family: "Helvetica".into(),
font_features: FontFeatures::default(),
font_size: rems(1.).into(),
line_height: phi(),

View file

@ -37,10 +37,10 @@ where
})))
}
/// Inserts a new `[Subscription]` for the given `emitter_key`. By default, subscriptions
/// Inserts a new [`Subscription`] for the given `emitter_key`. By default, subscriptions
/// are inert, meaning that they won't be listed when calling `[SubscriberSet::remove]` or `[SubscriberSet::retain]`.
/// This method returns a tuple of a `[Subscription]` and an `impl FnOnce`, and you can use the latter
/// to activate the `[Subscription]`.
/// This method returns a tuple of a [`Subscription`] and an `impl FnOnce`, and you can use the latter
/// to activate the [`Subscription`].
#[must_use]
pub fn insert(
&self,

View file

@ -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::*;
@ -12,7 +39,6 @@ pub fn run_test(
max_retries: usize,
test_fn: &mut (dyn RefUnwindSafe + Fn(TestDispatcher, u64)),
on_fail_fn: Option<fn()>,
_fn_name: String, // todo!("re-enable fn_name")
) {
let starting_seed = env::var("SEED")
.map(|seed| seed.parse().expect("invalid SEED variable"))
@ -68,6 +94,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| {

View file

@ -258,7 +258,7 @@ impl TextSystem {
pub fn shape_text(
&self,
text: &str, // todo!("pass a SharedString and preserve it when passed a single line?")
text: SharedString,
font_size: Pixels,
runs: &[TextRun],
wrap_width: Option<Pixels>,
@ -268,8 +268,8 @@ impl TextSystem {
let mut lines = SmallVec::new();
let mut line_start = 0;
for line_text in text.split('\n') {
let line_text = SharedString::from(line_text.to_string());
let mut process_line = |line_text: SharedString| {
let line_end = line_start + line_text.len();
let mut last_font: Option<Font> = None;
@ -335,6 +335,24 @@ impl TextSystem {
}
font_runs.clear();
};
let mut split_lines = text.split('\n');
let mut processed = false;
if let Some(first_line) = split_lines.next() {
if let Some(second_line) = split_lines.next() {
processed = true;
process_line(first_line.to_string().into());
process_line(second_line.to_string().into());
for line_text in split_lines {
process_line(line_text.to_string().into());
}
}
}
if !processed {
process_line(text);
}
self.font_runs_pool.lock().push(font_runs);

View file

@ -143,7 +143,7 @@ mod tests {
#[test]
fn test_wrap_line() {
let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
let cx = TestAppContext::new(dispatcher);
let cx = TestAppContext::new(dispatcher, None);
cx.update(|cx| {
let text_system = cx.text_system().clone();

View file

@ -1,3 +1,5 @@
#![deny(missing_docs)]
use crate::{
px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext,
AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels,
@ -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));
@ -231,6 +238,7 @@ impl Drop for FocusHandle {
/// FocusableView allows users of your view to easily
/// focus it (using cx.focus_view(view))
pub trait FocusableView: 'static + Render {
/// Returns the focus handle associated with this view.
fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
}
@ -240,9 +248,11 @@ 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.
#[doc(hidden)]
pub struct Window {
pub(crate) handle: AnyWindowHandle,
pub(crate) removed: bool,
@ -259,7 +269,7 @@ pub struct Window {
pub(crate) dirty_views: FxHashSet<EntityId>,
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
blur_listeners: SubscriberSet<(), AnyObserver>,
focus_lost_listeners: SubscriberSet<(), AnyObserver>,
default_prevented: bool,
mouse_position: Point<Pixels>,
modifiers: Modifiers,
@ -288,6 +298,7 @@ pub(crate) struct ElementStateBox {
pub(crate) struct Frame {
focus: Option<FocusId>,
window_active: bool,
pub(crate) element_states: FxHashMap<GlobalElementId, ElementStateBox>,
mouse_listeners: FxHashMap<TypeId, Vec<(StackingOrder, EntityId, AnyMouseListener)>>,
pub(crate) dispatch_tree: DispatchTree,
@ -305,6 +316,7 @@ impl Frame {
fn new(dispatch_tree: DispatchTree) -> Self {
Frame {
focus: None,
window_active: false,
element_states: FxHashMap::default(),
mouse_listeners: FxHashMap::default(),
dispatch_tree,
@ -415,7 +427,7 @@ impl Window {
dirty_views: FxHashSet::default(),
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
focus_listeners: SubscriberSet::new(),
blur_listeners: SubscriberSet::new(),
focus_lost_listeners: SubscriberSet::new(),
default_prevented: true,
mouse_position,
modifiers,
@ -443,6 +455,7 @@ impl Window {
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[repr(C)]
pub struct ContentMask<P: Clone + Default + Debug> {
/// The bounds
pub bounds: Bounds<P>,
}
@ -462,8 +475,8 @@ impl ContentMask<Pixels> {
}
/// Provides access to application state in the context of a single window. Derefs
/// to an `AppContext`, so you can also pass a `WindowContext` to any method that takes
/// an `AppContext` and call any `AppContext` methods.
/// to an [`AppContext`], so you can also pass a [`WindowContext`] to any method that takes
/// an [`AppContext`] and call any [`AppContext`] methods.
pub struct WindowContext<'a> {
pub(crate) app: &'a mut AppContext,
pub(crate) window: &'a mut Window,
@ -492,20 +505,20 @@ impl<'a> WindowContext<'a> {
self.window.removed = true;
}
/// Obtain a new `FocusHandle`, which allows you to track and manipulate the keyboard focus
/// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus
/// for elements rendered within this window.
pub fn focus_handle(&mut self) -> FocusHandle {
FocusHandle::new(&self.window.focus_handles)
}
/// Obtain the currently focused `FocusHandle`. If no elements are focused, returns `None`.
/// Obtain the currently focused [`FocusHandle`]. If no elements are focused, returns `None`.
pub fn focused(&self) -> Option<FocusHandle> {
self.window
.focus
.and_then(|id| FocusHandle::for_id(id, &self.window.focus_handles))
}
/// Move focus to the element associated with the given `FocusHandle`.
/// Move focus to the element associated with the given [`FocusHandle`].
pub fn focus(&mut self, handle: &FocusHandle) {
if !self.window.focus_enabled || self.window.focus == Some(handle.id) {
return;
@ -535,11 +548,13 @@ impl<'a> WindowContext<'a> {
self.refresh();
}
/// 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();
@ -601,6 +616,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,
@ -764,7 +782,7 @@ impl<'a> WindowContext<'a> {
.request_measured_layout(style, rem_size, measure)
}
pub fn layout_style(&self, layout_id: LayoutId) -> Option<&Style> {
pub(crate) fn layout_style(&self, layout_id: LayoutId) -> Option<&Style> {
self.window
.layout_engine
.as_ref()
@ -772,6 +790,9 @@ impl<'a> WindowContext<'a> {
.requested_style(layout_id)
}
/// 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);
@ -806,30 +827,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()
@ -837,6 +865,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();
}
@ -941,6 +970,7 @@ impl<'a> WindowContext<'a> {
.on_action(action_type, Rc::new(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()
@ -967,6 +997,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)
}
@ -997,7 +1028,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,
@ -1416,6 +1447,7 @@ impl<'a> WindowContext<'a> {
self.window.focus,
);
self.window.next_frame.focus = self.window.focus;
self.window.next_frame.window_active = self.window.active;
self.window.root_view = Some(root_view);
// Reuse mouse listeners that didn't change since the last frame.
@ -1471,26 +1503,10 @@ impl<'a> WindowContext<'a> {
self.window.next_frame.scene.finish();
let previous_focus_path = self.window.rendered_frame.focus_path();
let previous_window_active = self.window.rendered_frame.window_active;
mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame);
let current_focus_path = self.window.rendered_frame.focus_path();
if previous_focus_path != current_focus_path {
if !previous_focus_path.is_empty() && current_focus_path.is_empty() {
self.window
.blur_listeners
.clone()
.retain(&(), |listener| listener(self));
}
let event = FocusEvent {
previous_focus_path,
current_focus_path,
};
self.window
.focus_listeners
.clone()
.retain(&(), |listener| listener(&event, self));
}
let current_window_active = self.window.rendered_frame.window_active;
// Set the cursor only if we're the active window.
let cursor_style = self
@ -1506,6 +1522,34 @@ impl<'a> WindowContext<'a> {
self.window.drawing = false;
ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear());
if previous_focus_path != current_focus_path
|| previous_window_active != current_window_active
{
if !previous_focus_path.is_empty() && current_focus_path.is_empty() {
self.window
.focus_lost_listeners
.clone()
.retain(&(), |listener| listener(self));
}
let event = FocusEvent {
previous_focus_path: if previous_window_active {
previous_focus_path
} else {
Default::default()
},
current_focus_path: if current_window_active {
current_focus_path
} else {
Default::default()
},
};
self.window
.focus_listeners
.clone()
.retain(&(), |listener| listener(&event, self));
}
self.window
.platform_window
.draw(&self.window.rendered_frame.scene);
@ -1537,9 +1581,7 @@ impl<'a> WindowContext<'a> {
InputEvent::MouseUp(mouse_up)
}
InputEvent::MouseExited(mouse_exited) => {
// todo!("Should we record that the mouse is outside of the window somehow? Or are these global pixels?")
self.window.modifiers = mouse_exited.modifiers;
InputEvent::MouseExited(mouse_exited)
}
InputEvent::ModifiersChanged(modifiers_changed) => {
@ -1741,6 +1783,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
@ -1807,27 +1850,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
@ -1846,6 +1896,7 @@ impl<'a> WindowContext<'a> {
.available_actions(node_id)
}
/// Returns key bindings that invoke the given action on the currently focused element.
pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
self.window
.rendered_frame
@ -1856,6 +1907,7 @@ impl<'a> WindowContext<'a> {
)
}
/// Returns any bindings that would invoke the given action on the given focus handle if it were focused.
pub fn bindings_for_action_in(
&self,
action: &dyn Action,
@ -1874,6 +1926,7 @@ impl<'a> WindowContext<'a> {
dispatch_tree.bindings_for_action(action, &context_stack)
}
/// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle.
pub fn listener_for<V: Render, E>(
&self,
view: &View<V>,
@ -1885,6 +1938,7 @@ impl<'a> WindowContext<'a> {
}
}
/// Returns a generic handler that invokes the given handler with the view and context associated with the given view handle.
pub fn handler_for<V: Render>(
&self,
view: &View<V>,
@ -1896,7 +1950,8 @@ impl<'a> WindowContext<'a> {
}
}
//========== ELEMENT RELATED FUNCTIONS ===========
/// Invoke the given function with the given focus handle present on the key dispatch stack.
/// If you want an element to participate in key dispatch, use this method to push its key context and focus handle into the stack during paint.
pub fn with_key_dispatch<R>(
&mut self,
context: Option<KeyContext>,
@ -2043,6 +2098,8 @@ impl<'a> WindowContext<'a> {
}
}
/// Register a callback that can interrupt the closing of the current window based the returned boolean.
/// If the callback returns false, the window won't be closed.
pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) {
let mut this = self.to_async();
self.window
@ -2217,19 +2274,24 @@ impl<'a> BorrowMut<AppContext> for WindowContext<'a> {
}
}
/// This trait contains functionality that is shared across [`ViewContext`] and [`WindowContext`]
pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
#[doc(hidden)]
fn app_mut(&mut self) -> &mut AppContext {
self.borrow_mut()
}
#[doc(hidden)]
fn app(&self) -> &AppContext {
self.borrow()
}
#[doc(hidden)]
fn window(&self) -> &Window {
self.borrow()
}
#[doc(hidden)]
fn window_mut(&mut self) -> &mut Window {
self.borrow_mut()
}
@ -2387,6 +2449,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::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>,
@ -2424,14 +2490,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
}
@ -2441,6 +2510,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,
@ -2458,6 +2528,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
});
}
/// Observe another model or view for changes to its state, as tracked by [`ModelContext::notify`].
pub fn observe<V2, E>(
&mut self,
entity: &E,
@ -2491,6 +2562,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,
@ -2548,6 +2622,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,
@ -2574,6 +2649,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) {
for view_id in self
.window
@ -2594,6 +2671,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,
@ -2607,6 +2685,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,
@ -2698,14 +2777,16 @@ impl<'a, V: 'static> ViewContext<'a, V> {
subscription
}
/// Register a listener to be called when the window loses focus.
/// Register a listener to be called when nothing in the window has focus.
/// This typically happens when the node that was focused is removed from the tree,
/// and this callback lets you chose a default place to restore the users focus.
/// Returns a subscription and persists until the subscription is dropped.
pub fn on_blur_window(
pub fn on_focus_lost(
&mut self,
mut listener: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
) -> Subscription {
let view = self.view.downgrade();
let (subscription, activate) = self.window.blur_listeners.insert(
let (subscription, activate) = self.window.focus_lost_listeners.insert(
(),
Box::new(move |cx| view.update(cx, |view, cx| listener(view, cx)).is_ok()),
);
@ -2739,6 +2820,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,
@ -2751,6 +2836,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,
@ -2761,6 +2847,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,
@ -2779,6 +2866,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,
@ -2791,6 +2881,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,
@ -2803,6 +2894,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,
@ -2817,6 +2909,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,
@ -2830,6 +2923,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,
@ -2837,6 +2931,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,
@ -2946,14 +3045,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]
@ -2963,6 +3068,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 {
@ -2973,6 +3080,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,
@ -2984,6 +3094,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,
@ -3000,6 +3113,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
@ -3016,6 +3132,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,
@ -3023,6 +3142,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,
@ -3030,6 +3152,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)
@ -3065,6 +3190,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,
@ -3072,10 +3198,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 {
@ -3087,6 +3216,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,
@ -3098,6 +3230,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,
@ -3118,12 +3253,21 @@ impl AnyWindowHandle {
// }
// }
/// An identifier for an [`Element`](crate::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 {
/// The ID of a View element
View(EntityId),
/// An integer ID.
Integer(usize),
/// A string based ID.
Name(SharedString),
/// An ID that's equated with a focus handle.
FocusHandle(FocusId),
/// A combination of a name and an integer.
NamedInteger(SharedString, usize),
}
@ -3193,7 +3337,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>,