diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 1001493242..64768d1a47 100644 --- a/crates/collab/src/tests.rs +++ b/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, cx: &mut TestAppContext, ) -> ViewHandle { - let (_, root_view) = cx.add_window(|_| EmptyView); - cx.add_view(&root_view, |cx| Workspace::test_new(project.clone(), cx)) + struct WorkspaceContainer { + workspace: Option>, + } + + impl Entity for WorkspaceContainer { + type Event = (); + } + + impl View for WorkspaceContainer { + fn ui_name() -> &'static str { + "WorkspaceContainer" + } + + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + 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 } } diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index bdf43124d4..40f1173579 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/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() }) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 97fb82a5d6..69ca64360c 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/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 for AvatarRibbon { &mut self, constraint: gpui::SizeConstraint, _: &mut CollabTitlebarItem, - _: &mut ViewContext, + _: &mut LayoutContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { (constraint.max, ()) } diff --git a/crates/collab_ui/src/face_pile.rs b/crates/collab_ui/src/face_pile.rs index 839ea7c735..1bbceee9af 100644 --- a/crates/collab_ui/src/face_pile.rs +++ b/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 for FacePile { &mut self, constraint: gpui::SizeConstraint, view: &mut CollabTitlebarItem, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY); diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 441fbb84a6..4e0e776000 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/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, } -fn toggle_command_palette(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { - 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) { + 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::() { - let filter = cx.global::(); - 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>, ) -> 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::>(); + 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::() { + let filter = cx.global::(); + 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::>(); + let candidates = actions + .iter() + .enumerate() + .map(|(ix, command)| StringMatchCandidate { + id: ix, + string: command.name.to_string(), + char_bag: command.name.chars().collect(), + }) + .collect::>(); 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 diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index 0a4cd59384..7f821c06e2 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -177,9 +177,7 @@ impl View for ContextMenu { } impl ContextMenu { - pub fn new(cx: &mut ViewContext) -> Self { - let parent_view_id = cx.parent().unwrap(); - + pub fn new(parent_view_id: usize, cx: &mut ViewContext) -> Self { Self { show_count: 0, anchor_position: Default::default(), diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index aa93af4d4c..4b0c9b494a 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -144,8 +144,9 @@ impl View for CopilotButton { impl CopilotButton { pub fn new(cx: &mut ViewContext) -> 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 }); diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 17d142ba4b..d82c653a09 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/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) }); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5568ed79b5..2bb9869e6d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1227,6 +1227,7 @@ impl Editor { get_field_editor_theme: Option>, cx: &mut ViewContext, ) -> Self { + let editor_view_id = cx.view_id(); let display_map = cx.add_model(|cx| { let settings = cx.global::(); 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(), diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 3099bb640d..0f749bde48 100644 --- a/crates/editor/src/editor_tests.rs +++ b/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(); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7c43885763..d1ad1f2625 100644 --- a/crates/editor/src/element.rs +++ b/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, + cx: &mut LayoutContext, ) -> (f32, Vec) { let tooltip_style = cx.global::().theme.tooltip.clone(); let scroll_x = snapshot.scroll_anchor.offset.x(); @@ -1594,7 +1594,7 @@ impl Element for EditorElement { &mut self, constraint: SizeConstraint, editor: &mut Editor, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (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, ) }); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index df93326e9a..e971af943a 100644 --- a/crates/editor/src/items.rs +++ b/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::() .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))) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index caebc2714a..ee12b8bc6a 100644 --- a/crates/gpui/src/app.rs +++ b/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, 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>, views: HashMap<(usize, usize), Box>, views_metadata: HashMap<(usize, usize), ViewMetadata>, - pub(crate) parents: HashMap<(usize, usize), ParentId>, windows: HashMap, globals: HashMap>, element_states: HashMap>, @@ -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(&mut self, parent: &AnyViewHandle, build_view: F) -> ViewHandle - where - S: View, - F: FnOnce(&mut ViewContext) -> 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(&self, handle: &ViewHandle) -> &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::::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::>() { + 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 refreshing { - self.perform_window_refresh(); - } else { - self.update_windows(); + 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); + } + } + }); + } + + 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::>(); - 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::>(); - 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) { 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::>() { - 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) { - 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::>()) - .unwrap_or_default(); - let focused_parents = focused_id - .map(|focused_id| cx.ancestors(focused_id).collect::>()) - .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::>() { + 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::>() { + 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) { 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(&mut self, task_name: Option<&'static str>, f: F) -> Task @@ -2059,6 +2072,43 @@ pub struct WindowInvalidation { pub removed: Vec, } +#[derive(Debug)] +pub enum FocusEffect { + View { + window_id: usize, + view_id: Option, + 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, }, - Focus { - window_id: usize, - view_id: Option, - }, + 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 { - 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) -> 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(&mut self, build_view: F) -> ViewHandle - where - S: View, - F: FnOnce(&mut ViewContext) -> S, - { - self.window_context - .build_and_insert_view(ParentId::View(self.view_id), |cx| Some(build_view(cx))) - .unwrap() - } - - pub fn add_option_view(&mut self, build_view: F) -> Option> - where - S: View, - F: FnOnce(&mut ViewContext) -> Option, - { - 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(&mut self, handle: &H, mut callback: F) -> Subscription where E: Entity, @@ -3269,6 +3261,116 @@ impl 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, + views_to_notify_if_ancestors_change: &'c mut HashMap>, + 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, + views_to_notify_if_ancestors_change: &'c mut HashMap>, + 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> { + 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 DerefMut for LayoutContext<'_, '_, '_, V> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.view_context + } +} + +impl BorrowAppContext for LayoutContext<'_, '_, '_, V> { + fn read_with T>(&self, f: F) -> T { + BorrowAppContext::read_with(&*self.view_context, f) + } + + fn update T>(&mut self, f: F) -> T { + BorrowAppContext::update(&mut *self.view_context, f) + } +} + +impl BorrowWindowContext for LayoutContext<'_, '_, '_, V> { + fn read_with T>(&self, window_id: usize, f: F) -> T { + BorrowWindowContext::read_with(&*self.view_context, window_id, f) + } + + fn update 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>>, + child: Option, } impl Entity for View { @@ -5218,8 +5321,11 @@ mod tests { } impl super::View for View { - fn render(&mut self, _: &mut ViewContext) -> AnyElement { - Empty::new().into_any() + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + 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>> = 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, } impl Entity for ViewA { @@ -5399,8 +5502,11 @@ mod tests { } impl View for ViewA { - fn render(&mut self, _: &mut ViewContext) -> AnyElement { - Empty::new().into_any() + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + 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, } impl Entity for ViewB { @@ -5417,8 +5524,11 @@ mod tests { } impl View for ViewB { - fn render(&mut self, _: &mut ViewContext) -> AnyElement { - Empty::new().into_any() + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + 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, } impl Entity for View { @@ -5565,8 +5723,11 @@ mod tests { } impl super::View for View { - fn render(&mut self, _: &mut ViewContext) -> AnyElement { - Empty::new().into_any() + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + 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 @@ -5681,7 +5849,9 @@ mod tests { fn test_keystrokes_for_action(cx: &mut TestAppContext) { actions!(test, [Action1, Action2, GlobalAction]); - struct View1 {} + struct View1 { + child: ViewHandle, + } struct View2 {} impl Entity for View1 { @@ -5692,8 +5862,8 @@ mod tests { } impl super::View for View1 { - fn render(&mut self, _: &mut ViewContext) -> AnyElement { - Empty::new().into_any() + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + ChildView::new(&self.child, cx).into_any() } fn ui_name() -> &'static str { "View1" @@ -5708,17 +5878,19 @@ mod tests { } } - let (_, view_1) = cx.add_window(|_| View1 {}); - let view_2 = cx.add_view(&view_1, |cx| { - cx.focus_self(); - View2 {} + let (window_id, view_1) = cx.add_window(|cx| { + let view_2 = cx.add_view(|cx| { + cx.focus_self(); + View2 {} + }); + View1 { child: view_2 } }); + let view_2 = view_1.read_with(cx, |view, _| view.child.clone()); cx.update(|cx| { cx.add_action(|_: &mut View1, _: &Action1, _cx| {}); cx.add_action(|_: &mut View2, _: &Action2, _cx| {}); cx.add_global_action(|_: &GlobalAction, _| {}); - cx.add_bindings(vec![ Binding::new("a", Action1, Some("View1")), Binding::new("b", Action2, Some("View1 > View2")), @@ -5726,19 +5898,28 @@ mod tests { ]); }); - // Here we update the views to ensure that, even if they are on the stack, - // we can still retrieve keystrokes correctly. + let view_1_id = view_1.id(); view_1.update(cx, |_, cx| { view_2.update(cx, |_, cx| { // Sanity check + 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, + ); assert_eq!( - cx.keystrokes_for_action(view_1.id(), &Action1) + layout_cx + .keystrokes_for_action(view_1_id, &Action1) .unwrap() .as_slice(), &[Keystroke::parse("a").unwrap()] ); assert_eq!( - cx.keystrokes_for_action(view_2.id(), &Action2) + layout_cx + .keystrokes_for_action(view_2.id(), &Action2) .unwrap() .as_slice(), &[Keystroke::parse("b").unwrap()] @@ -5747,45 +5928,51 @@ mod tests { // The 'a' keystroke propagates up the view tree from view_2 // to view_1. The action, Action1, is handled by view_1. assert_eq!( - cx.keystrokes_for_action(view_2.id(), &Action1) + layout_cx + .keystrokes_for_action(view_2.id(), &Action1) .unwrap() .as_slice(), &[Keystroke::parse("a").unwrap()] ); // Actions that are handled below the current view don't have bindings - assert_eq!(cx.keystrokes_for_action(view_1.id(), &Action2), None); + assert_eq!(layout_cx.keystrokes_for_action(view_1_id, &Action2), None); // Actions that are handled in other branches of the tree should not have a binding - assert_eq!(cx.keystrokes_for_action(view_2.id(), &GlobalAction), None); - - // Check that global actions do not have a binding, even if a binding does exist in another view assert_eq!( - &available_actions(view_1.id(), cx), - &[ - ("test::Action1", vec![Keystroke::parse("a").unwrap()]), - ("test::GlobalAction", vec![]) - ], - ); - - // Check that view 1 actions and bindings are available even when called from view 2 - assert_eq!( - &available_actions(view_2.id(), cx), - &[ - ("test::Action1", vec![Keystroke::parse("a").unwrap()]), - ("test::Action2", vec![Keystroke::parse("b").unwrap()]), - ("test::GlobalAction", vec![]), - ], + layout_cx.keystrokes_for_action(view_2.id(), &GlobalAction), + None ); }); }); + // Check that global actions do not have a binding, even if a binding does exist in another view + assert_eq!( + &available_actions(window_id, view_1.id(), cx), + &[ + ("test::Action1", vec![Keystroke::parse("a").unwrap()]), + ("test::GlobalAction", vec![]) + ], + ); + + // Check that view 1 actions and bindings are available even when called from view 2 + assert_eq!( + &available_actions(window_id, view_2.id(), cx), + &[ + ("test::Action1", vec![Keystroke::parse("a").unwrap()]), + ("test::Action2", vec![Keystroke::parse("b").unwrap()]), + ("test::GlobalAction", vec![]), + ], + ); + // Produces a list of actions and key bindings fn available_actions( + window_id: usize, view_id: usize, - cx: &WindowContext, + cx: &TestAppContext, ) -> Vec<(&'static str, Vec)> { - cx.available_actions(view_id) + cx.available_actions(window_id, view_id) + .into_iter() .map(|(action_name, _, bindings)| { ( action_name, @@ -5917,8 +6104,8 @@ mod tests { #[crate::test(self)] #[should_panic(expected = "view dropped with pending condition")] async fn test_view_condition_panic_on_drop(cx: &mut TestAppContext) { - let (_, root_view) = cx.add_window(|_| TestView::default()); - let view = cx.add_view(&root_view, |_| TestView::default()); + let (window_id, _root_view) = cx.add_window(|_| TestView::default()); + let view = cx.add_view(window_id, |_| TestView::default()); let condition = view.condition(cx, |_, _| false); cx.update(|_| drop(view)); @@ -5951,10 +6138,12 @@ mod tests { ); }); - let view = cx.add_view(&root_view, |cx| { - cx.refresh_windows(); - View(0) - }); + let view = cx + .update_window(window_id, |cx| { + cx.refresh_windows(); + cx.add_view(|_| View(0)) + }) + .unwrap(); cx.update_window(window_id, |cx| { assert_eq!( diff --git a/crates/gpui/src/app/menu.rs b/crates/gpui/src/app/menu.rs index ed23296618..a2ac13984b 100644 --- a/crates/gpui/src/app/menu.rs +++ b/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 diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs index 2d079a6042..4af436a7b8 100644 --- a/crates/gpui/src/app/test_app_context.rs +++ b/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(&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(&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 dispatch_global_action(&self, action: A) { - self.cx.borrow_mut().dispatch_global_action_any(&action); + pub fn available_actions( + &self, + window_id: usize, + view_id: usize, + ) -> Vec<(&'static str, Box, SmallVec<[Binding; 1]>)> { + self.read_window(window_id, |cx| cx.available_actions(view_id)) + .unwrap_or_default() + } + + pub fn dispatch_global_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(&mut self, parent_handle: &AnyViewHandle, build_view: F) -> ViewHandle + pub fn add_view(&mut self, window_id: usize, build_view: F) -> ViewHandle where T: View, F: FnOnce(&mut ViewContext) -> 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(&mut self, callback: F) -> Subscription diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 94c4df2385..bd9bd6d2db 100644 --- a/crates/gpui/src/app/window.rs +++ b/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, pub(crate) focused_view_id: Option, + pub(crate) parents: HashMap, pub(crate) is_active: bool, pub(crate) is_fullscreen: bool, pub(crate) invalidation: Option, @@ -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> { - 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, SmallVec<[&Binding; 1]>)> { + ) -> Vec<(&'static str, Box, SmallVec<[Binding; 1]>)> { let window_id = self.window_id; let mut contexts = Vec::new(); let mut handler_depths_by_action_type = HashMap::::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 = 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::>() { @@ -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::>() { @@ -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::>() { @@ -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 { + 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(¤t_view_id); + let new_parent_id = new_parents.get(¤t_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 { 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, - action: &dyn Action, - ) -> bool { + pub(crate) fn dispatch_action(&mut self, view_id: Option, 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 { - 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, { - 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( - &mut self, - parent_id: ParentId, - build_view: F, - ) -> Option> + pub fn add_view(&mut self, build_view: F) -> ViewHandle + where + T: View, + F: FnOnce(&mut ViewContext) -> T, + { + self.add_option_view(|cx| Some(build_view(cx))).unwrap() + } + + pub fn add_option_view(&mut self, build_view: F) -> Option> where T: View, F: FnOnce(&mut ViewContext) -> Option, { 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 Element for ChildView { &mut self, constraint: SizeConstraint, _: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (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); diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 7de0bc10f5..e2c4af143c 100644 --- a/crates/gpui/src/elements.rs +++ b/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: 'static { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState); fn paint( @@ -211,7 +214,7 @@ trait AnyElementState { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> Vector2F; fn paint( @@ -263,7 +266,7 @@ impl> AnyElementState for ElementState { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> Vector2F { let result; *self = match mem::take(self) { @@ -444,7 +447,7 @@ impl AnyElement { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> Vector2F { self.state.layout(constraint, view, cx) } @@ -505,7 +508,7 @@ impl Element for AnyElement { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let size = self.layout(constraint, view, cx); (size, ()) @@ -597,7 +600,7 @@ impl> Element for ComponentHost { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, AnyElement) { let mut element = self.component.render(view, cx); let size = element.layout(constraint, view, cx); @@ -642,7 +645,14 @@ impl> Element for ComponentHost { } pub trait AnyRootElement { - fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result; + fn layout( + &mut self, + constraint: SizeConstraint, + new_parents: &mut HashMap, + views_to_notify_if_ancestors_change: &mut HashMap>, + refreshing: bool, + cx: &mut WindowContext, + ) -> Result; fn paint( &mut self, scene: &mut SceneBuilder, @@ -660,12 +670,27 @@ pub trait AnyRootElement { } impl AnyRootElement for RootElement { - fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result { + fn layout( + &mut self, + constraint: SizeConstraint, + new_parents: &mut HashMap, + views_to_notify_if_ancestors_change: &mut HashMap>, + refreshing: bool, + cx: &mut WindowContext, + ) -> Result { 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( diff --git a/crates/gpui/src/elements/align.rs b/crates/gpui/src/elements/align.rs index b3724c923f..165cfcf190 100644 --- a/crates/gpui/src/elements/align.rs +++ b/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 Element for Align { &mut self, mut constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let mut size = constraint.max; constraint.min = Vector2F::zero(); diff --git a/crates/gpui/src/elements/canvas.rs b/crates/gpui/src/elements/canvas.rs index 36ff4e2cf4..bbd8d0393c 100644 --- a/crates/gpui/src/elements/canvas.rs +++ b/crates/gpui/src/elements/canvas.rs @@ -34,7 +34,7 @@ where &mut self, constraint: crate::SizeConstraint, _: &mut V, - _: &mut crate::ViewContext, + _: &mut crate::LayoutContext, ) -> (Vector2F, Self::LayoutState) { let x = if constraint.max.x().is_finite() { constraint.max.x() diff --git a/crates/gpui/src/elements/clipped.rs b/crates/gpui/src/elements/clipped.rs index 85466972d3..a87dc3e773 100644 --- a/crates/gpui/src/elements/clipped.rs +++ b/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 { child: AnyElement, @@ -23,7 +25,7 @@ impl Element for Clipped { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { (self.child.layout(constraint, view, cx), ()) } diff --git a/crates/gpui/src/elements/constrained_box.rs b/crates/gpui/src/elements/constrained_box.rs index fc033c9b43..46916c74f1 100644 --- a/crates/gpui/src/elements/constrained_box.rs +++ b/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 { @@ -15,7 +15,7 @@ pub struct ConstrainedBox { pub enum Constraint { Static(SizeConstraint), - Dynamic(Box) -> SizeConstraint>), + Dynamic(Box) -> SizeConstraint>), } impl ToJson for Constraint { @@ -37,7 +37,8 @@ impl ConstrainedBox { pub fn dynamically( mut self, - constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext) -> SizeConstraint, + constraint: impl 'static + + FnMut(SizeConstraint, &mut V, &mut LayoutContext) -> SizeConstraint, ) -> Self { self.constraint = Constraint::Dynamic(Box::new(constraint)); self @@ -119,7 +120,7 @@ impl ConstrainedBox { &mut self, input_constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> SizeConstraint { match &mut self.constraint { Constraint::Static(constraint) => *constraint, @@ -138,7 +139,7 @@ impl Element for ConstrainedBox { &mut self, mut parent_constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let constraint = self.constraint(parent_constraint, view, cx); parent_constraint.min = parent_constraint.min.max(constraint.min); diff --git a/crates/gpui/src/elements/container.rs b/crates/gpui/src/elements/container.rs index 1cbef9a52e..3ce323db09 100644 --- a/crates/gpui/src/elements/container.rs +++ b/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 Element for Container { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let mut size_buffer = self.margin_size() + self.padding_size(); if !self.style.border.overlay { diff --git a/crates/gpui/src/elements/empty.rs b/crates/gpui/src/elements/empty.rs index d1a3cbafdb..42a3824bfc 100644 --- a/crates/gpui/src/elements/empty.rs +++ b/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 Element for Empty { &mut self, constraint: SizeConstraint, _: &mut V, - _: &mut ViewContext, + _: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let x = if constraint.max.x().is_finite() && !self.collapsed { constraint.max.x() diff --git a/crates/gpui/src/elements/expanded.rs b/crates/gpui/src/elements/expanded.rs index 8a59191b7a..1fb935b2b8 100644 --- a/crates/gpui/src/elements/expanded.rs +++ b/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 Element for Expanded { &mut self, mut constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { if self.full_width { constraint.min.set_x(constraint.max.x()); diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index 7fee0006f7..e0e8dfc215 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/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 Flex { remaining_flex: &mut f32, cross_axis_max: &mut f32, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) { let cross_axis = self.axis.invert(); for child in &mut self.children { @@ -125,7 +125,7 @@ impl Element for Flex { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let mut total_flex = None; let mut fixed_space = 0.0; @@ -214,7 +214,7 @@ impl Element for Flex { } 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 Element for FlexItem { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let size = self.child.layout(constraint, view, cx); (size, ()) diff --git a/crates/gpui/src/elements/hook.rs b/crates/gpui/src/elements/hook.rs index cb22760285..310b3c25eb 100644 --- a/crates/gpui/src/elements/hook.rs +++ b/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 { @@ -36,7 +36,7 @@ impl Element for Hook { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let size = self.child.layout(constraint, view, cx); if let Some(handler) = self.after_layout.as_mut() { diff --git a/crates/gpui/src/elements/image.rs b/crates/gpui/src/elements/image.rs index 162446c1f0..98c5ae6226 100644 --- a/crates/gpui/src/elements/image.rs +++ b/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 Element for Image { &mut self, constraint: SizeConstraint, _: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let data = match &self.source { ImageSource::Path(path) => match cx.asset_cache.png(path) { diff --git a/crates/gpui/src/elements/keystroke_label.rs b/crates/gpui/src/elements/keystroke_label.rs index b179d78127..c011649b2e 100644 --- a/crates/gpui/src/elements/keystroke_label.rs +++ b/crates/gpui/src/elements/keystroke_label.rs @@ -39,7 +39,7 @@ impl Element for KeystrokeLabel { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, AnyElement) { let mut element = if let Some(keystrokes) = cx.keystrokes_for_action(self.view_id, self.action.as_ref()) diff --git a/crates/gpui/src/elements/label.rs b/crates/gpui/src/elements/label.rs index 2669f4d5f2..9499841b3d 100644 --- a/crates/gpui/src/elements/label.rs +++ b/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 Element for Label { &mut self, constraint: SizeConstraint, _: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let runs = self.compute_runs(); let line = cx.text_layout_cache().layout_str( diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 84043f5e1f..ca73196c8b 100644 --- a/crates/gpui/src/elements/list.rs +++ b/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 Element for List { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let state = &mut *self.state.0.borrow_mut(); let size = constraint.max; @@ -452,7 +453,7 @@ impl StateInner { existing_element: Option<&ListItem>, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> Option>>> { 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, + _: &mut LayoutContext, ) -> (Vector2F, ()) { (self.size, ()) } diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 8abcf5f835..ed624922d5 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/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 Element for MouseEventHandler { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { (self.child.layout(constraint, view, cx), ()) } diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 0abd1231e2..0f7e4a35c6 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/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 Element for Overlay { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let constraint = if self.anchor_position.is_some() { SizeConstraint::new(Vector2F::zero(), cx.window_size()) diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index 37e15541f4..0e78cc07fb 100644 --- a/crates/gpui/src/elements/resizable.rs +++ b/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 Element for Resizable { &mut self, constraint: crate::SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { (self.child.layout(constraint, view, cx), ()) } diff --git a/crates/gpui/src/elements/stack.rs b/crates/gpui/src/elements/stack.rs index 348a9b4a3b..196c04d203 100644 --- a/crates/gpui/src/elements/stack.rs +++ b/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 Element for Stack { &mut self, mut constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let mut size = constraint.min; let mut children = self.children.iter_mut(); diff --git a/crates/gpui/src/elements/svg.rs b/crates/gpui/src/elements/svg.rs index 0f75be82fd..5444221322 100644 --- a/crates/gpui/src/elements/svg.rs +++ b/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 Element for Svg { &mut self, constraint: SizeConstraint, _: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { match cx.asset_cache.svg(&self.path) { Ok(tree) => { diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index a92581f6c8..829511c62f 100644 --- a/crates/gpui/src/elements/text.rs +++ b/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 Element for Text { &mut self, constraint: SizeConstraint, _: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (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); diff --git a/crates/gpui/src/elements/tooltip.rs b/crates/gpui/src/elements/tooltip.rs index a879b996d5..7b4892fc1c 100644 --- a/crates/gpui/src/elements/tooltip.rs +++ b/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 Element for Tooltip { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let size = self.child.layout(constraint, view, cx); if let Some(tooltip) = self.tooltip.as_mut() { diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index e75425dc3f..9ccd57b291 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/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 Element for UniformList { &mut self, constraint: SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { if constraint.max.y().is_infinite() { unimplemented!( diff --git a/crates/gpui/src/keymap_matcher/binding.rs b/crates/gpui/src/keymap_matcher/binding.rs index c1cfd14e82..aa40e8c6af 100644 --- a/crates/gpui/src/keymap_matcher/binding.rs +++ b/crates/gpui/src/keymap_matcher/binding.rs @@ -11,6 +11,16 @@ pub struct Binding { context_predicate: Option, } +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(keystrokes: &str, action: A, context: Option<&str>) -> Self { Self::load(keystrokes, Box::new(action), context).unwrap() diff --git a/crates/gpui/src/keymap_matcher/keymap_context.rs b/crates/gpui/src/keymap_matcher/keymap_context.rs index b1a449edf3..be61fea531 100644 --- a/crates/gpui/src/keymap_matcher/keymap_context.rs +++ b/crates/gpui/src/keymap_matcher/keymap_context.rs @@ -44,7 +44,7 @@ impl KeymapContext { } } -#[derive(Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum KeymapContextPredicate { Identifier(String), Equal(String, String), diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index ec80bd245a..7602ff7db8 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/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(), }; diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 6720ef93de..25828f17ca 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/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, diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index ee5a2e8332..91284a545f 100644 --- a/crates/search/src/buffer_search.rs +++ b/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); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index ea29f9cfda..d68cad71d8 100644 --- a/crates/search/src/project_search.rs +++ b/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::()) { - 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 { diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index eb9f1508c2..6402a07f5e 100644 --- a/crates/settings/src/settings_file.rs +++ b/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(), diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index 8edf03f527..a92f7285b5 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -107,11 +107,12 @@ impl View for TerminalButton { impl TerminalButton { pub fn new(workspace: ViewHandle, cx: &mut ViewContext) -> 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 }), diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 392b050f43..ae2342cd97 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/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), ) -> impl Fn(E, &mut TerminalView, &mut EventContext) { 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 for TerminalElement { &mut self, constraint: gpui::SizeConstraint, view: &mut TerminalView, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { let settings = cx.global::(); let font_cache = cx.font_cache(); diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 3fcbf39887..dfb2334dc5 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/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 { + 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)) + })?) }) } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 8ac432dc47..7efcb7f9d3 100644 --- a/crates/workspace/src/dock.rs +++ b/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); } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 3d1e477941..96320a4baf 100644 --- a/crates/workspace/src/pane.rs +++ b/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, _background_actions: BackgroundActions, workspace: WeakViewHandle, + has_focus: bool, } pub struct ItemNavHistory { @@ -226,8 +228,9 @@ impl Pane { background_actions: BackgroundActions, cx: &mut ViewContext, ) -> 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, cx: &mut ViewContext) { 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.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.has_focus = false; self.toolbar.update(cx, |toolbar, cx| { toolbar.pane_focus_update(false, cx); }); @@ -1998,7 +2008,7 @@ impl Element for PaneBackdrop { &mut self, constraint: gpui::SizeConstraint, view: &mut V, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let size = self.child.layout(constraint, view, cx); (size, ()) diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index 47c802d938..5cc54a6a7f 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -90,7 +90,7 @@ impl View for SharedScreen { .contained() .with_style(cx.global::().theme.shared_screen) }) - .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent_view()) + .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent()) .into_any() } } diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index ed629745a8..6463ab7d24 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -147,7 +147,7 @@ impl Sidebar { } }), ]; - cx.reparent(&view); + self.items.push(Item { icon_path, tooltip, diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs index 60bc1f81f5..b4de6b3575 100644 --- a/crates/workspace/src/status_bar.rs +++ b/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 for StatusBarElement { &mut self, mut constraint: SizeConstraint, view: &mut StatusBar, - cx: &mut ViewContext, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let max_width = constraint.max.x(); constraint.min = vec2f(0., constraint.min.y()); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index cbac091128..d69484caa7 100644 --- a/crates/workspace/src/workspace.rs +++ b/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(¢er_pane, move |this, _, event, cx| { - this.handle_pane_event(pane_id, event, cx) - }) - .detach(); + cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); cx.focus(¢er_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, event: &pane::Event, cx: &mut ViewContext, ) { - 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); - } - 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); - } - } - pane::Event::ChangeItemTitle => { - if pane == self.active_pane { - self.active_item_path_changed(cx); - } - 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(); - } - } - } - _ => {} + let is_dock = &pane == self.dock.pane(); + match event { + pane::Event::Split(direction) if !is_dock => { + self.split_pane(pane, *direction, cx); } - - self.serialize_workspace(cx); - } else if self.dock.visible_pane().is_none() { - error!("pane {} not found", pane_id); + 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); + } + } + pane::Event::ChangeItemTitle => { + if pane == self.active_pane { + self.active_item_path_changed(cx); + } + 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(); + } + } + } + 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> { - self.panes.iter().find(|pane| pane.id() == pane_id).cloned() - } - pub fn active_pane(&self) -> &ViewHandle { &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) { + fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { 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::>(); - 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());