Merge pull request #2441 from zed-industries/implicit-ancestry

Antonio Scandurra created

Determine view ancestry during layout

Change summary

crates/collab/src/tests.rs                       |  42 +
crates/collab/src/tests/integration_tests.rs     |  30 
crates/collab_ui/src/collab_titlebar_item.rs     |   9 
crates/collab_ui/src/face_pile.rs                |   4 
crates/command_palette/src/command_palette.rs    |  98 +-
crates/context_menu/src/context_menu.rs          |   4 
crates/copilot_button/src/copilot_button.rs      |   3 
crates/diagnostics/src/diagnostics.rs            |   8 
crates/editor/src/editor.rs                      |   4 
crates/editor/src/editor_tests.rs                |   4 
crates/editor/src/element.rs                     |  18 
crates/editor/src/items.rs                       |  11 
crates/gpui/src/app.rs                           | 644 +++++++++++------
crates/gpui/src/app/menu.rs                      |   2 
crates/gpui/src/app/test_app_context.rs          |  39 
crates/gpui/src/app/window.rs                    | 180 ++--
crates/gpui/src/elements.rs                      |  45 
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/gpui/src/keymap_matcher/binding.rs        |  10 
crates/gpui/src/keymap_matcher/keymap_context.rs |   2 
crates/project_panel/src/project_panel.rs        |   3 
crates/project_symbols/src/project_symbols.rs    |   4 
crates/search/src/buffer_search.rs               |   8 
crates/search/src/project_search.rs              |   4 
crates/settings/src/settings_file.rs             |  54 
crates/terminal_view/src/terminal_button.rs      |   3 
crates/terminal_view/src/terminal_element.rs     |  12 
crates/terminal_view/src/terminal_view.rs        |  20 
crates/workspace/src/dock.rs                     |   8 
crates/workspace/src/pane.rs                     |  26 
crates/workspace/src/shared_screen.rs            |   2 
crates/workspace/src/sidebar.rs                  |   2 
crates/workspace/src/status_bar.rs               |   8 
crates/workspace/src/workspace.rs                | 136 +--
54 files changed, 923 insertions(+), 680 deletions(-)

Detailed changes

crates/collab/src/tests.rs 🔗

@@ -12,7 +12,10 @@ use client::{
 use collections::{HashMap, HashSet};
 use fs::FakeFs;
 use futures::{channel::oneshot, StreamExt as _};
-use gpui::{executor::Deterministic, test::EmptyView, ModelHandle, TestAppContext, ViewHandle};
+use gpui::{
+    elements::*, executor::Deterministic, AnyElement, Entity, ModelHandle, TestAppContext, View,
+    ViewContext, ViewHandle, WeakViewHandle,
+};
 use language::LanguageRegistry;
 use parking_lot::Mutex;
 use project::{Project, WorktreeId};
@@ -462,8 +465,41 @@ impl TestClient {
         project: &ModelHandle<Project>,
         cx: &mut TestAppContext,
     ) -> ViewHandle<Workspace> {
-        let (_, root_view) = cx.add_window(|_| EmptyView);
-        cx.add_view(&root_view, |cx| Workspace::test_new(project.clone(), cx))
+        struct WorkspaceContainer {
+            workspace: Option<WeakViewHandle<Workspace>>,
+        }
+
+        impl Entity for WorkspaceContainer {
+            type Event = ();
+        }
+
+        impl View for WorkspaceContainer {
+            fn ui_name() -> &'static str {
+                "WorkspaceContainer"
+            }
+
+            fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+                if let Some(workspace) = self
+                    .workspace
+                    .as_ref()
+                    .and_then(|workspace| workspace.upgrade(cx))
+                {
+                    ChildView::new(&workspace, cx).into_any()
+                } else {
+                    Empty::new().into_any()
+                }
+            }
+        }
+
+        // We use a workspace container so that we don't need to remove the window in order to
+        // drop the workspace and we can use a ViewHandle instead.
+        let (window_id, container) = cx.add_window(|_| WorkspaceContainer { workspace: None });
+        let workspace = cx.add_view(window_id, |cx| Workspace::test_new(project.clone(), cx));
+        container.update(cx, |container, cx| {
+            container.workspace = Some(workspace.downgrade());
+            cx.notify();
+        });
+        workspace
     }
 }
 

crates/collab/src/tests/integration_tests.rs 🔗

@@ -1202,7 +1202,7 @@ async fn test_share_project(
     cx_c: &mut TestAppContext,
 ) {
     deterministic.forbid_parking();
-    let (_, window_b) = cx_b.add_window(|_| EmptyView);
+    let (window_b, _) = cx_b.add_window(|_| EmptyView);
     let mut server = TestServer::start(&deterministic).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
@@ -1289,7 +1289,7 @@ async fn test_share_project(
         .await
         .unwrap();
 
-    let editor_b = cx_b.add_view(&window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
+    let editor_b = cx_b.add_view(window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
 
     // Client A sees client B's selection
     deterministic.run_until_parked();
@@ -3076,13 +3076,13 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
         .await
         .unwrap();
-    let (_, window_a) = cx_a.add_window(|_| EmptyView);
-    let editor_a = cx_a.add_view(&window_a, |cx| {
+    let (window_a, _) = cx_a.add_window(|_| EmptyView);
+    let editor_a = cx_a.add_view(window_a, |cx| {
         Editor::for_buffer(buffer_a, Some(project_a), cx)
     });
     let mut editor_cx_a = EditorTestContext {
         cx: cx_a,
-        window_id: window_a.id(),
+        window_id: window_a,
         editor: editor_a,
     };
 
@@ -3091,13 +3091,13 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
         .await
         .unwrap();
-    let (_, window_b) = cx_b.add_window(|_| EmptyView);
-    let editor_b = cx_b.add_view(&window_b, |cx| {
+    let (window_b, _) = cx_b.add_window(|_| EmptyView);
+    let editor_b = cx_b.add_view(window_b, |cx| {
         Editor::for_buffer(buffer_b, Some(project_b), cx)
     });
     let mut editor_cx_b = EditorTestContext {
         cx: cx_b,
-        window_id: window_b.id(),
+        window_id: window_b,
         editor: editor_b,
     };
 
@@ -3836,8 +3836,8 @@ async fn test_collaborating_with_completion(
         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
         .await
         .unwrap();
-    let (_, window_b) = cx_b.add_window(|_| EmptyView);
-    let editor_b = cx_b.add_view(&window_b, |cx| {
+    let (window_b, _) = cx_b.add_window(|_| EmptyView);
+    let editor_b = cx_b.add_view(window_b, |cx| {
         Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
     });
 
@@ -6808,13 +6808,10 @@ async fn test_peers_following_each_other(
     // Clients A and B follow each other in split panes
     workspace_a.update(cx_a, |workspace, cx| {
         workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
-        let pane_a1 = pane_a1.clone();
-        cx.defer(move |workspace, _| {
-            assert_ne!(*workspace.active_pane(), pane_a1);
-        });
     });
     workspace_a
         .update(cx_a, |workspace, cx| {
+            assert_ne!(*workspace.active_pane(), pane_a1);
             let leader_id = *project_a.read(cx).collaborators().keys().next().unwrap();
             workspace.toggle_follow(leader_id, cx).unwrap()
         })
@@ -6822,13 +6819,10 @@ async fn test_peers_following_each_other(
         .unwrap();
     workspace_b.update(cx_b, |workspace, cx| {
         workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
-        let pane_b1 = pane_b1.clone();
-        cx.defer(move |workspace, _| {
-            assert_ne!(*workspace.active_pane(), pane_b1);
-        });
     });
     workspace_b
         .update(cx_b, |workspace, cx| {
+            assert_ne!(*workspace.active_pane(), pane_b1);
             let leader_id = *project_b.read(cx).collaborators().keys().next().unwrap();
             workspace.toggle_follow(leader_id, cx).unwrap()
         })

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;
@@ -165,6 +165,7 @@ impl CollabTitlebarItem {
             }),
         );
 
+        let view_id = cx.view_id();
         Self {
             workspace: workspace.weak_handle(),
             project,
@@ -172,7 +173,7 @@ impl CollabTitlebarItem {
             client,
             contacts_popover: None,
             user_menu: cx.add_view(|cx| {
-                let mut menu = ContextMenu::new(cx);
+                let mut menu = ContextMenu::new(view_id, cx);
                 menu.set_position_mode(OverlayPositionMode::Local);
                 menu
             }),
@@ -865,7 +866,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/command_palette/src/command_palette.rs 🔗

@@ -2,7 +2,7 @@ use collections::CommandPaletteFilter;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
     actions, elements::*, keymap_matcher::Keystroke, Action, AppContext, Element, MouseState,
-    ViewContext, WindowContext,
+    ViewContext,
 };
 use picker::{Picker, PickerDelegate, PickerEvent};
 use settings::Settings;
@@ -41,47 +41,17 @@ struct Command {
     keystrokes: Vec<Keystroke>,
 }
 
-fn toggle_command_palette(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
-    let workspace = cx.handle();
-    let focused_view_id = cx.focused_view_id().unwrap_or_else(|| workspace.id());
-
-    cx.window_context().defer(move |cx| {
-        // Build the delegate before the workspace is put on the stack so we can find it when
-        // computing the actions. We should really not allow available_actions to be called
-        // if it's not reliable however.
-        let delegate = CommandPaletteDelegate::new(focused_view_id, cx);
-        workspace.update(cx, |workspace, cx| {
-            workspace.toggle_modal(cx, |_, cx| cx.add_view(|cx| Picker::new(delegate, cx)));
-        })
+fn toggle_command_palette(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+    let focused_view_id = cx.focused_view_id().unwrap_or_else(|| cx.view_id());
+    workspace.toggle_modal(cx, |_, cx| {
+        cx.add_view(|cx| Picker::new(CommandPaletteDelegate::new(focused_view_id), cx))
     });
 }
 
 impl CommandPaletteDelegate {
-    pub fn new(focused_view_id: usize, cx: &mut WindowContext) -> Self {
-        let actions = cx
-            .available_actions(focused_view_id)
-            .filter_map(|(name, action, bindings)| {
-                if cx.has_global::<CommandPaletteFilter>() {
-                    let filter = cx.global::<CommandPaletteFilter>();
-                    if filter.filtered_namespaces.contains(action.namespace()) {
-                        return None;
-                    }
-                }
-
-                Some(Command {
-                    name: humanize_action_name(name),
-                    action,
-                    keystrokes: bindings
-                        .iter()
-                        .map(|binding| binding.keystrokes())
-                        .last()
-                        .map_or(Vec::new(), |keystrokes| keystrokes.to_vec()),
-                })
-            })
-            .collect();
-
+    pub fn new(focused_view_id: usize) -> Self {
         Self {
-            actions,
+            actions: Default::default(),
             matches: vec![],
             selected_ix: 0,
             focused_view_id,
@@ -111,17 +81,46 @@ impl PickerDelegate for CommandPaletteDelegate {
         query: String,
         cx: &mut ViewContext<Picker<Self>>,
     ) -> gpui::Task<()> {
-        let candidates = self
-            .actions
-            .iter()
-            .enumerate()
-            .map(|(ix, command)| StringMatchCandidate {
-                id: ix,
-                string: command.name.to_string(),
-                char_bag: command.name.chars().collect(),
-            })
-            .collect::<Vec<_>>();
+        let window_id = cx.window_id();
+        let view_id = self.focused_view_id;
         cx.spawn(move |picker, mut cx| async move {
+            let actions = cx
+                .available_actions(window_id, view_id)
+                .into_iter()
+                .filter_map(|(name, action, bindings)| {
+                    let filtered = cx.read(|cx| {
+                        if cx.has_global::<CommandPaletteFilter>() {
+                            let filter = cx.global::<CommandPaletteFilter>();
+                            filter.filtered_namespaces.contains(action.namespace())
+                        } else {
+                            false
+                        }
+                    });
+
+                    if filtered {
+                        None
+                    } else {
+                        Some(Command {
+                            name: humanize_action_name(name),
+                            action,
+                            keystrokes: bindings
+                                .iter()
+                                .map(|binding| binding.keystrokes())
+                                .last()
+                                .map_or(Vec::new(), |keystrokes| keystrokes.to_vec()),
+                        })
+                    }
+                })
+                .collect::<Vec<_>>();
+            let candidates = actions
+                .iter()
+                .enumerate()
+                .map(|(ix, command)| StringMatchCandidate {
+                    id: ix,
+                    string: command.name.to_string(),
+                    char_bag: command.name.chars().collect(),
+                })
+                .collect::<Vec<_>>();
             let matches = if query.is_empty() {
                 candidates
                     .into_iter()
@@ -147,6 +146,7 @@ impl PickerDelegate for CommandPaletteDelegate {
             picker
                 .update(&mut cx, |picker, _| {
                     let delegate = picker.delegate_mut();
+                    delegate.actions = actions;
                     delegate.matches = matches;
                     if delegate.matches.is_empty() {
                         delegate.selected_ix = 0;
@@ -304,8 +304,8 @@ mod tests {
         });
 
         let project = Project::test(app_state.fs.clone(), [], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let editor = cx.add_view(&workspace, |cx| {
+        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let editor = cx.add_view(window_id, |cx| {
             let mut editor = Editor::single_line(None, cx);
             editor.set_text("abc", cx);
             editor

crates/context_menu/src/context_menu.rs 🔗

@@ -177,9 +177,7 @@ impl View for ContextMenu {
 }
 
 impl ContextMenu {
-    pub fn new(cx: &mut ViewContext<Self>) -> Self {
-        let parent_view_id = cx.parent().unwrap();
-
+    pub fn new(parent_view_id: usize, cx: &mut ViewContext<Self>) -> Self {
         Self {
             show_count: 0,
             anchor_position: Default::default(),

crates/copilot_button/src/copilot_button.rs 🔗

@@ -144,8 +144,9 @@ impl View for CopilotButton {
 
 impl CopilotButton {
     pub fn new(cx: &mut ViewContext<Self>) -> Self {
+        let button_view_id = cx.view_id();
         let menu = cx.add_view(|cx| {
-            let mut menu = ContextMenu::new(cx);
+            let mut menu = ContextMenu::new(button_view_id, cx);
             menu.set_position_mode(OverlayPositionMode::Local);
             menu
         });

crates/diagnostics/src/diagnostics.rs 🔗

@@ -852,7 +852,7 @@ mod tests {
 
         let language_server_id = LanguageServerId(0);
         let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 
         // Create some diagnostics
         project.update(cx, |project, cx| {
@@ -939,7 +939,7 @@ mod tests {
         });
 
         // Open the project diagnostics view while there are already diagnostics.
-        let view = cx.add_view(&workspace, |cx| {
+        let view = cx.add_view(window_id, |cx| {
             ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
         });
 
@@ -1244,9 +1244,9 @@ mod tests {
         let server_id_1 = LanguageServerId(100);
         let server_id_2 = LanguageServerId(101);
         let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 
-        let view = cx.add_view(&workspace, |cx| {
+        let view = cx.add_view(window_id, |cx| {
             ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
         });
 

crates/editor/src/editor.rs 🔗

@@ -1227,6 +1227,7 @@ impl Editor {
         get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
+        let editor_view_id = cx.view_id();
         let display_map = cx.add_model(|cx| {
             let settings = cx.global::<Settings>();
             let style = build_style(&*settings, get_field_editor_theme.as_deref(), None, cx);
@@ -1274,7 +1275,8 @@ impl Editor {
             background_highlights: Default::default(),
             nav_history: None,
             context_menu: None,
-            mouse_context_menu: cx.add_view(context_menu::ContextMenu::new),
+            mouse_context_menu: cx
+                .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)),
             completion_tasks: Default::default(),
             next_completion_id: 0,
             available_code_actions: Default::default(),

crates/editor/src/editor_tests.rs 🔗

@@ -493,9 +493,9 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
 
     let fs = FakeFs::new(cx.background());
     let project = Project::test(fs, [], cx).await;
-    let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+    let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
     let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-    cx.add_view(&pane, |cx| {
+    cx.add_view(window_id, |cx| {
         let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
         let mut editor = build_editor(buffer.clone(), cx);
         let handle = cx.handle();

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/editor/src/items.rs 🔗

@@ -3,7 +3,7 @@ use crate::{
     movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor,
     Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
 };
-use anyhow::{anyhow, Context, Result};
+use anyhow::{Context, Result};
 use collections::HashSet;
 use futures::future::try_join_all;
 use gpui::{
@@ -864,16 +864,13 @@ impl Item for Editor {
                     let buffer = project_item
                         .downcast::<Buffer>()
                         .context("Project item at stored path was not a buffer")?;
-                    let pane = pane
-                        .upgrade(&cx)
-                        .ok_or_else(|| anyhow!("pane was dropped"))?;
-                    Ok(cx.update(|cx| {
-                        cx.add_view(&pane, |cx| {
+                    Ok(pane.update(&mut cx, |_, cx| {
+                        cx.add_view(|cx| {
                             let mut editor = Editor::for_buffer(buffer, Some(project), cx);
                             editor.read_scroll_position_from_db(item_id, workspace_id, cx);
                             editor
                         })
-                    }))
+                    })?)
                 })
             })
             .unwrap_or_else(|error| Task::ready(Err(error)))

crates/gpui/src/app.rs 🔗

@@ -25,6 +25,7 @@ use std::{
 use anyhow::{anyhow, Context, Result};
 use parking_lot::Mutex;
 use postage::oneshot;
+use smallvec::SmallVec;
 use smol::prelude::*;
 use util::ResultExt;
 use uuid::Uuid;
@@ -336,11 +337,20 @@ impl AsyncAppContext {
         self.0
             .borrow_mut()
             .update_window(window_id, |window| {
-                window.handle_dispatch_action_from_effect(Some(view_id), action);
+                window.dispatch_action(Some(view_id), action);
             })
             .ok_or_else(|| anyhow!("window not found"))
     }
 
+    pub fn available_actions(
+        &self,
+        window_id: usize,
+        view_id: usize,
+    ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
+        self.read_window(window_id, |cx| cx.available_actions(view_id))
+            .unwrap_or_default()
+    }
+
     pub fn has_window(&self, window_id: usize) -> bool {
         self.read(|cx| cx.windows.contains_key(&window_id))
     }
@@ -442,7 +452,6 @@ pub struct AppContext {
     models: HashMap<usize, Box<dyn AnyModel>>,
     views: HashMap<(usize, usize), Box<dyn AnyView>>,
     views_metadata: HashMap<(usize, usize), ViewMetadata>,
-    pub(crate) parents: HashMap<(usize, usize), ParentId>,
     windows: HashMap<usize, Window>,
     globals: HashMap<TypeId, Box<dyn Any>>,
     element_states: HashMap<ElementStateId, Box<dyn Any>>,
@@ -505,7 +514,6 @@ impl AppContext {
             models: Default::default(),
             views: Default::default(),
             views_metadata: Default::default(),
-            parents: Default::default(),
             windows: Default::default(),
             globals: Default::default(),
             element_states: Default::default(),
@@ -1365,9 +1373,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
     }
@@ -1384,18 +1392,6 @@ impl AppContext {
         self.update_window(window_id, |cx| cx.replace_root_view(build_root_view))
     }
 
-    pub fn add_view<S, F>(&mut self, parent: &AnyViewHandle, build_view: F) -> ViewHandle<S>
-    where
-        S: View,
-        F: FnOnce(&mut ViewContext<S>) -> S,
-    {
-        self.update_window(parent.window_id, |cx| {
-            cx.build_and_insert_view(ParentId::View(parent.view_id), |cx| Some(build_view(cx)))
-                .unwrap()
-        })
-        .unwrap()
-    }
-
     pub fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
         if let Some(view) = self.views.get(&(handle.window_id, handle.view_id)) {
             view.as_any().downcast_ref().expect("downcast is type safe")
@@ -1456,6 +1452,7 @@ impl AppContext {
                 let mut view = self.views.remove(&(window_id, view_id)).unwrap();
                 view.release(self);
                 let change_focus_to = self.windows.get_mut(&window_id).and_then(|window| {
+                    window.parents.remove(&view_id);
                     window
                         .invalidation
                         .get_or_insert_with(Default::default)
@@ -1467,10 +1464,14 @@ impl AppContext {
                         None
                     }
                 });
-                self.parents.remove(&(window_id, view_id));
 
                 if let Some(view_id) = change_focus_to {
-                    self.handle_focus_effect(window_id, Some(view_id));
+                    self.pending_effects
+                        .push_back(Effect::Focus(FocusEffect::View {
+                            window_id,
+                            view_id: Some(view_id),
+                            is_forced: false,
+                        }));
                 }
 
                 self.pending_effects
@@ -1491,7 +1492,10 @@ impl AppContext {
             self.flushing_effects = true;
 
             let mut refreshing = false;
+            let mut updated_windows = HashSet::default();
+            let mut focus_effects = HashMap::<usize, FocusEffect>::default();
             loop {
+                self.remove_dropped_entities();
                 if let Some(effect) = self.pending_effects.pop_front() {
                     match effect {
                         Effect::Subscription {
@@ -1564,8 +1568,15 @@ impl AppContext {
                             self.handle_entity_release_effect(view_id, view.as_any())
                         }
 
-                        Effect::Focus { window_id, view_id } => {
-                            self.handle_focus_effect(window_id, view_id);
+                        Effect::Focus(mut effect) => {
+                            if focus_effects
+                                .get(&effect.window_id())
+                                .map_or(false, |prev_effect| prev_effect.is_forced())
+                            {
+                                effect.force();
+                            }
+
+                            focus_effects.insert(effect.window_id(), effect);
                         }
 
                         Effect::FocusObservation {
@@ -1606,7 +1617,22 @@ impl AppContext {
                         Effect::ActivateWindow {
                             window_id,
                             is_active,
-                        } => self.handle_window_activation_effect(window_id, is_active),
+                        } => {
+                            if self.handle_window_activation_effect(window_id, is_active)
+                                && is_active
+                            {
+                                focus_effects
+                                    .entry(window_id)
+                                    .or_insert_with(|| FocusEffect::View {
+                                        window_id,
+                                        view_id: self
+                                            .read_window(window_id, |cx| cx.focused_view_id())
+                                            .flatten(),
+                                        is_forced: true,
+                                    })
+                                    .force();
+                            }
+                        }
 
                         Effect::WindowFullscreenObservation {
                             window_id,
@@ -1665,14 +1691,32 @@ impl AppContext {
                         ),
                     }
                     self.pending_notifications.clear();
-                    self.remove_dropped_entities();
                 } else {
-                    self.remove_dropped_entities();
+                    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 refreshing {
-                        self.perform_window_refresh();
-                    } else {
-                        self.update_windows();
+                    for (_, effect) in focus_effects.drain() {
+                        self.handle_focus_effect(effect);
                     }
 
                     if self.pending_effects.is_empty() {
@@ -1680,6 +1724,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();
@@ -1694,21 +1746,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 });
@@ -1752,23 +1789,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();
 
@@ -1849,69 +1869,58 @@ impl AppContext {
         });
     }
 
-    fn handle_window_activation_effect(&mut self, window_id: usize, active: bool) {
+    fn handle_window_activation_effect(&mut self, window_id: usize, active: bool) -> bool {
         self.update_window(window_id, |cx| {
             if cx.window.is_active == active {
-                return;
+                return false;
             }
             cx.window.is_active = active;
 
-            if let Some(focused_id) = cx.window.focused_view_id {
-                for view_id in cx.ancestors(focused_id).collect::<Vec<_>>() {
-                    cx.update_any_view(view_id, |view, cx| {
-                        if active {
-                            view.focus_in(focused_id, cx, view_id);
-                        } else {
-                            view.focus_out(focused_id, cx, view_id);
-                        }
-                    });
-                }
-            }
-
             let mut observations = cx.window_activation_observations.clone();
             observations.emit(window_id, |callback| callback(active, cx));
-        });
+            true
+        })
+        .unwrap_or(false)
     }
 
-    fn handle_focus_effect(&mut self, window_id: usize, mut focused_id: Option<usize>) {
-        let mut blurred_id = None;
+    fn handle_focus_effect(&mut self, effect: FocusEffect) {
+        let window_id = effect.window_id();
         self.update_window(window_id, |cx| {
-            if cx.window.focused_view_id == focused_id {
-                focused_id = None;
-                return;
-            }
-            blurred_id = cx.window.focused_view_id;
+            let focused_id = match effect {
+                FocusEffect::View { view_id, .. } => view_id,
+                FocusEffect::ViewParent { view_id, .. } => cx.ancestors(view_id).skip(1).next(),
+            };
+
+            let focus_changed = cx.window.focused_view_id != focused_id;
+            let blurred_id = cx.window.focused_view_id;
             cx.window.focused_view_id = focused_id;
 
-            let blurred_parents = blurred_id
-                .map(|blurred_id| cx.ancestors(blurred_id).collect::<Vec<_>>())
-                .unwrap_or_default();
-            let focused_parents = focused_id
-                .map(|focused_id| cx.ancestors(focused_id).collect::<Vec<_>>())
-                .unwrap_or_default();
-
-            if let Some(blurred_id) = blurred_id {
-                for view_id in blurred_parents.iter().copied() {
-                    if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
-                        view.focus_out(blurred_id, cx, view_id);
-                        cx.views.insert((window_id, view_id), view);
+            if focus_changed {
+                if let Some(blurred_id) = blurred_id {
+                    for view_id in cx.ancestors(blurred_id).collect::<Vec<_>>() {
+                        if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
+                            view.focus_out(blurred_id, cx, view_id);
+                            cx.views.insert((window_id, view_id), view);
+                        }
                     }
-                }
 
-                let mut subscriptions = cx.focus_observations.clone();
-                subscriptions.emit(blurred_id, |callback| callback(false, cx));
+                    let mut subscriptions = cx.focus_observations.clone();
+                    subscriptions.emit(blurred_id, |callback| callback(false, cx));
+                }
             }
 
-            if let Some(focused_id) = focused_id {
-                for view_id in focused_parents {
-                    if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
-                        view.focus_in(focused_id, cx, view_id);
-                        cx.views.insert((window_id, view_id), view);
+            if focus_changed || effect.is_forced() {
+                if let Some(focused_id) = focused_id {
+                    for view_id in cx.ancestors(focused_id).collect::<Vec<_>>() {
+                        if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
+                            view.focus_in(focused_id, cx, view_id);
+                            cx.views.insert((window_id, view_id), view);
+                        }
                     }
-                }
 
-                let mut subscriptions = cx.focus_observations.clone();
-                subscriptions.emit(focused_id, |callback| callback(true, cx));
+                    let mut subscriptions = cx.focus_observations.clone();
+                    subscriptions.emit(focused_id, |callback| callback(true, cx));
+                }
             }
         });
     }
@@ -1963,7 +1972,11 @@ impl AppContext {
 
     pub fn focus(&mut self, window_id: usize, view_id: Option<usize>) {
         self.pending_effects
-            .push_back(Effect::Focus { window_id, view_id });
+            .push_back(Effect::Focus(FocusEffect::View {
+                window_id,
+                view_id,
+                is_forced: false,
+            }));
     }
 
     fn spawn_internal<F, Fut, T>(&mut self, task_name: Option<&'static str>, f: F) -> Task<T>
@@ -2059,6 +2072,43 @@ pub struct WindowInvalidation {
     pub removed: Vec<usize>,
 }
 
+#[derive(Debug)]
+pub enum FocusEffect {
+    View {
+        window_id: usize,
+        view_id: Option<usize>,
+        is_forced: bool,
+    },
+    ViewParent {
+        window_id: usize,
+        view_id: usize,
+        is_forced: bool,
+    },
+}
+
+impl FocusEffect {
+    fn window_id(&self) -> usize {
+        match self {
+            FocusEffect::View { window_id, .. } => *window_id,
+            FocusEffect::ViewParent { window_id, .. } => *window_id,
+        }
+    }
+
+    fn is_forced(&self) -> bool {
+        match self {
+            FocusEffect::View { is_forced, .. } => *is_forced,
+            FocusEffect::ViewParent { is_forced, .. } => *is_forced,
+        }
+    }
+
+    fn force(&mut self) {
+        match self {
+            FocusEffect::View { is_forced, .. } => *is_forced = true,
+            FocusEffect::ViewParent { is_forced, .. } => *is_forced = true,
+        }
+    }
+}
+
 pub enum Effect {
     Subscription {
         entity_id: usize,
@@ -2104,10 +2154,7 @@ pub enum Effect {
         view_id: usize,
         view: Box<dyn AnyView>,
     },
-    Focus {
-        window_id: usize,
-        view_id: Option<usize>,
-    },
+    Focus(FocusEffect),
     FocusObservation {
         view_id: usize,
         subscription_id: usize,
@@ -2223,11 +2270,7 @@ impl Debug for Effect {
                 .debug_struct("Effect::ViewRelease")
                 .field("view_id", view_id)
                 .finish(),
-            Effect::Focus { window_id, view_id } => f
-                .debug_struct("Effect::Focus")
-                .field("window_id", window_id)
-                .field("view_id", view_id)
-                .finish(),
+            Effect::Focus(focus) => f.debug_tuple("Effect::Focus").field(focus).finish(),
             Effect::FocusObservation {
                 view_id,
                 subscription_id,
@@ -2795,10 +2838,6 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
         WeakViewHandle::new(self.window_id, self.view_id)
     }
 
-    pub fn parent(&self) -> Option<usize> {
-        self.window_context.parent(self.view_id)
-    }
-
     pub fn window_id(&self) -> usize {
         self.window_id
     }
@@ -2849,30 +2888,15 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
         self.window.focused_view_id == Some(self.view_id)
     }
 
-    pub fn is_parent_view_focused(&self) -> bool {
-        if let Some(parent_view_id) = self.ancestors(self.view_id).next().clone() {
-            self.focused_view_id() == Some(parent_view_id)
-        } else {
-            false
-        }
-    }
-
-    pub fn focus_parent_view(&mut self) {
-        let next = self.ancestors(self.view_id).next().clone();
-        if let Some(parent_view_id) = next {
-            let window_id = self.window_id;
-            self.window_context.focus(window_id, Some(parent_view_id));
-        }
-    }
-
-    pub fn is_child(&self, view: impl Into<AnyViewHandle>) -> bool {
-        let view = view.into();
-        if self.window_id != view.window_id {
-            return false;
-        }
-        self.ancestors(view.view_id)
-            .skip(1) // Skip self id
-            .any(|parent| parent == self.view_id)
+    pub fn focus_parent(&mut self) {
+        let window_id = self.window_id;
+        let view_id = self.view_id;
+        self.pending_effects
+            .push_back(Effect::Focus(FocusEffect::ViewParent {
+                window_id,
+                view_id,
+                is_forced: false,
+            }));
     }
 
     pub fn blur(&mut self) {
@@ -2902,38 +2926,6 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
             });
     }
 
-    pub fn add_view<S, F>(&mut self, build_view: F) -> ViewHandle<S>
-    where
-        S: View,
-        F: FnOnce(&mut ViewContext<S>) -> S,
-    {
-        self.window_context
-            .build_and_insert_view(ParentId::View(self.view_id), |cx| Some(build_view(cx)))
-            .unwrap()
-    }
-
-    pub fn add_option_view<S, F>(&mut self, build_view: F) -> Option<ViewHandle<S>>
-    where
-        S: View,
-        F: FnOnce(&mut ViewContext<S>) -> Option<S>,
-    {
-        self.window_context
-            .build_and_insert_view(ParentId::View(self.view_id), build_view)
-    }
-
-    pub fn reparent(&mut self, view_handle: &AnyViewHandle) {
-        if self.window_id != view_handle.window_id {
-            panic!("Can't reparent view to a view from a different window");
-        }
-        self.parents
-            .remove(&(view_handle.window_id, view_handle.view_id));
-        let new_parent_id = self.view_id;
-        self.parents.insert(
-            (view_handle.window_id, view_handle.view_id),
-            ParentId::View(new_parent_id),
-        );
-    }
-
     pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
     where
         E: Entity,
@@ -3269,6 +3261,116 @@ 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 HashMap<usize, SmallVec<[usize; 2]>>,
+    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 HashMap<usize, SmallVec<[usize; 2]>>,
+        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
+    }
+
+    /// Return keystrokes that would dispatch the given action on the given view.
+    pub(crate) fn keystrokes_for_action(
+        &mut self,
+        view_id: usize,
+        action: &dyn Action,
+    ) -> Option<SmallVec<[Keystroke; 2]>> {
+        self.notify_if_view_ancestors_change(view_id);
+
+        let window_id = self.window_id;
+        let mut contexts = Vec::new();
+        let mut handler_depth = None;
+        for (i, view_id) in self.ancestors(view_id).enumerate() {
+            if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
+                if let Some(actions) = self.actions.get(&view_metadata.type_id) {
+                    if actions.contains_key(&action.as_any().type_id()) {
+                        handler_depth = Some(i);
+                    }
+                }
+                contexts.push(view_metadata.keymap_context.clone());
+            }
+        }
+
+        if self.global_actions.contains_key(&action.as_any().type_id()) {
+            handler_depth = Some(contexts.len())
+        }
+
+        self.keystroke_matcher
+            .bindings_for_action_type(action.as_any().type_id())
+            .find_map(|b| {
+                handler_depth
+                    .map(|highest_handler| {
+                        if (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..])) {
+                            Some(b.keystrokes().into())
+                        } else {
+                            None
+                        }
+                    })
+                    .flatten()
+            })
+    }
+
+    fn notify_if_view_ancestors_change(&mut self, view_id: usize) {
+        let self_view_id = self.view_id;
+        self.views_to_notify_if_ancestors_change
+            .entry(view_id)
+            .or_default()
+            .push(self_view_id);
+    }
+}
+
+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,
@@ -4521,9 +4623,9 @@ mod tests {
             }
         }
 
-        let (_, root_view) = cx.add_window(|cx| View::new(None, cx));
-        let handle_1 = cx.add_view(&root_view, |cx| View::new(None, cx));
-        let handle_2 = cx.add_view(&root_view, |cx| View::new(Some(handle_1.clone()), cx));
+        let (window_id, _root_view) = cx.add_window(|cx| View::new(None, cx));
+        let handle_1 = cx.add_view(window_id, |cx| View::new(None, cx));
+        let handle_2 = cx.add_view(window_id, |cx| View::new(Some(handle_1.clone()), cx));
         assert_eq!(cx.read(|cx| cx.views.len()), 3);
 
         handle_1.update(cx, |view, cx| {
@@ -4683,8 +4785,8 @@ mod tests {
             type Event = String;
         }
 
-        let (_, handle_1) = cx.add_window(|_| TestView::default());
-        let handle_2 = cx.add_view(&handle_1, |_| TestView::default());
+        let (window_id, handle_1) = cx.add_window(|_| TestView::default());
+        let handle_2 = cx.add_view(window_id, |_| TestView::default());
         let handle_3 = cx.add_model(|_| Model);
 
         handle_1.update(cx, |_, cx| {
@@ -4910,9 +5012,9 @@ mod tests {
             type Event = ();
         }
 
-        let (_, root_view) = cx.add_window(|_| TestView::default());
-        let observing_view = cx.add_view(&root_view, |_| TestView::default());
-        let emitting_view = cx.add_view(&root_view, |_| TestView::default());
+        let (window_id, _root_view) = cx.add_window(|_| TestView::default());
+        let observing_view = cx.add_view(window_id, |_| TestView::default());
+        let emitting_view = cx.add_view(window_id, |_| TestView::default());
         let observing_model = cx.add_model(|_| Model);
         let observed_model = cx.add_model(|_| Model);
 
@@ -5037,8 +5139,8 @@ mod tests {
             type Event = ();
         }
 
-        let (_, root_view) = cx.add_window(|_| TestView::default());
-        let observing_view = cx.add_view(&root_view, |_| TestView::default());
+        let (window_id, _root_view) = cx.add_window(|_| TestView::default());
+        let observing_view = cx.add_view(window_id, |_| TestView::default());
         let observing_model = cx.add_model(|_| Model);
         let observed_model = cx.add_model(|_| Model);
 
@@ -5160,9 +5262,9 @@ mod tests {
             }
         }
 
-        let (_, root_view) = cx.add_window(|_| View);
-        let observing_view = cx.add_view(&root_view, |_| View);
-        let observed_view = cx.add_view(&root_view, |_| View);
+        let (window_id, _root_view) = cx.add_window(|_| View);
+        let observing_view = cx.add_view(window_id, |_| View);
+        let observed_view = cx.add_view(window_id, |_| View);
 
         let observation_count = Rc::new(RefCell::new(0));
         observing_view.update(cx, |_, cx| {
@@ -5211,6 +5313,7 @@ mod tests {
         struct View {
             name: String,
             events: Arc<Mutex<Vec<String>>>,
+            child: Option<AnyViewHandle>,
         }
 
         impl Entity for View {
@@ -5218,8 +5321,11 @@ mod tests {
         }
 
         impl super::View for View {
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                Empty::new().into_any()
+            fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+                self.child
+                    .as_ref()
+                    .map(|child| ChildView::new(child, cx).into_any())
+                    .unwrap_or(Empty::new().into_any())
             }
 
             fn ui_name() -> &'static str {
@@ -5243,11 +5349,22 @@ mod tests {
         let (window_id, view_1) = cx.add_window(|_| View {
             events: view_events.clone(),
             name: "view 1".to_string(),
+            child: None,
         });
-        let view_2 = cx.add_view(&view_1, |_| View {
-            events: view_events.clone(),
-            name: "view 2".to_string(),
-        });
+        let view_2 = cx
+            .update_window(window_id, |cx| {
+                let view_2 = cx.add_view(|_| View {
+                    events: view_events.clone(),
+                    name: "view 2".to_string(),
+                    child: None,
+                });
+                view_1.update(cx, |view_1, cx| {
+                    view_1.child = Some(view_2.clone().into_any());
+                    cx.notify();
+                });
+                view_2
+            })
+            .unwrap();
 
         let observed_events: Arc<Mutex<Vec<String>>> = Default::default();
         view_1.update(cx, |_, cx| {
@@ -5284,44 +5401,25 @@ mod tests {
         assert_eq!(mem::take(&mut *observed_events.lock()), Vec::<&str>::new());
 
         view_1.update(cx, |_, cx| {
-            // Ensure focus events are sent for all intermediate focuses
+            // Ensure only the last focus event is honored.
             cx.focus(&view_2);
             cx.focus(&view_1);
             cx.focus(&view_2);
         });
 
-        cx.read_window(window_id, |cx| {
-            assert!(cx.is_child_focused(&view_1));
-            assert!(!cx.is_child_focused(&view_2));
-        });
         assert_eq!(
             mem::take(&mut *view_events.lock()),
-            [
-                "view 1 blurred",
-                "view 2 focused",
-                "view 2 blurred",
-                "view 1 focused",
-                "view 1 blurred",
-                "view 2 focused"
-            ],
+            ["view 1 blurred", "view 2 focused"],
         );
         assert_eq!(
             mem::take(&mut *observed_events.lock()),
             [
-                "view 2 observed view 1's blur",
-                "view 1 observed view 2's focus",
-                "view 1 observed view 2's blur",
-                "view 2 observed view 1's focus",
                 "view 2 observed view 1's blur",
                 "view 1 observed view 2's focus"
             ]
         );
 
         view_1.update(cx, |_, cx| cx.focus(&view_1));
-        cx.read_window(window_id, |cx| {
-            assert!(!cx.is_child_focused(&view_1));
-            assert!(!cx.is_child_focused(&view_2));
-        });
         assert_eq!(
             mem::take(&mut *view_events.lock()),
             ["view 2 blurred", "view 1 focused"],
@@ -5347,7 +5445,11 @@ mod tests {
             ]
         );
 
-        view_1.update(cx, |_, _| drop(view_2));
+        println!("=====================");
+        view_1.update(cx, |view, _| {
+            drop(view_2);
+            view.child = None;
+        });
         assert_eq!(mem::take(&mut *view_events.lock()), ["view 1 focused"]);
         assert_eq!(mem::take(&mut *observed_events.lock()), Vec::<&str>::new());
     }
@@ -5392,6 +5494,7 @@ mod tests {
     fn test_dispatch_action(cx: &mut AppContext) {
         struct ViewA {
             id: usize,
+            child: Option<AnyViewHandle>,
         }
 
         impl Entity for ViewA {
@@ -5399,8 +5502,11 @@ mod tests {
         }
 
         impl View for ViewA {
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                Empty::new().into_any()
+            fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+                self.child
+                    .as_ref()
+                    .map(|child| ChildView::new(child, cx).into_any())
+                    .unwrap_or(Empty::new().into_any())
             }
 
             fn ui_name() -> &'static str {
@@ -5410,6 +5516,7 @@ mod tests {
 
         struct ViewB {
             id: usize,
+            child: Option<AnyViewHandle>,
         }
 
         impl Entity for ViewB {
@@ -5417,8 +5524,11 @@ mod tests {
         }
 
         impl View for ViewB {
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                Empty::new().into_any()
+            fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+                self.child
+                    .as_ref()
+                    .map(|child| ChildView::new(child, cx).into_any())
+                    .unwrap_or(Empty::new().into_any())
             }
 
             fn ui_name() -> &'static str {
@@ -5455,7 +5565,7 @@ mod tests {
                 if view.id != 1 {
                     cx.add_view(|cx| {
                         cx.propagate_action(); // Still works on a nested ViewContext
-                        ViewB { id: 5 }
+                        ViewB { id: 5, child: None }
                     });
                 }
                 actions.borrow_mut().push(format!("{} b", view.id));
@@ -5493,13 +5603,41 @@ mod tests {
         })
         .detach();
 
-        let (window_id, view_1) = cx.add_window(Default::default(), |_| ViewA { id: 1 });
-        let view_2 = cx.add_view(&view_1, |_| ViewB { id: 2 });
-        let view_3 = cx.add_view(&view_2, |_| ViewA { id: 3 });
-        let view_4 = cx.add_view(&view_3, |_| ViewB { id: 4 });
+        let (window_id, view_1) =
+            cx.add_window(Default::default(), |_| ViewA { id: 1, child: None });
+        let view_2 = cx
+            .update_window(window_id, |cx| {
+                let child = cx.add_view(|_| ViewB { id: 2, child: None });
+                view_1.update(cx, |view, cx| {
+                    view.child = Some(child.clone().into_any());
+                    cx.notify();
+                });
+                child
+            })
+            .unwrap();
+        let view_3 = cx
+            .update_window(window_id, |cx| {
+                let child = cx.add_view(|_| ViewA { id: 3, child: None });
+                view_2.update(cx, |view, cx| {
+                    view.child = Some(child.clone().into_any());
+                    cx.notify();
+                });
+                child
+            })
+            .unwrap();
+        let view_4 = cx
+            .update_window(window_id, |cx| {
+                let child = cx.add_view(|_| ViewB { id: 4, child: None });
+                view_3.update(cx, |view, cx| {
+                    view.child = Some(child.clone().into_any());
+                    cx.notify();
+                });
+                child
+            })
+            .unwrap();
 
         cx.update_window(window_id, |cx| {
-            cx.handle_dispatch_action_from_effect(Some(view_4.id()), &Action("bar".to_string()))
+            cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string()))
         });
 
         assert_eq!(
@@ -5520,13 +5658,32 @@ mod tests {
 
         // Remove view_1, which doesn't propagate the action
 
-        let (window_id, view_2) = cx.add_window(Default::default(), |_| ViewB { id: 2 });
-        let view_3 = cx.add_view(&view_2, |_| ViewA { id: 3 });
-        let view_4 = cx.add_view(&view_3, |_| ViewB { id: 4 });
+        let (window_id, view_2) =
+            cx.add_window(Default::default(), |_| ViewB { id: 2, child: None });
+        let view_3 = cx
+            .update_window(window_id, |cx| {
+                let child = cx.add_view(|_| ViewA { id: 3, child: None });
+                view_2.update(cx, |view, cx| {
+                    view.child = Some(child.clone().into_any());
+                    cx.notify();
+                });
+                child
+            })
+            .unwrap();
+        let view_4 = cx
+            .update_window(window_id, |cx| {
+                let child = cx.add_view(|_| ViewB { id: 4, child: None });
+                view_3.update(cx, |view, cx| {
+                    view.child = Some(child.clone().into_any());
+                    cx.notify();
+                });
+                child
+            })
+            .unwrap();
 
         actions.borrow_mut().clear();
         cx.update_window(window_id, |cx| {
-            cx.handle_dispatch_action_from_effect(Some(view_4.id()), &Action("bar".to_string()))
+            cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string()))
         });
 
         assert_eq!(
@@ -5558,6 +5715,7 @@ mod tests {
         struct View {
             id: usize,
             keymap_context: KeymapContext,
+            child: Option<AnyViewHandle>,
         }
 
         impl Entity for View {
@@ -5565,8 +5723,11 @@ mod tests {
         }
 
         impl super::View for View {
-            fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-                Empty::new().into_any()
+            fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+                self.child
+                    .as_ref()
+                    .map(|child| ChildView::new(child, cx).into_any())
+                    .unwrap_or(Empty::new().into_any())
             }
 
             fn ui_name() -> &'static str {
@@ -5583,6 +5744,7 @@ mod tests {
                 View {
                     id,
                     keymap_context: KeymapContext::default(),
+                    child: None,
                 }
             }
         }
@@ -5597,11 +5759,17 @@ mod tests {
         view_3.keymap_context.add_identifier("b");
         view_3.keymap_context.add_identifier("c");
 
-        let (window_id, view_1) = cx.add_window(Default::default(), |_| view_1);
-        let view_2 = cx.add_view(&view_1, |_| view_2);
-        let _view_3 = cx.add_view(&view_2, |cx| {
-            cx.focus_self();
-            view_3
+        let (window_id, _view_1) = cx.add_window(Default::default(), |cx| {
+            let view_2 = cx.add_view(|cx| {
+                let view_3 = cx.add_view(|cx| {
+                    cx.focus_self();
+                    view_3
+                });
+                view_2.child = Some(view_3.into_any());
+                view_2
+            });
+            view_1.child = Some(view_2.into_any());
+            view_1
         });
 
         // This binding only dispatches an action on view 2 because that view will have

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

@@ -81,7 +81,7 @@ pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform,
                 let dispatched = cx
                     .update_window(main_window_id, |cx| {
                         if let Some(view_id) = cx.focused_view_id() {
-                            cx.handle_dispatch_action_from_effect(Some(view_id), action);
+                            cx.dispatch_action(Some(view_id), action);
                             true
                         } else {
                             false

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

@@ -1,17 +1,18 @@
 use crate::{
     executor,
     geometry::vector::Vector2F,
-    keymap_matcher::Keystroke,
+    keymap_matcher::{Binding, Keystroke},
     platform,
     platform::{Event, InputHandler, KeyDownEvent, Platform},
-    Action, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache,
-    Handle, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
-    WeakHandle, WindowContext,
+    Action, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache, Handle,
+    ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakHandle,
+    WindowContext,
 };
 use collections::BTreeMap;
 use futures::Future;
 use itertools::Itertools;
 use parking_lot::{Mutex, RwLock};
+use smallvec::SmallVec;
 use smol::stream::StreamExt;
 use std::{
     any::Any,
@@ -71,17 +72,24 @@ impl TestAppContext {
         cx
     }
 
-    pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) {
-        self.cx
-            .borrow_mut()
-            .update_window(window_id, |window| {
-                window.handle_dispatch_action_from_effect(window.focused_view_id(), &action);
-            })
-            .expect("window not found");
+    pub fn dispatch_action<A: Action>(&mut self, window_id: usize, action: A) {
+        self.update_window(window_id, |window| {
+            window.dispatch_action(window.focused_view_id(), &action);
+        })
+        .expect("window not found");
+    }
+
+    pub fn available_actions(
+        &self,
+        window_id: usize,
+        view_id: usize,
+    ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
+        self.read_window(window_id, |cx| cx.available_actions(view_id))
+            .unwrap_or_default()
     }
 
-    pub fn dispatch_global_action<A: Action>(&self, action: A) {
-        self.cx.borrow_mut().dispatch_global_action_any(&action);
+    pub fn dispatch_global_action<A: Action>(&mut self, action: A) {
+        self.update(|cx| cx.dispatch_global_action_any(&action));
     }
 
     pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
@@ -153,12 +161,13 @@ impl TestAppContext {
         (window_id, view)
     }
 
-    pub fn add_view<T, F>(&mut self, parent_handle: &AnyViewHandle, build_view: F) -> ViewHandle<T>
+    pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
     where
         T: View,
         F: FnOnce(&mut ViewContext<T>) -> T,
     {
-        self.cx.borrow_mut().add_view(parent_handle, build_view)
+        self.update_window(window_id, |cx| cx.add_view(build_view))
+            .expect("window not found")
     }
 
     pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription

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

@@ -14,7 +14,7 @@ use crate::{
     text_layout::TextLayoutCache,
     util::post_inc,
     Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect,
-    Element, Entity, Handle, MouseRegion, MouseRegionId, ParentId, SceneBuilder, Subscription,
+    Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription,
     View, ViewContext, ViewHandle, WindowInvalidation,
 };
 use anyhow::{anyhow, bail, Result};
@@ -39,6 +39,7 @@ use super::{Reference, ViewMetadata};
 pub struct Window {
     pub(crate) root_view: Option<AnyViewHandle>,
     pub(crate) focused_view_id: Option<usize>,
+    pub(crate) parents: HashMap<usize, usize>,
     pub(crate) is_active: bool,
     pub(crate) is_fullscreen: bool,
     pub(crate) invalidation: Option<WindowInvalidation>,
@@ -72,6 +73,7 @@ impl Window {
         let mut window = Self {
             root_view: None,
             focused_view_id: None,
+            parents: Default::default(),
             is_active: false,
             invalidation: None,
             is_fullscreen: false,
@@ -90,11 +92,9 @@ impl Window {
         };
 
         let mut window_context = WindowContext::mutable(cx, &mut window, window_id);
-        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);
+        let root_view = window_context.add_view(|cx| build_view(cx));
+        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,
         }
     }
@@ -359,49 +356,10 @@ impl<'a> WindowContext<'a> {
         )
     }
 
-    /// Return keystrokes that would dispatch the given action on the given view.
-    pub(crate) fn keystrokes_for_action(
-        &mut self,
-        view_id: usize,
-        action: &dyn Action,
-    ) -> Option<SmallVec<[Keystroke; 2]>> {
-        let window_id = self.window_id;
-        let mut contexts = Vec::new();
-        let mut handler_depth = None;
-        for (i, view_id) in self.ancestors(view_id).enumerate() {
-            if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
-                if let Some(actions) = self.actions.get(&view_metadata.type_id) {
-                    if actions.contains_key(&action.as_any().type_id()) {
-                        handler_depth = Some(i);
-                    }
-                }
-                contexts.push(view_metadata.keymap_context.clone());
-            }
-        }
-
-        if self.global_actions.contains_key(&action.as_any().type_id()) {
-            handler_depth = Some(contexts.len())
-        }
-
-        self.keystroke_matcher
-            .bindings_for_action_type(action.as_any().type_id())
-            .find_map(|b| {
-                handler_depth
-                    .map(|highest_handler| {
-                        if (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..])) {
-                            Some(b.keystrokes().into())
-                        } else {
-                            None
-                        }
-                    })
-                    .flatten()
-            })
-    }
-
-    pub fn available_actions(
+    pub(crate) fn available_actions(
         &self,
         view_id: usize,
-    ) -> impl Iterator<Item = (&'static str, Box<dyn Action>, SmallVec<[&Binding; 1]>)> {
+    ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
         let window_id = self.window_id;
         let mut contexts = Vec::new();
         let mut handler_depths_by_action_type = HashMap::<TypeId, usize>::default();
@@ -443,15 +401,17 @@ impl<'a> WindowContext<'a> {
                             .filter(|b| {
                                 (0..=action_depth).any(|depth| b.match_context(&contexts[depth..]))
                             })
+                            .cloned()
                             .collect(),
                     ))
                 } else {
                     None
                 }
             })
+            .collect()
     }
 
-    pub fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool {
+    pub(crate) fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool {
         let window_id = self.window_id;
         if let Some(focused_view_id) = self.focused_view_id() {
             let dispatch_path = self
@@ -473,8 +433,7 @@ impl<'a> WindowContext<'a> {
                 MatchResult::Pending => true,
                 MatchResult::Matches(matches) => {
                     for (view_id, action) in matches {
-                        if self.handle_dispatch_action_from_effect(Some(*view_id), action.as_ref())
-                        {
+                        if self.dispatch_action(Some(*view_id), action.as_ref()) {
                             self.keystroke_matcher.clear_pending();
                             handled_by = Some(action.boxed_clone());
                             break;
@@ -497,7 +456,7 @@ impl<'a> WindowContext<'a> {
         }
     }
 
-    pub fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
+    pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
         let mut mouse_events = SmallVec::<[_; 2]>::new();
         let mut notified_views: HashSet<usize> = Default::default();
         let window_id = self.window_id;
@@ -833,7 +792,7 @@ impl<'a> WindowContext<'a> {
         any_event_handled
     }
 
-    pub fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool {
+    pub(crate) fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool {
         let window_id = self.window_id;
         if let Some(focused_view_id) = self.window.focused_view_id {
             for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
@@ -852,7 +811,7 @@ impl<'a> WindowContext<'a> {
         false
     }
 
-    pub fn dispatch_key_up(&mut self, event: &KeyUpEvent) -> bool {
+    pub(crate) fn dispatch_key_up(&mut self, event: &KeyUpEvent) -> bool {
         let window_id = self.window_id;
         if let Some(focused_view_id) = self.window.focused_view_id {
             for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
@@ -871,7 +830,7 @@ impl<'a> WindowContext<'a> {
         false
     }
 
-    pub fn dispatch_modifiers_changed(&mut self, event: &ModifiersChangedEvent) -> bool {
+    pub(crate) fn dispatch_modifiers_changed(&mut self, event: &ModifiersChangedEvent) -> bool {
         let window_id = self.window_id;
         if let Some(focused_view_id) = self.window.focused_view_id {
             for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
@@ -890,7 +849,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 {
@@ -931,13 +890,52 @@ 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 = HashMap::default();
+        rendered_root.layout(
+            SizeConstraint::strict(window_size),
+            &mut new_parents,
+            &mut views_to_notify_if_ancestors_change,
+            refreshing,
+            self,
+        )?;
+
+        for (view_id, view_ids_to_notify) in views_to_notify_if_ancestors_change {
+            let mut current_view_id = view_id;
+            loop {
+                let old_parent_id = self.window.parents.get(&current_view_id);
+                let new_parent_id = new_parents.get(&current_view_id);
+                if old_parent_id.is_none() && new_parent_id.is_none() {
+                    break;
+                } else if old_parent_id == new_parent_id {
+                    current_view_id = *old_parent_id.unwrap();
+                } else {
+                    let window_id = self.window_id;
+                    for view_id_to_notify in view_ids_to_notify {
+                        self.notify_view(window_id, view_id_to_notify);
+                    }
+                    break;
+                }
+            }
+        }
+
+        self.window.parents = new_parents;
+        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(
@@ -1000,11 +998,7 @@ impl<'a> WindowContext<'a> {
         self.window.is_fullscreen
     }
 
-    pub(crate) fn handle_dispatch_action_from_effect(
-        &mut self,
-        view_id: Option<usize>,
-        action: &dyn Action,
-    ) -> bool {
+    pub(crate) fn dispatch_action(&mut self, view_id: Option<usize>, action: &dyn Action) -> bool {
         if let Some(view_id) = view_id {
             self.halt_action_dispatch = false;
             self.visit_dispatch_path(view_id, |view_id, capture_phase, cx| {
@@ -1050,9 +1044,7 @@ impl<'a> WindowContext<'a> {
         std::iter::once(view_id)
             .into_iter()
             .chain(std::iter::from_fn(move || {
-                if let Some(ParentId::View(parent_id)) =
-                    self.parents.get(&(self.window_id, view_id))
-                {
+                if let Some(parent_id) = self.window.parents.get(&view_id) {
                     view_id = *parent_id;
                     Some(view_id)
                 } else {
@@ -1061,16 +1053,6 @@ impl<'a> WindowContext<'a> {
             }))
     }
 
-    /// Returns the id of the parent of the given view, or none if the given
-    /// view is the root.
-    pub(crate) fn parent(&self, view_id: usize) -> Option<usize> {
-        if let Some(ParentId::View(view_id)) = self.parents.get(&(self.window_id, view_id)) {
-            Some(*view_id)
-        } else {
-            None
-        }
-    }
-
     // Traverses the parent tree. Walks down the tree toward the passed
     // view calling visit with true. Then walks back up the tree calling visit with false.
     // If `visit` returns false this function will immediately return.
@@ -1101,16 +1083,6 @@ impl<'a> WindowContext<'a> {
         self.window.focused_view_id
     }
 
-    pub fn is_child_focused(&self, view: &AnyViewHandle) -> bool {
-        if let Some(focused_view_id) = self.focused_view_id() {
-            self.ancestors(focused_view_id)
-                .skip(1) // Skip self id
-                .any(|parent| parent == view.view_id)
-        } else {
-            false
-        }
-    }
-
     pub fn window_bounds(&self) -> WindowBounds {
         self.window.platform_window.bounds()
     }
@@ -1153,27 +1125,27 @@ impl<'a> WindowContext<'a> {
         V: View,
         F: FnOnce(&mut ViewContext<V>) -> V,
     {
-        let root_view = self
-            .build_and_insert_view(ParentId::Root, |cx| Some(build_root_view(cx)))
-            .unwrap();
+        let root_view = self.add_view(|cx| build_root_view(cx));
         self.window.root_view = Some(root_view.clone().into_any());
         self.window.focused_view_id = Some(root_view.id());
         root_view
     }
 
-    pub(crate) fn build_and_insert_view<T, F>(
-        &mut self,
-        parent_id: ParentId,
-        build_view: F,
-    ) -> Option<ViewHandle<T>>
+    pub fn add_view<T, F>(&mut self, build_view: F) -> ViewHandle<T>
+    where
+        T: View,
+        F: FnOnce(&mut ViewContext<T>) -> T,
+    {
+        self.add_option_view(|cx| Some(build_view(cx))).unwrap()
+    }
+
+    pub fn add_option_view<T, F>(&mut self, build_view: F) -> Option<ViewHandle<T>>
     where
         T: View,
         F: FnOnce(&mut ViewContext<T>) -> Option<T>,
     {
         let window_id = self.window_id;
         let view_id = post_inc(&mut self.next_entity_id);
-        // Make sure we can tell child views about their parentu
-        self.parents.insert((window_id, view_id), parent_id);
         let mut cx = ViewContext::mutable(self, view_id);
         let handle = if let Some(view) = build_view(&mut cx) {
             let mut keymap_context = KeymapContext::default();
@@ -1193,7 +1165,6 @@ impl<'a> WindowContext<'a> {
                 .insert(view_id);
             Some(ViewHandle::new(window_id, view_id, &self.ref_counts))
         } else {
-            self.parents.remove(&(window_id, view_id));
             None
         };
         handle
@@ -1374,11 +1345,18 @@ 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) {
+            cx.new_parents.insert(self.view_id, cx.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,11 +33,14 @@ 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;
 use core::panic;
 use json::ToJson;
+use smallvec::SmallVec;
 use std::{
     any::Any,
     borrow::Cow,
@@ -54,7 +57,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 +214,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 +266,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 +447,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 +508,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 +600,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 +645,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 HashMap<usize, SmallVec<[usize; 2]>>,
+        refreshing: bool,
+        cx: &mut WindowContext,
+    ) -> Result<Vector2F>;
     fn paint(
         &mut self,
         scene: &mut SceneBuilder,
@@ -660,12 +670,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 HashMap<usize, SmallVec<[usize; 2]>>,
+        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/gpui/src/keymap_matcher/binding.rs 🔗

@@ -11,6 +11,16 @@ pub struct Binding {
     context_predicate: Option<KeymapContextPredicate>,
 }
 
+impl Clone for Binding {
+    fn clone(&self) -> Self {
+        Self {
+            action: self.action.boxed_clone(),
+            keystrokes: self.keystrokes.clone(),
+            context_predicate: self.context_predicate.clone(),
+        }
+    }
+}
+
 impl Binding {
     pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
         Self::load(keystrokes, Box::new(action), context).unwrap()

crates/project_panel/src/project_panel.rs 🔗

@@ -196,6 +196,7 @@ impl ProjectPanel {
             })
             .detach();
 
+            let view_id = cx.view_id();
             let mut this = Self {
                 project: project.clone(),
                 list: Default::default(),
@@ -206,7 +207,7 @@ impl ProjectPanel {
                 edit_state: None,
                 filename_editor,
                 clipboard_entry: None,
-                context_menu: cx.add_view(ContextMenu::new),
+                context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
                 dragged_entry_destination: None,
                 workspace: workspace.weak_handle(),
             };

crates/project_symbols/src/project_symbols.rs 🔗

@@ -318,10 +318,10 @@ mod tests {
             },
         );
 
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 
         // Create the project symbols view.
-        let symbols = cx.add_view(&workspace, |cx| {
+        let symbols = cx.add_view(window_id, |cx| {
             ProjectSymbols::new(
                 ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()),
                 cx,

crates/search/src/buffer_search.rs 🔗

@@ -670,13 +670,11 @@ mod tests {
                 cx,
             )
         });
-        let (_, root_view) = cx.add_window(|_| EmptyView);
+        let (window_id, _root_view) = cx.add_window(|_| EmptyView);
 
-        let editor = cx.add_view(&root_view, |cx| {
-            Editor::for_buffer(buffer.clone(), None, cx)
-        });
+        let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
 
-        let search_bar = cx.add_view(&root_view, |cx| {
+        let search_bar = cx.add_view(window_id, |cx| {
             let mut search_bar = BufferSearchBar::new(cx);
             search_bar.set_active_pane_item(Some(&editor), cx);
             search_bar.show(false, true, cx);

crates/search/src/project_search.rs 🔗

@@ -200,7 +200,7 @@ impl View for ProjectSearchView {
                     .flex(1., true)
             })
             .on_down(MouseButton::Left, |_, _, cx| {
-                cx.focus_parent_view();
+                cx.focus_parent();
             })
             .into_any_named("project search view")
         } else {
@@ -939,8 +939,6 @@ impl ToolbarItemView for ProjectSearchBar {
         self.subscription = None;
         self.active_project_search = None;
         if let Some(search) = active_pane_item.and_then(|i| i.downcast::<ProjectSearchView>()) {
-            let query_editor = search.read(cx).query_editor.clone();
-            cx.reparent(&query_editor);
             self.subscription = Some(cx.observe(&search, |_, _, cx| cx.notify()));
             self.active_project_search = Some(search);
             ToolbarItemLocation::PrimaryLeft {

crates/settings/src/settings_file.rs 🔗

@@ -84,7 +84,7 @@ mod tests {
         watch_files, watched_json::watch_settings_file, EditorSettings, Settings, SoftWrap,
     };
     use fs::FakeFs;
-    use gpui::{actions, elements::*, Action, Entity, View, ViewContext, WindowContext};
+    use gpui::{actions, elements::*, Action, Entity, TestAppContext, View, ViewContext};
     use theme::ThemeRegistry;
 
     struct TestView;
@@ -171,13 +171,12 @@ mod tests {
         let (window_id, _view) = cx.add_window(|_| TestView);
 
         // Test loading the keymap base at all
-        cx.read_window(window_id, |cx| {
-            assert_key_bindings_for(
-                cx,
-                vec![("backspace", &A), ("k", &ActivatePreviousPane)],
-                line!(),
-            );
-        });
+        assert_key_bindings_for(
+            window_id,
+            cx,
+            vec![("backspace", &A), ("k", &ActivatePreviousPane)],
+            line!(),
+        );
 
         // Test modifying the users keymap, while retaining the base keymap
         fs.save(
@@ -199,13 +198,12 @@ mod tests {
 
         cx.foreground().run_until_parked();
 
-        cx.read_window(window_id, |cx| {
-            assert_key_bindings_for(
-                cx,
-                vec![("backspace", &B), ("k", &ActivatePreviousPane)],
-                line!(),
-            );
-        });
+        assert_key_bindings_for(
+            window_id,
+            cx,
+            vec![("backspace", &B), ("k", &ActivatePreviousPane)],
+            line!(),
+        );
 
         // Test modifying the base, while retaining the users keymap
         fs.save(
@@ -223,31 +221,33 @@ mod tests {
 
         cx.foreground().run_until_parked();
 
-        cx.read_window(window_id, |cx| {
-            assert_key_bindings_for(
-                cx,
-                vec![("backspace", &B), ("[", &ActivatePrevItem)],
-                line!(),
-            );
-        });
+        assert_key_bindings_for(
+            window_id,
+            cx,
+            vec![("backspace", &B), ("[", &ActivatePrevItem)],
+            line!(),
+        );
     }
 
     fn assert_key_bindings_for<'a>(
-        cx: &WindowContext,
+        window_id: usize,
+        cx: &TestAppContext,
         actions: Vec<(&'static str, &'a dyn Action)>,
         line: u32,
     ) {
         for (key, action) in actions {
             // assert that...
             assert!(
-                cx.available_actions(0).any(|(_, bound_action, b)| {
-                    // action names match...
-                    bound_action.name() == action.name()
+                cx.available_actions(window_id, 0)
+                    .into_iter()
+                    .any(|(_, bound_action, b)| {
+                        // action names match...
+                        bound_action.name() == action.name()
                     && bound_action.namespace() == action.namespace()
                     // and key strokes contain the given key
                     && b.iter()
                         .any(|binding| binding.keystrokes().iter().any(|k| k.key == key))
-                }),
+                    }),
                 "On {} Failed to find {} with key binding {}",
                 line,
                 action.name(),

crates/terminal_view/src/terminal_button.rs 🔗

@@ -107,11 +107,12 @@ impl View for TerminalButton {
 
 impl TerminalButton {
     pub fn new(workspace: ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
+        let button_view_id = cx.view_id();
         cx.observe(&workspace, |_, _, cx| cx.notify()).detach();
         Self {
             workspace: workspace.downgrade(),
             popup_menu: cx.add_view(|cx| {
-                let mut menu = ContextMenu::new(cx);
+                let mut menu = ContextMenu::new(button_view_id, cx);
                 menu.set_position_mode(OverlayPositionMode::Local);
                 menu
             }),

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;
@@ -370,7 +370,7 @@ impl TerminalElement {
         f: impl Fn(&mut Terminal, Vector2F, E, &mut ModelContext<Terminal>),
     ) -> impl Fn(E, &mut TerminalView, &mut EventContext<TerminalView>) {
         move |event, _: &mut TerminalView, cx| {
-            cx.focus_parent_view();
+            cx.focus_parent();
             if let Some(conn_handle) = connection.upgrade(cx) {
                 conn_handle.update(cx, |terminal, cx| {
                     f(terminal, origin, event, cx);
@@ -408,7 +408,7 @@ impl TerminalElement {
             )
             // Update drag selections
             .on_drag(MouseButton::Left, move |event, _: &mut TerminalView, cx| {
-                if cx.is_parent_view_focused() {
+                if cx.is_self_focused() {
                     if let Some(conn_handle) = connection.upgrade(cx) {
                         conn_handle.update(cx, |terminal, cx| {
                             terminal.mouse_drag(event, origin);
@@ -444,7 +444,7 @@ impl TerminalElement {
                 },
             )
             .on_move(move |event, _: &mut TerminalView, cx| {
-                if cx.is_parent_view_focused() {
+                if cx.is_self_focused() {
                     if let Some(conn_handle) = connection.upgrade(cx) {
                         conn_handle.update(cx, |terminal, cx| {
                             terminal.mouse_move(&event, origin);
@@ -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/terminal_view/src/terminal_view.rs 🔗

@@ -2,7 +2,6 @@ mod persistence;
 pub mod terminal_button;
 pub mod terminal_element;
 
-use anyhow::anyhow;
 use context_menu::{ContextMenu, ContextMenuItem};
 use dirs::home_dir;
 use gpui::{
@@ -125,6 +124,7 @@ impl TerminalView {
         workspace_id: WorkspaceId,
         cx: &mut ViewContext<Self>,
     ) -> Self {
+        let view_id = cx.view_id();
         cx.observe(&terminal, |_, _, cx| cx.notify()).detach();
         cx.subscribe(&terminal, |this, _, event, cx| match event {
             Event::Wakeup => {
@@ -163,7 +163,7 @@ impl TerminalView {
             terminal,
             has_new_content: true,
             has_bell: false,
-            context_menu: cx.add_view(ContextMenu::new),
+            context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
             blink_state: true,
             blinking_on: false,
             blinking_paused: false,
@@ -627,16 +627,12 @@ impl Item for TerminalView {
                     })
                 });
 
-            let pane = pane
-                .upgrade(&cx)
-                .ok_or_else(|| anyhow!("pane was dropped"))?;
-            cx.update(|cx| {
-                let terminal = project.update(cx, |project, cx| {
-                    project.create_terminal(cwd, window_id, cx)
-                })?;
-
-                Ok(cx.add_view(&pane, |cx| TerminalView::new(terminal, workspace_id, cx)))
-            })
+            let terminal = project.update(&mut cx, |project, cx| {
+                project.create_terminal(cwd, window_id, cx)
+            })?;
+            Ok(pane.update(&mut cx, |_, cx| {
+                cx.add_view(|cx| TerminalView::new(terminal, workspace_id, cx))
+            })?)
         })
     }
 

crates/workspace/src/dock.rs 🔗

@@ -178,11 +178,7 @@ impl Dock {
         pane.update(cx, |pane, cx| {
             pane.set_active(false, cx);
         });
-        let pane_id = pane.id();
-        cx.subscribe(&pane, move |workspace, _, event, cx| {
-            workspace.handle_pane_event(pane_id, event, cx);
-        })
-        .detach();
+        cx.subscribe(&pane, Workspace::handle_pane_event).detach();
 
         Self {
             pane,
@@ -730,7 +726,7 @@ mod tests {
             self.update_workspace(|workspace, cx| Dock::move_dock(workspace, anchor, true, cx));
         }
 
-        pub fn hide_dock(&self) {
+        pub fn hide_dock(&mut self) {
             self.cx.dispatch_action(self.window_id, HideDock);
         }
 

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;
@@ -134,6 +134,7 @@ pub enum Event {
     RemoveItem { item_id: usize },
     Split(SplitDirection),
     ChangeItemTitle,
+    Focus,
 }
 
 pub struct Pane {
@@ -150,6 +151,7 @@ pub struct Pane {
     docked: Option<DockAnchor>,
     _background_actions: BackgroundActions,
     workspace: WeakViewHandle<Workspace>,
+    has_focus: bool,
 }
 
 pub struct ItemNavHistory {
@@ -226,8 +228,9 @@ impl Pane {
         background_actions: BackgroundActions,
         cx: &mut ViewContext<Self>,
     ) -> Self {
+        let pane_view_id = cx.view_id();
         let handle = cx.weak_handle();
-        let context_menu = cx.add_view(ContextMenu::new);
+        let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx));
         context_menu.update(cx, |menu, _| {
             menu.set_position_mode(OverlayPositionMode::Local)
         });
@@ -252,10 +255,11 @@ impl Pane {
                 kind: TabBarContextMenuKind::New,
                 handle: context_menu,
             },
-            tab_context_menu: cx.add_view(ContextMenu::new),
+            tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
             docked,
             _background_actions: background_actions,
             workspace,
+            has_focus: false,
         }
     }
 
@@ -272,6 +276,10 @@ impl Pane {
         cx.notify();
     }
 
+    pub fn has_focus(&self) -> bool {
+        self.has_focus
+    }
+
     pub fn set_docked(&mut self, docked: Option<DockAnchor>, cx: &mut ViewContext<Self>) {
         self.docked = docked;
         cx.notify();
@@ -537,7 +545,6 @@ impl Pane {
             // If the item already exists, move it to the desired destination and activate it
             pane.update(cx, |pane, cx| {
                 if existing_item_index != insertion_index {
-                    cx.reparent(item.as_any());
                     let existing_item_is_active = existing_item_index == pane.active_item_index;
 
                     // If the caller didn't specify a destination and the added item is already
@@ -567,7 +574,6 @@ impl Pane {
             });
         } else {
             pane.update(cx, |pane, cx| {
-                cx.reparent(item.as_any());
                 pane.items.insert(insertion_index, item);
                 if insertion_index <= pane.active_item_index {
                     pane.active_item_index += 1;
@@ -1764,7 +1770,7 @@ impl View for Pane {
                     self.render_blank_pane(&theme, cx)
                 })
                 .on_down(MouseButton::Left, |_, _, cx| {
-                    cx.focus_parent_view();
+                    cx.focus_parent();
                 })
                 .into_any()
             }
@@ -1798,6 +1804,7 @@ impl View for Pane {
     }
 
     fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        self.has_focus = true;
         self.toolbar.update(cx, |toolbar, cx| {
             toolbar.pane_focus_update(true, cx);
         });
@@ -1823,9 +1830,12 @@ impl View for Pane {
                     .insert(active_item.id(), focused.downgrade());
             }
         }
+
+        cx.emit(Event::Focus);
     }
 
     fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        self.has_focus = false;
         self.toolbar.update(cx, |toolbar, cx| {
             toolbar.pane_focus_update(false, cx);
         });
@@ -1998,7 +2008,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/shared_screen.rs 🔗

@@ -90,7 +90,7 @@ impl View for SharedScreen {
             .contained()
             .with_style(cx.global::<Settings>().theme.shared_screen)
         })
-        .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent_view())
+        .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent())
         .into_any()
     }
 }

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;
 
@@ -93,7 +93,6 @@ impl StatusBar {
     where
         T: 'static + StatusItemView,
     {
-        cx.reparent(item.as_any());
         self.left_items.push(Box::new(item));
         cx.notify();
     }
@@ -102,7 +101,6 @@ impl StatusBar {
     where
         T: 'static + StatusItemView,
     {
-        cx.reparent(item.as_any());
         self.right_items.push(Box::new(item));
         cx.notify();
     }
@@ -157,7 +155,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());

crates/workspace/src/workspace.rs 🔗

@@ -63,7 +63,7 @@ use crate::{
     persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace},
 };
 use lazy_static::lazy_static;
-use log::{error, warn};
+use log::warn;
 use notifications::{NotificationHandle, NotifyResultExt};
 pub use pane::*;
 pub use pane_group::*;
@@ -536,11 +536,7 @@ impl Workspace {
 
         let center_pane = cx
             .add_view(|cx| Pane::new(weak_handle.clone(), None, app_state.background_actions, cx));
-        let pane_id = center_pane.id();
-        cx.subscribe(&center_pane, move |this, _, event, cx| {
-            this.handle_pane_event(pane_id, event, cx)
-        })
-        .detach();
+        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
         cx.focus(&center_pane);
         cx.emit(Event::PaneAdded(center_pane.clone()));
         let dock = Dock::new(
@@ -1433,11 +1429,7 @@ impl Workspace {
                 cx,
             )
         });
-        let pane_id = pane.id();
-        cx.subscribe(&pane, move |this, _, event, cx| {
-            this.handle_pane_event(pane_id, event, cx)
-        })
-        .detach();
+        cx.subscribe(&pane, Self::handle_pane_event).detach();
         self.panes.push(pane.clone());
         cx.focus(&pane);
         cx.emit(Event::PaneAdded(pane.clone()));
@@ -1634,47 +1626,46 @@ impl Workspace {
 
     fn handle_pane_event(
         &mut self,
-        pane_id: usize,
+        pane: ViewHandle<Pane>,
         event: &pane::Event,
         cx: &mut ViewContext<Self>,
     ) {
-        if let Some(pane) = self.pane(pane_id) {
-            let is_dock = &pane == self.dock.pane();
-            match event {
-                pane::Event::Split(direction) if !is_dock => {
-                    self.split_pane(pane, *direction, cx);
+        let is_dock = &pane == self.dock.pane();
+        match event {
+            pane::Event::Split(direction) if !is_dock => {
+                self.split_pane(pane, *direction, cx);
+            }
+            pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
+            pane::Event::Remove if is_dock => Dock::hide(self, cx),
+            pane::Event::ActivateItem { local } => {
+                if *local {
+                    self.unfollow(&pane, cx);
                 }
-                pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
-                pane::Event::Remove if is_dock => Dock::hide(self, cx),
-                pane::Event::ActivateItem { local } => {
-                    if *local {
-                        self.unfollow(&pane, cx);
-                    }
-                    if &pane == self.active_pane() {
-                        self.active_item_path_changed(cx);
-                    }
+                if &pane == self.active_pane() {
+                    self.active_item_path_changed(cx);
                 }
-                pane::Event::ChangeItemTitle => {
-                    if pane == self.active_pane {
-                        self.active_item_path_changed(cx);
-                    }
-                    self.update_window_edited(cx);
+            }
+            pane::Event::ChangeItemTitle => {
+                if pane == self.active_pane {
+                    self.active_item_path_changed(cx);
                 }
-                pane::Event::RemoveItem { item_id } => {
-                    self.update_window_edited(cx);
-                    if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
-                        if entry.get().id() == pane.id() {
-                            entry.remove();
-                        }
+                self.update_window_edited(cx);
+            }
+            pane::Event::RemoveItem { item_id } => {
+                self.update_window_edited(cx);
+                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
+                    if entry.get().id() == pane.id() {
+                        entry.remove();
                     }
                 }
-                _ => {}
             }
-
-            self.serialize_workspace(cx);
-        } else if self.dock.visible_pane().is_none() {
-            error!("pane {} not found", pane_id);
+            pane::Event::Focus => {
+                self.handle_pane_focused(pane.clone(), cx);
+            }
+            _ => {}
         }
+
+        self.serialize_workspace(cx);
     }
 
     pub fn split_pane(
@@ -1773,10 +1764,6 @@ impl Workspace {
         &self.panes
     }
 
-    fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
-        self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
-    }
-
     pub fn active_pane(&self) -> &ViewHandle<Pane> {
         &self.active_pane
     }
@@ -2365,19 +2352,14 @@ impl Workspace {
         }
 
         for (pane, item) in items_to_activate {
-            let active_item_was_focused = pane
-                .read(cx)
-                .active_item()
-                .map(|active_item| cx.is_child_focused(active_item.as_any()))
-                .unwrap_or_default();
-
+            let pane_was_focused = pane.read(cx).has_focus();
             if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
                 pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
             } else {
                 Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
             }
 
-            if active_item_was_focused {
+            if pane_was_focused {
                 pane.update(cx, |pane, cx| pane.focus_active_item(cx));
             }
         }
@@ -2796,17 +2778,9 @@ impl View for Workspace {
             .into_any_named("workspace")
     }
 
-    fn focus_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext<Self>) {
+    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         if cx.is_self_focused() {
             cx.focus(&self.active_pane);
-        } else {
-            for pane in self.panes() {
-                let view = view.clone();
-                if pane.update(cx, |_, cx| view.id() == cx.view_id() || cx.is_child(view)) {
-                    self.handle_pane_focused(pane.clone(), cx);
-                    break;
-                }
-            }
         }
     }
 }
@@ -3154,10 +3128,10 @@ mod tests {
 
         let fs = FakeFs::new(cx.background());
         let project = Project::test(fs, [], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 
         // Adding an item with no ambiguity renders the tab without detail.
-        let item1 = cx.add_view(&workspace, |_| {
+        let item1 = cx.add_view(window_id, |_| {
             let mut item = TestItem::new();
             item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
             item
@@ -3169,7 +3143,7 @@ mod tests {
 
         // Adding an item that creates ambiguity increases the level of detail on
         // both tabs.
-        let item2 = cx.add_view(&workspace, |_| {
+        let item2 = cx.add_view(window_id, |_| {
             let mut item = TestItem::new();
             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
             item
@@ -3183,7 +3157,7 @@ mod tests {
         // Adding an item that creates ambiguity increases the level of detail only
         // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
         // we stop at the highest detail available.
-        let item3 = cx.add_view(&workspace, |_| {
+        let item3 = cx.add_view(window_id, |_| {
             let mut item = TestItem::new();
             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
             item
@@ -3223,10 +3197,10 @@ mod tests {
             project.worktrees(cx).next().unwrap().read(cx).id()
         });
 
-        let item1 = cx.add_view(&workspace, |cx| {
+        let item1 = cx.add_view(window_id, |cx| {
             TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
         });
-        let item2 = cx.add_view(&workspace, |cx| {
+        let item2 = cx.add_view(window_id, |cx| {
             TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
         });
 
@@ -3311,15 +3285,15 @@ mod tests {
         let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 
         // When there are no dirty items, there's nothing to do.
-        let item1 = cx.add_view(&workspace, |_| TestItem::new());
+        let item1 = cx.add_view(window_id, |_| TestItem::new());
         workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
         assert!(task.await.unwrap());
 
         // When there are dirty untitled items, prompt to save each one. If the user
         // cancels any prompt, then abort.
-        let item2 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true));
-        let item3 = cx.add_view(&workspace, |cx| {
+        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
+        let item3 = cx.add_view(window_id, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
@@ -3345,24 +3319,24 @@ mod tests {
         let project = Project::test(fs, None, cx).await;
         let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
 
-        let item1 = cx.add_view(&workspace, |cx| {
+        let item1 = cx.add_view(window_id, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
         });
-        let item2 = cx.add_view(&workspace, |cx| {
+        let item2 = cx.add_view(window_id, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_conflict(true)
                 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
         });
-        let item3 = cx.add_view(&workspace, |cx| {
+        let item3 = cx.add_view(window_id, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_conflict(true)
                 .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
         });
-        let item4 = cx.add_view(&workspace, |cx| {
+        let item4 = cx.add_view(window_id, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_project_items(&[TestProjectItem::new_untitled(cx)])
@@ -3456,7 +3430,7 @@ mod tests {
         // workspace items with multiple project entries.
         let single_entry_items = (0..=4)
             .map(|project_entry_id| {
-                cx.add_view(&workspace, |cx| {
+                cx.add_view(window_id, |cx| {
                     TestItem::new()
                         .with_dirty(true)
                         .with_project_items(&[TestProjectItem::new(
@@ -3467,7 +3441,7 @@ mod tests {
                 })
             })
             .collect::<Vec<_>>();
-        let item_2_3 = cx.add_view(&workspace, |cx| {
+        let item_2_3 = cx.add_view(window_id, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_singleton(false)
@@ -3476,7 +3450,7 @@ mod tests {
                     single_entry_items[3].read(cx).project_items[0].clone(),
                 ])
         });
-        let item_3_4 = cx.add_view(&workspace, |cx| {
+        let item_3_4 = cx.add_view(window_id, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_singleton(false)
@@ -3559,7 +3533,7 @@ mod tests {
         let project = Project::test(fs, [], cx).await;
         let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
 
-        let item = cx.add_view(&workspace, |cx| {
+        let item = cx.add_view(window_id, |cx| {
             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
         });
         let item_id = item.id();
@@ -3674,9 +3648,9 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, [], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
 
-        let item = cx.add_view(&workspace, |cx| {
+        let item = cx.add_view(window_id, |cx| {
             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
         });
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());