Pass a MutableAppContext to Element::layout and ::dispatch_event

Nathan Sobo , Antonio Scandurra , and Max Brunsfeld created

We need to mutate the app in these cases to relay layout state, so this just makes things easier. We won't be able to perform layout in parallel but it's questionable whether we'll ever actually do that. If we do, we can revisit.

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

gpui/src/app.rs                          | 53 ++++++++++++++++++++-----
gpui/src/elements.rs                     |  4 
gpui/src/elements/mouse_event_handler.rs |  4 
gpui/src/elements/uniform_list.rs        |  5 -
gpui/src/presenter.rs                    | 10 ++--
zed/src/editor/element.rs                | 42 ++++++++++----------
zed/src/file_finder.rs                   |  1 
7 files changed, 75 insertions(+), 44 deletions(-)

Detailed changes

gpui/src/app.rs 🔗

@@ -5,7 +5,8 @@ use crate::{
     platform::{self, Platform, PromptLevel, WindowOptions},
     presenter::Presenter,
     util::{post_inc, timeout},
-    AssetCache, AssetSource, ClipboardItem, FontCache, PathPromptOptions, TextLayoutCache,
+    AssetCache, AssetSource, ClipboardItem, EventContext, FontCache, PathPromptOptions,
+    TextLayoutCache,
 };
 use anyhow::{anyhow, Result};
 use async_task::Task;
@@ -22,6 +23,7 @@ use std::{
     fmt::{self, Debug},
     hash::{Hash, Hasher},
     marker::PhantomData,
+    ops::Deref,
     path::{Path, PathBuf},
     rc::{self, Rc},
     sync::{Arc, Weak},
@@ -1476,6 +1478,14 @@ impl AsRef<AppContext> for MutableAppContext {
     }
 }
 
+impl Deref for MutableAppContext {
+    type Target = AppContext;
+
+    fn deref(&self) -> &Self::Target {
+        &self.cx
+    }
+}
+
 pub struct AppContext {
     models: HashMap<usize, Box<dyn AnyModel>>,
     views: HashMap<(usize, usize), Box<dyn AnyView>>,
@@ -1534,8 +1544,10 @@ impl AppContext {
 
     pub fn value<Tag: 'static, T: 'static + Default>(&self, id: usize) -> ValueHandle<T> {
         let key = (TypeId::of::<Tag>(), id);
-        let mut values = self.values.write();
-        values.entry(key).or_insert_with(|| Box::new(T::default()));
+        self.values
+            .write()
+            .entry(key)
+            .or_insert_with(|| Box::new(T::default()));
         ValueHandle::new(TypeId::of::<Tag>(), id, &self.ref_counts)
     }
 }
@@ -2090,6 +2102,14 @@ impl<M> AsRef<AppContext> for ViewContext<'_, M> {
     }
 }
 
+impl<M> Deref for ViewContext<'_, M> {
+    type Target = MutableAppContext;
+
+    fn deref(&self) -> &Self::Target {
+        &self.app
+    }
+}
+
 impl<M> AsMut<MutableAppContext> for ViewContext<'_, M> {
     fn as_mut(&mut self) -> &mut MutableAppContext {
         self.app
@@ -2633,8 +2653,7 @@ impl<T: View> WeakViewHandle<T> {
         }
     }
 
-    pub fn upgrade(&self, cx: impl AsRef<AppContext>) -> Option<ViewHandle<T>> {
-        let cx = cx.as_ref();
+    pub fn upgrade(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
         if cx.ref_counts.lock().is_entity_alive(self.view_id) {
             Some(ViewHandle::new(
                 self.window_id,
@@ -2684,13 +2703,25 @@ impl<T: 'static> ValueHandle<T> {
             .unwrap())
     }
 
-    pub fn update<R>(&self, cx: &AppContext, f: impl FnOnce(&mut T) -> R) -> R {
-        f(cx.values
+    pub fn update<R>(
+        &self,
+        cx: &mut EventContext,
+        f: impl FnOnce(&mut T, &mut EventContext) -> R,
+    ) -> R {
+        let mut value = cx
+            .app
+            .cx
+            .values
             .write()
-            .get_mut(&(self.tag_type_id, self.id))
-            .unwrap()
-            .downcast_mut()
-            .unwrap())
+            .remove(&(self.tag_type_id, self.id))
+            .unwrap();
+        let result = f(value.downcast_mut().unwrap(), cx);
+        cx.app
+            .cx
+            .values
+            .write()
+            .insert((self.tag_type_id, self.id), value);
+        result
     }
 }
 

gpui/src/elements.rs 🔗

@@ -29,8 +29,8 @@ pub use uniform_list::*;
 
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
-    json, AfterLayoutContext, AppContext, DebugContext, Event, EventContext, LayoutContext,
-    PaintContext, SizeConstraint,
+    json, AfterLayoutContext, DebugContext, Event, EventContext, LayoutContext, PaintContext,
+    SizeConstraint,
 };
 use core::panic;
 use json::ToJson;

gpui/src/elements/mouse_event_handler.rs 🔗

@@ -24,7 +24,7 @@ impl MouseEventHandler {
         F: FnOnce(MouseState) -> ElementBox,
     {
         let state_handle = cx.value::<Tag, _>(id);
-        let state = state_handle.read(cx, |state| *state);
+        let state = state_handle.read(cx.as_ref(), |state| *state);
         let child = render_child(state);
         Self {
             state: state_handle,
@@ -81,7 +81,7 @@ impl Element for MouseEventHandler {
 
         let handled_in_child = self.child.dispatch_event(event, cx);
 
-        self.state.update(cx.app, |state| match event {
+        self.state.update(cx, |state, cx| match event {
             Event::MouseMoved { position } => {
                 let mouse_in = bounds.contains_point(*position);
                 if state.hovered != mouse_in {

gpui/src/elements/uniform_list.rs 🔗

@@ -1,6 +1,5 @@
 use super::{
-    AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, PaintContext,
-    SizeConstraint,
+    AfterLayoutContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
 };
 use crate::{
     geometry::{
@@ -8,7 +7,7 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{self, json},
-    ElementBox,
+    AppContext, ElementBox,
 };
 use json::ToJson;
 use parking_lot::Mutex;

gpui/src/presenter.rs 🔗

@@ -76,7 +76,7 @@ impl Presenter {
         let mut scene = Scene::new(scale_factor);
 
         if let Some(root_view_id) = cx.root_view_id(self.window_id) {
-            self.layout(window_size, cx.as_ref());
+            self.layout(window_size, cx);
             self.after_layout(cx);
             let mut paint_cx = PaintContext {
                 scene: &mut scene,
@@ -98,7 +98,7 @@ impl Presenter {
         scene
     }
 
-    fn layout(&mut self, size: Vector2F, cx: &AppContext) {
+    fn layout(&mut self, size: Vector2F, cx: &mut MutableAppContext) {
         if let Some(root_view_id) = cx.root_view_id(self.window_id) {
             let mut layout_ctx = LayoutContext {
                 rendered_views: &mut self.rendered_views,
@@ -138,7 +138,7 @@ impl Presenter {
                 text_layout_cache: &self.text_layout_cache,
                 view_stack: Default::default(),
                 invalidated_views: Default::default(),
-                app: cx.as_ref(),
+                app: cx,
             };
             event_cx.dispatch_event(root_view_id, &event);
 
@@ -184,7 +184,7 @@ pub struct LayoutContext<'a> {
     pub font_cache: &'a FontCache,
     pub text_layout_cache: &'a TextLayoutCache,
     pub asset_cache: &'a AssetCache,
-    pub app: &'a AppContext,
+    pub app: &'a mut MutableAppContext,
     view_stack: Vec<usize>,
 }
 
@@ -240,7 +240,7 @@ pub struct EventContext<'a> {
     actions: Vec<ActionToDispatch>,
     pub font_cache: &'a FontCache,
     pub text_layout_cache: &'a TextLayoutCache,
-    pub app: &'a AppContext,
+    pub app: &'a mut MutableAppContext,
     view_stack: Vec<usize>,
     invalidated_views: HashSet<usize>,
 }

zed/src/editor/element.rs 🔗

@@ -38,8 +38,9 @@ impl EditorElement {
         cx: &mut EventContext,
     ) -> bool {
         if paint.text_bounds.contains_point(position) {
-            let view = self.view(cx.app);
-            let position = paint.point_for_position(view, layout, position, cx.font_cache, cx.app);
+            let view = self.view(cx.app.as_ref());
+            let position =
+                paint.point_for_position(view, layout, position, cx.font_cache, cx.app.as_ref());
             cx.dispatch_action("buffer:select", SelectAction::Begin { position, add: cmd });
             true
         } else {
@@ -48,7 +49,7 @@ impl EditorElement {
     }
 
     fn mouse_up(&self, _position: Vector2F, cx: &mut EventContext) -> bool {
-        if self.view(cx.app).is_selecting() {
+        if self.view(cx.app.as_ref()).is_selecting() {
             cx.dispatch_action("buffer:select", SelectAction::End);
             true
         } else {
@@ -63,7 +64,7 @@ impl EditorElement {
         paint: &mut PaintState,
         cx: &mut EventContext,
     ) -> bool {
-        let view = self.view(cx.app);
+        let view = self.view(cx.app.as_ref());
 
         if view.is_selecting() {
             let rect = paint.text_bounds;
@@ -93,22 +94,21 @@ impl EditorElement {
                 ))
             }
 
-            cx.dispatch_action(
-                "buffer:select",
-                SelectAction::Update {
-                    position: paint.point_for_position(
-                        view,
-                        layout,
-                        position,
-                        cx.font_cache,
-                        cx.app,
-                    ),
-                    scroll_position: (view.scroll_position() + scroll_delta).clamp(
-                        Vector2F::zero(),
-                        layout.scroll_max(view, cx.font_cache, cx.text_layout_cache, cx.app),
-                    ),
-                },
-            );
+            let action = SelectAction::Update {
+                position: paint.point_for_position(
+                    view,
+                    layout,
+                    position,
+                    cx.font_cache,
+                    cx.app.as_ref(),
+                ),
+                scroll_position: (view.scroll_position() + scroll_delta).clamp(
+                    Vector2F::zero(),
+                    layout.scroll_max(view, cx.font_cache, cx.text_layout_cache, cx.app),
+                ),
+            };
+
+            cx.dispatch_action("buffer:select", action);
             true
         } else {
             false
@@ -326,7 +326,7 @@ impl Element for EditorElement {
         constraint: SizeConstraint,
         cx: &mut LayoutContext,
     ) -> (Vector2F, Self::LayoutState) {
-        let app = cx.app;
+        let app = &mut cx.app;
         let mut size = constraint.max;
         if size.y().is_infinite() {
             let view = self.view(app);

zed/src/file_finder.rs 🔗

@@ -124,6 +124,7 @@ impl FileFinder {
             self.list_state.clone(),
             self.matches.len(),
             move |mut range, items, cx| {
+                let cx = cx.as_ref();
                 let finder = handle.upgrade(cx).unwrap();
                 let finder = finder.read(cx);
                 let start = range.start;