Detailed changes
@@ -12,7 +12,10 @@ use client::{
use collections::{HashMap, HashSet};
use fs::FakeFs;
use futures::{channel::oneshot, StreamExt as _};
-use gpui::{executor::Deterministic, test::EmptyView, ModelHandle, TestAppContext, ViewHandle};
+use gpui::{
+ elements::*, executor::Deterministic, AnyElement, Entity, ModelHandle, TestAppContext, View,
+ ViewContext, ViewHandle, WeakViewHandle,
+};
use language::LanguageRegistry;
use parking_lot::Mutex;
use project::{Project, WorktreeId};
@@ -462,8 +465,41 @@ impl TestClient {
project: &ModelHandle<Project>,
cx: &mut TestAppContext,
) -> ViewHandle<Workspace> {
- let (_, root_view) = cx.add_window(|_| EmptyView);
- cx.add_view(&root_view, |cx| Workspace::test_new(project.clone(), cx))
+ struct WorkspaceContainer {
+ workspace: Option<WeakViewHandle<Workspace>>,
+ }
+
+ impl Entity for WorkspaceContainer {
+ type Event = ();
+ }
+
+ impl View for WorkspaceContainer {
+ fn ui_name() -> &'static str {
+ "WorkspaceContainer"
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+ if let Some(workspace) = self
+ .workspace
+ .as_ref()
+ .and_then(|workspace| workspace.upgrade(cx))
+ {
+ ChildView::new(&workspace, cx).into_any()
+ } else {
+ Empty::new().into_any()
+ }
+ }
+ }
+
+ // We use a workspace container so that we don't need to remove the window in order to
+ // drop the workspace and we can use a ViewHandle instead.
+ let (window_id, container) = cx.add_window(|_| WorkspaceContainer { workspace: None });
+ let workspace = cx.add_view(window_id, |cx| Workspace::test_new(project.clone(), cx));
+ container.update(cx, |container, cx| {
+ container.workspace = Some(workspace.downgrade());
+ cx.notify();
+ });
+ workspace
}
}
@@ -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()
})
@@ -14,8 +14,8 @@ use gpui::{
geometry::{rect::RectF, vector::vec2f, PathBuilder},
json::{self, ToJson},
platform::{CursorStyle, MouseButton},
- AppContext, Entity, ImageData, ModelHandle, SceneBuilder, Subscription, View, ViewContext,
- ViewHandle, WeakViewHandle,
+ AppContext, Entity, ImageData, LayoutContext, ModelHandle, SceneBuilder, Subscription, View,
+ ViewContext, ViewHandle, WeakViewHandle,
};
use project::Project;
use settings::Settings;
@@ -165,6 +165,7 @@ impl CollabTitlebarItem {
}),
);
+ let view_id = cx.view_id();
Self {
workspace: workspace.weak_handle(),
project,
@@ -172,7 +173,7 @@ impl CollabTitlebarItem {
client,
contacts_popover: None,
user_menu: cx.add_view(|cx| {
- let mut menu = ContextMenu::new(cx);
+ let mut menu = ContextMenu::new(view_id, cx);
menu.set_position_mode(OverlayPositionMode::Local);
menu
}),
@@ -865,7 +866,7 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
&mut self,
constraint: gpui::SizeConstraint,
_: &mut CollabTitlebarItem,
- _: &mut ViewContext<CollabTitlebarItem>,
+ _: &mut LayoutContext<CollabTitlebarItem>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
(constraint.max, ())
}
@@ -7,7 +7,7 @@ use gpui::{
},
json::ToJson,
serde_json::{self, json},
- AnyElement, Axis, Element, SceneBuilder, ViewContext,
+ AnyElement, Axis, Element, LayoutContext, SceneBuilder, ViewContext,
};
use crate::CollabTitlebarItem;
@@ -34,7 +34,7 @@ impl Element<CollabTitlebarItem> for FacePile {
&mut self,
constraint: gpui::SizeConstraint,
view: &mut CollabTitlebarItem,
- cx: &mut ViewContext<CollabTitlebarItem>,
+ cx: &mut LayoutContext<CollabTitlebarItem>,
) -> (Vector2F, Self::LayoutState) {
debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
@@ -2,7 +2,7 @@ use collections::CommandPaletteFilter;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions, elements::*, keymap_matcher::Keystroke, Action, AppContext, Element, MouseState,
- ViewContext, WindowContext,
+ ViewContext,
};
use picker::{Picker, PickerDelegate, PickerEvent};
use settings::Settings;
@@ -41,47 +41,17 @@ struct Command {
keystrokes: Vec<Keystroke>,
}
-fn toggle_command_palette(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
- let workspace = cx.handle();
- let focused_view_id = cx.focused_view_id().unwrap_or_else(|| workspace.id());
-
- cx.window_context().defer(move |cx| {
- // Build the delegate before the workspace is put on the stack so we can find it when
- // computing the actions. We should really not allow available_actions to be called
- // if it's not reliable however.
- let delegate = CommandPaletteDelegate::new(focused_view_id, cx);
- workspace.update(cx, |workspace, cx| {
- workspace.toggle_modal(cx, |_, cx| cx.add_view(|cx| Picker::new(delegate, cx)));
- })
+fn toggle_command_palette(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+ let focused_view_id = cx.focused_view_id().unwrap_or_else(|| cx.view_id());
+ workspace.toggle_modal(cx, |_, cx| {
+ cx.add_view(|cx| Picker::new(CommandPaletteDelegate::new(focused_view_id), cx))
});
}
impl CommandPaletteDelegate {
- pub fn new(focused_view_id: usize, cx: &mut WindowContext) -> Self {
- let actions = cx
- .available_actions(focused_view_id)
- .filter_map(|(name, action, bindings)| {
- if cx.has_global::<CommandPaletteFilter>() {
- let filter = cx.global::<CommandPaletteFilter>();
- if filter.filtered_namespaces.contains(action.namespace()) {
- return None;
- }
- }
-
- Some(Command {
- name: humanize_action_name(name),
- action,
- keystrokes: bindings
- .iter()
- .map(|binding| binding.keystrokes())
- .last()
- .map_or(Vec::new(), |keystrokes| keystrokes.to_vec()),
- })
- })
- .collect();
-
+ pub fn new(focused_view_id: usize) -> Self {
Self {
- actions,
+ actions: Default::default(),
matches: vec![],
selected_ix: 0,
focused_view_id,
@@ -111,17 +81,46 @@ impl PickerDelegate for CommandPaletteDelegate {
query: String,
cx: &mut ViewContext<Picker<Self>>,
) -> gpui::Task<()> {
- let candidates = self
- .actions
- .iter()
- .enumerate()
- .map(|(ix, command)| StringMatchCandidate {
- id: ix,
- string: command.name.to_string(),
- char_bag: command.name.chars().collect(),
- })
- .collect::<Vec<_>>();
+ let window_id = cx.window_id();
+ let view_id = self.focused_view_id;
cx.spawn(move |picker, mut cx| async move {
+ let actions = cx
+ .available_actions(window_id, view_id)
+ .into_iter()
+ .filter_map(|(name, action, bindings)| {
+ let filtered = cx.read(|cx| {
+ if cx.has_global::<CommandPaletteFilter>() {
+ let filter = cx.global::<CommandPaletteFilter>();
+ filter.filtered_namespaces.contains(action.namespace())
+ } else {
+ false
+ }
+ });
+
+ if filtered {
+ None
+ } else {
+ Some(Command {
+ name: humanize_action_name(name),
+ action,
+ keystrokes: bindings
+ .iter()
+ .map(|binding| binding.keystrokes())
+ .last()
+ .map_or(Vec::new(), |keystrokes| keystrokes.to_vec()),
+ })
+ }
+ })
+ .collect::<Vec<_>>();
+ let candidates = actions
+ .iter()
+ .enumerate()
+ .map(|(ix, command)| StringMatchCandidate {
+ id: ix,
+ string: command.name.to_string(),
+ char_bag: command.name.chars().collect(),
+ })
+ .collect::<Vec<_>>();
let matches = if query.is_empty() {
candidates
.into_iter()
@@ -147,6 +146,7 @@ impl PickerDelegate for CommandPaletteDelegate {
picker
.update(&mut cx, |picker, _| {
let delegate = picker.delegate_mut();
+ delegate.actions = actions;
delegate.matches = matches;
if delegate.matches.is_empty() {
delegate.selected_ix = 0;
@@ -304,8 +304,8 @@ mod tests {
});
let project = Project::test(app_state.fs.clone(), [], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let editor = cx.add_view(&workspace, |cx| {
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let editor = cx.add_view(window_id, |cx| {
let mut editor = Editor::single_line(None, cx);
editor.set_text("abc", cx);
editor
@@ -177,9 +177,7 @@ impl View for ContextMenu {
}
impl ContextMenu {
- pub fn new(cx: &mut ViewContext<Self>) -> Self {
- let parent_view_id = cx.parent().unwrap();
-
+ pub fn new(parent_view_id: usize, cx: &mut ViewContext<Self>) -> Self {
Self {
show_count: 0,
anchor_position: Default::default(),
@@ -144,8 +144,9 @@ impl View for CopilotButton {
impl CopilotButton {
pub fn new(cx: &mut ViewContext<Self>) -> Self {
+ let button_view_id = cx.view_id();
let menu = cx.add_view(|cx| {
- let mut menu = ContextMenu::new(cx);
+ let mut menu = ContextMenu::new(button_view_id, cx);
menu.set_position_mode(OverlayPositionMode::Local);
menu
});
@@ -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)
});
@@ -1227,6 +1227,7 @@ impl Editor {
get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
cx: &mut ViewContext<Self>,
) -> Self {
+ let editor_view_id = cx.view_id();
let display_map = cx.add_model(|cx| {
let settings = cx.global::<Settings>();
let style = build_style(&*settings, get_field_editor_theme.as_deref(), None, cx);
@@ -1274,7 +1275,8 @@ impl Editor {
background_highlights: Default::default(),
nav_history: None,
context_menu: None,
- mouse_context_menu: cx.add_view(context_menu::ContextMenu::new),
+ mouse_context_menu: cx
+ .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)),
completion_tasks: Default::default(),
next_completion_id: 0,
available_code_actions: Default::default(),
@@ -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();
@@ -30,8 +30,8 @@ use gpui::{
json::{self, ToJson},
platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
text_layout::{self, Line, RunStyle, TextLayoutCache},
- AnyElement, Axis, Border, CursorRegion, Element, EventContext, MouseRegion, Quad, SceneBuilder,
- SizeConstraint, ViewContext, WindowContext,
+ AnyElement, Axis, Border, CursorRegion, Element, EventContext, LayoutContext, MouseRegion,
+ Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
};
use itertools::Itertools;
use json::json;
@@ -1388,7 +1388,7 @@ impl EditorElement {
line_layouts: &[text_layout::Line],
include_root: bool,
editor: &mut Editor,
- cx: &mut ViewContext<Editor>,
+ cx: &mut LayoutContext<Editor>,
) -> (f32, Vec<BlockLayout>) {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let scroll_x = snapshot.scroll_anchor.offset.x();
@@ -1594,7 +1594,7 @@ impl Element<Editor> for EditorElement {
&mut self,
constraint: SizeConstraint,
editor: &mut Editor,
- cx: &mut ViewContext<Editor>,
+ cx: &mut LayoutContext<Editor>,
) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.max;
if size.x().is_infinite() {
@@ -2565,10 +2565,18 @@ mod tests {
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (size, mut state) = editor.update(cx, |editor, cx| {
+ let mut new_parents = Default::default();
+ let mut notify_views_if_parents_change = Default::default();
+ let mut layout_cx = LayoutContext::new(
+ cx,
+ &mut new_parents,
+ &mut notify_views_if_parents_change,
+ false,
+ );
element.layout(
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
editor,
- cx,
+ &mut layout_cx,
)
});
@@ -3,7 +3,7 @@ use crate::{
movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor,
Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
};
-use anyhow::{anyhow, Context, Result};
+use anyhow::{Context, Result};
use collections::HashSet;
use futures::future::try_join_all;
use gpui::{
@@ -864,16 +864,13 @@ impl Item for Editor {
let buffer = project_item
.downcast::<Buffer>()
.context("Project item at stored path was not a buffer")?;
- let pane = pane
- .upgrade(&cx)
- .ok_or_else(|| anyhow!("pane was dropped"))?;
- Ok(cx.update(|cx| {
- cx.add_view(&pane, |cx| {
+ Ok(pane.update(&mut cx, |_, cx| {
+ cx.add_view(|cx| {
let mut editor = Editor::for_buffer(buffer, Some(project), cx);
editor.read_scroll_position_from_db(item_id, workspace_id, cx);
editor
})
- }))
+ })?)
})
})
.unwrap_or_else(|error| Task::ready(Err(error)))
@@ -25,6 +25,7 @@ use std::{
use anyhow::{anyhow, Context, Result};
use parking_lot::Mutex;
use postage::oneshot;
+use smallvec::SmallVec;
use smol::prelude::*;
use util::ResultExt;
use uuid::Uuid;
@@ -336,11 +337,20 @@ impl AsyncAppContext {
self.0
.borrow_mut()
.update_window(window_id, |window| {
- window.handle_dispatch_action_from_effect(Some(view_id), action);
+ window.dispatch_action(Some(view_id), action);
})
.ok_or_else(|| anyhow!("window not found"))
}
+ pub fn available_actions(
+ &self,
+ window_id: usize,
+ view_id: usize,
+ ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
+ self.read_window(window_id, |cx| cx.available_actions(view_id))
+ .unwrap_or_default()
+ }
+
pub fn has_window(&self, window_id: usize) -> bool {
self.read(|cx| cx.windows.contains_key(&window_id))
}
@@ -442,7 +452,6 @@ pub struct AppContext {
models: HashMap<usize, Box<dyn AnyModel>>,
views: HashMap<(usize, usize), Box<dyn AnyView>>,
views_metadata: HashMap<(usize, usize), ViewMetadata>,
- pub(crate) parents: HashMap<(usize, usize), ParentId>,
windows: HashMap<usize, Window>,
globals: HashMap<TypeId, Box<dyn Any>>,
element_states: HashMap<ElementStateId, Box<dyn Any>>,
@@ -505,7 +514,6 @@ impl AppContext {
models: Default::default(),
views: Default::default(),
views_metadata: Default::default(),
- parents: Default::default(),
windows: Default::default(),
globals: Default::default(),
element_states: Default::default(),
@@ -1365,9 +1373,9 @@ impl AppContext {
}));
let mut window = Window::new(window_id, platform_window, self, build_root_view);
- let scene = WindowContext::mutable(self, &mut window, window_id)
- .build_scene()
- .expect("initial scene should not error");
+ let mut cx = WindowContext::mutable(self, &mut window, window_id);
+ cx.layout(false).expect("initial layout should not error");
+ let scene = cx.paint().expect("initial paint should not error");
window.platform_window.present_scene(scene);
window
}
@@ -1384,18 +1392,6 @@ impl AppContext {
self.update_window(window_id, |cx| cx.replace_root_view(build_root_view))
}
- pub fn add_view<S, F>(&mut self, parent: &AnyViewHandle, build_view: F) -> ViewHandle<S>
- where
- S: View,
- F: FnOnce(&mut ViewContext<S>) -> S,
- {
- self.update_window(parent.window_id, |cx| {
- cx.build_and_insert_view(ParentId::View(parent.view_id), |cx| Some(build_view(cx)))
- .unwrap()
- })
- .unwrap()
- }
-
pub fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
if let Some(view) = self.views.get(&(handle.window_id, handle.view_id)) {
view.as_any().downcast_ref().expect("downcast is type safe")
@@ -1456,6 +1452,7 @@ impl AppContext {
let mut view = self.views.remove(&(window_id, view_id)).unwrap();
view.release(self);
let change_focus_to = self.windows.get_mut(&window_id).and_then(|window| {
+ window.parents.remove(&view_id);
window
.invalidation
.get_or_insert_with(Default::default)
@@ -1467,10 +1464,14 @@ impl AppContext {
None
}
});
- self.parents.remove(&(window_id, view_id));
if let Some(view_id) = change_focus_to {
- self.handle_focus_effect(window_id, Some(view_id));
+ self.pending_effects
+ .push_back(Effect::Focus(FocusEffect::View {
+ window_id,
+ view_id: Some(view_id),
+ is_forced: false,
+ }));
}
self.pending_effects
@@ -1491,7 +1492,10 @@ impl AppContext {
self.flushing_effects = true;
let mut refreshing = false;
+ let mut updated_windows = HashSet::default();
+ let mut focus_effects = HashMap::<usize, FocusEffect>::default();
loop {
+ self.remove_dropped_entities();
if let Some(effect) = self.pending_effects.pop_front() {
match effect {
Effect::Subscription {
@@ -1564,8 +1568,15 @@ impl AppContext {
self.handle_entity_release_effect(view_id, view.as_any())
}
- Effect::Focus { window_id, view_id } => {
- self.handle_focus_effect(window_id, view_id);
+ Effect::Focus(mut effect) => {
+ if focus_effects
+ .get(&effect.window_id())
+ .map_or(false, |prev_effect| prev_effect.is_forced())
+ {
+ effect.force();
+ }
+
+ focus_effects.insert(effect.window_id(), effect);
}
Effect::FocusObservation {
@@ -1606,7 +1617,22 @@ impl AppContext {
Effect::ActivateWindow {
window_id,
is_active,
- } => self.handle_window_activation_effect(window_id, is_active),
+ } => {
+ if self.handle_window_activation_effect(window_id, is_active)
+ && is_active
+ {
+ focus_effects
+ .entry(window_id)
+ .or_insert_with(|| FocusEffect::View {
+ window_id,
+ view_id: self
+ .read_window(window_id, |cx| cx.focused_view_id())
+ .flatten(),
+ is_forced: true,
+ })
+ .force();
+ }
+ }
Effect::WindowFullscreenObservation {
window_id,
@@ -1665,14 +1691,32 @@ impl AppContext {
),
}
self.pending_notifications.clear();
- self.remove_dropped_entities();
} else {
- self.remove_dropped_entities();
+ for window_id in self.windows.keys().cloned().collect::<Vec<_>>() {
+ self.update_window(window_id, |cx| {
+ let invalidation = if refreshing {
+ let mut invalidation =
+ cx.window.invalidation.take().unwrap_or_default();
+ invalidation
+ .updated
+ .extend(cx.window.rendered_views.keys().copied());
+ Some(invalidation)
+ } else {
+ cx.window.invalidation.take()
+ };
+
+ if let Some(invalidation) = invalidation {
+ let appearance = cx.window.platform_window.appearance();
+ cx.invalidate(invalidation, appearance);
+ if cx.layout(refreshing).log_err().is_some() {
+ updated_windows.insert(window_id);
+ }
+ }
+ });
+ }
- if refreshing {
- self.perform_window_refresh();
- } else {
- self.update_windows();
+ for (_, effect) in focus_effects.drain() {
+ self.handle_focus_effect(effect);
}
if self.pending_effects.is_empty() {
@@ -1680,6 +1724,14 @@ impl AppContext {
callback(self);
}
+ for window_id in updated_windows.drain() {
+ self.update_window(window_id, |cx| {
+ if let Some(scene) = cx.paint().log_err() {
+ cx.window.platform_window.present_scene(scene);
+ }
+ });
+ }
+
if self.pending_effects.is_empty() {
self.flushing_effects = false;
self.pending_notifications.clear();
@@ -1694,21 +1746,6 @@ impl AppContext {
}
}
- fn update_windows(&mut self) {
- let window_ids = self.windows.keys().cloned().collect::<Vec<_>>();
- for window_id in window_ids {
- self.update_window(window_id, |cx| {
- if let Some(mut invalidation) = cx.window.invalidation.take() {
- let appearance = cx.window.platform_window.appearance();
- cx.invalidate(&mut invalidation, appearance);
- if let Some(scene) = cx.build_scene().log_err() {
- cx.window.platform_window.present_scene(scene);
- }
- }
- });
- }
- }
-
fn window_was_resized(&mut self, window_id: usize) {
self.pending_effects
.push_back(Effect::ResizeWindow { window_id });
@@ -1752,23 +1789,6 @@ impl AppContext {
self.pending_effects.push_back(Effect::RefreshWindows);
}
- fn perform_window_refresh(&mut self) {
- let window_ids = self.windows.keys().cloned().collect::<Vec<_>>();
- for window_id in window_ids {
- self.update_window(window_id, |cx| {
- let mut invalidation = cx.window.invalidation.take().unwrap_or_default();
- invalidation
- .updated
- .extend(cx.window.rendered_views.keys().copied());
- cx.invalidate(&mut invalidation, cx.window.platform_window.appearance());
- cx.refreshing = true;
- if let Some(scene) = cx.build_scene().log_err() {
- cx.window.platform_window.present_scene(scene);
- }
- });
- }
- }
-
fn emit_global_event(&mut self, payload: Box<dyn Any>) {
let type_id = (&*payload).type_id();
@@ -1849,69 +1869,58 @@ impl AppContext {
});
}
- fn handle_window_activation_effect(&mut self, window_id: usize, active: bool) {
+ fn handle_window_activation_effect(&mut self, window_id: usize, active: bool) -> bool {
self.update_window(window_id, |cx| {
if cx.window.is_active == active {
- return;
+ return false;
}
cx.window.is_active = active;
- if let Some(focused_id) = cx.window.focused_view_id {
- for view_id in cx.ancestors(focused_id).collect::<Vec<_>>() {
- cx.update_any_view(view_id, |view, cx| {
- if active {
- view.focus_in(focused_id, cx, view_id);
- } else {
- view.focus_out(focused_id, cx, view_id);
- }
- });
- }
- }
-
let mut observations = cx.window_activation_observations.clone();
observations.emit(window_id, |callback| callback(active, cx));
- });
+ true
+ })
+ .unwrap_or(false)
}
- fn handle_focus_effect(&mut self, window_id: usize, mut focused_id: Option<usize>) {
- let mut blurred_id = None;
+ fn handle_focus_effect(&mut self, effect: FocusEffect) {
+ let window_id = effect.window_id();
self.update_window(window_id, |cx| {
- if cx.window.focused_view_id == focused_id {
- focused_id = None;
- return;
- }
- blurred_id = cx.window.focused_view_id;
+ let focused_id = match effect {
+ FocusEffect::View { view_id, .. } => view_id,
+ FocusEffect::ViewParent { view_id, .. } => cx.ancestors(view_id).skip(1).next(),
+ };
+
+ let focus_changed = cx.window.focused_view_id != focused_id;
+ let blurred_id = cx.window.focused_view_id;
cx.window.focused_view_id = focused_id;
- let blurred_parents = blurred_id
- .map(|blurred_id| cx.ancestors(blurred_id).collect::<Vec<_>>())
- .unwrap_or_default();
- let focused_parents = focused_id
- .map(|focused_id| cx.ancestors(focused_id).collect::<Vec<_>>())
- .unwrap_or_default();
-
- if let Some(blurred_id) = blurred_id {
- for view_id in blurred_parents.iter().copied() {
- if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
- view.focus_out(blurred_id, cx, view_id);
- cx.views.insert((window_id, view_id), view);
+ if focus_changed {
+ if let Some(blurred_id) = blurred_id {
+ for view_id in cx.ancestors(blurred_id).collect::<Vec<_>>() {
+ if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
+ view.focus_out(blurred_id, cx, view_id);
+ cx.views.insert((window_id, view_id), view);
+ }
}
- }
- let mut subscriptions = cx.focus_observations.clone();
- subscriptions.emit(blurred_id, |callback| callback(false, cx));
+ let mut subscriptions = cx.focus_observations.clone();
+ subscriptions.emit(blurred_id, |callback| callback(false, cx));
+ }
}
- if let Some(focused_id) = focused_id {
- for view_id in focused_parents {
- if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
- view.focus_in(focused_id, cx, view_id);
- cx.views.insert((window_id, view_id), view);
+ if focus_changed || effect.is_forced() {
+ if let Some(focused_id) = focused_id {
+ for view_id in cx.ancestors(focused_id).collect::<Vec<_>>() {
+ if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
+ view.focus_in(focused_id, cx, view_id);
+ cx.views.insert((window_id, view_id), view);
+ }
}
- }
- let mut subscriptions = cx.focus_observations.clone();
- subscriptions.emit(focused_id, |callback| callback(true, cx));
+ let mut subscriptions = cx.focus_observations.clone();
+ subscriptions.emit(focused_id, |callback| callback(true, cx));
+ }
}
});
}
@@ -1963,7 +1972,11 @@ impl AppContext {
pub fn focus(&mut self, window_id: usize, view_id: Option<usize>) {
self.pending_effects
- .push_back(Effect::Focus { window_id, view_id });
+ .push_back(Effect::Focus(FocusEffect::View {
+ window_id,
+ view_id,
+ is_forced: false,
+ }));
}
fn spawn_internal<F, Fut, T>(&mut self, task_name: Option<&'static str>, f: F) -> Task<T>
@@ -2059,6 +2072,43 @@ pub struct WindowInvalidation {
pub removed: Vec<usize>,
}
+#[derive(Debug)]
+pub enum FocusEffect {
+ View {
+ window_id: usize,
+ view_id: Option<usize>,
+ is_forced: bool,
+ },
+ ViewParent {
+ window_id: usize,
+ view_id: usize,
+ is_forced: bool,
+ },
+}
+
+impl FocusEffect {
+ fn window_id(&self) -> usize {
+ match self {
+ FocusEffect::View { window_id, .. } => *window_id,
+ FocusEffect::ViewParent { window_id, .. } => *window_id,
+ }
+ }
+
+ fn is_forced(&self) -> bool {
+ match self {
+ FocusEffect::View { is_forced, .. } => *is_forced,
+ FocusEffect::ViewParent { is_forced, .. } => *is_forced,
+ }
+ }
+
+ fn force(&mut self) {
+ match self {
+ FocusEffect::View { is_forced, .. } => *is_forced = true,
+ FocusEffect::ViewParent { is_forced, .. } => *is_forced = true,
+ }
+ }
+}
+
pub enum Effect {
Subscription {
entity_id: usize,
@@ -2104,10 +2154,7 @@ pub enum Effect {
view_id: usize,
view: Box<dyn AnyView>,
},
- Focus {
- window_id: usize,
- view_id: Option<usize>,
- },
+ Focus(FocusEffect),
FocusObservation {
view_id: usize,
subscription_id: usize,
@@ -2223,11 +2270,7 @@ impl Debug for Effect {
.debug_struct("Effect::ViewRelease")
.field("view_id", view_id)
.finish(),
- Effect::Focus { window_id, view_id } => f
- .debug_struct("Effect::Focus")
- .field("window_id", window_id)
- .field("view_id", view_id)
- .finish(),
+ Effect::Focus(focus) => f.debug_tuple("Effect::Focus").field(focus).finish(),
Effect::FocusObservation {
view_id,
subscription_id,
@@ -2795,10 +2838,6 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
WeakViewHandle::new(self.window_id, self.view_id)
}
- pub fn parent(&self) -> Option<usize> {
- self.window_context.parent(self.view_id)
- }
-
pub fn window_id(&self) -> usize {
self.window_id
}
@@ -2849,30 +2888,15 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
self.window.focused_view_id == Some(self.view_id)
}
- pub fn is_parent_view_focused(&self) -> bool {
- if let Some(parent_view_id) = self.ancestors(self.view_id).next().clone() {
- self.focused_view_id() == Some(parent_view_id)
- } else {
- false
- }
- }
-
- pub fn focus_parent_view(&mut self) {
- let next = self.ancestors(self.view_id).next().clone();
- if let Some(parent_view_id) = next {
- let window_id = self.window_id;
- self.window_context.focus(window_id, Some(parent_view_id));
- }
- }
-
- pub fn is_child(&self, view: impl Into<AnyViewHandle>) -> bool {
- let view = view.into();
- if self.window_id != view.window_id {
- return false;
- }
- self.ancestors(view.view_id)
- .skip(1) // Skip self id
- .any(|parent| parent == self.view_id)
+ pub fn focus_parent(&mut self) {
+ let window_id = self.window_id;
+ let view_id = self.view_id;
+ self.pending_effects
+ .push_back(Effect::Focus(FocusEffect::ViewParent {
+ window_id,
+ view_id,
+ is_forced: false,
+ }));
}
pub fn blur(&mut self) {
@@ -2902,38 +2926,6 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
});
}
- pub fn add_view<S, F>(&mut self, build_view: F) -> ViewHandle<S>
- where
- S: View,
- F: FnOnce(&mut ViewContext<S>) -> S,
- {
- self.window_context
- .build_and_insert_view(ParentId::View(self.view_id), |cx| Some(build_view(cx)))
- .unwrap()
- }
-
- pub fn add_option_view<S, F>(&mut self, build_view: F) -> Option<ViewHandle<S>>
- where
- S: View,
- F: FnOnce(&mut ViewContext<S>) -> Option<S>,
- {
- self.window_context
- .build_and_insert_view(ParentId::View(self.view_id), build_view)
- }
-
- pub fn reparent(&mut self, view_handle: &AnyViewHandle) {
- if self.window_id != view_handle.window_id {
- panic!("Can't reparent view to a view from a different window");
- }
- self.parents
- .remove(&(view_handle.window_id, view_handle.view_id));
- let new_parent_id = self.view_id;
- self.parents.insert(
- (view_handle.window_id, view_handle.view_id),
- ParentId::View(new_parent_id),
- );
- }
-
pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
where
E: Entity,
@@ -3269,6 +3261,116 @@ impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
}
}
+pub struct LayoutContext<'a, 'b, 'c, V: View> {
+ view_context: &'c mut ViewContext<'a, 'b, V>,
+ new_parents: &'c mut HashMap<usize, usize>,
+ views_to_notify_if_ancestors_change: &'c mut HashMap<usize, SmallVec<[usize; 2]>>,
+ pub refreshing: bool,
+}
+
+impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
+ pub fn new(
+ view_context: &'c mut ViewContext<'a, 'b, V>,
+ new_parents: &'c mut HashMap<usize, usize>,
+ views_to_notify_if_ancestors_change: &'c mut HashMap<usize, SmallVec<[usize; 2]>>,
+ refreshing: bool,
+ ) -> Self {
+ Self {
+ view_context,
+ new_parents,
+ views_to_notify_if_ancestors_change,
+ refreshing,
+ }
+ }
+
+ pub fn view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
+ self.view_context
+ }
+
+ /// Return keystrokes that would dispatch the given action on the given view.
+ pub(crate) fn keystrokes_for_action(
+ &mut self,
+ view_id: usize,
+ action: &dyn Action,
+ ) -> Option<SmallVec<[Keystroke; 2]>> {
+ self.notify_if_view_ancestors_change(view_id);
+
+ let window_id = self.window_id;
+ let mut contexts = Vec::new();
+ let mut handler_depth = None;
+ for (i, view_id) in self.ancestors(view_id).enumerate() {
+ if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
+ if let Some(actions) = self.actions.get(&view_metadata.type_id) {
+ if actions.contains_key(&action.as_any().type_id()) {
+ handler_depth = Some(i);
+ }
+ }
+ contexts.push(view_metadata.keymap_context.clone());
+ }
+ }
+
+ if self.global_actions.contains_key(&action.as_any().type_id()) {
+ handler_depth = Some(contexts.len())
+ }
+
+ self.keystroke_matcher
+ .bindings_for_action_type(action.as_any().type_id())
+ .find_map(|b| {
+ handler_depth
+ .map(|highest_handler| {
+ if (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..])) {
+ Some(b.keystrokes().into())
+ } else {
+ None
+ }
+ })
+ .flatten()
+ })
+ }
+
+ fn notify_if_view_ancestors_change(&mut self, view_id: usize) {
+ let self_view_id = self.view_id;
+ self.views_to_notify_if_ancestors_change
+ .entry(view_id)
+ .or_default()
+ .push(self_view_id);
+ }
+}
+
+impl<'a, 'b, 'c, V: View> Deref for LayoutContext<'a, 'b, 'c, V> {
+ type Target = ViewContext<'a, 'b, V>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.view_context
+ }
+}
+
+impl<V: View> DerefMut for LayoutContext<'_, '_, '_, V> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.view_context
+ }
+}
+
+impl<V: View> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
+ fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
+ BorrowAppContext::read_with(&*self.view_context, f)
+ }
+
+ fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
+ BorrowAppContext::update(&mut *self.view_context, f)
+ }
+}
+
+impl<V: View> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
+ fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
+ BorrowWindowContext::read_with(&*self.view_context, window_id, f)
+ }
+
+ fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
+ BorrowWindowContext::update(&mut *self.view_context, window_id, f)
+ }
+}
+
pub struct EventContext<'a, 'b, 'c, V: View> {
view_context: &'c mut ViewContext<'a, 'b, V>,
pub(crate) handled: bool,
@@ -4521,9 +4623,9 @@ mod tests {
}
}
- let (_, root_view) = cx.add_window(|cx| View::new(None, cx));
- let handle_1 = cx.add_view(&root_view, |cx| View::new(None, cx));
- let handle_2 = cx.add_view(&root_view, |cx| View::new(Some(handle_1.clone()), cx));
+ let (window_id, _root_view) = cx.add_window(|cx| View::new(None, cx));
+ let handle_1 = cx.add_view(window_id, |cx| View::new(None, cx));
+ let handle_2 = cx.add_view(window_id, |cx| View::new(Some(handle_1.clone()), cx));
assert_eq!(cx.read(|cx| cx.views.len()), 3);
handle_1.update(cx, |view, cx| {
@@ -4683,8 +4785,8 @@ mod tests {
type Event = String;
}
- let (_, handle_1) = cx.add_window(|_| TestView::default());
- let handle_2 = cx.add_view(&handle_1, |_| TestView::default());
+ let (window_id, handle_1) = cx.add_window(|_| TestView::default());
+ let handle_2 = cx.add_view(window_id, |_| TestView::default());
let handle_3 = cx.add_model(|_| Model);
handle_1.update(cx, |_, cx| {
@@ -4910,9 +5012,9 @@ mod tests {
type Event = ();
}
- let (_, root_view) = cx.add_window(|_| TestView::default());
- let observing_view = cx.add_view(&root_view, |_| TestView::default());
- let emitting_view = cx.add_view(&root_view, |_| TestView::default());
+ let (window_id, _root_view) = cx.add_window(|_| TestView::default());
+ let observing_view = cx.add_view(window_id, |_| TestView::default());
+ let emitting_view = cx.add_view(window_id, |_| TestView::default());
let observing_model = cx.add_model(|_| Model);
let observed_model = cx.add_model(|_| Model);
@@ -5037,8 +5139,8 @@ mod tests {
type Event = ();
}
- let (_, root_view) = cx.add_window(|_| TestView::default());
- let observing_view = cx.add_view(&root_view, |_| TestView::default());
+ let (window_id, _root_view) = cx.add_window(|_| TestView::default());
+ let observing_view = cx.add_view(window_id, |_| TestView::default());
let observing_model = cx.add_model(|_| Model);
let observed_model = cx.add_model(|_| Model);
@@ -5160,9 +5262,9 @@ mod tests {
}
}
- let (_, root_view) = cx.add_window(|_| View);
- let observing_view = cx.add_view(&root_view, |_| View);
- let observed_view = cx.add_view(&root_view, |_| View);
+ let (window_id, _root_view) = cx.add_window(|_| View);
+ let observing_view = cx.add_view(window_id, |_| View);
+ let observed_view = cx.add_view(window_id, |_| View);
let observation_count = Rc::new(RefCell::new(0));
observing_view.update(cx, |_, cx| {
@@ -5211,6 +5313,7 @@ mod tests {
struct View {
name: String,
events: Arc<Mutex<Vec<String>>>,
+ child: Option<AnyViewHandle>,
}
impl Entity for View {
@@ -5218,8 +5321,11 @@ mod tests {
}
impl super::View for View {
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+ self.child
+ .as_ref()
+ .map(|child| ChildView::new(child, cx).into_any())
+ .unwrap_or(Empty::new().into_any())
}
fn ui_name() -> &'static str {
@@ -5243,11 +5349,22 @@ mod tests {
let (window_id, view_1) = cx.add_window(|_| View {
events: view_events.clone(),
name: "view 1".to_string(),
+ child: None,
});
- let view_2 = cx.add_view(&view_1, |_| View {
- events: view_events.clone(),
- name: "view 2".to_string(),
- });
+ let view_2 = cx
+ .update_window(window_id, |cx| {
+ let view_2 = cx.add_view(|_| View {
+ events: view_events.clone(),
+ name: "view 2".to_string(),
+ child: None,
+ });
+ view_1.update(cx, |view_1, cx| {
+ view_1.child = Some(view_2.clone().into_any());
+ cx.notify();
+ });
+ view_2
+ })
+ .unwrap();
let observed_events: Arc<Mutex<Vec<String>>> = Default::default();
view_1.update(cx, |_, cx| {
@@ -5284,44 +5401,25 @@ mod tests {
assert_eq!(mem::take(&mut *observed_events.lock()), Vec::<&str>::new());
view_1.update(cx, |_, cx| {
- // Ensure focus events are sent for all intermediate focuses
+ // Ensure only the last focus event is honored.
cx.focus(&view_2);
cx.focus(&view_1);
cx.focus(&view_2);
});
- cx.read_window(window_id, |cx| {
- assert!(cx.is_child_focused(&view_1));
- assert!(!cx.is_child_focused(&view_2));
- });
assert_eq!(
mem::take(&mut *view_events.lock()),
- [
- "view 1 blurred",
- "view 2 focused",
- "view 2 blurred",
- "view 1 focused",
- "view 1 blurred",
- "view 2 focused"
- ],
+ ["view 1 blurred", "view 2 focused"],
);
assert_eq!(
mem::take(&mut *observed_events.lock()),
[
- "view 2 observed view 1's blur",
- "view 1 observed view 2's focus",
- "view 1 observed view 2's blur",
- "view 2 observed view 1's focus",
"view 2 observed view 1's blur",
"view 1 observed view 2's focus"
]
);
view_1.update(cx, |_, cx| cx.focus(&view_1));
- cx.read_window(window_id, |cx| {
- assert!(!cx.is_child_focused(&view_1));
- assert!(!cx.is_child_focused(&view_2));
- });
assert_eq!(
mem::take(&mut *view_events.lock()),
["view 2 blurred", "view 1 focused"],
@@ -5347,7 +5445,11 @@ mod tests {
]
);
- view_1.update(cx, |_, _| drop(view_2));
+ println!("=====================");
+ view_1.update(cx, |view, _| {
+ drop(view_2);
+ view.child = None;
+ });
assert_eq!(mem::take(&mut *view_events.lock()), ["view 1 focused"]);
assert_eq!(mem::take(&mut *observed_events.lock()), Vec::<&str>::new());
}
@@ -5392,6 +5494,7 @@ mod tests {
fn test_dispatch_action(cx: &mut AppContext) {
struct ViewA {
id: usize,
+ child: Option<AnyViewHandle>,
}
impl Entity for ViewA {
@@ -5399,8 +5502,11 @@ mod tests {
}
impl View for ViewA {
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+ self.child
+ .as_ref()
+ .map(|child| ChildView::new(child, cx).into_any())
+ .unwrap_or(Empty::new().into_any())
}
fn ui_name() -> &'static str {
@@ -5410,6 +5516,7 @@ mod tests {
struct ViewB {
id: usize,
+ child: Option<AnyViewHandle>,
}
impl Entity for ViewB {
@@ -5417,8 +5524,11 @@ mod tests {
}
impl View for ViewB {
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+ self.child
+ .as_ref()
+ .map(|child| ChildView::new(child, cx).into_any())
+ .unwrap_or(Empty::new().into_any())
}
fn ui_name() -> &'static str {
@@ -5455,7 +5565,7 @@ mod tests {
if view.id != 1 {
cx.add_view(|cx| {
cx.propagate_action(); // Still works on a nested ViewContext
- ViewB { id: 5 }
+ ViewB { id: 5, child: None }
});
}
actions.borrow_mut().push(format!("{} b", view.id));
@@ -5493,13 +5603,41 @@ mod tests {
})
.detach();
- let (window_id, view_1) = cx.add_window(Default::default(), |_| ViewA { id: 1 });
- let view_2 = cx.add_view(&view_1, |_| ViewB { id: 2 });
- let view_3 = cx.add_view(&view_2, |_| ViewA { id: 3 });
- let view_4 = cx.add_view(&view_3, |_| ViewB { id: 4 });
+ let (window_id, view_1) =
+ cx.add_window(Default::default(), |_| ViewA { id: 1, child: None });
+ let view_2 = cx
+ .update_window(window_id, |cx| {
+ let child = cx.add_view(|_| ViewB { id: 2, child: None });
+ view_1.update(cx, |view, cx| {
+ view.child = Some(child.clone().into_any());
+ cx.notify();
+ });
+ child
+ })
+ .unwrap();
+ let view_3 = cx
+ .update_window(window_id, |cx| {
+ let child = cx.add_view(|_| ViewA { id: 3, child: None });
+ view_2.update(cx, |view, cx| {
+ view.child = Some(child.clone().into_any());
+ cx.notify();
+ });
+ child
+ })
+ .unwrap();
+ let view_4 = cx
+ .update_window(window_id, |cx| {
+ let child = cx.add_view(|_| ViewB { id: 4, child: None });
+ view_3.update(cx, |view, cx| {
+ view.child = Some(child.clone().into_any());
+ cx.notify();
+ });
+ child
+ })
+ .unwrap();
cx.update_window(window_id, |cx| {
- cx.handle_dispatch_action_from_effect(Some(view_4.id()), &Action("bar".to_string()))
+ cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string()))
});
assert_eq!(
@@ -5520,13 +5658,32 @@ mod tests {
// Remove view_1, which doesn't propagate the action
- let (window_id, view_2) = cx.add_window(Default::default(), |_| ViewB { id: 2 });
- let view_3 = cx.add_view(&view_2, |_| ViewA { id: 3 });
- let view_4 = cx.add_view(&view_3, |_| ViewB { id: 4 });
+ let (window_id, view_2) =
+ cx.add_window(Default::default(), |_| ViewB { id: 2, child: None });
+ let view_3 = cx
+ .update_window(window_id, |cx| {
+ let child = cx.add_view(|_| ViewA { id: 3, child: None });
+ view_2.update(cx, |view, cx| {
+ view.child = Some(child.clone().into_any());
+ cx.notify();
+ });
+ child
+ })
+ .unwrap();
+ let view_4 = cx
+ .update_window(window_id, |cx| {
+ let child = cx.add_view(|_| ViewB { id: 4, child: None });
+ view_3.update(cx, |view, cx| {
+ view.child = Some(child.clone().into_any());
+ cx.notify();
+ });
+ child
+ })
+ .unwrap();
actions.borrow_mut().clear();
cx.update_window(window_id, |cx| {
- cx.handle_dispatch_action_from_effect(Some(view_4.id()), &Action("bar".to_string()))
+ cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string()))
});
assert_eq!(
@@ -5558,6 +5715,7 @@ mod tests {
struct View {
id: usize,
keymap_context: KeymapContext,
+ child: Option<AnyViewHandle>,
}
impl Entity for View {
@@ -5565,8 +5723,11 @@ mod tests {
}
impl super::View for View {
- fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
- Empty::new().into_any()
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+ self.child
+ .as_ref()
+ .map(|child| ChildView::new(child, cx).into_any())
+ .unwrap_or(Empty::new().into_any())
}
fn ui_name() -> &'static str {
@@ -5583,6 +5744,7 @@ mod tests {
View {
id,
keymap_context: KeymapContext::default(),
+ child: None,
}
}
}
@@ -5597,11 +5759,17 @@ mod tests {
view_3.keymap_context.add_identifier("b");
view_3.keymap_context.add_identifier("c");
- let (window_id, view_1) = cx.add_window(Default::default(), |_| view_1);
- let view_2 = cx.add_view(&view_1, |_| view_2);
- let _view_3 = cx.add_view(&view_2, |cx| {
- cx.focus_self();
- view_3
+ let (window_id, _view_1) = cx.add_window(Default::default(), |cx| {
+ let view_2 = cx.add_view(|cx| {
+ let view_3 = cx.add_view(|cx| {
+ cx.focus_self();
+ view_3
+ });
+ view_2.child = Some(view_3.into_any());
+ view_2
+ });
+ view_1.child = Some(view_2.into_any());
+ view_1
});
// This binding only dispatches an action on view 2 because that view will have
@@ -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
@@ -1,17 +1,18 @@
use crate::{
executor,
geometry::vector::Vector2F,
- keymap_matcher::Keystroke,
+ keymap_matcher::{Binding, Keystroke},
platform,
platform::{Event, InputHandler, KeyDownEvent, Platform},
- Action, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache,
- Handle, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
- WeakHandle, WindowContext,
+ Action, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache, Handle,
+ ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakHandle,
+ WindowContext,
};
use collections::BTreeMap;
use futures::Future;
use itertools::Itertools;
use parking_lot::{Mutex, RwLock};
+use smallvec::SmallVec;
use smol::stream::StreamExt;
use std::{
any::Any,
@@ -71,17 +72,24 @@ impl TestAppContext {
cx
}
- pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) {
- self.cx
- .borrow_mut()
- .update_window(window_id, |window| {
- window.handle_dispatch_action_from_effect(window.focused_view_id(), &action);
- })
- .expect("window not found");
+ pub fn dispatch_action<A: Action>(&mut self, window_id: usize, action: A) {
+ self.update_window(window_id, |window| {
+ window.dispatch_action(window.focused_view_id(), &action);
+ })
+ .expect("window not found");
+ }
+
+ pub fn available_actions(
+ &self,
+ window_id: usize,
+ view_id: usize,
+ ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
+ self.read_window(window_id, |cx| cx.available_actions(view_id))
+ .unwrap_or_default()
}
- pub fn dispatch_global_action<A: Action>(&self, action: A) {
- self.cx.borrow_mut().dispatch_global_action_any(&action);
+ pub fn dispatch_global_action<A: Action>(&mut self, action: A) {
+ self.update(|cx| cx.dispatch_global_action_any(&action));
}
pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
@@ -153,12 +161,13 @@ impl TestAppContext {
(window_id, view)
}
- pub fn add_view<T, F>(&mut self, parent_handle: &AnyViewHandle, build_view: F) -> ViewHandle<T>
+ pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
where
T: View,
F: FnOnce(&mut ViewContext<T>) -> T,
{
- self.cx.borrow_mut().add_view(parent_handle, build_view)
+ self.update_window(window_id, |cx| cx.add_view(build_view))
+ .expect("window not found")
}
pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
@@ -14,7 +14,7 @@ use crate::{
text_layout::TextLayoutCache,
util::post_inc,
Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect,
- Element, Entity, Handle, MouseRegion, MouseRegionId, ParentId, SceneBuilder, Subscription,
+ Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription,
View, ViewContext, ViewHandle, WindowInvalidation,
};
use anyhow::{anyhow, bail, Result};
@@ -39,6 +39,7 @@ use super::{Reference, ViewMetadata};
pub struct Window {
pub(crate) root_view: Option<AnyViewHandle>,
pub(crate) focused_view_id: Option<usize>,
+ pub(crate) parents: HashMap<usize, usize>,
pub(crate) is_active: bool,
pub(crate) is_fullscreen: bool,
pub(crate) invalidation: Option<WindowInvalidation>,
@@ -72,6 +73,7 @@ impl Window {
let mut window = Self {
root_view: None,
focused_view_id: None,
+ parents: Default::default(),
is_active: false,
invalidation: None,
is_fullscreen: false,
@@ -90,11 +92,9 @@ impl Window {
};
let mut window_context = WindowContext::mutable(cx, &mut window, window_id);
- let root_view = window_context
- .build_and_insert_view(ParentId::Root, |cx| Some(build_view(cx)))
- .unwrap();
- if let Some(mut invalidation) = window_context.window.invalidation.take() {
- window_context.invalidate(&mut invalidation, appearance);
+ let root_view = window_context.add_view(|cx| build_view(cx));
+ if let Some(invalidation) = window_context.window.invalidation.take() {
+ window_context.invalidate(invalidation, appearance);
}
window.focused_view_id = Some(root_view.id());
window.root_view = Some(root_view.into_any());
@@ -113,7 +113,6 @@ pub struct WindowContext<'a> {
pub(crate) app_context: Reference<'a, AppContext>,
pub(crate) window: Reference<'a, Window>,
pub(crate) window_id: usize,
- pub(crate) refreshing: bool,
pub(crate) removed: bool,
}
@@ -169,7 +168,6 @@ impl<'a> WindowContext<'a> {
app_context: Reference::Mutable(app_context),
window: Reference::Mutable(window),
window_id,
- refreshing: false,
removed: false,
}
}
@@ -179,7 +177,6 @@ impl<'a> WindowContext<'a> {
app_context: Reference::Immutable(app_context),
window: Reference::Immutable(window),
window_id,
- refreshing: false,
removed: false,
}
}
@@ -359,49 +356,10 @@ impl<'a> WindowContext<'a> {
)
}
- /// Return keystrokes that would dispatch the given action on the given view.
- pub(crate) fn keystrokes_for_action(
- &mut self,
- view_id: usize,
- action: &dyn Action,
- ) -> Option<SmallVec<[Keystroke; 2]>> {
- let window_id = self.window_id;
- let mut contexts = Vec::new();
- let mut handler_depth = None;
- for (i, view_id) in self.ancestors(view_id).enumerate() {
- if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
- if let Some(actions) = self.actions.get(&view_metadata.type_id) {
- if actions.contains_key(&action.as_any().type_id()) {
- handler_depth = Some(i);
- }
- }
- contexts.push(view_metadata.keymap_context.clone());
- }
- }
-
- if self.global_actions.contains_key(&action.as_any().type_id()) {
- handler_depth = Some(contexts.len())
- }
-
- self.keystroke_matcher
- .bindings_for_action_type(action.as_any().type_id())
- .find_map(|b| {
- handler_depth
- .map(|highest_handler| {
- if (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..])) {
- Some(b.keystrokes().into())
- } else {
- None
- }
- })
- .flatten()
- })
- }
-
- pub fn available_actions(
+ pub(crate) fn available_actions(
&self,
view_id: usize,
- ) -> impl Iterator<Item = (&'static str, Box<dyn Action>, SmallVec<[&Binding; 1]>)> {
+ ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
let window_id = self.window_id;
let mut contexts = Vec::new();
let mut handler_depths_by_action_type = HashMap::<TypeId, usize>::default();
@@ -443,15 +401,17 @@ impl<'a> WindowContext<'a> {
.filter(|b| {
(0..=action_depth).any(|depth| b.match_context(&contexts[depth..]))
})
+ .cloned()
.collect(),
))
} else {
None
}
})
+ .collect()
}
- pub fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool {
+ pub(crate) fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool {
let window_id = self.window_id;
if let Some(focused_view_id) = self.focused_view_id() {
let dispatch_path = self
@@ -473,8 +433,7 @@ impl<'a> WindowContext<'a> {
MatchResult::Pending => true,
MatchResult::Matches(matches) => {
for (view_id, action) in matches {
- if self.handle_dispatch_action_from_effect(Some(*view_id), action.as_ref())
- {
+ if self.dispatch_action(Some(*view_id), action.as_ref()) {
self.keystroke_matcher.clear_pending();
handled_by = Some(action.boxed_clone());
break;
@@ -497,7 +456,7 @@ impl<'a> WindowContext<'a> {
}
}
- pub fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
+ pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
let mut mouse_events = SmallVec::<[_; 2]>::new();
let mut notified_views: HashSet<usize> = Default::default();
let window_id = self.window_id;
@@ -833,7 +792,7 @@ impl<'a> WindowContext<'a> {
any_event_handled
}
- pub fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool {
+ pub(crate) fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool {
let window_id = self.window_id;
if let Some(focused_view_id) = self.window.focused_view_id {
for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
@@ -852,7 +811,7 @@ impl<'a> WindowContext<'a> {
false
}
- pub fn dispatch_key_up(&mut self, event: &KeyUpEvent) -> bool {
+ pub(crate) fn dispatch_key_up(&mut self, event: &KeyUpEvent) -> bool {
let window_id = self.window_id;
if let Some(focused_view_id) = self.window.focused_view_id {
for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
@@ -871,7 +830,7 @@ impl<'a> WindowContext<'a> {
false
}
- pub fn dispatch_modifiers_changed(&mut self, event: &ModifiersChangedEvent) -> bool {
+ pub(crate) fn dispatch_modifiers_changed(&mut self, event: &ModifiersChangedEvent) -> bool {
let window_id = self.window_id;
if let Some(focused_view_id) = self.window.focused_view_id {
for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
@@ -890,7 +849,7 @@ impl<'a> WindowContext<'a> {
false
}
- pub fn invalidate(&mut self, invalidation: &mut WindowInvalidation, appearance: Appearance) {
+ pub fn invalidate(&mut self, mut invalidation: WindowInvalidation, appearance: Appearance) {
self.start_frame();
self.window.appearance = appearance;
for view_id in &invalidation.removed {
@@ -931,13 +890,52 @@ impl<'a> WindowContext<'a> {
Ok(element)
}
- pub fn build_scene(&mut self) -> Result<Scene> {
+ pub(crate) fn layout(&mut self, refreshing: bool) -> Result<()> {
+ let window_size = self.window.platform_window.content_size();
+ let root_view_id = self.window.root_view().id();
+ let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
+ let mut new_parents = HashMap::default();
+ let mut views_to_notify_if_ancestors_change = HashMap::default();
+ rendered_root.layout(
+ SizeConstraint::strict(window_size),
+ &mut new_parents,
+ &mut views_to_notify_if_ancestors_change,
+ refreshing,
+ self,
+ )?;
+
+ for (view_id, view_ids_to_notify) in views_to_notify_if_ancestors_change {
+ let mut current_view_id = view_id;
+ loop {
+ let old_parent_id = self.window.parents.get(¤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<Scene> {
let window_size = self.window.platform_window.content_size();
let scale_factor = self.window.platform_window.scale_factor();
let root_view_id = self.window.root_view().id();
let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
- rendered_root.layout(SizeConstraint::strict(window_size), self)?;
let mut scene_builder = SceneBuilder::new(scale_factor);
rendered_root.paint(
@@ -1000,11 +998,7 @@ impl<'a> WindowContext<'a> {
self.window.is_fullscreen
}
- pub(crate) fn handle_dispatch_action_from_effect(
- &mut self,
- view_id: Option<usize>,
- action: &dyn Action,
- ) -> bool {
+ pub(crate) fn dispatch_action(&mut self, view_id: Option<usize>, action: &dyn Action) -> bool {
if let Some(view_id) = view_id {
self.halt_action_dispatch = false;
self.visit_dispatch_path(view_id, |view_id, capture_phase, cx| {
@@ -1050,9 +1044,7 @@ impl<'a> WindowContext<'a> {
std::iter::once(view_id)
.into_iter()
.chain(std::iter::from_fn(move || {
- if let Some(ParentId::View(parent_id)) =
- self.parents.get(&(self.window_id, view_id))
- {
+ if let Some(parent_id) = self.window.parents.get(&view_id) {
view_id = *parent_id;
Some(view_id)
} else {
@@ -1061,16 +1053,6 @@ impl<'a> WindowContext<'a> {
}))
}
- /// Returns the id of the parent of the given view, or none if the given
- /// view is the root.
- pub(crate) fn parent(&self, view_id: usize) -> Option<usize> {
- if let Some(ParentId::View(view_id)) = self.parents.get(&(self.window_id, view_id)) {
- Some(*view_id)
- } else {
- None
- }
- }
-
// Traverses the parent tree. Walks down the tree toward the passed
// view calling visit with true. Then walks back up the tree calling visit with false.
// If `visit` returns false this function will immediately return.
@@ -1101,16 +1083,6 @@ impl<'a> WindowContext<'a> {
self.window.focused_view_id
}
- pub fn is_child_focused(&self, view: &AnyViewHandle) -> bool {
- if let Some(focused_view_id) = self.focused_view_id() {
- self.ancestors(focused_view_id)
- .skip(1) // Skip self id
- .any(|parent| parent == view.view_id)
- } else {
- false
- }
- }
-
pub fn window_bounds(&self) -> WindowBounds {
self.window.platform_window.bounds()
}
@@ -1153,27 +1125,27 @@ impl<'a> WindowContext<'a> {
V: View,
F: FnOnce(&mut ViewContext<V>) -> V,
{
- let root_view = self
- .build_and_insert_view(ParentId::Root, |cx| Some(build_root_view(cx)))
- .unwrap();
+ let root_view = self.add_view(|cx| build_root_view(cx));
self.window.root_view = Some(root_view.clone().into_any());
self.window.focused_view_id = Some(root_view.id());
root_view
}
- pub(crate) fn build_and_insert_view<T, F>(
- &mut self,
- parent_id: ParentId,
- build_view: F,
- ) -> Option<ViewHandle<T>>
+ pub fn add_view<T, F>(&mut self, build_view: F) -> ViewHandle<T>
+ where
+ T: View,
+ F: FnOnce(&mut ViewContext<T>) -> T,
+ {
+ self.add_option_view(|cx| Some(build_view(cx))).unwrap()
+ }
+
+ pub fn add_option_view<T, F>(&mut self, build_view: F) -> Option<ViewHandle<T>>
where
T: View,
F: FnOnce(&mut ViewContext<T>) -> Option<T>,
{
let window_id = self.window_id;
let view_id = post_inc(&mut self.next_entity_id);
- // Make sure we can tell child views about their parentu
- self.parents.insert((window_id, view_id), parent_id);
let mut cx = ViewContext::mutable(self, view_id);
let handle = if let Some(view) = build_view(&mut cx) {
let mut keymap_context = KeymapContext::default();
@@ -1193,7 +1165,6 @@ impl<'a> WindowContext<'a> {
.insert(view_id);
Some(ViewHandle::new(window_id, view_id, &self.ref_counts))
} else {
- self.parents.remove(&(window_id, view_id));
None
};
handle
@@ -1374,11 +1345,18 @@ impl<V: View> Element<V> for ChildView {
&mut self,
constraint: SizeConstraint,
_: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
+ cx.new_parents.insert(self.view_id, cx.view_id());
let size = rendered_view
- .layout(constraint, cx)
+ .layout(
+ constraint,
+ cx.new_parents,
+ cx.views_to_notify_if_ancestors_change,
+ cx.refreshing,
+ cx.view_context,
+ )
.log_err()
.unwrap_or(Vector2F::zero());
cx.window.rendered_views.insert(self.view_id, rendered_view);
@@ -33,11 +33,14 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
- json, Action, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle, WindowContext,
+ json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle,
+ WindowContext,
};
use anyhow::{anyhow, Result};
+use collections::HashMap;
use core::panic;
use json::ToJson;
+use smallvec::SmallVec;
use std::{
any::Any,
borrow::Cow,
@@ -54,7 +57,7 @@ pub trait Element<V: View>: 'static {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState);
fn paint(
@@ -211,7 +214,7 @@ trait AnyElementState<V: View> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> Vector2F;
fn paint(
@@ -263,7 +266,7 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> Vector2F {
let result;
*self = match mem::take(self) {
@@ -444,7 +447,7 @@ impl<V: View> AnyElement<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> Vector2F {
self.state.layout(constraint, view, cx)
}
@@ -505,7 +508,7 @@ impl<V: View> Element<V> for AnyElement<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.layout(constraint, view, cx);
(size, ())
@@ -597,7 +600,7 @@ impl<V: View, C: Component<V>> Element<V> for ComponentHost<V, C> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, AnyElement<V>) {
let mut element = self.component.render(view, cx);
let size = element.layout(constraint, view, cx);
@@ -642,7 +645,14 @@ impl<V: View, C: Component<V>> Element<V> for ComponentHost<V, C> {
}
pub trait AnyRootElement {
- fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F>;
+ fn layout(
+ &mut self,
+ constraint: SizeConstraint,
+ new_parents: &mut HashMap<usize, usize>,
+ views_to_notify_if_ancestors_change: &mut HashMap<usize, SmallVec<[usize; 2]>>,
+ refreshing: bool,
+ cx: &mut WindowContext,
+ ) -> Result<Vector2F>;
fn paint(
&mut self,
scene: &mut SceneBuilder,
@@ -660,12 +670,27 @@ pub trait AnyRootElement {
}
impl<V: View> AnyRootElement for RootElement<V> {
- fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F> {
+ fn layout(
+ &mut self,
+ constraint: SizeConstraint,
+ new_parents: &mut HashMap<usize, usize>,
+ views_to_notify_if_ancestors_change: &mut HashMap<usize, SmallVec<[usize; 2]>>,
+ refreshing: bool,
+ cx: &mut WindowContext,
+ ) -> Result<Vector2F> {
let view = self
.view
.upgrade(cx)
.ok_or_else(|| anyhow!("layout called on a root element for a dropped view"))?;
- view.update(cx, |view, cx| Ok(self.element.layout(constraint, view, cx)))
+ view.update(cx, |view, cx| {
+ let mut cx = LayoutContext::new(
+ cx,
+ new_parents,
+ views_to_notify_if_ancestors_change,
+ refreshing,
+ );
+ Ok(self.element.layout(constraint, view, &mut cx))
+ })
}
fn paint(
@@ -1,6 +1,6 @@
use crate::{
geometry::{rect::RectF, vector::Vector2F},
- json, AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+ json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
use json::ToJson;
@@ -48,7 +48,7 @@ impl<V: View> Element<V> for Align<V> {
&mut self,
mut constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.max;
constraint.min = Vector2F::zero();
@@ -34,7 +34,7 @@ where
&mut self,
constraint: crate::SizeConstraint,
_: &mut V,
- _: &mut crate::ViewContext<V>,
+ _: &mut crate::LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let x = if constraint.max.x().is_finite() {
constraint.max.x()
@@ -3,7 +3,9 @@ use std::ops::Range;
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use serde_json::json;
-use crate::{json, AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext};
+use crate::{
+ json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+};
pub struct Clipped<V: View> {
child: AnyElement<V>,
@@ -23,7 +25,7 @@ impl<V: View> Element<V> for Clipped<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, view, cx), ())
}
@@ -5,7 +5,7 @@ use serde_json::json;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
- json, AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+ json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
pub struct ConstrainedBox<V: View> {
@@ -15,7 +15,7 @@ pub struct ConstrainedBox<V: View> {
pub enum Constraint<V: View> {
Static(SizeConstraint),
- Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint>),
+ Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint>),
}
impl<V: View> ToJson for Constraint<V> {
@@ -37,7 +37,8 @@ impl<V: View> ConstrainedBox<V> {
pub fn dynamically(
mut self,
- constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint,
+ constraint: impl 'static
+ + FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint,
) -> Self {
self.constraint = Constraint::Dynamic(Box::new(constraint));
self
@@ -119,7 +120,7 @@ impl<V: View> ConstrainedBox<V> {
&mut self,
input_constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> SizeConstraint {
match &mut self.constraint {
Constraint::Static(constraint) => *constraint,
@@ -138,7 +139,7 @@ impl<V: View> Element<V> for ConstrainedBox<V> {
&mut self,
mut parent_constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let constraint = self.constraint(parent_constraint, view, cx);
parent_constraint.min = parent_constraint.min.max(constraint.min);
@@ -10,7 +10,7 @@ use crate::{
json::ToJson,
platform::CursorStyle,
scene::{self, Border, CursorRegion, Quad},
- AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+ AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
use serde::Deserialize;
use serde_json::json;
@@ -192,7 +192,7 @@ impl<V: View> Element<V> for Container<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut size_buffer = self.margin_size() + self.padding_size();
if !self.style.border.overlay {
@@ -6,7 +6,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
- SceneBuilder, View, ViewContext,
+ LayoutContext, SceneBuilder, View, ViewContext,
};
use crate::{Element, SizeConstraint};
@@ -34,7 +34,7 @@ impl<V: View> Element<V> for Empty {
&mut self,
constraint: SizeConstraint,
_: &mut V,
- _: &mut ViewContext<V>,
+ _: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let x = if constraint.max.x().is_finite() && !self.collapsed {
constraint.max.x()
@@ -2,7 +2,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
- json, AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+ json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
use serde_json::json;
@@ -42,7 +42,7 @@ impl<V: View> Element<V> for Expanded<V> {
&mut self,
mut constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
if self.full_width {
constraint.min.set_x(constraint.max.x());
@@ -2,8 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
use crate::{
json::{self, ToJson, Value},
- AnyElement, Axis, Element, ElementStateHandle, SceneBuilder, SizeConstraint, Vector2FExt, View,
- ViewContext,
+ AnyElement, Axis, Element, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint,
+ Vector2FExt, View, ViewContext,
};
use pathfinder_geometry::{
rect::RectF,
@@ -74,7 +74,7 @@ impl<V: View> Flex<V> {
remaining_flex: &mut f32,
cross_axis_max: &mut f32,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) {
let cross_axis = self.axis.invert();
for child in &mut self.children {
@@ -125,7 +125,7 @@ impl<V: View> Element<V> for Flex<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut total_flex = None;
let mut fixed_space = 0.0;
@@ -214,7 +214,7 @@ impl<V: View> Element<V> for Flex<V> {
}
if let Some(scroll_state) = self.scroll_state.as_ref() {
- scroll_state.0.update(cx, |scroll_state, _| {
+ scroll_state.0.update(cx.view_context(), |scroll_state, _| {
if let Some(scroll_to) = scroll_state.scroll_to.take() {
let visible_start = scroll_state.scroll_position.get();
let visible_end = visible_start + size.along(self.axis);
@@ -432,7 +432,7 @@ impl<V: View> Element<V> for FlexItem<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, view, cx);
(size, ())
@@ -3,7 +3,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::json,
- AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+ AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
pub struct Hook<V: View> {
@@ -36,7 +36,7 @@ impl<V: View> Element<V> for Hook<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, view, cx);
if let Some(handler) = self.after_layout.as_mut() {
@@ -5,7 +5,8 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
- scene, Border, Element, ImageData, SceneBuilder, SizeConstraint, View, ViewContext,
+ scene, Border, Element, ImageData, LayoutContext, SceneBuilder, SizeConstraint, View,
+ ViewContext,
};
use serde::Deserialize;
use std::{ops::Range, sync::Arc};
@@ -63,7 +64,7 @@ impl<V: View> Element<V> for Image {
&mut self,
constraint: SizeConstraint,
_: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let data = match &self.source {
ImageSource::Path(path) => match cx.asset_cache.png(path) {
@@ -39,7 +39,7 @@ impl<V: View> Element<V> for KeystrokeLabel {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, AnyElement<V>) {
let mut element = if let Some(keystrokes) =
cx.keystrokes_for_action(self.view_id, self.action.as_ref())
@@ -8,7 +8,7 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle},
- Element, SceneBuilder, SizeConstraint, View, ViewContext,
+ Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
use serde::Deserialize;
use serde_json::json;
@@ -135,7 +135,7 @@ impl<V: View> Element<V> for Label {
&mut self,
constraint: SizeConstraint,
_: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let runs = self.compute_runs();
let line = cx.text_layout_cache().layout_str(
@@ -4,7 +4,8 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::json,
- AnyElement, Element, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
+ AnyElement, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
+ ViewContext,
};
use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
@@ -99,7 +100,7 @@ impl<V: View> Element<V> for List<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let state = &mut *self.state.0.borrow_mut();
let size = constraint.max;
@@ -452,7 +453,7 @@ impl<V: View> StateInner<V> {
existing_element: Option<&ListItem<V>>,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> Option<Rc<RefCell<AnyElement<V>>>> {
if let Some(ListItem::Rendered(element)) = existing_element {
Some(element.clone())
@@ -665,7 +666,15 @@ mod tests {
});
let mut list = List::new(state.clone());
- let (size, _) = list.layout(constraint, &mut view, cx);
+ let mut new_parents = Default::default();
+ let mut notify_views_if_parents_change = Default::default();
+ let mut layout_cx = LayoutContext::new(
+ cx,
+ &mut new_parents,
+ &mut notify_views_if_parents_change,
+ false,
+ );
+ let (size, _) = list.layout(constraint, &mut view, &mut layout_cx);
assert_eq!(size, vec2f(100., 40.));
assert_eq!(
state.0.borrow().items.summary().clone(),
@@ -689,7 +698,13 @@ mod tests {
cx,
);
- let (_, logical_scroll_top) = list.layout(constraint, &mut view, cx);
+ let mut layout_cx = LayoutContext::new(
+ cx,
+ &mut new_parents,
+ &mut notify_views_if_parents_change,
+ false,
+ );
+ let (_, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
assert_eq!(
logical_scroll_top,
ListOffset {
@@ -713,7 +728,13 @@ mod tests {
}
);
- let (size, logical_scroll_top) = list.layout(constraint, &mut view, cx);
+ let mut layout_cx = LayoutContext::new(
+ cx,
+ &mut new_parents,
+ &mut notify_views_if_parents_change,
+ false,
+ );
+ let (size, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
assert_eq!(size, vec2f(100., 40.));
assert_eq!(
state.0.borrow().items.summary().clone(),
@@ -831,10 +852,18 @@ mod tests {
let mut list = List::new(state.clone());
let window_size = vec2f(width, height);
+ let mut new_parents = Default::default();
+ let mut notify_views_if_parents_change = Default::default();
+ let mut layout_cx = LayoutContext::new(
+ cx,
+ &mut new_parents,
+ &mut notify_views_if_parents_change,
+ false,
+ );
let (size, logical_scroll_top) = list.layout(
SizeConstraint::new(vec2f(0., 0.), window_size),
&mut view,
- cx,
+ &mut layout_cx,
);
assert_eq!(size, window_size);
last_logical_scroll_top = Some(logical_scroll_top);
@@ -947,7 +976,7 @@ mod tests {
&mut self,
_: SizeConstraint,
_: &mut V,
- _: &mut ViewContext<V>,
+ _: &mut LayoutContext<V>,
) -> (Vector2F, ()) {
(self.size, ())
}
@@ -10,8 +10,8 @@ use crate::{
CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover,
MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
},
- AnyElement, Element, EventContext, MouseRegion, MouseState, SceneBuilder, SizeConstraint, View,
- ViewContext,
+ AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, SceneBuilder,
+ SizeConstraint, View, ViewContext,
};
use serde_json::json;
use std::{marker::PhantomData, ops::Range};
@@ -220,7 +220,7 @@ impl<Tag, V: View> Element<V> for MouseEventHandler<Tag, V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, view, cx), ())
}
@@ -3,7 +3,8 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
- AnyElement, Axis, Element, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
+ AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
+ ViewContext,
};
use serde_json::json;
@@ -124,7 +125,7 @@ impl<V: View> Element<V> for Overlay<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let constraint = if self.anchor_position.is_some() {
SizeConstraint::new(Vector2F::zero(), cx.window_size())
@@ -7,7 +7,8 @@ use crate::{
geometry::rect::RectF,
platform::{CursorStyle, MouseButton},
scene::MouseDrag,
- AnyElement, Axis, Element, ElementStateHandle, MouseRegion, SceneBuilder, View, ViewContext,
+ AnyElement, Axis, Element, ElementStateHandle, LayoutContext, MouseRegion, SceneBuilder, View,
+ ViewContext,
};
use super::{ConstrainedBox, Hook};
@@ -139,7 +140,7 @@ impl<V: View> Element<V> for Resizable<V> {
&mut self,
constraint: crate::SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, view, cx), ())
}
@@ -3,7 +3,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::{self, json, ToJson},
- AnyElement, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+ AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
/// Element which renders it's children in a stack on top of each other.
@@ -34,7 +34,7 @@ impl<V: View> Element<V> for Stack<V> {
&mut self,
mut constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.min;
let mut children = self.children.iter_mut();
@@ -8,7 +8,7 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
- scene, Element, SceneBuilder, SizeConstraint, View, ViewContext,
+ scene, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
pub struct Svg {
@@ -38,7 +38,7 @@ impl<V: View> Element<V> for Svg {
&mut self,
constraint: SizeConstraint,
_: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
match cx.asset_cache.svg(&self.path) {
Ok(tree) => {
@@ -7,8 +7,8 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle, ShapedBoundary},
- AppContext, Element, FontCache, SceneBuilder, SizeConstraint, TextLayoutCache, View,
- ViewContext,
+ AppContext, Element, FontCache, LayoutContext, SceneBuilder, SizeConstraint, TextLayoutCache,
+ View, ViewContext,
};
use log::warn;
use serde_json::json;
@@ -78,7 +78,7 @@ impl<V: View> Element<V> for Text {
&mut self,
constraint: SizeConstraint,
_: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
// Convert the string and highlight ranges into an iterator of highlighted chunks.
@@ -411,10 +411,18 @@ mod tests {
let mut view = TestView;
fonts::with_font_cache(cx.font_cache().clone(), || {
let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
+ let mut new_parents = Default::default();
+ let mut notify_views_if_parents_change = Default::default();
+ let mut layout_cx = LayoutContext::new(
+ cx,
+ &mut new_parents,
+ &mut notify_views_if_parents_change,
+ false,
+ );
let (_, state) = text.layout(
SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
&mut view,
- cx,
+ &mut layout_cx,
);
assert_eq!(state.shaped_lines.len(), 2);
assert_eq!(state.wrap_boundaries.len(), 2);
@@ -6,7 +6,8 @@ use crate::{
fonts::TextStyle,
geometry::{rect::RectF, vector::Vector2F},
json::json,
- Action, Axis, ElementStateHandle, SceneBuilder, SizeConstraint, Task, View, ViewContext,
+ Action, Axis, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint, Task, View,
+ ViewContext,
};
use serde::Deserialize;
use std::{
@@ -172,7 +173,7 @@ impl<V: View> Element<V> for Tooltip<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, view, cx);
if let Some(tooltip) = self.tooltip.as_mut() {
@@ -6,7 +6,7 @@ use crate::{
},
json::{self, json},
platform::ScrollWheelEvent,
- AnyElement, MouseRegion, SceneBuilder, View, ViewContext,
+ AnyElement, LayoutContext, MouseRegion, SceneBuilder, View, ViewContext,
};
use json::ToJson;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -159,7 +159,7 @@ impl<V: View> Element<V> for UniformList<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
if constraint.max.y().is_infinite() {
unimplemented!(
@@ -11,6 +11,16 @@ pub struct Binding {
context_predicate: Option<KeymapContextPredicate>,
}
+impl Clone for Binding {
+ fn clone(&self) -> Self {
+ Self {
+ action: self.action.boxed_clone(),
+ keystrokes: self.keystrokes.clone(),
+ context_predicate: self.context_predicate.clone(),
+ }
+ }
+}
+
impl Binding {
pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
Self::load(keystrokes, Box::new(action), context).unwrap()
@@ -44,7 +44,7 @@ impl KeymapContext {
}
}
-#[derive(Debug, Eq, PartialEq)]
+#[derive(Clone, Debug, Eq, PartialEq)]
pub enum KeymapContextPredicate {
Identifier(String),
Equal(String, String),
@@ -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(),
};
@@ -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,
@@ -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);
@@ -200,7 +200,7 @@ impl View for ProjectSearchView {
.flex(1., true)
})
.on_down(MouseButton::Left, |_, _, cx| {
- cx.focus_parent_view();
+ cx.focus_parent();
})
.into_any_named("project search view")
} else {
@@ -939,8 +939,6 @@ impl ToolbarItemView for ProjectSearchBar {
self.subscription = None;
self.active_project_search = None;
if let Some(search) = active_pane_item.and_then(|i| i.downcast::<ProjectSearchView>()) {
- let query_editor = search.read(cx).query_editor.clone();
- cx.reparent(&query_editor);
self.subscription = Some(cx.observe(&search, |_, _, cx| cx.notify()));
self.active_project_search = Some(search);
ToolbarItemLocation::PrimaryLeft {
@@ -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(),
@@ -107,11 +107,12 @@ impl View for TerminalButton {
impl TerminalButton {
pub fn new(workspace: ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
+ let button_view_id = cx.view_id();
cx.observe(&workspace, |_, _, cx| cx.notify()).detach();
Self {
workspace: workspace.downgrade(),
popup_menu: cx.add_view(|cx| {
- let mut menu = ContextMenu::new(cx);
+ let mut menu = ContextMenu::new(button_view_id, cx);
menu.set_position_mode(OverlayPositionMode::Local);
menu
}),
@@ -10,8 +10,8 @@ use gpui::{
platform::{CursorStyle, MouseButton},
serde_json::json,
text_layout::{Line, RunStyle},
- AnyElement, Element, EventContext, FontCache, ModelContext, MouseRegion, Quad, SceneBuilder,
- SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle,
+ AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion, Quad,
+ SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle,
};
use itertools::Itertools;
use language::CursorShape;
@@ -370,7 +370,7 @@ impl TerminalElement {
f: impl Fn(&mut Terminal, Vector2F, E, &mut ModelContext<Terminal>),
) -> impl Fn(E, &mut TerminalView, &mut EventContext<TerminalView>) {
move |event, _: &mut TerminalView, cx| {
- cx.focus_parent_view();
+ cx.focus_parent();
if let Some(conn_handle) = connection.upgrade(cx) {
conn_handle.update(cx, |terminal, cx| {
f(terminal, origin, event, cx);
@@ -408,7 +408,7 @@ impl TerminalElement {
)
// Update drag selections
.on_drag(MouseButton::Left, move |event, _: &mut TerminalView, cx| {
- if cx.is_parent_view_focused() {
+ if cx.is_self_focused() {
if let Some(conn_handle) = connection.upgrade(cx) {
conn_handle.update(cx, |terminal, cx| {
terminal.mouse_drag(event, origin);
@@ -444,7 +444,7 @@ impl TerminalElement {
},
)
.on_move(move |event, _: &mut TerminalView, cx| {
- if cx.is_parent_view_focused() {
+ if cx.is_self_focused() {
if let Some(conn_handle) = connection.upgrade(cx) {
conn_handle.update(cx, |terminal, cx| {
terminal.mouse_move(&event, origin);
@@ -561,7 +561,7 @@ impl Element<TerminalView> for TerminalElement {
&mut self,
constraint: gpui::SizeConstraint,
view: &mut TerminalView,
- cx: &mut ViewContext<TerminalView>,
+ cx: &mut LayoutContext<TerminalView>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
let settings = cx.global::<Settings>();
let font_cache = cx.font_cache();
@@ -2,7 +2,6 @@ mod persistence;
pub mod terminal_button;
pub mod terminal_element;
-use anyhow::anyhow;
use context_menu::{ContextMenu, ContextMenuItem};
use dirs::home_dir;
use gpui::{
@@ -125,6 +124,7 @@ impl TerminalView {
workspace_id: WorkspaceId,
cx: &mut ViewContext<Self>,
) -> Self {
+ let view_id = cx.view_id();
cx.observe(&terminal, |_, _, cx| cx.notify()).detach();
cx.subscribe(&terminal, |this, _, event, cx| match event {
Event::Wakeup => {
@@ -163,7 +163,7 @@ impl TerminalView {
terminal,
has_new_content: true,
has_bell: false,
- context_menu: cx.add_view(ContextMenu::new),
+ context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
blink_state: true,
blinking_on: false,
blinking_paused: false,
@@ -627,16 +627,12 @@ impl Item for TerminalView {
})
});
- let pane = pane
- .upgrade(&cx)
- .ok_or_else(|| anyhow!("pane was dropped"))?;
- cx.update(|cx| {
- let terminal = project.update(cx, |project, cx| {
- project.create_terminal(cwd, window_id, cx)
- })?;
-
- Ok(cx.add_view(&pane, |cx| TerminalView::new(terminal, workspace_id, cx)))
- })
+ let terminal = project.update(&mut cx, |project, cx| {
+ project.create_terminal(cwd, window_id, cx)
+ })?;
+ Ok(pane.update(&mut cx, |_, cx| {
+ cx.add_view(|cx| TerminalView::new(terminal, workspace_id, cx))
+ })?)
})
}
@@ -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);
}
@@ -24,8 +24,8 @@ use gpui::{
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
- ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle,
- WindowContext,
+ LayoutContext, ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle,
+ WeakViewHandle, WindowContext,
};
use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize;
@@ -134,6 +134,7 @@ pub enum Event {
RemoveItem { item_id: usize },
Split(SplitDirection),
ChangeItemTitle,
+ Focus,
}
pub struct Pane {
@@ -150,6 +151,7 @@ pub struct Pane {
docked: Option<DockAnchor>,
_background_actions: BackgroundActions,
workspace: WeakViewHandle<Workspace>,
+ has_focus: bool,
}
pub struct ItemNavHistory {
@@ -226,8 +228,9 @@ impl Pane {
background_actions: BackgroundActions,
cx: &mut ViewContext<Self>,
) -> Self {
+ let pane_view_id = cx.view_id();
let handle = cx.weak_handle();
- let context_menu = cx.add_view(ContextMenu::new);
+ let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx));
context_menu.update(cx, |menu, _| {
menu.set_position_mode(OverlayPositionMode::Local)
});
@@ -252,10 +255,11 @@ impl Pane {
kind: TabBarContextMenuKind::New,
handle: context_menu,
},
- tab_context_menu: cx.add_view(ContextMenu::new),
+ tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
docked,
_background_actions: background_actions,
workspace,
+ has_focus: false,
}
}
@@ -272,6 +276,10 @@ impl Pane {
cx.notify();
}
+ pub fn has_focus(&self) -> bool {
+ self.has_focus
+ }
+
pub fn set_docked(&mut self, docked: Option<DockAnchor>, cx: &mut ViewContext<Self>) {
self.docked = docked;
cx.notify();
@@ -537,7 +545,6 @@ impl Pane {
// If the item already exists, move it to the desired destination and activate it
pane.update(cx, |pane, cx| {
if existing_item_index != insertion_index {
- cx.reparent(item.as_any());
let existing_item_is_active = existing_item_index == pane.active_item_index;
// If the caller didn't specify a destination and the added item is already
@@ -567,7 +574,6 @@ impl Pane {
});
} else {
pane.update(cx, |pane, cx| {
- cx.reparent(item.as_any());
pane.items.insert(insertion_index, item);
if insertion_index <= pane.active_item_index {
pane.active_item_index += 1;
@@ -1764,7 +1770,7 @@ impl View for Pane {
self.render_blank_pane(&theme, cx)
})
.on_down(MouseButton::Left, |_, _, cx| {
- cx.focus_parent_view();
+ cx.focus_parent();
})
.into_any()
}
@@ -1798,6 +1804,7 @@ impl View for Pane {
}
fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
+ self.has_focus = true;
self.toolbar.update(cx, |toolbar, cx| {
toolbar.pane_focus_update(true, cx);
});
@@ -1823,9 +1830,12 @@ impl View for Pane {
.insert(active_item.id(), focused.downgrade());
}
}
+
+ cx.emit(Event::Focus);
}
fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+ self.has_focus = false;
self.toolbar.update(cx, |toolbar, cx| {
toolbar.pane_focus_update(false, cx);
});
@@ -1998,7 +2008,7 @@ impl<V: View> Element<V> for PaneBackdrop<V> {
&mut self,
constraint: gpui::SizeConstraint,
view: &mut V,
- cx: &mut ViewContext<V>,
+ cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, view, cx);
(size, ())
@@ -90,7 +90,7 @@ impl View for SharedScreen {
.contained()
.with_style(cx.global::<Settings>().theme.shared_screen)
})
- .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent_view())
+ .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent())
.into_any()
}
}
@@ -147,7 +147,7 @@ impl Sidebar {
}
}),
];
- cx.reparent(&view);
+
self.items.push(Item {
icon_path,
tooltip,
@@ -8,8 +8,8 @@ use gpui::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
- AnyElement, AnyViewHandle, Entity, SceneBuilder, SizeConstraint, Subscription, View,
- ViewContext, ViewHandle, WindowContext,
+ AnyElement, AnyViewHandle, Entity, LayoutContext, SceneBuilder, SizeConstraint, Subscription,
+ View, ViewContext, ViewHandle, WindowContext,
};
use settings::Settings;
@@ -93,7 +93,6 @@ impl StatusBar {
where
T: 'static + StatusItemView,
{
- cx.reparent(item.as_any());
self.left_items.push(Box::new(item));
cx.notify();
}
@@ -102,7 +101,6 @@ impl StatusBar {
where
T: 'static + StatusItemView,
{
- cx.reparent(item.as_any());
self.right_items.push(Box::new(item));
cx.notify();
}
@@ -157,7 +155,7 @@ impl Element<StatusBar> for StatusBarElement {
&mut self,
mut constraint: SizeConstraint,
view: &mut StatusBar,
- cx: &mut ViewContext<StatusBar>,
+ cx: &mut LayoutContext<StatusBar>,
) -> (Vector2F, Self::LayoutState) {
let max_width = constraint.max.x();
constraint.min = vec2f(0., constraint.min.y());
@@ -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<Pane>,
event: &pane::Event,
cx: &mut ViewContext<Self>,
) {
- if let Some(pane) = self.pane(pane_id) {
- let is_dock = &pane == self.dock.pane();
- match event {
- pane::Event::Split(direction) if !is_dock => {
- self.split_pane(pane, *direction, cx);
+ let is_dock = &pane == self.dock.pane();
+ match event {
+ pane::Event::Split(direction) if !is_dock => {
+ self.split_pane(pane, *direction, cx);
+ }
+ pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
+ pane::Event::Remove if is_dock => Dock::hide(self, cx),
+ pane::Event::ActivateItem { local } => {
+ if *local {
+ self.unfollow(&pane, cx);
}
- pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
- pane::Event::Remove if is_dock => Dock::hide(self, cx),
- pane::Event::ActivateItem { local } => {
- if *local {
- self.unfollow(&pane, cx);
- }
- if &pane == self.active_pane() {
- self.active_item_path_changed(cx);
- }
+ if &pane == self.active_pane() {
+ self.active_item_path_changed(cx);
}
- pane::Event::ChangeItemTitle => {
- if pane == self.active_pane {
- self.active_item_path_changed(cx);
- }
- self.update_window_edited(cx);
+ }
+ pane::Event::ChangeItemTitle => {
+ if pane == self.active_pane {
+ self.active_item_path_changed(cx);
}
- pane::Event::RemoveItem { item_id } => {
- self.update_window_edited(cx);
- if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
- if entry.get().id() == pane.id() {
- entry.remove();
- }
+ self.update_window_edited(cx);
+ }
+ pane::Event::RemoveItem { item_id } => {
+ self.update_window_edited(cx);
+ if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
+ if entry.get().id() == pane.id() {
+ entry.remove();
}
}
- _ => {}
}
-
- self.serialize_workspace(cx);
- } else if self.dock.visible_pane().is_none() {
- error!("pane {} not found", pane_id);
+ pane::Event::Focus => {
+ self.handle_pane_focused(pane.clone(), cx);
+ }
+ _ => {}
}
+
+ self.serialize_workspace(cx);
}
pub fn split_pane(
@@ -1773,10 +1764,6 @@ impl Workspace {
&self.panes
}
- fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
- self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
- }
-
pub fn active_pane(&self) -> &ViewHandle<Pane> {
&self.active_pane
}
@@ -2365,19 +2352,14 @@ impl Workspace {
}
for (pane, item) in items_to_activate {
- let active_item_was_focused = pane
- .read(cx)
- .active_item()
- .map(|active_item| cx.is_child_focused(active_item.as_any()))
- .unwrap_or_default();
-
+ let pane_was_focused = pane.read(cx).has_focus();
if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
} else {
Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
}
- if active_item_was_focused {
+ if pane_was_focused {
pane.update(cx, |pane, cx| pane.focus_active_item(cx));
}
}
@@ -2796,17 +2778,9 @@ impl View for Workspace {
.into_any_named("workspace")
}
- fn focus_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext<Self>) {
+ fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
if cx.is_self_focused() {
cx.focus(&self.active_pane);
- } else {
- for pane in self.panes() {
- let view = view.clone();
- if pane.update(cx, |_, cx| view.id() == cx.view_id() || cx.is_child(view)) {
- self.handle_pane_focused(pane.clone(), cx);
- break;
- }
- }
}
}
}
@@ -3154,10 +3128,10 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
// Adding an item with no ambiguity renders the tab without detail.
- let item1 = cx.add_view(&workspace, |_| {
+ let item1 = cx.add_view(window_id, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
item
@@ -3169,7 +3143,7 @@ mod tests {
// Adding an item that creates ambiguity increases the level of detail on
// both tabs.
- let item2 = cx.add_view(&workspace, |_| {
+ let item2 = cx.add_view(window_id, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
item
@@ -3183,7 +3157,7 @@ mod tests {
// Adding an item that creates ambiguity increases the level of detail only
// on the ambiguous tabs. In this case, the ambiguity can't be resolved so
// we stop at the highest detail available.
- let item3 = cx.add_view(&workspace, |_| {
+ let item3 = cx.add_view(window_id, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
item
@@ -3223,10 +3197,10 @@ mod tests {
project.worktrees(cx).next().unwrap().read(cx).id()
});
- let item1 = cx.add_view(&workspace, |cx| {
+ let item1 = cx.add_view(window_id, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
});
- let item2 = cx.add_view(&workspace, |cx| {
+ let item2 = cx.add_view(window_id, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
});
@@ -3311,15 +3285,15 @@ mod tests {
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
// When there are no dirty items, there's nothing to do.
- let item1 = cx.add_view(&workspace, |_| TestItem::new());
+ let item1 = cx.add_view(window_id, |_| TestItem::new());
workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
assert!(task.await.unwrap());
// When there are dirty untitled items, prompt to save each one. If the user
// cancels any prompt, then abort.
- let item2 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true));
- let item3 = cx.add_view(&workspace, |cx| {
+ let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
+ let item3 = cx.add_view(window_id, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
@@ -3345,24 +3319,24 @@ mod tests {
let project = Project::test(fs, None, cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let item1 = cx.add_view(&workspace, |cx| {
+ let item1 = cx.add_view(window_id, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
});
- let item2 = cx.add_view(&workspace, |cx| {
+ let item2 = cx.add_view(window_id, |cx| {
TestItem::new()
.with_dirty(true)
.with_conflict(true)
.with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
});
- let item3 = cx.add_view(&workspace, |cx| {
+ let item3 = cx.add_view(window_id, |cx| {
TestItem::new()
.with_dirty(true)
.with_conflict(true)
.with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
});
- let item4 = cx.add_view(&workspace, |cx| {
+ let item4 = cx.add_view(window_id, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new_untitled(cx)])
@@ -3456,7 +3430,7 @@ mod tests {
// workspace items with multiple project entries.
let single_entry_items = (0..=4)
.map(|project_entry_id| {
- cx.add_view(&workspace, |cx| {
+ cx.add_view(window_id, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new(
@@ -3467,7 +3441,7 @@ mod tests {
})
})
.collect::<Vec<_>>();
- let item_2_3 = cx.add_view(&workspace, |cx| {
+ let item_2_3 = cx.add_view(window_id, |cx| {
TestItem::new()
.with_dirty(true)
.with_singleton(false)
@@ -3476,7 +3450,7 @@ mod tests {
single_entry_items[3].read(cx).project_items[0].clone(),
])
});
- let item_3_4 = cx.add_view(&workspace, |cx| {
+ let item_3_4 = cx.add_view(window_id, |cx| {
TestItem::new()
.with_dirty(true)
.with_singleton(false)
@@ -3559,7 +3533,7 @@ mod tests {
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let item = cx.add_view(&workspace, |cx| {
+ let item = cx.add_view(window_id, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
});
let item_id = item.id();
@@ -3674,9 +3648,9 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let item = cx.add_view(&workspace, |cx| {
+ let item = cx.add_view(window_id, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
});
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());