Separate `Window::build_scene` into `layout` and `paint`

Antonio Scandurra created

Change summary

crates/collab_ui/src/collab_titlebar_item.rs    |   6 
crates/collab_ui/src/face_pile.rs               |   4 
crates/editor/src/element.rs                    |  18 +
crates/gpui/src/app.rs                          | 133 +++++++++++++-----
crates/gpui/src/app/window.rs                   |  46 ++++-
crates/gpui/src/elements.rs                     |  44 ++++-
crates/gpui/src/elements/align.rs               |   4 
crates/gpui/src/elements/canvas.rs              |   2 
crates/gpui/src/elements/clipped.rs             |   6 
crates/gpui/src/elements/constrained_box.rs     |  11 
crates/gpui/src/elements/container.rs           |   4 
crates/gpui/src/elements/empty.rs               |   4 
crates/gpui/src/elements/expanded.rs            |   4 
crates/gpui/src/elements/flex.rs                |  12 
crates/gpui/src/elements/hook.rs                |   4 
crates/gpui/src/elements/image.rs               |   5 
crates/gpui/src/elements/keystroke_label.rs     |   2 
crates/gpui/src/elements/label.rs               |   4 
crates/gpui/src/elements/list.rs                |  45 +++++-
crates/gpui/src/elements/mouse_event_handler.rs |   6 
crates/gpui/src/elements/overlay.rs             |   5 
crates/gpui/src/elements/resizable.rs           |   5 
crates/gpui/src/elements/stack.rs               |   4 
crates/gpui/src/elements/svg.rs                 |   4 
crates/gpui/src/elements/text.rs                |  16 +
crates/gpui/src/elements/tooltip.rs             |   5 
crates/gpui/src/elements/uniform_list.rs        |   4 
crates/terminal_view/src/terminal_element.rs    |   6 
crates/workspace/src/pane.rs                    |   6 
crates/workspace/src/status_bar.rs              |   6 
30 files changed, 289 insertions(+), 136 deletions(-)

Detailed changes

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -14,8 +14,8 @@ use gpui::{
     geometry::{rect::RectF, vector::vec2f, PathBuilder},
     json::{self, ToJson},
     platform::{CursorStyle, MouseButton},
-    AppContext, Entity, ImageData, ModelHandle, SceneBuilder, Subscription, View, ViewContext,
-    ViewHandle, WeakViewHandle,
+    AppContext, Entity, ImageData, LayoutContext, ModelHandle, SceneBuilder, Subscription, View,
+    ViewContext, ViewHandle, WeakViewHandle,
 };
 use project::Project;
 use settings::Settings;
@@ -865,7 +865,7 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
         &mut self,
         constraint: gpui::SizeConstraint,
         _: &mut CollabTitlebarItem,
-        _: &mut ViewContext<CollabTitlebarItem>,
+        _: &mut LayoutContext<CollabTitlebarItem>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         (constraint.max, ())
     }

crates/collab_ui/src/face_pile.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
     },
     json::ToJson,
     serde_json::{self, json},
-    AnyElement, Axis, Element, SceneBuilder, ViewContext,
+    AnyElement, Axis, Element, LayoutContext, SceneBuilder, ViewContext,
 };
 
 use crate::CollabTitlebarItem;
@@ -34,7 +34,7 @@ impl Element<CollabTitlebarItem> for FacePile {
         &mut self,
         constraint: gpui::SizeConstraint,
         view: &mut CollabTitlebarItem,
-        cx: &mut ViewContext<CollabTitlebarItem>,
+        cx: &mut LayoutContext<CollabTitlebarItem>,
     ) -> (Vector2F, Self::LayoutState) {
         debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
 

crates/editor/src/element.rs 🔗

@@ -30,8 +30,8 @@ use gpui::{
     json::{self, ToJson},
     platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
     text_layout::{self, Line, RunStyle, TextLayoutCache},
-    AnyElement, Axis, Border, CursorRegion, Element, EventContext, MouseRegion, Quad, SceneBuilder,
-    SizeConstraint, ViewContext, WindowContext,
+    AnyElement, Axis, Border, CursorRegion, Element, EventContext, LayoutContext, MouseRegion,
+    Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
 };
 use itertools::Itertools;
 use json::json;
@@ -1388,7 +1388,7 @@ impl EditorElement {
         line_layouts: &[text_layout::Line],
         include_root: bool,
         editor: &mut Editor,
-        cx: &mut ViewContext<Editor>,
+        cx: &mut LayoutContext<Editor>,
     ) -> (f32, Vec<BlockLayout>) {
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
         let scroll_x = snapshot.scroll_anchor.offset.x();
@@ -1594,7 +1594,7 @@ impl Element<Editor> for EditorElement {
         &mut self,
         constraint: SizeConstraint,
         editor: &mut Editor,
-        cx: &mut ViewContext<Editor>,
+        cx: &mut LayoutContext<Editor>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size = constraint.max;
         if size.x().is_infinite() {
@@ -2565,10 +2565,18 @@ mod tests {
 
         let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
         let (size, mut state) = editor.update(cx, |editor, cx| {
+            let mut new_parents = Default::default();
+            let mut notify_views_if_parents_change = Default::default();
+            let mut layout_cx = LayoutContext::new(
+                cx,
+                &mut new_parents,
+                &mut notify_views_if_parents_change,
+                false,
+            );
             element.layout(
                 SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
                 editor,
-                cx,
+                &mut layout_cx,
             )
         });
 

crates/gpui/src/app.rs 🔗

@@ -1361,9 +1361,9 @@ impl AppContext {
         }));
 
         let mut window = Window::new(window_id, platform_window, self, build_root_view);
-        let scene = WindowContext::mutable(self, &mut window, window_id)
-            .build_scene()
-            .expect("initial scene should not error");
+        let mut cx = WindowContext::mutable(self, &mut window, window_id);
+        cx.layout(false).expect("initial layout should not error");
+        let scene = cx.paint().expect("initial paint should not error");
         window.platform_window.present_scene(scene);
         window
     }
@@ -1486,6 +1486,7 @@ impl AppContext {
             self.flushing_effects = true;
 
             let mut refreshing = false;
+            let mut updated_windows = HashSet::default();
             loop {
                 if let Some(effect) = self.pending_effects.pop_front() {
                     match effect {
@@ -1664,10 +1665,27 @@ impl AppContext {
                 } else {
                     self.remove_dropped_entities();
 
-                    if refreshing {
-                        self.perform_window_refresh();
-                    } else {
-                        self.update_windows();
+                    for window_id in self.windows.keys().cloned().collect::<Vec<_>>() {
+                        self.update_window(window_id, |cx| {
+                            let invalidation = if refreshing {
+                                let mut invalidation =
+                                    cx.window.invalidation.take().unwrap_or_default();
+                                invalidation
+                                    .updated
+                                    .extend(cx.window.rendered_views.keys().copied());
+                                Some(invalidation)
+                            } else {
+                                cx.window.invalidation.take()
+                            };
+
+                            if let Some(invalidation) = invalidation {
+                                let appearance = cx.window.platform_window.appearance();
+                                cx.invalidate(invalidation, appearance);
+                                if cx.layout(refreshing).log_err().is_some() {
+                                    updated_windows.insert(window_id);
+                                }
+                            }
+                        });
                     }
 
                     if self.pending_effects.is_empty() {
@@ -1675,6 +1693,14 @@ impl AppContext {
                             callback(self);
                         }
 
+                        for window_id in updated_windows.drain() {
+                            self.update_window(window_id, |cx| {
+                                if let Some(scene) = cx.paint().log_err() {
+                                    cx.window.platform_window.present_scene(scene);
+                                }
+                            });
+                        }
+
                         if self.pending_effects.is_empty() {
                             self.flushing_effects = false;
                             self.pending_notifications.clear();
@@ -1689,21 +1715,6 @@ impl AppContext {
         }
     }
 
-    fn update_windows(&mut self) {
-        let window_ids = self.windows.keys().cloned().collect::<Vec<_>>();
-        for window_id in window_ids {
-            self.update_window(window_id, |cx| {
-                if let Some(mut invalidation) = cx.window.invalidation.take() {
-                    let appearance = cx.window.platform_window.appearance();
-                    cx.invalidate(&mut invalidation, appearance);
-                    if let Some(scene) = cx.build_scene().log_err() {
-                        cx.window.platform_window.present_scene(scene);
-                    }
-                }
-            });
-        }
-    }
-
     fn window_was_resized(&mut self, window_id: usize) {
         self.pending_effects
             .push_back(Effect::ResizeWindow { window_id });
@@ -1747,23 +1758,6 @@ impl AppContext {
         self.pending_effects.push_back(Effect::RefreshWindows);
     }
 
-    fn perform_window_refresh(&mut self) {
-        let window_ids = self.windows.keys().cloned().collect::<Vec<_>>();
-        for window_id in window_ids {
-            self.update_window(window_id, |cx| {
-                let mut invalidation = cx.window.invalidation.take().unwrap_or_default();
-                invalidation
-                    .updated
-                    .extend(cx.window.rendered_views.keys().copied());
-                cx.invalidate(&mut invalidation, cx.window.platform_window.appearance());
-                cx.refreshing = true;
-                if let Some(scene) = cx.build_scene().log_err() {
-                    cx.window.platform_window.present_scene(scene);
-                }
-            });
-        }
-    }
-
     fn emit_global_event(&mut self, payload: Box<dyn Any>) {
         let type_id = (&*payload).type_id();
 
@@ -3255,6 +3249,67 @@ impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
     }
 }
 
+pub struct LayoutContext<'a, 'b, 'c, V: View> {
+    view_context: &'c mut ViewContext<'a, 'b, V>,
+    new_parents: &'c mut HashMap<usize, usize>,
+    views_to_notify_if_ancestors_change: &'c mut HashSet<usize>,
+    pub refreshing: bool,
+}
+
+impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
+    pub fn new(
+        view_context: &'c mut ViewContext<'a, 'b, V>,
+        new_parents: &'c mut HashMap<usize, usize>,
+        views_to_notify_if_ancestors_change: &'c mut HashSet<usize>,
+        refreshing: bool,
+    ) -> Self {
+        Self {
+            view_context,
+            new_parents,
+            views_to_notify_if_ancestors_change,
+            refreshing,
+        }
+    }
+
+    pub fn view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
+        self.view_context
+    }
+}
+
+impl<'a, 'b, 'c, V: View> Deref for LayoutContext<'a, 'b, 'c, V> {
+    type Target = ViewContext<'a, 'b, V>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.view_context
+    }
+}
+
+impl<V: View> DerefMut for LayoutContext<'_, '_, '_, V> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.view_context
+    }
+}
+
+impl<V: View> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
+    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
+        BorrowAppContext::read_with(&*self.view_context, f)
+    }
+
+    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
+        BorrowAppContext::update(&mut *self.view_context, f)
+    }
+}
+
+impl<V: View> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
+    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
+        BorrowWindowContext::read_with(&*self.view_context, window_id, f)
+    }
+
+    fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
+        BorrowWindowContext::update(&mut *self.view_context, window_id, f)
+    }
+}
+
 pub struct EventContext<'a, 'b, 'c, V: View> {
     view_context: &'c mut ViewContext<'a, 'b, V>,
     pub(crate) handled: bool,

crates/gpui/src/app/window.rs 🔗

@@ -14,8 +14,8 @@ use crate::{
     text_layout::TextLayoutCache,
     util::post_inc,
     Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect,
-    Element, Entity, Handle, MouseRegion, MouseRegionId, ParentId, SceneBuilder, Subscription,
-    View, ViewContext, ViewHandle, WindowInvalidation,
+    Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, ParentId, SceneBuilder,
+    Subscription, View, ViewContext, ViewHandle, WindowInvalidation,
 };
 use anyhow::{anyhow, bail, Result};
 use collections::{HashMap, HashSet};
@@ -93,8 +93,8 @@ impl Window {
         let root_view = window_context
             .build_and_insert_view(ParentId::Root, |cx| Some(build_view(cx)))
             .unwrap();
-        if let Some(mut invalidation) = window_context.window.invalidation.take() {
-            window_context.invalidate(&mut invalidation, appearance);
+        if let Some(invalidation) = window_context.window.invalidation.take() {
+            window_context.invalidate(invalidation, appearance);
         }
         window.focused_view_id = Some(root_view.id());
         window.root_view = Some(root_view.into_any());
@@ -113,7 +113,6 @@ pub struct WindowContext<'a> {
     pub(crate) app_context: Reference<'a, AppContext>,
     pub(crate) window: Reference<'a, Window>,
     pub(crate) window_id: usize,
-    pub(crate) refreshing: bool,
     pub(crate) removed: bool,
 }
 
@@ -169,7 +168,6 @@ impl<'a> WindowContext<'a> {
             app_context: Reference::Mutable(app_context),
             window: Reference::Mutable(window),
             window_id,
-            refreshing: false,
             removed: false,
         }
     }
@@ -179,7 +177,6 @@ impl<'a> WindowContext<'a> {
             app_context: Reference::Immutable(app_context),
             window: Reference::Immutable(window),
             window_id,
-            refreshing: false,
             removed: false,
         }
     }
@@ -891,7 +888,7 @@ impl<'a> WindowContext<'a> {
         false
     }
 
-    pub fn invalidate(&mut self, invalidation: &mut WindowInvalidation, appearance: Appearance) {
+    pub fn invalidate(&mut self, mut invalidation: WindowInvalidation, appearance: Appearance) {
         self.start_frame();
         self.window.appearance = appearance;
         for view_id in &invalidation.removed {
@@ -932,13 +929,32 @@ impl<'a> WindowContext<'a> {
         Ok(element)
     }
 
-    pub fn build_scene(&mut self) -> Result<Scene> {
+    pub(crate) fn layout(&mut self, refreshing: bool) -> Result<()> {
+        let window_size = self.window.platform_window.content_size();
+        let root_view_id = self.window.root_view().id();
+        let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
+        let mut new_parents = HashMap::default();
+        let mut views_to_notify_if_ancestors_change = HashSet::default();
+        rendered_root.layout(
+            SizeConstraint::strict(window_size),
+            &mut new_parents,
+            &mut views_to_notify_if_ancestors_change,
+            refreshing,
+            self,
+        )?;
+
+        self.window
+            .rendered_views
+            .insert(root_view_id, rendered_root);
+        Ok(())
+    }
+
+    pub(crate) fn paint(&mut self) -> Result<Scene> {
         let window_size = self.window.platform_window.content_size();
         let scale_factor = self.window.platform_window.scale_factor();
 
         let root_view_id = self.window.root_view().id();
         let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
-        rendered_root.layout(SizeConstraint::strict(window_size), self)?;
 
         let mut scene_builder = SceneBuilder::new(scale_factor);
         rendered_root.paint(
@@ -1366,11 +1382,17 @@ impl<V: View> Element<V> for ChildView {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
             let size = rendered_view
-                .layout(constraint, cx)
+                .layout(
+                    constraint,
+                    cx.new_parents,
+                    cx.views_to_notify_if_ancestors_change,
+                    cx.refreshing,
+                    cx.view_context,
+                )
                 .log_err()
                 .unwrap_or(Vector2F::zero());
             cx.window.rendered_views.insert(self.view_id, rendered_view);

crates/gpui/src/elements.rs 🔗

@@ -33,9 +33,11 @@ use crate::{
         rect::RectF,
         vector::{vec2f, Vector2F},
     },
-    json, Action, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle, WindowContext,
+    json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle,
+    WindowContext,
 };
 use anyhow::{anyhow, Result};
+use collections::{HashMap, HashSet};
 use core::panic;
 use json::ToJson;
 use std::{
@@ -54,7 +56,7 @@ pub trait Element<V: View>: 'static {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState);
 
     fn paint(
@@ -211,7 +213,7 @@ trait AnyElementState<V: View> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> Vector2F;
 
     fn paint(
@@ -263,7 +265,7 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> Vector2F {
         let result;
         *self = match mem::take(self) {
@@ -444,7 +446,7 @@ impl<V: View> AnyElement<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> Vector2F {
         self.state.layout(constraint, view, cx)
     }
@@ -505,7 +507,7 @@ impl<V: View> Element<V> for AnyElement<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.layout(constraint, view, cx);
         (size, ())
@@ -597,7 +599,7 @@ impl<V: View, C: Component<V>> Element<V> for ComponentHost<V, C> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, AnyElement<V>) {
         let mut element = self.component.render(view, cx);
         let size = element.layout(constraint, view, cx);
@@ -642,7 +644,14 @@ impl<V: View, C: Component<V>> Element<V> for ComponentHost<V, C> {
 }
 
 pub trait AnyRootElement {
-    fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F>;
+    fn layout(
+        &mut self,
+        constraint: SizeConstraint,
+        new_parents: &mut HashMap<usize, usize>,
+        views_to_notify_if_ancestors_change: &mut HashSet<usize>,
+        refreshing: bool,
+        cx: &mut WindowContext,
+    ) -> Result<Vector2F>;
     fn paint(
         &mut self,
         scene: &mut SceneBuilder,
@@ -660,12 +669,27 @@ pub trait AnyRootElement {
 }
 
 impl<V: View> AnyRootElement for RootElement<V> {
-    fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F> {
+    fn layout(
+        &mut self,
+        constraint: SizeConstraint,
+        new_parents: &mut HashMap<usize, usize>,
+        views_to_notify_if_ancestors_change: &mut HashSet<usize>,
+        refreshing: bool,
+        cx: &mut WindowContext,
+    ) -> Result<Vector2F> {
         let view = self
             .view
             .upgrade(cx)
             .ok_or_else(|| anyhow!("layout called on a root element for a dropped view"))?;
-        view.update(cx, |view, cx| Ok(self.element.layout(constraint, view, cx)))
+        view.update(cx, |view, cx| {
+            let mut cx = LayoutContext::new(
+                cx,
+                new_parents,
+                views_to_notify_if_ancestors_change,
+                refreshing,
+            );
+            Ok(self.element.layout(constraint, view, &mut cx))
+        })
     }
 
     fn paint(

crates/gpui/src/elements/align.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
-    json, AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+    json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 use json::ToJson;
 
@@ -48,7 +48,7 @@ impl<V: View> Element<V> for Align<V> {
         &mut self,
         mut constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size = constraint.max;
         constraint.min = Vector2F::zero();

crates/gpui/src/elements/canvas.rs 🔗

@@ -34,7 +34,7 @@ where
         &mut self,
         constraint: crate::SizeConstraint,
         _: &mut V,
-        _: &mut crate::ViewContext<V>,
+        _: &mut crate::LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let x = if constraint.max.x().is_finite() {
             constraint.max.x()

crates/gpui/src/elements/clipped.rs 🔗

@@ -3,7 +3,9 @@ use std::ops::Range;
 use pathfinder_geometry::{rect::RectF, vector::Vector2F};
 use serde_json::json;
 
-use crate::{json, AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext};
+use crate::{
+    json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+};
 
 pub struct Clipped<V: View> {
     child: AnyElement<V>,
@@ -23,7 +25,7 @@ impl<V: View> Element<V> for Clipped<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         (self.child.layout(constraint, view, cx), ())
     }

crates/gpui/src/elements/constrained_box.rs 🔗

@@ -5,7 +5,7 @@ use serde_json::json;
 
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
-    json, AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+    json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 
 pub struct ConstrainedBox<V: View> {
@@ -15,7 +15,7 @@ pub struct ConstrainedBox<V: View> {
 
 pub enum Constraint<V: View> {
     Static(SizeConstraint),
-    Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint>),
+    Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint>),
 }
 
 impl<V: View> ToJson for Constraint<V> {
@@ -37,7 +37,8 @@ impl<V: View> ConstrainedBox<V> {
 
     pub fn dynamically(
         mut self,
-        constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint,
+        constraint: impl 'static
+            + FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint,
     ) -> Self {
         self.constraint = Constraint::Dynamic(Box::new(constraint));
         self
@@ -119,7 +120,7 @@ impl<V: View> ConstrainedBox<V> {
         &mut self,
         input_constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> SizeConstraint {
         match &mut self.constraint {
             Constraint::Static(constraint) => *constraint,
@@ -138,7 +139,7 @@ impl<V: View> Element<V> for ConstrainedBox<V> {
         &mut self,
         mut parent_constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let constraint = self.constraint(parent_constraint, view, cx);
         parent_constraint.min = parent_constraint.min.max(constraint.min);

crates/gpui/src/elements/container.rs 🔗

@@ -10,7 +10,7 @@ use crate::{
     json::ToJson,
     platform::CursorStyle,
     scene::{self, Border, CursorRegion, Quad},
-    AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+    AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 use serde::Deserialize;
 use serde_json::json;
@@ -192,7 +192,7 @@ impl<V: View> Element<V> for Container<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size_buffer = self.margin_size() + self.padding_size();
         if !self.style.border.overlay {

crates/gpui/src/elements/empty.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    SceneBuilder, View, ViewContext,
+    LayoutContext, SceneBuilder, View, ViewContext,
 };
 use crate::{Element, SizeConstraint};
 
@@ -34,7 +34,7 @@ impl<V: View> Element<V> for Empty {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        _: &mut ViewContext<V>,
+        _: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let x = if constraint.max.x().is_finite() && !self.collapsed {
             constraint.max.x()

crates/gpui/src/elements/expanded.rs 🔗

@@ -2,7 +2,7 @@ use std::ops::Range;
 
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
-    json, AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+    json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 use serde_json::json;
 
@@ -42,7 +42,7 @@ impl<V: View> Element<V> for Expanded<V> {
         &mut self,
         mut constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         if self.full_width {
             constraint.min.set_x(constraint.max.x());

crates/gpui/src/elements/flex.rs 🔗

@@ -2,8 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
 
 use crate::{
     json::{self, ToJson, Value},
-    AnyElement, Axis, Element, ElementStateHandle, SceneBuilder, SizeConstraint, Vector2FExt, View,
-    ViewContext,
+    AnyElement, Axis, Element, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint,
+    Vector2FExt, View, ViewContext,
 };
 use pathfinder_geometry::{
     rect::RectF,
@@ -74,7 +74,7 @@ impl<V: View> Flex<V> {
         remaining_flex: &mut f32,
         cross_axis_max: &mut f32,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) {
         let cross_axis = self.axis.invert();
         for child in &mut self.children {
@@ -125,7 +125,7 @@ impl<V: View> Element<V> for Flex<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut total_flex = None;
         let mut fixed_space = 0.0;
@@ -214,7 +214,7 @@ impl<V: View> Element<V> for Flex<V> {
         }
 
         if let Some(scroll_state) = self.scroll_state.as_ref() {
-            scroll_state.0.update(cx, |scroll_state, _| {
+            scroll_state.0.update(cx.view_context(), |scroll_state, _| {
                 if let Some(scroll_to) = scroll_state.scroll_to.take() {
                     let visible_start = scroll_state.scroll_position.get();
                     let visible_end = visible_start + size.along(self.axis);
@@ -432,7 +432,7 @@ impl<V: View> Element<V> for FlexItem<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.child.layout(constraint, view, cx);
         (size, ())

crates/gpui/src/elements/hook.rs 🔗

@@ -3,7 +3,7 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::json,
-    AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+    AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 
 pub struct Hook<V: View> {
@@ -36,7 +36,7 @@ impl<V: View> Element<V> for Hook<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.child.layout(constraint, view, cx);
         if let Some(handler) = self.after_layout.as_mut() {

crates/gpui/src/elements/image.rs 🔗

@@ -5,7 +5,8 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    scene, Border, Element, ImageData, SceneBuilder, SizeConstraint, View, ViewContext,
+    scene, Border, Element, ImageData, LayoutContext, SceneBuilder, SizeConstraint, View,
+    ViewContext,
 };
 use serde::Deserialize;
 use std::{ops::Range, sync::Arc};
@@ -63,7 +64,7 @@ impl<V: View> Element<V> for Image {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let data = match &self.source {
             ImageSource::Path(path) => match cx.asset_cache.png(path) {

crates/gpui/src/elements/keystroke_label.rs 🔗

@@ -39,7 +39,7 @@ impl<V: View> Element<V> for KeystrokeLabel {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, AnyElement<V>) {
         let mut element = if let Some(keystrokes) =
             cx.keystrokes_for_action(self.view_id, self.action.as_ref())

crates/gpui/src/elements/label.rs 🔗

@@ -8,7 +8,7 @@ use crate::{
     },
     json::{ToJson, Value},
     text_layout::{Line, RunStyle},
-    Element, SceneBuilder, SizeConstraint, View, ViewContext,
+    Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 use serde::Deserialize;
 use serde_json::json;
@@ -135,7 +135,7 @@ impl<V: View> Element<V> for Label {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let runs = self.compute_runs();
         let line = cx.text_layout_cache().layout_str(

crates/gpui/src/elements/list.rs 🔗

@@ -4,7 +4,8 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::json,
-    AnyElement, Element, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
+    AnyElement, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
+    ViewContext,
 };
 use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
 use sum_tree::{Bias, SumTree};
@@ -99,7 +100,7 @@ impl<V: View> Element<V> for List<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let state = &mut *self.state.0.borrow_mut();
         let size = constraint.max;
@@ -452,7 +453,7 @@ impl<V: View> StateInner<V> {
         existing_element: Option<&ListItem<V>>,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> Option<Rc<RefCell<AnyElement<V>>>> {
         if let Some(ListItem::Rendered(element)) = existing_element {
             Some(element.clone())
@@ -665,7 +666,15 @@ mod tests {
             });
 
             let mut list = List::new(state.clone());
-            let (size, _) = list.layout(constraint, &mut view, cx);
+            let mut new_parents = Default::default();
+            let mut notify_views_if_parents_change = Default::default();
+            let mut layout_cx = LayoutContext::new(
+                cx,
+                &mut new_parents,
+                &mut notify_views_if_parents_change,
+                false,
+            );
+            let (size, _) = list.layout(constraint, &mut view, &mut layout_cx);
             assert_eq!(size, vec2f(100., 40.));
             assert_eq!(
                 state.0.borrow().items.summary().clone(),
@@ -689,7 +698,13 @@ mod tests {
                 cx,
             );
 
-            let (_, logical_scroll_top) = list.layout(constraint, &mut view, cx);
+            let mut layout_cx = LayoutContext::new(
+                cx,
+                &mut new_parents,
+                &mut notify_views_if_parents_change,
+                false,
+            );
+            let (_, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
             assert_eq!(
                 logical_scroll_top,
                 ListOffset {
@@ -713,7 +728,13 @@ mod tests {
                 }
             );
 
-            let (size, logical_scroll_top) = list.layout(constraint, &mut view, cx);
+            let mut layout_cx = LayoutContext::new(
+                cx,
+                &mut new_parents,
+                &mut notify_views_if_parents_change,
+                false,
+            );
+            let (size, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
             assert_eq!(size, vec2f(100., 40.));
             assert_eq!(
                 state.0.borrow().items.summary().clone(),
@@ -831,10 +852,18 @@ mod tests {
 
                 let mut list = List::new(state.clone());
                 let window_size = vec2f(width, height);
+                let mut new_parents = Default::default();
+                let mut notify_views_if_parents_change = Default::default();
+                let mut layout_cx = LayoutContext::new(
+                    cx,
+                    &mut new_parents,
+                    &mut notify_views_if_parents_change,
+                    false,
+                );
                 let (size, logical_scroll_top) = list.layout(
                     SizeConstraint::new(vec2f(0., 0.), window_size),
                     &mut view,
-                    cx,
+                    &mut layout_cx,
                 );
                 assert_eq!(size, window_size);
                 last_logical_scroll_top = Some(logical_scroll_top);
@@ -947,7 +976,7 @@ mod tests {
             &mut self,
             _: SizeConstraint,
             _: &mut V,
-            _: &mut ViewContext<V>,
+            _: &mut LayoutContext<V>,
         ) -> (Vector2F, ()) {
             (self.size, ())
         }

crates/gpui/src/elements/mouse_event_handler.rs 🔗

@@ -10,8 +10,8 @@ use crate::{
         CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover,
         MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
     },
-    AnyElement, Element, EventContext, MouseRegion, MouseState, SceneBuilder, SizeConstraint, View,
-    ViewContext,
+    AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, SceneBuilder,
+    SizeConstraint, View, ViewContext,
 };
 use serde_json::json;
 use std::{marker::PhantomData, ops::Range};
@@ -220,7 +220,7 @@ impl<Tag, V: View> Element<V> for MouseEventHandler<Tag, V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         (self.child.layout(constraint, view, cx), ())
     }

crates/gpui/src/elements/overlay.rs 🔗

@@ -3,7 +3,8 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::ToJson,
-    AnyElement, Axis, Element, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
+    AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
+    ViewContext,
 };
 use serde_json::json;
 
@@ -124,7 +125,7 @@ impl<V: View> Element<V> for Overlay<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let constraint = if self.anchor_position.is_some() {
             SizeConstraint::new(Vector2F::zero(), cx.window_size())

crates/gpui/src/elements/resizable.rs 🔗

@@ -7,7 +7,8 @@ use crate::{
     geometry::rect::RectF,
     platform::{CursorStyle, MouseButton},
     scene::MouseDrag,
-    AnyElement, Axis, Element, ElementStateHandle, MouseRegion, SceneBuilder, View, ViewContext,
+    AnyElement, Axis, Element, ElementStateHandle, LayoutContext, MouseRegion, SceneBuilder, View,
+    ViewContext,
 };
 
 use super::{ConstrainedBox, Hook};
@@ -139,7 +140,7 @@ impl<V: View> Element<V> for Resizable<V> {
         &mut self,
         constraint: crate::SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         (self.child.layout(constraint, view, cx), ())
     }

crates/gpui/src/elements/stack.rs 🔗

@@ -3,7 +3,7 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::{self, json, ToJson},
-    AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+    AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 
 /// Element which renders it's children in a stack on top of each other.
@@ -34,7 +34,7 @@ impl<V: View> Element<V> for Stack<V> {
         &mut self,
         mut constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size = constraint.min;
         let mut children = self.children.iter_mut();

crates/gpui/src/elements/svg.rs 🔗

@@ -8,7 +8,7 @@ use crate::{
         rect::RectF,
         vector::{vec2f, Vector2F},
     },
-    scene, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+    scene, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 
 pub struct Svg {
@@ -38,7 +38,7 @@ impl<V: View> Element<V> for Svg {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         match cx.asset_cache.svg(&self.path) {
             Ok(tree) => {

crates/gpui/src/elements/text.rs 🔗

@@ -7,8 +7,8 @@ use crate::{
     },
     json::{ToJson, Value},
     text_layout::{Line, RunStyle, ShapedBoundary},
-    AppContext, Element, FontCache, SceneBuilder, SizeConstraint, TextLayoutCache, View,
-    ViewContext,
+    AppContext, Element, FontCache, LayoutContext, SceneBuilder, SizeConstraint, TextLayoutCache,
+    View, ViewContext,
 };
 use log::warn;
 use serde_json::json;
@@ -78,7 +78,7 @@ impl<V: View> Element<V> for Text {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         // Convert the string and highlight ranges into an iterator of highlighted chunks.
 
@@ -411,10 +411,18 @@ mod tests {
             let mut view = TestView;
             fonts::with_font_cache(cx.font_cache().clone(), || {
                 let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
+                let mut new_parents = Default::default();
+                let mut notify_views_if_parents_change = Default::default();
+                let mut layout_cx = LayoutContext::new(
+                    cx,
+                    &mut new_parents,
+                    &mut notify_views_if_parents_change,
+                    false,
+                );
                 let (_, state) = text.layout(
                     SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
                     &mut view,
-                    cx,
+                    &mut layout_cx,
                 );
                 assert_eq!(state.shaped_lines.len(), 2);
                 assert_eq!(state.wrap_boundaries.len(), 2);

crates/gpui/src/elements/tooltip.rs 🔗

@@ -6,7 +6,8 @@ use crate::{
     fonts::TextStyle,
     geometry::{rect::RectF, vector::Vector2F},
     json::json,
-    Action, Axis, ElementStateHandle, SceneBuilder, SizeConstraint, Task, View, ViewContext,
+    Action, Axis, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint, Task, View,
+    ViewContext,
 };
 use serde::Deserialize;
 use std::{
@@ -172,7 +173,7 @@ impl<V: View> Element<V> for Tooltip<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.child.layout(constraint, view, cx);
         if let Some(tooltip) = self.tooltip.as_mut() {

crates/gpui/src/elements/uniform_list.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
     },
     json::{self, json},
     platform::ScrollWheelEvent,
-    AnyElement, MouseRegion, SceneBuilder, View, ViewContext,
+    AnyElement, LayoutContext, MouseRegion, SceneBuilder, View, ViewContext,
 };
 use json::ToJson;
 use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -159,7 +159,7 @@ impl<V: View> Element<V> for UniformList<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         if constraint.max.y().is_infinite() {
             unimplemented!(

crates/terminal_view/src/terminal_element.rs 🔗

@@ -10,8 +10,8 @@ use gpui::{
     platform::{CursorStyle, MouseButton},
     serde_json::json,
     text_layout::{Line, RunStyle},
-    AnyElement, Element, EventContext, FontCache, ModelContext, MouseRegion, Quad, SceneBuilder,
-    SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle,
+    AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion, Quad,
+    SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle,
 };
 use itertools::Itertools;
 use language::CursorShape;
@@ -561,7 +561,7 @@ impl Element<TerminalView> for TerminalElement {
         &mut self,
         constraint: gpui::SizeConstraint,
         view: &mut TerminalView,
-        cx: &mut ViewContext<TerminalView>,
+        cx: &mut LayoutContext<TerminalView>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         let settings = cx.global::<Settings>();
         let font_cache = cx.font_cache();

crates/workspace/src/pane.rs 🔗

@@ -24,8 +24,8 @@ use gpui::{
     keymap_matcher::KeymapContext,
     platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
     Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
-    ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle,
-    WindowContext,
+    LayoutContext, ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle,
+    WeakViewHandle, WindowContext,
 };
 use project::{Project, ProjectEntryId, ProjectPath};
 use serde::Deserialize;
@@ -1999,7 +1999,7 @@ impl<V: View> Element<V> for PaneBackdrop<V> {
         &mut self,
         constraint: gpui::SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut LayoutContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.child.layout(constraint, view, cx);
         (size, ())

crates/workspace/src/status_bar.rs 🔗

@@ -8,8 +8,8 @@ use gpui::{
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    AnyElement, AnyViewHandle, Entity, SceneBuilder, SizeConstraint, Subscription, View,
-    ViewContext, ViewHandle, WindowContext,
+    AnyElement, AnyViewHandle, Entity, LayoutContext, SceneBuilder, SizeConstraint, Subscription,
+    View, ViewContext, ViewHandle, WindowContext,
 };
 use settings::Settings;
 
@@ -157,7 +157,7 @@ impl Element<StatusBar> for StatusBarElement {
         &mut self,
         mut constraint: SizeConstraint,
         view: &mut StatusBar,
-        cx: &mut ViewContext<StatusBar>,
+        cx: &mut LayoutContext<StatusBar>,
     ) -> (Vector2F, Self::LayoutState) {
         let max_width = constraint.max.x();
         constraint.min = vec2f(0., constraint.min.y());