Cargo.lock 🔗
@@ -3371,6 +3371,7 @@ dependencies = [
"project",
"settings",
"theme",
+ "util",
"workspace",
]
Antonio Scandurra created
Give elements access to their parent views and simplify contexts
Cargo.lock | 1
crates/activity_indicator/src/activity_indicator.rs | 15
crates/auto_update/src/auto_update.rs | 2
crates/auto_update/src/update_notification.rs | 14
crates/breadcrumbs/src/breadcrumbs.rs | 30
crates/collab/src/tests/integration_tests.rs | 25
crates/collab_ui/src/collab_titlebar_item.rs | 148
crates/collab_ui/src/collab_ui.rs | 2
crates/collab_ui/src/collaborator_list_popover.rs | 43
crates/collab_ui/src/contact_finder.rs | 99
crates/collab_ui/src/contact_list.rs | 86
crates/collab_ui/src/contact_notification.rs | 5
crates/collab_ui/src/contacts_popover.rs | 27
crates/collab_ui/src/face_pile.rs | 31
crates/collab_ui/src/incoming_call_notification.rs | 16
crates/collab_ui/src/notifications.rs | 14
crates/collab_ui/src/project_shared_notification.rs | 22
crates/collab_ui/src/sharing_status_indicator.rs | 10
crates/command_palette/src/command_palette.rs | 168
crates/context_menu/src/context_menu.rs | 50
crates/copilot/src/copilot.rs | 8
crates/copilot/src/sign_in.rs | 89
crates/copilot_button/src/copilot_button.rs | 17
crates/diagnostics/src/diagnostics.rs | 25
crates/diagnostics/src/items.rs | 18
crates/drag_and_drop/src/drag_and_drop.rs | 64
crates/editor/src/display_map/block_map.rs | 26
crates/editor/src/editor.rs | 159
crates/editor/src/editor_tests.rs | 516 +
crates/editor/src/element.rs | 528 +-
crates/editor/src/hover_popover.rs | 28
crates/editor/src/items.rs | 48
crates/editor/src/link_go_to_definition.rs | 2
crates/editor/src/scroll.rs | 10
crates/editor/src/test/editor_test_context.rs | 3
crates/feedback/src/deploy_feedback_button.rs | 10
crates/feedback/src/feedback_editor.rs | 46
crates/feedback/src/feedback_info_text.rs | 8
crates/feedback/src/submit_feedback_button.rs | 10
crates/file_finder/src/file_finder.rs | 303
crates/go_to_line/src/go_to_line.rs | 43
crates/gpui/examples/text.rs | 25
crates/gpui/src/app.rs | 730 --
crates/gpui/src/app/callback_collection.rs | 13
crates/gpui/src/app/menu.rs | 14
crates/gpui/src/app/test_app_context.rs | 213
crates/gpui/src/app/window.rs | 1493 ++++++
crates/gpui/src/app/window_input_handler.rs | 57
crates/gpui/src/elements.rs | 503 +
crates/gpui/src/elements/align.rs | 35
crates/gpui/src/elements/canvas.rs | 35
crates/gpui/src/elements/clipped.rs | 41
crates/gpui/src/elements/constrained_box.rs | 57
crates/gpui/src/elements/container.rs | 58
crates/gpui/src/elements/empty.rs | 20
crates/gpui/src/elements/expanded.rs | 36
crates/gpui/src/elements/flex.rs | 92
crates/gpui/src/elements/hook.rs | 39
crates/gpui/src/elements/image.rs | 20
crates/gpui/src/elements/keystroke_label.rs | 37
crates/gpui/src/elements/label.rs | 34
crates/gpui/src/elements/list.rs | 622 +-
crates/gpui/src/elements/mouse_event_handler.rs | 91
crates/gpui/src/elements/overlay.rs | 64
crates/gpui/src/elements/resizable.rs | 74
crates/gpui/src/elements/stack.rs | 49
crates/gpui/src/elements/svg.rs | 20
crates/gpui/src/elements/text.rs | 58
crates/gpui/src/elements/tooltip.rs | 107
crates/gpui/src/elements/uniform_list.rs | 89
crates/gpui/src/gpui.rs | 8
crates/gpui/src/platform.rs | 2
crates/gpui/src/platform/mac/platform.rs | 2
crates/gpui/src/platform/test.rs | 37
crates/gpui/src/presenter.rs | 1154 ----
crates/gpui/src/scene.rs | 22
crates/gpui/src/scene/mouse_region.rs | 292
crates/gpui/src/test.rs | 8
crates/gpui/src/text_layout.rs | 22
crates/gpui/src/views/select.rs | 54
crates/gpui_macros/src/gpui_macros.rs | 57
crates/journal/src/journal.rs | 58
crates/language_selector/Cargo.toml | 1
crates/language_selector/src/active_buffer_language.rs | 10
crates/language_selector/src/language_selector.rs | 154
crates/outline/src/outline.rs | 132
crates/picker/src/picker.rs | 228
crates/project_panel/src/project_panel.rs | 69
crates/project_symbols/src/project_symbols.rs | 204
crates/recent_projects/src/highlighted_workspace_location.rs | 4
crates/recent_projects/src/recent_projects.rs | 152
crates/search/src/buffer_search.rs | 39
crates/search/src/project_search.rs | 41
crates/settings/src/settings_file.rs | 30
crates/sum_tree/src/sum_tree.rs | 2
crates/terminal_view/src/terminal_button.rs | 14
crates/terminal_view/src/terminal_element.rs | 165
crates/terminal_view/src/terminal_view.rs | 36
crates/theme/src/ui.rs | 72
crates/theme_selector/src/theme_selector.rs | 175
crates/theme_testbench/src/theme_testbench.rs | 36
crates/util/src/test/marked_text.rs | 5
crates/vim/src/editor_events.rs | 86
crates/vim/src/motion.rs | 4
crates/vim/src/normal.rs | 49
crates/vim/src/normal/change.rs | 6
crates/vim/src/normal/delete.rs | 6
crates/vim/src/normal/yank.rs | 6
crates/vim/src/object.rs | 21
crates/vim/src/test/vim_test_context.rs | 19
crates/vim/src/vim.rs | 193
crates/vim/src/visual.rs | 10
crates/vim/test_data/test_change_sentence_object.json | 44
crates/vim/test_data/test_delete_sentence_object.json | 44
crates/vim/test_data/test_f_and_t.json | 430
crates/welcome/src/base_keymap_picker.rs | 123
crates/welcome/src/welcome.rs | 8
crates/workspace/src/dock.rs | 34
crates/workspace/src/dock/toggle_dock_button.rs | 20
crates/workspace/src/item.rs | 179
crates/workspace/src/notifications.rs | 16
crates/workspace/src/pane.rs | 306
crates/workspace/src/pane/dragged_item_receiver.rs | 31
crates/workspace/src/pane_group.rs | 18
crates/workspace/src/persistence/model.rs | 21
crates/workspace/src/searchable.rs | 43
crates/workspace/src/shared_screen.rs | 24
crates/workspace/src/sidebar.rs | 22
crates/workspace/src/status_bar.rs | 47
crates/workspace/src/toolbar.rs | 26
crates/workspace/src/workspace.rs | 409
crates/zed/src/main.rs | 132
crates/zed/src/zed.rs | 268
133 files changed, 6,995 insertions(+), 6,430 deletions(-)
@@ -3371,6 +3371,7 @@ dependencies = [
"project",
"settings",
"theme",
+ "util",
"workspace",
]
@@ -2,10 +2,10 @@ use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
use editor::Editor;
use futures::StreamExt;
use gpui::{
- actions,
+ actions, anyhow,
elements::*,
platform::{CursorStyle, MouseButton},
- Action, AppContext, Entity, ModelHandle, RenderContext, View, ViewContext, ViewHandle,
+ Action, AppContext, Entity, ModelHandle, View, ViewContext, ViewHandle,
};
use language::{LanguageRegistry, LanguageServerBinaryStatus};
use project::{LanguageServerProgress, Project};
@@ -73,11 +73,12 @@ impl ActivityIndicator {
status: event,
});
cx.notify();
- });
+ })?;
} else {
break;
}
}
+ anyhow::Ok(())
})
.detach();
cx.observe(&project, |_, _, cx| cx.notify()).detach();
@@ -172,7 +173,7 @@ impl ActivityIndicator {
.flatten()
}
- fn content_to_render(&mut self, cx: &mut RenderContext<Self>) -> Content {
+ fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Content {
// Show any language server has pending activity.
let mut pending_work = self.pending_language_server_work(cx);
if let Some(PendingWork {
@@ -314,14 +315,14 @@ impl View for ActivityIndicator {
"ActivityIndicator"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let Content {
icon,
message,
action,
} = self.content_to_render(cx);
- let mut element = MouseEventHandler::<Self>::new(0, cx, |state, cx| {
+ let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
let theme = &cx
.global::<Settings>()
.theme
@@ -361,7 +362,7 @@ impl View for ActivityIndicator {
if let Some(action) = action {
element = element
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_any_action(action.boxed_clone())
});
}
@@ -113,7 +113,7 @@ pub fn notify_of_any_new_update(
.read(cx)
.set_should_show_update_notification(false, cx)
.detach_and_log_err(cx);
- });
+ })?;
}
}
anyhow::Ok(())
@@ -2,7 +2,7 @@ use crate::ViewReleaseNotes;
use gpui::{
elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
platform::{AppVersion, CursorStyle, MouseButton},
- Element, Entity, View, ViewContext,
+ Drawable, Entity, View, ViewContext,
};
use menu::Cancel;
use settings::Settings;
@@ -26,13 +26,13 @@ impl View for UpdateNotification {
"UpdateNotification"
}
- fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
+ fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::Element<Self> {
let theme = cx.global::<Settings>().theme.clone();
let theme = &theme.update_notification;
let app_name = cx.global::<ReleaseChannel>().display_name();
- MouseEventHandler::<ViewReleaseNotes>::new(0, cx, |state, cx| {
+ MouseEventHandler::<ViewReleaseNotes, _>::new(0, cx, |state, cx| {
Flex::column()
.with_child(
Flex::row()
@@ -50,7 +50,7 @@ impl View for UpdateNotification {
.boxed(),
)
.with_child(
- MouseEventHandler::<Cancel>::new(0, cx, |state, _| {
+ MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state, false);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)
@@ -65,7 +65,9 @@ impl View for UpdateNotification {
.boxed()
})
.with_padding(Padding::uniform(5.))
- .on_click(MouseButton::Left, move |_, cx| cx.dispatch_action(Cancel))
+ .on_click(MouseButton::Left, move |_, _, cx| {
+ cx.dispatch_action(Cancel)
+ })
.aligned()
.constrained()
.with_height(cx.font_cache().line_height(theme.message.text.font_size))
@@ -87,7 +89,7 @@ impl View for UpdateNotification {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, cx| {
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(ViewReleaseNotes)
})
.boxed()
@@ -1,6 +1,6 @@
use gpui::{
- elements::*, platform::MouseButton, AppContext, Entity, RenderContext, Subscription, View,
- ViewContext, ViewHandle,
+ elements::*, platform::MouseButton, AppContext, Entity, Subscription, View, ViewContext,
+ ViewHandle,
};
use itertools::Itertools;
use search::ProjectSearchView;
@@ -41,7 +41,7 @@ impl View for Breadcrumbs {
"Breadcrumbs"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let active_item = match &self.active_item {
Some(active_item) => active_item,
None => return Empty::new().boxed(),
@@ -54,10 +54,22 @@ impl View for Breadcrumbs {
let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
Some(breadcrumbs) => breadcrumbs,
None => return Empty::new().boxed(),
- };
+ }
+ .into_iter()
+ .map(|breadcrumb| {
+ let text = Text::new(
+ breadcrumb.text,
+ theme.workspace.breadcrumbs.default.text.clone(),
+ );
+ if let Some(highlights) = breadcrumb.highlights {
+ text.with_highlights(highlights).boxed()
+ } else {
+ text.boxed()
+ }
+ });
let crumbs = Flex::row()
- .with_children(Itertools::intersperse_with(breadcrumbs.into_iter(), || {
+ .with_children(Itertools::intersperse_with(breadcrumbs, || {
Label::new(" 〉 ", style.default.text.clone()).boxed()
}))
.constrained()
@@ -72,14 +84,14 @@ impl View for Breadcrumbs {
.boxed();
}
- MouseEventHandler::<Breadcrumbs>::new(0, cx, |state, _| {
+ MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
let style = style.style_for(state, false);
crumbs.with_style(style.container).boxed()
})
- .on_click(MouseButton::Left, |_, cx| {
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(outline::Toggle);
})
- .with_tooltip::<Breadcrumbs, _>(
+ .with_tooltip::<Breadcrumbs>(
0,
"Show symbol outline".to_owned(),
Some(Box::new(outline::Toggle)),
@@ -136,7 +148,7 @@ impl ToolbarItemView for Breadcrumbs {
}
}
- fn pane_focus_update(&mut self, pane_focused: bool, _: &mut gpui::AppContext) {
+ fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
self.pane_focused = pane_focused;
}
}
@@ -1470,7 +1470,8 @@ async fn test_host_disconnect(
deterministic.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
- let (_, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+ let (window_id_b, workspace_b) =
+ cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, cx)
@@ -1479,12 +1480,9 @@ async fn test_host_disconnect(
.unwrap()
.downcast::<Editor>()
.unwrap();
- cx_b.read(|cx| {
- assert_eq!(
- cx.focused_view_id(workspace_b.window_id()),
- Some(editor_b.id())
- );
- });
+ assert!(cx_b
+ .read_window(window_id_b, |cx| editor_b.is_focused(cx))
+ .unwrap());
editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
assert!(cx_b.is_window_edited(workspace_b.window_id()));
@@ -1498,8 +1496,8 @@ async fn test_host_disconnect(
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
// Ensure client B's edited state is reset and that the whole window is blurred.
- cx_b.read(|cx| {
- assert_eq!(cx.focused_view_id(workspace_b.window_id()), None);
+ cx_b.read_window(window_id_b, |cx| {
+ assert_eq!(cx.focused_view_id(), None);
});
assert!(!cx_b.is_window_edited(workspace_b.window_id()));
@@ -6228,7 +6226,8 @@ async fn test_basic_following(
.update(cx_a, |workspace, cx| {
workspace::Pane::go_back(workspace, None, cx)
})
- .await;
+ .await
+ .unwrap();
deterministic.run_until_parked();
workspace_b.read_with(cx_b, |workspace, cx| {
assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
@@ -6238,7 +6237,8 @@ async fn test_basic_following(
.update(cx_a, |workspace, cx| {
workspace::Pane::go_back(workspace, None, cx)
})
- .await;
+ .await
+ .unwrap();
deterministic.run_until_parked();
workspace_b.read_with(cx_b, |workspace, cx| {
assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b2.id());
@@ -6248,7 +6248,8 @@ async fn test_basic_following(
.update(cx_a, |workspace, cx| {
workspace::Pane::go_forward(workspace, None, cx)
})
- .await;
+ .await
+ .unwrap();
deterministic.run_until_parked();
workspace_b.read_with(cx_b, |workspace, cx| {
assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
@@ -16,7 +16,7 @@ use gpui::{
impl_internal_actions,
json::{self, ToJson},
platform::{CursorStyle, MouseButton},
- AppContext, Entity, ImageData, ModelHandle, RenderContext, Subscription, View, ViewContext,
+ AppContext, Entity, ImageData, ModelHandle, SceneBuilder, Subscription, View, ViewContext,
ViewHandle, WeakViewHandle,
};
use settings::Settings;
@@ -68,7 +68,7 @@ impl View for CollabTitlebarItem {
"CollabTitlebarItem"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let workspace = if let Some(workspace) = self.workspace.upgrade(cx) {
workspace
} else {
@@ -325,8 +325,8 @@ impl CollabTitlebarItem {
fn render_toggle_contacts_button(
&self,
theme: &Theme,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let titlebar = &theme.workspace.titlebar;
let badge = if self
@@ -352,7 +352,7 @@ impl CollabTitlebarItem {
Stack::new()
.with_child(
- MouseEventHandler::<ToggleContactsMenu>::new(0, cx, |state, _| {
+ MouseEventHandler::<ToggleContactsMenu, Self>::new(0, cx, |state, _| {
let style = titlebar
.toggle_contacts_button
.style_for(state, self.contacts_popover.is_some());
@@ -369,10 +369,10 @@ impl CollabTitlebarItem {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ToggleContactsMenu);
})
- .with_tooltip::<ToggleContactsMenu, _>(
+ .with_tooltip::<ToggleContactsMenu>(
0,
"Show contacts menu".into(),
Some(Box::new(ToggleContactsMenu)),
@@ -390,8 +390,8 @@ impl CollabTitlebarItem {
&self,
theme: &Theme,
room: &ModelHandle<Room>,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let icon;
let tooltip;
if room.read(cx).is_screen_sharing() {
@@ -403,7 +403,7 @@ impl CollabTitlebarItem {
}
let titlebar = &theme.workspace.titlebar;
- MouseEventHandler::<ToggleScreenSharing>::new(0, cx, |state, _| {
+ MouseEventHandler::<ToggleScreenSharing, Self>::new(0, cx, |state, _| {
let style = titlebar.call_control.style_for(state, false);
Svg::new(icon)
.with_color(style.color)
@@ -418,10 +418,10 @@ impl CollabTitlebarItem {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ToggleScreenSharing);
})
- .with_tooltip::<ToggleScreenSharing, _>(
+ .with_tooltip::<ToggleScreenSharing>(
0,
tooltip.into(),
Some(Box::new(ToggleScreenSharing)),
@@ -436,8 +436,8 @@ impl CollabTitlebarItem {
&self,
workspace: &ViewHandle<Workspace>,
theme: &Theme,
- cx: &mut RenderContext<Self>,
- ) -> Option<ElementBox> {
+ cx: &mut ViewContext<Self>,
+ ) -> Option<Element<Self>> {
let project = workspace.read(cx).project();
if project.read(cx).is_remote() {
return None;
@@ -457,7 +457,7 @@ impl CollabTitlebarItem {
Some(
Stack::new()
.with_child(
- MouseEventHandler::<ShareUnshare>::new(0, cx, |state, _| {
+ MouseEventHandler::<ShareUnshare, Self>::new(0, cx, |state, _| {
//TODO: Ensure this button has consistant width for both text variations
let style = titlebar.share_button.style_for(state, false);
Label::new(label, style.text.clone())
@@ -466,14 +466,14 @@ impl CollabTitlebarItem {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
if is_shared {
cx.dispatch_action(UnshareProject);
} else {
cx.dispatch_action(ShareProject);
}
})
- .with_tooltip::<ShareUnshare, _>(
+ .with_tooltip::<ShareUnshare>(
0,
tooltip.to_owned(),
None,
@@ -489,12 +489,12 @@ impl CollabTitlebarItem {
)
}
- fn render_user_menu_button(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render_user_menu_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> Element<Self> {
let titlebar = &theme.workspace.titlebar;
Stack::new()
.with_child(
- MouseEventHandler::<ToggleUserMenu>::new(0, cx, |state, _| {
+ MouseEventHandler::<ToggleUserMenu, Self>::new(0, cx, |state, _| {
let style = titlebar.call_control.style_for(state, false);
Svg::new("icons/ellipsis_14.svg")
.with_color(style.color)
@@ -509,10 +509,10 @@ impl CollabTitlebarItem {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ToggleUserMenu);
})
- .with_tooltip::<ToggleUserMenu, _>(
+ .with_tooltip::<ToggleUserMenu>(
0,
"Toggle user menu".to_owned(),
Some(Box::new(ToggleUserMenu)),
@@ -533,9 +533,9 @@ impl CollabTitlebarItem {
.boxed()
}
- fn render_sign_in_button(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> Element<Self> {
let titlebar = &theme.workspace.titlebar;
- MouseEventHandler::<SignIn>::new(0, cx, |state, _| {
+ MouseEventHandler::<SignIn, Self>::new(0, cx, |state, _| {
let style = titlebar.sign_in_prompt.style_for(state, false);
Label::new("Sign In", style.text.clone())
.contained()
@@ -543,7 +543,7 @@ impl CollabTitlebarItem {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(SignIn);
})
.boxed()
@@ -552,8 +552,8 @@ impl CollabTitlebarItem {
fn render_contacts_popover_host<'a>(
&'a self,
_theme: &'a theme::Titlebar,
- cx: &'a RenderContext<Self>,
- ) -> Option<ElementBox> {
+ cx: &'a ViewContext<Self>,
+ ) -> Option<Element<Self>> {
self.contacts_popover.as_ref().map(|popover| {
Overlay::new(ChildView::new(popover, cx).boxed())
.with_fit_mode(OverlayFitMode::SwitchAnchor)
@@ -571,8 +571,8 @@ impl CollabTitlebarItem {
workspace: &ViewHandle<Workspace>,
theme: &Theme,
room: &ModelHandle<Room>,
- cx: &mut RenderContext<Self>,
- ) -> Vec<ElementBox> {
+ cx: &mut ViewContext<Self>,
+ ) -> Vec<Element<Self>> {
let mut participants = room
.read(cx)
.remote_participants()
@@ -613,8 +613,8 @@ impl CollabTitlebarItem {
theme: &Theme,
user: &Arc<User>,
peer_id: PeerId,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let replica_id = workspace.read(cx).project().read(cx).replica_id();
Container::new(self.render_face_pile(
user,
@@ -637,8 +637,8 @@ impl CollabTitlebarItem {
location: Option<ParticipantLocation>,
workspace: &ViewHandle<Workspace>,
theme: &Theme,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let project_id = workspace.read(cx).project().read(cx).remote_id();
let room = ActiveCall::global(cx).read(cx).room();
let is_being_followed = workspace.read(cx).is_being_followed(peer_id);
@@ -749,41 +749,42 @@ impl CollabTitlebarItem {
if let Some(location) = location {
if let Some(replica_id) = replica_id {
- content =
- MouseEventHandler::<ToggleFollow>::new(replica_id.into(), cx, move |_, _| {
- content
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
- cx.dispatch_action(ToggleFollow(peer_id))
- })
- .with_tooltip::<ToggleFollow, _>(
- peer_id.as_u64() as usize,
- if is_being_followed {
- format!("Unfollow {}", user.github_login)
- } else {
- format!("Follow {}", user.github_login)
- },
- Some(Box::new(FollowNextCollaborator)),
- theme.tooltip.clone(),
- cx,
- )
- .boxed();
+ content = MouseEventHandler::<ToggleFollow, Self>::new(
+ replica_id.into(),
+ cx,
+ move |_, _| content,
+ )
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, move |_, _, cx| {
+ cx.dispatch_action(ToggleFollow(peer_id))
+ })
+ .with_tooltip::<ToggleFollow>(
+ peer_id.as_u64() as usize,
+ if is_being_followed {
+ format!("Unfollow {}", user.github_login)
+ } else {
+ format!("Follow {}", user.github_login)
+ },
+ Some(Box::new(FollowNextCollaborator)),
+ theme.tooltip.clone(),
+ cx,
+ )
+ .boxed();
} else if let ParticipantLocation::SharedProject { project_id } = location {
let user_id = user.id;
- content = MouseEventHandler::<JoinProject>::new(
+ content = MouseEventHandler::<JoinProject, Self>::new(
peer_id.as_u64() as usize,
cx,
move |_, _| content,
)
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(JoinProject {
project_id,
follow_user_id: user_id,
})
})
- .with_tooltip::<JoinProject, _>(
+ .with_tooltip::<JoinProject>(
peer_id.as_u64() as usize,
format!("Follow {} into external project", user.github_login),
Some(Box::new(FollowNextCollaborator)),
@@ -800,7 +801,7 @@ impl CollabTitlebarItem {
workspace: &ViewHandle<Workspace>,
location: Option<ParticipantLocation>,
mut style: AvatarStyle,
- cx: &RenderContext<Self>,
+ cx: &ViewContext<Self>,
) -> AvatarStyle {
if let Some(location) = location {
if let ParticipantLocation::SharedProject { project_id } = location {
@@ -815,11 +816,11 @@ impl CollabTitlebarItem {
style
}
- fn render_face(
+ fn render_face<V: View>(
avatar: Arc<ImageData>,
avatar_style: AvatarStyle,
background_color: Color,
- ) -> ElementBox {
+ ) -> Element<V> {
Image::from_data(avatar)
.with_style(avatar_style.image)
.aligned()
@@ -836,8 +837,8 @@ impl CollabTitlebarItem {
fn render_connection_status(
&self,
status: &client::Status,
- cx: &mut RenderContext<Self>,
- ) -> Option<ElementBox> {
+ cx: &mut ViewContext<Self>,
+ ) -> Option<Element<Self>> {
enum ConnectionStatusButton {}
let theme = &cx.global::<Settings>().theme.clone();
@@ -863,7 +864,7 @@ impl CollabTitlebarItem {
.boxed(),
),
client::Status::UpgradeRequired => Some(
- MouseEventHandler::<ConnectionStatusButton>::new(0, cx, |_, _| {
+ MouseEventHandler::<ConnectionStatusButton, Self>::new(0, cx, |_, _| {
Label::new(
"Please update Zed to collaborate",
theme.workspace.titlebar.outdated_warning.text.clone(),
@@ -874,7 +875,7 @@ impl CollabTitlebarItem {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, cx| {
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(auto_update::Check);
})
.boxed(),
@@ -894,7 +895,7 @@ impl AvatarRibbon {
}
}
-impl Element for AvatarRibbon {
+impl Drawable<CollabTitlebarItem> for AvatarRibbon {
type LayoutState = ();
type PaintState = ();
@@ -902,17 +903,20 @@ impl Element for AvatarRibbon {
fn layout(
&mut self,
constraint: gpui::SizeConstraint,
- _: &mut gpui::LayoutContext,
+ _: &mut CollabTitlebarItem,
+ _: &mut ViewContext<CollabTitlebarItem>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
(constraint.max, ())
}
fn paint(
&mut self,
- bounds: gpui::geometry::rect::RectF,
- _: gpui::geometry::rect::RectF,
+ scene: &mut SceneBuilder,
+ bounds: RectF,
+ _: RectF,
_: &mut Self::LayoutState,
- cx: &mut gpui::PaintContext,
+ _: &mut CollabTitlebarItem,
+ _: &mut ViewContext<CollabTitlebarItem>,
) -> Self::PaintState {
let mut path = PathBuilder::new();
path.reset(bounds.lower_left());
@@ -923,7 +927,7 @@ impl Element for AvatarRibbon {
path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
path.curve_to(bounds.lower_right(), bounds.upper_right());
path.line_to(bounds.lower_left());
- cx.scene.push_path(path.build(self.color, None));
+ scene.push_path(path.build(self.color, None));
}
fn rect_for_text_range(
@@ -933,17 +937,19 @@ impl Element for AvatarRibbon {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &gpui::MeasurementContext,
+ _: &CollabTitlebarItem,
+ _: &ViewContext<CollabTitlebarItem>,
) -> Option<RectF> {
None
}
fn debug(
&self,
- bounds: gpui::geometry::rect::RectF,
+ bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &gpui::DebugContext,
+ _: &CollabTitlebarItem,
+ _: &ViewContext<CollabTitlebarItem>,
) -> gpui::json::Value {
json::json!({
"type": "AvatarRibbon",
@@ -126,7 +126,7 @@ fn join_project(action: &JoinProject, app_state: Arc<AppState>, cx: &mut AppCont
}
}
}
- });
+ })?;
anyhow::Ok(())
})
@@ -1,10 +1,7 @@
use call::ActiveCall;
use client::UserStore;
use gpui::Action;
-use gpui::{
- actions, elements::*, platform::MouseButton, Entity, ModelHandle, RenderContext, View,
- ViewContext,
-};
+use gpui::{actions, elements::*, platform::MouseButton, Entity, ModelHandle, View, ViewContext};
use settings::Settings;
use crate::collab_titlebar_item::ToggleCollaboratorList;
@@ -21,7 +18,7 @@ enum Collaborator {
actions!(collaborator_list_popover, [NoOp]);
pub(crate) struct CollaboratorListPopover {
- list_state: ListState,
+ list_state: ListState<Self>,
}
impl Entity for CollaboratorListPopover {
@@ -33,10 +30,10 @@ impl View for CollaboratorListPopover {
"CollaboratorListPopover"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = cx.global::<Settings>().theme.clone();
- MouseEventHandler::<Self>::new(0, cx, |_, _| {
+ MouseEventHandler::<Self, Self>::new(0, cx, |_, _| {
List::new(self.list_state.clone())
.contained()
.with_style(theme.contacts_popover.container) //TODO: Change the name of this theme key
@@ -45,7 +42,7 @@ impl View for CollaboratorListPopover {
.with_height(theme.contacts_popover.height)
.boxed()
})
- .on_down_out(MouseButton::Left, move |_, cx| {
+ .on_down_out(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ToggleCollaboratorList);
})
.boxed()
@@ -83,7 +80,6 @@ impl CollaboratorListPopover {
collaborators.len(),
Orientation::Top,
0.,
- cx,
move |_, index, cx| match &collaborators[index] {
Collaborator::SelfUser { username } => render_collaborator_list_entry(
index,
@@ -120,8 +116,8 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
icon: Svg,
icon_action: IA,
icon_tooltip: String,
- cx: &mut RenderContext<CollaboratorListPopover>,
-) -> ElementBox {
+ cx: &mut ViewContext<CollaboratorListPopover>,
+) -> Element<CollaboratorListPopover> {
enum Username {}
enum UsernameTooltip {}
enum Icon {}
@@ -131,19 +127,20 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
let username_theme = theme.contact_list.contact_username.text.clone();
let tooltip_theme = theme.tooltip.clone();
- let username = MouseEventHandler::<Username>::new(index, cx, |_, _| {
- Label::new(username.to_owned(), username_theme.clone()).boxed()
- })
- .on_click(MouseButton::Left, move |_, cx| {
- if let Some(username_action) = username_action.clone() {
- cx.dispatch_action(username_action);
- }
- });
+ let username =
+ MouseEventHandler::<Username, CollaboratorListPopover>::new(index, cx, |_, _| {
+ Label::new(username.to_owned(), username_theme.clone()).boxed()
+ })
+ .on_click(MouseButton::Left, move |_, _, cx| {
+ if let Some(username_action) = username_action.clone() {
+ cx.dispatch_action(username_action);
+ }
+ });
Flex::row()
.with_child(if let Some(username_tooltip) = username_tooltip {
username
- .with_tooltip::<UsernameTooltip, _>(
+ .with_tooltip::<UsernameTooltip>(
index,
username_tooltip,
None,
@@ -155,11 +152,11 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
username.boxed()
})
.with_child(
- MouseEventHandler::<Icon>::new(index, cx, |_, _| icon.boxed())
- .on_click(MouseButton::Left, move |_, cx| {
+ MouseEventHandler::<Icon, CollaboratorListPopover>::new(index, cx, |_, _| icon.boxed())
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(icon_action.clone())
})
- .with_tooltip::<IconTooltip, _>(index, icon_tooltip, None, tooltip_theme, cx)
+ .with_tooltip::<IconTooltip>(index, icon_tooltip, None, tooltip_theme, cx)
.boxed(),
)
.boxed()
@@ -1,49 +1,41 @@
use client::{ContactRequestStatus, User, UserStore};
-use gpui::{
- elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, RenderContext, Task,
- View, ViewContext, ViewHandle,
-};
-use picker::{Picker, PickerDelegate};
+use gpui::{elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext};
+use picker::{Picker, PickerDelegate, PickerEvent};
use settings::Settings;
use std::sync::Arc;
use util::TryFutureExt;
pub fn init(cx: &mut AppContext) {
- Picker::<ContactFinder>::init(cx);
+ Picker::<ContactFinderDelegate>::init(cx);
}
-pub struct ContactFinder {
- picker: ViewHandle<Picker<Self>>,
- potential_contacts: Arc<[Arc<User>]>,
- user_store: ModelHandle<UserStore>,
- selected_index: usize,
-}
+pub type ContactFinder = Picker<ContactFinderDelegate>;
-pub enum Event {
- Dismissed,
+pub fn build_contact_finder(
+ user_store: ModelHandle<UserStore>,
+ cx: &mut ViewContext<ContactFinder>,
+) -> ContactFinder {
+ Picker::new(
+ ContactFinderDelegate {
+ user_store,
+ potential_contacts: Arc::from([]),
+ selected_index: 0,
+ },
+ cx,
+ )
}
-impl Entity for ContactFinder {
- type Event = Event;
+pub struct ContactFinderDelegate {
+ potential_contacts: Arc<[Arc<User>]>,
+ user_store: ModelHandle<UserStore>,
+ selected_index: usize,
}
-impl View for ContactFinder {
- fn ui_name() -> &'static str {
- "ContactFinder"
- }
-
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
- ChildView::new(&self.picker, cx).boxed()
- }
-
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.is_self_focused() {
- cx.focus(&self.picker);
- }
+impl PickerDelegate for ContactFinderDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Search collaborator by username...".into()
}
-}
-impl PickerDelegate for ContactFinder {
fn match_count(&self) -> usize {
self.potential_contacts.len()
}
@@ -52,22 +44,22 @@ impl PickerDelegate for ContactFinder {
self.selected_index
}
- fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
+ fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+ fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let search_users = self
.user_store
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
- cx.spawn(|this, mut cx| async move {
+ cx.spawn(|picker, mut cx| async move {
async {
let potential_contacts = search_users.await?;
- this.update(&mut cx, |this, cx| {
- this.potential_contacts = potential_contacts.into();
+ picker.update(&mut cx, |picker, cx| {
+ picker.delegate_mut().potential_contacts = potential_contacts.into();
cx.notify();
- });
+ })?;
anyhow::Ok(())
}
.log_err()
@@ -75,7 +67,7 @@ impl PickerDelegate for ContactFinder {
})
}
- fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
if let Some(user) = self.potential_contacts.get(self.selected_index) {
let user_store = self.user_store.read(cx);
match user_store.contact_request_status(user) {
@@ -94,8 +86,8 @@ impl PickerDelegate for ContactFinder {
}
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
- cx.emit(Event::Dismissed);
+ fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
+ cx.emit(PickerEvent::Dismiss);
}
fn render_match(
@@ -104,7 +96,7 @@ impl PickerDelegate for ContactFinder {
mouse_state: &mut MouseState,
selected: bool,
cx: &gpui::AppContext,
- ) -> ElementBox {
+ ) -> Element<Picker<Self>> {
let theme = &cx.global::<Settings>().theme;
let user = &self.potential_contacts[ix];
let request_status = self.user_store.read(cx).contact_request_status(user);
@@ -164,28 +156,3 @@ impl PickerDelegate for ContactFinder {
.boxed()
}
}
-
-impl ContactFinder {
- pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
- let this = cx.weak_handle();
- Self {
- picker: cx.add_view(|cx| {
- Picker::new("Search collaborator by username...", this, cx)
- .with_theme(|theme| theme.contact_finder.picker.clone())
- }),
- potential_contacts: Arc::from([]),
- user_store,
- selected_index: 0,
- }
- }
-
- pub fn editor_text(&self, cx: &AppContext) -> String {
- self.picker.read(cx).query(cx)
- }
-
- pub fn with_editor_text(self, editor_text: String, cx: &mut ViewContext<Self>) -> Self {
- self.picker
- .update(cx, |picker, cx| picker.set_query(editor_text, cx));
- self
- }
-}
@@ -11,7 +11,7 @@ use gpui::{
impl_actions, impl_internal_actions,
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, PromptLevel},
- AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext, ViewHandle,
+ AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle,
};
use menu::{Confirm, SelectNext, SelectPrev};
use project::Project;
@@ -159,7 +159,7 @@ pub enum Event {
pub struct ContactList {
entries: Vec<ContactEntry>,
match_candidates: Vec<StringMatchCandidate>,
- list_state: ListState,
+ list_state: ListState<Self>,
project: ModelHandle<Project>,
user_store: ModelHandle<UserStore>,
filter_editor: ViewHandle<Editor>,
@@ -202,7 +202,7 @@ impl ContactList {
})
.detach();
- let list_state = ListState::new(0, Orientation::Top, 1000., cx, move |this, ix, cx| {
+ let list_state = ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
let theme = cx.global::<Settings>().theme.clone();
let is_selected = this.selection == Some(ix);
let current_project_id = this.project.read(cx).remote_id();
@@ -748,7 +748,7 @@ impl ContactList {
is_pending: bool,
is_selected: bool,
theme: &theme::ContactList,
- ) -> ElementBox {
+ ) -> Element<Self> {
Flex::row()
.with_children(user.avatar.clone().map(|avatar| {
Image::from_data(avatar)
@@ -799,8 +799,8 @@ impl ContactList {
is_last: bool,
is_selected: bool,
theme: &theme::ContactList,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let font_cache = cx.font_cache();
let host_avatar_height = theme
.contact_avatar
@@ -819,7 +819,7 @@ impl ContactList {
worktree_root_names.join(", ")
};
- MouseEventHandler::<JoinProject>::new(project_id as usize, cx, |mouse_state, _| {
+ MouseEventHandler::<JoinProject, Self>::new(project_id as usize, cx, |mouse_state, _| {
let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
let row = theme.project_row.style_for(mouse_state, is_selected);
@@ -827,14 +827,14 @@ impl ContactList {
.with_child(
Stack::new()
.with_child(
- Canvas::new(move |bounds, _, cx| {
+ Canvas::new(move |scene, bounds, _, _, _| {
let start_x = bounds.min_x() + (bounds.width() / 2.)
- (tree_branch.width / 2.);
let end_x = bounds.max_x();
let start_y = bounds.min_y();
let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
- cx.scene.push_quad(gpui::Quad {
+ scene.push_quad(gpui::Quad {
bounds: RectF::from_points(
vec2f(start_x, start_y),
vec2f(
@@ -846,7 +846,7 @@ impl ContactList {
border: gpui::Border::default(),
corner_radius: 0.,
});
- cx.scene.push_quad(gpui::Quad {
+ scene.push_quad(gpui::Quad {
bounds: RectF::from_points(
vec2f(start_x, end_y),
vec2f(end_x, end_y + tree_branch.width),
@@ -882,7 +882,7 @@ impl ContactList {
} else {
CursorStyle::Arrow
})
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
if !is_current {
cx.dispatch_global_action(JoinProject {
project_id,
@@ -898,8 +898,8 @@ impl ContactList {
is_last: bool,
is_selected: bool,
theme: &theme::ContactList,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let font_cache = cx.font_cache();
let host_avatar_height = theme
.contact_avatar
@@ -913,7 +913,7 @@ impl ContactList {
let baseline_offset =
row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
- MouseEventHandler::<OpenSharedScreen>::new(
+ MouseEventHandler::<OpenSharedScreen, Self>::new(
peer_id.as_u64() as usize,
cx,
|mouse_state, _| {
@@ -924,7 +924,7 @@ impl ContactList {
.with_child(
Stack::new()
.with_child(
- Canvas::new(move |bounds, _, cx| {
+ Canvas::new(move |scene, bounds, _, _, _| {
let start_x = bounds.min_x() + (bounds.width() / 2.)
- (tree_branch.width / 2.);
let end_x = bounds.max_x();
@@ -932,7 +932,7 @@ impl ContactList {
let end_y =
bounds.min_y() + baseline_offset - (cap_height / 2.);
- cx.scene.push_quad(gpui::Quad {
+ scene.push_quad(gpui::Quad {
bounds: RectF::from_points(
vec2f(start_x, start_y),
vec2f(
@@ -944,7 +944,7 @@ impl ContactList {
border: gpui::Border::default(),
corner_radius: 0.,
});
- cx.scene.push_quad(gpui::Quad {
+ scene.push_quad(gpui::Quad {
bounds: RectF::from_points(
vec2f(start_x, end_y),
vec2f(end_x, end_y + tree_branch.width),
@@ -988,7 +988,7 @@ impl ContactList {
},
)
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(OpenSharedScreen { peer_id });
})
.boxed()
@@ -999,8 +999,8 @@ impl ContactList {
theme: &theme::ContactList,
is_selected: bool,
is_collapsed: bool,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
enum Header {}
enum LeaveCallContactList {}
@@ -1015,14 +1015,14 @@ impl ContactList {
};
let leave_call = if section == Section::ActiveCall {
Some(
- MouseEventHandler::<LeaveCallContactList>::new(0, cx, |state, _| {
+ MouseEventHandler::<LeaveCallContactList, Self>::new(0, cx, |state, _| {
let style = theme.leave_call.style_for(state, false);
Label::new("Leave Call", style.text.clone())
.contained()
.with_style(style.container)
.boxed()
})
- .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(LeaveCall))
+ .on_click(MouseButton::Left, |_, _, cx| cx.dispatch_action(LeaveCall))
.aligned()
.boxed(),
)
@@ -1031,7 +1031,7 @@ impl ContactList {
};
let icon_size = theme.section_icon_size;
- MouseEventHandler::<Header>::new(section as usize, cx, |_, _| {
+ MouseEventHandler::<Header, Self>::new(section as usize, cx, |_, _| {
Flex::row()
.with_child(
Svg::new(if is_collapsed {
@@ -1065,7 +1065,7 @@ impl ContactList {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ToggleExpanded(section))
})
.boxed()
@@ -1077,15 +1077,15 @@ impl ContactList {
project: &ModelHandle<Project>,
theme: &theme::ContactList,
is_selected: bool,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let online = contact.online;
let busy = contact.busy || calling;
let user_id = contact.user.id;
let github_login = contact.user.github_login.clone();
let initial_project = project.clone();
let mut element =
- MouseEventHandler::<Contact>::new(contact.user.id as usize, cx, |_, cx| {
+ MouseEventHandler::<Contact, Self>::new(contact.user.id as usize, cx, |_, cx| {
Flex::row()
.with_children(contact.user.avatar.clone().map(|avatar| {
let status_badge = if contact.online {
@@ -1128,7 +1128,7 @@ impl ContactList {
.boxed(),
)
.with_child(
- MouseEventHandler::<Cancel>::new(
+ MouseEventHandler::<Cancel, Self>::new(
contact.user.id as usize,
cx,
|mouse_state, _| {
@@ -1142,7 +1142,7 @@ impl ContactList {
)
.with_padding(Padding::uniform(2.))
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(RemoveContact {
user_id,
github_login: github_login.clone(),
@@ -1172,7 +1172,7 @@ impl ContactList {
)
.boxed()
})
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
if online && !busy {
cx.dispatch_action(Call {
recipient_user_id: user_id,
@@ -1194,8 +1194,8 @@ impl ContactList {
theme: &theme::ContactList,
is_incoming: bool,
is_selected: bool,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
enum Decline {}
enum Accept {}
enum Cancel {}
@@ -1228,7 +1228,7 @@ impl ContactList {
if is_incoming {
row.add_children([
- MouseEventHandler::<Decline>::new(user.id as usize, cx, |mouse_state, _| {
+ MouseEventHandler::<Decline, Self>::new(user.id as usize, cx, |mouse_state, _| {
let button_style = if is_contact_request_pending {
&theme.disabled_button
} else {
@@ -1239,7 +1239,7 @@ impl ContactList {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(RespondToContactRequest {
user_id,
accept: false,
@@ -1248,7 +1248,7 @@ impl ContactList {
.contained()
.with_margin_right(button_spacing)
.boxed(),
- MouseEventHandler::<Accept>::new(user.id as usize, cx, |mouse_state, _| {
+ MouseEventHandler::<Accept, Self>::new(user.id as usize, cx, |mouse_state, _| {
let button_style = if is_contact_request_pending {
&theme.disabled_button
} else {
@@ -1260,7 +1260,7 @@ impl ContactList {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(RespondToContactRequest {
user_id,
accept: true,
@@ -1270,7 +1270,7 @@ impl ContactList {
]);
} else {
row.add_child(
- MouseEventHandler::<Cancel>::new(user.id as usize, cx, |mouse_state, _| {
+ MouseEventHandler::<Cancel, Self>::new(user.id as usize, cx, |mouse_state, _| {
let button_style = if is_contact_request_pending {
&theme.disabled_button
} else {
@@ -1283,7 +1283,7 @@ impl ContactList {
})
.with_padding(Padding::uniform(2.))
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(RemoveContact {
user_id,
github_login: github_login.clone(),
@@ -1331,7 +1331,7 @@ impl View for ContactList {
cx
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
enum AddContact {}
let theme = cx.global::<Settings>().theme.clone();
@@ -1346,7 +1346,7 @@ impl View for ContactList {
.boxed(),
)
.with_child(
- MouseEventHandler::<AddContact>::new(0, cx, |_, _| {
+ MouseEventHandler::<AddContact, Self>::new(0, cx, |_, _| {
render_icon_button(
&theme.contact_list.add_contact_button,
"icons/user_plus_16.svg",
@@ -1354,10 +1354,10 @@ impl View for ContactList {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, cx| {
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(contacts_popover::ToggleContactFinder)
})
- .with_tooltip::<AddContact, _>(
+ .with_tooltip::<AddContact>(
0,
"Search for new contact".into(),
None,
@@ -1387,7 +1387,7 @@ impl View for ContactList {
}
}
-fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Element {
+fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Drawable<ContactList> {
Svg::new(svg_path)
.with_color(style.color)
.constrained()
@@ -3,8 +3,7 @@ use std::sync::Arc;
use crate::notifications::render_user_notification;
use client::{ContactEventKind, User, UserStore};
use gpui::{
- elements::*, impl_internal_actions, AppContext, Entity, ModelHandle, RenderContext, View,
- ViewContext,
+ elements::*, impl_internal_actions, AppContext, Entity, ModelHandle, View, ViewContext,
};
use workspace::notifications::Notification;
@@ -43,7 +42,7 @@ impl View for ContactNotification {
"ContactNotification"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
match self.kind {
ContactEventKind::Requested => render_user_notification(
self.user.clone(),
@@ -1,9 +1,14 @@
-use crate::{contact_finder::ContactFinder, contact_list::ContactList, ToggleContactsMenu};
+use crate::{
+ contact_finder::{build_contact_finder, ContactFinder},
+ contact_list::ContactList,
+ ToggleContactsMenu,
+};
use client::UserStore;
use gpui::{
- actions, elements::*, platform::MouseButton, AppContext, Entity, ModelHandle, RenderContext,
- View, ViewContext, ViewHandle,
+ actions, elements::*, platform::MouseButton, AppContext, Entity, ModelHandle, View,
+ ViewContext, ViewHandle,
};
+use picker::PickerEvent;
use project::Project;
use settings::Settings;
@@ -50,19 +55,19 @@ impl ContactsPopover {
fn toggle_contact_finder(&mut self, _: &ToggleContactFinder, cx: &mut ViewContext<Self>) {
match &self.child {
Child::ContactList(list) => self.show_contact_finder(list.read(cx).editor_text(cx), cx),
- Child::ContactFinder(finder) => {
- self.show_contact_list(finder.read(cx).editor_text(cx), cx)
- }
+ Child::ContactFinder(finder) => self.show_contact_list(finder.read(cx).query(cx), cx),
}
}
fn show_contact_finder(&mut self, editor_text: String, cx: &mut ViewContext<ContactsPopover>) {
let child = cx.add_view(|cx| {
- ContactFinder::new(self.user_store.clone(), cx).with_editor_text(editor_text, cx)
+ let finder = build_contact_finder(self.user_store.clone(), cx);
+ finder.set_query(editor_text, cx);
+ finder
});
cx.focus(&child);
self._subscription = Some(cx.subscribe(&child, |_, _, event, cx| match event {
- crate::contact_finder::Event::Dismissed => cx.emit(Event::Dismissed),
+ PickerEvent::Dismiss => cx.emit(Event::Dismissed),
}));
self.child = Child::ContactFinder(child);
cx.notify();
@@ -91,14 +96,14 @@ impl View for ContactsPopover {
"ContactsPopover"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = cx.global::<Settings>().theme.clone();
let child = match &self.child {
Child::ContactList(child) => ChildView::new(child, cx),
Child::ContactFinder(child) => ChildView::new(child, cx),
};
- MouseEventHandler::<ContactsPopover>::new(0, cx, |_, _| {
+ MouseEventHandler::<ContactsPopover, Self>::new(0, cx, |_, _| {
Flex::column()
.with_child(child.flex(1., true).boxed())
.contained()
@@ -108,7 +113,7 @@ impl View for ContactsPopover {
.with_height(theme.contacts_popover.height)
.boxed()
})
- .on_down_out(MouseButton::Left, move |_, cx| {
+ .on_down_out(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ToggleContactsMenu);
})
.boxed()
@@ -7,12 +7,14 @@ use gpui::{
},
json::ToJson,
serde_json::{self, json},
- Axis, DebugContext, Element, ElementBox, MeasurementContext, PaintContext,
+ Axis, Drawable, Element, SceneBuilder, ViewContext,
};
+use crate::CollabTitlebarItem;
+
pub(crate) struct FacePile {
overlap: f32,
- faces: Vec<ElementBox>,
+ faces: Vec<Element<CollabTitlebarItem>>,
}
impl FacePile {
@@ -24,20 +26,21 @@ impl FacePile {
}
}
-impl Element for FacePile {
+impl Drawable<CollabTitlebarItem> for FacePile {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: gpui::SizeConstraint,
- cx: &mut gpui::LayoutContext,
+ view: &mut CollabTitlebarItem,
+ cx: &mut ViewContext<CollabTitlebarItem>,
) -> (Vector2F, Self::LayoutState) {
debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
let mut width = 0.;
for face in &mut self.faces {
- width += face.layout(constraint, cx).x();
+ width += face.layout(constraint, view, cx).x();
}
width -= self.overlap * self.faces.len().saturating_sub(1) as f32;
@@ -46,10 +49,12 @@ impl Element for FacePile {
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_layout: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut CollabTitlebarItem,
+ cx: &mut ViewContext<CollabTitlebarItem>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
@@ -59,8 +64,8 @@ impl Element for FacePile {
for face in self.faces.iter_mut().rev() {
let size = face.size();
origin_x -= size.x();
- cx.paint_layer(None, |cx| {
- face.paint(vec2f(origin_x, origin_y), visible_bounds, cx);
+ scene.paint_layer(None, |scene| {
+ face.paint(scene, vec2f(origin_x, origin_y), visible_bounds, view, cx);
});
origin_x += self.overlap;
}
@@ -75,7 +80,8 @@ impl Element for FacePile {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &MeasurementContext,
+ _: &CollabTitlebarItem,
+ _: &ViewContext<CollabTitlebarItem>,
) -> Option<RectF> {
None
}
@@ -85,7 +91,8 @@ impl Element for FacePile {
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &DebugContext,
+ _: &CollabTitlebarItem,
+ _: &ViewContext<CollabTitlebarItem>,
) -> serde_json::Value {
json!({
"type": "FacePile",
@@ -94,8 +101,8 @@ impl Element for FacePile {
}
}
-impl Extend<ElementBox> for FacePile {
- fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
+impl Extend<Element<CollabTitlebarItem>> for FacePile {
+ fn extend<T: IntoIterator<Item = Element<CollabTitlebarItem>>>(&mut self, children: T) {
self.faces.extend(children);
}
}
@@ -6,7 +6,7 @@ use gpui::{
geometry::{rect::RectF, vector::vec2f},
impl_internal_actions,
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
- AppContext, Entity, RenderContext, View, ViewContext,
+ AppContext, Element, Entity, View, ViewContext,
};
use settings::Settings;
use util::ResultExt;
@@ -99,7 +99,7 @@ impl IncomingCallNotification {
}
}
- fn render_caller(&self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render_caller(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
let default_project = proto::ParticipantProject::default();
let initial_project = self
@@ -165,13 +165,13 @@ impl IncomingCallNotification {
.boxed()
}
- fn render_buttons(&self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render_buttons(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
enum Accept {}
enum Decline {}
Flex::column()
.with_child(
- MouseEventHandler::<Accept>::new(0, cx, |_, cx| {
+ MouseEventHandler::<Accept, Self>::new(0, cx, |_, cx| {
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
Label::new("Accept", theme.accept_button.text.clone())
.aligned()
@@ -180,14 +180,14 @@ impl IncomingCallNotification {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, cx| {
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(RespondToCall { accept: true });
})
.flex(1., true)
.boxed(),
)
.with_child(
- MouseEventHandler::<Decline>::new(0, cx, |_, cx| {
+ MouseEventHandler::<Decline, Self>::new(0, cx, |_, cx| {
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
Label::new("Decline", theme.decline_button.text.clone())
.aligned()
@@ -196,7 +196,7 @@ impl IncomingCallNotification {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, cx| {
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(RespondToCall { accept: false });
})
.flex(1., true)
@@ -222,7 +222,7 @@ impl View for IncomingCallNotification {
"IncomingCallNotification"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let background = cx
.global::<Settings>()
.theme
@@ -2,7 +2,7 @@ use client::User;
use gpui::{
elements::*,
platform::{CursorStyle, MouseButton},
- Action, Element, ElementBox, RenderContext, View,
+ Action, Drawable, Element, View, ViewContext,
};
use settings::Settings;
use std::sync::Arc;
@@ -16,8 +16,8 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
body: Option<&'static str>,
dismiss_action: A,
buttons: Vec<(&'static str, Box<dyn Action>)>,
- cx: &mut RenderContext<V>,
-) -> ElementBox {
+ cx: &mut ViewContext<V>,
+) -> Element<V> {
let theme = cx.global::<Settings>().theme.clone();
let theme = &theme.contact_notification;
@@ -51,7 +51,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
.boxed(),
)
.with_child(
- MouseEventHandler::<Dismiss>::new(user.id as usize, cx, |state, _| {
+ MouseEventHandler::<Dismiss, V>::new(user.id as usize, cx, |state, _| {
let style = theme.dismiss_button.style_for(state, false);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)
@@ -67,7 +67,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
})
.with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(5.))
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_any_action(dismiss_action.boxed_clone())
})
.aligned()
@@ -96,7 +96,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
Flex::row()
.with_children(buttons.into_iter().enumerate().map(
|(ix, (message, action))| {
- MouseEventHandler::<Button>::new(ix, cx, |state, _| {
+ MouseEventHandler::<Button, V>::new(ix, cx, |state, _| {
let button = theme.button.style_for(state, false);
Label::new(message, button.text.clone())
.contained()
@@ -104,7 +104,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_any_action(action.boxed_clone())
})
.boxed()
@@ -6,7 +6,7 @@ use gpui::{
elements::*,
geometry::{rect::RectF, vector::vec2f},
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
- AppContext, Entity, RenderContext, View, ViewContext,
+ AppContext, Entity, View, ViewContext,
};
use settings::Settings;
use std::sync::Arc;
@@ -94,17 +94,15 @@ impl ProjectSharedNotification {
}
fn join(&mut self, _: &JoinProject, cx: &mut ViewContext<Self>) {
- let window_id = cx.window_id();
- cx.remove_window(window_id);
+ cx.remove_window();
cx.propagate_action();
}
fn dismiss(&mut self, _: &DismissProject, cx: &mut ViewContext<Self>) {
- let window_id = cx.window_id();
- cx.remove_window(window_id);
+ cx.remove_window();
}
- fn render_owner(&self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render_owner(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = &cx.global::<Settings>().theme.project_shared_notification;
Flex::row()
.with_children(self.owner.avatar.clone().map(|avatar| {
@@ -164,7 +162,7 @@ impl ProjectSharedNotification {
.boxed()
}
- fn render_buttons(&self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render_buttons(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
enum Open {}
enum Dismiss {}
@@ -173,7 +171,7 @@ impl ProjectSharedNotification {
Flex::column()
.with_child(
- MouseEventHandler::<Open>::new(0, cx, |_, cx| {
+ MouseEventHandler::<Open, Self>::new(0, cx, |_, cx| {
let theme = &cx.global::<Settings>().theme.project_shared_notification;
Label::new("Open", theme.open_button.text.clone())
.aligned()
@@ -182,7 +180,7 @@ impl ProjectSharedNotification {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(JoinProject {
project_id,
follow_user_id: owner_user_id,
@@ -192,7 +190,7 @@ impl ProjectSharedNotification {
.boxed(),
)
.with_child(
- MouseEventHandler::<Dismiss>::new(0, cx, |_, cx| {
+ MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, cx| {
let theme = &cx.global::<Settings>().theme.project_shared_notification;
Label::new("Dismiss", theme.dismiss_button.text.clone())
.aligned()
@@ -201,7 +199,7 @@ impl ProjectSharedNotification {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, cx| {
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(DismissProject);
})
.flex(1., true)
@@ -227,7 +225,7 @@ impl View for ProjectSharedNotification {
"ProjectSharedNotification"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::Element<Self> {
let background = cx
.global::<Settings>()
.theme
@@ -3,7 +3,7 @@ use gpui::{
color::Color,
elements::{MouseEventHandler, Svg},
platform::{Appearance, MouseButton},
- AppContext, Element, ElementBox, Entity, RenderContext, View,
+ AppContext, Drawable, Element, Entity, View, ViewContext,
};
use settings::Settings;
@@ -40,13 +40,13 @@ impl View for SharingStatusIndicator {
"SharingStatusIndicator"
}
- fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
- let color = match cx.appearance {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
+ let color = match cx.window_appearance() {
Appearance::Light | Appearance::VibrantLight => Color::black(),
Appearance::Dark | Appearance::VibrantDark => Color::white(),
};
- MouseEventHandler::<Self>::new(0, cx, |_, _| {
+ MouseEventHandler::<Self, Self>::new(0, cx, |_, _| {
Svg::new("icons/disable_screen_sharing_12.svg")
.with_color(color)
.constrained()
@@ -54,7 +54,7 @@ impl View for SharingStatusIndicator {
.aligned()
.boxed()
})
- .on_click(MouseButton::Left, |_, cx| {
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(ToggleScreenSharing);
})
.boxed()
@@ -1,26 +1,25 @@
use collections::CommandPaletteFilter;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
- actions,
- elements::{ChildView, Flex, Label, ParentElement},
- keymap_matcher::Keystroke,
- Action, AnyViewHandle, AppContext, Element, Entity, MouseState, RenderContext, View,
- ViewContext, ViewHandle,
+ actions, elements::*, keymap_matcher::Keystroke, Action, AppContext, Drawable, MouseState,
+ ViewContext,
};
-use picker::{Picker, PickerDelegate};
+use picker::{Picker, PickerDelegate, PickerEvent};
use settings::Settings;
use std::cmp;
+use util::ResultExt;
use workspace::Workspace;
pub fn init(cx: &mut AppContext) {
- cx.add_action(CommandPalette::toggle);
- Picker::<CommandPalette>::init(cx);
+ cx.add_action(toggle_command_palette);
+ CommandPalette::init(cx);
}
actions!(command_palette, [Toggle]);
-pub struct CommandPalette {
- picker: ViewHandle<Picker<Self>>,
+pub type CommandPalette = Picker<CommandPaletteDelegate>;
+
+pub struct CommandPaletteDelegate {
actions: Vec<Command>,
matches: Vec<StringMatch>,
selected_ix: usize,
@@ -42,11 +41,21 @@ struct Command {
keystrokes: Vec<Keystroke>,
}
-impl CommandPalette {
- pub fn new(focused_view_id: usize, cx: &mut ViewContext<Self>) -> Self {
- let this = cx.weak_handle();
+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.defer(move |workspace, cx| {
+ workspace.toggle_modal(cx, |_, cx| {
+ cx.add_view(|cx| Picker::new(CommandPaletteDelegate::new(focused_view_id, cx), cx))
+ });
+ });
+}
+
+impl CommandPaletteDelegate {
+ pub fn new(focused_view_id: usize, cx: &mut ViewContext<Picker<Self>>) -> Self {
let actions = cx
- .available_actions(cx.window_id(), focused_view_id)
+ .available_actions(focused_view_id)
.filter_map(|(name, action, bindings)| {
if cx.has_global::<CommandPaletteFilter>() {
let filter = cx.global::<CommandPaletteFilter>();
@@ -67,79 +76,20 @@ impl CommandPalette {
})
.collect();
- let picker = cx.add_view(|cx| Picker::new("Execute a command...", this, cx));
Self {
- picker,
actions,
matches: vec![],
selected_ix: 0,
focused_view_id,
}
}
-
- fn toggle(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
- let workspace = cx.handle();
- let window_id = cx.window_id();
- let focused_view_id = cx
- .focused_view_id(window_id)
- .unwrap_or_else(|| workspace.id());
-
- cx.as_mut().defer(move |cx| {
- let this = cx.add_view(&workspace, |cx| Self::new(focused_view_id, cx));
- workspace.update(cx, |workspace, cx| {
- workspace.toggle_modal(cx, |_, cx| {
- cx.subscribe(&this, Self::on_event).detach();
- this
- });
- });
- });
- }
-
- fn on_event(
- workspace: &mut Workspace,
- _: ViewHandle<Self>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Dismissed => workspace.dismiss_modal(cx),
- Event::Confirmed {
- window_id,
- focused_view_id,
- action,
- } => {
- let window_id = *window_id;
- let focused_view_id = *focused_view_id;
- let action = action.boxed_clone();
- workspace.dismiss_modal(cx);
- cx.as_mut()
- .defer(move |cx| cx.dispatch_any_action_at(window_id, focused_view_id, action))
- }
- }
- }
-}
-
-impl Entity for CommandPalette {
- type Event = Event;
}
-impl View for CommandPalette {
- fn ui_name() -> &'static str {
- "CommandPalette"
- }
-
- fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
- ChildView::new(&self.picker, cx).boxed()
+impl PickerDelegate for CommandPaletteDelegate {
+ fn placeholder_text(&self) -> std::sync::Arc<str> {
+ "Execute a command...".into()
}
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.is_self_focused() {
- cx.focus(&self.picker);
- }
- }
-}
-
-impl PickerDelegate for CommandPalette {
fn match_count(&self) -> usize {
self.matches.len()
}
@@ -148,14 +98,14 @@ impl PickerDelegate for CommandPalette {
self.selected_ix
}
- fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
+ fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
self.selected_ix = ix;
}
fn update_matches(
&mut self,
query: String,
- cx: &mut gpui::ViewContext<Self>,
+ cx: &mut ViewContext<Picker<Self>>,
) -> gpui::Task<()> {
let candidates = self
.actions
@@ -167,7 +117,7 @@ impl PickerDelegate for CommandPalette {
char_bag: command.name.chars().collect(),
})
.collect::<Vec<_>>();
- cx.spawn(move |this, mut cx| async move {
+ cx.spawn(move |picker, mut cx| async move {
let matches = if query.is_empty() {
candidates
.into_iter()
@@ -190,32 +140,34 @@ impl PickerDelegate for CommandPalette {
)
.await
};
- this.update(&mut cx, |this, _| {
- this.matches = matches;
- if this.matches.is_empty() {
- this.selected_ix = 0;
- } else {
- this.selected_ix = cmp::min(this.selected_ix, this.matches.len() - 1);
- }
- });
+ picker
+ .update(&mut cx, |picker, _| {
+ let delegate = picker.delegate_mut();
+ delegate.matches = matches;
+ if delegate.matches.is_empty() {
+ delegate.selected_ix = 0;
+ } else {
+ delegate.selected_ix =
+ cmp::min(delegate.selected_ix, delegate.matches.len() - 1);
+ }
+ })
+ .log_err();
})
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
- cx.emit(Event::Dismissed);
- }
+ fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
- fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
if !self.matches.is_empty() {
+ let window_id = cx.window_id();
+ let focused_view_id = self.focused_view_id;
let action_ix = self.matches[self.selected_ix].candidate_id;
- cx.emit(Event::Confirmed {
- window_id: cx.window_id(),
- focused_view_id: self.focused_view_id,
- action: self.actions.remove(action_ix).action,
+ let action = self.actions.remove(action_ix).action;
+ cx.defer(move |_, cx| {
+ cx.dispatch_any_action_at(window_id, focused_view_id, action);
});
- } else {
- cx.emit(Event::Dismissed);
}
+ cx.emit(PickerEvent::Dismiss);
}
fn render_match(
@@ -224,7 +176,7 @@ impl PickerDelegate for CommandPalette {
mouse_state: &mut MouseState,
selected: bool,
cx: &gpui::AppContext,
- ) -> gpui::ElementBox {
+ ) -> Element<Picker<Self>> {
let mat = &self.matches[ix];
let command = &self.actions[mat.candidate_id];
let settings = cx.global::<Settings>();
@@ -360,7 +312,7 @@ mod tests {
});
workspace.update(cx, |workspace, cx| {
- CommandPalette::toggle(workspace, &Toggle, cx)
+ toggle_command_palette(workspace, &Toggle, cx);
});
let palette = workspace.read_with(cx, |workspace, _| {
@@ -369,13 +321,15 @@ mod tests {
palette
.update(cx, |palette, cx| {
- palette.update_matches("bcksp".to_string(), cx)
+ palette
+ .delegate_mut()
+ .update_matches("bcksp".to_string(), cx)
})
.await;
palette.update(cx, |palette, cx| {
- assert_eq!(palette.matches[0].string, "editor: backspace");
- palette.confirm(cx);
+ assert_eq!(palette.delegate().matches[0].string, "editor: backspace");
+ palette.confirm(&Default::default(), cx);
});
editor.read_with(cx, |editor, cx| {
@@ -390,7 +344,7 @@ mod tests {
});
workspace.update(cx, |workspace, cx| {
- CommandPalette::toggle(workspace, &Toggle, cx);
+ toggle_command_palette(workspace, &Toggle, cx);
});
// Assert editor command not present
@@ -400,10 +354,14 @@ mod tests {
palette
.update(cx, |palette, cx| {
- palette.update_matches("bcksp".to_string(), cx)
+ palette
+ .delegate_mut()
+ .update_matches("bcksp".to_string(), cx)
})
.await;
- palette.update(cx, |palette, _| assert!(palette.matches.is_empty()));
+ palette.update(cx, |palette, _| {
+ assert!(palette.delegate().matches.is_empty())
+ });
}
}
@@ -4,15 +4,13 @@ use gpui::{
impl_internal_actions,
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton},
- Action, AnyViewHandle, AppContext, Axis, Entity, MouseState, RenderContext, SizeConstraint,
- Subscription, View, ViewContext,
+ Action, AnyViewHandle, AppContext, Axis, Entity, MouseState, SizeConstraint, Subscription,
+ View, ViewContext,
};
use menu::*;
use settings::Settings;
use std::{any::TypeId, borrow::Cow, time::Duration};
-pub type StaticItem = Box<dyn Fn(&mut AppContext) -> ElementBox>;
-
#[derive(Copy, Clone, PartialEq)]
struct Clicked;
@@ -28,7 +26,10 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(ContextMenu::cancel);
}
-type ContextMenuItemBuilder = Box<dyn Fn(&mut MouseState, &theme::ContextMenuItem) -> ElementBox>;
+pub type StaticItem = Box<dyn Fn(&mut AppContext) -> Element<ContextMenu>>;
+
+type ContextMenuItemBuilder =
+ Box<dyn Fn(&mut MouseState, &theme::ContextMenuItem) -> Element<ContextMenu>>;
pub enum ContextMenuItemLabel {
String(Cow<'static, str>),
@@ -141,7 +142,7 @@ impl View for ContextMenu {
cx
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
if !self.visible {
return Empty::new().boxed();
}
@@ -151,10 +152,10 @@ impl View for ContextMenu {
let expanded_menu = self
.render_menu(cx)
.constrained()
- .dynamically(move |constraint, cx| {
+ .dynamically(move |constraint, view, cx| {
SizeConstraint::strict_along(
Axis::Horizontal,
- collapsed_menu.layout(constraint, cx).x(),
+ collapsed_menu.layout(constraint, view, cx).x(),
)
})
.boxed();
@@ -209,9 +210,9 @@ impl ContextMenu {
cx.notify();
cx.spawn(|this, mut cx| async move {
cx.background().timer(Duration::from_millis(50)).await;
- this.update(&mut cx, |this, cx| this.cancel(&Default::default(), cx));
+ this.update(&mut cx, |this, cx| this.cancel(&Default::default(), cx))
})
- .detach();
+ .detach_and_log_err(cx);
}
}
}
@@ -314,7 +315,7 @@ impl ContextMenu {
self.visible = true;
self.show_count += 1;
if !cx.is_self_focused() {
- self.previously_focused_view_id = cx.focused_view_id(cx.window_id());
+ self.previously_focused_view_id = cx.focused_view_id();
}
cx.focus_self();
} else {
@@ -327,8 +328,10 @@ impl ContextMenu {
self.position_mode = mode;
}
- fn render_menu_for_measurement(&self, cx: &mut RenderContext<Self>) -> impl Element {
- let window_id = cx.window_id();
+ fn render_menu_for_measurement(
+ &self,
+ cx: &mut ViewContext<Self>,
+ ) -> impl Drawable<ContextMenu> {
let style = cx.global::<Settings>().theme.context_menu.clone();
Flex::row()
.with_child(
@@ -386,7 +389,6 @@ impl ContextMenu {
};
KeystrokeLabel::new(
- window_id,
view_id,
action.boxed_clone(),
style.keystroke.container,
@@ -414,14 +416,13 @@ impl ContextMenu {
.with_style(style.container)
}
- fn render_menu(&self, cx: &mut RenderContext<Self>) -> impl Element {
+ fn render_menu(&self, cx: &mut ViewContext<Self>) -> impl Drawable<ContextMenu> {
enum Menu {}
enum MenuItem {}
let style = cx.global::<Settings>().theme.context_menu.clone();
- let window_id = cx.window_id();
- MouseEventHandler::<Menu>::new(0, cx, |_, cx| {
+ MouseEventHandler::<Menu, ContextMenu>::new(0, cx, |_, cx| {
Flex::column()
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
match item {
@@ -435,7 +436,7 @@ impl ContextMenu {
}
};
- MouseEventHandler::<MenuItem>::new(ix, cx, |state, _| {
+ MouseEventHandler::<MenuItem, ContextMenu>::new(ix, cx, |state, _| {
let style =
style.item.style_for(state, Some(ix) == self.selected_index);
@@ -452,7 +453,6 @@ impl ContextMenu {
})
.with_child({
KeystrokeLabel::new(
- window_id,
view_id,
action.boxed_clone(),
style.keystroke.container,
@@ -466,14 +466,14 @@ impl ContextMenu {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_up(MouseButton::Left, |_, _| {}) // Capture these events
- .on_down(MouseButton::Left, |_, _| {}) // Capture these events
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_up(MouseButton::Left, |_, _, _| {}) // Capture these events
+ .on_down(MouseButton::Left, |_, _, _| {}) // Capture these events
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(Clicked);
let window_id = cx.window_id();
cx.dispatch_any_action_at(window_id, view_id, action.boxed_clone());
})
- .on_drag(MouseButton::Left, |_, _| {})
+ .on_drag(MouseButton::Left, |_, _, _| {})
.boxed()
}
@@ -491,7 +491,7 @@ impl ContextMenu {
.with_style(style.container)
.boxed()
})
- .on_down_out(MouseButton::Left, |_, cx| cx.dispatch_action(Cancel))
- .on_down_out(MouseButton::Right, |_, cx| cx.dispatch_action(Cancel))
+ .on_down_out(MouseButton::Left, |_, _, cx| cx.dispatch_action(Cancel))
+ .on_down_out(MouseButton::Right, |_, _, cx| cx.dispatch_action(Cancel))
}
}
@@ -48,8 +48,8 @@ pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, cx: &mut
cx.observe(&copilot, |handle, cx| {
let status = handle.read(cx).status();
- cx.update_global::<collections::CommandPaletteFilter, _, _>(
- move |filter, _cx| match status {
+ cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
+ match status {
Status::Disabled => {
filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE);
@@ -62,8 +62,8 @@ pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, cx: &mut
filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
}
- },
- );
+ }
+ });
})
.detach();
@@ -4,7 +4,8 @@ use gpui::{
geometry::rect::RectF,
impl_internal_actions,
platform::{WindowBounds, WindowKind, WindowOptions},
- AppContext, ClipboardItem, Element, Entity, View, ViewContext, ViewHandle,
+ AnyViewHandle, AppContext, ClipboardItem, Drawable, Element, Entity, View, ViewContext,
+ ViewHandle,
};
use settings::Settings;
use theme::ui::modal;
@@ -31,26 +32,32 @@ pub fn init(cx: &mut AppContext) {
match &status {
crate::Status::SigningIn { prompt } => {
if let Some(code_verification_handle) = code_verification.as_mut() {
- if cx.has_window(code_verification_handle.window_id()) {
- code_verification_handle.update(cx, |code_verification_view, cx| {
- code_verification_view.set_status(status, cx)
+ let window_id = code_verification_handle.window_id();
+ if cx.has_window(window_id) {
+ cx.update_window(window_id, |cx| {
+ code_verification_handle.update(cx, |code_verification, cx| {
+ code_verification.set_status(status, cx)
+ });
+ cx.activate_window();
});
- cx.activate_window(code_verification_handle.window_id());
} else {
- create_copilot_auth_window(cx, &status, &mut code_verification);
+ code_verification = Some(create_copilot_auth_window(cx, &status));
}
} else if let Some(_prompt) = prompt {
- create_copilot_auth_window(cx, &status, &mut code_verification);
+ code_verification = Some(create_copilot_auth_window(cx, &status));
}
}
Status::Authorized | Status::Unauthorized => {
if let Some(code_verification) = code_verification.as_ref() {
- code_verification.update(cx, |code_verification, cx| {
- code_verification.set_status(status, cx)
- });
+ let window_id = code_verification.window_id();
+ cx.update_window(window_id, |cx| {
+ code_verification.update(cx, |code_verification, cx| {
+ code_verification.set_status(status, cx)
+ });
- cx.platform().activate(true);
- cx.activate_window(code_verification.window_id());
+ cx.platform().activate(true);
+ cx.activate_window();
+ });
}
}
_ => {
@@ -73,8 +80,7 @@ pub fn init(cx: &mut AppContext) {
fn create_copilot_auth_window(
cx: &mut AppContext,
status: &Status,
- code_verification: &mut Option<ViewHandle<CopilotCodeVerification>>,
-) {
+) -> ViewHandle<CopilotCodeVerification> {
let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions();
let window_options = WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
@@ -88,7 +94,7 @@ fn create_copilot_auth_window(
let (_, view) = cx.add_window(window_options, |_cx| {
CopilotCodeVerification::new(status.clone())
});
- *code_verification = Some(view);
+ view
}
pub struct CopilotCodeVerification {
@@ -112,8 +118,8 @@ impl CopilotCodeVerification {
fn render_device_code(
data: &PromptUserDeviceFlow,
style: &theme::Copilot,
- cx: &mut gpui::RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let copied = cx
.read_from_clipboard()
.map(|item| item.text() == &data.user_code)
@@ -121,7 +127,7 @@ impl CopilotCodeVerification {
let device_code_style = &style.auth.prompting.device_code;
- MouseEventHandler::<Self>::new(0, cx, |state, _cx| {
+ MouseEventHandler::<Self, _>::new(0, cx, |state, _cx| {
Flex::row()
.with_children([
Label::new(data.user_code.clone(), device_code_style.text.clone())
@@ -148,7 +154,7 @@ impl CopilotCodeVerification {
})
.on_click(gpui::platform::MouseButton::Left, {
let user_code = data.user_code.clone();
- move |_, cx| {
+ move |_, _, cx| {
cx.platform()
.write_to_clipboard(ClipboardItem::new(user_code.clone()));
cx.notify();
@@ -162,8 +168,10 @@ impl CopilotCodeVerification {
connect_clicked: bool,
data: &PromptUserDeviceFlow,
style: &theme::Copilot,
- cx: &mut gpui::RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
+ enum ConnectButton {}
+
Flex::column()
.with_children([
Flex::column()
@@ -205,7 +213,7 @@ impl CopilotCodeVerification {
.contained()
.with_style(style.auth.prompting.hint.container.clone())
.boxed(),
- theme::ui::cta_button_with_click(
+ theme::ui::cta_button_with_click::<ConnectButton, _, _, _>(
if connect_clicked {
"Waiting for connection..."
} else {
@@ -216,7 +224,7 @@ impl CopilotCodeVerification {
cx,
{
let verification_uri = data.verification_uri.clone();
- move |_, cx| {
+ move |_, _, cx| {
cx.platform().open_url(&verification_uri);
cx.dispatch_action(ClickedConnect)
}
@@ -227,10 +235,9 @@ impl CopilotCodeVerification {
.align_children_center()
.boxed()
}
- fn render_enabled_modal(
- style: &theme::Copilot,
- cx: &mut gpui::RenderContext<Self>,
- ) -> ElementBox {
+ fn render_enabled_modal(style: &theme::Copilot, cx: &mut ViewContext<Self>) -> Element<Self> {
+ enum DoneButton {}
+
let enabled_style = &style.auth.authorized;
Flex::column()
.with_children([
@@ -261,15 +268,12 @@ impl CopilotCodeVerification {
.contained()
.with_style(enabled_style.hint.container)
.boxed(),
- theme::ui::cta_button_with_click(
+ theme::ui::cta_button_with_click::<DoneButton, _, _, _>(
"Done",
style.auth.content_width,
&style.auth.cta_button,
cx,
- |_, cx| {
- let window_id = cx.window_id();
- cx.remove_window(window_id)
- },
+ |_, _, cx| cx.remove_window(),
)
.boxed(),
])
@@ -278,8 +282,8 @@ impl CopilotCodeVerification {
}
fn render_unauthorized_modal(
style: &theme::Copilot,
- cx: &mut gpui::RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let unauthorized_style = &style.auth.not_authorized;
Flex::column()
@@ -322,14 +326,13 @@ impl CopilotCodeVerification {
.contained()
.with_style(unauthorized_style.warning.container)
.boxed(),
- theme::ui::cta_button_with_click(
+ theme::ui::cta_button_with_click::<CopilotCodeVerification, _, _, _>(
"Subscribe on GitHub",
style.auth.content_width,
&style.auth.cta_button,
cx,
- |_, cx| {
- let window_id = cx.window_id();
- cx.remove_window(window_id);
+ |_, _, cx| {
+ cx.remove_window();
cx.platform().open_url(COPILOT_SIGN_UP_URL)
},
)
@@ -349,18 +352,20 @@ impl View for CopilotCodeVerification {
"CopilotCodeVerification"
}
- fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext<Self>) {
+ fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
cx.notify()
}
- fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext<Self>) {
+ fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
cx.notify()
}
- fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
+ enum ConnectModal {}
+
let style = cx.global::<Settings>().theme.clone();
- modal("Connect Copilot to Zed", &style.copilot.modal, cx, |cx| {
+ modal::<ConnectModal, _, _, _>("Connect Copilot to Zed", &style.copilot.modal, cx, |cx| {
Flex::column()
.with_children([
theme::ui::icon(&style.copilot.auth.header).boxed(),
@@ -6,8 +6,7 @@ use gpui::{
elements::*,
impl_internal_actions,
platform::{CursorStyle, MouseButton},
- AppContext, Element, ElementBox, Entity, MouseState, RenderContext, Subscription, View,
- ViewContext, ViewHandle,
+ AppContext, Drawable, Element, Entity, MouseState, Subscription, View, ViewContext, ViewHandle,
};
use settings::{settings_file::SettingsFile, Settings};
use workspace::{
@@ -156,7 +155,7 @@ impl View for CopilotButton {
"CopilotButton"
}
- fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let settings = cx.global::<Settings>();
if !settings.features.copilot {
@@ -176,7 +175,7 @@ impl View for CopilotButton {
Stack::new()
.with_child(
- MouseEventHandler::<Self>::new(0, cx, {
+ MouseEventHandler::<Self, _>::new(0, cx, {
let theme = theme.clone();
let status = status.clone();
move |state, _cx| {
@@ -218,7 +217,7 @@ impl View for CopilotButton {
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, {
let status = status.clone();
- move |_, cx| match status {
+ move |_, _, cx| match status {
Status::Authorized => cx.dispatch_action(DeployCopilotMenu),
Status::Error(ref e) => cx.dispatch_action(workspace::Toast::new_action(
COPILOT_ERROR_TOAST_ID,
@@ -229,13 +228,7 @@ impl View for CopilotButton {
_ => cx.dispatch_action(DeployCopilotStartMenu),
}
})
- .with_tooltip::<Self, _>(
- 0,
- "GitHub Copilot".into(),
- None,
- theme.tooltip.clone(),
- cx,
- )
+ .with_tooltip::<Self>(0, "GitHub Copilot".into(), None, theme.tooltip.clone(), cx)
.boxed(),
)
.with_child(
@@ -11,8 +11,7 @@ use editor::{
};
use gpui::{
actions, elements::*, fonts::TextStyle, impl_internal_actions, serde_json, AnyViewHandle,
- AppContext, Entity, ModelHandle, RenderContext, Task, View, ViewContext, ViewHandle,
- WeakViewHandle,
+ AppContext, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use language::{
Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
@@ -90,7 +89,7 @@ impl View for ProjectDiagnosticsEditor {
"ProjectDiagnosticsEditor"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
if self.path_states.is_empty() {
let theme = &cx.global::<Settings>().theme.project_diagnostics;
Label::new("No problems in workspace", theme.empty_message.clone())
@@ -223,7 +222,7 @@ impl ProjectDiagnosticsEditor {
.await?;
this.update(&mut cx, |this, cx| {
this.populate_excerpts(path, language_server_id, buffer, cx)
- })
+ })?;
}
Result::<_, anyhow::Error>::Ok(())
}
@@ -531,12 +530,12 @@ impl ProjectDiagnosticsEditor {
}
impl Item for ProjectDiagnosticsEditor {
- fn tab_content(
+ fn tab_content<T: View>(
&self,
_detail: Option<usize>,
style: &theme::Tab,
cx: &AppContext,
- ) -> ElementBox {
+ ) -> Element<T> {
render_summary(
&self.summary,
&style.label.text,
@@ -726,11 +725,11 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
})
}
-pub(crate) fn render_summary(
+pub(crate) fn render_summary<T: View>(
summary: &DiagnosticSummary,
text_style: &TextStyle,
theme: &theme::ProjectDiagnostics,
-) -> ElementBox {
+) -> Element<T> {
if summary.error_count == 0 && summary.warning_count == 0 {
Label::new("No problems", text_style.clone()).boxed()
} else {
@@ -804,7 +803,7 @@ mod tests {
display_map::{BlockContext, TransformBlock},
DisplayPoint,
};
- use gpui::TestAppContext;
+ use gpui::{TestAppContext, WindowContext};
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
use project::FakeFs;
use serde_json::json;
@@ -1479,10 +1478,8 @@ mod tests {
});
}
- fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut AppContext) -> Vec<(u32, String)> {
- let mut presenter = cx.build_presenter(editor.id(), 0., Default::default());
- let mut cx = presenter.build_layout_context(Default::default(), false, cx);
- cx.render(editor, |editor, cx| {
+ fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut WindowContext) -> Vec<(u32, String)> {
+ editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
snapshot
.blocks_in_range(0..snapshot.max_point().row())
@@ -1490,7 +1487,7 @@ mod tests {
let name = match block {
TransformBlock::Custom(block) => block
.render(&mut BlockContext {
- cx,
+ view_context: cx,
anchor_x: 0.,
scroll_x: 0.,
gutter_padding: 0.,
@@ -3,8 +3,8 @@ use editor::{Editor, GoToDiagnostic};
use gpui::{
elements::*,
platform::{CursorStyle, MouseButton},
- serde_json, AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext,
- ViewHandle, WeakViewHandle,
+ serde_json, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle,
+ WeakViewHandle,
};
use language::Diagnostic;
use lsp::LanguageServerId;
@@ -85,14 +85,14 @@ impl View for DiagnosticIndicator {
"DiagnosticIndicator"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
enum Summary {}
enum Message {}
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let in_progress = !self.in_progress_checks.is_empty();
let mut element = Flex::row().with_child(
- MouseEventHandler::<Summary>::new(0, cx, |state, cx| {
+ MouseEventHandler::<Summary, _>::new(0, cx, |state, cx| {
let style = cx
.global::<Settings>()
.theme
@@ -164,8 +164,10 @@ impl View for DiagnosticIndicator {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(crate::Deploy))
- .with_tooltip::<Summary, _>(
+ .on_click(MouseButton::Left, |_, _, cx| {
+ cx.dispatch_action(crate::Deploy)
+ })
+ .with_tooltip::<Summary>(
0,
"Project Diagnostics".to_string(),
Some(Box::new(crate::Deploy)),
@@ -190,7 +192,7 @@ impl View for DiagnosticIndicator {
} else if let Some(diagnostic) = &self.current_diagnostic {
let message_style = style.diagnostic_message.clone();
element.add_child(
- MouseEventHandler::<Message>::new(1, cx, |state, _| {
+ MouseEventHandler::<Message, _>::new(1, cx, |state, _| {
Label::new(
diagnostic.message.split('\n').next().unwrap().to_string(),
message_style.style_for(state, false).text.clone(),
@@ -201,7 +203,7 @@ impl View for DiagnosticIndicator {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, cx| {
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(GoToDiagnostic)
})
.boxed(),
@@ -6,7 +6,7 @@ use gpui::{
geometry::{rect::RectF, vector::Vector2F},
platform::{CursorStyle, MouseButton},
scene::{MouseDown, MouseDrag},
- AppContext, Element, ElementBox, EventContext, RenderContext, View, WeakViewHandle,
+ Drawable, Element, View, ViewContext, WeakViewHandle, WindowContext,
};
const DEAD_ZONE: f32 = 4.;
@@ -26,7 +26,7 @@ enum State<V: View> {
region_offset: Vector2F,
region: RectF,
payload: Rc<dyn Any + 'static>,
- render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
+ render: Rc<dyn Fn(Rc<dyn Any>, &mut ViewContext<V>) -> Element<V>>,
},
Canceled,
}
@@ -111,7 +111,7 @@ impl<V: View> DragAndDrop<V> {
})
}
- pub fn drag_started(event: MouseDown, cx: &mut EventContext) {
+ pub fn drag_started(event: MouseDown, cx: &mut WindowContext) {
cx.update_global(|this: &mut Self, _| {
this.currently_dragged = Some(State::Down {
region_offset: event.position - event.region.origin(),
@@ -123,8 +123,8 @@ impl<V: View> DragAndDrop<V> {
pub fn dragging<T: Any>(
event: MouseDrag,
payload: Rc<T>,
- cx: &mut EventContext,
- render: Rc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>,
+ cx: &mut WindowContext,
+ render: Rc<impl 'static + Fn(&T, &mut ViewContext<V>) -> Element<V>>,
) {
let window_id = cx.window_id();
cx.update_global(|this: &mut Self, cx| {
@@ -178,7 +178,7 @@ impl<V: View> DragAndDrop<V> {
});
}
- pub fn render(cx: &mut RenderContext<V>) -> Option<ElementBox> {
+ pub fn render(cx: &mut ViewContext<V>) -> Option<Element<V>> {
enum DraggedElementHandler {}
cx.global::<Self>()
.currently_dragged
@@ -202,20 +202,22 @@ impl<V: View> DragAndDrop<V> {
let position = position - region_offset;
Some(
Overlay::new(
- MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
- render(payload, cx)
- })
+ MouseEventHandler::<DraggedElementHandler, V>::new(
+ 0,
+ cx,
+ |_, cx| render(payload, cx),
+ )
.with_cursor_style(CursorStyle::Arrow)
- .on_up(MouseButton::Left, |_, cx| {
- cx.defer(|cx| {
+ .on_up(MouseButton::Left, |_, _, cx| {
+ cx.window_context().defer(|cx| {
cx.update_global::<Self, _, _>(|this, cx| {
this.finish_dragging(cx)
});
});
cx.propagate_event();
})
- .on_up_out(MouseButton::Left, |_, cx| {
- cx.defer(|cx| {
+ .on_up_out(MouseButton::Left, |_, _, cx| {
+ cx.window_context().defer(|cx| {
cx.update_global::<Self, _, _>(|this, cx| {
this.finish_dragging(cx)
});
@@ -234,22 +236,22 @@ impl<V: View> DragAndDrop<V> {
}
State::Canceled => Some(
- MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, _| {
+ MouseEventHandler::<DraggedElementHandler, V>::new(0, cx, |_, _| {
Empty::new()
.constrained()
.with_width(0.)
.with_height(0.)
.boxed()
})
- .on_up(MouseButton::Left, |_, cx| {
- cx.defer(|cx| {
+ .on_up(MouseButton::Left, |_, _, cx| {
+ cx.window_context().defer(|cx| {
cx.update_global::<Self, _, _>(|this, _| {
this.currently_dragged = None;
});
});
})
- .on_up_out(MouseButton::Left, |_, cx| {
- cx.defer(|cx| {
+ .on_up_out(MouseButton::Left, |_, _, cx| {
+ cx.window_context().defer(|cx| {
cx.update_global::<Self, _, _>(|this, _| {
this.currently_dragged = None;
});
@@ -261,7 +263,7 @@ impl<V: View> DragAndDrop<V> {
})
}
- pub fn cancel_dragging<P: Any>(&mut self, cx: &mut AppContext) {
+ pub fn cancel_dragging<P: Any>(&mut self, cx: &mut WindowContext) {
if let Some(State::Dragging {
payload, window_id, ..
}) = &self.currently_dragged
@@ -274,13 +276,13 @@ impl<V: View> DragAndDrop<V> {
}
}
- fn finish_dragging(&mut self, cx: &mut AppContext) {
+ fn finish_dragging(&mut self, cx: &mut WindowContext) {
if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
self.notify_containers_for_window(window_id, cx);
}
}
- fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut AppContext) {
+ fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut WindowContext) {
self.containers.retain(|container| {
if let Some(container) = container.upgrade(cx) {
if container.window_id() == window_id {
@@ -294,35 +296,35 @@ impl<V: View> DragAndDrop<V> {
}
}
-pub trait Draggable {
- fn as_draggable<V: View, P: Any>(
+pub trait Draggable<V: View> {
+ fn as_draggable<D: View, P: Any>(
self,
payload: P,
- render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
+ render: impl 'static + Fn(&P, &mut ViewContext<D>) -> Element<D>,
) -> Self
where
Self: Sized;
}
-impl<Tag> Draggable for MouseEventHandler<Tag> {
- fn as_draggable<V: View, P: Any>(
+impl<Tag, V: View> Draggable<V> for MouseEventHandler<Tag, V> {
+ fn as_draggable<D: View, P: Any>(
self,
payload: P,
- render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
+ render: impl 'static + Fn(&P, &mut ViewContext<D>) -> Element<D>,
) -> Self
where
Self: Sized,
{
let payload = Rc::new(payload);
let render = Rc::new(render);
- self.on_down(MouseButton::Left, move |e, cx| {
+ self.on_down(MouseButton::Left, move |e, _, cx| {
cx.propagate_event();
- DragAndDrop::<V>::drag_started(e, cx);
+ DragAndDrop::<D>::drag_started(e, cx);
})
- .on_drag(MouseButton::Left, move |e, cx| {
+ .on_drag(MouseButton::Left, move |e, _, cx| {
let payload = payload.clone();
let render = render.clone();
- DragAndDrop::<V>::dragging(e, payload, cx, render)
+ DragAndDrop::<D>::dragging(e, payload, cx, render)
})
}
}
@@ -2,9 +2,9 @@ use super::{
wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
TextHighlights,
};
-use crate::{Anchor, ExcerptId, ExcerptRange, ToPoint as _};
+use crate::{Anchor, Editor, ExcerptId, ExcerptRange, ToPoint as _};
use collections::{Bound, HashMap, HashSet};
-use gpui::{fonts::HighlightStyle, ElementBox, RenderContext};
+use gpui::{fonts::HighlightStyle, Element, ViewContext};
use language::{BufferSnapshot, Chunk, Patch, Point};
use parking_lot::Mutex;
use std::{
@@ -50,7 +50,7 @@ struct BlockRow(u32);
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
struct WrapRow(u32);
-pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> ElementBox>;
+pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> Element<Editor>>;
pub struct Block {
id: BlockId,
@@ -69,7 +69,7 @@ where
pub position: P,
pub height: u8,
pub style: BlockStyle,
- pub render: Arc<dyn Fn(&mut BlockContext) -> ElementBox>,
+ pub render: Arc<dyn Fn(&mut BlockContext) -> Element<Editor>>,
pub disposition: BlockDisposition,
}
@@ -80,8 +80,8 @@ pub enum BlockStyle {
Sticky,
}
-pub struct BlockContext<'a, 'b> {
- pub cx: &'b mut RenderContext<'a, crate::Editor>,
+pub struct BlockContext<'a, 'b, 'c, 'd> {
+ pub view_context: &'d mut ViewContext<'a, 'b, 'c, Editor>,
pub anchor_x: f32,
pub scroll_x: f32,
pub gutter_width: f32,
@@ -932,22 +932,22 @@ impl BlockDisposition {
}
}
-impl<'a, 'b> Deref for BlockContext<'a, 'b> {
- type Target = RenderContext<'a, crate::Editor>;
+impl<'a, 'b, 'c, 'd> Deref for BlockContext<'a, 'b, 'c, 'd> {
+ type Target = ViewContext<'a, 'b, 'c, Editor>;
fn deref(&self) -> &Self::Target {
- self.cx
+ self.view_context
}
}
-impl<'a, 'b> DerefMut for BlockContext<'a, 'b> {
+impl DerefMut for BlockContext<'_, '_, '_, '_> {
fn deref_mut(&mut self) -> &mut Self::Target {
- self.cx
+ self.view_context
}
}
impl Block {
- pub fn render(&self, cx: &mut BlockContext) -> ElementBox {
+ pub fn render(&self, cx: &mut BlockContext) -> Element<Editor> {
self.render.lock()(cx)
}
@@ -994,7 +994,7 @@ mod tests {
use crate::display_map::suggestion_map::SuggestionMap;
use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
use crate::multi_buffer::MultiBuffer;
- use gpui::{elements::Empty, Element};
+ use gpui::{elements::Empty, Drawable};
use rand::prelude::*;
use settings::Settings;
use std::env;
@@ -41,8 +41,8 @@ use gpui::{
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton},
serde_json::{self, json},
- AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity,
- ModelHandle, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+ AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Drawable, Element, Entity,
+ ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HideHover, HoverState};
@@ -724,8 +724,8 @@ impl ContextMenu {
&self,
cursor_position: DisplayPoint,
style: EditorStyle,
- cx: &mut RenderContext<Editor>,
- ) -> (DisplayPoint, ElementBox) {
+ cx: &mut ViewContext<Editor>,
+ ) -> (DisplayPoint, Element<Editor>) {
match self {
ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)),
ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
@@ -777,7 +777,7 @@ impl CompletionsMenu {
!self.matches.is_empty()
}
- fn render(&self, style: EditorStyle, cx: &mut RenderContext<Editor>) -> ElementBox {
+ fn render(&self, style: EditorStyle, cx: &mut ViewContext<Editor>) -> Element<Editor> {
enum CompletionTag {}
let completions = self.completions.clone();
@@ -794,7 +794,7 @@ impl CompletionsMenu {
let completion = &completions[mat.candidate_id];
let item_ix = start_ix + ix;
items.push(
- MouseEventHandler::<CompletionTag>::new(
+ MouseEventHandler::<CompletionTag, _>::new(
mat.candidate_id,
cx,
|state, _| {
@@ -823,7 +823,7 @@ impl CompletionsMenu {
},
)
.with_cursor_style(CursorStyle::PointingHand)
- .on_down(MouseButton::Left, move |_, cx| {
+ .on_down(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ConfirmCompletion {
item_ix: Some(item_ix),
});
@@ -953,8 +953,8 @@ impl CodeActionsMenu {
&self,
mut cursor_position: DisplayPoint,
style: EditorStyle,
- cx: &mut RenderContext<Editor>,
- ) -> (DisplayPoint, ElementBox) {
+ cx: &mut ViewContext<Editor>,
+ ) -> (DisplayPoint, Element<Editor>) {
enum ActionTag {}
let container_style = style.autocomplete.container;
@@ -969,7 +969,7 @@ impl CodeActionsMenu {
for (ix, action) in actions[range].iter().enumerate() {
let item_ix = start_ix + ix;
items.push(
- MouseEventHandler::<ActionTag>::new(item_ix, cx, |state, _| {
+ MouseEventHandler::<ActionTag, _>::new(item_ix, cx, |state, _| {
let item_style = if item_ix == selected_item {
style.autocomplete.selected_item
} else if state.hovered() {
@@ -985,7 +985,7 @@ impl CodeActionsMenu {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_down(MouseButton::Left, move |_, cx| {
+ .on_down(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ConfirmCodeAction {
item_ix: Some(item_ix),
});
@@ -1349,7 +1349,7 @@ impl Editor {
self.buffer().read(cx).title(cx)
}
- pub fn snapshot(&mut self, cx: &mut AppContext) -> EditorSnapshot {
+ pub fn snapshot(&mut self, cx: &mut WindowContext) -> EditorSnapshot {
EditorSnapshot {
mode: self.mode,
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
@@ -2518,7 +2518,7 @@ impl Editor {
this.update_visible_copilot_suggestion(cx);
}
}
- });
+ })?;
Ok::<_, anyhow::Error>(())
}
@@ -2655,11 +2655,13 @@ impl Editor {
prev_task.await;
task = this
.upgrade(&cx)
- .and_then(|this| this.update(&mut cx, |this, _| this.code_actions_task.take()));
+ .ok_or_else(|| anyhow!("editor dropped"))?
+ .update(&mut cx, |this, _| this.code_actions_task.take())?;
}
- if let Some(this) = this.upgrade(&cx) {
- this.update(&mut cx, |this, cx| {
+ this.upgrade(&cx)
+ .ok_or_else(|| anyhow!("editor dropped"))?
+ .update(&mut cx, |this, cx| {
if this.focused {
if let Some((buffer, actions)) = this.available_code_actions.clone() {
this.show_context_menu(
@@ -2674,8 +2676,8 @@ impl Editor {
);
}
}
- })
- }
+ })?;
+
Ok::<_, anyhow::Error>(())
})
.detach_and_log_err(cx);
@@ -2787,7 +2789,7 @@ impl Editor {
cx,
);
});
- });
+ })?;
Ok(())
}
@@ -2818,6 +2820,7 @@ impl Editor {
});
cx.notify();
})
+ .log_err();
}
}));
None
@@ -2907,7 +2910,8 @@ impl Editor {
cx,
);
cx.notify();
- });
+ })
+ .log_err();
}
}));
None
@@ -2953,20 +2957,21 @@ impl Editor {
.flatten()
.collect_vec();
- this.upgrade(&cx)?.update(&mut cx, |this, cx| {
- if !completions.is_empty() {
- this.copilot_state.cycled = false;
- this.copilot_state.pending_cycling_refresh = Task::ready(None);
- this.copilot_state.completions.clear();
- this.copilot_state.active_completion_index = 0;
- this.copilot_state.excerpt_id = Some(cursor.excerpt_id);
- for completion in completions {
- this.copilot_state.push_completion(completion);
+ this.upgrade(&cx)?
+ .update(&mut cx, |this, cx| {
+ if !completions.is_empty() {
+ this.copilot_state.cycled = false;
+ this.copilot_state.pending_cycling_refresh = Task::ready(None);
+ this.copilot_state.completions.clear();
+ this.copilot_state.active_completion_index = 0;
+ this.copilot_state.excerpt_id = Some(cursor.excerpt_id);
+ for completion in completions {
+ this.copilot_state.push_completion(completion);
+ }
+ this.update_visible_copilot_suggestion(cx);
}
- this.update_visible_copilot_suggestion(cx);
- }
- });
-
+ })
+ .log_err()?;
Some(())
});
@@ -2997,14 +3002,16 @@ impl Editor {
})
.await;
- this.upgrade(&cx)?.update(&mut cx, |this, cx| {
- this.copilot_state.cycled = true;
- for completion in completions.log_err().into_iter().flatten() {
- this.copilot_state.push_completion(completion);
- }
- this.copilot_state.cycle_completions(direction);
- this.update_visible_copilot_suggestion(cx);
- });
+ this.upgrade(&cx)?
+ .update(&mut cx, |this, cx| {
+ this.copilot_state.cycled = true;
+ for completion in completions.log_err().into_iter().flatten() {
+ this.copilot_state.push_completion(completion);
+ }
+ this.copilot_state.cycle_completions(direction);
+ this.update_visible_copilot_suggestion(cx);
+ })
+ .log_err()?;
Some(())
});
@@ -3123,19 +3130,19 @@ impl Editor {
&self,
style: &EditorStyle,
active: bool,
- cx: &mut RenderContext<Self>,
- ) -> Option<ElementBox> {
+ cx: &mut ViewContext<Self>,
+ ) -> Option<Element<Self>> {
if self.available_code_actions.is_some() {
enum CodeActions {}
Some(
- MouseEventHandler::<CodeActions>::new(0, cx, |state, _| {
+ MouseEventHandler::<CodeActions, _>::new(0, cx, |state, _| {
Svg::new("icons/bolt_8.svg")
.with_color(style.code_actions.indicator.style_for(state, active).color)
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(3.))
- .on_down(MouseButton::Left, |_, cx| {
+ .on_down(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(ToggleCodeActions {
deployed_from_indicator: true,
});
@@ -3154,8 +3161,8 @@ impl Editor {
gutter_hovered: bool,
line_height: f32,
gutter_margin: f32,
- cx: &mut RenderContext<Self>,
- ) -> Vec<Option<ElementBox>> {
+ cx: &mut ViewContext<Self>,
+ ) -> Vec<Option<Element<Self>>> {
enum FoldIndicators {}
let style = style.folds.clone();
@@ -3167,10 +3174,10 @@ impl Editor {
fold_data
.map(|(fold_status, buffer_row, active)| {
(active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
- MouseEventHandler::<FoldIndicators>::new(
+ MouseEventHandler::<FoldIndicators, _>::new(
ix as usize,
cx,
- |mouse_state, _| -> ElementBox {
+ |mouse_state, _| -> Element<Editor> {
Svg::new(match fold_status {
FoldStatus::Folded => style.folded_icon.clone(),
FoldStatus::Foldable => style.foldable_icon.clone(),
@@ -3197,7 +3204,7 @@ impl Editor {
.with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(3.))
.on_click(MouseButton::Left, {
- move |_, cx| {
+ move |_, _, cx| {
cx.dispatch_any_action(match fold_status {
FoldStatus::Folded => Box::new(UnfoldAt { buffer_row }),
FoldStatus::Foldable => Box::new(FoldAt { buffer_row }),
@@ -3222,8 +3229,8 @@ impl Editor {
&self,
cursor_position: DisplayPoint,
style: EditorStyle,
- cx: &mut RenderContext<Editor>,
- ) -> Option<(DisplayPoint, ElementBox)> {
+ cx: &mut ViewContext<Editor>,
+ ) -> Option<(DisplayPoint, Element<Editor>)> {
self.context_menu
.as_ref()
.map(|menu| menu.render(cursor_position, style, cx))
@@ -4106,7 +4113,7 @@ impl Editor {
pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
self.transact(cx, |this, cx| {
- if let Some(item) = cx.as_mut().read_from_clipboard() {
+ if let Some(item) = cx.read_from_clipboard() {
let mut clipboard_text = Cow::Borrowed(item.text());
if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
let old_selections = this.selections.all::<usize>(cx);
@@ -5610,7 +5617,7 @@ impl Editor {
let definitions = definitions.await?;
workspace.update(&mut cx, |workspace, cx| {
Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx);
- });
+ })?;
Ok::<(), anyhow::Error>(())
})
@@ -5711,7 +5718,7 @@ impl Editor {
Self::open_locations_in_multibuffer(
workspace, locations, replica_id, title, cx,
);
- });
+ })?;
Ok(())
},
@@ -5899,7 +5906,7 @@ impl Editor {
editor: rename_editor,
block_id,
});
- });
+ })?;
}
Ok(())
@@ -5945,7 +5952,7 @@ impl Editor {
editor.update(&mut cx, |editor, cx| {
editor.refresh_document_highlights(cx);
- });
+ })?;
Ok(())
}))
}
@@ -5988,7 +5995,7 @@ impl Editor {
self.pending_rename.as_ref()
}
- fn format(&mut self, _: &Format, cx: &mut ViewContext<'_, Self>) -> Option<Task<Result<()>>> {
+ fn format(&mut self, _: &Format, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
let project = match &self.project {
Some(project) => project.clone(),
None => return None,
@@ -6001,7 +6008,7 @@ impl Editor {
&mut self,
project: ModelHandle<Project>,
trigger: FormatTrigger,
- cx: &mut ViewContext<'_, Self>,
+ cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
let buffer = self.buffer().clone();
let buffers = buffer.read(cx).all_buffers();
@@ -6747,9 +6754,16 @@ impl Editor {
let position = action.position;
let anchor = action.anchor;
cx.spawn_weak(|_, mut cx| async move {
- let editor = editor.await.log_err()?.downcast::<Editor>()?;
+ let editor = editor
+ .await?
+ .downcast::<Editor>()
+ .ok_or_else(|| anyhow!("opened item was not an editor"))?;
editor.update(&mut cx, |editor, cx| {
- let buffer = editor.buffer().read(cx).as_singleton()?;
+ let buffer = editor
+ .buffer()
+ .read(cx)
+ .as_singleton()
+ .ok_or_else(|| anyhow!("cannot jump in a multi-buffer"))?;
let buffer = buffer.read(cx);
let cursor = if buffer.can_resolve(&anchor) {
language::ToPoint::to_point(&anchor, buffer)
@@ -6763,11 +6777,11 @@ impl Editor {
});
editor.nav_history = nav_history;
- Some(())
- })?;
- Some(())
+ anyhow::Ok(())
+ })??;
+ anyhow::Ok(())
})
- .detach()
+ .detach_and_log_err(cx);
}
fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
@@ -6993,7 +7007,7 @@ impl Entity for Editor {
}
impl View for Editor {
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let style = self.style(cx);
let font_changed = self.display_map.update(cx, |map, cx| {
map.set_fold_ellipses_color(style.folds.ellipses.text_color);
@@ -7001,19 +7015,14 @@ impl View for Editor {
});
if font_changed {
- let handle = self.handle.clone();
- cx.defer(move |cx| {
- if let Some(editor) = handle.upgrade(cx) {
- editor.update(cx, |editor, cx| {
- hide_hover(editor, &HideHover, cx);
- hide_link_definition(editor, cx);
- })
- }
+ cx.defer(move |editor, cx: &mut ViewContext<Editor>| {
+ hide_hover(editor, &HideHover, cx);
+ hide_link_definition(editor, cx);
});
}
Stack::new()
- .with_child(EditorElement::new(self.handle.clone(), style.clone()).boxed())
+ .with_child(EditorElement::new(style.clone()).boxed())
.with_child(ChildView::new(&self.mouse_context_menu, cx).boxed())
.boxed()
}
@@ -9,7 +9,7 @@ use gpui::{
executor::Deterministic,
geometry::{rect::RectF, vector::vec2f},
platform::{WindowBounds, WindowOptions},
- serde_json,
+ serde_json, TestAppContext,
};
use indoc::indoc;
use language::{BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point};
@@ -23,13 +23,13 @@ use util::{
test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
};
use workspace::{
- item::{FollowableItem, ItemHandle},
+ item::{FollowableItem, Item, ItemHandle},
NavigationEntry, Pane, ViewId,
};
#[gpui::test]
-fn test_edit_events(cx: &mut AppContext) {
- cx.set_global(Settings::test(cx));
+fn test_edit_events(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = cx.add_model(|cx| {
let mut buffer = language::Buffer::new(0, "123456", cx);
buffer.set_group_interval(Duration::from_secs(1));
@@ -37,7 +37,7 @@ fn test_edit_events(cx: &mut AppContext) {
});
let events = Rc::new(RefCell::new(Vec::new()));
- let (_, editor1) = cx.add_window(Default::default(), {
+ let (_, editor1) = cx.add_window({
let events = events.clone();
|cx| {
cx.subscribe(&cx.handle(), move |_, _, event, _| {
@@ -52,7 +52,7 @@ fn test_edit_events(cx: &mut AppContext) {
Editor::for_buffer(buffer.clone(), None, cx)
}
});
- let (_, editor2) = cx.add_window(Default::default(), {
+ let (_, editor2) = cx.add_window({
let events = events.clone();
|cx| {
cx.subscribe(&cx.handle(), move |_, _, event, _| {
@@ -155,13 +155,13 @@ fn test_edit_events(cx: &mut AppContext) {
}
#[gpui::test]
-fn test_undo_redo_with_selection_restoration(cx: &mut AppContext) {
- cx.set_global(Settings::test(cx));
+fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
let mut now = Instant::now();
let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
- let group_interval = buffer.read(cx).transaction_group_interval();
+ let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+ let (_, editor) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
editor.update(cx, |editor, cx| {
editor.start_transaction_at(now, cx);
@@ -225,8 +225,8 @@ fn test_undo_redo_with_selection_restoration(cx: &mut AppContext) {
}
#[gpui::test]
-fn test_ime_composition(cx: &mut AppContext) {
- cx.set_global(Settings::test(cx));
+fn test_ime_composition(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = cx.add_model(|cx| {
let mut buffer = language::Buffer::new(0, "abcde", cx);
// Ensure automatic grouping doesn't occur.
@@ -235,7 +235,7 @@ fn test_ime_composition(cx: &mut AppContext) {
});
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- cx.add_window(Default::default(), |cx| {
+ cx.add_window(|cx| {
let mut editor = build_editor(buffer.clone(), cx);
// Start a new IME composition.
@@ -327,11 +327,13 @@ fn test_ime_composition(cx: &mut AppContext) {
}
#[gpui::test]
-fn test_selection_with_mouse(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
+fn test_selection_with_mouse(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
- let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
- let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+ let (_, editor) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
+ build_editor(buffer, cx)
+ });
editor.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
});
@@ -392,10 +394,12 @@ fn test_selection_with_mouse(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_canceling_pending_selection(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_canceling_pending_selection(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
@@ -424,7 +428,7 @@ fn test_canceling_pending_selection(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_clone(cx: &mut gpui::AppContext) {
+fn test_clone(cx: &mut TestAppContext) {
let (text, selection_ranges) = marked_text_ranges(
indoc! {"
one
@@ -435,10 +439,12 @@ fn test_clone(cx: &mut gpui::AppContext) {
"},
true,
);
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple(&text, cx);
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
- let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+ let (_, editor) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&text, cx);
+ build_editor(buffer, cx)
+ });
editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
@@ -470,8 +476,8 @@ fn test_clone(cx: &mut gpui::AppContext) {
snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
);
assert_set_eq!(
- cloned_editor.read(cx).selections.ranges::<Point>(cx),
- editor.read(cx).selections.ranges(cx)
+ cloned_editor.read_with(cx, |editor, cx| editor.selections.ranges::<Point>(cx)),
+ editor.read_with(cx, |editor, cx| editor.selections.ranges(cx))
);
assert_set_eq!(
cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)),
@@ -480,19 +486,19 @@ fn test_clone(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_navigation_history(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
+fn test_navigation_history(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
cx.set_global(DragAndDrop::<Workspace>::default());
use workspace::item::Item;
- let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(0, None, || &[], cx));
- let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
+ let (_, pane) = cx.add_window(|cx| Pane::new(0, None, || &[], cx));
cx.add_view(&pane, |cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
let mut editor = build_editor(buffer.clone(), cx);
let handle = cx.handle();
editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
- fn pop_history(editor: &mut Editor, cx: &mut AppContext) -> Option<NavigationEntry> {
+ fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
editor.nav_history.as_mut().unwrap().pop_backward(cx)
}
@@ -590,10 +596,12 @@ fn test_navigation_history(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_cancel(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_cancel(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
@@ -630,30 +638,32 @@ fn test_cancel(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_fold_action(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple(
- &"
- impl Foo {
- // Hello!
+fn test_fold_action(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(
+ &"
+ impl Foo {
+ // Hello!
- fn a() {
- 1
- }
+ fn a() {
+ 1
+ }
- fn b() {
- 2
- }
+ fn b() {
+ 2
+ }
- fn c() {
- 3
+ fn c() {
+ 3
+ }
}
- }
- "
- .unindent(),
- cx,
- );
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+ "
+ .unindent(),
+ cx,
+ );
+ build_editor(buffer.clone(), cx)
+ });
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@@ -712,15 +722,15 @@ fn test_fold_action(cx: &mut gpui::AppContext) {
);
view.unfold_lines(&UnfoldLines, cx);
- assert_eq!(view.display_text(cx), buffer.read(cx).read(cx).text());
+ assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
});
}
#[gpui::test]
-fn test_move_cursor(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+fn test_move_cursor(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
+ let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
buffer.update(cx, |buffer, cx| {
buffer.edit(
@@ -732,7 +742,6 @@ fn test_move_cursor(cx: &mut gpui::AppContext) {
cx,
);
});
-
view.update(cx, |view, cx| {
assert_eq!(
view.selections.display_ranges(cx),
@@ -793,10 +802,12 @@ fn test_move_cursor(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_move_cursor_multibyte(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
+ build_editor(buffer.clone(), cx)
+ });
assert_eq!('ⓐ'.len_utf8(), 3);
assert_eq!('α'.len_utf8(), 2);
@@ -895,10 +906,12 @@ fn test_move_cursor_multibyte(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_move_cursor_different_line_lengths(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
+ build_editor(buffer.clone(), cx)
+ });
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
@@ -942,10 +955,12 @@ fn test_move_cursor_different_line_lengths(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_beginning_end_of_line(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("abc\n def", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_beginning_end_of_line(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\n def", cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -1102,10 +1117,12 @@ fn test_beginning_end_of_line(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_prev_next_word_boundary(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -1151,10 +1168,12 @@ fn test_prev_next_word_boundary(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.set_wrap_width(Some(140.), cx);
@@ -1330,10 +1349,12 @@ async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-fn test_delete_to_word_boundary(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("one two three four", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("one two three four", cx);
+ build_editor(buffer.clone(), cx)
+ });
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@@ -1345,10 +1366,9 @@ fn test_delete_to_word_boundary(cx: &mut gpui::AppContext) {
])
});
view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
+ assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
});
- assert_eq!(buffer.read(cx).read(cx).text(), "e two te four");
-
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -1359,16 +1379,17 @@ fn test_delete_to_word_boundary(cx: &mut gpui::AppContext) {
])
});
view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
+ assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
});
-
- assert_eq!(buffer.read(cx).read(cx).text(), "e t te our");
}
#[gpui::test]
-fn test_newline(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+fn test_newline(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
+ build_editor(buffer.clone(), cx)
+ });
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@@ -1385,24 +1406,23 @@ fn test_newline(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_newline_with_old_selections(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple(
- "
- a
- b(
- X
- )
- c(
- X
- )
- "
- .unindent()
- .as_str(),
- cx,
- );
-
- let (_, editor) = cx.add_window(Default::default(), |cx| {
+fn test_newline_with_old_selections(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, editor) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(
+ "
+ a
+ b(
+ X
+ )
+ c(
+ X
+ )
+ "
+ .unindent()
+ .as_str(),
+ cx,
+ );
let mut editor = build_editor(buffer.clone(), cx);
editor.change_selections(None, cx, |s| {
s.select_ranges([
@@ -1413,28 +1433,27 @@ fn test_newline_with_old_selections(cx: &mut gpui::AppContext) {
editor
});
- // Edit the buffer directly, deleting ranges surrounding the editor's selections
- buffer.update(cx, |buffer, cx| {
- buffer.edit(
- [
- (Point::new(1, 2)..Point::new(3, 0), ""),
- (Point::new(4, 2)..Point::new(6, 0), ""),
- ],
- None,
- cx,
- );
- assert_eq!(
- buffer.read(cx).text(),
- "
- a
- b()
- c()
- "
- .unindent()
- );
- });
-
editor.update(cx, |editor, cx| {
+ // Edit the buffer directly, deleting ranges surrounding the editor's selections
+ editor.buffer.update(cx, |buffer, cx| {
+ buffer.edit(
+ [
+ (Point::new(1, 2)..Point::new(3, 0), ""),
+ (Point::new(4, 2)..Point::new(6, 0), ""),
+ ],
+ None,
+ cx,
+ );
+ assert_eq!(
+ buffer.read(cx).text(),
+ "
+ a
+ b()
+ c()
+ "
+ .unindent()
+ );
+ });
assert_eq!(
editor.selections.ranges(cx),
&[
@@ -1566,22 +1585,21 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-fn test_insert_with_old_selections(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
- let (_, editor) = cx.add_window(Default::default(), |cx| {
+fn test_insert_with_old_selections(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, editor) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
let mut editor = build_editor(buffer.clone(), cx);
editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
editor
});
- // Edit the buffer directly, deleting ranges surrounding the editor's selections
- buffer.update(cx, |buffer, cx| {
- buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
- assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
- });
-
editor.update(cx, |editor, cx| {
+ // Edit the buffer directly, deleting ranges surrounding the editor's selections
+ editor.buffer.update(cx, |buffer, cx| {
+ buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
+ assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
+ });
assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
editor.insert("Z", cx);
@@ -1885,24 +1903,26 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-fn test_indent_outdent_with_excerpts(cx: &mut gpui::AppContext) {
- cx.set_global(
- Settings::test(cx)
- .with_language_defaults(
- "TOML",
- EditorSettings {
- tab_size: Some(2.try_into().unwrap()),
- ..Default::default()
- },
- )
- .with_language_defaults(
- "Rust",
- EditorSettings {
- tab_size: Some(4.try_into().unwrap()),
- ..Default::default()
- },
- ),
- );
+fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
+ cx.update(|cx| {
+ cx.set_global(
+ Settings::test(cx)
+ .with_language_defaults(
+ "TOML",
+ EditorSettings {
+ tab_size: Some(2.try_into().unwrap()),
+ ..Default::default()
+ },
+ )
+ .with_language_defaults(
+ "Rust",
+ EditorSettings {
+ tab_size: Some(4.try_into().unwrap()),
+ ..Default::default()
+ },
+ ),
+ );
+ });
let toml_language = Arc::new(Language::new(
LanguageConfig {
name: "TOML".into(),
@@ -1944,7 +1964,7 @@ fn test_indent_outdent_with_excerpts(cx: &mut gpui::AppContext) {
multibuffer
});
- cx.add_window(Default::default(), |cx| {
+ cx.add_window(|cx| {
let mut editor = build_editor(multibuffer, cx);
assert_eq!(
@@ -2071,10 +2091,12 @@ async fn test_delete(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-fn test_delete_line(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_delete_line(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -2094,9 +2116,11 @@ fn test_delete_line(cx: &mut gpui::AppContext) {
);
});
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
@@ -2111,10 +2135,12 @@ fn test_delete_line(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_duplicate_line(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_duplicate_line(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -2137,8 +2163,10 @@ fn test_duplicate_line(cx: &mut gpui::AppContext) {
);
});
- let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -2159,10 +2187,12 @@ fn test_duplicate_line(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_move_line_up_down(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_move_line_up_down(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.fold_ranges(
vec![
@@ -2255,12 +2285,14 @@ fn test_move_line_up_down(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_move_line_up_down_with_blocks(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
- let snapshot = buffer.read(cx).snapshot(cx);
- let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, editor) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
+ build_editor(buffer, cx)
+ });
editor.update(cx, |editor, cx| {
+ let snapshot = editor.buffer.read(cx).snapshot(cx);
editor.insert_blocks(
[BlockProperties {
style: BlockStyle::Fixed,
@@ -2279,11 +2311,11 @@ fn test_move_line_up_down_with_blocks(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_transpose(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
+fn test_transpose(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
_ = cx
- .add_window(Default::default(), |cx| {
+ .add_window(|cx| {
let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
@@ -2304,7 +2336,7 @@ fn test_transpose(cx: &mut gpui::AppContext) {
.1;
_ = cx
- .add_window(Default::default(), |cx| {
+ .add_window(|cx| {
let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
@@ -2330,7 +2362,7 @@ fn test_transpose(cx: &mut gpui::AppContext) {
.1;
_ = cx
- .add_window(Default::default(), |cx| {
+ .add_window(|cx| {
let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
@@ -2359,7 +2391,7 @@ fn test_transpose(cx: &mut gpui::AppContext) {
.1;
_ = cx
- .add_window(Default::default(), |cx| {
+ .add_window(|cx| {
let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
@@ -2573,10 +2605,12 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-fn test_select_all(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_select_all(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.select_all(&SelectAll, cx);
assert_eq!(
@@ -2587,10 +2621,12 @@ fn test_select_all(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_select_line(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_select_line(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -2631,10 +2667,12 @@ fn test_select_line(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_split_selection_into_lines(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_split_selection_into_lines(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.fold_ranges(
vec![
@@ -2699,10 +2737,12 @@ fn test_split_selection_into_lines(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_add_selection_above_below(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_add_selection_above_below(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, view) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
+ build_editor(buffer, cx)
+ });
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@@ -4022,7 +4062,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(cx)));
- let save = cx.update(|cx| editor.save(project.clone(), cx));
+ let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
fake_server
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!(
@@ -4057,7 +4097,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
futures::future::pending::<()>().await;
unreachable!()
});
- let save = cx.update(|cx| editor.save(project.clone(), cx));
+ let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
cx.foreground().start_waiting();
save.await.unwrap();
@@ -4080,7 +4120,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
})
});
- let save = cx.update(|cx| editor.save(project.clone(), cx));
+ let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
fake_server
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!(
@@ -4136,7 +4176,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(cx)));
- let save = cx.update(|cx| editor.save(project.clone(), cx));
+ let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
fake_server
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
assert_eq!(
@@ -4173,7 +4213,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
unreachable!()
},
);
- let save = cx.update(|cx| editor.save(project.clone(), cx));
+ let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
cx.foreground().start_waiting();
save.await.unwrap();
@@ -4196,7 +4236,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
})
});
- let save = cx.update(|cx| editor.save(project.clone(), cx));
+ let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
fake_server
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
assert_eq!(
@@ -4977,8 +5017,8 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-fn test_editing_disjoint_excerpts(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
+fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
let multibuffer = cx.add_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
@@ -4996,12 +5036,11 @@ fn test_editing_disjoint_excerpts(cx: &mut gpui::AppContext) {
],
cx,
);
+ assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
multibuffer
});
- assert_eq!(multibuffer.read(cx).read(cx).text(), "aaaa\nbbbb");
-
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
+ let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
view.update(cx, |view, cx| {
assert_eq!(view.text(cx), "aaaa\nbbbb");
view.change_selections(None, cx, |s| {
@@ -5024,8 +5063,8 @@ fn test_editing_disjoint_excerpts(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_editing_overlapping_excerpts(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
+fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
let markers = vec![('[', ']').into(), ('(', ')').into()];
let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
indoc! {"
@@ -5049,7 +5088,7 @@ fn test_editing_overlapping_excerpts(cx: &mut gpui::AppContext) {
multibuffer
});
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
+ let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
view.update(cx, |view, cx| {
let (expected_text, selection_ranges) = marked_text_ranges(
indoc! {"
@@ -5097,8 +5136,8 @@ fn test_editing_overlapping_excerpts(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_refresh_selections(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
+fn test_refresh_selections(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
let mut excerpt1_id = None;
let multibuffer = cx.add_model(|cx| {
@@ -5120,13 +5159,11 @@ fn test_refresh_selections(cx: &mut gpui::AppContext) {
)
.into_iter()
.next();
+ assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
multibuffer
});
- assert_eq!(
- multibuffer.read(cx).read(cx).text(),
- "aaaa\nbbbb\nbbbb\ncccc"
- );
- let (_, editor) = cx.add_window(Default::default(), |cx| {
+
+ let (_, editor) = cx.add_window(|cx| {
let mut editor = build_editor(multibuffer.clone(), cx);
let snapshot = editor.snapshot(cx);
editor.change_selections(None, cx, |s| {
@@ -5183,8 +5220,8 @@ fn test_refresh_selections(cx: &mut gpui::AppContext) {
}
#[gpui::test]
-fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::AppContext) {
- cx.set_global(Settings::test(cx));
+fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
let mut excerpt1_id = None;
let multibuffer = cx.add_model(|cx| {
@@ -5206,13 +5243,11 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::AppContext)
)
.into_iter()
.next();
+ assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
multibuffer
});
- assert_eq!(
- multibuffer.read(cx).read(cx).text(),
- "aaaa\nbbbb\nbbbb\ncccc"
- );
- let (_, editor) = cx.add_window(Default::default(), |cx| {
+
+ let (_, editor) = cx.add_window(|cx| {
let mut editor = build_editor(multibuffer.clone(), cx);
let snapshot = editor.snapshot(cx);
editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
@@ -5316,17 +5351,18 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-fn test_highlighted_ranges(cx: &mut gpui::AppContext) {
- let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
-
- cx.set_global(Settings::test(cx));
- let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+fn test_highlighted_ranges(cx: &mut TestAppContext) {
+ cx.update(|cx| cx.set_global(Settings::test(cx)));
+ let (_, editor) = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
+ build_editor(buffer.clone(), cx)
+ });
editor.update(cx, |editor, cx| {
struct Type1;
struct Type2;
- let buffer = buffer.read(cx).snapshot(cx);
+ let buffer = editor.buffer.read(cx).snapshot(cx);
let anchor_range =
|range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
@@ -31,8 +31,8 @@ use gpui::{
json::{self, ToJson},
platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
text_layout::{self, Line, RunStyle, TextLayoutCache},
- AppContext, Axis, Border, CursorRegion, Element, ElementBox, EventContext, LayoutContext,
- MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WeakViewHandle,
+ Axis, Border, CursorRegion, Drawable, Element, EventContext, MouseRegion, Quad, SceneBuilder,
+ SizeConstraint, ViewContext, WindowContext,
};
use itertools::Itertools;
use json::json;
@@ -44,7 +44,7 @@ use std::{
cmp::{self, Ordering},
fmt::Write,
iter,
- ops::{DerefMut, Range},
+ ops::Range,
sync::Arc,
};
use workspace::item::Item;
@@ -86,144 +86,128 @@ impl SelectionLayout {
#[derive(Clone)]
pub struct EditorElement {
- view: WeakViewHandle<Editor>,
style: Arc<EditorStyle>,
}
impl EditorElement {
- pub fn new(view: WeakViewHandle<Editor>, style: EditorStyle) -> Self {
+ pub fn new(style: EditorStyle) -> Self {
Self {
- view,
style: Arc::new(style),
}
}
- fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
- self.view.upgrade(cx).unwrap().read(cx)
- }
-
- fn update_view<F, T>(&self, cx: &mut AppContext, f: F) -> T
- where
- F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
- {
- self.view.upgrade(cx).unwrap().update(cx, f)
- }
-
- fn snapshot(&self, cx: &mut AppContext) -> EditorSnapshot {
- self.update_view(cx, |view, cx| view.snapshot(cx))
- }
-
fn attach_mouse_handlers(
- view: &WeakViewHandle<Editor>,
+ scene: &mut SceneBuilder,
position_map: &Arc<PositionMap>,
has_popovers: bool,
visible_bounds: RectF,
text_bounds: RectF,
gutter_bounds: RectF,
bounds: RectF,
- cx: &mut PaintContext,
+ cx: &mut ViewContext<Editor>,
) {
enum EditorElementMouseHandlers {}
- cx.scene.push_mouse_region(
- MouseRegion::new::<EditorElementMouseHandlers>(view.id(), view.id(), visible_bounds)
- .on_down(MouseButton::Left, {
- let position_map = position_map.clone();
- move |e, cx| {
- if !Self::mouse_down(
- e.platform_event,
- position_map.as_ref(),
- text_bounds,
- gutter_bounds,
- cx,
- ) {
- cx.propagate_event();
- }
- }
- })
- .on_down(MouseButton::Right, {
- let position_map = position_map.clone();
- move |e, cx| {
- if !Self::mouse_right_down(
- e.position,
- position_map.as_ref(),
- text_bounds,
- cx,
- ) {
- cx.propagate_event();
- }
+ scene.push_mouse_region(
+ MouseRegion::new::<EditorElementMouseHandlers>(
+ cx.view_id(),
+ cx.view_id(),
+ visible_bounds,
+ )
+ .on_down(MouseButton::Left, {
+ let position_map = position_map.clone();
+ move |e, _, cx| {
+ if !Self::mouse_down(
+ e.platform_event,
+ position_map.as_ref(),
+ text_bounds,
+ gutter_bounds,
+ cx,
+ ) {
+ cx.propagate_event();
}
- })
- .on_up(MouseButton::Left, {
- let view = view.clone();
- let position_map = position_map.clone();
- move |e, cx| {
- if !Self::mouse_up(
- view.clone(),
- e.position,
- e.cmd,
- e.shift,
- position_map.as_ref(),
- text_bounds,
- cx,
- ) {
- cx.propagate_event()
- }
+ }
+ })
+ .on_down(MouseButton::Right, {
+ let position_map = position_map.clone();
+ move |event, _, cx| {
+ if !Self::mouse_right_down(
+ event.position,
+ position_map.as_ref(),
+ text_bounds,
+ cx,
+ ) {
+ cx.propagate_event();
}
- })
- .on_drag(MouseButton::Left, {
- let view = view.clone();
- let position_map = position_map.clone();
- move |e, cx| {
- if !Self::mouse_dragged(
- view.clone(),
- e.platform_event,
- position_map.as_ref(),
- text_bounds,
- cx,
- ) {
- cx.propagate_event()
- }
+ }
+ })
+ .on_up(MouseButton::Left, {
+ let position_map = position_map.clone();
+ move |event, editor, cx| {
+ if !Self::mouse_up(
+ event.position,
+ event.cmd,
+ event.shift,
+ position_map.as_ref(),
+ text_bounds,
+ editor,
+ cx,
+ ) {
+ cx.propagate_event()
}
- })
- .on_move({
- let position_map = position_map.clone();
- move |e, cx| {
- if !Self::mouse_moved(e.platform_event, &position_map, text_bounds, cx) {
- cx.propagate_event()
- }
+ }
+ })
+ .on_drag(MouseButton::Left, {
+ let position_map = position_map.clone();
+ move |event, editor, cx| {
+ if !Self::mouse_dragged(
+ event.platform_event,
+ position_map.as_ref(),
+ text_bounds,
+ editor,
+ cx,
+ ) {
+ cx.propagate_event()
}
- })
- .on_move_out(move |_, cx| {
- if has_popovers {
- cx.dispatch_action(HideHover);
+ }
+ })
+ .on_move({
+ let position_map = position_map.clone();
+ move |e, _, cx| {
+ if !Self::mouse_moved(e.platform_event, &position_map, text_bounds, cx) {
+ cx.propagate_event()
}
- })
- .on_scroll({
- let position_map = position_map.clone();
- move |e, cx| {
- if !Self::scroll(
- e.position,
- *e.delta.raw(),
- e.delta.precise(),
- &position_map,
- bounds,
- cx,
- ) {
- cx.propagate_event()
- }
+ }
+ })
+ .on_move_out(move |_, _: &mut Editor, cx| {
+ if has_popovers {
+ cx.dispatch_action(HideHover);
+ }
+ })
+ .on_scroll({
+ let position_map = position_map.clone();
+ move |e, _, cx| {
+ if !Self::scroll(
+ e.position,
+ *e.delta.raw(),
+ e.delta.precise(),
+ &position_map,
+ bounds,
+ cx,
+ ) {
+ cx.propagate_event()
}
- }),
+ }
+ }),
);
enum GutterHandlers {}
- cx.scene.push_mouse_region(
- MouseRegion::new::<GutterHandlers>(view.id(), view.id() + 1, gutter_bounds).on_hover(
- |hover, cx| {
+ scene.push_mouse_region(
+ MouseRegion::new::<GutterHandlers>(cx.view_id(), cx.view_id() + 1, gutter_bounds)
+ .on_hover(|hover, _: &mut Editor, cx| {
cx.dispatch_action(GutterHover {
hovered: hover.started,
})
- },
- ),
+ }),
)
}
@@ -244,7 +228,7 @@ impl EditorElement {
position_map: &PositionMap,
text_bounds: RectF,
gutter_bounds: RectF,
- cx: &mut EventContext,
+ cx: &mut EventContext<Editor>,
) -> bool {
if gutter_bounds.contains_point(position) {
click_count = 3; // Simulate triple-click when clicking the gutter to select lines
@@ -279,7 +263,7 @@ impl EditorElement {
position: Vector2F,
position_map: &PositionMap,
text_bounds: RectF,
- cx: &mut EventContext,
+ cx: &mut EventContext<Editor>,
) -> bool {
if !text_bounds.contains_point(position) {
return false;
@@ -292,17 +276,16 @@ impl EditorElement {
}
fn mouse_up(
- view: WeakViewHandle<Editor>,
position: Vector2F,
cmd: bool,
shift: bool,
position_map: &PositionMap,
text_bounds: RectF,
- cx: &mut EventContext,
+ editor: &mut Editor,
+ cx: &mut EventContext<Editor>,
) -> bool {
- let view = view.upgrade(cx.app).unwrap().read(cx.app);
- let end_selection = view.has_pending_selection();
- let pending_nonempty_selections = view.has_pending_nonempty_selection();
+ let end_selection = editor.has_pending_selection();
+ let pending_nonempty_selections = editor.has_pending_nonempty_selection();
if end_selection {
cx.dispatch_action(Select(SelectPhase::End));
@@ -326,7 +309,6 @@ impl EditorElement {
}
fn mouse_dragged(
- view: WeakViewHandle<Editor>,
MouseMovedEvent {
modifiers: Modifiers { cmd, shift, .. },
position,
@@ -334,7 +316,8 @@ impl EditorElement {
}: MouseMovedEvent,
position_map: &PositionMap,
text_bounds: RectF,
- cx: &mut EventContext,
+ editor: &mut Editor,
+ cx: &mut EventContext<Editor>,
) -> bool {
// This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
// Don't trigger hover popover if mouse is hovering over context menu
@@ -355,8 +338,7 @@ impl EditorElement {
shift_held: shift,
});
- let view = view.upgrade(cx.app).unwrap().read(cx.app);
- if view.has_pending_selection() {
+ if editor.has_pending_selection() {
let mut scroll_delta = Vector2F::zero();
let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0);
@@ -409,7 +391,7 @@ impl EditorElement {
}: MouseMovedEvent,
position_map: &PositionMap,
text_bounds: RectF,
- cx: &mut EventContext,
+ cx: &mut ViewContext<Editor>,
) -> bool {
// This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
// Don't trigger hover popover if mouse is hovering over context menu
@@ -432,7 +414,7 @@ impl EditorElement {
precise: bool,
position_map: &PositionMap,
bounds: RectF,
- cx: &mut EventContext,
+ cx: &mut ViewContext<Editor>,
) -> bool {
if !bounds.contains_point(position) {
return false;
@@ -465,21 +447,21 @@ impl EditorElement {
fn paint_background(
&self,
+ scene: &mut SceneBuilder,
gutter_bounds: RectF,
text_bounds: RectF,
layout: &LayoutState,
- cx: &mut PaintContext,
) {
let bounds = gutter_bounds.union_rect(text_bounds);
let scroll_top =
layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height;
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: gutter_bounds,
background: Some(self.style.gutter_background),
border: Border::new(0., Color::transparent_black()),
corner_radius: 0.,
});
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: text_bounds,
background: Some(self.style.background),
border: Border::new(0., Color::transparent_black()),
@@ -507,7 +489,7 @@ impl EditorElement {
bounds.width(),
layout.position_map.line_height * (end_row - start_row + 1) as f32,
);
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: RectF::new(origin, size),
background: Some(self.style.active_line_background),
border: Border::default(),
@@ -527,7 +509,7 @@ impl EditorElement {
bounds.width(),
layout.position_map.line_height * highlighted_rows.len() as f32,
);
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: RectF::new(origin, size),
background: Some(self.style.highlighted_line_background),
border: Border::default(),
@@ -539,10 +521,12 @@ impl EditorElement {
fn paint_gutter(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut LayoutState,
- cx: &mut PaintContext,
+ editor: &mut Editor,
+ cx: &mut ViewContext<Editor>,
) {
let line_height = layout.position_map.line_height;
@@ -558,7 +542,7 @@ impl EditorElement {
);
if show_gutter {
- Self::paint_diff_hunks(bounds, layout, cx);
+ Self::paint_diff_hunks(scene, bounds, layout, cx);
}
for (ix, line) in layout.line_number_layouts.iter().enumerate() {
@@ -569,7 +553,7 @@ impl EditorElement {
ix as f32 * line_height - (scroll_top % line_height),
);
- line.paint(line_origin, visible_bounds, line_height, cx);
+ line.paint(scene, line_origin, visible_bounds, line_height, cx);
}
}
@@ -586,7 +570,7 @@ impl EditorElement {
let indicator_origin = bounds.origin() + position + centering_offset;
- indicator.paint(indicator_origin, visible_bounds, cx);
+ indicator.paint(scene, indicator_origin, visible_bounds, editor, cx);
}
}
@@ -595,11 +579,22 @@ impl EditorElement {
let mut y = *row as f32 * line_height - scroll_top;
x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
y += (line_height - indicator.size().y()) / 2.;
- indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
+ indicator.paint(
+ scene,
+ bounds.origin() + vec2f(x, y),
+ visible_bounds,
+ editor,
+ cx,
+ );
}
}
- fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
+ fn paint_diff_hunks(
+ scene: &mut SceneBuilder,
+ bounds: RectF,
+ layout: &mut LayoutState,
+ cx: &mut ViewContext<Editor>,
+ ) {
let diff_style = &cx.global::<Settings>().theme.editor.diff.clone();
let line_height = layout.position_map.line_height;
@@ -618,7 +613,7 @@ impl EditorElement {
let highlight_size = vec2f(width * 2., end_y - start_y);
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: highlight_bounds,
background: Some(diff_style.modified),
border: Border::new(0., Color::transparent_black()),
@@ -651,7 +646,7 @@ impl EditorElement {
let highlight_size = vec2f(width * 2., end_y - start_y);
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: highlight_bounds,
background: Some(diff_style.deleted),
border: Border::new(0., Color::transparent_black()),
@@ -673,7 +668,7 @@ impl EditorElement {
let highlight_size = vec2f(width * 2., end_y - start_y);
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: highlight_bounds,
background: Some(color),
border: Border::new(0., Color::transparent_black()),
@@ -684,14 +679,15 @@ impl EditorElement {
fn paint_text(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut LayoutState,
- cx: &mut PaintContext,
+ editor: &mut Editor,
+ cx: &mut ViewContext<Editor>,
) {
- let view = self.view(cx.app);
let style = &self.style;
- let local_replica_id = view.replica_id(cx);
+ let local_replica_id = editor.replica_id(cx);
let scroll_position = layout.position_map.snapshot.scroll_position();
let start_row = layout.visible_display_row_range.start;
let scroll_top = scroll_position.y() * layout.position_map.line_height;
@@ -700,11 +696,11 @@ impl EditorElement {
let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
let line_end_overshoot = 0.15 * layout.position_map.line_height;
- cx.scene.push_layer(Some(bounds));
+ scene.push_layer(Some(bounds));
- cx.scene.push_cursor_region(CursorRegion {
+ scene.push_cursor_region(CursorRegion {
bounds,
- style: if !view.link_go_to_definition_state.definitions.is_empty() {
+ style: if !editor.link_go_to_definition_state.definitions.is_empty() {
CursorStyle::PointingHand
} else {
CursorStyle::IBeam
@@ -715,6 +711,7 @@ impl EditorElement {
self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height;
for (id, range, color) in layout.fold_ranges.iter() {
self.paint_highlighted_range(
+ scene,
range.clone(),
*color,
fold_corner_radius,
@@ -724,7 +721,6 @@ impl EditorElement {
scroll_top,
scroll_left,
bounds,
- cx,
);
for bound in range_to_bounds(
@@ -736,7 +732,7 @@ impl EditorElement {
line_end_overshoot,
&layout.position_map,
) {
- cx.scene.push_cursor_region(CursorRegion {
+ scene.push_cursor_region(CursorRegion {
bounds: bound,
style: CursorStyle::PointingHand,
});
@@ -747,9 +743,9 @@ impl EditorElement {
.to_point(&layout.position_map.snapshot.display_snapshot)
.row;
- cx.scene.push_mouse_region(
- MouseRegion::new::<FoldMarkers>(self.view.id(), *id as usize, bound)
- .on_click(MouseButton::Left, move |_, cx| {
+ scene.push_mouse_region(
+ MouseRegion::new::<FoldMarkers>(cx.view_id(), *id as usize, bound)
+ .on_click(MouseButton::Left, move |_, _: &mut Editor, cx| {
cx.dispatch_action(UnfoldAt { buffer_row })
})
.with_notify_on_hover(true)
@@ -760,6 +756,7 @@ impl EditorElement {
for (range, color) in &layout.highlighted_ranges {
self.paint_highlighted_range(
+ scene,
range.clone(),
*color,
0.,
@@ -769,7 +766,6 @@ impl EditorElement {
scroll_top,
scroll_left,
bounds,
- cx,
);
}
@@ -781,6 +777,7 @@ impl EditorElement {
for selection in selections {
self.paint_highlighted_range(
+ scene,
selection.range.clone(),
selection_style.selection,
corner_radius,
@@ -790,10 +787,9 @@ impl EditorElement {
scroll_top,
scroll_left,
bounds,
- cx,
);
- if view.show_local_cursors(cx) || *replica_id != local_replica_id {
+ if editor.show_local_cursors(cx) || *replica_id != local_replica_id {
let cursor_position = selection.head;
if layout
.visible_display_row_range
@@ -820,7 +816,7 @@ impl EditorElement {
cursor_row_layout.font_for_index(cursor_column)?;
let text = character.to_string();
- Some(cx.text_layout_cache.layout_str(
+ Some(cx.text_layout_cache().layout_str(
&text,
cursor_row_layout.font_size(),
&[(
@@ -858,6 +854,7 @@ impl EditorElement {
for (ix, line) in layout.position_map.line_layouts.iter().enumerate() {
let row = start_row + ix as u32;
line.paint(
+ scene,
content_origin
+ vec2f(
-scroll_left,
@@ -870,14 +867,14 @@ impl EditorElement {
}
}
- cx.scene.push_layer(Some(bounds));
- for cursor in cursors {
- cursor.paint(content_origin, cx);
- }
- cx.scene.pop_layer();
+ scene.paint_layer(Some(bounds), |scene| {
+ for cursor in cursors {
+ cursor.paint(scene, content_origin, cx);
+ }
+ });
if let Some((position, context_menu)) = layout.context_menu.as_mut() {
- cx.scene.push_stacking_context(None, None);
+ scene.push_stacking_context(None, None);
let cursor_row_layout =
&layout.position_map.line_layouts[(position.row() - start_row) as usize];
let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
@@ -888,8 +885,8 @@ impl EditorElement {
// Snap the right edge of the list to the right edge of the window if
// its horizontal bounds overflow.
- if list_origin.x() + list_width > cx.window_size.x() {
- list_origin.set_x((cx.window_size.x() - list_width).max(0.));
+ if list_origin.x() + list_width > cx.window_size().x() {
+ list_origin.set_x((cx.window_size().x() - list_width).max(0.));
}
if list_origin.y() + list_height > bounds.max_y() {
@@ -897,16 +894,18 @@ impl EditorElement {
}
context_menu.paint(
+ scene,
list_origin,
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
+ editor,
cx,
);
- cx.scene.pop_stacking_context();
+ scene.pop_stacking_context();
}
if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {
- cx.scene.push_stacking_context(None, None);
+ scene.push_stacking_context(None, None);
// This is safe because we check on layout whether the required row is available
let hovered_row_layout =
@@ -937,8 +936,10 @@ impl EditorElement {
}
hover_popover.paint(
+ scene,
popover_origin,
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
+ editor,
cx,
);
@@ -957,8 +958,10 @@ impl EditorElement {
}
hover_popover.paint(
+ scene,
popover_origin,
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
+ editor,
cx,
);
@@ -966,19 +969,24 @@ impl EditorElement {
}
}
- cx.scene.pop_stacking_context();
+ scene.pop_stacking_context();
}
- cx.scene.pop_layer();
+ scene.pop_layer();
}
- fn paint_scrollbar(&mut self, bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
+ fn paint_scrollbar(
+ &mut self,
+ scene: &mut SceneBuilder,
+ bounds: RectF,
+ layout: &mut LayoutState,
+ cx: &mut ViewContext<Editor>,
+ ) {
enum ScrollbarMouseHandlers {}
if layout.mode != EditorMode::Full {
return;
}
- let view = self.view.clone();
let style = &self.style.theme.scrollbar;
let top = bounds.min_y();
@@ -1008,13 +1016,13 @@ impl EditorElement {
let thumb_bounds = RectF::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom));
if layout.show_scrollbars {
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: track_bounds,
border: style.track.border,
background: style.track.background_color,
..Default::default()
});
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: thumb_bounds,
border: style.thumb.border,
background: style.thumb.background_color,
@@ -1022,63 +1030,42 @@ impl EditorElement {
});
}
- cx.scene.push_cursor_region(CursorRegion {
+ scene.push_cursor_region(CursorRegion {
bounds: track_bounds,
style: CursorStyle::Arrow,
});
- cx.scene.push_mouse_region(
- MouseRegion::new::<ScrollbarMouseHandlers>(view.id(), view.id(), track_bounds)
- .on_move({
- let view = view.clone();
- move |_, cx| {
- if let Some(view) = view.upgrade(cx.deref_mut()) {
- view.update(cx.deref_mut(), |view, cx| {
- view.scroll_manager.show_scrollbar(cx);
- });
- }
- }
+ scene.push_mouse_region(
+ MouseRegion::new::<ScrollbarMouseHandlers>(cx.view_id(), cx.view_id(), track_bounds)
+ .on_move(move |_, editor: &mut Editor, cx| {
+ editor.scroll_manager.show_scrollbar(cx);
})
.on_down(MouseButton::Left, {
- let view = view.clone();
let row_range = row_range.clone();
- move |e, cx| {
- let y = e.position.y();
- if let Some(view) = view.upgrade(cx.deref_mut()) {
- view.update(cx.deref_mut(), |view, cx| {
- if y < thumb_top || thumb_bottom < y {
- let center_row =
- ((y - top) * max_row as f32 / height).round() as u32;
- let top_row = center_row.saturating_sub(
- (row_range.end - row_range.start) as u32 / 2,
- );
- let mut position = view.scroll_position(cx);
- position.set_y(top_row as f32);
- view.set_scroll_position(position, cx);
- } else {
- view.scroll_manager.show_scrollbar(cx);
- }
- });
+ move |event, editor: &mut Editor, cx| {
+ let y = event.position.y();
+ if y < thumb_top || thumb_bottom < y {
+ let center_row = ((y - top) * max_row as f32 / height).round() as u32;
+ let top_row = center_row
+ .saturating_sub((row_range.end - row_range.start) as u32 / 2);
+ let mut position = editor.scroll_position(cx);
+ position.set_y(top_row as f32);
+ editor.set_scroll_position(position, cx);
+ } else {
+ editor.scroll_manager.show_scrollbar(cx);
}
}
})
.on_drag(MouseButton::Left, {
- let view = view.clone();
- move |e, cx| {
- let y = e.prev_mouse_position.y();
- let new_y = e.position.y();
+ move |event, editor: &mut Editor, cx| {
+ let y = event.prev_mouse_position.y();
+ let new_y = event.position.y();
if thumb_top < y && y < thumb_bottom {
- if let Some(view) = view.upgrade(cx.deref_mut()) {
- view.update(cx.deref_mut(), |view, cx| {
- let mut position = view.scroll_position(cx);
- position.set_y(
- position.y() + (new_y - y) * (max_row as f32) / height,
- );
- if position.y() < 0.0 {
- position.set_y(0.);
- }
- view.set_scroll_position(position, cx);
- });
+ let mut position = editor.scroll_position(cx);
+ position.set_y(position.y() + (new_y - y) * (max_row as f32) / height);
+ if position.y() < 0.0 {
+ position.set_y(0.);
}
+ editor.set_scroll_position(position, cx);
}
}
}),
@@ -1088,6 +1075,7 @@ impl EditorElement {
#[allow(clippy::too_many_arguments)]
fn paint_highlighted_range(
&self,
+ scene: &mut SceneBuilder,
range: Range<DisplayPoint>,
color: Color,
corner_radius: f32,
@@ -1097,7 +1085,6 @@ impl EditorElement {
scroll_top: f32,
scroll_left: f32,
bounds: RectF,
- cx: &mut PaintContext,
) {
let start_row = layout.visible_display_row_range.start;
let end_row = layout.visible_display_row_range.end;
@@ -1141,16 +1128,18 @@ impl EditorElement {
.collect(),
};
- highlighted_range.paint(bounds, cx.scene);
+ highlighted_range.paint(bounds, scene);
}
}
fn paint_blocks(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut LayoutState,
- cx: &mut PaintContext,
+ editor: &mut Editor,
+ cx: &mut ViewContext<Editor>,
) {
let scroll_position = layout.position_map.snapshot.scroll_position();
let scroll_left = scroll_position.x() * layout.position_map.em_width;
@@ -1165,15 +1154,17 @@ impl EditorElement {
if !matches!(block.style, BlockStyle::Sticky) {
origin += vec2f(-scroll_left, 0.);
}
- block.element.paint(origin, visible_bounds, cx);
+ block
+ .element
+ .paint(scene, origin, visible_bounds, editor, cx);
}
}
- fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &LayoutContext) -> f32 {
+ fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> f32 {
let digit_count = (snapshot.max_buffer_row() as f32).log10().floor() as usize + 1;
let style = &self.style;
- cx.text_layout_cache
+ cx.text_layout_cache()
.layout_str(
"1".repeat(digit_count).as_str(),
style.text.font_size,
@@ -1218,7 +1209,7 @@ impl EditorElement {
active_rows: &BTreeMap<u32, bool>,
is_singleton: bool,
snapshot: &EditorSnapshot,
- cx: &LayoutContext,
+ cx: &ViewContext<Editor>,
) -> (
Vec<Option<text_layout::Line>>,
Vec<Option<(FoldStatus, BufferRow, bool)>>,
@@ -1243,7 +1234,7 @@ impl EditorElement {
if include_line_numbers {
line_number.clear();
write!(&mut line_number, "{}", buffer_row + 1).unwrap();
- line_number_layouts.push(Some(cx.text_layout_cache.layout_str(
+ line_number_layouts.push(Some(cx.text_layout_cache().layout_str(
&line_number,
style.text.font_size,
&[(
@@ -1278,7 +1269,7 @@ impl EditorElement {
&mut self,
rows: Range<u32>,
snapshot: &EditorSnapshot,
- cx: &LayoutContext,
+ cx: &ViewContext<Editor>,
) -> Vec<text_layout::Line> {
if rows.start >= rows.end {
return Vec::new();
@@ -1301,7 +1292,7 @@ impl EditorElement {
.take(rows.len());
placeholder_lines
.map(|line| {
- cx.text_layout_cache.layout_str(
+ cx.text_layout_cache().layout_str(
line,
placeholder_style.font_size,
&[(
@@ -1361,8 +1352,8 @@ impl EditorElement {
layout_highlighted_chunks(
chunks,
&style.text,
- cx.text_layout_cache,
- cx.font_cache,
+ cx.text_layout_cache(),
+ cx.font_cache(),
MAX_LINE_LEN,
rows.len() as usize,
)
@@ -1384,14 +1375,9 @@ impl EditorElement {
style: &EditorStyle,
line_layouts: &[text_layout::Line],
include_root: bool,
- cx: &mut LayoutContext,
+ editor: &mut Editor,
+ cx: &mut ViewContext<Editor>,
) -> (f32, Vec<BlockLayout>) {
- let editor = if let Some(editor) = self.view.upgrade(cx) {
- editor
- } else {
- return Default::default();
- };
-
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let scroll_x = snapshot.scroll_anchor.offset.x();
let (fixed_blocks, non_fixed_blocks) = snapshot
@@ -1412,20 +1398,18 @@ impl EditorElement {
line_layouts[(align_to.row() - rows.start) as usize]
.x_for_index(align_to.column() as usize)
} else {
- layout_line(align_to.row(), snapshot, style, cx.text_layout_cache)
+ layout_line(align_to.row(), snapshot, style, cx.text_layout_cache())
.x_for_index(align_to.column() as usize)
};
- cx.render(&editor, |_, cx| {
- block.render(&mut BlockContext {
- cx,
- anchor_x,
- gutter_padding,
- line_height,
- scroll_x,
- gutter_width,
- em_width,
- })
+ block.render(&mut BlockContext {
+ view_context: cx,
+ anchor_x,
+ gutter_padding,
+ line_height,
+ scroll_x,
+ gutter_width,
+ em_width,
})
}
TransformBlock::ExcerptHeader {
@@ -1451,36 +1435,34 @@ impl EditorElement {
};
enum JumpIcon {}
- cx.render(&editor, |_, cx| {
- MouseEventHandler::<JumpIcon>::new(id.into(), cx, |state, _| {
- let style = style.jump_icon.style_for(state, false);
- Svg::new("icons/arrow_up_right_8.svg")
- .with_color(style.color)
- .constrained()
- .with_width(style.icon_width)
- .aligned()
- .contained()
- .with_style(style.container)
- .constrained()
- .with_width(style.button_width)
- .with_height(style.button_width)
- .boxed()
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
- cx.dispatch_action(jump_action.clone())
- })
- .with_tooltip::<JumpIcon, _>(
- id.into(),
- "Jump to Buffer".to_string(),
- Some(Box::new(crate::OpenExcerpts)),
- tooltip_style.clone(),
- cx,
- )
- .aligned()
- .flex_float()
- .boxed()
+ MouseEventHandler::<JumpIcon, _>::new(id.into(), cx, |state, _| {
+ let style = style.jump_icon.style_for(state, false);
+ Svg::new("icons/arrow_up_right_8.svg")
+ .with_color(style.color)
+ .constrained()
+ .with_width(style.icon_width)
+ .aligned()
+ .contained()
+ .with_style(style.container)
+ .constrained()
+ .with_width(style.button_width)
+ .with_height(style.button_width)
+ .boxed()
+ })
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, move |_, _, cx| {
+ cx.dispatch_action(jump_action.clone())
})
+ .with_tooltip::<JumpIcon>(
+ id.into(),
+ "Jump to Buffer".to_string(),
+ Some(Box::new(crate::OpenExcerpts)),
+ tooltip_style.clone(),
+ cx,
+ )
+ .aligned()
+ .flex_float()
+ .boxed()
});
if *starts_new_buffer {
@@ -4,7 +4,7 @@ use gpui::{
elements::{Flex, MouseEventHandler, Padding, Text},
impl_internal_actions,
platform::{CursorStyle, MouseButton},
- AppContext, Axis, Element, ElementBox, ModelHandle, RenderContext, Task, ViewContext,
+ AppContext, Axis, Drawable, Element, ModelHandle, Task, ViewContext,
};
use language::{Bias, DiagnosticEntry, DiagnosticSeverity};
use project::{HoverBlock, Project};
@@ -208,7 +208,7 @@ fn show_hover(
local_diagnostic,
primary_diagnostic,
});
- });
+ })?;
}
// Construct new hover popover from hover request
@@ -254,7 +254,7 @@ fn show_hover(
this.hover_state.info_popover = hover_popover;
cx.notify();
- });
+ })?;
}
Ok::<_, anyhow::Error>(())
}
@@ -282,8 +282,8 @@ impl HoverState {
snapshot: &EditorSnapshot,
style: &EditorStyle,
visible_rows: Range<u32>,
- cx: &mut RenderContext<Editor>,
- ) -> Option<(DisplayPoint, Vec<ElementBox>)> {
+ cx: &mut ViewContext<Editor>,
+ ) -> Option<(DisplayPoint, Vec<Element<Editor>>)> {
// If there is a diagnostic, position the popovers based on that.
// Otherwise use the start of the hover range
let anchor = self
@@ -323,9 +323,9 @@ pub struct InfoPopover {
}
impl InfoPopover {
- pub fn render(&self, style: &EditorStyle, cx: &mut RenderContext<Editor>) -> ElementBox {
- MouseEventHandler::<InfoPopover>::new(0, cx, |_, cx| {
- let mut flex = Flex::new(Axis::Vertical).scrollable::<HoverBlock, _>(1, None, cx);
+ pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> Element<Editor> {
+ MouseEventHandler::<InfoPopover, _>::new(0, cx, |_, cx| {
+ let mut flex = Flex::new(Axis::Vertical).scrollable::<HoverBlock>(1, None, cx);
flex.extend(self.contents.iter().map(|content| {
let languages = self.project.read(cx).languages();
if let Some(language) = content.language.clone().and_then(|language| {
@@ -360,7 +360,7 @@ impl InfoPopover {
.with_style(style.hover_popover.container)
.boxed()
})
- .on_move(|_, _| {}) // Consume move events so they don't reach regions underneath.
+ .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath.
.with_cursor_style(CursorStyle::Arrow)
.with_padding(Padding {
bottom: HOVER_POPOVER_GAP,
@@ -378,7 +378,7 @@ pub struct DiagnosticPopover {
}
impl DiagnosticPopover {
- pub fn render(&self, style: &EditorStyle, cx: &mut RenderContext<Editor>) -> ElementBox {
+ pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> Element<Editor> {
enum PrimaryDiagnostic {}
let mut text_style = style.hover_popover.prose.clone();
@@ -394,7 +394,7 @@ impl DiagnosticPopover {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
- MouseEventHandler::<DiagnosticPopover>::new(0, cx, |_, _| {
+ MouseEventHandler::<DiagnosticPopover, _>::new(0, cx, |_, _| {
Text::new(self.local_diagnostic.diagnostic.message.clone(), text_style)
.with_soft_wrap(true)
.contained()
@@ -406,12 +406,12 @@ impl DiagnosticPopover {
bottom: HOVER_POPOVER_GAP,
..Default::default()
})
- .on_move(|_, _| {}) // Consume move events so they don't reach regions underneath.
- .on_click(MouseButton::Left, |_, cx| {
+ .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath.
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(GoToDiagnostic)
})
.with_cursor_style(CursorStyle::PointingHand)
- .with_tooltip::<PrimaryDiagnostic, _>(
+ .with_tooltip::<PrimaryDiagnostic>(
0,
"Go To Diagnostic".to_string(),
Some(Box::new(crate::GoToDiagnostic)),
@@ -8,7 +8,7 @@ use collections::HashSet;
use futures::future::try_join_all;
use gpui::{
elements::*, geometry::vector::vec2f, AppContext, AsyncAppContext, Entity, ModelHandle,
- RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+ Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
@@ -28,7 +28,7 @@ use std::{
};
use text::Selection;
use util::{ResultExt, TryFutureExt};
-use workspace::item::FollowableItemHandle;
+use workspace::item::{BreadcrumbText, FollowableItemHandle};
use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
@@ -80,7 +80,9 @@ impl FollowableItem for Editor {
})
});
- let editor = editor.unwrap_or_else(|| {
+ let editor = if let Some(editor) = editor {
+ editor
+ } else {
pane.update(&mut cx, |_, cx| {
let multibuffer = cx.add_model(|cx| {
let mut multibuffer;
@@ -121,8 +123,8 @@ impl FollowableItem for Editor {
editor.remote_id = Some(remote_id);
editor
})
- })
- });
+ })?
+ };
update_editor_from_message(
editor.clone(),
@@ -367,7 +369,7 @@ async fn update_editor_from_message(
multibuffer.remove_excerpts(removed_excerpt_ids, cx);
});
- });
+ })?;
// Deserialize the editor state.
let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
@@ -384,7 +386,7 @@ async fn update_editor_from_message(
.scroll_top_anchor
.and_then(|anchor| deserialize_anchor(&buffer, anchor));
anyhow::Ok((selections, pending_selection, scroll_top_anchor))
- })?;
+ })??;
// Wait until the buffer has received all of the operations referenced by
// the editor's new state.
@@ -399,7 +401,7 @@ async fn update_editor_from_message(
cx,
)
})
- })
+ })?
.await?;
// Update the editor's state.
@@ -416,7 +418,7 @@ async fn update_editor_from_message(
cx,
);
}
- });
+ })?;
Ok(())
}
@@ -556,12 +558,12 @@ impl Item for Editor {
}
}
- fn tab_content(
+ fn tab_content<T: View>(
&self,
detail: Option<usize>,
style: &theme::Tab,
cx: &AppContext,
- ) -> ElementBox {
+ ) -> Element<T> {
Flex::row()
.with_child(
Label::new(self.title(cx).to_string(), style.label.clone())
@@ -641,7 +643,7 @@ impl Item for Editor {
self.report_event("save editor", cx);
let format = self.perform_format(project.clone(), FormatTrigger::Save, cx);
let buffers = self.buffer().clone().read(cx).all_buffers();
- cx.as_mut().spawn(|mut cx| async move {
+ cx.spawn(|_, mut cx| async move {
format.await?;
if buffers.len() == 1 {
@@ -705,7 +707,7 @@ impl Item for Editor {
let transaction = reload_buffers.log_err().await;
this.update(&mut cx, |editor, cx| {
editor.request_autoscroll(Autoscroll::fit(), cx)
- });
+ })?;
buffer.update(&mut cx, |buffer, _| {
if let Some(transaction) = transaction {
if !buffer.is_singleton() {
@@ -762,7 +764,7 @@ impl Item for Editor {
ToolbarItemLocation::PrimaryLeft { flex: None }
}
- fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
+ fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
let cursor = self.selections.newest_anchor().head();
let multibuffer = &self.buffer().read(cx);
let (buffer_id, symbols) =
@@ -782,15 +784,13 @@ impl Item for Editor {
.map(|path| path.to_string_lossy().to_string())
.unwrap_or_else(|| "untitled".to_string());
- let filename_label = Label::new(filename, theme.workspace.breadcrumbs.default.text.clone());
- let mut breadcrumbs = vec![filename_label.boxed()];
- breadcrumbs.extend(symbols.into_iter().map(|symbol| {
- Text::new(
- symbol.text,
- theme.workspace.breadcrumbs.default.text.clone(),
- )
- .with_highlights(symbol.highlight_ranges)
- .boxed()
+ let mut breadcrumbs = vec![BreadcrumbText {
+ text: filename,
+ highlights: None,
+ }];
+ breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
+ text: symbol.text,
+ highlights: Some(symbol.highlight_ranges),
}));
Some(breadcrumbs)
}
@@ -1113,7 +1113,7 @@ impl View for CursorPosition {
"CursorPosition"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
if let Some(position) = self.position {
let theme = &cx.global::<Settings>().theme.workspace.status_bar;
let mut text = format!("{},{}", position.row + 1, position.column + 1);
@@ -261,7 +261,7 @@ pub fn show_link_definition(
hide_link_definition(this, cx);
}
}
- })
+ })?;
}
Ok::<_, anyhow::Error>(())
@@ -248,10 +248,12 @@ impl ScrollManager {
self.hide_scrollbar_task = Some(cx.spawn_weak(|editor, mut cx| async move {
cx.background().timer(SCROLLBAR_SHOW_INTERVAL).await;
if let Some(editor) = editor.upgrade(&cx) {
- editor.update(&mut cx, |editor, cx| {
- editor.scroll_manager.show_scrollbars = false;
- cx.notify();
- });
+ editor
+ .update(&mut cx, |editor, cx| {
+ editor.scroll_manager.show_scrollbars = false;
+ cx.notify();
+ })
+ .log_err();
}
}));
} else {
@@ -34,11 +34,10 @@ impl<'a> EditorTestContext<'a> {
crate::init(cx);
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
+ cx.focus_self();
build_editor(MultiBuffer::build_simple("", cx), cx)
});
- editor.update(cx, |_, cx| cx.focus_self());
-
(window_id, editor)
});
@@ -1,7 +1,7 @@
use gpui::{
elements::*,
platform::{CursorStyle, MouseButton},
- Entity, RenderContext, View, ViewContext,
+ Entity, View, ViewContext,
};
use settings::Settings;
use workspace::{item::ItemHandle, StatusItemView};
@@ -27,12 +27,12 @@ impl View for DeployFeedbackButton {
"DeployFeedbackButton"
}
- fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let active = self.active;
let theme = cx.global::<Settings>().theme.clone();
Stack::new()
.with_child(
- MouseEventHandler::<Self>::new(0, cx, |state, _| {
+ MouseEventHandler::<Self, Self>::new(0, cx, |state, _| {
let style = &theme
.workspace
.status_bar
@@ -53,12 +53,12 @@ impl View for DeployFeedbackButton {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
if !active {
cx.dispatch_action(GiveFeedback)
}
})
- .with_tooltip::<Self, _>(
+ .with_tooltip::<Self>(
0,
"Send Feedback".into(),
Some(Box::new(GiveFeedback)),
@@ -13,8 +13,8 @@ use gpui::{
actions,
elements::{ChildView, Flex, Label, ParentElement, Svg},
platform::PromptLevel,
- serde_json, AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, RenderContext,
- Task, View, ViewContext, ViewHandle,
+ serde_json, AnyViewHandle, AppContext, Drawable, Element, Entity, ModelHandle, Task, View,
+ ViewContext, ViewHandle,
};
use isahc::Request;
use language::Buffer;
@@ -134,24 +134,21 @@ impl FeedbackEditor {
if answer == Some(0) {
match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await {
Ok(_) => {
- cx.update(|cx| {
- this.update(cx, |_, cx| {
- cx.dispatch_action(workspace::CloseActiveItem);
- })
- });
+ this.update(&mut cx, |_, cx| {
+ cx.dispatch_action(workspace::CloseActiveItem);
+ })
+ .log_err();
}
Err(error) => {
log::error!("{}", error);
-
- cx.update(|cx| {
- this.update(cx, |_, cx| {
- cx.prompt(
- PromptLevel::Critical,
- FEEDBACK_SUBMISSION_ERROR_TEXT,
- &["OK"],
- );
- })
- });
+ this.update(&mut cx, |_, cx| {
+ cx.prompt(
+ PromptLevel::Critical,
+ FEEDBACK_SUBMISSION_ERROR_TEXT,
+ &["OK"],
+ );
+ })
+ .log_err();
}
}
}
@@ -221,10 +218,10 @@ impl FeedbackEditor {
.add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx));
workspace.add_item(Box::new(feedback_editor), cx);
})
- })
- .await;
+ })?
+ .await
})
- .detach();
+ .detach_and_log_err(cx);
}
}
@@ -233,7 +230,7 @@ impl View for FeedbackEditor {
"FeedbackEditor"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
ChildView::new(&self.editor, cx).boxed()
}
@@ -253,7 +250,12 @@ impl Item for FeedbackEditor {
Some("Send Feedback".into())
}
- fn tab_content(&self, _: Option<usize>, style: &theme::Tab, _: &AppContext) -> ElementBox {
+ fn tab_content<T: View>(
+ &self,
+ _: Option<usize>,
+ style: &theme::Tab,
+ _: &AppContext,
+ ) -> Element<T> {
Flex::row()
.with_child(
Svg::new("icons/feedback_16.svg")
@@ -1,7 +1,7 @@
use gpui::{
elements::{Flex, Label, MouseEventHandler, ParentElement, Text},
platform::{CursorStyle, MouseButton},
- Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle,
+ Drawable, Element, Entity, View, ViewContext, ViewHandle,
};
use settings::Settings;
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
@@ -29,7 +29,7 @@ impl View for FeedbackInfoText {
"FeedbackInfoText"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = cx.global::<Settings>().theme.clone();
Flex::row()
@@ -43,7 +43,7 @@ impl View for FeedbackInfoText {
.boxed(),
)
.with_child(
- MouseEventHandler::<OpenZedCommunityRepo>::new(0, cx, |state, _| {
+ MouseEventHandler::<OpenZedCommunityRepo, Self>::new(0, cx, |state, _| {
let contained_text = if state.hovered() {
&theme.feedback.link_text_hover
} else {
@@ -58,7 +58,7 @@ impl View for FeedbackInfoText {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, cx| {
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(OpenZedCommunityRepo)
})
.boxed(),
@@ -1,7 +1,7 @@
use gpui::{
elements::{Label, MouseEventHandler},
platform::{CursorStyle, MouseButton},
- Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle,
+ Drawable, Element, Entity, View, ViewContext, ViewHandle,
};
use settings::Settings;
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
@@ -29,10 +29,10 @@ impl View for SubmitFeedbackButton {
"SubmitFeedbackButton"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = cx.global::<Settings>().theme.clone();
enum SubmitFeedbackButton {}
- MouseEventHandler::<SubmitFeedbackButton>::new(0, cx, |state, _| {
+ MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
let style = theme.feedback.submit_button.style_for(state, false);
Label::new("Submit as Markdown", style.text.clone())
.contained()
@@ -40,13 +40,13 @@ impl View for SubmitFeedbackButton {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, cx| {
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(SubmitFeedback)
})
.aligned()
.contained()
.with_margin_left(theme.feedback.button_margin)
- .with_tooltip::<Self, _>(
+ .with_tooltip::<Self>(
0,
"cmd-s".into(),
Some(Box::new(SubmitFeedback)),
@@ -1,7 +1,6 @@
use fuzzy::PathMatch;
use gpui::{
- actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
- RenderContext, Task, View, ViewContext, ViewHandle,
+ actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle,
};
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
@@ -13,12 +12,14 @@ use std::{
Arc,
},
};
-use util::post_inc;
+use util::{post_inc, ResultExt};
use workspace::Workspace;
-pub struct FileFinder {
+pub type FileFinder = Picker<FileFinderDelegate>;
+
+pub struct FileFinderDelegate {
+ workspace: WeakViewHandle<Workspace>,
project: ModelHandle<Project>,
- picker: ViewHandle<Picker<Self>>,
search_count: usize,
latest_search_id: usize,
latest_search_did_cancel: bool,
@@ -32,8 +33,26 @@ pub struct FileFinder {
actions!(file_finder, [Toggle]);
pub fn init(cx: &mut AppContext) {
- cx.add_action(FileFinder::toggle);
- Picker::<FileFinder>::init(cx);
+ cx.add_action(toggle_file_finder);
+ FileFinder::init(cx);
+}
+
+fn toggle_file_finder(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+ workspace.toggle_modal(cx, |workspace, cx| {
+ let relative_to = workspace
+ .active_item(cx)
+ .and_then(|item| item.project_path(cx))
+ .map(|project_path| project_path.path.clone());
+ let project = workspace.project().clone();
+ let workspace = cx.handle().downgrade();
+ let finder = cx.add_view(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(workspace, project, relative_to, cx),
+ cx,
+ )
+ });
+ finder
+ });
}
pub enum Event {
@@ -41,27 +60,7 @@ pub enum Event {
Dismissed,
}
-impl Entity for FileFinder {
- type Event = Event;
-}
-
-impl View for FileFinder {
- fn ui_name() -> &'static str {
- "FileFinder"
- }
-
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
- ChildView::new(&self.picker, cx).boxed()
- }
-
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.is_self_focused() {
- cx.focus(&self.picker);
- }
- }
-}
-
-impl FileFinder {
+impl FileFinderDelegate {
fn labels_for_match(&self, path_match: &PathMatch) -> (String, Vec<usize>, String, Vec<usize>) {
let path = &path_match.path;
let path_string = path.to_string_lossy();
@@ -88,48 +87,19 @@ impl FileFinder {
(file_name, file_name_positions, full_path, path_positions)
}
- fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
- workspace.toggle_modal(cx, |workspace, cx| {
- let project = workspace.project().clone();
- let relative_to = workspace
- .active_item(cx)
- .and_then(|item| item.project_path(cx))
- .map(|project_path| project_path.path.clone());
- let finder = cx.add_view(|cx| Self::new(project, relative_to, cx));
- cx.subscribe(&finder, Self::on_event).detach();
- finder
- });
- }
-
- fn on_event(
- workspace: &mut Workspace,
- _: ViewHandle<FileFinder>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Selected(project_path) => {
- workspace
- .open_path(project_path.clone(), None, true, cx)
- .detach_and_log_err(cx);
- workspace.dismiss_modal(cx);
- }
- Event::Dismissed => {
- workspace.dismiss_modal(cx);
- }
- }
- }
-
pub fn new(
+ workspace: WeakViewHandle<Workspace>,
project: ModelHandle<Project>,
relative_to: Option<Arc<Path>>,
- cx: &mut ViewContext<Self>,
+ cx: &mut ViewContext<FileFinder>,
) -> Self {
- let handle = cx.weak_handle();
- cx.observe(&project, Self::project_updated).detach();
+ cx.observe(&project, |picker, _, cx| {
+ picker.update_matches(picker.query(cx), cx);
+ })
+ .detach();
Self {
+ workspace,
project,
- picker: cx.add_view(|cx| Picker::new("Search project files...", handle, cx)),
search_count: 0,
latest_search_id: 0,
latest_search_did_cancel: false,
@@ -141,12 +111,7 @@ impl FileFinder {
}
}
- fn project_updated(&mut self, _: ModelHandle<Project>, cx: &mut ViewContext<Self>) {
- self.spawn_search(self.picker.read(cx).query(cx), cx)
- .detach();
- }
-
- fn spawn_search(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+ fn spawn_search(&mut self, query: String, cx: &mut ViewContext<FileFinder>) -> Task<()> {
let relative_to = self.relative_to.clone();
let worktrees = self
.project
@@ -172,7 +137,7 @@ impl FileFinder {
self.cancel_flag.store(true, atomic::Ordering::Relaxed);
self.cancel_flag = Arc::new(AtomicBool::new(false));
let cancel_flag = self.cancel_flag.clone();
- cx.spawn(|this, mut cx| async move {
+ cx.spawn(|picker, mut cx| async move {
let matches = fuzzy::match_path_sets(
candidate_sets.as_slice(),
&query,
@@ -184,9 +149,13 @@ impl FileFinder {
)
.await;
let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
- this.update(&mut cx, |this, cx| {
- this.set_matches(search_id, did_cancel, query, matches, cx)
- });
+ picker
+ .update(&mut cx, |picker, cx| {
+ picker
+ .delegate_mut()
+ .set_matches(search_id, did_cancel, query, matches, cx)
+ })
+ .log_err();
})
}
@@ -196,7 +165,7 @@ impl FileFinder {
did_cancel: bool,
query: String,
matches: Vec<PathMatch>,
- cx: &mut ViewContext<Self>,
+ cx: &mut ViewContext<FileFinder>,
) {
if search_id >= self.latest_search_id {
self.latest_search_id = search_id;
@@ -208,12 +177,15 @@ impl FileFinder {
self.latest_search_query = query;
self.latest_search_did_cancel = did_cancel;
cx.notify();
- self.picker.update(cx, |_, cx| cx.notify());
}
}
}
-impl PickerDelegate for FileFinder {
+impl PickerDelegate for FileFinderDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Search project files...".into()
+ }
+
fn match_count(&self) -> usize {
self.matches.len()
}
@@ -231,13 +203,13 @@ impl PickerDelegate for FileFinder {
0
}
- fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
+ fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<FileFinder>) {
let mat = &self.matches[ix];
self.selected = Some((mat.worktree_id, mat.path.clone()));
cx.notify();
}
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+ fn update_matches(&mut self, query: String, cx: &mut ViewContext<FileFinder>) -> Task<()> {
if query.is_empty() {
self.latest_search_id = post_inc(&mut self.search_count);
self.matches.clear();
@@ -248,18 +220,25 @@ impl PickerDelegate for FileFinder {
}
}
- fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<FileFinder>) {
if let Some(m) = self.matches.get(self.selected_index()) {
- cx.emit(Event::Selected(ProjectPath {
- worktree_id: WorktreeId::from_usize(m.worktree_id),
- path: m.path.clone(),
- }));
+ if let Some(workspace) = self.workspace.upgrade(cx) {
+ let project_path = ProjectPath {
+ worktree_id: WorktreeId::from_usize(m.worktree_id),
+ path: m.path.clone(),
+ };
+
+ workspace.update(cx, |workspace, cx| {
+ workspace
+ .open_path(project_path.clone(), None, true, cx)
+ .detach_and_log_err(cx);
+ workspace.dismiss_modal(cx);
+ })
+ }
}
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
- cx.emit(Event::Dismissed);
- }
+ fn dismissed(&mut self, _: &mut ViewContext<FileFinder>) {}
fn render_match(
&self,
@@ -267,7 +246,7 @@ impl PickerDelegate for FileFinder {
mouse_state: &mut MouseState,
selected: bool,
cx: &AppContext,
- ) -> ElementBox {
+ ) -> Element<Picker<Self>> {
let path_match = &self.matches[ix];
let settings = cx.global::<Settings>();
let style = settings.theme.picker.item.style_for(mouse_state, selected);
@@ -335,11 +314,11 @@ mod tests {
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
finder
.update(cx, |finder, cx| {
- finder.update_matches("bna".to_string(), cx)
+ finder.delegate_mut().update_matches("bna".to_string(), cx)
})
.await;
finder.read_with(cx, |finder, _| {
- assert_eq!(finder.matches.len(), 2);
+ assert_eq!(finder.delegate().matches.len(), 2);
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
@@ -384,23 +363,33 @@ mod tests {
let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let (_, finder) =
- cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
+ let (_, finder) = cx.add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ None,
+ cx,
+ ),
+ cx,
+ )
+ });
let query = "hi".to_string();
finder
- .update(cx, |f, cx| f.spawn_search(query.clone(), cx))
+ .update(cx, |f, cx| f.delegate_mut().spawn_search(query.clone(), cx))
.await;
- finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 5));
+ finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 5));
finder.update(cx, |finder, cx| {
- let matches = finder.matches.clone();
+ let delegate = finder.delegate_mut();
+ let matches = delegate.matches.clone();
// Simulate a search being cancelled after the time limit,
// returning only a subset of the matches that would have been found.
- drop(finder.spawn_search(query.clone(), cx));
- finder.set_matches(
- finder.latest_search_id,
+ drop(delegate.spawn_search(query.clone(), cx));
+ delegate.set_matches(
+ delegate.latest_search_id,
true, // did-cancel
query.clone(),
vec![matches[1].clone(), matches[3].clone()],
@@ -408,16 +397,16 @@ mod tests {
);
// Simulate another cancellation.
- drop(finder.spawn_search(query.clone(), cx));
- finder.set_matches(
- finder.latest_search_id,
+ drop(delegate.spawn_search(query.clone(), cx));
+ delegate.set_matches(
+ delegate.latest_search_id,
true, // did-cancel
query.clone(),
vec![matches[0].clone(), matches[2].clone(), matches[3].clone()],
cx,
);
- assert_eq!(finder.matches, matches[0..4])
+ assert_eq!(delegate.matches, matches[0..4])
});
}
@@ -458,12 +447,21 @@ mod tests {
)
.await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let (_, finder) =
- cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
+ let (_, finder) = cx.add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ None,
+ cx,
+ ),
+ cx,
+ )
+ });
finder
- .update(cx, |f, cx| f.spawn_search("hi".into(), cx))
+ .update(cx, |f, cx| f.delegate_mut().spawn_search("hi".into(), cx))
.await;
- finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 7));
+ finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 7));
}
#[gpui::test]
@@ -482,20 +480,30 @@ mod tests {
)
.await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let (_, finder) =
- cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
+ let (_, finder) = cx.add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ None,
+ cx,
+ ),
+ cx,
+ )
+ });
// Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file.
finder
- .update(cx, |f, cx| f.spawn_search("thf".into(), cx))
+ .update(cx, |f, cx| f.delegate_mut().spawn_search("thf".into(), cx))
.await;
cx.read(|cx| {
let finder = finder.read(cx);
- assert_eq!(finder.matches.len(), 1);
+ let delegate = finder.delegate();
+ assert_eq!(delegate.matches.len(), 1);
let (file_name, file_name_positions, full_path, full_path_positions) =
- finder.labels_for_match(&finder.matches[0]);
+ delegate.labels_for_match(&delegate.matches[0]);
assert_eq!(file_name, "the-file");
assert_eq!(file_name_positions, &[0, 1, 4]);
assert_eq!(full_path, "the-file");
@@ -505,9 +513,9 @@ mod tests {
// Since the worktree root is a file, searching for its name followed by a slash does
// not match anything.
finder
- .update(cx, |f, cx| f.spawn_search("thf/".into(), cx))
+ .update(cx, |f, cx| f.delegate_mut().spawn_search("thf/".into(), cx))
.await;
- finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 0));
+ finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0));
}
#[gpui::test]
@@ -535,22 +543,32 @@ mod tests {
.await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let (_, finder) =
- cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
+ let (_, finder) = cx.add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ None,
+ cx,
+ ),
+ cx,
+ )
+ });
// Run a search that matches two files with the same relative path.
finder
- .update(cx, |f, cx| f.spawn_search("a.t".into(), cx))
+ .update(cx, |f, cx| f.delegate_mut().spawn_search("a.t".into(), cx))
.await;
// Can switch between different matches with the same relative path.
- finder.update(cx, |f, cx| {
- assert_eq!(f.matches.len(), 2);
- assert_eq!(f.selected_index(), 0);
- f.set_selected_index(1, cx);
- assert_eq!(f.selected_index(), 1);
- f.set_selected_index(0, cx);
- assert_eq!(f.selected_index(), 0);
+ finder.update(cx, |finder, cx| {
+ let delegate = finder.delegate_mut();
+ assert_eq!(delegate.matches.len(), 2);
+ assert_eq!(delegate.selected_index(), 0);
+ delegate.set_selected_index(1, cx);
+ assert_eq!(delegate.selected_index(), 1);
+ delegate.set_selected_index(0, cx);
+ assert_eq!(delegate.selected_index(), 0);
});
}
@@ -581,16 +599,28 @@ mod tests {
// first when they have the same name. In this case, b.txt is closer to dir2's a.txt
// so that one should be sorted earlier
let b_path = Some(Arc::from(Path::new("/root/dir2/b.txt")));
- let (_, finder) =
- cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), b_path, cx));
+ let (_, finder) = cx.add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ b_path,
+ cx,
+ ),
+ cx,
+ )
+ });
finder
- .update(cx, |f, cx| f.spawn_search("a.txt".into(), cx))
+ .update(cx, |f, cx| {
+ f.delegate_mut().spawn_search("a.txt".into(), cx)
+ })
.await;
finder.read_with(cx, |f, _| {
- assert_eq!(f.matches[0].path.as_ref(), Path::new("dir2/a.txt"));
- assert_eq!(f.matches[1].path.as_ref(), Path::new("dir1/a.txt"));
+ let delegate = f.delegate();
+ assert_eq!(delegate.matches[0].path.as_ref(), Path::new("dir2/a.txt"));
+ assert_eq!(delegate.matches[1].path.as_ref(), Path::new("dir1/a.txt"));
});
}
@@ -613,14 +643,23 @@ mod tests {
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let (_, finder) =
- cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
+ let (_, finder) = cx.add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ None,
+ cx,
+ ),
+ cx,
+ )
+ });
finder
- .update(cx, |f, cx| f.spawn_search("dir".into(), cx))
+ .update(cx, |f, cx| f.delegate_mut().spawn_search("dir".into(), cx))
.await;
cx.read(|cx| {
let finder = finder.read(cx);
- assert_eq!(finder.matches.len(), 0);
+ assert_eq!(finder.delegate().matches.len(), 0);
});
}
}
@@ -3,12 +3,12 @@ use std::sync::Arc;
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
use gpui::{
actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Axis, Entity,
- RenderContext, View, ViewContext, ViewHandle,
+ View, ViewContext, ViewHandle,
};
use menu::{Cancel, Confirm};
use settings::Settings;
use text::{Bias, Point};
-use workspace::Workspace;
+use workspace::{Modal, Workspace};
actions!(go_to_line, [Toggle]);
@@ -65,11 +65,7 @@ impl GoToLine {
.active_item(cx)
.and_then(|active_item| active_item.downcast::<Editor>())
{
- workspace.toggle_modal(cx, |_, cx| {
- let view = cx.add_view(|cx| GoToLine::new(editor, cx));
- cx.subscribe(&view, Self::on_event).detach();
- view
- });
+ workspace.toggle_modal(cx, |_, cx| cx.add_view(|cx| GoToLine::new(editor, cx)));
}
}
@@ -91,17 +87,6 @@ impl GoToLine {
cx.emit(Event::Dismissed);
}
- fn on_event(
- workspace: &mut Workspace,
- _: ViewHandle<Self>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Dismissed => workspace.dismiss_modal(cx),
- }
- }
-
fn on_line_editor_event(
&mut self,
_: ViewHandle<Editor>,
@@ -142,12 +127,14 @@ impl Entity for GoToLine {
fn release(&mut self, cx: &mut AppContext) {
let scroll_position = self.prev_scroll_position.take();
- self.active_editor.update(cx, |editor, cx| {
- editor.highlight_rows(None);
- if let Some(scroll_position) = scroll_position {
- editor.set_scroll_position(scroll_position, cx);
- }
- })
+ cx.update_window(self.active_editor.window_id(), |cx| {
+ self.active_editor.update(cx, |editor, cx| {
+ editor.highlight_rows(None);
+ if let Some(scroll_position) = scroll_position {
+ editor.set_scroll_position(scroll_position, cx);
+ }
+ })
+ });
}
}
@@ -156,7 +143,7 @@ impl View for GoToLine {
"GoToLine"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = &cx.global::<Settings>().theme.picker;
let label = format!(
@@ -192,3 +179,9 @@ impl View for GoToLine {
cx.focus(&self.line_editor);
}
}
+
+impl Modal for GoToLine {
+ fn dismiss_on_event(event: &Self::Event) -> bool {
+ matches!(event, Event::Dismissed)
+ }
+}
@@ -2,7 +2,7 @@ use gpui::{
color::Color,
fonts::{Properties, Weight},
text_layout::RunStyle,
- DebugContext, Element as _, MeasurementContext, Quad,
+ Drawable, Element, Quad, SceneBuilder, View, ViewContext,
};
use log::LevelFilter;
use pathfinder_geometry::rect::RectF;
@@ -30,12 +30,12 @@ impl gpui::View for TextView {
"View"
}
- fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
+ fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Element<TextView> {
TextElement.boxed()
}
}
-impl gpui::Element for TextElement {
+impl<V: View> Drawable<V> for TextElement {
type LayoutState = ();
type PaintState = ();
@@ -43,17 +43,20 @@ impl gpui::Element for TextElement {
fn layout(
&mut self,
constraint: gpui::SizeConstraint,
- _: &mut gpui::LayoutContext,
+ _: &mut V,
+ _: &mut ViewContext<V>,
) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) {
(constraint.max, ())
}
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut gpui::PaintContext,
+ _: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
let font_size = 12.;
let family = cx
@@ -84,7 +87,7 @@ impl gpui::Element for TextElement {
};
let text = "Hello world!";
- let line = cx.text_layout_cache.layout_str(
+ let line = cx.text_layout_cache().layout_str(
text,
font_size,
&[
@@ -96,12 +99,12 @@ impl gpui::Element for TextElement {
],
);
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds,
background: Some(Color::white()),
..Default::default()
});
- line.paint(bounds.origin(), visible_bounds, bounds.height(), cx);
+ line.paint(scene, bounds.origin(), visible_bounds, bounds.height(), cx);
}
fn rect_for_text_range(
@@ -111,7 +114,8 @@ impl gpui::Element for TextElement {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &MeasurementContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> Option<RectF> {
None
}
@@ -121,7 +125,8 @@ impl gpui::Element for TextElement {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &DebugContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> gpui::json::Value {
todo!()
}
@@ -4,6 +4,7 @@ mod menu;
pub(crate) mod ref_counts;
#[cfg(any(test, feature = "test-support"))]
pub mod test_app_context;
+pub(crate) mod window;
mod window_input_handler;
use std::{
@@ -23,10 +24,9 @@ use std::{
use anyhow::{anyhow, Context, Result};
use parking_lot::Mutex;
-use pathfinder_geometry::vector::Vector2F;
use postage::oneshot;
-use smallvec::SmallVec;
use smol::prelude::*;
+use util::ResultExt;
use uuid::Uuid;
pub use action::*;
@@ -41,16 +41,16 @@ pub use test_app_context::{ContextHandle, TestAppContext};
use window_input_handler::WindowInputHandler;
use crate::{
- elements::ElementBox,
+ elements::{AnyRootElement, Element, RootElement},
executor::{self, Task},
keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult},
platform::{
- self, Appearance, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton,
+ self, FontSystem, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton,
PathPromptOptions, Platform, PromptLevel, WindowBounds, WindowOptions,
},
- presenter::Presenter,
util::post_inc,
- AssetCache, AssetSource, ClipboardItem, FontCache, MouseRegionId, TextLayoutCache,
+ window::{Window, WindowContext},
+ AssetCache, AssetSource, ClipboardItem, FontCache, MouseRegionId,
};
use self::ref_counts::RefCounts;
@@ -69,7 +69,7 @@ pub trait Entity: 'static {
pub trait View: Entity + Sized {
fn ui_name() -> &'static str;
- fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox;
+ fn render(&mut self, cx: &mut ViewContext<'_, '_, '_, Self>) -> Element<Self>;
fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {}
fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {}
fn key_down(&mut self, _: &KeyDownEvent, _: &mut ViewContext<Self>) -> bool {
@@ -173,11 +173,13 @@ pub trait ReadViewWith {
}
pub trait UpdateView {
+ type Output<S>;
+
fn update_view<T, S>(
&mut self,
handle: &ViewHandle<T>,
update: &mut dyn FnMut(&mut T, &mut ViewContext<T>) -> S,
- ) -> S
+ ) -> Self::Output<S>
where
T: View;
}
@@ -321,6 +323,17 @@ impl App {
state.pending_notifications.clear();
result
}
+
+ fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
+ &mut self,
+ window_id: usize,
+ callback: F,
+ ) -> Option<T> {
+ let mut state = self.0.borrow_mut();
+ let result = state.update_window(window_id, callback);
+ state.pending_notifications.clear();
+ result
+ }
}
impl AsyncAppContext {
@@ -341,6 +354,14 @@ impl AsyncAppContext {
self.0.borrow_mut().update(callback)
}
+ pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
+ &mut self,
+ window_id: usize,
+ callback: F,
+ ) -> Option<T> {
+ self.0.borrow_mut().update_window(window_id, callback)
+ }
+
pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
where
T: Entity,
@@ -366,17 +387,18 @@ impl AsyncAppContext {
}
pub fn activate_window(&mut self, window_id: usize) {
- self.update(|cx| cx.activate_window(window_id))
+ self.update_window(window_id, |cx| cx.activate_window());
}
+ // TODO: Can we eliminate this method and move it to WindowContext then call it with update_window?s
pub fn prompt(
&mut self,
window_id: usize,
level: PromptLevel,
msg: &str,
answers: &[&str],
- ) -> oneshot::Receiver<usize> {
- self.update(|cx| cx.prompt(window_id, level, msg, answers))
+ ) -> Option<oneshot::Receiver<usize>> {
+ self.update_window(window_id, |cx| cx.prompt(level, msg, answers))
}
pub fn platform(&self) -> Arc<dyn Platform> {
@@ -442,15 +464,20 @@ impl ReadModelWith for AsyncAppContext {
}
impl UpdateView for AsyncAppContext {
+ type Output<S> = Result<S>;
+
fn update_view<T, S>(
&mut self,
handle: &ViewHandle<T>,
update: &mut dyn FnMut(&mut T, &mut ViewContext<T>) -> S,
- ) -> S
+ ) -> Result<S>
where
T: View,
{
- self.0.borrow_mut().update_view(handle, update)
+ self.0
+ .borrow_mut()
+ .update_window(handle.window_id, |cx| cx.update_view(handle, update))
+ .ok_or_else(|| anyhow!("window was closed"))
}
}
@@ -469,21 +496,21 @@ impl ReadViewWith for AsyncAppContext {
}
}
-type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut AppContext, usize, usize);
+type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut WindowContext, usize);
type GlobalActionCallback = dyn FnMut(&dyn Action, &mut AppContext);
type SubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool>;
type GlobalSubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut AppContext)>;
type ObservationCallback = Box<dyn FnMut(&mut AppContext) -> bool>;
type GlobalObservationCallback = Box<dyn FnMut(&mut AppContext)>;
-type FocusObservationCallback = Box<dyn FnMut(bool, &mut AppContext) -> bool>;
+type FocusObservationCallback = Box<dyn FnMut(bool, &mut WindowContext) -> bool>;
type ReleaseObservationCallback = Box<dyn FnMut(&dyn Any, &mut AppContext)>;
type ActionObservationCallback = Box<dyn FnMut(TypeId, &mut AppContext)>;
-type WindowActivationCallback = Box<dyn FnMut(bool, &mut AppContext) -> bool>;
-type WindowFullscreenCallback = Box<dyn FnMut(bool, &mut AppContext) -> bool>;
-type WindowBoundsCallback = Box<dyn FnMut(WindowBounds, Uuid, &mut AppContext) -> bool>;
+type WindowActivationCallback = Box<dyn FnMut(bool, &mut WindowContext) -> bool>;
+type WindowFullscreenCallback = Box<dyn FnMut(bool, &mut WindowContext) -> bool>;
+type WindowBoundsCallback = Box<dyn FnMut(WindowBounds, Uuid, &mut WindowContext) -> bool>;
type KeystrokeCallback =
- Box<dyn FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut AppContext) -> bool>;
+ Box<dyn FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut WindowContext) -> bool>;
type ActiveLabeledTasksCallback = Box<dyn FnMut(&mut AppContext) -> bool>;
type DeserializeActionCallback = fn(json: &str) -> anyhow::Result<Box<dyn Action>>;
type WindowShouldCloseSubscriptionCallback = Box<dyn FnMut(&mut AppContext) -> bool>;
@@ -497,12 +524,13 @@ pub struct AppContext {
element_states: HashMap<ElementStateId, Box<dyn Any>>,
background: Arc<executor::Background>,
ref_counts: Arc<Mutex<RefCounts>>,
- font_cache: Arc<FontCache>,
- platform: Arc<dyn Platform>,
weak_self: Option<rc::Weak<RefCell<Self>>>,
+ platform: Arc<dyn Platform>,
foreground_platform: Rc<dyn platform::ForegroundPlatform>,
- assets: Arc<AssetCache>,
+ pub asset_cache: Arc<AssetCache>,
+ font_system: Arc<dyn FontSystem>,
+ pub font_cache: Arc<FontCache>,
action_deserializers: HashMap<&'static str, (TypeId, DeserializeActionCallback)>,
capture_actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
// Entity Types -> { Action Types -> Action Handlers }
@@ -528,9 +556,6 @@ pub struct AppContext {
keystroke_observations: CallbackCollection<usize, KeystrokeCallback>,
active_labeled_task_observations: CallbackCollection<(), ActiveLabeledTasksCallback>,
- #[allow(clippy::type_complexity)]
- presenters_and_platform_windows:
- HashMap<usize, (Rc<RefCell<Presenter>>, Box<dyn platform::Window>)>,
foreground: Rc<executor::Foreground>,
pending_effects: VecDeque<Effect>,
pending_notifications: HashSet<usize>,
@@ -561,12 +586,13 @@ impl AppContext {
element_states: Default::default(),
ref_counts: Arc::new(Mutex::new(ref_counts)),
background,
- font_cache,
- platform,
weak_self: None,
+ font_system: platform.fonts(),
+ platform,
foreground_platform,
- assets: Arc::new(AssetCache::new(asset_source)),
+ font_cache,
+ asset_cache: Arc::new(AssetCache::new(asset_source)),
action_deserializers: Default::default(),
capture_actions: Default::default(),
actions: Default::default(),
@@ -588,7 +614,6 @@ impl AppContext {
keystroke_observations: Default::default(),
action_dispatch_observations: Default::default(),
active_labeled_task_observations: Default::default(),
- presenters_and_platform_windows: Default::default(),
foreground,
pending_effects: VecDeque::new(),
pending_notifications: Default::default(),
@@ -601,6 +626,30 @@ impl AppContext {
}
}
+ pub fn background(&self) -> &Arc<executor::Background> {
+ &self.background
+ }
+
+ pub fn font_cache(&self) -> &Arc<FontCache> {
+ &self.font_cache
+ }
+
+ pub fn platform(&self) -> &Arc<dyn Platform> {
+ &self.platform
+ }
+
+ pub fn has_global<T: 'static>(&self) -> bool {
+ self.globals.contains_key(&TypeId::of::<T>())
+ }
+
+ pub fn global<T: 'static>(&self) -> &T {
+ if let Some(global) = self.globals.get(&TypeId::of::<T>()) {
+ global.downcast_ref().unwrap()
+ } else {
+ panic!("no global has been added for {}", type_name::<T>());
+ }
+ }
+
pub fn upgrade(&self) -> App {
App(self.weak_self.as_ref().unwrap().upgrade().unwrap())
}
@@ -635,9 +684,7 @@ impl AppContext {
}
pub fn remove_all_windows(&mut self) {
- for (window_id, _) in self.windows.drain() {
- self.presenters_and_platform_windows.remove(&window_id);
- }
+ self.windows.clear();
self.flush_effects();
}
@@ -645,12 +692,6 @@ impl AppContext {
&self.foreground
}
- pub fn debug_elements(&self, window_id: usize) -> Option<crate::json::Value> {
- self.presenters_and_platform_windows
- .get(&window_id)
- .and_then(|(presenter, _)| presenter.borrow().debug_elements(self))
- }
-
pub fn deserialize_action(
&self,
name: &str,
@@ -692,11 +733,10 @@ impl AppContext {
let handler = Box::new(
move |view: &mut dyn AnyView,
action: &dyn Action,
- cx: &mut AppContext,
- window_id: usize,
+ cx: &mut WindowContext,
view_id: usize| {
let action = action.as_any().downcast_ref().unwrap();
- let mut cx = ViewContext::new(cx, window_id, view_id);
+ let mut cx = ViewContext::mutable(cx, view_id);
handler(
view.as_any_mut()
.downcast_mut()
@@ -764,50 +804,32 @@ impl AppContext {
}
}
- pub fn is_topmost_window_for_position(&self, window_id: usize, position: Vector2F) -> bool {
- self.presenters_and_platform_windows
- .get(&window_id)
- .map_or(false, |(_, window)| {
- window.is_topmost_for_position(position)
- })
- }
-
pub fn has_window(&self, window_id: usize) -> bool {
self.window_ids()
.find(|window| window == &window_id)
.is_some()
}
- pub fn window_ids(&self) -> impl Iterator<Item = usize> + '_ {
- self.windows.keys().copied()
- }
-
- pub fn activate_window(&self, window_id: usize) {
- if let Some((_, window)) = self.presenters_and_platform_windows.get(&window_id) {
- window.activate()
- }
+ pub fn window_is_active(&self, window_id: usize) -> bool {
+ self.windows.get(&window_id).map_or(false, |w| w.is_active)
}
- pub fn window_is_active(&self, window_id: usize) -> bool {
- self.windows
- .get(&window_id)
- .map_or(false, |window| window.is_active)
+ pub fn root_view(&self, window_id: usize) -> Option<&AnyViewHandle> {
+ self.windows.get(&window_id).map(|w| w.root_view())
}
- pub fn window_is_fullscreen(&self, window_id: usize) -> bool {
- self.windows
- .get(&window_id)
- .map_or(false, |window| window.is_fullscreen)
+ pub fn window_ids(&self) -> impl Iterator<Item = usize> + '_ {
+ self.windows.keys().copied()
}
- pub fn window_bounds(&self, window_id: usize) -> Option<WindowBounds> {
- let (_, window) = self.presenters_and_platform_windows.get(&window_id)?;
- Some(window.bounds())
+ pub fn view_ui_name(&self, window_id: usize, view_id: usize) -> Option<&'static str> {
+ Some(self.views.get(&(window_id, view_id))?.ui_name())
}
- pub fn window_display_uuid(&self, window_id: usize) -> Option<Uuid> {
- let (_, window) = self.presenters_and_platform_windows.get(&window_id)?;
- window.screen().display_uuid()
+ pub fn view_type_id(&self, window_id: usize, view_id: usize) -> Option<TypeId> {
+ self.views
+ .get(&(window_id, view_id))
+ .map(|view| view.as_any().type_id())
}
pub fn active_labeled_tasks<'a>(
@@ -816,58 +838,6 @@ impl AppContext {
self.active_labeled_tasks.values().cloned()
}
- pub fn render_view(&mut self, params: RenderParams) -> Result<ElementBox> {
- let window_id = params.window_id;
- let view_id = params.view_id;
- let mut view = self
- .views
- .remove(&(window_id, view_id))
- .ok_or_else(|| anyhow!("view not found"))?;
- let element = view.render(params, self);
- self.views.insert((window_id, view_id), view);
- Ok(element)
- }
-
- pub fn render_views(
- &mut self,
- window_id: usize,
- titlebar_height: f32,
- appearance: Appearance,
- ) -> HashMap<usize, ElementBox> {
- self.start_frame();
- #[allow(clippy::needless_collect)]
- let view_ids = self
- .views
- .keys()
- .filter_map(|(win_id, view_id)| {
- if *win_id == window_id {
- Some(*view_id)
- } else {
- None
- }
- })
- .collect::<Vec<_>>();
-
- view_ids
- .into_iter()
- .map(|view_id| {
- (
- view_id,
- self.render_view(RenderParams {
- window_id,
- view_id,
- titlebar_height,
- hovered_region_ids: Default::default(),
- clicked_region_ids: None,
- refreshing: false,
- appearance,
- })
- .unwrap(),
- )
- })
- .collect()
- }
-
pub(crate) fn start_frame(&mut self) {
self.frame_count += 1;
}
@@ -879,35 +849,39 @@ impl AppContext {
result
}
- fn show_character_palette(&self, window_id: usize) {
- let (_, window) = &self.presenters_and_platform_windows[&window_id];
- window.show_character_palette();
- }
-
- pub fn minimize_window(&self, window_id: usize) {
- let (_, window) = &self.presenters_and_platform_windows[&window_id];
- window.minimize();
- }
-
- pub fn zoom_window(&self, window_id: usize) {
- let (_, window) = &self.presenters_and_platform_windows[&window_id];
- window.zoom();
+ pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
+ &self,
+ window_id: usize,
+ callback: F,
+ ) -> Option<T> {
+ let window = self.windows.get(&window_id)?;
+ let window_context = WindowContext::immutable(self, &window, window_id);
+ Some(callback(&window_context))
}
- pub fn toggle_window_full_screen(&self, window_id: usize) {
- let (_, window) = &self.presenters_and_platform_windows[&window_id];
- window.toggle_full_screen();
+ pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
+ &mut self,
+ window_id: usize,
+ callback: F,
+ ) -> Option<T> {
+ self.update(|app_context| {
+ let mut window = app_context.windows.remove(&window_id)?;
+ let mut window_context = WindowContext::mutable(app_context, &mut window, window_id);
+ let result = callback(&mut window_context);
+ if !window_context.removed {
+ app_context.windows.insert(window_id, window);
+ }
+ Some(result)
+ })
}
- pub fn prompt(
- &self,
- window_id: usize,
- level: PromptLevel,
- msg: &str,
- answers: &[&str],
- ) -> oneshot::Receiver<usize> {
- let (_, window) = &self.presenters_and_platform_windows[&window_id];
- window.prompt(level, msg, answers)
+ pub fn update_active_window<T, F: FnOnce(&mut WindowContext) -> T>(
+ &mut self,
+ callback: F,
+ ) -> Option<T> {
+ self.platform
+ .main_window_id()
+ .and_then(|id| self.update_window(id, callback))
}
pub fn prompt_for_paths(
@@ -978,7 +952,7 @@ impl AppContext {
})
}
- pub fn subscribe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
+ fn subscribe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
where
E: Entity,
E::Event: 'static,
@@ -1028,7 +1002,7 @@ impl AppContext {
fn observe_focus<F, V>(&mut self, handle: &ViewHandle<V>, mut callback: F) -> Subscription
where
- F: 'static + FnMut(ViewHandle<V>, bool, &mut AppContext) -> bool,
+ F: 'static + FnMut(ViewHandle<V>, bool, &mut WindowContext) -> bool,
V: View,
{
let subscription_id = post_inc(&mut self.next_subscription_id);
@@ -1111,72 +1085,7 @@ impl AppContext {
)
}
- fn observe_window_activation<F>(&mut self, window_id: usize, callback: F) -> Subscription
- where
- F: 'static + FnMut(bool, &mut AppContext) -> bool,
- {
- let subscription_id = post_inc(&mut self.next_subscription_id);
- self.pending_effects
- .push_back(Effect::WindowActivationObservation {
- window_id,
- subscription_id,
- callback: Box::new(callback),
- });
- Subscription::WindowActivationObservation(
- self.window_activation_observations
- .subscribe(window_id, subscription_id),
- )
- }
-
- fn observe_fullscreen<F>(&mut self, window_id: usize, callback: F) -> Subscription
- where
- F: 'static + FnMut(bool, &mut AppContext) -> bool,
- {
- let subscription_id = post_inc(&mut self.next_subscription_id);
- self.pending_effects
- .push_back(Effect::WindowFullscreenObservation {
- window_id,
- subscription_id,
- callback: Box::new(callback),
- });
- Subscription::WindowActivationObservation(
- self.window_activation_observations
- .subscribe(window_id, subscription_id),
- )
- }
-
- fn observe_window_bounds<F>(&mut self, window_id: usize, callback: F) -> Subscription
- where
- F: 'static + FnMut(WindowBounds, Uuid, &mut AppContext) -> bool,
- {
- let subscription_id = post_inc(&mut self.next_subscription_id);
- self.pending_effects
- .push_back(Effect::WindowBoundsObservation {
- window_id,
- subscription_id,
- callback: Box::new(callback),
- });
- Subscription::WindowBoundsObservation(
- self.window_bounds_observations
- .subscribe(window_id, subscription_id),
- )
- }
-
- pub fn observe_keystrokes<F>(&mut self, window_id: usize, callback: F) -> Subscription
- where
- F: 'static
- + FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut AppContext) -> bool,
- {
- let subscription_id = post_inc(&mut self.next_subscription_id);
- self.keystroke_observations
- .add_callback(window_id, subscription_id, Box::new(callback));
- Subscription::KeystrokeObservation(
- self.keystroke_observations
- .subscribe(window_id, subscription_id),
- )
- }
-
- pub fn observe_active_labeled_tasks<F>(&mut self, callback: F) -> Subscription
+ fn observe_active_labeled_tasks<F>(&mut self, callback: F) -> Subscription
where
F: 'static + FnMut(&mut AppContext) -> bool,
{
@@ -1203,172 +1112,54 @@ impl AppContext {
})
}
- pub(crate) fn notify_model(&mut self, model_id: usize) {
+ fn notify_model(&mut self, model_id: usize) {
if self.pending_notifications.insert(model_id) {
self.pending_effects
.push_back(Effect::ModelNotification { model_id });
}
}
- pub(crate) fn notify_view(&mut self, window_id: usize, view_id: usize) {
+ fn notify_view(&mut self, window_id: usize, view_id: usize) {
if self.pending_notifications.insert(view_id) {
self.pending_effects
.push_back(Effect::ViewNotification { window_id, view_id });
}
}
- pub(crate) fn notify_global(&mut self, type_id: TypeId) {
+ fn notify_global(&mut self, type_id: TypeId) {
if self.pending_global_notifications.insert(type_id) {
self.pending_effects
.push_back(Effect::GlobalNotification { type_id });
}
}
- pub(crate) fn name_for_view(&self, window_id: usize, view_id: usize) -> Option<&str> {
- self.views
- .get(&(window_id, view_id))
- .map(|view| view.ui_name())
- }
-
pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = &'static str> + 'a {
self.action_deserializers.keys().copied()
}
- /// Return keystrokes that would dispatch the given action on the given view.
- pub(crate) fn keystrokes_for_action(
- &mut self,
- window_id: usize,
- view_id: usize,
- action: &dyn Action,
- ) -> Option<SmallVec<[Keystroke; 2]>> {
- let mut contexts = Vec::new();
- let mut handler_depth = None;
- for (i, view_id) in self.ancestors(window_id, view_id).enumerate() {
- if let Some(view) = self.views.get(&(window_id, view_id)) {
- if let Some(actions) = self.actions.get(&view.as_any().type_id()) {
- if actions.contains_key(&action.as_any().type_id()) {
- handler_depth = Some(i);
- }
- }
- contexts.push(view.keymap_context(self));
- }
- }
-
- 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(
- &self,
- window_id: usize,
- view_id: usize,
- ) -> impl Iterator<Item = (&'static str, Box<dyn Action>, SmallVec<[&Binding; 1]>)> {
- let mut contexts = Vec::new();
- let mut handler_depths_by_action_type = HashMap::<TypeId, usize>::default();
- for (depth, view_id) in self.ancestors(window_id, view_id).enumerate() {
- if let Some(view) = self.views.get(&(window_id, view_id)) {
- contexts.push(view.keymap_context(self));
- let view_type = view.as_any().type_id();
- if let Some(actions) = self.actions.get(&view_type) {
- handler_depths_by_action_type.extend(
- actions
- .keys()
- .copied()
- .map(|action_type| (action_type, depth)),
- );
- }
- }
- }
-
- handler_depths_by_action_type.extend(
- self.global_actions
- .keys()
- .copied()
- .map(|action_type| (action_type, contexts.len())),
- );
-
- self.action_deserializers
- .iter()
- .filter_map(move |(name, (type_id, deserialize))| {
- if let Some(action_depth) = handler_depths_by_action_type.get(type_id).copied() {
- Some((
- *name,
- deserialize("{}").ok()?,
- self.keystroke_matcher
- .bindings_for_action_type(*type_id)
- .filter(|b| {
- (0..=action_depth).any(|depth| b.match_context(&contexts[depth..]))
- })
- .collect(),
- ))
- } else {
- None
- }
- })
- }
-
pub fn is_action_available(&self, action: &dyn Action) -> bool {
+ let mut available_in_window = false;
let action_type = action.as_any().type_id();
if let Some(window_id) = self.platform.main_window_id() {
- if let Some(focused_view_id) = self.focused_view_id(window_id) {
- for view_id in self.ancestors(window_id, focused_view_id) {
- if let Some(view) = self.views.get(&(window_id, view_id)) {
- let view_type = view.as_any().type_id();
- if let Some(actions) = self.actions.get(&view_type) {
- if actions.contains_key(&action_type) {
- return true;
+ available_in_window = self
+ .read_window(window_id, |cx| {
+ if let Some(focused_view_id) = cx.focused_view_id() {
+ for view_id in cx.ancestors(focused_view_id) {
+ if let Some(view) = cx.views.get(&(window_id, view_id)) {
+ let view_type = view.as_any().type_id();
+ if let Some(actions) = cx.actions.get(&view_type) {
+ if actions.contains_key(&action_type) {
+ return true;
+ }
+ }
}
}
}
- }
- }
- }
- self.global_actions.contains_key(&action_type)
- }
-
- // 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.
- // Returns a bool indicating if the traversal was completed early.
- fn visit_dispatch_path(
- &mut self,
- window_id: usize,
- view_id: usize,
- mut visit: impl FnMut(usize, bool, &mut AppContext) -> bool,
- ) -> bool {
- // List of view ids from the leaf to the root of the window
- let path = self.ancestors(window_id, view_id).collect::<Vec<_>>();
-
- // Walk down from the root to the leaf calling visit with capture_phase = true
- for view_id in path.iter().rev() {
- if !visit(*view_id, true, self) {
- return false;
- }
- }
-
- // Walk up from the leaf to the root calling visit with capture_phase = false
- for view_id in path.iter() {
- if !visit(*view_id, false, self) {
- return false;
- }
+ false
+ })
+ .unwrap_or(false);
}
-
- true
+ available_in_window || self.global_actions.contains_key(&action_type)
}
fn actions_mut(
@@ -1406,121 +1197,6 @@ impl AppContext {
self.keystroke_matcher.clear_bindings();
}
- pub fn dispatch_key_down(&mut self, window_id: usize, event: &KeyDownEvent) -> bool {
- if let Some(focused_view_id) = self.focused_view_id(window_id) {
- for view_id in self
- .ancestors(window_id, focused_view_id)
- .collect::<Vec<_>>()
- {
- if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
- let handled = view.key_down(event, self, window_id, view_id);
- self.views.insert((window_id, view_id), view);
- if handled {
- return true;
- }
- } else {
- log::error!("view {} does not exist", view_id)
- }
- }
- }
-
- false
- }
-
- pub fn dispatch_key_up(&mut self, window_id: usize, event: &KeyUpEvent) -> bool {
- if let Some(focused_view_id) = self.focused_view_id(window_id) {
- for view_id in self
- .ancestors(window_id, focused_view_id)
- .collect::<Vec<_>>()
- {
- if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
- let handled = view.key_up(event, self, window_id, view_id);
- self.views.insert((window_id, view_id), view);
- if handled {
- return true;
- }
- } else {
- log::error!("view {} does not exist", view_id)
- }
- }
- }
-
- false
- }
-
- pub fn dispatch_modifiers_changed(
- &mut self,
- window_id: usize,
- event: &ModifiersChangedEvent,
- ) -> bool {
- if let Some(focused_view_id) = self.focused_view_id(window_id) {
- for view_id in self
- .ancestors(window_id, focused_view_id)
- .collect::<Vec<_>>()
- {
- if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
- let handled = view.modifiers_changed(event, self, window_id, view_id);
- self.views.insert((window_id, view_id), view);
- if handled {
- return true;
- }
- } else {
- log::error!("view {} does not exist", view_id)
- }
- }
- }
-
- false
- }
-
- pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: &Keystroke) -> bool {
- if let Some(focused_view_id) = self.focused_view_id(window_id) {
- let dispatch_path = self
- .ancestors(window_id, focused_view_id)
- .filter_map(|view_id| {
- self.views
- .get(&(window_id, view_id))
- .map(|view| (view_id, view.keymap_context(self)))
- })
- .collect();
-
- let match_result = self
- .keystroke_matcher
- .push_keystroke(keystroke.clone(), dispatch_path);
- let mut handled_by = None;
-
- let keystroke_handled = match &match_result {
- MatchResult::None => false,
- MatchResult::Pending => true,
- MatchResult::Matches(matches) => {
- for (view_id, action) in matches {
- if self.handle_dispatch_action_from_effect(
- window_id,
- Some(*view_id),
- action.as_ref(),
- ) {
- self.keystroke_matcher.clear_pending();
- handled_by = Some(action.boxed_clone());
- break;
- }
- }
- handled_by.is_some()
- }
- };
-
- self.keystroke(
- window_id,
- keystroke.clone(),
- handled_by,
- match_result.clone(),
- );
- keystroke_handled
- } else {
- self.keystroke(window_id, keystroke.clone(), None, MatchResult::None);
- false
- }
- }
-
pub fn default_global<T: 'static + Default>(&mut self) -> &T {
let type_id = TypeId::of::<T>();
self.update(|this| {
@@ -1545,37 +1221,55 @@ impl AppContext {
T: 'static + Default,
F: FnOnce(&mut T, &mut AppContext) -> U,
{
- self.update(|this| {
- let type_id = TypeId::of::<T>();
- let mut state = this
- .globals
- .remove(&type_id)
- .unwrap_or_else(|| Box::new(T::default()));
- let result = update(state.downcast_mut().unwrap(), this);
- this.globals.insert(type_id, state);
- this.notify_global(type_id);
- result
+ self.update(|mut this| {
+ Self::update_default_global_internal(&mut this, |global, cx| update(global, cx))
})
}
+ fn update_default_global_internal<C, T, F, U>(this: &mut C, update: F) -> U
+ where
+ C: DerefMut<Target = AppContext>,
+ T: 'static + Default,
+ F: FnOnce(&mut T, &mut C) -> U,
+ {
+ let type_id = TypeId::of::<T>();
+ let mut state = this
+ .globals
+ .remove(&type_id)
+ .unwrap_or_else(|| Box::new(T::default()));
+ let result = update(state.downcast_mut().unwrap(), this);
+ this.globals.insert(type_id, state);
+ this.notify_global(type_id);
+ result
+ }
+
pub fn update_global<T, F, U>(&mut self, update: F) -> U
where
T: 'static,
F: FnOnce(&mut T, &mut AppContext) -> U,
{
- self.update(|this| {
- let type_id = TypeId::of::<T>();
- if let Some(mut state) = this.globals.remove(&type_id) {
- let result = update(state.downcast_mut().unwrap(), this);
- this.globals.insert(type_id, state);
- this.notify_global(type_id);
- result
- } else {
- panic!("No global added for {}", std::any::type_name::<T>());
- }
+ self.update(|mut this| {
+ Self::update_global_internal(&mut this, |global, cx| update(global, cx))
})
}
+ fn update_global_internal<C, T, F, U>(this: &mut C, update: F) -> U
+ where
+ C: DerefMut<Target = AppContext>,
+ T: 'static,
+ F: FnOnce(&mut T, &mut C) -> U,
+ {
+ let type_id = TypeId::of::<T>();
+ if let Some(mut state) = this.globals.remove(&type_id) {
+ let result = update(state.downcast_mut().unwrap(), this);
+ this.globals.insert(type_id, state);
+ this.notify_global(type_id);
+ result
+ } else {
+ panic!("No global added for {}", std::any::type_name::<T>());
+ }
+ }
+
pub fn clear_globals(&mut self) {
self.globals.clear();
}
@@ -1595,66 +1289,42 @@ impl AppContext {
})
}
- pub fn add_window<T, F>(
+ pub fn add_window<V, F>(
&mut self,
window_options: WindowOptions,
build_root_view: F,
- ) -> (usize, ViewHandle<T>)
+ ) -> (usize, ViewHandle<V>)
where
- T: View,
- F: FnOnce(&mut ViewContext<T>) -> T,
+ V: View,
+ F: FnOnce(&mut ViewContext<V>) -> V,
{
self.update(|this| {
let window_id = post_inc(&mut this.next_window_id);
- let root_view = this
- .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx)))
- .unwrap();
- this.windows.insert(
- window_id,
- Window {
- root_view: root_view.clone().into_any(),
- focused_view_id: Some(root_view.id()),
- is_active: false,
- invalidation: None,
- is_fullscreen: false,
- },
- );
- root_view.update(this, |view, cx| view.focus_in(cx.handle().into_any(), cx));
-
- let window =
+ let platform_window =
this.platform
.open_window(window_id, window_options, this.foreground.clone());
- this.register_platform_window(window_id, window);
-
+ let window = this.build_window(window_id, platform_window, build_root_view);
+ let root_view = window.root_view().clone().downcast::<V>().unwrap();
+ this.windows.insert(window_id, window);
(window_id, root_view)
})
}
- pub fn add_status_bar_item<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
+ pub fn add_status_bar_item<V, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<V>)
where
- T: View,
- F: FnOnce(&mut ViewContext<T>) -> T,
+ V: View,
+ F: FnOnce(&mut ViewContext<V>) -> V,
{
self.update(|this| {
let window_id = post_inc(&mut this.next_window_id);
- let root_view = this
- .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx)))
- .unwrap();
- let focused_view_id = root_view.id();
- this.windows.insert(
- window_id,
- Window {
- root_view: root_view.clone().into_any(),
- focused_view_id: Some(focused_view_id),
- is_active: false,
- invalidation: None,
- is_fullscreen: false,
- },
- );
- root_view.update(this, |view, cx| view.focus_in(cx.handle().into_any(), cx));
+ let platform_window = this.platform.add_status_item(window_id);
+ let window = this.build_window(window_id, platform_window, build_root_view);
+ let root_view = window.root_view().clone().downcast::<V>().unwrap();
- let status_item = this.platform.add_status_item();
- this.register_platform_window(window_id, status_item);
+ this.windows.insert(window_id, window);
+ this.update_window(window_id, |cx| {
+ root_view.update(cx, |view, cx| view.focus_in(cx.handle().into_any(), cx))
+ });
(window_id, root_view)
})
@@ -1,4 +1,3 @@
-use crate::AppContext;
use collections::{BTreeMap, HashMap, HashSet};
use parking_lot::Mutex;
use std::sync::Arc;
@@ -93,12 +92,10 @@ impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> {
drop(callbacks);
}
- pub fn emit<C: FnMut(&mut F, &mut AppContext) -> bool>(
- &mut self,
- key: K,
- cx: &mut AppContext,
- mut call_callback: C,
- ) {
+ pub fn emit<C>(&mut self, key: K, mut call_callback: C)
+ where
+ C: FnMut(&mut F) -> bool,
+ {
let callbacks = self.internal.lock().callbacks.remove(&key);
if let Some(callbacks) = callbacks {
for (subscription_id, mut callback) in callbacks {
@@ -110,7 +107,7 @@ impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> {
}
drop(this);
- let alive = call_callback(&mut callback, cx);
+ let alive = call_callback(&mut callback);
// If this callback's subscription was dropped while invoking the callback
// itself, or if the callback returns false, then just drop the callback.
@@ -78,8 +78,18 @@ pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform,
move |action| {
let mut cx = cx.borrow_mut();
if let Some(main_window_id) = cx.platform.main_window_id() {
- if let Some(view_id) = cx.focused_view_id(main_window_id) {
- cx.handle_dispatch_action_from_effect(main_window_id, Some(view_id), action);
+ 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);
+ true
+ } else {
+ false
+ }
+ })
+ .unwrap_or(false);
+
+ if dispatched {
return;
}
}
@@ -1,6 +1,6 @@
use std::{
+ any::Any,
cell::RefCell,
- marker::PhantomData,
mem,
path::PathBuf,
rc::Rc,
@@ -21,10 +21,10 @@ use crate::{
geometry::vector::Vector2F,
keymap_matcher::Keystroke,
platform,
- platform::{Appearance, Event, InputHandler, KeyDownEvent, Platform},
+ platform::{Event, InputHandler, KeyDownEvent, Platform},
Action, AnyViewHandle, AppContext, Entity, FontCache, Handle, ModelContext, ModelHandle,
- ReadModelWith, ReadViewWith, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext,
- ViewHandle, WeakHandle,
+ ReadModelWith, ReadViewWith, Subscription, Task, UpdateModel, UpdateView, View, ViewContext,
+ ViewHandle, WeakHandle, WindowContext,
};
use collections::BTreeMap;
@@ -75,7 +75,7 @@ impl TestAppContext {
pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) {
let mut cx = self.cx.borrow_mut();
- if let Some(view_id) = cx.focused_view_id(window_id) {
+ if let Some(view_id) = cx.windows.get(&window_id).and_then(|w| w.focused_view_id) {
cx.handle_dispatch_action_from_effect(window_id, Some(view_id), &action);
}
}
@@ -85,31 +85,27 @@ impl TestAppContext {
}
pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
- let handled = self.cx.borrow_mut().update(|cx| {
- let presenter = cx
- .presenters_and_platform_windows
- .get(&window_id)
- .unwrap()
- .0
- .clone();
-
- if cx.dispatch_keystroke(window_id, &keystroke) {
- return true;
- }
+ let handled = self
+ .cx
+ .borrow_mut()
+ .update_window(window_id, |cx| {
+ if cx.dispatch_keystroke(&keystroke) {
+ return true;
+ }
- if presenter.borrow_mut().dispatch_event(
- Event::KeyDown(KeyDownEvent {
- keystroke: keystroke.clone(),
- is_held,
- }),
- false,
- cx,
- ) {
- return true;
- }
+ if cx.dispatch_event(
+ Event::KeyDown(KeyDownEvent {
+ keystroke: keystroke.clone(),
+ is_held,
+ }),
+ false,
+ ) {
+ return true;
+ }
- false
- });
+ false
+ })
+ .unwrap_or(false);
if !handled && !keystroke.cmd && !keystroke.ctrl {
WindowInputHandler {
@@ -120,6 +116,22 @@ impl TestAppContext {
}
}
+ pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
+ &self,
+ window_id: usize,
+ callback: F,
+ ) -> Option<T> {
+ self.cx.borrow().read_window(window_id, callback)
+ }
+
+ pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
+ &mut self,
+ window_id: usize,
+ callback: F,
+ ) -> Option<T> {
+ self.cx.borrow_mut().update_window(window_id, callback)
+ }
+
pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
where
T: Entity,
@@ -149,12 +161,28 @@ impl TestAppContext {
self.cx.borrow_mut().add_view(parent_handle, build_view)
}
- pub fn window_ids(&self) -> Vec<usize> {
- self.cx.borrow().window_ids().collect()
+ pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
+ where
+ E: Any,
+ F: 'static + FnMut(&mut AppContext),
+ {
+ self.cx.borrow_mut().observe_global::<E, F>(callback)
+ }
+
+ pub fn set_global<T: 'static>(&mut self, state: T) {
+ self.cx.borrow_mut().set_global(state);
}
- pub fn root_view(&self, window_id: usize) -> Option<AnyViewHandle> {
- self.cx.borrow().root_view(window_id)
+ pub fn subscribe_global<E, F>(&mut self, callback: F) -> Subscription
+ where
+ E: Any,
+ F: 'static + FnMut(&E, &mut AppContext),
+ {
+ self.cx.borrow_mut().subscribe_global(callback)
+ }
+
+ pub fn window_ids(&self) -> Vec<usize> {
+ self.cx.borrow().window_ids().collect()
}
pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
@@ -172,27 +200,6 @@ impl TestAppContext {
result
}
- pub fn render<F, V, T>(&mut self, handle: &ViewHandle<V>, f: F) -> T
- where
- F: FnOnce(&mut V, &mut RenderContext<V>) -> T,
- V: View,
- {
- handle.update(&mut *self.cx.borrow_mut(), |view, cx| {
- let mut render_cx = RenderContext {
- app: cx,
- window_id: handle.window_id(),
- view_id: handle.id(),
- view_type: PhantomData,
- titlebar_height: 0.,
- hovered_region_ids: Default::default(),
- clicked_region_ids: None,
- refreshing: false,
- appearance: Appearance::Light,
- };
- f(view, &mut render_cx)
- })
- }
-
pub fn to_async(&self) -> AsyncAppContext {
AsyncAppContext(self.cx.clone())
}
@@ -245,7 +252,7 @@ impl TestAppContext {
use postage::prelude::Sink as _;
let mut done_tx = self
- .window_mut(window_id)
+ .platform_window_mut(window_id)
.pending_prompts
.borrow_mut()
.pop_front()
@@ -254,20 +261,23 @@ impl TestAppContext {
}
pub fn has_pending_prompt(&self, window_id: usize) -> bool {
- let window = self.window_mut(window_id);
+ let window = self.platform_window_mut(window_id);
let prompts = window.pending_prompts.borrow_mut();
!prompts.is_empty()
}
pub fn current_window_title(&self, window_id: usize) -> Option<String> {
- self.window_mut(window_id).title.clone()
+ self.platform_window_mut(window_id).title.clone()
}
pub fn simulate_window_close(&self, window_id: usize) -> bool {
- let handler = self.window_mut(window_id).should_close_handler.take();
+ let handler = self
+ .platform_window_mut(window_id)
+ .should_close_handler
+ .take();
if let Some(mut handler) = handler {
let should_close = handler();
- self.window_mut(window_id).should_close_handler = Some(handler);
+ self.platform_window_mut(window_id).should_close_handler = Some(handler);
should_close
} else {
false
@@ -275,47 +285,37 @@ impl TestAppContext {
}
pub fn simulate_window_resize(&self, window_id: usize, size: Vector2F) {
- let mut window = self.window_mut(window_id);
+ let mut window = self.platform_window_mut(window_id);
window.size = size;
let mut handlers = mem::take(&mut window.resize_handlers);
drop(window);
for handler in &mut handlers {
handler();
}
- self.window_mut(window_id).resize_handlers = handlers;
+ self.platform_window_mut(window_id).resize_handlers = handlers;
}
pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
- let mut handlers = BTreeMap::new();
- {
- let mut cx = self.cx.borrow_mut();
- for (window_id, (_, window)) in &mut cx.presenters_and_platform_windows {
- let window = window
- .as_any_mut()
- .downcast_mut::<platform::test::Window>()
- .unwrap();
- handlers.insert(
- *window_id,
- mem::take(&mut window.active_status_change_handlers),
- );
+ self.cx.borrow_mut().update(|cx| {
+ let other_window_ids = cx
+ .windows
+ .keys()
+ .filter(|window_id| Some(**window_id) != to_activate)
+ .copied()
+ .collect::<Vec<_>>();
+
+ for window_id in other_window_ids {
+ cx.window_changed_active_status(window_id, false)
}
- };
- let mut handlers = handlers.into_iter().collect::<Vec<_>>();
- handlers.sort_unstable_by_key(|(window_id, _)| Some(*window_id) == to_activate);
- for (window_id, mut window_handlers) in handlers {
- for window_handler in &mut window_handlers {
- window_handler(Some(window_id) == to_activate);
+ if let Some(to_activate) = to_activate {
+ cx.window_changed_active_status(to_activate, true)
}
-
- self.window_mut(window_id)
- .active_status_change_handlers
- .extend(window_handlers);
- }
+ });
}
pub fn is_window_edited(&self, window_id: usize) -> bool {
- self.window_mut(window_id).edited
+ self.platform_window_mut(window_id).edited
}
pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
@@ -338,13 +338,11 @@ impl TestAppContext {
self.assert_dropped(weak);
}
- fn window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
+ fn platform_window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
std::cell::RefMut::map(self.cx.borrow_mut(), |state| {
- let (_, window) = state
- .presenters_and_platform_windows
- .get_mut(&window_id)
- .unwrap();
+ let window = state.windows.get_mut(&window_id).unwrap();
let test_window = window
+ .platform_window
.as_any_mut()
.downcast_mut::<platform::test::Window>()
.unwrap();
@@ -406,6 +404,8 @@ impl ReadModelWith for TestAppContext {
}
impl UpdateView for TestAppContext {
+ type Output<S> = S;
+
fn update_view<T, S>(
&mut self,
handle: &ViewHandle<T>,
@@ -414,7 +414,10 @@ impl UpdateView for TestAppContext {
where
T: View,
{
- self.cx.borrow_mut().update_view(handle, update)
+ self.cx
+ .borrow_mut()
+ .update_window(handle.window_id, |cx| cx.update_view(handle, update))
+ .unwrap()
}
}
@@ -579,22 +582,20 @@ impl<T: View> ViewHandle<T> {
let timeout_duration = cx.condition_duration();
let mut cx = cx.cx.borrow_mut();
- let subscriptions = self.update(&mut *cx, |_, cx| {
- (
- cx.observe(self, {
- let mut tx = tx.clone();
- move |_, _, _| {
- tx.blocking_send(()).ok();
- }
- }),
- cx.subscribe(self, {
- let mut tx = tx.clone();
- move |_, _, _, _| {
- tx.blocking_send(()).ok();
- }
- }),
- )
- });
+ let subscriptions = (
+ cx.observe(self, {
+ let mut tx = tx.clone();
+ move |_, _| {
+ tx.blocking_send(()).ok();
+ }
+ }),
+ cx.subscribe(self, {
+ let mut tx = tx.clone();
+ move |_, _, _| {
+ tx.blocking_send(()).ok();
+ }
+ }),
+ );
let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
let handle = self.downgrade();
@@ -0,0 +1,1493 @@
+use crate::{
+ elements::AnyRootElement,
+ geometry::rect::RectF,
+ json::{self, ToJson},
+ keymap_matcher::{Binding, Keystroke, MatchResult},
+ platform::{
+ self, Appearance, CursorStyle, Event, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent,
+ MouseButton, MouseMovedEvent, PromptLevel, WindowBounds,
+ },
+ scene::{
+ CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover,
+ MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene,
+ },
+ text_layout::TextLayoutCache,
+ util::post_inc,
+ Action, AnyModelHandle, AnyView, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle,
+ AppContext, Drawable, Effect, Entity, Handle, ModelContext, ModelHandle, MouseRegion,
+ MouseRegionId, ParentId, ReadModel, ReadView, SceneBuilder, Subscription, UpdateModel,
+ UpdateView, UpgradeModelHandle, UpgradeViewHandle, View, ViewContext, ViewHandle,
+ WeakModelHandle, WeakViewHandle, WindowInvalidation,
+};
+use anyhow::{anyhow, bail, Result};
+use collections::{HashMap, HashSet};
+use pathfinder_geometry::vector::{vec2f, Vector2F};
+use postage::oneshot;
+use serde_json::json;
+use smallvec::SmallVec;
+use sqlez::{
+ bindable::{Bind, Column, StaticColumnCount},
+ statement::Statement,
+};
+use std::{
+ any::TypeId,
+ ops::{Deref, DerefMut, Range},
+};
+use util::ResultExt;
+use uuid::Uuid;
+
+use super::Reference;
+
+pub struct Window {
+ pub(crate) root_view: Option<AnyViewHandle>,
+ pub(crate) focused_view_id: Option<usize>,
+ pub(crate) is_active: bool,
+ pub(crate) is_fullscreen: bool,
+ pub(crate) invalidation: Option<WindowInvalidation>,
+ pub(crate) platform_window: Box<dyn platform::Window>,
+ pub(crate) rendered_views: HashMap<usize, Box<dyn AnyRootElement>>,
+ titlebar_height: f32,
+ appearance: Appearance,
+ cursor_regions: Vec<CursorRegion>,
+ mouse_regions: Vec<(MouseRegion, usize)>,
+ last_mouse_moved_event: Option<Event>,
+ pub(crate) hovered_region_ids: HashSet<MouseRegionId>,
+ pub(crate) clicked_region_ids: HashSet<MouseRegionId>,
+ pub(crate) clicked_button: Option<MouseButton>,
+ mouse_position: Vector2F,
+ text_layout_cache: TextLayoutCache,
+}
+
+impl Window {
+ pub fn new<V, F>(
+ window_id: usize,
+ platform_window: Box<dyn platform::Window>,
+ cx: &mut AppContext,
+ build_view: F,
+ ) -> Self
+ where
+ F: FnOnce(&mut ViewContext<V>) -> V,
+ V: View,
+ {
+ let titlebar_height = platform_window.titlebar_height();
+ let appearance = platform_window.appearance();
+ let mut window = Self {
+ root_view: None,
+ focused_view_id: None,
+ is_active: false,
+ invalidation: None,
+ is_fullscreen: false,
+ platform_window,
+ rendered_views: Default::default(),
+ cursor_regions: Default::default(),
+ mouse_regions: Default::default(),
+ text_layout_cache: TextLayoutCache::new(cx.font_system.clone()),
+ last_mouse_moved_event: None,
+ hovered_region_ids: Default::default(),
+ clicked_region_ids: Default::default(),
+ clicked_button: None,
+ mouse_position: vec2f(0., 0.),
+ titlebar_height,
+ appearance,
+ };
+
+ 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);
+ }
+ window.focused_view_id = Some(root_view.id());
+ window.root_view = Some(root_view.into_any());
+ window
+ }
+
+ pub fn root_view(&self) -> &AnyViewHandle {
+ &self
+ .root_view
+ .as_ref()
+ .expect("root_view called during window construction")
+ }
+}
+
+pub struct WindowContext<'a: 'b, 'b> {
+ pub(crate) app_context: Reference<'a, AppContext>,
+ pub(crate) window: Reference<'b, Window>,
+ pub(crate) window_id: usize,
+ pub(crate) refreshing: bool,
+ pub(crate) removed: bool,
+}
+
+impl Deref for WindowContext<'_, '_> {
+ type Target = AppContext;
+
+ fn deref(&self) -> &Self::Target {
+ &self.app_context
+ }
+}
+
+impl DerefMut for WindowContext<'_, '_> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.app_context
+ }
+}
+
+impl ReadModel for WindowContext<'_, '_> {
+ fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
+ self.app_context.read_model(handle)
+ }
+}
+
+impl UpdateModel for WindowContext<'_, '_> {
+ fn update_model<T: Entity, R>(
+ &mut self,
+ handle: &ModelHandle<T>,
+ update: &mut dyn FnMut(&mut T, &mut ModelContext<T>) -> R,
+ ) -> R {
+ self.app_context.update_model(handle, update)
+ }
+}
+
+impl ReadView for WindowContext<'_, '_> {
+ fn read_view<W: View>(&self, handle: &crate::ViewHandle<W>) -> &W {
+ self.app_context.read_view(handle)
+ }
+}
+
+impl UpdateView for WindowContext<'_, '_> {
+ type Output<S> = S;
+
+ fn update_view<T, S>(
+ &mut self,
+ handle: &ViewHandle<T>,
+ update: &mut dyn FnMut(&mut T, &mut ViewContext<T>) -> S,
+ ) -> S
+ where
+ T: View,
+ {
+ self.update_any_view(handle.view_id, |view, cx| {
+ let mut cx = ViewContext::mutable(cx, handle.view_id);
+ update(
+ view.as_any_mut()
+ .downcast_mut()
+ .expect("downcast is type safe"),
+ &mut cx,
+ )
+ })
+ .expect("view is already on the stack")
+ }
+}
+
+impl UpgradeModelHandle for WindowContext<'_, '_> {
+ fn upgrade_model_handle<T: Entity>(
+ &self,
+ handle: &WeakModelHandle<T>,
+ ) -> Option<ModelHandle<T>> {
+ self.app_context.upgrade_model_handle(handle)
+ }
+
+ fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
+ self.app_context.model_handle_is_upgradable(handle)
+ }
+
+ fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
+ self.app_context.upgrade_any_model_handle(handle)
+ }
+}
+
+impl UpgradeViewHandle for WindowContext<'_, '_> {
+ fn upgrade_view_handle<T: View>(&self, handle: &WeakViewHandle<T>) -> Option<ViewHandle<T>> {
+ self.app_context.upgrade_view_handle(handle)
+ }
+
+ fn upgrade_any_view_handle(&self, handle: &AnyWeakViewHandle) -> Option<AnyViewHandle> {
+ self.app_context.upgrade_any_view_handle(handle)
+ }
+}
+
+impl<'a: 'b, 'b> WindowContext<'a, 'b> {
+ pub fn mutable(
+ app_context: &'a mut AppContext,
+ window: &'b mut Window,
+ window_id: usize,
+ ) -> Self {
+ Self {
+ app_context: Reference::Mutable(app_context),
+ window: Reference::Mutable(window),
+ window_id,
+ refreshing: false,
+ removed: false,
+ }
+ }
+
+ pub fn immutable(app_context: &'a AppContext, window: &'b Window, window_id: usize) -> Self {
+ Self {
+ app_context: Reference::Immutable(app_context),
+ window: Reference::Immutable(window),
+ window_id,
+ refreshing: false,
+ removed: false,
+ }
+ }
+
+ pub fn remove_window(&mut self) {
+ self.removed = true;
+ }
+
+ pub fn window_id(&self) -> usize {
+ self.window_id
+ }
+
+ pub fn app_context(&mut self) -> &mut AppContext {
+ &mut self.app_context
+ }
+
+ pub fn root_view(&self) -> &AnyViewHandle {
+ self.window.root_view()
+ }
+
+ pub fn window_size(&self) -> Vector2F {
+ self.window.platform_window.content_size()
+ }
+
+ pub fn text_layout_cache(&self) -> &TextLayoutCache {
+ &self.window.text_layout_cache
+ }
+
+ pub(crate) fn update_any_view<F, T>(&mut self, view_id: usize, f: F) -> Option<T>
+ where
+ F: FnOnce(&mut dyn AnyView, &mut Self) -> T,
+ {
+ let window_id = self.window_id;
+ let mut view = self.views.remove(&(window_id, view_id))?;
+ let result = f(view.as_mut(), self);
+ self.views.insert((window_id, view_id), view);
+ Some(result)
+ }
+
+ pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut WindowContext)) {
+ let window_id = self.window_id;
+ self.app_context.defer(move |cx| {
+ cx.update_window(window_id, |cx| callback(cx));
+ })
+ }
+
+ pub fn update_global<T, F, U>(&mut self, update: F) -> U
+ where
+ T: 'static,
+ F: FnOnce(&mut T, &mut Self) -> U,
+ {
+ AppContext::update_global_internal(self, |global, cx| update(global, cx))
+ }
+
+ pub fn update_default_global<T, F, U>(&mut self, update: F) -> U
+ where
+ T: 'static + Default,
+ F: FnOnce(&mut T, &mut Self) -> U,
+ {
+ AppContext::update_default_global_internal(self, |global, cx| update(global, cx))
+ }
+
+ pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
+ where
+ E: Entity,
+ E::Event: 'static,
+ H: Handle<E>,
+ F: 'static + FnMut(H, &E::Event, &mut WindowContext),
+ {
+ self.subscribe_internal(handle, move |emitter, event, cx| {
+ callback(emitter, event, cx);
+ true
+ })
+ }
+
+ pub fn subscribe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
+ where
+ E: Entity,
+ E::Event: 'static,
+ H: Handle<E>,
+ F: 'static + FnMut(H, &E::Event, &mut WindowContext) -> bool,
+ {
+ let window_id = self.window_id;
+ self.app_context
+ .subscribe_internal(handle, move |emitter, event, cx| {
+ cx.update_window(window_id, |cx| callback(emitter, event, cx))
+ .unwrap_or(false)
+ })
+ }
+
+ pub(crate) fn observe_window_activation<F>(&mut self, callback: F) -> Subscription
+ where
+ F: 'static + FnMut(bool, &mut WindowContext) -> bool,
+ {
+ let window_id = self.window_id;
+ let subscription_id = post_inc(&mut self.next_subscription_id);
+ self.pending_effects
+ .push_back(Effect::WindowActivationObservation {
+ window_id,
+ subscription_id,
+ callback: Box::new(callback),
+ });
+ Subscription::WindowActivationObservation(
+ self.window_activation_observations
+ .subscribe(window_id, subscription_id),
+ )
+ }
+
+ pub(crate) fn observe_fullscreen<F>(&mut self, callback: F) -> Subscription
+ where
+ F: 'static + FnMut(bool, &mut WindowContext) -> bool,
+ {
+ let window_id = self.window_id;
+ let subscription_id = post_inc(&mut self.next_subscription_id);
+ self.pending_effects
+ .push_back(Effect::WindowFullscreenObservation {
+ window_id,
+ subscription_id,
+ callback: Box::new(callback),
+ });
+ Subscription::WindowActivationObservation(
+ self.window_activation_observations
+ .subscribe(window_id, subscription_id),
+ )
+ }
+
+ pub(crate) fn observe_window_bounds<F>(&mut self, callback: F) -> Subscription
+ where
+ F: 'static + FnMut(WindowBounds, Uuid, &mut WindowContext) -> bool,
+ {
+ let window_id = self.window_id;
+ let subscription_id = post_inc(&mut self.next_subscription_id);
+ self.pending_effects
+ .push_back(Effect::WindowBoundsObservation {
+ window_id,
+ subscription_id,
+ callback: Box::new(callback),
+ });
+ Subscription::WindowBoundsObservation(
+ self.window_bounds_observations
+ .subscribe(window_id, subscription_id),
+ )
+ }
+
+ pub fn observe_keystrokes<F>(&mut self, callback: F) -> Subscription
+ where
+ F: 'static
+ + FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut WindowContext) -> bool,
+ {
+ let window_id = self.window_id;
+ let subscription_id = post_inc(&mut self.next_subscription_id);
+ self.keystroke_observations
+ .add_callback(window_id, subscription_id, Box::new(callback));
+ Subscription::KeystrokeObservation(
+ self.keystroke_observations
+ .subscribe(window_id, subscription_id),
+ )
+ }
+
+ /// 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) = self.views.get(&(window_id, view_id)) {
+ if let Some(actions) = self.actions.get(&view.as_any().type_id()) {
+ if actions.contains_key(&action.as_any().type_id()) {
+ handler_depth = Some(i);
+ }
+ }
+ contexts.push(view.keymap_context(self));
+ }
+ }
+
+ 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(
+ &self,
+ view_id: usize,
+ ) -> impl Iterator<Item = (&'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();
+ for (depth, view_id) in self.ancestors(view_id).enumerate() {
+ if let Some(view) = self.views.get(&(window_id, view_id)) {
+ contexts.push(view.keymap_context(self));
+ let view_type = view.as_any().type_id();
+ if let Some(actions) = self.actions.get(&view_type) {
+ handler_depths_by_action_type.extend(
+ actions
+ .keys()
+ .copied()
+ .map(|action_type| (action_type, depth)),
+ );
+ }
+ }
+ }
+
+ handler_depths_by_action_type.extend(
+ self.global_actions
+ .keys()
+ .copied()
+ .map(|action_type| (action_type, contexts.len())),
+ );
+
+ self.action_deserializers
+ .iter()
+ .filter_map(move |(name, (type_id, deserialize))| {
+ if let Some(action_depth) = handler_depths_by_action_type.get(type_id).copied() {
+ Some((
+ *name,
+ deserialize("{}").ok()?,
+ self.keystroke_matcher
+ .bindings_for_action_type(*type_id)
+ .filter(|b| {
+ (0..=action_depth).any(|depth| b.match_context(&contexts[depth..]))
+ })
+ .collect(),
+ ))
+ } else {
+ None
+ }
+ })
+ }
+
+ pub 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
+ .ancestors(focused_view_id)
+ .filter_map(|view_id| {
+ self.views
+ .get(&(window_id, view_id))
+ .map(|view| (view_id, view.keymap_context(self)))
+ })
+ .collect();
+
+ let match_result = self
+ .keystroke_matcher
+ .push_keystroke(keystroke.clone(), dispatch_path);
+ let mut handled_by = None;
+
+ let keystroke_handled = match &match_result {
+ MatchResult::None => false,
+ 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())
+ {
+ self.keystroke_matcher.clear_pending();
+ handled_by = Some(action.boxed_clone());
+ break;
+ }
+ }
+ handled_by.is_some()
+ }
+ };
+
+ self.keystroke(
+ window_id,
+ keystroke.clone(),
+ handled_by,
+ match_result.clone(),
+ );
+ keystroke_handled
+ } else {
+ self.keystroke(window_id, keystroke.clone(), None, MatchResult::None);
+ false
+ }
+ }
+
+ pub 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;
+
+ // 1. Handle platform event. Keyboard events get dispatched immediately, while mouse events
+ // get mapped into the mouse-specific MouseEvent type.
+ // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?]
+ // -> Also updates mouse-related state
+ match &event {
+ Event::KeyDown(e) => return self.dispatch_key_down(e),
+
+ Event::KeyUp(e) => return self.dispatch_key_up(e),
+
+ Event::ModifiersChanged(e) => return self.dispatch_modifiers_changed(e),
+
+ Event::MouseDown(e) => {
+ // Click events are weird because they can be fired after a drag event.
+ // MDN says that browsers handle this by starting from 'the most
+ // specific ancestor element that contained both [positions]'
+ // So we need to store the overlapping regions on mouse down.
+
+ // If there is already clicked_button stored, don't replace it.
+ if self.window.clicked_button.is_none() {
+ self.window.clicked_region_ids = self
+ .window
+ .mouse_regions
+ .iter()
+ .filter_map(|(region, _)| {
+ if region.bounds.contains_point(e.position) {
+ Some(region.id())
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ self.window.clicked_button = Some(e.button);
+ }
+
+ mouse_events.push(MouseEvent::Down(MouseDown {
+ region: Default::default(),
+ platform_event: e.clone(),
+ }));
+ mouse_events.push(MouseEvent::DownOut(MouseDownOut {
+ region: Default::default(),
+ platform_event: e.clone(),
+ }));
+ }
+
+ Event::MouseUp(e) => {
+ // NOTE: The order of event pushes is important! MouseUp events MUST be fired
+ // before click events, and so the MouseUp events need to be pushed before
+ // MouseClick events.
+ mouse_events.push(MouseEvent::Up(MouseUp {
+ region: Default::default(),
+ platform_event: e.clone(),
+ }));
+ mouse_events.push(MouseEvent::UpOut(MouseUpOut {
+ region: Default::default(),
+ platform_event: e.clone(),
+ }));
+ mouse_events.push(MouseEvent::Click(MouseClick {
+ region: Default::default(),
+ platform_event: e.clone(),
+ }));
+ }
+
+ Event::MouseMoved(
+ e @ MouseMovedEvent {
+ position,
+ pressed_button,
+ ..
+ },
+ ) => {
+ let mut style_to_assign = CursorStyle::Arrow;
+ for region in self.window.cursor_regions.iter().rev() {
+ if region.bounds.contains_point(*position) {
+ style_to_assign = region.style;
+ break;
+ }
+ }
+
+ if self
+ .window
+ .platform_window
+ .is_topmost_for_position(*position)
+ {
+ self.platform().set_cursor_style(style_to_assign);
+ }
+
+ if !event_reused {
+ if pressed_button.is_some() {
+ mouse_events.push(MouseEvent::Drag(MouseDrag {
+ region: Default::default(),
+ prev_mouse_position: self.window.mouse_position,
+ platform_event: e.clone(),
+ }));
+ } else if let Some(clicked_button) = self.window.clicked_button {
+ // Mouse up event happened outside the current window. Simulate mouse up button event
+ let button_event = e.to_button_event(clicked_button);
+ mouse_events.push(MouseEvent::Up(MouseUp {
+ region: Default::default(),
+ platform_event: button_event.clone(),
+ }));
+ mouse_events.push(MouseEvent::UpOut(MouseUpOut {
+ region: Default::default(),
+ platform_event: button_event.clone(),
+ }));
+ mouse_events.push(MouseEvent::Click(MouseClick {
+ region: Default::default(),
+ platform_event: button_event.clone(),
+ }));
+ }
+
+ mouse_events.push(MouseEvent::Move(MouseMove {
+ region: Default::default(),
+ platform_event: e.clone(),
+ }));
+ }
+
+ mouse_events.push(MouseEvent::Hover(MouseHover {
+ region: Default::default(),
+ platform_event: e.clone(),
+ started: false,
+ }));
+ mouse_events.push(MouseEvent::MoveOut(MouseMoveOut {
+ region: Default::default(),
+ }));
+
+ self.window.last_mouse_moved_event = Some(event.clone());
+ }
+
+ Event::MouseExited(event) => {
+ // When the platform sends a MouseExited event, synthesize
+ // a MouseMoved event whose position is outside the window's
+ // bounds so that hover and cursor state can be updated.
+ return self.dispatch_event(
+ Event::MouseMoved(MouseMovedEvent {
+ position: event.position,
+ pressed_button: event.pressed_button,
+ modifiers: event.modifiers,
+ }),
+ event_reused,
+ );
+ }
+
+ Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel {
+ region: Default::default(),
+ platform_event: e.clone(),
+ })),
+ }
+
+ if let Some(position) = event.position() {
+ self.window.mouse_position = position;
+ }
+
+ // 2. Dispatch mouse events on regions
+ let mut any_event_handled = false;
+ for mut mouse_event in mouse_events {
+ let mut valid_regions = Vec::new();
+
+ // GPUI elements are arranged by z_index but sibling elements can register overlapping
+ // mouse regions. As such, hover events are only fired on overlapping elements which
+ // are at the same z-index as the topmost element which overlaps with the mouse.
+ match &mouse_event {
+ MouseEvent::Hover(_) => {
+ let mut highest_z_index = None;
+ let mouse_position = self.window.mouse_position.clone();
+ let window = &mut *self.window;
+ for (region, z_index) in window.mouse_regions.iter().rev() {
+ // Allow mouse regions to appear transparent to hovers
+ if !region.hoverable {
+ continue;
+ }
+
+ let contains_mouse = region.bounds.contains_point(mouse_position);
+
+ if contains_mouse && highest_z_index.is_none() {
+ highest_z_index = Some(z_index);
+ }
+
+ // This unwrap relies on short circuiting boolean expressions
+ // The right side of the && is only executed when contains_mouse
+ // is true, and we know above that when contains_mouse is true
+ // highest_z_index is set.
+ if contains_mouse && z_index == highest_z_index.unwrap() {
+ //Ensure that hover entrance events aren't sent twice
+ if window.hovered_region_ids.insert(region.id()) {
+ valid_regions.push(region.clone());
+ if region.notify_on_hover {
+ notified_views.insert(region.id().view_id());
+ }
+ }
+ } else {
+ // Ensure that hover exit events aren't sent twice
+ if window.hovered_region_ids.remove(®ion.id()) {
+ valid_regions.push(region.clone());
+ if region.notify_on_hover {
+ notified_views.insert(region.id().view_id());
+ }
+ }
+ }
+ }
+ }
+
+ MouseEvent::Down(_) | MouseEvent::Up(_) => {
+ for (region, _) in self.window.mouse_regions.iter().rev() {
+ if region.bounds.contains_point(self.window.mouse_position) {
+ valid_regions.push(region.clone());
+ if region.notify_on_click {
+ notified_views.insert(region.id().view_id());
+ }
+ }
+ }
+ }
+
+ MouseEvent::Click(e) => {
+ // Only raise click events if the released button is the same as the one stored
+ if self
+ .window
+ .clicked_button
+ .map(|clicked_button| clicked_button == e.button)
+ .unwrap_or(false)
+ {
+ // Clear clicked regions and clicked button
+ let clicked_region_ids = std::mem::replace(
+ &mut self.window.clicked_region_ids,
+ Default::default(),
+ );
+ self.window.clicked_button = None;
+
+ // Find regions which still overlap with the mouse since the last MouseDown happened
+ for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
+ if clicked_region_ids.contains(&mouse_region.id()) {
+ if mouse_region
+ .bounds
+ .contains_point(self.window.mouse_position)
+ {
+ valid_regions.push(mouse_region.clone());
+ }
+ }
+ }
+ }
+ }
+
+ MouseEvent::Drag(_) => {
+ for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
+ if self.window.clicked_region_ids.contains(&mouse_region.id()) {
+ valid_regions.push(mouse_region.clone());
+ }
+ }
+ }
+
+ MouseEvent::MoveOut(_) | MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => {
+ for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
+ // NOT contains
+ if !mouse_region
+ .bounds
+ .contains_point(self.window.mouse_position)
+ {
+ valid_regions.push(mouse_region.clone());
+ }
+ }
+ }
+
+ _ => {
+ for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
+ // Contains
+ if mouse_region
+ .bounds
+ .contains_point(self.window.mouse_position)
+ {
+ valid_regions.push(mouse_region.clone());
+ }
+ }
+ }
+ }
+
+ //3. Fire region events
+ let hovered_region_ids = self.window.hovered_region_ids.clone();
+ for valid_region in valid_regions.into_iter() {
+ let mut handled = false;
+ mouse_event.set_region(valid_region.bounds);
+ if let MouseEvent::Hover(e) = &mut mouse_event {
+ e.started = hovered_region_ids.contains(&valid_region.id())
+ }
+ // Handle Down events if the MouseRegion has a Click or Drag handler. This makes the api more intuitive as you would
+ // not expect a MouseRegion to be transparent to Down events if it also has a Click handler.
+ // This behavior can be overridden by adding a Down handler
+ if let MouseEvent::Down(e) = &mouse_event {
+ let has_click = valid_region
+ .handlers
+ .contains(MouseEvent::click_disc(), Some(e.button));
+ let has_drag = valid_region
+ .handlers
+ .contains(MouseEvent::drag_disc(), Some(e.button));
+ let has_down = valid_region
+ .handlers
+ .contains(MouseEvent::down_disc(), Some(e.button));
+ if !has_down && (has_click || has_drag) {
+ handled = true;
+ }
+ }
+
+ // `event_consumed` should only be true if there are any handlers for this event.
+ let mut event_consumed = handled;
+ if let Some(callbacks) = valid_region.handlers.get(&mouse_event.handler_key()) {
+ for callback in callbacks {
+ handled = true;
+ let view_id = valid_region.id().view_id();
+ self.update_any_view(view_id, |view, cx| {
+ handled = callback(mouse_event.clone(), view.as_any_mut(), cx, view_id);
+ });
+ event_consumed |= handled;
+ any_event_handled |= handled;
+ }
+ }
+
+ any_event_handled |= handled;
+
+ // For bubbling events, if the event was handled, don't continue dispatching.
+ // This only makes sense for local events which return false from is_capturable.
+ if event_consumed && mouse_event.is_capturable() {
+ break;
+ }
+ }
+ }
+
+ for view_id in notified_views {
+ self.notify_view(window_id, view_id);
+ }
+
+ any_event_handled
+ }
+
+ pub 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<_>>() {
+ if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
+ let handled = view.key_down(event, self, view_id);
+ self.views.insert((window_id, view_id), view);
+ if handled {
+ return true;
+ }
+ } else {
+ log::error!("view {} does not exist", view_id)
+ }
+ }
+ }
+
+ false
+ }
+
+ pub 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<_>>() {
+ if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
+ let handled = view.key_up(event, self, view_id);
+ self.views.insert((window_id, view_id), view);
+ if handled {
+ return true;
+ }
+ } else {
+ log::error!("view {} does not exist", view_id)
+ }
+ }
+ }
+
+ false
+ }
+
+ pub 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<_>>() {
+ if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
+ let handled = view.modifiers_changed(event, self, view_id);
+ self.views.insert((window_id, view_id), view);
+ if handled {
+ return true;
+ }
+ } else {
+ log::error!("view {} does not exist", view_id)
+ }
+ }
+ }
+
+ false
+ }
+
+ pub fn invalidate(&mut self, invalidation: &mut WindowInvalidation, appearance: Appearance) {
+ self.start_frame();
+ self.window.appearance = appearance;
+ for view_id in &invalidation.removed {
+ invalidation.updated.remove(view_id);
+ self.window.rendered_views.remove(view_id);
+ }
+ for view_id in &invalidation.updated {
+ let titlebar_height = self.window.titlebar_height;
+ let hovered_region_ids = self.window.hovered_region_ids.clone();
+ let clicked_region_ids = self
+ .window
+ .clicked_button
+ .map(|button| (self.window.clicked_region_ids.clone(), button));
+
+ let element = self
+ .render_view(RenderParams {
+ view_id: *view_id,
+ titlebar_height,
+ hovered_region_ids,
+ clicked_region_ids,
+ refreshing: false,
+ appearance,
+ })
+ .unwrap();
+ self.window.rendered_views.insert(*view_id, element);
+ }
+ }
+
+ pub fn render_view(&mut self, params: RenderParams) -> Result<Box<dyn AnyRootElement>> {
+ let window_id = self.window_id;
+ let view_id = params.view_id;
+ let mut view = self
+ .views
+ .remove(&(window_id, view_id))
+ .ok_or_else(|| anyhow!("view not found"))?;
+ let element = view.render(self, view_id);
+ self.views.insert((window_id, view_id), view);
+ Ok(element)
+ }
+
+ pub fn build_scene(&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(
+ &mut scene_builder,
+ Vector2F::zero(),
+ RectF::from_points(Vector2F::zero(), window_size),
+ self,
+ )?;
+ self.window
+ .rendered_views
+ .insert(root_view_id, rendered_root);
+
+ self.window.text_layout_cache.finish_frame();
+ let scene = scene_builder.build();
+ self.window.cursor_regions = scene.cursor_regions();
+ self.window.mouse_regions = scene.mouse_regions();
+
+ if self.window_is_active() {
+ if let Some(event) = self.window.last_mouse_moved_event.clone() {
+ self.dispatch_event(event, true);
+ }
+ }
+
+ Ok(scene)
+ }
+
+ pub fn rect_for_text_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
+ let root_view_id = self.window.root_view().id();
+ self.window
+ .rendered_views
+ .get(&root_view_id)?
+ .rect_for_text_range(range_utf16, self)
+ .log_err()
+ .flatten()
+ }
+
+ pub fn debug_elements(&self) -> Option<json::Value> {
+ let view = self.window.root_view();
+ Some(json!({
+ "root_view": view.debug_json(self),
+ "root_element": self.window.rendered_views.get(&view.id())
+ .and_then(|root_element| {
+ root_element.debug(self).log_err()
+ })
+ }))
+ }
+
+ pub fn set_window_title(&mut self, title: &str) {
+ self.window.platform_window.set_title(title);
+ }
+
+ pub fn set_window_edited(&mut self, edited: bool) {
+ self.window.platform_window.set_edited(edited);
+ }
+
+ pub fn is_topmost_window_for_position(&self, position: Vector2F) -> bool {
+ self.window
+ .platform_window
+ .is_topmost_for_position(position)
+ }
+
+ pub fn activate_window(&self) {
+ self.window.platform_window.activate();
+ }
+
+ pub fn window_is_active(&self) -> bool {
+ self.window.is_active
+ }
+
+ pub fn window_is_fullscreen(&self) -> bool {
+ self.window.is_fullscreen
+ }
+
+ pub(crate) fn handle_dispatch_action_from_effect(
+ &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| {
+ cx.update_any_view(view_id, |view, cx| {
+ let type_id = view.as_any().type_id();
+ if let Some((name, mut handlers)) = cx
+ .actions_mut(capture_phase)
+ .get_mut(&type_id)
+ .and_then(|h| h.remove_entry(&action.id()))
+ {
+ for handler in handlers.iter_mut().rev() {
+ cx.halt_action_dispatch = true;
+ handler(view, action, cx, view_id);
+ if cx.halt_action_dispatch {
+ break;
+ }
+ }
+ cx.actions_mut(capture_phase)
+ .get_mut(&type_id)
+ .unwrap()
+ .insert(name, handlers);
+ }
+ });
+
+ !cx.halt_action_dispatch
+ });
+ }
+
+ if !self.halt_action_dispatch {
+ self.halt_action_dispatch = self.dispatch_global_action_any(action);
+ }
+
+ self.pending_effects
+ .push_back(Effect::ActionDispatchNotification {
+ action_id: action.id(),
+ });
+ self.halt_action_dispatch
+ }
+
+ /// Returns an iterator over all of the view ids from the passed view up to the root of the window
+ /// Includes the passed view itself
+ pub(crate) fn ancestors(&self, mut view_id: usize) -> impl Iterator<Item = usize> + '_ {
+ 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))
+ {
+ view_id = *parent_id;
+ Some(view_id)
+ } else {
+ None
+ }
+ }))
+ }
+
+ /// 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.
+ fn visit_dispatch_path(
+ &mut self,
+ view_id: usize,
+ mut visit: impl FnMut(usize, bool, &mut WindowContext) -> bool,
+ ) {
+ // List of view ids from the leaf to the root of the window
+ let path = self.ancestors(view_id).collect::<Vec<_>>();
+
+ // Walk down from the root to the leaf calling visit with capture_phase = true
+ for view_id in path.iter().rev() {
+ if !visit(*view_id, true, self) {
+ return;
+ }
+ }
+
+ // Walk up from the leaf to the root calling visit with capture_phase = false
+ for view_id in path.iter() {
+ if !visit(*view_id, false, self) {
+ return;
+ }
+ }
+ }
+
+ pub fn focused_view_id(&self) -> Option<usize> {
+ 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()
+ }
+
+ pub fn window_appearance(&self) -> Appearance {
+ self.window.appearance
+ }
+
+ pub fn window_display_uuid(&self) -> Option<Uuid> {
+ self.window.platform_window.screen().display_uuid()
+ }
+
+ pub fn show_character_palette(&self) {
+ self.window.platform_window.show_character_palette();
+ }
+
+ pub fn minimize_window(&self) {
+ self.window.platform_window.minimize();
+ }
+
+ pub fn zoom_window(&self) {
+ self.window.platform_window.zoom();
+ }
+
+ pub fn toggle_full_screen(&self) {
+ self.window.platform_window.toggle_full_screen();
+ }
+
+ pub fn prompt(
+ &self,
+ level: PromptLevel,
+ msg: &str,
+ answers: &[&str],
+ ) -> oneshot::Receiver<usize> {
+ self.window.platform_window.prompt(level, msg, answers)
+ }
+
+ pub fn replace_root_view<V, F>(&mut self, build_root_view: F) -> ViewHandle<V>
+ where
+ 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();
+ 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>>
+ 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) {
+ self.views.insert((window_id, view_id), Box::new(view));
+ self.window
+ .invalidation
+ .get_or_insert_with(Default::default)
+ .updated
+ .insert(view_id);
+ Some(ViewHandle::new(window_id, view_id, &self.ref_counts))
+ } else {
+ self.parents.remove(&(window_id, view_id));
+ None
+ };
+ handle
+ }
+}
+
+pub struct RenderParams {
+ pub view_id: usize,
+ pub titlebar_height: f32,
+ pub hovered_region_ids: HashSet<MouseRegionId>,
+ pub clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
+ pub refreshing: bool,
+ pub appearance: Appearance,
+}
+
+#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
+pub enum Axis {
+ #[default]
+ Horizontal,
+ Vertical,
+}
+
+impl Axis {
+ pub fn invert(self) -> Self {
+ match self {
+ Self::Horizontal => Self::Vertical,
+ Self::Vertical => Self::Horizontal,
+ }
+ }
+
+ pub fn component(&self, point: Vector2F) -> f32 {
+ match self {
+ Self::Horizontal => point.x(),
+ Self::Vertical => point.y(),
+ }
+ }
+}
+
+impl ToJson for Axis {
+ fn to_json(&self) -> serde_json::Value {
+ match self {
+ Axis::Horizontal => json!("horizontal"),
+ Axis::Vertical => json!("vertical"),
+ }
+ }
+}
+
+impl StaticColumnCount for Axis {}
+impl Bind for Axis {
+ fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
+ match self {
+ Axis::Horizontal => "Horizontal",
+ Axis::Vertical => "Vertical",
+ }
+ .bind(statement, start_index)
+ }
+}
+
+impl Column for Axis {
+ fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
+ String::column(statement, start_index).and_then(|(axis_text, next_index)| {
+ Ok((
+ match axis_text.as_str() {
+ "Horizontal" => Axis::Horizontal,
+ "Vertical" => Axis::Vertical,
+ _ => bail!("Stored serialized item kind is incorrect"),
+ },
+ next_index,
+ ))
+ })
+ }
+}
+
+pub trait Vector2FExt {
+ fn along(self, axis: Axis) -> f32;
+}
+
+impl Vector2FExt for Vector2F {
+ fn along(self, axis: Axis) -> f32 {
+ match axis {
+ Axis::Horizontal => self.x(),
+ Axis::Vertical => self.y(),
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct SizeConstraint {
+ pub min: Vector2F,
+ pub max: Vector2F,
+}
+
+impl SizeConstraint {
+ pub fn new(min: Vector2F, max: Vector2F) -> Self {
+ Self { min, max }
+ }
+
+ pub fn strict(size: Vector2F) -> Self {
+ Self {
+ min: size,
+ max: size,
+ }
+ }
+
+ pub fn strict_along(axis: Axis, max: f32) -> Self {
+ match axis {
+ Axis::Horizontal => Self {
+ min: vec2f(max, 0.0),
+ max: vec2f(max, f32::INFINITY),
+ },
+ Axis::Vertical => Self {
+ min: vec2f(0.0, max),
+ max: vec2f(f32::INFINITY, max),
+ },
+ }
+ }
+
+ pub fn max_along(&self, axis: Axis) -> f32 {
+ match axis {
+ Axis::Horizontal => self.max.x(),
+ Axis::Vertical => self.max.y(),
+ }
+ }
+
+ pub fn min_along(&self, axis: Axis) -> f32 {
+ match axis {
+ Axis::Horizontal => self.min.x(),
+ Axis::Vertical => self.min.y(),
+ }
+ }
+
+ pub fn constrain(&self, size: Vector2F) -> Vector2F {
+ vec2f(
+ size.x().min(self.max.x()).max(self.min.x()),
+ size.y().min(self.max.y()).max(self.min.y()),
+ )
+ }
+}
+
+impl Default for SizeConstraint {
+ fn default() -> Self {
+ SizeConstraint {
+ min: Vector2F::zero(),
+ max: Vector2F::splat(f32::INFINITY),
+ }
+ }
+}
+
+impl ToJson for SizeConstraint {
+ fn to_json(&self) -> serde_json::Value {
+ json!({
+ "min": self.min.to_json(),
+ "max": self.max.to_json(),
+ })
+ }
+}
+
+pub struct ChildView {
+ view_id: usize,
+ view_name: &'static str,
+}
+
+impl ChildView {
+ pub fn new(view: &AnyViewHandle, cx: &AppContext) -> Self {
+ let view_name = cx.view_ui_name(view.window_id(), view.id()).unwrap();
+ Self {
+ view_id: view.id(),
+ view_name,
+ }
+ }
+}
+
+impl<V: View> Drawable<V> for ChildView {
+ type LayoutState = ();
+ type PaintState = ();
+
+ fn layout(
+ &mut self,
+ constraint: SizeConstraint,
+ _: &mut V,
+ cx: &mut ViewContext<V>,
+ ) -> (Vector2F, Self::LayoutState) {
+ if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
+ let size = rendered_view
+ .layout(constraint, cx)
+ .log_err()
+ .unwrap_or(Vector2F::zero());
+ cx.window.rendered_views.insert(self.view_id, rendered_view);
+ (size, ())
+ } else {
+ log::error!(
+ "layout called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
+ self.view_id,
+ self.view_name
+ );
+ (Vector2F::zero(), ())
+ }
+ }
+
+ fn paint(
+ &mut self,
+ scene: &mut SceneBuilder,
+ bounds: RectF,
+ visible_bounds: RectF,
+ _: &mut Self::LayoutState,
+ _: &mut V,
+ cx: &mut ViewContext<V>,
+ ) {
+ if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
+ rendered_view
+ .paint(scene, bounds.origin(), visible_bounds, cx)
+ .log_err();
+ cx.window.rendered_views.insert(self.view_id, rendered_view);
+ } else {
+ log::error!(
+ "paint called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
+ self.view_id,
+ self.view_name
+ );
+ }
+ }
+
+ fn rect_for_text_range(
+ &self,
+ range_utf16: Range<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &V,
+ cx: &ViewContext<V>,
+ ) -> Option<RectF> {
+ if let Some(rendered_view) = cx.window.rendered_views.get(&self.view_id) {
+ rendered_view
+ .rect_for_text_range(range_utf16, &cx.window_context)
+ .log_err()
+ .flatten()
+ } else {
+ log::error!(
+ "rect_for_text_range called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
+ self.view_id,
+ self.view_name
+ );
+ None
+ }
+ }
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &V,
+ cx: &ViewContext<V>,
+ ) -> serde_json::Value {
+ json!({
+ "type": "ChildView",
+ "view_id": self.view_id,
+ "bounds": bounds.to_json(),
+ "view": if let Some(view) = cx.views.get(&(cx.window_id, self.view_id)) {
+ view.debug_json(cx)
+ } else {
+ json!(null)
+ },
+ "child": if let Some(element) = cx.window.rendered_views.get(&self.view_id) {
+ element.debug(&cx.window_context).log_err().unwrap_or_else(|| json!(null))
+ } else {
+ json!(null)
+ }
+ })
+ }
+}
@@ -2,7 +2,7 @@ use std::{cell::RefCell, ops::Range, rc::Rc};
use pathfinder_geometry::rect::RectF;
-use crate::{platform::InputHandler, AnyView, AppContext};
+use crate::{platform::InputHandler, window::WindowContext, AnyView, AppContext};
pub struct WindowInputHandler {
pub app: Rc<RefCell<AppContext>>,
@@ -12,7 +12,7 @@ pub struct WindowInputHandler {
impl WindowInputHandler {
fn read_focused_view<T, F>(&self, f: F) -> Option<T>
where
- F: FnOnce(&dyn AnyView, &AppContext) -> T,
+ F: FnOnce(&dyn AnyView, &WindowContext) -> T,
{
// Input-related application hooks are sometimes called by the OS during
// a call to a window-manipulation API, like prompting the user for file
@@ -20,26 +20,26 @@ impl WindowInputHandler {
// InputHandler methods need to fail gracefully.
//
// See https://github.com/zed-industries/community/issues/444
- let app = self.app.try_borrow().ok()?;
-
- let view_id = app.focused_view_id(self.window_id)?;
- let view = app.views.get(&(self.window_id, view_id))?;
- let result = f(view.as_ref(), &app);
- Some(result)
+ let mut app = self.app.try_borrow_mut().ok()?;
+ app.update_window(self.window_id, |cx| {
+ let view_id = cx.window.focused_view_id?;
+ let view = cx.views.get(&(self.window_id, view_id))?;
+ let result = f(view.as_ref(), &cx);
+ Some(result)
+ })
+ .flatten()
}
fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
where
- F: FnOnce(usize, usize, &mut dyn AnyView, &mut AppContext) -> T,
+ F: FnOnce(&mut dyn AnyView, &mut WindowContext, usize) -> T,
{
let mut app = self.app.try_borrow_mut().ok()?;
- app.update(|app| {
- let view_id = app.focused_view_id(self.window_id)?;
- let mut view = app.views.remove(&(self.window_id, view_id))?;
- let result = f(self.window_id, view_id, view.as_mut(), &mut *app);
- app.views.insert((self.window_id, view_id), view);
- Some(result)
+ app.update_window(self.window_id, |cx| {
+ let view_id = cx.window.focused_view_id?;
+ cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
})
+ .flatten()
}
}
@@ -55,8 +55,8 @@ impl InputHandler for WindowInputHandler {
}
fn replace_text_in_range(&mut self, range: Option<Range<usize>>, text: &str) {
- self.update_focused_view(|window_id, view_id, view, cx| {
- view.replace_text_in_range(range, text, cx, window_id, view_id);
+ self.update_focused_view(|view, cx, view_id| {
+ view.replace_text_in_range(range, text, cx, view_id);
});
}
@@ -66,8 +66,8 @@ impl InputHandler for WindowInputHandler {
}
fn unmark_text(&mut self) {
- self.update_focused_view(|window_id, view_id, view, cx| {
- view.unmark_text(cx, window_id, view_id);
+ self.update_focused_view(|view, cx, view_id| {
+ view.unmark_text(cx, view_id);
});
}
@@ -77,22 +77,15 @@ impl InputHandler for WindowInputHandler {
new_text: &str,
new_selected_range: Option<Range<usize>>,
) {
- self.update_focused_view(|window_id, view_id, view, cx| {
- view.replace_and_mark_text_in_range(
- range,
- new_text,
- new_selected_range,
- cx,
- window_id,
- view_id,
- );
+ self.update_focused_view(|view, cx, view_id| {
+ view.replace_and_mark_text_in_range(range, new_text, new_selected_range, cx, view_id);
});
}
fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
- let app = self.app.borrow();
- let (presenter, _) = app.presenters_and_platform_windows.get(&self.window_id)?;
- let presenter = presenter.borrow();
- presenter.rect_for_text_range(range_utf16, &app)
+ self.app
+ .borrow_mut()
+ .update_window(self.window_id, |cx| cx.rect_for_text_range(range_utf16))
+ .flatten()
}
}
@@ -25,59 +25,47 @@ pub use self::{
keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*, resizable::*,
stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
};
+pub use crate::window::ChildView;
+
use self::{clipped::Clipped, expanded::Expanded};
-pub use crate::presenter::ChildView;
use crate::{
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
- json,
- presenter::MeasurementContext,
- Action, DebugContext, EventContext, LayoutContext, PaintContext, RenderContext, SizeConstraint,
- View,
+ json, Action, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle, WindowContext,
};
+use anyhow::{anyhow, Result};
use core::panic;
use json::ToJson;
use std::{
any::Any,
borrow::Cow,
- cell::RefCell,
+ marker::PhantomData,
mem,
ops::{Deref, DerefMut, Range},
- rc::Rc,
};
+use util::ResultExt;
-trait AnyElement {
- fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F;
- fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext);
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- cx: &MeasurementContext,
- ) -> Option<RectF>;
- fn debug(&self, cx: &DebugContext) -> serde_json::Value;
-
- fn size(&self) -> Vector2F;
- fn metadata(&self) -> Option<&dyn Any>;
-}
-
-pub trait Element {
+pub trait Drawable<V: View> {
type LayoutState;
type PaintState;
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState);
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState;
fn rect_for_text_range(
@@ -87,7 +75,8 @@ pub trait Element {
visible_bounds: RectF,
layout: &Self::LayoutState,
paint: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF>;
fn metadata(&self) -> Option<&dyn Any> {
@@ -99,104 +88,117 @@ pub trait Element {
bounds: RectF,
layout: &Self::LayoutState,
paint: &Self::PaintState,
- cx: &DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> serde_json::Value;
- fn boxed(self) -> ElementBox
+ fn boxed(self) -> Element<V>
where
Self: 'static + Sized,
{
- ElementBox(ElementRc {
+ Element {
+ drawable: Box::new(Lifecycle::Init { element: self }),
+ view_type: PhantomData,
name: None,
- element: Rc::new(RefCell::new(Lifecycle::Init { element: self })),
- })
+ }
+ }
+
+ fn into_root(self, cx: &ViewContext<V>) -> RootElement<V>
+ where
+ Self: 'static + Sized,
+ {
+ RootElement {
+ element: self.boxed(),
+ view: cx.handle().downgrade(),
+ }
}
- fn named(self, name: impl Into<Cow<'static, str>>) -> ElementBox
+ fn named(self, name: impl Into<Cow<'static, str>>) -> Element<V>
where
Self: 'static + Sized,
{
- ElementBox(ElementRc {
+ Element {
+ drawable: Box::new(Lifecycle::Init { element: self }),
+ view_type: PhantomData,
name: Some(name.into()),
- element: Rc::new(RefCell::new(Lifecycle::Init { element: self })),
- })
+ }
}
- fn constrained(self) -> ConstrainedBox
+ fn constrained(self) -> ConstrainedBox<V>
where
Self: 'static + Sized,
{
ConstrainedBox::new(self.boxed())
}
- fn aligned(self) -> Align
+ fn aligned(self) -> Align<V>
where
Self: 'static + Sized,
{
Align::new(self.boxed())
}
- fn clipped(self) -> Clipped
+ fn clipped(self) -> Clipped<V>
where
Self: 'static + Sized,
{
Clipped::new(self.boxed())
}
- fn contained(self) -> Container
+ fn contained(self) -> Container<V>
where
Self: 'static + Sized,
{
Container::new(self.boxed())
}
- fn expanded(self) -> Expanded
+ fn expanded(self) -> Expanded<V>
where
Self: 'static + Sized,
{
Expanded::new(self.boxed())
}
- fn flex(self, flex: f32, expanded: bool) -> FlexItem
+ fn flex(self, flex: f32, expanded: bool) -> FlexItem<V>
where
Self: 'static + Sized,
{
FlexItem::new(self.boxed()).flex(flex, expanded)
}
- fn flex_float(self) -> FlexItem
+ fn flex_float(self) -> FlexItem<V>
where
Self: 'static + Sized,
{
FlexItem::new(self.boxed()).float()
}
- fn with_tooltip<Tag: 'static, T: View>(
+ fn with_tooltip<Tag: 'static>(
self,
id: usize,
text: String,
action: Option<Box<dyn Action>>,
style: TooltipStyle,
- cx: &mut RenderContext<T>,
- ) -> Tooltip
+ cx: &mut ViewContext<V>,
+ ) -> Tooltip<V>
where
Self: 'static + Sized,
{
- Tooltip::new::<Tag, T>(id, text, action, style, self.boxed(), cx)
+ Tooltip::new::<Tag, V>(id, text, action, style, self.boxed(), cx)
}
- fn with_resize_handle<Tag: 'static, T: View>(
+ fn with_resize_handle<Tag: 'static>(
self,
element_id: usize,
side: Side,
handle_size: f32,
initial_size: f32,
- cx: &mut RenderContext<T>,
- ) -> Resizable
+ cx: &mut ViewContext<V>,
+ ) -> Resizable<V>
where
Self: 'static + Sized,
{
- Resizable::new::<Tag, T>(
+ Resizable::new::<Tag, V>(
self.boxed(),
element_id,
side,
@@ -207,44 +209,72 @@ pub trait Element {
}
}
-pub enum Lifecycle<T: Element> {
+trait AnyDrawable<V: View> {
+ fn layout(
+ &mut self,
+ constraint: SizeConstraint,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
+ ) -> Vector2F;
+
+ fn paint(
+ &mut self,
+ scene: &mut SceneBuilder,
+ origin: Vector2F,
+ visible_bounds: RectF,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
+ );
+
+ fn rect_for_text_range(
+ &self,
+ range_utf16: Range<usize>,
+ view: &V,
+ cx: &ViewContext<V>,
+ ) -> Option<RectF>;
+
+ fn debug(&self, view: &V, cx: &ViewContext<V>) -> serde_json::Value;
+
+ fn size(&self) -> Vector2F;
+
+ fn metadata(&self) -> Option<&dyn Any>;
+}
+
+enum Lifecycle<V: View, E: Drawable<V>> {
Empty,
Init {
- element: T,
+ element: E,
},
PostLayout {
- element: T,
+ element: E,
constraint: SizeConstraint,
size: Vector2F,
- layout: T::LayoutState,
+ layout: E::LayoutState,
},
PostPaint {
- element: T,
+ element: E,
constraint: SizeConstraint,
bounds: RectF,
visible_bounds: RectF,
- layout: T::LayoutState,
- paint: T::PaintState,
+ layout: E::LayoutState,
+ paint: E::PaintState,
},
}
-pub struct ElementBox(ElementRc);
-
-#[derive(Clone)]
-pub struct ElementRc {
- name: Option<Cow<'static, str>>,
- element: Rc<RefCell<dyn AnyElement>>,
-}
-
-impl<T: Element> AnyElement for Lifecycle<T> {
- fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F {
+impl<V: View, E: Drawable<V>> AnyDrawable<V> for Lifecycle<V, E> {
+ fn layout(
+ &mut self,
+ constraint: SizeConstraint,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
+ ) -> Vector2F {
let result;
*self = match mem::take(self) {
Lifecycle::Empty => unreachable!(),
Lifecycle::Init { mut element }
| Lifecycle::PostLayout { mut element, .. }
| Lifecycle::PostPaint { mut element, .. } => {
- let (size, layout) = element.layout(constraint, cx);
+ let (size, layout) = element.layout(constraint, view, cx);
debug_assert!(size.x().is_finite());
debug_assert!(size.y().is_finite());
@@ -260,7 +290,14 @@ impl<T: Element> AnyElement for Lifecycle<T> {
result
}
- fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext) {
+ fn paint(
+ &mut self,
+ scene: &mut SceneBuilder,
+ origin: Vector2F,
+ visible_bounds: RectF,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
+ ) {
*self = match mem::take(self) {
Lifecycle::PostLayout {
mut element,
@@ -269,7 +306,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
mut layout,
} => {
let bounds = RectF::new(origin, size);
- let paint = element.paint(bounds, visible_bounds, &mut layout, cx);
+ let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx);
Lifecycle::PostPaint {
element,
constraint,
@@ -287,7 +324,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
..
} => {
let bounds = RectF::new(origin, bounds.size());
- let paint = element.paint(bounds, visible_bounds, &mut layout, cx);
+ let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx);
Lifecycle::PostPaint {
element,
constraint,
@@ -307,7 +344,8 @@ impl<T: Element> AnyElement for Lifecycle<T> {
fn rect_for_text_range(
&self,
range_utf16: Range<usize>,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
if let Lifecycle::PostPaint {
element,
@@ -318,7 +356,15 @@ impl<T: Element> AnyElement for Lifecycle<T> {
..
} = self
{
- element.rect_for_text_range(range_utf16, *bounds, *visible_bounds, layout, paint, cx)
+ element.rect_for_text_range(
+ range_utf16,
+ *bounds,
+ *visible_bounds,
+ layout,
+ paint,
+ view,
+ cx,
+ )
} else {
None
}
@@ -341,7 +387,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
}
}
- fn debug(&self, cx: &DebugContext) -> serde_json::Value {
+ fn debug(&self, view: &V, cx: &ViewContext<V>) -> serde_json::Value {
match self {
Lifecycle::PostPaint {
element,
@@ -351,7 +397,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
layout,
paint,
} => {
- let mut value = element.debug(*bounds, layout, paint, cx);
+ let mut value = element.debug(*bounds, layout, paint, view, cx);
if let json::Value::Object(map) = &mut value {
let mut new_map: crate::json::Map<String, serde_json::Value> =
Default::default();
@@ -373,72 +419,64 @@ impl<T: Element> AnyElement for Lifecycle<T> {
}
}
-impl<T: Element> Default for Lifecycle<T> {
+impl<V: View, E: Drawable<V>> Default for Lifecycle<V, E> {
fn default() -> Self {
Self::Empty
}
}
-impl ElementBox {
- pub fn name(&self) -> Option<&str> {
- self.0.name.as_deref()
- }
-
- pub fn metadata<T: 'static>(&self) -> Option<&T> {
- let element = unsafe { &*self.0.element.as_ptr() };
- element.metadata().and_then(|m| m.downcast_ref())
- }
-}
-
-impl Clone for ElementBox {
- fn clone(&self) -> Self {
- ElementBox(self.0.clone())
- }
-}
-
-impl From<ElementBox> for ElementRc {
- fn from(val: ElementBox) -> Self {
- val.0
- }
+pub struct Element<V: View> {
+ drawable: Box<dyn AnyDrawable<V>>,
+ view_type: PhantomData<V>,
+ name: Option<Cow<'static, str>>,
}
-impl Deref for ElementBox {
- type Target = ElementRc;
-
- fn deref(&self) -> &Self::Target {
- &self.0
+impl<V: View> Element<V> {
+ pub fn name(&self) -> Option<&str> {
+ self.name.as_deref()
}
-}
-impl DerefMut for ElementBox {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.0
+ pub fn metadata<T: 'static>(&self) -> Option<&T> {
+ self.drawable
+ .metadata()
+ .and_then(|data| data.downcast_ref::<T>())
}
-}
-impl ElementRc {
- pub fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F {
- self.element.borrow_mut().layout(constraint, cx)
+ pub fn layout(
+ &mut self,
+ constraint: SizeConstraint,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
+ ) -> Vector2F {
+ self.drawable.layout(constraint, view, cx)
}
- pub fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext) {
- self.element.borrow_mut().paint(origin, visible_bounds, cx);
+ pub fn paint(
+ &mut self,
+ scene: &mut SceneBuilder,
+ origin: Vector2F,
+ visible_bounds: RectF,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
+ ) {
+ self.drawable.paint(scene, origin, visible_bounds, view, cx);
}
pub fn rect_for_text_range(
&self,
range_utf16: Range<usize>,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
- self.element.borrow().rect_for_text_range(range_utf16, cx)
+ self.drawable.rect_for_text_range(range_utf16, view, cx)
}
pub fn size(&self) -> Vector2F {
- self.element.borrow().size()
+ self.drawable.size()
}
- pub fn debug(&self, cx: &DebugContext) -> json::Value {
- let mut value = self.element.borrow().debug(cx);
+ pub fn debug(&self, view: &V, cx: &ViewContext<V>) -> json::Value {
+ let mut value = self.drawable.debug(view, cx);
if let Some(name) = &self.name {
if let json::Value::Object(map) = &mut value {
@@ -457,31 +495,248 @@ impl ElementRc {
T: 'static,
F: FnOnce(Option<&T>) -> R,
{
- let element = self.element.borrow();
- f(element.metadata().and_then(|m| m.downcast_ref()))
+ f(self.drawable.metadata().and_then(|m| m.downcast_ref()))
+ }
+}
+
+pub struct RootElement<V: View> {
+ element: Element<V>,
+ view: WeakViewHandle<V>,
+}
+
+impl<V: View> RootElement<V> {
+ pub fn new(element: Element<V>, view: WeakViewHandle<V>) -> Self {
+ Self { element, view }
+ }
+}
+
+pub trait Component<V: View> {
+ fn render(&self, view: &mut V, cx: &mut ViewContext<V>) -> Element<V>;
+}
+
+pub struct ComponentHost<V: View, C: Component<V>> {
+ component: C,
+ view_type: PhantomData<V>,
+}
+
+impl<V: View, C: Component<V>> Deref for ComponentHost<V, C> {
+ type Target = C;
+
+ fn deref(&self) -> &Self::Target {
+ &self.component
+ }
+}
+
+impl<V: View, C: Component<V>> DerefMut for ComponentHost<V, C> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.component
+ }
+}
+
+impl<V: View, C: Component<V>> Drawable<V> for ComponentHost<V, C> {
+ type LayoutState = Element<V>;
+ type PaintState = ();
+
+ fn layout(
+ &mut self,
+ constraint: SizeConstraint,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
+ ) -> (Vector2F, Element<V>) {
+ let mut element = self.component.render(view, cx);
+ let size = element.layout(constraint, view, cx);
+ (size, element)
+ }
+
+ fn paint(
+ &mut self,
+ scene: &mut SceneBuilder,
+ bounds: RectF,
+ visible_bounds: RectF,
+ element: &mut Element<V>,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
+ ) {
+ element.paint(scene, bounds.origin(), visible_bounds, view, cx);
+ }
+
+ fn rect_for_text_range(
+ &self,
+ range_utf16: Range<usize>,
+ _: RectF,
+ _: RectF,
+ element: &Element<V>,
+ _: &(),
+ view: &V,
+ cx: &ViewContext<V>,
+ ) -> Option<RectF> {
+ element.rect_for_text_range(range_utf16, view, cx)
+ }
+
+ fn debug(
+ &self,
+ _: RectF,
+ element: &Element<V>,
+ _: &(),
+ view: &V,
+ cx: &ViewContext<V>,
+ ) -> serde_json::Value {
+ element.debug(view, cx)
+ }
+}
+
+pub trait AnyRootElement {
+ fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F>;
+ fn paint(
+ &mut self,
+ scene: &mut SceneBuilder,
+ origin: Vector2F,
+ visible_bounds: RectF,
+ cx: &mut WindowContext,
+ ) -> Result<()>;
+ fn rect_for_text_range(
+ &self,
+ range_utf16: Range<usize>,
+ cx: &WindowContext,
+ ) -> Result<Option<RectF>>;
+ fn debug(&self, cx: &WindowContext) -> Result<serde_json::Value>;
+ fn name(&self) -> Option<&str>;
+}
+
+impl<V: View> AnyRootElement for RootElement<V> {
+ fn layout(&mut self, constraint: SizeConstraint, 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)))
+ }
+
+ fn paint(
+ &mut self,
+ scene: &mut SceneBuilder,
+ origin: Vector2F,
+ visible_bounds: RectF,
+ cx: &mut WindowContext,
+ ) -> Result<()> {
+ let view = self
+ .view
+ .upgrade(cx)
+ .ok_or_else(|| anyhow!("paint called on a root element for a dropped view"))?;
+
+ view.update(cx, |view, cx| {
+ self.element.paint(scene, origin, visible_bounds, view, cx);
+ Ok(())
+ })
+ }
+
+ fn rect_for_text_range(
+ &self,
+ range_utf16: Range<usize>,
+ cx: &WindowContext,
+ ) -> Result<Option<RectF>> {
+ let view = self.view.upgrade(cx).ok_or_else(|| {
+ anyhow!("rect_for_text_range called on a root element for a dropped view")
+ })?;
+ let view = view.read(cx);
+ let view_context = ViewContext::immutable(cx, self.view.id());
+ Ok(self
+ .element
+ .rect_for_text_range(range_utf16, view, &view_context))
+ }
+
+ fn debug(&self, cx: &WindowContext) -> Result<serde_json::Value> {
+ let view = self
+ .view
+ .upgrade(cx)
+ .ok_or_else(|| anyhow!("debug called on a root element for a dropped view"))?;
+ let view = view.read(cx);
+ let view_context = ViewContext::immutable(cx, self.view.id());
+ Ok(self.element.debug(view, &view_context))
+ }
+
+ fn name(&self) -> Option<&str> {
+ self.element.name()
+ }
+}
+
+impl<V: View, R: View> Drawable<V> for RootElement<R> {
+ type LayoutState = ();
+ type PaintState = ();
+
+ fn layout(
+ &mut self,
+ constraint: SizeConstraint,
+ _view: &mut V,
+ cx: &mut ViewContext<V>,
+ ) -> (Vector2F, ()) {
+ let size = AnyRootElement::layout(self, constraint, cx)
+ .log_err()
+ .unwrap_or_else(|| Vector2F::zero());
+ (size, ())
+ }
+
+ fn paint(
+ &mut self,
+ scene: &mut SceneBuilder,
+ bounds: RectF,
+ visible_bounds: RectF,
+ _layout: &mut Self::LayoutState,
+ _view: &mut V,
+ cx: &mut ViewContext<V>,
+ ) {
+ AnyRootElement::paint(self, scene, bounds.origin(), visible_bounds, cx).log_err();
+ }
+
+ fn rect_for_text_range(
+ &self,
+ range_utf16: Range<usize>,
+ _bounds: RectF,
+ _visible_bounds: RectF,
+ _layout: &Self::LayoutState,
+ _paint: &Self::PaintState,
+ _view: &V,
+ cx: &ViewContext<V>,
+ ) -> Option<RectF> {
+ AnyRootElement::rect_for_text_range(self, range_utf16, cx)
+ .log_err()
+ .flatten()
+ }
+
+ fn debug(
+ &self,
+ _bounds: RectF,
+ _layout: &Self::LayoutState,
+ _paint: &Self::PaintState,
+ _view: &V,
+ cx: &ViewContext<V>,
+ ) -> serde_json::Value {
+ AnyRootElement::debug(self, cx)
+ .log_err()
+ .unwrap_or_default()
}
}
-pub trait ParentElement<'a>: Extend<ElementBox> + Sized {
- fn add_children(&mut self, children: impl IntoIterator<Item = ElementBox>) {
+pub trait ParentElement<'a, V: View>: Extend<Element<V>> + Sized {
+ fn add_children(&mut self, children: impl IntoIterator<Item = Element<V>>) {
self.extend(children);
}
- fn add_child(&mut self, child: ElementBox) {
+ fn add_child(&mut self, child: Element<V>) {
self.add_children(Some(child));
}
- fn with_children(mut self, children: impl IntoIterator<Item = ElementBox>) -> Self {
+ fn with_children(mut self, children: impl IntoIterator<Item = Element<V>>) -> Self {
self.add_children(children);
self
}
- fn with_child(self, child: ElementBox) -> Self {
+ fn with_child(self, child: Element<V>) -> Self {
self.with_children(Some(child))
}
}
-impl<'a, T> ParentElement<'a> for T where T: Extend<ElementBox> {}
+impl<'a, V: View, T> ParentElement<'a, V> for T where T: Extend<Element<V>> {}
pub fn constrain_size_preserving_aspect_ratio(max_size: Vector2F, size: Vector2F) -> Vector2F {
if max_size.x().is_infinite() && max_size.y().is_infinite() {
@@ -1,20 +1,18 @@
use crate::{
geometry::{rect::RectF, vector::Vector2F},
- json,
- presenter::MeasurementContext,
- DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+ json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
};
use json::ToJson;
use serde_json::json;
-pub struct Align {
- child: ElementBox,
+pub struct Align<V: View> {
+ child: Element<V>,
alignment: Vector2F,
}
-impl Align {
- pub fn new(child: ElementBox) -> Self {
+impl<V: View> Align<V> {
+ pub fn new(child: Element<V>) -> Self {
Self {
child,
alignment: Vector2F::zero(),
@@ -42,18 +40,19 @@ impl Align {
}
}
-impl Element for Align {
+impl<V: View> Drawable<V> for Align<V> {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
mut constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.max;
constraint.min = Vector2F::zero();
- let child_size = self.child.layout(constraint, cx);
+ let child_size = self.child.layout(constraint, view, cx);
if size.x().is_infinite() {
size.set_x(child_size.x());
}
@@ -65,10 +64,12 @@ impl Element for Align {
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
let my_center = bounds.size() / 2.;
let my_target = my_center + my_center * self.alignment;
@@ -77,8 +78,10 @@ impl Element for Align {
let child_target = child_center + child_center * self.alignment;
self.child.paint(
+ scene,
bounds.origin() - (child_target - my_target),
visible_bounds,
+ view,
cx,
);
}
@@ -90,9 +93,10 @@ impl Element for Align {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, cx)
+ self.child.rect_for_text_range(range_utf16, view, cx)
}
fn debug(
@@ -100,13 +104,14 @@ impl Element for Align {
bounds: pathfinder_geometry::rect::RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> json::Value {
json!({
"type": "Align",
"bounds": bounds.to_json(),
"alignment": self.alignment.to_json(),
- "child": self.child.debug(cx),
+ "child": self.child.debug(view, cx),
})
}
}
@@ -1,8 +1,9 @@
-use super::Element;
+use std::marker::PhantomData;
+
+use super::Drawable;
use crate::{
json::{self, json},
- presenter::MeasurementContext,
- DebugContext, PaintContext,
+ SceneBuilder, View, ViewContext,
};
use json::ToJson;
use pathfinder_geometry::{
@@ -10,20 +11,21 @@ use pathfinder_geometry::{
vector::{vec2f, Vector2F},
};
-pub struct Canvas<F>(F);
+pub struct Canvas<V, F>(F, PhantomData<V>);
-impl<F> Canvas<F>
+impl<V, F> Canvas<V, F>
where
- F: FnMut(RectF, RectF, &mut PaintContext),
+ V: View,
+ F: FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
{
pub fn new(f: F) -> Self {
- Self(f)
+ Self(f, PhantomData)
}
}
-impl<F> Element for Canvas<F>
+impl<V: View, F> Drawable<V> for Canvas<V, F>
where
- F: FnMut(RectF, RectF, &mut PaintContext),
+ F: FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
{
type LayoutState = ();
type PaintState = ();
@@ -31,7 +33,8 @@ where
fn layout(
&mut self,
constraint: crate::SizeConstraint,
- _: &mut crate::LayoutContext,
+ _: &mut V,
+ _: &mut crate::ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let x = if constraint.max.x().is_finite() {
constraint.max.x()
@@ -48,12 +51,14 @@ where
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
- self.0(bounds, visible_bounds, cx)
+ self.0(scene, bounds, visible_bounds, view, cx)
}
fn rect_for_text_range(
@@ -63,7 +68,8 @@ where
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &MeasurementContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> Option<RectF> {
None
}
@@ -73,7 +79,8 @@ where
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &DebugContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> json::Value {
json!({"type": "Canvas", "bounds": bounds.to_json()})
}
@@ -3,43 +3,44 @@ use std::ops::Range;
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use serde_json::json;
-use crate::{
- json, DebugContext, Element, ElementBox, LayoutContext, MeasurementContext, PaintContext,
- SizeConstraint,
-};
+use crate::{json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext};
-pub struct Clipped {
- child: ElementBox,
+pub struct Clipped<V: View> {
+ child: Element<V>,
}
-impl Clipped {
- pub fn new(child: ElementBox) -> Self {
+impl<V: View> Clipped<V> {
+ pub fn new(child: Element<V>) -> Self {
Self { child }
}
}
-impl Element for Clipped {
+impl<V: View> Drawable<V> for Clipped<V> {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
- (self.child.layout(constraint, cx), ())
+ (self.child.layout(constraint, view, cx), ())
}
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
- cx.scene.push_layer(Some(bounds));
- self.child.paint(bounds.origin(), visible_bounds, cx);
- cx.scene.pop_layer();
+ scene.paint_layer(Some(bounds), |scene| {
+ self.child
+ .paint(scene, bounds.origin(), visible_bounds, view, cx)
+ })
}
fn rect_for_text_range(
@@ -49,9 +50,10 @@ impl Element for Clipped {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, cx)
+ self.child.rect_for_text_range(range_utf16, view, cx)
}
fn debug(
@@ -59,11 +61,12 @@ impl Element for Clipped {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> json::Value {
json!({
"type": "Clipped",
- "child": self.child.debug(cx)
+ "child": self.child.debug(view, cx)
})
}
}
@@ -5,22 +5,20 @@ use serde_json::json;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
- json,
- presenter::MeasurementContext,
- DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+ json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
};
-pub struct ConstrainedBox {
- child: ElementBox,
- constraint: Constraint,
+pub struct ConstrainedBox<V: View> {
+ child: Element<V>,
+ constraint: Constraint<V>,
}
-pub enum Constraint {
+pub enum Constraint<V: View> {
Static(SizeConstraint),
- Dynamic(Box<dyn FnMut(SizeConstraint, &mut LayoutContext) -> SizeConstraint>),
+ Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint>),
}
-impl ToJson for Constraint {
+impl<V: View> ToJson for Constraint<V> {
fn to_json(&self) -> serde_json::Value {
match self {
Constraint::Static(constraint) => constraint.to_json(),
@@ -29,8 +27,8 @@ impl ToJson for Constraint {
}
}
-impl ConstrainedBox {
- pub fn new(child: ElementBox) -> Self {
+impl<V: View> ConstrainedBox<V> {
+ pub fn new(child: Element<V>) -> Self {
Self {
child,
constraint: Constraint::Static(Default::default()),
@@ -39,7 +37,7 @@ impl ConstrainedBox {
pub fn dynamically(
mut self,
- constraint: impl 'static + FnMut(SizeConstraint, &mut LayoutContext) -> SizeConstraint,
+ constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint,
) -> Self {
self.constraint = Constraint::Dynamic(Box::new(constraint));
self
@@ -120,41 +118,48 @@ impl ConstrainedBox {
fn constraint(
&mut self,
input_constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> SizeConstraint {
match &mut self.constraint {
Constraint::Static(constraint) => *constraint,
- Constraint::Dynamic(compute_constraint) => compute_constraint(input_constraint, cx),
+ Constraint::Dynamic(compute_constraint) => {
+ compute_constraint(input_constraint, view, cx)
+ }
}
}
}
-impl Element for ConstrainedBox {
+impl<V: View> Drawable<V> for ConstrainedBox<V> {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
mut parent_constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
- let constraint = self.constraint(parent_constraint, cx);
+ let constraint = self.constraint(parent_constraint, view, cx);
parent_constraint.min = parent_constraint.min.max(constraint.min);
parent_constraint.max = parent_constraint.max.min(constraint.max);
parent_constraint.max = parent_constraint.max.max(parent_constraint.min);
- let size = self.child.layout(parent_constraint, cx);
+ let size = self.child.layout(parent_constraint, view, cx);
(size, ())
}
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
- cx.paint_layer(Some(visible_bounds), |cx| {
- self.child.paint(bounds.origin(), visible_bounds, cx);
+ scene.paint_layer(Some(visible_bounds), |scene| {
+ self.child
+ .paint(scene, bounds.origin(), visible_bounds, view, cx);
})
}
@@ -165,9 +170,10 @@ impl Element for ConstrainedBox {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, cx)
+ self.child.rect_for_text_range(range_utf16, view, cx)
}
fn debug(
@@ -175,8 +181,9 @@ impl Element for ConstrainedBox {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> json::Value {
- json!({"type": "ConstrainedBox", "assigned_constraint": self.constraint.to_json(), "child": self.child.debug(cx)})
+ json!({"type": "ConstrainedBox", "assigned_constraint": self.constraint.to_json(), "child": self.child.debug(view, cx)})
}
}
@@ -9,9 +9,8 @@ use crate::{
},
json::ToJson,
platform::CursorStyle,
- presenter::MeasurementContext,
scene::{self, Border, CursorRegion, Quad},
- Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+ Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
};
use serde::Deserialize;
use serde_json::json;
@@ -36,13 +35,13 @@ pub struct ContainerStyle {
pub cursor: Option<CursorStyle>,
}
-pub struct Container {
- child: ElementBox,
+pub struct Container<V: View> {
+ child: Element<V>,
style: ContainerStyle,
}
-impl Container {
- pub fn new(child: ElementBox) -> Self {
+impl<V: View> Container<V> {
+ pub fn new(child: Element<V>) -> Self {
Self {
child,
style: Default::default(),
@@ -185,14 +184,15 @@ impl Container {
}
}
-impl Element for Container {
+impl<V: View> Drawable<V> for Container<V> {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut size_buffer = self.margin_size() + self.padding_size();
if !self.style.border.overlay {
@@ -202,16 +202,18 @@ impl Element for Container {
min: (constraint.min - size_buffer).max(Vector2F::zero()),
max: (constraint.max - size_buffer).max(Vector2F::zero()),
};
- let child_size = self.child.layout(child_constraint, cx);
+ let child_size = self.child.layout(child_constraint, view, cx);
(child_size + size_buffer, ())
}
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
let quad_bounds = RectF::from_points(
bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
@@ -219,7 +221,7 @@ impl Element for Container {
);
if let Some(shadow) = self.style.shadow.as_ref() {
- cx.scene.push_shadow(scene::Shadow {
+ scene.push_shadow(scene::Shadow {
bounds: quad_bounds + shadow.offset,
corner_radius: self.style.corner_radius,
sigma: shadow.blur,
@@ -229,7 +231,7 @@ impl Element for Container {
if let Some(hit_bounds) = quad_bounds.intersection(visible_bounds) {
if let Some(style) = self.style.cursor {
- cx.scene.push_cursor_region(CursorRegion {
+ scene.push_cursor_region(CursorRegion {
bounds: hit_bounds,
style,
});
@@ -240,25 +242,26 @@ impl Element for Container {
quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);
if self.style.border.overlay {
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: quad_bounds,
background: self.style.background_color,
border: Default::default(),
corner_radius: self.style.corner_radius,
});
- self.child.paint(child_origin, visible_bounds, cx);
+ self.child
+ .paint(scene, child_origin, visible_bounds, view, cx);
- cx.scene.push_layer(None);
- cx.scene.push_quad(Quad {
+ scene.push_layer(None);
+ scene.push_quad(Quad {
bounds: quad_bounds,
background: self.style.overlay_color,
border: self.style.border,
corner_radius: self.style.corner_radius,
});
- cx.scene.pop_layer();
+ scene.pop_layer();
} else {
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: quad_bounds,
background: self.style.background_color,
border: self.style.border,
@@ -270,17 +273,18 @@ impl Element for Container {
self.style.border.left_width(),
self.style.border.top_width(),
);
- self.child.paint(child_origin, visible_bounds, cx);
+ self.child
+ .paint(scene, child_origin, visible_bounds, view, cx);
if self.style.overlay_color.is_some() {
- cx.scene.push_layer(None);
- cx.scene.push_quad(Quad {
+ scene.push_layer(None);
+ scene.push_quad(Quad {
bounds: quad_bounds,
background: self.style.overlay_color,
border: Default::default(),
corner_radius: 0.,
});
- cx.scene.pop_layer();
+ scene.pop_layer();
}
}
}
@@ -292,9 +296,10 @@ impl Element for Container {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, cx)
+ self.child.rect_for_text_range(range_utf16, view, cx)
}
fn debug(
@@ -302,13 +307,14 @@ impl Element for Container {
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &crate::DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> serde_json::Value {
json!({
"type": "Container",
"bounds": bounds.to_json(),
"details": self.style.to_json(),
- "child": self.child.debug(cx),
+ "child": self.child.debug(view, cx),
})
}
}
@@ -6,10 +6,9 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
- presenter::MeasurementContext,
- DebugContext,
+ SceneBuilder, View, ViewContext,
};
-use crate::{Element, LayoutContext, PaintContext, SizeConstraint};
+use crate::{Drawable, SizeConstraint};
#[derive(Default)]
pub struct Empty {
@@ -27,14 +26,15 @@ impl Empty {
}
}
-impl Element for Empty {
+impl<V: View> Drawable<V> for Empty {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- _: &mut LayoutContext,
+ _: &mut V,
+ _: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let x = if constraint.max.x().is_finite() && !self.collapsed {
constraint.max.x()
@@ -52,10 +52,12 @@ impl Element for Empty {
fn paint(
&mut self,
+ _: &mut SceneBuilder,
_: RectF,
_: RectF,
_: &mut Self::LayoutState,
- _: &mut PaintContext,
+ _: &mut V,
+ _: &mut ViewContext<V>,
) -> Self::PaintState {
}
@@ -66,7 +68,8 @@ impl Element for Empty {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &MeasurementContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> Option<RectF> {
None
}
@@ -76,7 +79,8 @@ impl Element for Empty {
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &DebugContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> serde_json::Value {
json!({
"type": "Empty",
@@ -2,20 +2,18 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
- json,
- presenter::MeasurementContext,
- DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+ json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
};
use serde_json::json;
-pub struct Expanded {
- child: ElementBox,
+pub struct Expanded<V: View> {
+ child: Element<V>,
full_width: bool,
full_height: bool,
}
-impl Expanded {
- pub fn new(child: ElementBox) -> Self {
+impl<V: View> Expanded<V> {
+ pub fn new(child: Element<V>) -> Self {
Self {
child,
full_width: true,
@@ -36,14 +34,15 @@ impl Expanded {
}
}
-impl Element for Expanded {
+impl<V: View> Drawable<V> for Expanded<V> {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
mut constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
if self.full_width {
constraint.min.set_x(constraint.max.x());
@@ -51,18 +50,21 @@ impl Element for Expanded {
if self.full_height {
constraint.min.set_y(constraint.max.y());
}
- let size = self.child.layout(constraint, cx);
+ let size = self.child.layout(constraint, view, cx);
(size, ())
}
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
- self.child.paint(bounds.origin(), visible_bounds, cx);
+ self.child
+ .paint(scene, bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(
@@ -72,9 +74,10 @@ impl Element for Expanded {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, cx)
+ self.child.rect_for_text_range(range_utf16, view, cx)
}
fn debug(
@@ -82,13 +85,14 @@ impl Element for Expanded {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> json::Value {
json!({
"type": "Expanded",
"full_width": self.full_width,
"full_height": self.full_height,
- "child": self.child.debug(cx)
+ "child": self.child.debug(view, cx)
})
}
}
@@ -2,9 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
use crate::{
json::{self, ToJson, Value},
- presenter::MeasurementContext,
- Axis, DebugContext, Element, ElementBox, ElementStateHandle, LayoutContext, PaintContext,
- RenderContext, SizeConstraint, Vector2FExt, View,
+ Axis, Drawable, Element, ElementStateHandle, SceneBuilder, SizeConstraint, Vector2FExt, View,
+ ViewContext,
};
use pathfinder_geometry::{
rect::RectF,
@@ -18,14 +17,14 @@ struct ScrollState {
scroll_position: Cell<f32>,
}
-pub struct Flex {
+pub struct Flex<V: View> {
axis: Axis,
- children: Vec<ElementBox>,
+ children: Vec<Element<V>>,
scroll_state: Option<(ElementStateHandle<Rc<ScrollState>>, usize)>,
child_alignment: f32,
}
-impl Flex {
+impl<V: View> Flex<V> {
pub fn new(axis: Axis) -> Self {
Self {
axis,
@@ -52,15 +51,14 @@ impl Flex {
self
}
- pub fn scrollable<Tag, V>(
+ pub fn scrollable<Tag>(
mut self,
element_id: usize,
scroll_to: Option<usize>,
- cx: &mut RenderContext<V>,
+ cx: &mut ViewContext<V>,
) -> Self
where
Tag: 'static,
- V: View,
{
let scroll_state = cx.default_element_state::<Tag, Rc<ScrollState>>(element_id);
scroll_state.read(cx).scroll_to.set(scroll_to);
@@ -75,7 +73,8 @@ impl Flex {
remaining_space: &mut f32,
remaining_flex: &mut f32,
cross_axis_max: &mut f32,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) {
let cross_axis = self.axis.invert();
for child in &mut self.children {
@@ -102,7 +101,7 @@ impl Flex {
vec2f(constraint.max.x(), child_max),
),
};
- let child_size = child.layout(child_constraint, cx);
+ let child_size = child.layout(child_constraint, view, cx);
*remaining_space -= child_size.along(self.axis);
*remaining_flex -= flex;
*cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
@@ -112,20 +111,21 @@ impl Flex {
}
}
-impl Extend<ElementBox> for Flex {
- fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
+impl<V: View> Extend<Element<V>> for Flex<V> {
+ fn extend<T: IntoIterator<Item = Element<V>>>(&mut self, children: T) {
self.children.extend(children);
}
}
-impl Element for Flex {
+impl<V: View> Drawable<V> for Flex<V> {
type LayoutState = f32;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut total_flex = None;
let mut fixed_space = 0.0;
@@ -150,7 +150,7 @@ impl Element for Flex {
vec2f(constraint.max.x(), INFINITY),
),
};
- let size = child.layout(child_constraint, cx);
+ let size = child.layout(child_constraint, view, cx);
fixed_space += size.along(self.axis);
cross_axis_max = cross_axis_max.max(size.along(cross_axis));
}
@@ -168,6 +168,7 @@ impl Element for Flex {
&mut remaining_space,
&mut remaining_flex,
&mut cross_axis_max,
+ view,
cx,
);
self.layout_flex_children(
@@ -176,6 +177,7 @@ impl Element for Flex {
&mut remaining_space,
&mut remaining_flex,
&mut cross_axis_max,
+ view,
cx,
);
@@ -247,26 +249,28 @@ impl Element for Flex {
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
remaining_space: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
let mut remaining_space = *remaining_space;
let overflowing = remaining_space < 0.;
if overflowing {
- cx.scene.push_layer(Some(visible_bounds));
+ scene.push_layer(Some(visible_bounds));
}
if let Some(scroll_state) = &self.scroll_state {
- cx.scene.push_mouse_region(
+ scene.push_mouse_region(
crate::MouseRegion::new::<Self>(scroll_state.1, 0, bounds)
.on_scroll({
let scroll_state = scroll_state.0.read(cx).clone();
let axis = self.axis;
- move |e, cx| {
+ move |e, _: &mut V, cx| {
if remaining_space < 0. {
let scroll_delta = e.delta.raw();
@@ -294,7 +298,7 @@ impl Element for Flex {
}
}
})
- .on_move(|_, _| { /* Capture move events */ }),
+ .on_move(|_, _: &mut V, _| { /* Capture move events */ }),
)
}
@@ -343,7 +347,7 @@ impl Element for Flex {
aligned_child_origin
};
- child.paint(aligned_child_origin, visible_bounds, cx);
+ child.paint(scene, aligned_child_origin, visible_bounds, view, cx);
match self.axis {
Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
@@ -352,7 +356,7 @@ impl Element for Flex {
}
if overflowing {
- cx.scene.pop_layer();
+ scene.pop_layer();
}
}
@@ -363,11 +367,12 @@ impl Element for Flex {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
self.children
.iter()
- .find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx))
+ .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
}
fn debug(
@@ -375,13 +380,14 @@ impl Element for Flex {
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> json::Value {
json!({
"type": "Flex",
"bounds": bounds.to_json(),
"axis": self.axis.to_json(),
- "children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
+ "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
})
}
}
@@ -391,13 +397,13 @@ struct FlexParentData {
float: bool,
}
-pub struct FlexItem {
+pub struct FlexItem<V: View> {
metadata: FlexParentData,
- child: ElementBox,
+ child: Element<V>,
}
-impl FlexItem {
- pub fn new(child: ElementBox) -> Self {
+impl<V: View> FlexItem<V> {
+ pub fn new(child: Element<V>) -> Self {
FlexItem {
metadata: FlexParentData {
flex: None,
@@ -418,27 +424,31 @@ impl FlexItem {
}
}
-impl Element for FlexItem {
+impl<V: View> Drawable<V> for FlexItem<V> {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
- let size = self.child.layout(constraint, cx);
+ let size = self.child.layout(constraint, view, cx);
(size, ())
}
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
- self.child.paint(bounds.origin(), visible_bounds, cx)
+ self.child
+ .paint(scene, bounds.origin(), visible_bounds, view, cx)
}
fn rect_for_text_range(
@@ -448,9 +458,10 @@ impl Element for FlexItem {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, cx)
+ self.child.rect_for_text_range(range_utf16, view, cx)
}
fn metadata(&self) -> Option<&dyn Any> {
@@ -462,12 +473,13 @@ impl Element for FlexItem {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Value {
json!({
"type": "Flexible",
"flex": self.metadata.flex,
- "child": self.child.debug(cx)
+ "child": self.child.debug(view, cx)
})
}
}
@@ -3,17 +3,16 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::json,
- presenter::MeasurementContext,
- DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+ Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
};
-pub struct Hook {
- child: ElementBox,
- after_layout: Option<Box<dyn FnMut(Vector2F, &mut LayoutContext)>>,
+pub struct Hook<V: View> {
+ child: Element<V>,
+ after_layout: Option<Box<dyn FnMut(Vector2F, &mut ViewContext<V>)>>,
}
-impl Hook {
- pub fn new(child: ElementBox) -> Self {
+impl<V: View> Hook<V> {
+ pub fn new(child: Element<V>) -> Self {
Self {
child,
after_layout: None,
@@ -22,23 +21,24 @@ impl Hook {
pub fn on_after_layout(
mut self,
- f: impl 'static + FnMut(Vector2F, &mut LayoutContext),
+ f: impl 'static + FnMut(Vector2F, &mut ViewContext<V>),
) -> Self {
self.after_layout = Some(Box::new(f));
self
}
}
-impl Element for Hook {
+impl<V: View> Drawable<V> for Hook<V> {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
- let size = self.child.layout(constraint, cx);
+ let size = self.child.layout(constraint, view, cx);
if let Some(handler) = self.after_layout.as_mut() {
handler(size, cx);
}
@@ -47,12 +47,15 @@ impl Element for Hook {
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) {
- self.child.paint(bounds.origin(), visible_bounds, cx);
+ self.child
+ .paint(scene, bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(
@@ -62,9 +65,10 @@ impl Element for Hook {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, cx)
+ self.child.rect_for_text_range(range_utf16, view, cx)
}
fn debug(
@@ -72,11 +76,12 @@ impl Element for Hook {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> serde_json::Value {
json!({
"type": "Hooks",
- "child": self.child.debug(cx),
+ "child": self.child.debug(view, cx),
})
}
}
@@ -5,8 +5,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
- presenter::MeasurementContext,
- scene, Border, DebugContext, Element, ImageData, LayoutContext, PaintContext, SizeConstraint,
+ scene, Border, Drawable, ImageData, SceneBuilder, SizeConstraint, View, ViewContext,
};
use serde::Deserialize;
use std::{ops::Range, sync::Arc};
@@ -56,14 +55,15 @@ impl Image {
}
}
-impl Element for Image {
+impl<V: View> Drawable<V> for Image {
type LayoutState = Option<Arc<ImageData>>;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ _: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let data = match &self.source {
ImageSource::Path(path) => match cx.asset_cache.png(path) {
@@ -90,13 +90,15 @@ impl Element for Image {
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
_: RectF,
layout: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ _: &mut V,
+ _: &mut ViewContext<V>,
) -> Self::PaintState {
if let Some(data) = layout {
- cx.scene.push_image(scene::Image {
+ scene.push_image(scene::Image {
bounds,
border: self.style.border,
corner_radius: self.style.corner_radius,
@@ -113,7 +115,8 @@ impl Element for Image {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &MeasurementContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> Option<RectF> {
None
}
@@ -123,7 +126,8 @@ impl Element for Image {
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &DebugContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> serde_json::Value {
json!({
"type": "Image",
@@ -2,7 +2,7 @@ use crate::{
elements::*,
fonts::TextStyle,
geometry::{rect::RectF, vector::Vector2F},
- Action, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+ Action, Element, SizeConstraint,
};
use serde_json::json;
@@ -12,20 +12,17 @@ pub struct KeystrokeLabel {
action: Box<dyn Action>,
container_style: ContainerStyle,
text_style: TextStyle,
- window_id: usize,
view_id: usize,
}
impl KeystrokeLabel {
pub fn new(
- window_id: usize,
view_id: usize,
action: Box<dyn Action>,
container_style: ContainerStyle,
text_style: TextStyle,
) -> Self {
Self {
- window_id,
view_id,
action,
container_style,
@@ -34,18 +31,18 @@ impl KeystrokeLabel {
}
}
-impl Element for KeystrokeLabel {
- type LayoutState = ElementBox;
+impl<V: View> Drawable<V> for KeystrokeLabel {
+ type LayoutState = Element<V>;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
- ) -> (Vector2F, ElementBox) {
+ view: &mut V,
+ cx: &mut ViewContext<V>,
+ ) -> (Vector2F, Element<V>) {
let mut element = if let Some(keystrokes) =
- cx.app
- .keystrokes_for_action(self.window_id, self.view_id, self.action.as_ref())
+ cx.keystrokes_for_action(self.view_id, self.action.as_ref())
{
Flex::row()
.with_children(keystrokes.iter().map(|keystroke| {
@@ -59,18 +56,20 @@ impl Element for KeystrokeLabel {
Empty::new().collapsed().boxed()
};
- let size = element.layout(constraint, cx);
+ let size = element.layout(constraint, view, cx);
(size, element)
}
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
- element: &mut ElementBox,
- cx: &mut PaintContext,
+ element: &mut Element<V>,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) {
- element.paint(bounds.origin(), visible_bounds, cx);
+ element.paint(scene, bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(
@@ -80,7 +79,8 @@ impl Element for KeystrokeLabel {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &MeasurementContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> Option<RectF> {
None
}
@@ -88,14 +88,15 @@ impl Element for KeystrokeLabel {
fn debug(
&self,
_: RectF,
- element: &ElementBox,
+ element: &Element<V>,
_: &(),
- cx: &crate::DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> serde_json::Value {
json!({
"type": "KeystrokeLabel",
"action": self.action.name(),
- "child": element.debug(cx)
+ "child": element.debug(view, cx)
})
}
}
@@ -7,9 +7,8 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{ToJson, Value},
- presenter::MeasurementContext,
text_layout::{Line, RunStyle},
- DebugContext, Element, LayoutContext, PaintContext, SizeConstraint,
+ Drawable, SceneBuilder, SizeConstraint, View, ViewContext,
};
use serde::Deserialize;
use serde_json::json;
@@ -128,19 +127,22 @@ impl Label {
}
}
-impl Element for Label {
+impl<V: View> Drawable<V> for Label {
type LayoutState = Line;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ _: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let runs = self.compute_runs();
- let line =
- cx.text_layout_cache
- .layout_str(&self.text, self.style.text.font_size, runs.as_slice());
+ let line = cx.text_layout_cache().layout_str(
+ &self.text,
+ self.style.text.font_size,
+ runs.as_slice(),
+ );
let size = vec2f(
line.width()
@@ -155,12 +157,20 @@ impl Element for Label {
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
line: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ _: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
- line.paint(bounds.origin(), visible_bounds, bounds.size().y(), cx)
+ line.paint(
+ scene,
+ bounds.origin(),
+ visible_bounds,
+ bounds.size().y(),
+ cx,
+ )
}
fn rect_for_text_range(
@@ -170,7 +180,8 @@ impl Element for Label {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &MeasurementContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> Option<RectF> {
None
}
@@ -180,7 +191,8 @@ impl Element for Label {
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &DebugContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> Value {
json!({
"type": "Label",
@@ -4,19 +4,16 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::json,
- presenter::MeasurementContext,
- DebugContext, Element, ElementBox, ElementRc, EventContext, LayoutContext, MouseRegion,
- PaintContext, RenderContext, SizeConstraint, View, ViewContext,
+ Drawable, Element, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
};
-use std::{cell::RefCell, collections::VecDeque, ops::Range, rc::Rc};
+use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
-pub struct List {
- state: ListState,
+pub struct List<V: View> {
+ state: ListState<V>,
}
-#[derive(Clone)]
-pub struct ListState(Rc<RefCell<StateInner>>);
+pub struct ListState<V: View>(Rc<RefCell<StateInner<V>>>);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Orientation {
@@ -24,16 +21,16 @@ pub enum Orientation {
Bottom,
}
-struct StateInner {
+struct StateInner<V: View> {
last_layout_width: Option<f32>,
- render_item: Box<dyn FnMut(usize, &mut LayoutContext) -> Option<ElementBox>>,
+ render_item: Box<dyn FnMut(&mut V, usize, &mut ViewContext<V>) -> Element<V>>,
rendered_range: Range<usize>,
- items: SumTree<ListItem>,
+ items: SumTree<ListItem<V>>,
logical_scroll_top: Option<ListOffset>,
orientation: Orientation,
overdraw: f32,
#[allow(clippy::type_complexity)]
- scroll_handler: Option<Box<dyn FnMut(Range<usize>, &mut EventContext)>>,
+ scroll_handler: Option<Box<dyn FnMut(Range<usize>, &mut V, &mut ViewContext<V>)>>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
@@ -42,14 +39,23 @@ pub struct ListOffset {
pub offset_in_item: f32,
}
-#[derive(Clone)]
-enum ListItem {
+enum ListItem<V: View> {
Unrendered,
- Rendered(ElementRc),
+ Rendered(Rc<RefCell<Element<V>>>),
Removed(f32),
}
-impl std::fmt::Debug for ListItem {
+impl<V: View> Clone for ListItem<V> {
+ fn clone(&self) -> Self {
+ match self {
+ Self::Unrendered => Self::Unrendered,
+ Self::Rendered(element) => Self::Rendered(element.clone()),
+ Self::Removed(height) => Self::Removed(*height),
+ }
+ }
+}
+
+impl<V: View> Debug for ListItem<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unrendered => write!(f, "Unrendered"),
@@ -79,20 +85,21 @@ struct UnrenderedCount(usize);
#[derive(Clone, Debug, Default)]
struct Height(f32);
-impl List {
- pub fn new(state: ListState) -> Self {
+impl<V: View> List<V> {
+ pub fn new(state: ListState<V>) -> Self {
Self { state }
}
}
-impl Element for List {
+impl<V: View> Drawable<V> for List<V> {
type LayoutState = ListOffset;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let state = &mut *self.state.0.borrow_mut();
let size = constraint.max;
@@ -134,9 +141,10 @@ impl Element for List {
scroll_top.item_ix + ix,
existing_element,
item_constraint,
+ view,
cx,
) {
- rendered_height += element.size().y();
+ rendered_height += element.borrow().size().y();
rendered_items.push_back(ListItem::Rendered(element));
}
}
@@ -151,9 +159,9 @@ impl Element for List {
cursor.prev(&());
if cursor.item().is_some() {
if let Some(element) =
- state.render_item(cursor.start().0, None, item_constraint, cx)
+ state.render_item(cursor.start().0, None, item_constraint, view, cx)
{
- rendered_height += element.size().y();
+ rendered_height += element.borrow().size().y();
rendered_items.push_front(ListItem::Rendered(element));
}
} else {
@@ -187,9 +195,9 @@ impl Element for List {
cursor.prev(&());
if let Some(item) = cursor.item() {
if let Some(element) =
- state.render_item(cursor.start().0, Some(item), item_constraint, cx)
+ state.render_item(cursor.start().0, Some(item), item_constraint, view, cx)
{
- leading_overdraw += element.size().y();
+ leading_overdraw += element.borrow().size().y();
rendered_items.push_front(ListItem::Rendered(element));
}
} else {
@@ -241,25 +249,27 @@ impl Element for List {
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
scroll_top: &mut ListOffset,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
- cx.scene.push_layer(Some(visible_bounds));
-
- cx.scene.push_mouse_region(
- MouseRegion::new::<Self>(cx.current_view_id(), 0, bounds).on_scroll({
+ scene.push_layer(Some(visible_bounds));
+ scene.push_mouse_region(
+ MouseRegion::new::<Self>(cx.view_id(), 0, bounds).on_scroll({
let state = self.state.clone();
let height = bounds.height();
let scroll_top = scroll_top.clone();
- move |e, cx| {
+ move |e, view, cx| {
state.0.borrow_mut().scroll(
&scroll_top,
height,
*e.platform_event.delta.raw(),
e.platform_event.delta.precise(),
+ view,
cx,
)
}
@@ -267,11 +277,13 @@ impl Element for List {
);
let state = &mut *self.state.0.borrow_mut();
- for (mut element, origin) in state.visible_elements(bounds, scroll_top) {
- element.paint(origin, visible_bounds, cx);
+ for (element, origin) in state.visible_elements(bounds, scroll_top) {
+ element
+ .borrow_mut()
+ .paint(scene, origin, visible_bounds, view, cx);
}
- cx.scene.pop_layer();
+ scene.pop_layer();
}
fn rect_for_text_range(
@@ -281,7 +293,8 @@ impl Element for List {
_: RectF,
scroll_top: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
let state = self.state.0.borrow();
let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
@@ -293,11 +306,15 @@ impl Element for List {
}
if let ListItem::Rendered(element) = item {
- if let Some(rect) = element.rect_for_text_range(range_utf16.clone(), cx) {
+ if let Some(rect) =
+ element
+ .borrow()
+ .rect_for_text_range(range_utf16.clone(), view, cx)
+ {
return Some(rect);
}
- item_origin.set_y(item_origin.y() + element.size().y());
+ item_origin.set_y(item_origin.y() + element.borrow().size().y());
cursor.next(&());
} else {
unreachable!();
@@ -312,12 +329,13 @@ impl Element for List {
bounds: RectF,
scroll_top: &Self::LayoutState,
_: &(),
- cx: &DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> serde_json::Value {
let state = self.state.0.borrow_mut();
let visible_elements = state
.visible_elements(bounds, scroll_top)
- .map(|e| e.0.debug(cx))
+ .map(|e| e.0.borrow().debug(view, cx))
.collect::<Vec<_>>();
let visible_range = scroll_top.item_ix..(scroll_top.item_ix + visible_elements.len());
json!({
@@ -328,27 +346,22 @@ impl Element for List {
}
}
-impl ListState {
- pub fn new<F, V>(
+impl<V: View> ListState<V> {
+ pub fn new<F>(
element_count: usize,
orientation: Orientation,
overdraw: f32,
- cx: &mut ViewContext<V>,
- mut render_item: F,
+ render_item: F,
) -> Self
where
V: View,
- F: 'static + FnMut(&mut V, usize, &mut RenderContext<V>) -> ElementBox,
+ F: 'static + FnMut(&mut V, usize, &mut ViewContext<V>) -> Element<V>,
{
let mut items = SumTree::new();
items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
- let handle = cx.weak_handle();
Self(Rc::new(RefCell::new(StateInner {
last_layout_width: None,
- render_item: Box::new(move |ix, cx| {
- let handle = handle.upgrade(cx)?;
- Some(cx.render(&handle, |view, cx| render_item(view, ix, cx)))
- }),
+ render_item: Box::new(render_item),
rendered_range: 0..0,
items,
logical_scroll_top: None,
@@ -406,7 +419,7 @@ impl ListState {
pub fn set_scroll_handler(
&mut self,
- handler: impl FnMut(Range<usize>, &mut EventContext) + 'static,
+ handler: impl FnMut(Range<usize>, &mut V, &mut ViewContext<V>) + 'static,
) {
self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
}
@@ -426,20 +439,27 @@ impl ListState {
}
}
-impl StateInner {
+impl<V: View> Clone for ListState<V> {
+ fn clone(&self) -> Self {
+ Self(self.0.clone())
+ }
+}
+
+impl<V: View> StateInner<V> {
fn render_item(
&mut self,
ix: usize,
- existing_element: Option<&ListItem>,
+ existing_element: Option<&ListItem<V>>,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
- ) -> Option<ElementRc> {
+ view: &mut V,
+ cx: &mut ViewContext<V>,
+ ) -> Option<Rc<RefCell<Element<V>>>> {
if let Some(ListItem::Rendered(element)) = existing_element {
Some(element.clone())
} else {
- let mut element = (self.render_item)(ix, cx)?;
- element.layout(constraint, cx);
- Some(element.into())
+ let mut element = (self.render_item)(view, ix, cx);
+ element.layout(constraint, view, cx);
+ Some(Rc::new(RefCell::new(element)))
}
}
@@ -455,7 +475,7 @@ impl StateInner {
&'a self,
bounds: RectF,
scroll_top: &ListOffset,
- ) -> impl Iterator<Item = (ElementRc, Vector2F)> + 'a {
+ ) -> impl Iterator<Item = (Rc<RefCell<Element<V>>>, Vector2F)> + 'a {
let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
let mut cursor = self.items.cursor::<Count>();
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
@@ -467,7 +487,7 @@ impl StateInner {
if let ListItem::Rendered(element) = item {
let result = (element.clone(), item_origin);
- item_origin.set_y(item_origin.y() + element.size().y());
+ item_origin.set_y(item_origin.y() + element.borrow().size().y());
cursor.next(&());
return Some(result);
}
@@ -485,7 +505,8 @@ impl StateInner {
height: f32,
mut delta: Vector2F,
precise: bool,
- cx: &mut EventContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) {
if !precise {
delta *= 20.;
@@ -511,7 +532,7 @@ impl StateInner {
if self.scroll_handler.is_some() {
let visible_range = self.visible_range(height, scroll_top);
- self.scroll_handler.as_mut().unwrap()(visible_range, cx);
+ self.scroll_handler.as_mut().unwrap()(visible_range, view, cx);
}
cx.notify();
@@ -538,17 +559,17 @@ impl StateInner {
}
}
-impl ListItem {
+impl<V: View> ListItem<V> {
fn remove(&self) -> Self {
match self {
ListItem::Unrendered => ListItem::Unrendered,
- ListItem::Rendered(element) => ListItem::Removed(element.size().y()),
+ ListItem::Rendered(element) => ListItem::Removed(element.borrow().size().y()),
ListItem::Removed(height) => ListItem::Removed(*height),
}
}
}
-impl sum_tree::Item for ListItem {
+impl<V: View> sum_tree::Item for ListItem<V> {
type Summary = ListItemSummary;
fn summary(&self) -> Self::Summary {
@@ -563,7 +584,7 @@ impl sum_tree::Item for ListItem {
count: 1,
rendered_count: 1,
unrendered_count: 0,
- height: element.size().y(),
+ height: element.borrow().size().y(),
},
ListItem::Removed(height) => ListItemSummary {
count: 1,
@@ -631,264 +652,261 @@ mod tests {
#[crate::test(self)]
fn test_layout(cx: &mut crate::AppContext) {
- let mut presenter = cx.build_presenter(0, 0., Default::default());
- let (_, view) = cx.add_window(Default::default(), |_| TestView);
- let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
-
- let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
-
- let state = view.update(cx, |_, cx| {
- ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, cx, {
+ cx.add_window(Default::default(), |cx| {
+ let mut view = TestView;
+ let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
+ let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
+ let state = ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, {
let elements = elements.clone();
move |_, ix, _| {
let (id, height) = elements.borrow()[ix];
TestElement::new(id, height).boxed()
}
- })
- });
+ });
- let mut list = List::new(state.clone());
- let (size, _) = list.layout(
- constraint,
- &mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
- );
- assert_eq!(size, vec2f(100., 40.));
- assert_eq!(
- state.0.borrow().items.summary().clone(),
- ListItemSummary {
- count: 3,
- rendered_count: 3,
- unrendered_count: 0,
- height: 150.
- }
- );
+ let mut list = List::new(state.clone());
+ let (size, _) = list.layout(constraint, &mut view, cx);
+ assert_eq!(size, vec2f(100., 40.));
+ assert_eq!(
+ state.0.borrow().items.summary().clone(),
+ ListItemSummary {
+ count: 3,
+ rendered_count: 3,
+ unrendered_count: 0,
+ height: 150.
+ }
+ );
- state.0.borrow_mut().scroll(
- &ListOffset {
- item_ix: 0,
- offset_in_item: 0.,
- },
- 40.,
- vec2f(0., -54.),
- true,
- &mut presenter.build_event_context(&mut Default::default(), cx),
- );
- let (_, logical_scroll_top) = list.layout(
- constraint,
- &mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
- );
- assert_eq!(
- logical_scroll_top,
- ListOffset {
- item_ix: 2,
- offset_in_item: 4.
- }
- );
- assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.);
-
- elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
- elements.borrow_mut().push((5, 60.));
- state.splice(1..2, 2);
- state.splice(4..4, 1);
- assert_eq!(
- state.0.borrow().items.summary().clone(),
- ListItemSummary {
- count: 5,
- rendered_count: 2,
- unrendered_count: 3,
- height: 120.
- }
- );
+ state.0.borrow_mut().scroll(
+ &ListOffset {
+ item_ix: 0,
+ offset_in_item: 0.,
+ },
+ 40.,
+ vec2f(0., -54.),
+ true,
+ &mut view,
+ cx,
+ );
- let (size, logical_scroll_top) = list.layout(
- constraint,
- &mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
- );
- assert_eq!(size, vec2f(100., 40.));
- assert_eq!(
- state.0.borrow().items.summary().clone(),
- ListItemSummary {
- count: 5,
- rendered_count: 5,
- unrendered_count: 0,
- height: 270.
- }
- );
- assert_eq!(
- logical_scroll_top,
- ListOffset {
- item_ix: 3,
- offset_in_item: 4.
- }
- );
- assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.);
+ let (_, logical_scroll_top) = list.layout(constraint, &mut view, cx);
+ assert_eq!(
+ logical_scroll_top,
+ ListOffset {
+ item_ix: 2,
+ offset_in_item: 4.
+ }
+ );
+ assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.);
+
+ elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
+ elements.borrow_mut().push((5, 60.));
+ state.splice(1..2, 2);
+ state.splice(4..4, 1);
+ assert_eq!(
+ state.0.borrow().items.summary().clone(),
+ ListItemSummary {
+ count: 5,
+ rendered_count: 2,
+ unrendered_count: 3,
+ height: 120.
+ }
+ );
+
+ let (size, logical_scroll_top) = list.layout(constraint, &mut view, cx);
+ assert_eq!(size, vec2f(100., 40.));
+ assert_eq!(
+ state.0.borrow().items.summary().clone(),
+ ListItemSummary {
+ count: 5,
+ rendered_count: 5,
+ unrendered_count: 0,
+ height: 270.
+ }
+ );
+ assert_eq!(
+ logical_scroll_top,
+ ListOffset {
+ item_ix: 3,
+ offset_in_item: 4.
+ }
+ );
+ assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.);
+
+ view
+ });
}
- #[crate::test(self, iterations = 10, seed = 0)]
+ #[crate::test(self, iterations = 10)]
fn test_random(cx: &mut crate::AppContext, mut rng: StdRng) {
let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10);
- let (_, view) = cx.add_window(Default::default(), |_| TestView);
- let mut presenter = cx.build_presenter(0, 0., Default::default());
- let mut next_id = 0;
- let elements = Rc::new(RefCell::new(
- (0..rng.gen_range(0..=20))
- .map(|_| {
- let id = next_id;
- next_id += 1;
- (id, rng.gen_range(0..=200) as f32 / 2.0)
- })
- .collect::<Vec<_>>(),
- ));
- let orientation = *[Orientation::Top, Orientation::Bottom]
- .choose(&mut rng)
- .unwrap();
- let overdraw = rng.gen_range(1..=100) as f32;
-
- let state = view.update(cx, |_, cx| {
- ListState::new(elements.borrow().len(), orientation, overdraw, cx, {
+ cx.add_window(Default::default(), |cx| {
+ let mut view = TestView;
+
+ let mut next_id = 0;
+ let elements = Rc::new(RefCell::new(
+ (0..rng.gen_range(0..=20))
+ .map(|_| {
+ let id = next_id;
+ next_id += 1;
+ (id, rng.gen_range(0..=200) as f32 / 2.0)
+ })
+ .collect::<Vec<_>>(),
+ ));
+ let orientation = *[Orientation::Top, Orientation::Bottom]
+ .choose(&mut rng)
+ .unwrap();
+ let overdraw = rng.gen_range(1..=100) as f32;
+
+ let state = ListState::new(elements.borrow().len(), orientation, overdraw, {
let elements = elements.clone();
move |_, ix, _| {
let (id, height) = elements.borrow()[ix];
TestElement::new(id, height).boxed()
}
- })
- });
+ });
- let mut width = rng.gen_range(0..=2000) as f32 / 2.;
- let mut height = rng.gen_range(0..=2000) as f32 / 2.;
- log::info!("orientation: {:?}", orientation);
- log::info!("overdraw: {}", overdraw);
- log::info!("elements: {:?}", elements.borrow());
- log::info!("size: ({:?}, {:?})", width, height);
- log::info!("==================");
-
- let mut last_logical_scroll_top = None;
- for _ in 0..operations {
- match rng.gen_range(0..=100) {
- 0..=29 if last_logical_scroll_top.is_some() => {
- let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
- log::info!(
- "Scrolling by {:?}, previous scroll top: {:?}",
- delta,
- last_logical_scroll_top.unwrap()
- );
- state.0.borrow_mut().scroll(
- last_logical_scroll_top.as_ref().unwrap(),
- height,
- delta,
- true,
- &mut presenter.build_event_context(&mut Default::default(), cx),
- );
- }
- 30..=34 => {
- width = rng.gen_range(0..=2000) as f32 / 2.;
- log::info!("changing width: {:?}", width);
- }
- 35..=54 => {
- height = rng.gen_range(0..=1000) as f32 / 2.;
- log::info!("changing height: {:?}", height);
+ let mut width = rng.gen_range(0..=2000) as f32 / 2.;
+ let mut height = rng.gen_range(0..=2000) as f32 / 2.;
+ log::info!("orientation: {:?}", orientation);
+ log::info!("overdraw: {}", overdraw);
+ log::info!("elements: {:?}", elements.borrow());
+ log::info!("size: ({:?}, {:?})", width, height);
+ log::info!("==================");
+
+ let mut last_logical_scroll_top = None;
+ for _ in 0..operations {
+ match rng.gen_range(0..=100) {
+ 0..=29 if last_logical_scroll_top.is_some() => {
+ let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
+ log::info!(
+ "Scrolling by {:?}, previous scroll top: {:?}",
+ delta,
+ last_logical_scroll_top.unwrap()
+ );
+ state.0.borrow_mut().scroll(
+ last_logical_scroll_top.as_ref().unwrap(),
+ height,
+ delta,
+ true,
+ &mut view,
+ cx,
+ );
+ }
+ 30..=34 => {
+ width = rng.gen_range(0..=2000) as f32 / 2.;
+ log::info!("changing width: {:?}", width);
+ }
+ 35..=54 => {
+ height = rng.gen_range(0..=1000) as f32 / 2.;
+ log::info!("changing height: {:?}", height);
+ }
+ _ => {
+ let mut elements = elements.borrow_mut();
+ let end_ix = rng.gen_range(0..=elements.len());
+ let start_ix = rng.gen_range(0..=end_ix);
+ let new_elements = (0..rng.gen_range(0..10))
+ .map(|_| {
+ let id = next_id;
+ next_id += 1;
+ (id, rng.gen_range(0..=200) as f32 / 2.)
+ })
+ .collect::<Vec<_>>();
+ log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
+ state.splice(start_ix..end_ix, new_elements.len());
+ elements.splice(start_ix..end_ix, new_elements);
+ for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() {
+ if let ListItem::Rendered(element) = item {
+ let (expected_id, _) = elements[ix];
+ element.borrow().with_metadata(|metadata: Option<&usize>| {
+ assert_eq!(*metadata.unwrap(), expected_id);
+ });
+ }
+ }
+ }
}
- _ => {
- let mut elements = elements.borrow_mut();
- let end_ix = rng.gen_range(0..=elements.len());
- let start_ix = rng.gen_range(0..=end_ix);
- let new_elements = (0..rng.gen_range(0..10))
- .map(|_| {
- let id = next_id;
- next_id += 1;
- (id, rng.gen_range(0..=200) as f32 / 2.)
- })
- .collect::<Vec<_>>();
- log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
- state.splice(start_ix..end_ix, new_elements.len());
- elements.splice(start_ix..end_ix, new_elements);
- for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() {
- if let ListItem::Rendered(element) = item {
- let (expected_id, _) = elements[ix];
+
+ let mut list = List::new(state.clone());
+ let window_size = vec2f(width, height);
+ let (size, logical_scroll_top) = list.layout(
+ SizeConstraint::new(vec2f(0., 0.), window_size),
+ &mut view,
+ cx,
+ );
+ assert_eq!(size, window_size);
+ last_logical_scroll_top = Some(logical_scroll_top);
+
+ let state = state.0.borrow();
+ log::info!("items {:?}", state.items.items(&()));
+
+ let scroll_top = state.scroll_top(&logical_scroll_top);
+ let rendered_top = (scroll_top - overdraw).max(0.);
+ let rendered_bottom = scroll_top + height + overdraw;
+ let mut item_top = 0.;
+
+ log::info!(
+ "rendered top {:?}, rendered bottom {:?}, scroll top {:?}",
+ rendered_top,
+ rendered_bottom,
+ scroll_top,
+ );
+
+ let mut first_rendered_element_top = None;
+ let mut last_rendered_element_bottom = None;
+ assert_eq!(state.items.summary().count, elements.borrow().len());
+ for (ix, item) in state.items.cursor::<()>().enumerate() {
+ match item {
+ ListItem::Unrendered => {
+ let item_bottom = item_top;
+ assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
+ item_top = item_bottom;
+ }
+ ListItem::Removed(height) => {
+ let (id, expected_height) = elements.borrow()[ix];
+ assert_eq!(
+ *height, expected_height,
+ "element {} height didn't match",
+ id
+ );
+ let item_bottom = item_top + height;
+ assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
+ item_top = item_bottom;
+ }
+ ListItem::Rendered(element) => {
+ let (expected_id, expected_height) = elements.borrow()[ix];
+ let element = element.borrow();
element.with_metadata(|metadata: Option<&usize>| {
assert_eq!(*metadata.unwrap(), expected_id);
});
+ assert_eq!(element.size().y(), expected_height);
+ let item_bottom = item_top + element.size().y();
+ first_rendered_element_top.get_or_insert(item_top);
+ last_rendered_element_bottom = Some(item_bottom);
+ assert!(item_bottom > rendered_top || item_top < rendered_bottom);
+ item_top = item_bottom;
}
}
}
- }
-
- let mut list = List::new(state.clone());
- let window_size = vec2f(width, height);
- let (size, logical_scroll_top) = list.layout(
- SizeConstraint::new(vec2f(0., 0.), window_size),
- &mut presenter.build_layout_context(window_size, false, cx),
- );
- assert_eq!(size, window_size);
- last_logical_scroll_top = Some(logical_scroll_top);
-
- let state = state.0.borrow();
- log::info!("items {:?}", state.items.items(&()));
-
- let scroll_top = state.scroll_top(&logical_scroll_top);
- let rendered_top = (scroll_top - overdraw).max(0.);
- let rendered_bottom = scroll_top + height + overdraw;
- let mut item_top = 0.;
-
- log::info!(
- "rendered top {:?}, rendered bottom {:?}, scroll top {:?}",
- rendered_top,
- rendered_bottom,
- scroll_top,
- );
- let mut first_rendered_element_top = None;
- let mut last_rendered_element_bottom = None;
- assert_eq!(state.items.summary().count, elements.borrow().len());
- for (ix, item) in state.items.cursor::<()>().enumerate() {
- match item {
- ListItem::Unrendered => {
- let item_bottom = item_top;
- assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
- item_top = item_bottom;
- }
- ListItem::Removed(height) => {
- let (id, expected_height) = elements.borrow()[ix];
- assert_eq!(
- *height, expected_height,
- "element {} height didn't match",
- id
- );
- let item_bottom = item_top + height;
- assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
- item_top = item_bottom;
+ match orientation {
+ Orientation::Top => {
+ if let Some(first_rendered_element_top) = first_rendered_element_top {
+ assert!(first_rendered_element_top <= scroll_top);
+ }
}
- ListItem::Rendered(element) => {
- let (expected_id, expected_height) = elements.borrow()[ix];
- element.with_metadata(|metadata: Option<&usize>| {
- assert_eq!(*metadata.unwrap(), expected_id);
- });
- assert_eq!(element.size().y(), expected_height);
- let item_bottom = item_top + element.size().y();
- first_rendered_element_top.get_or_insert(item_top);
- last_rendered_element_bottom = Some(item_bottom);
- assert!(item_bottom > rendered_top || item_top < rendered_bottom);
- item_top = item_bottom;
+ Orientation::Bottom => {
+ if let Some(last_rendered_element_bottom) = last_rendered_element_bottom {
+ assert!(last_rendered_element_bottom >= scroll_top + height);
+ }
}
}
}
- match orientation {
- Orientation::Top => {
- if let Some(first_rendered_element_top) = first_rendered_element_top {
- assert!(first_rendered_element_top <= scroll_top);
- }
- }
- Orientation::Bottom => {
- if let Some(last_rendered_element_bottom) = last_rendered_element_bottom {
- assert!(last_rendered_element_bottom >= scroll_top + height);
- }
- }
- }
- }
+ view
+ });
}
struct TestView;
@@ -902,7 +920,7 @@ mod tests {
"TestView"
}
- fn render(&mut self, _: &mut RenderContext<'_, Self>) -> ElementBox {
+ fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
Empty::new().boxed()
}
}
@@ -921,15 +939,28 @@ mod tests {
}
}
- impl Element for TestElement {
+ impl<V: View> Drawable<V> for TestElement {
type LayoutState = ();
type PaintState = ();
- fn layout(&mut self, _: SizeConstraint, _: &mut LayoutContext) -> (Vector2F, ()) {
+ fn layout(
+ &mut self,
+ _: SizeConstraint,
+ _: &mut V,
+ _: &mut ViewContext<V>,
+ ) -> (Vector2F, ()) {
(self.size, ())
}
- fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut PaintContext) {
+ fn paint(
+ &mut self,
+ _: &mut SceneBuilder,
+ _: RectF,
+ _: RectF,
+ _: &mut (),
+ _: &mut V,
+ _: &mut ViewContext<V>,
+ ) {
todo!()
}
@@ -940,12 +971,13 @@ mod tests {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &MeasurementContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> Option<RectF> {
todo!()
}
- fn debug(&self, _: RectF, _: &(), _: &(), _: &DebugContext) -> serde_json::Value {
+ fn debug(&self, _: RectF, _: &(), _: &(), _: &V, _: &ViewContext<V>) -> serde_json::Value {
self.id.into()
}
@@ -10,14 +10,14 @@ use crate::{
CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover,
MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
},
- DebugContext, Element, ElementBox, EventContext, LayoutContext, MeasurementContext,
- MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View,
+ Drawable, Element, EventContext, MouseRegion, MouseState, SceneBuilder, SizeConstraint, View,
+ ViewContext,
};
use serde_json::json;
use std::{marker::PhantomData, ops::Range};
-pub struct MouseEventHandler<Tag: 'static> {
- child: ElementBox,
+pub struct MouseEventHandler<Tag: 'static, V: View> {
+ child: Element<V>,
region_id: usize,
cursor_style: Option<CursorStyle>,
handlers: HandlerSet,
@@ -31,11 +31,11 @@ pub struct MouseEventHandler<Tag: 'static> {
/// Element which provides a render_child callback with a MouseState and paints a mouse
/// region under (or above) it for easy mouse event handling.
-impl<Tag> MouseEventHandler<Tag> {
- pub fn new<V, F>(region_id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
+impl<Tag, V: View> MouseEventHandler<Tag, V> {
+ pub fn new<F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
where
V: View,
- F: FnOnce(&mut MouseState, &mut RenderContext<V>) -> ElementBox,
+ F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> Element<V>,
{
let mut mouse_state = cx.mouse_state::<Tag>(region_id);
let child = render_child(&mut mouse_state, cx);
@@ -58,10 +58,10 @@ impl<Tag> MouseEventHandler<Tag> {
/// Modifies the MouseEventHandler to render the MouseRegion above the child element. Useful
/// for drag and drop handling and similar events which should be captured before the child
/// gets the opportunity
- pub fn above<V, F>(region_id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
+ pub fn above<F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
where
V: View,
- F: FnOnce(&mut MouseState, &mut RenderContext<V>) -> ElementBox,
+ F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> Element<V>,
{
let mut handler = Self::new(region_id, cx, render_child);
handler.above = true;
@@ -78,14 +78,17 @@ impl<Tag> MouseEventHandler<Tag> {
self
}
- pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self {
+ pub fn on_move(
+ mut self,
+ handler: impl Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
+ ) -> Self {
self.handlers = self.handlers.on_move(handler);
self
}
pub fn on_move_out(
mut self,
- handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static,
+ handler: impl Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
) -> Self {
self.handlers = self.handlers.on_move_out(handler);
self
@@ -94,7 +97,7 @@ impl<Tag> MouseEventHandler<Tag> {
pub fn on_down(
mut self,
button: MouseButton,
- handler: impl Fn(MouseDown, &mut EventContext) + 'static,
+ handler: impl Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
) -> Self {
self.handlers = self.handlers.on_down(button, handler);
self
@@ -103,7 +106,7 @@ impl<Tag> MouseEventHandler<Tag> {
pub fn on_up(
mut self,
button: MouseButton,
- handler: impl Fn(MouseUp, &mut EventContext) + 'static,
+ handler: impl Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
) -> Self {
self.handlers = self.handlers.on_up(button, handler);
self
@@ -112,7 +115,7 @@ impl<Tag> MouseEventHandler<Tag> {
pub fn on_click(
mut self,
button: MouseButton,
- handler: impl Fn(MouseClick, &mut EventContext) + 'static,
+ handler: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
) -> Self {
self.handlers = self.handlers.on_click(button, handler);
self
@@ -121,7 +124,7 @@ impl<Tag> MouseEventHandler<Tag> {
pub fn on_down_out(
mut self,
button: MouseButton,
- handler: impl Fn(MouseDownOut, &mut EventContext) + 'static,
+ handler: impl Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
) -> Self {
self.handlers = self.handlers.on_down_out(button, handler);
self
@@ -130,7 +133,7 @@ impl<Tag> MouseEventHandler<Tag> {
pub fn on_up_out(
mut self,
button: MouseButton,
- handler: impl Fn(MouseUpOut, &mut EventContext) + 'static,
+ handler: impl Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
) -> Self {
self.handlers = self.handlers.on_up_out(button, handler);
self
@@ -139,20 +142,23 @@ impl<Tag> MouseEventHandler<Tag> {
pub fn on_drag(
mut self,
button: MouseButton,
- handler: impl Fn(MouseDrag, &mut EventContext) + 'static,
+ handler: impl Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
) -> Self {
self.handlers = self.handlers.on_drag(button, handler);
self
}
- pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self {
+ pub fn on_hover(
+ mut self,
+ handler: impl Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
+ ) -> Self {
self.handlers = self.handlers.on_hover(handler);
self
}
pub fn on_scroll(
mut self,
- handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static,
+ handler: impl Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
) -> Self {
self.handlers = self.handlers.on_scroll(handler);
self
@@ -176,19 +182,25 @@ impl<Tag> MouseEventHandler<Tag> {
.round_out()
}
- fn paint_regions(&self, bounds: RectF, visible_bounds: RectF, cx: &mut PaintContext) {
+ fn paint_regions(
+ &self,
+ scene: &mut SceneBuilder,
+ bounds: RectF,
+ visible_bounds: RectF,
+ cx: &mut ViewContext<V>,
+ ) {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
let hit_bounds = self.hit_bounds(visible_bounds);
if let Some(style) = self.cursor_style {
- cx.scene.push_cursor_region(CursorRegion {
+ scene.push_cursor_region(CursorRegion {
bounds: hit_bounds,
style,
});
}
- cx.scene.push_mouse_region(
+ scene.push_mouse_region(
MouseRegion::from_handlers::<Tag>(
- cx.current_view_id(),
+ cx.view_id(),
self.region_id,
hit_bounds,
self.handlers.clone(),
@@ -200,34 +212,39 @@ impl<Tag> MouseEventHandler<Tag> {
}
}
-impl<Tag> Element for MouseEventHandler<Tag> {
+impl<Tag, V: View> Drawable<V> for MouseEventHandler<Tag, V> {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
- (self.child.layout(constraint, cx), ())
+ (self.child.layout(constraint, view, cx), ())
}
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
if self.above {
- self.child.paint(bounds.origin(), visible_bounds, cx);
+ self.child
+ .paint(scene, bounds.origin(), visible_bounds, view, cx);
- cx.paint_layer(None, |cx| {
- self.paint_regions(bounds, visible_bounds, cx);
+ scene.paint_layer(None, |scene| {
+ self.paint_regions(scene, bounds, visible_bounds, cx);
});
} else {
- self.paint_regions(bounds, visible_bounds, cx);
- self.child.paint(bounds.origin(), visible_bounds, cx);
+ self.paint_regions(scene, bounds, visible_bounds, cx);
+ self.child
+ .paint(scene, bounds.origin(), visible_bounds, view, cx);
}
}
@@ -238,9 +255,10 @@ impl<Tag> Element for MouseEventHandler<Tag> {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, cx)
+ self.child.rect_for_text_range(range_utf16, view, cx)
}
fn debug(
@@ -248,11 +266,12 @@ impl<Tag> Element for MouseEventHandler<Tag> {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> serde_json::Value {
json!({
"type": "MouseEventHandler",
- "child": self.child.debug(cx),
+ "child": self.child.debug(view, cx),
})
}
}
@@ -3,14 +3,12 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
- presenter::MeasurementContext,
- Axis, DebugContext, Element, ElementBox, LayoutContext, MouseRegion, PaintContext,
- SizeConstraint,
+ Axis, Drawable, Element, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
};
use serde_json::json;
-pub struct Overlay {
- child: ElementBox,
+pub struct Overlay<V: View> {
+ child: Element<V>,
anchor_position: Option<Vector2F>,
anchor_corner: AnchorCorner,
fit_mode: OverlayFitMode,
@@ -74,8 +72,8 @@ impl AnchorCorner {
}
}
-impl Overlay {
- pub fn new(child: ElementBox) -> Self {
+impl<V: View> Overlay<V> {
+ pub fn new(child: Element<V>) -> Self {
Self {
child,
anchor_position: None,
@@ -118,30 +116,33 @@ impl Overlay {
}
}
-impl Element for Overlay {
+impl<V: View> Drawable<V> for Overlay<V> {
type LayoutState = Vector2F;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let constraint = if self.anchor_position.is_some() {
- SizeConstraint::new(Vector2F::zero(), cx.window_size)
+ SizeConstraint::new(Vector2F::zero(), cx.window_size())
} else {
constraint
};
- let size = self.child.layout(constraint, cx);
+ let size = self.child.layout(constraint, view, cx);
(Vector2F::zero(), size)
}
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
_: RectF,
size: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) {
let (anchor_position, mut bounds) = match self.position_mode {
OverlayPositionMode::Window => {
@@ -162,9 +163,9 @@ impl Element for Overlay {
OverlayFitMode::SnapToWindow => {
// Snap the horizontal edges of the overlay to the horizontal edges of the window if
// its horizontal bounds overflow
- if bounds.max_x() > cx.window_size.x() {
+ if bounds.max_x() > cx.window_size().x() {
let mut lower_right = bounds.lower_right();
- lower_right.set_x(cx.window_size.x());
+ lower_right.set_x(cx.window_size().x());
bounds = RectF::from_points(lower_right - *size, lower_right);
} else if bounds.min_x() < 0. {
let mut upper_left = bounds.origin();
@@ -174,9 +175,9 @@ impl Element for Overlay {
// Snap the vertical edges of the overlay to the vertical edges of the window if
// its vertical bounds overflow.
- if bounds.max_y() > cx.window_size.y() {
+ if bounds.max_y() > cx.window_size().y() {
let mut lower_right = bounds.lower_right();
- lower_right.set_y(cx.window_size.y());
+ lower_right.set_y(cx.window_size().y());
bounds = RectF::from_points(lower_right - *size, lower_right);
} else if bounds.min_y() < 0. {
let mut upper_left = bounds.origin();
@@ -187,11 +188,11 @@ impl Element for Overlay {
OverlayFitMode::SwitchAnchor => {
let mut anchor_corner = self.anchor_corner;
- if bounds.max_x() > cx.window_size.x() {
+ if bounds.max_x() > cx.window_size().x() {
anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
}
- if bounds.max_y() > cx.window_size.y() {
+ if bounds.max_y() > cx.window_size().y() {
anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
}
@@ -211,21 +212,22 @@ impl Element for Overlay {
OverlayFitMode::None => {}
}
- cx.paint_stacking_context(None, self.z_index, |cx| {
+ scene.paint_stacking_context(None, self.z_index, |scene| {
if self.hoverable {
enum OverlayHoverCapture {}
// Block hovers in lower stacking contexts
- cx.scene
- .push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
- cx.current_view_id(),
- cx.current_view_id(),
- bounds,
- ));
+ scene.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
+ cx.view_id(),
+ cx.view_id(),
+ bounds,
+ ));
}
self.child.paint(
+ scene,
bounds.origin(),
- RectF::new(Vector2F::zero(), cx.window_size),
+ RectF::new(Vector2F::zero(), cx.window_size()),
+ view,
cx,
);
});
@@ -238,9 +240,10 @@ impl Element for Overlay {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, cx)
+ self.child.rect_for_text_range(range_utf16, view, cx)
}
fn debug(
@@ -248,12 +251,13 @@ impl Element for Overlay {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> serde_json::Value {
json!({
"type": "Overlay",
"abs_position": self.anchor_position.to_json(),
- "child": self.child.debug(cx),
+ "child": self.child.debug(view, cx),
})
}
}
@@ -7,7 +7,7 @@ use crate::{
geometry::rect::RectF,
platform::{CursorStyle, MouseButton},
scene::MouseDrag,
- Axis, Element, ElementBox, ElementStateHandle, MouseRegion, RenderContext, View,
+ Axis, Drawable, Element, ElementStateHandle, MouseRegion, SceneBuilder, View, ViewContext,
};
use super::{ConstrainedBox, Hook};
@@ -75,22 +75,22 @@ struct ResizeHandleState {
custom_dimension: Cell<f32>,
}
-pub struct Resizable {
+pub struct Resizable<V: View> {
side: Side,
handle_size: f32,
- child: ElementBox,
+ child: Element<V>,
state: Rc<ResizeHandleState>,
_state_handle: ElementStateHandle<Rc<ResizeHandleState>>,
}
-impl Resizable {
+impl<V: View> Resizable<V> {
pub fn new<Tag: 'static, T: View>(
- child: ElementBox,
+ child: Element<V>,
element_id: usize,
side: Side,
handle_size: f32,
initial_size: f32,
- cx: &mut RenderContext<T>,
+ cx: &mut ViewContext<V>,
) -> Self {
let state_handle = cx.element_state::<Tag, Rc<ResizeHandleState>>(
element_id,
@@ -132,51 +132,50 @@ impl Resizable {
}
}
-impl Element for Resizable {
+impl<V: View> Drawable<V> for Resizable<V> {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: crate::SizeConstraint,
- cx: &mut crate::LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
- (self.child.layout(constraint, cx), ())
+ (self.child.layout(constraint, view, cx), ())
}
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: pathfinder_geometry::rect::RectF,
visible_bounds: pathfinder_geometry::rect::RectF,
_child_size: &mut Self::LayoutState,
- cx: &mut crate::PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
- cx.scene.push_stacking_context(None, None);
+ scene.push_stacking_context(None, None);
let handle_region = self.side.of_rect(bounds, self.handle_size);
enum ResizeHandle {}
- cx.scene.push_mouse_region(
- MouseRegion::new::<ResizeHandle>(
- cx.current_view_id(),
- self.side as usize,
- handle_region,
- )
- .on_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere
- .on_drag(MouseButton::Left, {
- let state = self.state.clone();
- let side = self.side;
- move |e, cx| {
- let prev_width = state.actual_dimension.get();
- state
- .custom_dimension
- .set(0f32.max(prev_width + side.compute_delta(e)).round());
- cx.notify();
- }
- }),
+ scene.push_mouse_region(
+ MouseRegion::new::<ResizeHandle>(cx.view_id(), self.side as usize, handle_region)
+ .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
+ .on_drag(MouseButton::Left, {
+ let state = self.state.clone();
+ let side = self.side;
+ move |e, _: &mut V, cx| {
+ let prev_width = state.actual_dimension.get();
+ state
+ .custom_dimension
+ .set(0f32.max(prev_width + side.compute_delta(e)).round());
+ cx.notify();
+ }
+ }),
);
- cx.scene.push_cursor_region(crate::CursorRegion {
+ scene.push_cursor_region(crate::CursorRegion {
bounds: handle_region,
style: match self.side.axis() {
Axis::Horizontal => CursorStyle::ResizeLeftRight,
@@ -184,9 +183,10 @@ impl Element for Resizable {
},
});
- cx.scene.pop_stacking_context();
+ scene.pop_stacking_context();
- self.child.paint(bounds.origin(), visible_bounds, cx);
+ self.child
+ .paint(scene, bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(
@@ -196,9 +196,10 @@ impl Element for Resizable {
_visible_bounds: pathfinder_geometry::rect::RectF,
_layout: &Self::LayoutState,
_paint: &Self::PaintState,
- cx: &crate::MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<pathfinder_geometry::rect::RectF> {
- self.child.rect_for_text_range(range_utf16, cx)
+ self.child.rect_for_text_range(range_utf16, view, cx)
}
fn debug(
@@ -206,10 +207,11 @@ impl Element for Resizable {
_bounds: pathfinder_geometry::rect::RectF,
_layout: &Self::LayoutState,
_paint: &Self::PaintState,
- cx: &crate::DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> serde_json::Value {
json!({
- "child": self.child.debug(cx),
+ "child": self.child.debug(view, cx),
})
}
}
@@ -3,41 +3,48 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::{self, json, ToJson},
- presenter::MeasurementContext,
- DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+ Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
};
/// Element which renders it's children in a stack on top of each other.
/// The first child determines the size of the others.
-#[derive(Default)]
-pub struct Stack {
- children: Vec<ElementBox>,
+pub struct Stack<V: View> {
+ children: Vec<Element<V>>,
}
-impl Stack {
+impl<V: View> Default for Stack<V> {
+ fn default() -> Self {
+ Self {
+ children: Vec::new(),
+ }
+ }
+}
+
+impl<V: View> Stack<V> {
pub fn new() -> Self {
Self::default()
}
}
-impl Element for Stack {
+impl<V: View> Drawable<V> for Stack<V> {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
mut constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.min;
let mut children = self.children.iter_mut();
if let Some(bottom_child) = children.next() {
- size = bottom_child.layout(constraint, cx);
+ size = bottom_child.layout(constraint, view, cx);
constraint = SizeConstraint::strict(size);
}
for child in children {
- child.layout(constraint, cx);
+ child.layout(constraint, view, cx);
}
(size, ())
@@ -45,14 +52,16 @@ impl Element for Stack {
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
for child in &mut self.children {
- cx.paint_layer(None, |cx| {
- child.paint(bounds.origin(), visible_bounds, cx);
+ scene.paint_layer(None, |scene| {
+ child.paint(scene, bounds.origin(), visible_bounds, view, cx);
});
}
}
@@ -64,12 +73,13 @@ impl Element for Stack {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
self.children
.iter()
.rev()
- .find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx))
+ .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
}
fn debug(
@@ -77,18 +87,19 @@ impl Element for Stack {
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> json::Value {
json!({
"type": "Stack",
"bounds": bounds.to_json(),
- "children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
+ "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
})
}
}
-impl Extend<ElementBox> for Stack {
- fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
+impl<V: View> Extend<Element<V>> for Stack<V> {
+ fn extend<T: IntoIterator<Item = Element<V>>>(&mut self, children: T) {
self.children.extend(children)
}
}
@@ -8,8 +8,7 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
- presenter::MeasurementContext,
- scene, DebugContext, Element, LayoutContext, PaintContext, SizeConstraint,
+ scene, Drawable, SceneBuilder, SizeConstraint, View, ViewContext,
};
pub struct Svg {
@@ -31,14 +30,15 @@ impl Svg {
}
}
-impl Element for Svg {
+impl<V: View> Drawable<V> for Svg {
type LayoutState = Option<usvg::Tree>;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ _: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
match cx.asset_cache.svg(&self.path) {
Ok(tree) => {
@@ -58,13 +58,15 @@ impl Element for Svg {
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
_visible_bounds: RectF,
svg: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ _: &mut V,
+ _: &mut ViewContext<V>,
) {
if let Some(svg) = svg.clone() {
- cx.scene.push_icon(scene::Icon {
+ scene.push_icon(scene::Icon {
bounds,
svg,
path: self.path.clone(),
@@ -80,7 +82,8 @@ impl Element for Svg {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &MeasurementContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> Option<RectF> {
None
}
@@ -90,7 +93,8 @@ impl Element for Svg {
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &DebugContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> serde_json::Value {
json!({
"type": "Svg",
@@ -6,9 +6,8 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{ToJson, Value},
- presenter::MeasurementContext,
text_layout::{Line, RunStyle, ShapedBoundary},
- DebugContext, Element, FontCache, LayoutContext, PaintContext, SizeConstraint, TextLayoutCache,
+ Drawable, FontCache, SceneBuilder, SizeConstraint, TextLayoutCache, View, ViewContext,
};
use log::warn;
use serde_json::json;
@@ -53,14 +52,15 @@ impl Text {
}
}
-impl Element for Text {
+impl<V: View> Drawable<V> for Text {
type LayoutState = LayoutState;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ _: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
// Convert the string and highlight ranges into an iterator of highlighted chunks.
@@ -98,8 +98,8 @@ impl Element for Text {
let shaped_lines = layout_highlighted_chunks(
chunks,
&self.style,
- cx.text_layout_cache,
- cx.font_cache,
+ cx.text_layout_cache(),
+ &cx.font_cache,
usize::MAX,
self.text.matches('\n').count() + 1,
);
@@ -143,10 +143,12 @@ impl Element for Text {
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ _: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
let mut origin = bounds.origin();
let empty = Vec::new();
@@ -163,6 +165,7 @@ impl Element for Text {
if boundaries.intersects(visible_bounds) {
if self.soft_wrap {
line.paint_wrapped(
+ scene,
origin,
visible_bounds,
layout.line_height,
@@ -170,7 +173,7 @@ impl Element for Text {
cx,
);
} else {
- line.paint(origin, visible_bounds, layout.line_height, cx);
+ line.paint(scene, origin, visible_bounds, layout.line_height, cx);
}
}
origin.set_y(boundaries.max_y());
@@ -184,7 +187,8 @@ impl Element for Text {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &MeasurementContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> Option<RectF> {
None
}
@@ -194,7 +198,8 @@ impl Element for Text {
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &DebugContext,
+ _: &V,
+ _: &ViewContext<V>,
) -> Value {
json!({
"type": "Text",
@@ -208,9 +213,9 @@ impl Element for Text {
/// Perform text layout on a series of highlighted chunks of text.
pub fn layout_highlighted_chunks<'a>(
chunks: impl Iterator<Item = (&'a str, Option<HighlightStyle>)>,
- text_style: &'a TextStyle,
- text_layout_cache: &'a TextLayoutCache,
- font_cache: &'a Arc<FontCache>,
+ text_style: &TextStyle,
+ text_layout_cache: &TextLayoutCache,
+ font_cache: &Arc<FontCache>,
max_line_len: usize,
max_line_count: usize,
) -> Vec<Line> {
@@ -271,20 +276,23 @@ pub fn layout_highlighted_chunks<'a>(
#[cfg(test)]
mod tests {
use super::*;
- use crate::{elements::Empty, fonts, AppContext, ElementBox, Entity, RenderContext, View};
+ use crate::{elements::Empty, fonts, AppContext, Element, Entity, View, ViewContext};
#[crate::test(self)]
fn test_soft_wrapping_with_carriage_returns(cx: &mut AppContext) {
- let (window_id, _) = cx.add_window(Default::default(), |_| TestView);
- let mut presenter = cx.build_presenter(window_id, Default::default(), Default::default());
- fonts::with_font_cache(cx.font_cache().clone(), || {
- let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
- let (_, state) = text.layout(
- SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
- &mut presenter.build_layout_context(Default::default(), false, cx),
- );
- assert_eq!(state.shaped_lines.len(), 2);
- assert_eq!(state.wrap_boundaries.len(), 2);
+ cx.add_window(Default::default(), |cx| {
+ 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 (_, state) = text.layout(
+ SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
+ &mut view,
+ cx,
+ );
+ assert_eq!(state.shaped_lines.len(), 2);
+ assert_eq!(state.wrap_boundaries.len(), 2);
+ });
+ view
});
}
@@ -299,7 +307,7 @@ mod tests {
"TestView"
}
- fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
Empty::new().boxed()
}
}
@@ -1,14 +1,12 @@
use super::{
- ContainerStyle, Element, ElementBox, Flex, KeystrokeLabel, MouseEventHandler, Overlay,
+ ContainerStyle, Drawable, Element, Flex, KeystrokeLabel, MouseEventHandler, Overlay,
OverlayFitMode, ParentElement, Text,
};
use crate::{
fonts::TextStyle,
geometry::{rect::RectF, vector::Vector2F},
json::json,
- presenter::MeasurementContext,
- Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint,
- Task, View,
+ Action, Axis, ElementStateHandle, SceneBuilder, SizeConstraint, Task, View, ViewContext,
};
use serde::Deserialize;
use std::{
@@ -17,12 +15,13 @@ use std::{
rc::Rc,
time::Duration,
};
+use util::ResultExt;
const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(500);
-pub struct Tooltip {
- child: ElementBox,
- tooltip: Option<ElementBox>,
+pub struct Tooltip<V: View> {
+ child: Element<V>,
+ tooltip: Option<Element<V>>,
_state: ElementStateHandle<Rc<TooltipState>>,
}
@@ -50,24 +49,23 @@ pub struct KeystrokeStyle {
text: TextStyle,
}
-impl Tooltip {
+impl<V: View> Tooltip<V> {
pub fn new<Tag: 'static, T: View>(
id: usize,
text: String,
action: Option<Box<dyn Action>>,
style: TooltipStyle,
- child: ElementBox,
- cx: &mut RenderContext<T>,
+ child: Element<V>,
+ cx: &mut ViewContext<V>,
) -> Self {
struct ElementState<Tag>(Tag);
struct MouseEventHandlerState<Tag>(Tag);
- let focused_view_id = cx.focused_view_id(cx.window_id);
+ let focused_view_id = cx.focused_view_id();
let state_handle = cx.default_element_state::<ElementState<Tag>, Rc<TooltipState>>(id);
let state = state_handle.read(cx).clone();
let tooltip = if state.visible.get() {
let mut collapsed_tooltip = Self::render_tooltip(
- cx.window_id,
focused_view_id,
text.clone(),
style.clone(),
@@ -77,12 +75,12 @@ impl Tooltip {
.boxed();
Some(
Overlay::new(
- Self::render_tooltip(cx.window_id, focused_view_id, text, style, action, false)
+ Self::render_tooltip(focused_view_id, text, style, action, false)
.constrained()
- .dynamically(move |constraint, cx| {
+ .dynamically(move |constraint, view, cx| {
SizeConstraint::strict_along(
Axis::Vertical,
- collapsed_tooltip.layout(constraint, cx).y(),
+ collapsed_tooltip.layout(constraint, view, cx).y(),
)
})
.boxed(),
@@ -94,32 +92,31 @@ impl Tooltip {
} else {
None
};
- let child = MouseEventHandler::<MouseEventHandlerState<Tag>>::new(id, cx, |_, _| child)
- .on_hover(move |e, cx| {
+ let child = MouseEventHandler::<MouseEventHandlerState<Tag>, _>::new(id, cx, |_, _| child)
+ .on_hover(move |e, _, cx| {
let position = e.position;
- let window_id = cx.window_id();
- if let Some(view_id) = cx.view_id() {
- if e.started {
- if !state.visible.get() {
- state.position.set(position);
+ if e.started {
+ if !state.visible.get() {
+ state.position.set(position);
- let mut debounce = state.debounce.borrow_mut();
- if debounce.is_none() {
- *debounce = Some(cx.spawn({
- let state = state.clone();
- |mut cx| async move {
- cx.background().timer(DEBOUNCE_TIMEOUT).await;
- state.visible.set(true);
- cx.update(|cx| cx.notify_view(window_id, view_id));
+ let mut debounce = state.debounce.borrow_mut();
+ if debounce.is_none() {
+ *debounce = Some(cx.spawn_weak({
+ let state = state.clone();
+ |view, mut cx| async move {
+ cx.background().timer(DEBOUNCE_TIMEOUT).await;
+ state.visible.set(true);
+ if let Some(view) = view.upgrade(&cx) {
+ view.update(&mut cx, |_, cx| cx.notify()).log_err();
}
- }));
- }
+ }
+ }));
}
- } else {
- state.visible.set(false);
- state.debounce.take();
- cx.notify();
}
+ } else {
+ state.visible.set(false);
+ state.debounce.take();
+ cx.notify();
}
})
.boxed();
@@ -131,13 +128,12 @@ impl Tooltip {
}
pub fn render_tooltip(
- window_id: usize,
focused_view_id: Option<usize>,
text: String,
style: TooltipStyle,
action: Option<Box<dyn Action>>,
measure: bool,
- ) -> impl Element {
+ ) -> impl Drawable<V> {
Flex::row()
.with_child({
let text = if let Some(max_text_width) = style.max_text_width {
@@ -156,7 +152,6 @@ impl Tooltip {
})
.with_children(action.and_then(|action| {
let keystroke_label = KeystrokeLabel::new(
- window_id,
focused_view_id?,
action,
style.keystroke.container,
@@ -173,32 +168,40 @@ impl Tooltip {
}
}
-impl Element for Tooltip {
+impl<V: View> Drawable<V> for Tooltip<V> {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
- let size = self.child.layout(constraint, cx);
+ let size = self.child.layout(constraint, view, cx);
if let Some(tooltip) = self.tooltip.as_mut() {
- tooltip.layout(SizeConstraint::new(Vector2F::zero(), cx.window_size), cx);
+ tooltip.layout(
+ SizeConstraint::new(Vector2F::zero(), cx.window_size()),
+ view,
+ cx,
+ );
}
(size, ())
}
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) {
- self.child.paint(bounds.origin(), visible_bounds, cx);
+ self.child
+ .paint(scene, bounds.origin(), visible_bounds, view, cx);
if let Some(tooltip) = self.tooltip.as_mut() {
- tooltip.paint(bounds.origin(), visible_bounds, cx);
+ tooltip.paint(scene, bounds.origin(), visible_bounds, view, cx);
}
}
@@ -209,9 +212,10 @@ impl Element for Tooltip {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
- self.child.rect_for_text_range(range, cx)
+ self.child.rect_for_text_range(range, view, cx)
}
fn debug(
@@ -219,11 +223,12 @@ impl Element for Tooltip {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- cx: &crate::DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> serde_json::Value {
json!({
- "child": self.child.debug(cx),
- "tooltip": self.tooltip.as_ref().map(|t| t.debug(cx)),
+ "child": self.child.debug(view, cx),
+ "tooltip": self.tooltip.as_ref().map(|t| t.debug(view, cx)),
})
}
}
@@ -1,4 +1,4 @@
-use super::{Element, EventContext, LayoutContext, PaintContext, SizeConstraint};
+use super::{Drawable, SizeConstraint};
use crate::{
geometry::{
rect::RectF,
@@ -6,9 +6,7 @@ use crate::{
},
json::{self, json},
platform::ScrollWheelEvent,
- presenter::MeasurementContext,
- scene::MouseScrollWheel,
- ElementBox, MouseRegion, RenderContext, View,
+ Element, MouseRegion, SceneBuilder, View, ViewContext,
};
use json::ToJson;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -38,45 +36,38 @@ struct StateInner {
scroll_to: Option<ScrollTarget>,
}
-pub struct LayoutState {
+pub struct LayoutState<V: View> {
scroll_max: f32,
item_height: f32,
- items: Vec<ElementBox>,
+ items: Vec<Element<V>>,
}
-pub struct UniformList {
+pub struct UniformList<V: View> {
state: UniformListState,
item_count: usize,
#[allow(clippy::type_complexity)]
- append_items: Box<dyn Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext)>,
+ append_items: Box<dyn Fn(&mut V, Range<usize>, &mut Vec<Element<V>>, &mut ViewContext<V>)>,
padding_top: f32,
padding_bottom: f32,
get_width_from_item: Option<usize>,
view_id: usize,
}
-impl UniformList {
- pub fn new<F, V>(
+impl<V: View> UniformList<V> {
+ pub fn new<F>(
state: UniformListState,
item_count: usize,
- cx: &mut RenderContext<V>,
+ cx: &mut ViewContext<V>,
append_items: F,
) -> Self
where
V: View,
- F: 'static + Fn(&mut V, Range<usize>, &mut Vec<ElementBox>, &mut RenderContext<V>),
+ F: 'static + Fn(&mut V, Range<usize>, &mut Vec<Element<V>>, &mut ViewContext<V>),
{
- let handle = cx.handle();
Self {
state,
item_count,
- append_items: Box::new(move |range, items, cx| {
- if let Some(handle) = handle.upgrade(cx) {
- cx.render(&handle, |view, cx| {
- append_items(view, range, items, cx);
- });
- }
- }),
+ append_items: Box::new(append_items),
padding_top: 0.,
padding_bottom: 0.,
get_width_from_item: None,
@@ -105,7 +96,7 @@ impl UniformList {
mut delta: Vector2F,
precise: bool,
scroll_max: f32,
- cx: &mut EventContext,
+ cx: &mut ViewContext<V>,
) -> bool {
if !precise {
delta *= 20.;
@@ -160,14 +151,15 @@ impl UniformList {
}
}
-impl Element for UniformList {
- type LayoutState = LayoutState;
+impl<V: View> Drawable<V> for UniformList<V> {
+ type LayoutState = LayoutState<V>;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
if constraint.max.y().is_infinite() {
unimplemented!(
@@ -194,18 +186,18 @@ impl Element for UniformList {
let sample_item_ix;
let sample_item;
if let Some(sample_ix) = self.get_width_from_item {
- (self.append_items)(sample_ix..sample_ix + 1, &mut items, cx);
+ (self.append_items)(view, sample_ix..sample_ix + 1, &mut items, cx);
sample_item_ix = sample_ix;
if let Some(mut item) = items.pop() {
- item_size = item.layout(constraint, cx);
+ item_size = item.layout(constraint, view, cx);
size.set_x(item_size.x());
sample_item = item;
} else {
return no_items;
}
} else {
- (self.append_items)(0..1, &mut items, cx);
+ (self.append_items)(view, 0..1, &mut items, cx);
sample_item_ix = 0;
if let Some(mut item) = items.pop() {
item_size = item.layout(
@@ -213,6 +205,7 @@ impl Element for UniformList {
vec2f(constraint.max.x(), 0.0),
vec2f(constraint.max.x(), f32::INFINITY),
),
+ view,
cx,
);
item_size.set_x(size.x());
@@ -249,20 +242,20 @@ impl Element for UniformList {
if (start..end).contains(&sample_item_ix) {
if sample_item_ix > start {
- (self.append_items)(start..sample_item_ix, &mut items, cx);
+ (self.append_items)(view, start..sample_item_ix, &mut items, cx);
}
items.push(sample_item);
if sample_item_ix < end {
- (self.append_items)(sample_item_ix + 1..end, &mut items, cx);
+ (self.append_items)(view, sample_item_ix + 1..end, &mut items, cx);
}
} else {
- (self.append_items)(start..end, &mut items, cx);
+ (self.append_items)(view, start..end, &mut items, cx);
}
for item in &mut items {
- let item_size = item.layout(item_constraint, cx);
+ let item_size = item.layout(item_constraint, view, cx);
if item_size.x() > size.x() {
size.set_x(item_size.x());
}
@@ -280,27 +273,25 @@ impl Element for UniformList {
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
- cx.scene.push_layer(Some(visible_bounds));
+ scene.push_layer(Some(visible_bounds));
- cx.scene.push_mouse_region(
+ scene.push_mouse_region(
MouseRegion::new::<Self>(self.view_id, 0, visible_bounds).on_scroll({
let scroll_max = layout.scroll_max;
let state = self.state.clone();
- move |MouseScrollWheel {
- platform_event:
- ScrollWheelEvent {
- position, delta, ..
- },
- ..
- },
- cx| {
+ move |event, _, cx| {
+ let ScrollWheelEvent {
+ position, delta, ..
+ } = event.platform_event;
if !Self::scroll(
state.clone(),
position,
@@ -322,11 +313,11 @@ impl Element for UniformList {
);
for item in &mut layout.items {
- item.paint(item_origin, visible_bounds, cx);
+ item.paint(scene, item_origin, visible_bounds, view, cx);
item_origin += vec2f(0.0, layout.item_height);
}
- cx.scene.pop_layer();
+ scene.pop_layer();
}
fn rect_for_text_range(
@@ -336,12 +327,13 @@ impl Element for UniformList {
_: RectF,
layout: &Self::LayoutState,
_: &Self::PaintState,
- cx: &MeasurementContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> Option<RectF> {
layout
.items
.iter()
- .find_map(|child| child.rect_for_text_range(range.clone(), cx))
+ .find_map(|child| child.rect_for_text_range(range.clone(), view, cx))
}
fn debug(
@@ -349,14 +341,15 @@ impl Element for UniformList {
bounds: RectF,
layout: &Self::LayoutState,
_: &Self::PaintState,
- cx: &crate::DebugContext,
+ view: &V,
+ cx: &ViewContext<V>,
) -> json::Value {
json!({
"type": "UniformList",
"bounds": bounds.to_json(),
"scroll_max": layout.scroll_max,
"item_height": layout.item_height,
- "items": layout.items.iter().map(|item| item.debug(cx)).collect::<Vec<json::Value>>()
+ "items": layout.items.iter().map(|item| item.debug(view, cx)).collect::<Vec<json::Value>>()
})
}
@@ -14,13 +14,12 @@ mod clipboard;
pub use clipboard::ClipboardItem;
pub mod fonts;
pub mod geometry;
-mod presenter;
pub mod scene;
pub use scene::{Border, CursorRegion, MouseRegion, MouseRegionId, Quad, Scene, SceneBuilder};
pub mod text_layout;
pub use text_layout::TextLayoutCache;
mod util;
-pub use elements::{Element, ElementBox, ElementRc};
+pub use elements::{Drawable, Element};
pub mod executor;
pub use executor::Task;
pub mod color;
@@ -28,10 +27,7 @@ pub mod json;
pub mod keymap_matcher;
pub mod platform;
pub use gpui_macros::test;
-pub use presenter::{
- Axis, DebugContext, EventContext, LayoutContext, MeasurementContext, PaintContext,
- SizeConstraint, Vector2FExt,
-};
+pub use window::{Axis, SizeConstraint, Vector2FExt, WindowContext};
pub use anyhow;
pub use serde_json;
@@ -63,7 +63,7 @@ pub trait Platform: Send + Sync {
) -> Box<dyn Window>;
fn main_window_id(&self) -> Option<usize>;
- fn add_status_item(&self) -> Box<dyn Window>;
+ fn add_status_item(&self, id: usize) -> Box<dyn Window>;
fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
@@ -601,7 +601,7 @@ impl platform::Platform for MacPlatform {
Window::main_window_id()
}
- fn add_status_item(&self) -> Box<dyn platform::Window> {
+ fn add_status_item(&self, _id: usize) -> Box<dyn platform::Window> {
Box::new(StatusItem::add(self.fonts()))
}
@@ -102,6 +102,7 @@ pub struct Platform {
fonts: Arc<dyn super::FontSystem>,
current_clipboard_item: Mutex<Option<ClipboardItem>>,
cursor: Mutex<CursorStyle>,
+ active_window_id: Arc<Mutex<Option<usize>>>,
}
impl Platform {
@@ -111,6 +112,7 @@ impl Platform {
fonts: Arc::new(super::current::FontSystem::new()),
current_clipboard_item: Default::default(),
cursor: Mutex::new(CursorStyle::Arrow),
+ active_window_id: Default::default(),
}
}
}
@@ -144,22 +146,31 @@ impl super::Platform for Platform {
fn open_window(
&self,
- _: usize,
+ id: usize,
options: super::WindowOptions,
_executor: Rc<super::executor::Foreground>,
) -> Box<dyn super::Window> {
- Box::new(Window::new(match options.bounds {
- WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.),
- WindowBounds::Fixed(rect) => rect.size(),
- }))
+ *self.active_window_id.lock() = Some(id);
+ Box::new(Window::new(
+ id,
+ match options.bounds {
+ WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.),
+ WindowBounds::Fixed(rect) => rect.size(),
+ },
+ self.active_window_id.clone(),
+ ))
}
fn main_window_id(&self) -> Option<usize> {
- None
+ self.active_window_id.lock().clone()
}
- fn add_status_item(&self) -> Box<dyn crate::platform::Window> {
- Box::new(Window::new(vec2f(24., 24.)))
+ fn add_status_item(&self, id: usize) -> Box<dyn crate::platform::Window> {
+ Box::new(Window::new(
+ id,
+ vec2f(24., 24.),
+ self.active_window_id.clone(),
+ ))
}
fn write_to_clipboard(&self, item: ClipboardItem) {
@@ -245,6 +256,7 @@ impl super::Screen for Screen {
}
pub struct Window {
+ id: usize,
pub(crate) size: Vector2F,
scale_factor: f32,
current_scene: Option<crate::Scene>,
@@ -258,11 +270,13 @@ pub struct Window {
pub(crate) title: Option<String>,
pub(crate) edited: bool,
pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
+ active_window_id: Arc<Mutex<Option<usize>>>,
}
impl Window {
- fn new(size: Vector2F) -> Self {
+ pub fn new(id: usize, size: Vector2F, active_window_id: Arc<Mutex<Option<usize>>>) -> Self {
Self {
+ id,
size,
event_handlers: Default::default(),
resize_handlers: Default::default(),
@@ -276,6 +290,7 @@ impl Window {
title: None,
edited: false,
pending_prompts: Default::default(),
+ active_window_id,
}
}
@@ -326,7 +341,9 @@ impl super::Window for Window {
done_rx
}
- fn activate(&self) {}
+ fn activate(&self) {
+ *self.active_window_id.lock() = Some(self.id);
+ }
fn set_title(&mut self, title: &str) {
self.title = Some(title.to_string())
@@ -1,1154 +0,0 @@
-use crate::{
- app::WindowInvalidation,
- elements::Element,
- font_cache::FontCache,
- geometry::rect::RectF,
- json::{self, ToJson},
- platform::{Appearance, CursorStyle, Event, FontSystem, MouseButton, MouseMovedEvent},
- scene::{
- CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover,
- MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene,
- },
- text_layout::TextLayoutCache,
- Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext,
- AssetCache, ElementBox, Entity, ModelHandle, MouseRegion, MouseRegionId, MouseState, ParentId,
- ReadModel, ReadView, RenderContext, RenderParams, SceneBuilder, UpgradeModelHandle,
- UpgradeViewHandle, View, ViewHandle, WeakModelHandle, WeakViewHandle,
-};
-use anyhow::bail;
-use collections::{HashMap, HashSet};
-use pathfinder_geometry::vector::{vec2f, Vector2F};
-use serde_json::json;
-use smallvec::SmallVec;
-use sqlez::{
- bindable::{Bind, Column, StaticColumnCount},
- statement::Statement,
-};
-use std::{
- marker::PhantomData,
- ops::{Deref, DerefMut, Range},
- sync::Arc,
-};
-
-pub struct Presenter {
- window_id: usize,
- pub(crate) rendered_views: HashMap<usize, ElementBox>,
- cursor_regions: Vec<CursorRegion>,
- mouse_regions: Vec<(MouseRegion, usize)>,
- font_cache: Arc<FontCache>,
- text_layout_cache: TextLayoutCache,
- asset_cache: Arc<AssetCache>,
- last_mouse_moved_event: Option<Event>,
- hovered_region_ids: HashSet<MouseRegionId>,
- clicked_region_ids: HashSet<MouseRegionId>,
- clicked_button: Option<MouseButton>,
- mouse_position: Vector2F,
- titlebar_height: f32,
- appearance: Appearance,
-}
-
-impl Presenter {
- pub fn new(
- window_id: usize,
- titlebar_height: f32,
- appearance: Appearance,
- font_cache: Arc<FontCache>,
- text_layout_cache: TextLayoutCache,
- asset_cache: Arc<AssetCache>,
- cx: &mut AppContext,
- ) -> Self {
- Self {
- window_id,
- rendered_views: cx.render_views(window_id, titlebar_height, appearance),
- cursor_regions: Default::default(),
- mouse_regions: Default::default(),
- font_cache,
- text_layout_cache,
- asset_cache,
- last_mouse_moved_event: None,
- hovered_region_ids: Default::default(),
- clicked_region_ids: Default::default(),
- clicked_button: None,
- mouse_position: vec2f(0., 0.),
- titlebar_height,
- appearance,
- }
- }
-
- pub fn invalidate(
- &mut self,
- invalidation: &mut WindowInvalidation,
- appearance: Appearance,
- cx: &mut AppContext,
- ) {
- cx.start_frame();
- self.appearance = appearance;
- for view_id in &invalidation.removed {
- invalidation.updated.remove(view_id);
- self.rendered_views.remove(view_id);
- }
- for view_id in &invalidation.updated {
- self.rendered_views.insert(
- *view_id,
- cx.render_view(RenderParams {
- window_id: self.window_id,
- view_id: *view_id,
- titlebar_height: self.titlebar_height,
- hovered_region_ids: self.hovered_region_ids.clone(),
- clicked_region_ids: self
- .clicked_button
- .map(|button| (self.clicked_region_ids.clone(), button)),
- refreshing: false,
- appearance,
- })
- .unwrap(),
- );
- }
- }
-
- pub fn refresh(
- &mut self,
- invalidation: &mut WindowInvalidation,
- appearance: Appearance,
- cx: &mut AppContext,
- ) {
- self.invalidate(invalidation, appearance, cx);
- for (view_id, view) in &mut self.rendered_views {
- if !invalidation.updated.contains(view_id) {
- *view = cx
- .render_view(RenderParams {
- window_id: self.window_id,
- view_id: *view_id,
- titlebar_height: self.titlebar_height,
- hovered_region_ids: self.hovered_region_ids.clone(),
- clicked_region_ids: self
- .clicked_button
- .map(|button| (self.clicked_region_ids.clone(), button)),
- refreshing: true,
- appearance,
- })
- .unwrap();
- }
- }
- }
-
- pub fn build_scene(
- &mut self,
- window_size: Vector2F,
- scale_factor: f32,
- refreshing: bool,
- cx: &mut AppContext,
- ) -> Scene {
- let mut scene_builder = SceneBuilder::new(scale_factor);
-
- if let Some(root_view_id) = cx.root_view_id(self.window_id) {
- self.layout(window_size, refreshing, cx);
- let mut paint_cx = self.build_paint_context(&mut scene_builder, window_size, cx);
- paint_cx.paint(
- root_view_id,
- Vector2F::zero(),
- RectF::new(Vector2F::zero(), window_size),
- );
- self.text_layout_cache.finish_frame();
- let scene = scene_builder.build();
- self.cursor_regions = scene.cursor_regions();
- self.mouse_regions = scene.mouse_regions();
-
- // window.is_topmost for the mouse moved event's postion?
- if cx.window_is_active(self.window_id) {
- if let Some(event) = self.last_mouse_moved_event.clone() {
- self.dispatch_event(event, true, cx);
- }
- }
-
- scene
- } else {
- log::error!("could not find root_view_id for window {}", self.window_id);
- scene_builder.build()
- }
- }
-
- fn layout(&mut self, window_size: Vector2F, refreshing: bool, cx: &mut AppContext) {
- if let Some(root_view_id) = cx.root_view_id(self.window_id) {
- self.build_layout_context(window_size, refreshing, cx)
- .layout(root_view_id, SizeConstraint::strict(window_size));
- }
- }
-
- pub fn build_layout_context<'a>(
- &'a mut self,
- window_size: Vector2F,
- refreshing: bool,
- cx: &'a mut AppContext,
- ) -> LayoutContext<'a> {
- LayoutContext {
- window_id: self.window_id,
- rendered_views: &mut self.rendered_views,
- font_cache: &self.font_cache,
- font_system: cx.platform().fonts(),
- text_layout_cache: &self.text_layout_cache,
- asset_cache: &self.asset_cache,
- view_stack: Vec::new(),
- refreshing,
- hovered_region_ids: self.hovered_region_ids.clone(),
- clicked_region_ids: self
- .clicked_button
- .map(|button| (self.clicked_region_ids.clone(), button)),
- titlebar_height: self.titlebar_height,
- appearance: self.appearance,
- window_size,
- app: cx,
- }
- }
-
- pub fn build_paint_context<'a>(
- &'a mut self,
- scene: &'a mut SceneBuilder,
- window_size: Vector2F,
- cx: &'a mut AppContext,
- ) -> PaintContext {
- PaintContext {
- scene,
- window_size,
- font_cache: &self.font_cache,
- text_layout_cache: &self.text_layout_cache,
- rendered_views: &mut self.rendered_views,
- view_stack: Vec::new(),
- app: cx,
- }
- }
-
- pub fn rect_for_text_range(&self, range_utf16: Range<usize>, cx: &AppContext) -> Option<RectF> {
- cx.focused_view_id(self.window_id).and_then(|view_id| {
- let cx = MeasurementContext {
- app: cx,
- rendered_views: &self.rendered_views,
- window_id: self.window_id,
- };
- cx.rect_for_text_range(view_id, range_utf16)
- })
- }
-
- pub fn dispatch_event(
- &mut self,
- event: Event,
- event_reused: bool,
- cx: &mut AppContext,
- ) -> bool {
- let mut mouse_events = SmallVec::<[_; 2]>::new();
- let mut notified_views: HashSet<usize> = Default::default();
-
- // 1. Handle platform event. Keyboard events get dispatched immediately, while mouse events
- // get mapped into the mouse-specific MouseEvent type.
- // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?]
- // -> Also updates mouse-related state
- match &event {
- Event::KeyDown(e) => return cx.dispatch_key_down(self.window_id, e),
-
- Event::KeyUp(e) => return cx.dispatch_key_up(self.window_id, e),
-
- Event::ModifiersChanged(e) => return cx.dispatch_modifiers_changed(self.window_id, e),
-
- Event::MouseDown(e) => {
- // Click events are weird because they can be fired after a drag event.
- // MDN says that browsers handle this by starting from 'the most
- // specific ancestor element that contained both [positions]'
- // So we need to store the overlapping regions on mouse down.
-
- // If there is already clicked_button stored, don't replace it.
- if self.clicked_button.is_none() {
- self.clicked_region_ids = self
- .mouse_regions
- .iter()
- .filter_map(|(region, _)| {
- if region.bounds.contains_point(e.position) {
- Some(region.id())
- } else {
- None
- }
- })
- .collect();
-
- self.clicked_button = Some(e.button);
- }
-
- mouse_events.push(MouseEvent::Down(MouseDown {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- mouse_events.push(MouseEvent::DownOut(MouseDownOut {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- }
-
- Event::MouseUp(e) => {
- // NOTE: The order of event pushes is important! MouseUp events MUST be fired
- // before click events, and so the MouseUp events need to be pushed before
- // MouseClick events.
- mouse_events.push(MouseEvent::Up(MouseUp {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- mouse_events.push(MouseEvent::UpOut(MouseUpOut {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- mouse_events.push(MouseEvent::Click(MouseClick {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- }
-
- Event::MouseMoved(
- e @ MouseMovedEvent {
- position,
- pressed_button,
- ..
- },
- ) => {
- let mut style_to_assign = CursorStyle::Arrow;
- for region in self.cursor_regions.iter().rev() {
- if region.bounds.contains_point(*position) {
- style_to_assign = region.style;
- break;
- }
- }
-
- if cx.is_topmost_window_for_position(self.window_id, *position) {
- cx.platform().set_cursor_style(style_to_assign);
- }
-
- if !event_reused {
- if pressed_button.is_some() {
- mouse_events.push(MouseEvent::Drag(MouseDrag {
- region: Default::default(),
- prev_mouse_position: self.mouse_position,
- platform_event: e.clone(),
- }));
- } else if let Some(clicked_button) = self.clicked_button {
- // Mouse up event happened outside the current window. Simulate mouse up button event
- let button_event = e.to_button_event(clicked_button);
- mouse_events.push(MouseEvent::Up(MouseUp {
- region: Default::default(),
- platform_event: button_event.clone(),
- }));
- mouse_events.push(MouseEvent::UpOut(MouseUpOut {
- region: Default::default(),
- platform_event: button_event.clone(),
- }));
- mouse_events.push(MouseEvent::Click(MouseClick {
- region: Default::default(),
- platform_event: button_event.clone(),
- }));
- }
-
- mouse_events.push(MouseEvent::Move(MouseMove {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- }
-
- mouse_events.push(MouseEvent::Hover(MouseHover {
- region: Default::default(),
- platform_event: e.clone(),
- started: false,
- }));
- mouse_events.push(MouseEvent::MoveOut(MouseMoveOut {
- region: Default::default(),
- }));
-
- self.last_mouse_moved_event = Some(event.clone());
- }
-
- Event::MouseExited(event) => {
- // When the platform sends a MouseExited event, synthesize
- // a MouseMoved event whose position is outside the window's
- // bounds so that hover and cursor state can be updated.
- return self.dispatch_event(
- Event::MouseMoved(MouseMovedEvent {
- position: event.position,
- pressed_button: event.pressed_button,
- modifiers: event.modifiers,
- }),
- event_reused,
- cx,
- );
- }
-
- Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel {
- region: Default::default(),
- platform_event: e.clone(),
- })),
- }
-
- if let Some(position) = event.position() {
- self.mouse_position = position;
- }
-
- // 2. Dispatch mouse events on regions
- let mut any_event_handled = false;
- for mut mouse_event in mouse_events {
- let mut valid_regions = Vec::new();
-
- // GPUI elements are arranged by z_index but sibling elements can register overlapping
- // mouse regions. As such, hover events are only fired on overlapping elements which
- // are at the same z-index as the topmost element which overlaps with the mouse.
- match &mouse_event {
- MouseEvent::Hover(_) => {
- let mut highest_z_index = None;
- let mouse_position = self.mouse_position.clone();
- for (region, z_index) in self.mouse_regions.iter().rev() {
- // Allow mouse regions to appear transparent to hovers
- if !region.hoverable {
- continue;
- }
-
- let contains_mouse = region.bounds.contains_point(mouse_position);
-
- if contains_mouse && highest_z_index.is_none() {
- highest_z_index = Some(z_index);
- }
-
- // This unwrap relies on short circuiting boolean expressions
- // The right side of the && is only executed when contains_mouse
- // is true, and we know above that when contains_mouse is true
- // highest_z_index is set.
- if contains_mouse && z_index == highest_z_index.unwrap() {
- //Ensure that hover entrance events aren't sent twice
- if self.hovered_region_ids.insert(region.id()) {
- valid_regions.push(region.clone());
- if region.notify_on_hover {
- notified_views.insert(region.id().view_id());
- }
- }
- } else {
- // Ensure that hover exit events aren't sent twice
- if self.hovered_region_ids.remove(®ion.id()) {
- valid_regions.push(region.clone());
- if region.notify_on_hover {
- notified_views.insert(region.id().view_id());
- }
- }
- }
- }
- }
-
- MouseEvent::Down(_) | MouseEvent::Up(_) => {
- for (region, _) in self.mouse_regions.iter().rev() {
- if region.bounds.contains_point(self.mouse_position) {
- valid_regions.push(region.clone());
- if region.notify_on_click {
- notified_views.insert(region.id().view_id());
- }
- }
- }
- }
-
- MouseEvent::Click(e) => {
- // Only raise click events if the released button is the same as the one stored
- if self
- .clicked_button
- .map(|clicked_button| clicked_button == e.button)
- .unwrap_or(false)
- {
- // Clear clicked regions and clicked button
- let clicked_region_ids =
- std::mem::replace(&mut self.clicked_region_ids, Default::default());
- self.clicked_button = None;
-
- // Find regions which still overlap with the mouse since the last MouseDown happened
- for (mouse_region, _) in self.mouse_regions.iter().rev() {
- if clicked_region_ids.contains(&mouse_region.id()) {
- if mouse_region.bounds.contains_point(self.mouse_position) {
- valid_regions.push(mouse_region.clone());
- }
- }
- }
- }
- }
-
- MouseEvent::Drag(_) => {
- for (mouse_region, _) in self.mouse_regions.iter().rev() {
- if self.clicked_region_ids.contains(&mouse_region.id()) {
- valid_regions.push(mouse_region.clone());
- }
- }
- }
-
- MouseEvent::MoveOut(_) | MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => {
- for (mouse_region, _) in self.mouse_regions.iter().rev() {
- // NOT contains
- if !mouse_region.bounds.contains_point(self.mouse_position) {
- valid_regions.push(mouse_region.clone());
- }
- }
- }
-
- _ => {
- for (mouse_region, _) in self.mouse_regions.iter().rev() {
- // Contains
- if mouse_region.bounds.contains_point(self.mouse_position) {
- valid_regions.push(mouse_region.clone());
- }
- }
- }
- }
-
- //3. Fire region events
- let hovered_region_ids = self.hovered_region_ids.clone();
- for valid_region in valid_regions.into_iter() {
- let mut event_cx = self.build_event_context(&mut notified_views, cx);
-
- mouse_event.set_region(valid_region.bounds);
- if let MouseEvent::Hover(e) = &mut mouse_event {
- e.started = hovered_region_ids.contains(&valid_region.id())
- }
- // Handle Down events if the MouseRegion has a Click or Drag handler. This makes the api more intuitive as you would
- // not expect a MouseRegion to be transparent to Down events if it also has a Click handler.
- // This behavior can be overridden by adding a Down handler
- if let MouseEvent::Down(e) = &mouse_event {
- let has_click = valid_region
- .handlers
- .contains(MouseEvent::click_disc(), Some(e.button));
- let has_drag = valid_region
- .handlers
- .contains(MouseEvent::drag_disc(), Some(e.button));
- let has_down = valid_region
- .handlers
- .contains(MouseEvent::down_disc(), Some(e.button));
- if !has_down && (has_click || has_drag) {
- event_cx.handled = true;
- }
- }
-
- // `event_consumed` should only be true if there are any handlers for this event.
- let mut event_consumed = event_cx.handled;
- if let Some(callbacks) = valid_region.handlers.get(&mouse_event.handler_key()) {
- for callback in callbacks {
- event_cx.handled = true;
- event_cx.with_current_view(valid_region.id().view_id(), {
- let region_event = mouse_event.clone();
- |cx| callback(region_event, cx)
- });
- event_consumed |= event_cx.handled;
- any_event_handled |= event_cx.handled;
- }
- }
-
- any_event_handled |= event_cx.handled;
-
- // For bubbling events, if the event was handled, don't continue dispatching.
- // This only makes sense for local events which return false from is_capturable.
- if event_consumed && mouse_event.is_capturable() {
- break;
- }
- }
- }
-
- for view_id in notified_views {
- cx.notify_view(self.window_id, view_id);
- }
-
- any_event_handled
- }
-
- pub fn build_event_context<'a>(
- &'a mut self,
- notified_views: &'a mut HashSet<usize>,
- cx: &'a mut AppContext,
- ) -> EventContext<'a> {
- EventContext {
- font_cache: &self.font_cache,
- text_layout_cache: &self.text_layout_cache,
- view_stack: Default::default(),
- notified_views,
- notify_count: 0,
- handled: false,
- window_id: self.window_id,
- app: cx,
- }
- }
-
- pub fn debug_elements(&self, cx: &AppContext) -> Option<json::Value> {
- let view = cx.root_view(self.window_id)?;
- Some(json!({
- "root_view": view.debug_json(cx),
- "root_element": self.rendered_views.get(&view.id())
- .map(|root_element| {
- root_element.debug(&DebugContext {
- rendered_views: &self.rendered_views,
- font_cache: &self.font_cache,
- app: cx,
- })
- })
- }))
- }
-}
-
-pub struct LayoutContext<'a> {
- window_id: usize,
- rendered_views: &'a mut HashMap<usize, ElementBox>,
- view_stack: Vec<usize>,
- pub font_cache: &'a Arc<FontCache>,
- pub font_system: Arc<dyn FontSystem>,
- pub text_layout_cache: &'a TextLayoutCache,
- pub asset_cache: &'a AssetCache,
- pub app: &'a mut AppContext,
- pub refreshing: bool,
- pub window_size: Vector2F,
- titlebar_height: f32,
- appearance: Appearance,
- hovered_region_ids: HashSet<MouseRegionId>,
- clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
-}
-
-impl<'a> LayoutContext<'a> {
- pub fn mouse_state<Tag: 'static>(&self, region_id: usize) -> MouseState {
- let view_id = self.view_stack.last().unwrap();
-
- let region_id = MouseRegionId::new::<Tag>(*view_id, region_id);
- MouseState {
- hovered: self.hovered_region_ids.contains(®ion_id),
- clicked: self.clicked_region_ids.as_ref().and_then(|(ids, button)| {
- if ids.contains(®ion_id) {
- Some(*button)
- } else {
- None
- }
- }),
- accessed_hovered: false,
- accessed_clicked: false,
- }
- }
-
- fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F {
- let print_error = |view_id| {
- format!(
- "{} with id {}",
- self.app.name_for_view(self.window_id, view_id).unwrap(),
- view_id,
- )
- };
- match (
- self.view_stack.last(),
- self.app.parents.get(&(self.window_id, view_id)),
- ) {
- (Some(layout_parent), Some(ParentId::View(app_parent))) => {
- if layout_parent != app_parent {
- panic!(
- "View {} was laid out with parent {} when it was constructed with parent {}",
- print_error(view_id),
- print_error(*layout_parent),
- print_error(*app_parent))
- }
- }
- (None, Some(ParentId::View(app_parent))) => panic!(
- "View {} was laid out without a parent when it was constructed with parent {}",
- print_error(view_id),
- print_error(*app_parent)
- ),
- (Some(layout_parent), Some(ParentId::Root)) => panic!(
- "View {} was laid out with parent {} when it was constructed as a window root",
- print_error(view_id),
- print_error(*layout_parent),
- ),
- (_, None) => panic!(
- "View {} did not have a registered parent in the app context",
- print_error(view_id),
- ),
- _ => {}
- }
-
- self.view_stack.push(view_id);
- let mut rendered_view = self.rendered_views.remove(&view_id).unwrap();
- let size = rendered_view.layout(constraint, self);
- self.rendered_views.insert(view_id, rendered_view);
- self.view_stack.pop();
- size
- }
-
- pub fn render<F, V, T>(&mut self, handle: &ViewHandle<V>, f: F) -> T
- where
- F: FnOnce(&mut V, &mut RenderContext<V>) -> T,
- V: View,
- {
- handle.update(self.app, |view, cx| {
- let mut render_cx = RenderContext {
- app: cx,
- window_id: handle.window_id(),
- view_id: handle.id(),
- view_type: PhantomData,
- titlebar_height: self.titlebar_height,
- hovered_region_ids: self.hovered_region_ids.clone(),
- clicked_region_ids: self.clicked_region_ids.clone(),
- refreshing: self.refreshing,
- appearance: self.appearance,
- };
- f(view, &mut render_cx)
- })
- }
-}
-
-impl<'a> Deref for LayoutContext<'a> {
- type Target = AppContext;
-
- fn deref(&self) -> &Self::Target {
- self.app
- }
-}
-
-impl<'a> DerefMut for LayoutContext<'a> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- self.app
- }
-}
-
-impl<'a> ReadView for LayoutContext<'a> {
- fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
- self.app.read_view(handle)
- }
-}
-
-impl<'a> ReadModel for LayoutContext<'a> {
- fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
- self.app.read_model(handle)
- }
-}
-
-impl<'a> UpgradeModelHandle for LayoutContext<'a> {
- fn upgrade_model_handle<T: Entity>(
- &self,
- handle: &WeakModelHandle<T>,
- ) -> Option<ModelHandle<T>> {
- self.app.upgrade_model_handle(handle)
- }
-
- fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
- self.app.model_handle_is_upgradable(handle)
- }
-
- fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
- self.app.upgrade_any_model_handle(handle)
- }
-}
-
-impl<'a> UpgradeViewHandle for LayoutContext<'a> {
- fn upgrade_view_handle<T: View>(&self, handle: &WeakViewHandle<T>) -> Option<ViewHandle<T>> {
- self.app.upgrade_view_handle(handle)
- }
-
- fn upgrade_any_view_handle(&self, handle: &crate::AnyWeakViewHandle) -> Option<AnyViewHandle> {
- self.app.upgrade_any_view_handle(handle)
- }
-}
-
-pub struct PaintContext<'a> {
- rendered_views: &'a mut HashMap<usize, ElementBox>,
- view_stack: Vec<usize>,
- pub window_size: Vector2F,
- pub scene: &'a mut SceneBuilder,
- pub font_cache: &'a FontCache,
- pub text_layout_cache: &'a TextLayoutCache,
- pub app: &'a AppContext,
-}
-
-impl<'a> PaintContext<'a> {
- fn paint(&mut self, view_id: usize, origin: Vector2F, visible_bounds: RectF) {
- if let Some(mut tree) = self.rendered_views.remove(&view_id) {
- self.view_stack.push(view_id);
- tree.paint(origin, visible_bounds, self);
- self.rendered_views.insert(view_id, tree);
- self.view_stack.pop();
- }
- }
-
- #[inline]
- pub fn paint_stacking_context<F>(
- &mut self,
- clip_bounds: Option<RectF>,
- z_index: Option<usize>,
- f: F,
- ) where
- F: FnOnce(&mut Self),
- {
- self.scene.push_stacking_context(clip_bounds, z_index);
- f(self);
- self.scene.pop_stacking_context();
- }
-
- #[inline]
- pub fn paint_layer<F>(&mut self, clip_bounds: Option<RectF>, f: F)
- where
- F: FnOnce(&mut Self),
- {
- self.scene.push_layer(clip_bounds);
- f(self);
- self.scene.pop_layer();
- }
-
- pub fn current_view_id(&self) -> usize {
- *self.view_stack.last().unwrap()
- }
-}
-
-impl<'a> Deref for PaintContext<'a> {
- type Target = AppContext;
-
- fn deref(&self) -> &Self::Target {
- self.app
- }
-}
-
-pub struct EventContext<'a> {
- pub font_cache: &'a FontCache,
- pub text_layout_cache: &'a TextLayoutCache,
- pub app: &'a mut AppContext,
- pub window_id: usize,
- pub notify_count: usize,
- view_stack: Vec<usize>,
- handled: bool,
- notified_views: &'a mut HashSet<usize>,
-}
-
-impl<'a> EventContext<'a> {
- fn with_current_view<F, T>(&mut self, view_id: usize, f: F) -> T
- where
- F: FnOnce(&mut Self) -> T,
- {
- self.view_stack.push(view_id);
- let result = f(self);
- self.view_stack.pop();
- result
- }
-
- pub fn window_id(&self) -> usize {
- self.window_id
- }
-
- pub fn view_id(&self) -> Option<usize> {
- self.view_stack.last().copied()
- }
-
- pub fn is_parent_view_focused(&self) -> bool {
- if let Some(parent_view_id) = self.view_stack.last() {
- self.app.focused_view_id(self.window_id) == Some(*parent_view_id)
- } else {
- false
- }
- }
-
- pub fn focus_parent_view(&mut self) {
- if let Some(parent_view_id) = self.view_stack.last() {
- self.app.focus(self.window_id, Some(*parent_view_id))
- }
- }
-
- pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
- self.app
- .dispatch_any_action_at(self.window_id, *self.view_stack.last().unwrap(), action)
- }
-
- pub fn dispatch_action<A: Action>(&mut self, action: A) {
- self.dispatch_any_action(Box::new(action));
- }
-
- pub fn notify(&mut self) {
- self.notify_count += 1;
- if let Some(view_id) = self.view_stack.last() {
- self.notified_views.insert(*view_id);
- }
- }
-
- pub fn notify_count(&self) -> usize {
- self.notify_count
- }
-
- pub fn propagate_event(&mut self) {
- self.handled = false;
- }
-}
-
-impl<'a> Deref for EventContext<'a> {
- type Target = AppContext;
-
- fn deref(&self) -> &Self::Target {
- self.app
- }
-}
-
-impl<'a> DerefMut for EventContext<'a> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- self.app
- }
-}
-
-pub struct MeasurementContext<'a> {
- app: &'a AppContext,
- rendered_views: &'a HashMap<usize, ElementBox>,
- pub window_id: usize,
-}
-
-impl<'a> Deref for MeasurementContext<'a> {
- type Target = AppContext;
-
- fn deref(&self) -> &Self::Target {
- self.app
- }
-}
-
-impl<'a> MeasurementContext<'a> {
- fn rect_for_text_range(&self, view_id: usize, range_utf16: Range<usize>) -> Option<RectF> {
- let element = self.rendered_views.get(&view_id)?;
- element.rect_for_text_range(range_utf16, self)
- }
-}
-
-pub struct DebugContext<'a> {
- rendered_views: &'a HashMap<usize, ElementBox>,
- pub font_cache: &'a FontCache,
- pub app: &'a AppContext,
-}
-
-#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
-pub enum Axis {
- #[default]
- Horizontal,
- Vertical,
-}
-
-impl Axis {
- pub fn invert(self) -> Self {
- match self {
- Self::Horizontal => Self::Vertical,
- Self::Vertical => Self::Horizontal,
- }
- }
-
- pub fn component(&self, point: Vector2F) -> f32 {
- match self {
- Self::Horizontal => point.x(),
- Self::Vertical => point.y(),
- }
- }
-}
-
-impl ToJson for Axis {
- fn to_json(&self) -> serde_json::Value {
- match self {
- Axis::Horizontal => json!("horizontal"),
- Axis::Vertical => json!("vertical"),
- }
- }
-}
-
-impl StaticColumnCount for Axis {}
-impl Bind for Axis {
- fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
- match self {
- Axis::Horizontal => "Horizontal",
- Axis::Vertical => "Vertical",
- }
- .bind(statement, start_index)
- }
-}
-
-impl Column for Axis {
- fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
- String::column(statement, start_index).and_then(|(axis_text, next_index)| {
- Ok((
- match axis_text.as_str() {
- "Horizontal" => Axis::Horizontal,
- "Vertical" => Axis::Vertical,
- _ => bail!("Stored serialized item kind is incorrect"),
- },
- next_index,
- ))
- })
- }
-}
-
-pub trait Vector2FExt {
- fn along(self, axis: Axis) -> f32;
-}
-
-impl Vector2FExt for Vector2F {
- fn along(self, axis: Axis) -> f32 {
- match axis {
- Axis::Horizontal => self.x(),
- Axis::Vertical => self.y(),
- }
- }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct SizeConstraint {
- pub min: Vector2F,
- pub max: Vector2F,
-}
-
-impl SizeConstraint {
- pub fn new(min: Vector2F, max: Vector2F) -> Self {
- Self { min, max }
- }
-
- pub fn strict(size: Vector2F) -> Self {
- Self {
- min: size,
- max: size,
- }
- }
-
- pub fn strict_along(axis: Axis, max: f32) -> Self {
- match axis {
- Axis::Horizontal => Self {
- min: vec2f(max, 0.0),
- max: vec2f(max, f32::INFINITY),
- },
- Axis::Vertical => Self {
- min: vec2f(0.0, max),
- max: vec2f(f32::INFINITY, max),
- },
- }
- }
-
- pub fn max_along(&self, axis: Axis) -> f32 {
- match axis {
- Axis::Horizontal => self.max.x(),
- Axis::Vertical => self.max.y(),
- }
- }
-
- pub fn min_along(&self, axis: Axis) -> f32 {
- match axis {
- Axis::Horizontal => self.min.x(),
- Axis::Vertical => self.min.y(),
- }
- }
-
- pub fn constrain(&self, size: Vector2F) -> Vector2F {
- vec2f(
- size.x().min(self.max.x()).max(self.min.x()),
- size.y().min(self.max.y()).max(self.min.y()),
- )
- }
-}
-
-impl Default for SizeConstraint {
- fn default() -> Self {
- SizeConstraint {
- min: Vector2F::zero(),
- max: Vector2F::splat(f32::INFINITY),
- }
- }
-}
-
-impl ToJson for SizeConstraint {
- fn to_json(&self) -> serde_json::Value {
- json!({
- "min": self.min.to_json(),
- "max": self.max.to_json(),
- })
- }
-}
-
-pub struct ChildView {
- view: AnyWeakViewHandle,
- view_name: &'static str,
-}
-
-impl ChildView {
- pub fn new(view: &AnyViewHandle, cx: &AppContext) -> Self {
- let view_name = cx.view_ui_name(view.window_id(), view.id()).unwrap();
- Self {
- view: view.downgrade(),
- view_name,
- }
- }
-}
-
-impl Element for ChildView {
- type LayoutState = bool;
- type PaintState = ();
-
- fn layout(
- &mut self,
- constraint: SizeConstraint,
- cx: &mut LayoutContext,
- ) -> (Vector2F, Self::LayoutState) {
- if cx.rendered_views.contains_key(&self.view.id()) {
- let size = cx.layout(self.view.id(), constraint);
- (size, true)
- } else {
- log::error!(
- "layout called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
- self.view.id(),
- self.view_name
- );
- (Vector2F::zero(), false)
- }
- }
-
- fn paint(
- &mut self,
- bounds: RectF,
- visible_bounds: RectF,
- view_is_valid: &mut Self::LayoutState,
- cx: &mut PaintContext,
- ) {
- if *view_is_valid {
- cx.paint(self.view.id(), bounds.origin(), visible_bounds);
- } else {
- log::error!(
- "paint called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
- self.view.id(),
- self.view_name
- );
- }
- }
-
- fn rect_for_text_range(
- &self,
- range_utf16: Range<usize>,
- _: RectF,
- _: RectF,
- view_is_valid: &Self::LayoutState,
- _: &Self::PaintState,
- cx: &MeasurementContext,
- ) -> Option<RectF> {
- if *view_is_valid {
- cx.rect_for_text_range(self.view.id(), range_utf16)
- } else {
- log::error!(
- "rect_for_text_range called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
- self.view.id(),
- self.view_name
- );
- None
- }
- }
-
- fn debug(
- &self,
- bounds: RectF,
- _: &Self::LayoutState,
- _: &Self::PaintState,
- cx: &DebugContext,
- ) -> serde_json::Value {
- json!({
- "type": "ChildView",
- "view_id": self.view.id(),
- "bounds": bounds.to_json(),
- "view": if let Some(view) = self.view.upgrade(cx.app) {
- view.debug_json(cx.app)
- } else {
- json!(null)
- },
- "child": if let Some(view) = cx.rendered_views.get(&self.view.id()) {
- view.debug(cx)
- } else {
- json!(null)
- }
- })
- }
-}
@@ -236,6 +236,19 @@ impl SceneBuilder {
self.scale_factor
}
+ pub fn paint_stacking_context<F>(
+ &mut self,
+ clip_bounds: Option<RectF>,
+ z_index: Option<usize>,
+ f: F,
+ ) where
+ F: FnOnce(&mut Self),
+ {
+ self.push_stacking_context(clip_bounds, z_index);
+ f(self);
+ self.pop_stacking_context();
+ }
+
pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>, z_index: Option<usize>) {
let z_index = z_index.unwrap_or_else(|| self.active_stacking_context().z_index + 1);
self.active_stacking_context_stack
@@ -249,6 +262,15 @@ impl SceneBuilder {
assert!(!self.active_stacking_context_stack.is_empty());
}
+ pub fn paint_layer<F>(&mut self, clip_bounds: Option<RectF>, f: F)
+ where
+ F: FnOnce(&mut Self),
+ {
+ self.push_layer(clip_bounds);
+ f(self);
+ self.pop_layer();
+ }
+
pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {
self.active_stacking_context().push_layer(clip_bounds);
}
@@ -1,11 +1,13 @@
-use std::{any::TypeId, fmt::Debug, mem::Discriminant, rc::Rc};
-
+use crate::{platform::MouseButton, window::WindowContext, EventContext, View, ViewContext};
use collections::HashMap;
-
use pathfinder_geometry::rect::RectF;
use smallvec::SmallVec;
-
-use crate::{platform::MouseButton, EventContext};
+use std::{
+ any::{Any, TypeId},
+ fmt::Debug,
+ mem::Discriminant,
+ rc::Rc,
+};
use super::{
mouse_event::{
@@ -60,82 +62,92 @@ impl MouseRegion {
}
}
- pub fn on_down(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseDown, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
+ {
self.handlers = self.handlers.on_down(button, handler);
self
}
- pub fn on_up(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseUp, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
+ {
self.handlers = self.handlers.on_up(button, handler);
self
}
- pub fn on_click(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseClick, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
+ {
self.handlers = self.handlers.on_click(button, handler);
self
}
- pub fn on_down_out(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseDownOut, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
+ {
self.handlers = self.handlers.on_down_out(button, handler);
self
}
- pub fn on_up_out(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseUpOut, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
+ {
self.handlers = self.handlers.on_up_out(button, handler);
self
}
- pub fn on_drag(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseDrag, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
+ {
self.handlers = self.handlers.on_drag(button, handler);
self
}
- pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self {
+ pub fn on_hover<V, F>(mut self, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
+ {
self.handlers = self.handlers.on_hover(handler);
self
}
- pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self {
+ pub fn on_move<V, F>(mut self, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
+ {
self.handlers = self.handlers.on_move(handler);
self
}
- pub fn on_move_out(
- mut self,
- handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_move_out<V, F>(mut self, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
+ {
self.handlers = self.handlers.on_move_out(handler);
self
}
- pub fn on_scroll(
- mut self,
- handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_scroll<V, F>(mut self, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
+ {
self.handlers = self.handlers.on_scroll(handler);
self
}
@@ -186,7 +198,7 @@ impl MouseRegionId {
}
}
-pub type HandlerCallback = Rc<dyn Fn(MouseEvent, &mut EventContext)>;
+pub type HandlerCallback = Rc<dyn Fn(MouseEvent, &mut dyn Any, &mut WindowContext, usize) -> bool>;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct HandlerKey {
@@ -211,41 +223,41 @@ impl HandlerSet {
set.insert(
HandlerKey::new(MouseEvent::move_disc(), None),
- SmallVec::from_buf([Rc::new(|_, _| {})]),
+ SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
);
set.insert(
HandlerKey::new(MouseEvent::hover_disc(), None),
- SmallVec::from_buf([Rc::new(|_, _| {})]),
+ SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
);
for button in MouseButton::all() {
set.insert(
HandlerKey::new(MouseEvent::drag_disc(), Some(button)),
- SmallVec::from_buf([Rc::new(|_, _| {})]),
+ SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
);
set.insert(
HandlerKey::new(MouseEvent::down_disc(), Some(button)),
- SmallVec::from_buf([Rc::new(|_, _| {})]),
+ SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
);
set.insert(
HandlerKey::new(MouseEvent::up_disc(), Some(button)),
- SmallVec::from_buf([Rc::new(|_, _| {})]),
+ SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
);
set.insert(
HandlerKey::new(MouseEvent::click_disc(), Some(button)),
- SmallVec::from_buf([Rc::new(|_, _| {})]),
+ SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
);
set.insert(
HandlerKey::new(MouseEvent::down_out_disc(), Some(button)),
- SmallVec::from_buf([Rc::new(|_, _| {})]),
+ SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
);
set.insert(
HandlerKey::new(MouseEvent::up_out_disc(), Some(button)),
- SmallVec::from_buf([Rc::new(|_, _| {})]),
+ SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
);
}
set.insert(
HandlerKey::new(MouseEvent::scroll_wheel_disc(), None),
- SmallVec::from_buf([Rc::new(|_, _| {})]),
+ SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
);
HandlerSet { set }
@@ -283,11 +295,19 @@ impl HandlerSet {
}
}
- pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self {
+ pub fn on_move<V, F>(mut self, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
+ {
self.insert(MouseEvent::move_disc(), None,
- Rc::new(move |region_event, cx| {
+ Rc::new(move |region_event, view, cx, view_id| {
if let MouseEvent::Move(e) = region_event {
- handler(e, cx);
+ let view = view.downcast_mut().unwrap();
+ let mut cx = ViewContext::mutable(cx, view_id);
+ let mut cx = EventContext::new(&mut cx);
+ handler(e, view, &mut cx);
+ cx.handled
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}",
@@ -297,14 +317,19 @@ impl HandlerSet {
self
}
- pub fn on_move_out(
- mut self,
- handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_move_out<V, F>(mut self, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
+ {
self.insert(MouseEvent::move_out_disc(), None,
- Rc::new(move |region_event, cx| {
+ Rc::new(move |region_event, view, cx, view_id| {
if let MouseEvent::MoveOut(e) = region_event {
- handler(e, cx);
+ let view = view.downcast_mut().unwrap();
+ let mut cx = ViewContext::<V>::mutable(cx, view_id);
+ let mut cx = EventContext::new(&mut cx);
+ handler(e, view, &mut cx);
+ cx.handled
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::MoveOut, found {:?}",
@@ -314,15 +339,19 @@ impl HandlerSet {
self
}
- pub fn on_down(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseDown, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
+ {
self.insert(MouseEvent::down_disc(), Some(button),
- Rc::new(move |region_event, cx| {
+ Rc::new(move |region_event, view, cx, view_id| {
if let MouseEvent::Down(e) = region_event {
- handler(e, cx);
+ let view = view.downcast_mut().unwrap();
+ let mut cx = ViewContext::mutable(cx, view_id);
+ let mut cx = EventContext::new(&mut cx);
+ handler(e, view, &mut cx);
+ cx.handled
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}",
@@ -332,15 +361,19 @@ impl HandlerSet {
self
}
- pub fn on_up(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseUp, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
+ {
self.insert(MouseEvent::up_disc(), Some(button),
- Rc::new(move |region_event, cx| {
+ Rc::new(move |region_event, view, cx, view_id| {
if let MouseEvent::Up(e) = region_event {
- handler(e, cx);
+ let view = view.downcast_mut().unwrap();
+ let mut cx = ViewContext::mutable(cx, view_id);
+ let mut cx = EventContext::new(&mut cx);
+ handler(e, view, &mut cx);
+ cx.handled
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}",
@@ -350,15 +383,19 @@ impl HandlerSet {
self
}
- pub fn on_click(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseClick, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
+ {
self.insert(MouseEvent::click_disc(), Some(button),
- Rc::new(move |region_event, cx| {
+ Rc::new(move |region_event, view, cx, view_id| {
if let MouseEvent::Click(e) = region_event {
- handler(e, cx);
+ let view = view.downcast_mut().unwrap();
+ let mut cx = ViewContext::mutable(cx, view_id);
+ let mut cx = EventContext::new(&mut cx);
+ handler(e, view, &mut cx);
+ cx.handled
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}",
@@ -368,15 +405,19 @@ impl HandlerSet {
self
}
- pub fn on_down_out(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseDownOut, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
+ {
self.insert(MouseEvent::down_out_disc(), Some(button),
- Rc::new(move |region_event, cx| {
+ Rc::new(move |region_event, view, cx, view_id| {
if let MouseEvent::DownOut(e) = region_event {
- handler(e, cx);
+ let view = view.downcast_mut().unwrap();
+ let mut cx = ViewContext::mutable(cx, view_id);
+ let mut cx = EventContext::new(&mut cx);
+ handler(e, view, &mut cx);
+ cx.handled
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}",
@@ -386,15 +427,19 @@ impl HandlerSet {
self
}
- pub fn on_up_out(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseUpOut, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
+ {
self.insert(MouseEvent::up_out_disc(), Some(button),
- Rc::new(move |region_event, cx| {
+ Rc::new(move |region_event, view, cx, view_id| {
if let MouseEvent::UpOut(e) = region_event {
- handler(e, cx);
+ let view = view.downcast_mut().unwrap();
+ let mut cx = ViewContext::mutable(cx, view_id);
+ let mut cx = EventContext::new(&mut cx);
+ handler(e, view, &mut cx);
+ cx.handled
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}",
@@ -404,15 +449,19 @@ impl HandlerSet {
self
}
- pub fn on_drag(
- mut self,
- button: MouseButton,
- handler: impl Fn(MouseDrag, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
+ {
self.insert(MouseEvent::drag_disc(), Some(button),
- Rc::new(move |region_event, cx| {
+ Rc::new(move |region_event, view, cx, view_id| {
if let MouseEvent::Drag(e) = region_event {
- handler(e, cx);
+ let view = view.downcast_mut().unwrap();
+ let mut cx = ViewContext::mutable(cx, view_id);
+ let mut cx = EventContext::new(&mut cx);
+ handler(e, view, &mut cx);
+ cx.handled
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}",
@@ -422,11 +471,19 @@ impl HandlerSet {
self
}
- pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self {
+ pub fn on_hover<V, F>(mut self, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
+ {
self.insert(MouseEvent::hover_disc(), None,
- Rc::new(move |region_event, cx| {
+ Rc::new(move |region_event, view, cx, view_id| {
if let MouseEvent::Hover(e) = region_event {
- handler(e, cx);
+ let view = view.downcast_mut().unwrap();
+ let mut cx = ViewContext::mutable(cx, view_id);
+ let mut cx = EventContext::new(&mut cx);
+ handler(e, view, &mut cx);
+ cx.handled
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}",
@@ -436,14 +493,19 @@ impl HandlerSet {
self
}
- pub fn on_scroll(
- mut self,
- handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static,
- ) -> Self {
+ pub fn on_scroll<V, F>(mut self, handler: F) -> Self
+ where
+ V: View,
+ F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
+ {
self.insert(MouseEvent::scroll_wheel_disc(), None,
- Rc::new(move |region_event, cx| {
+ Rc::new(move |region_event, view, cx, view_id| {
if let MouseEvent::ScrollWheel(e) = region_event {
- handler(e, cx);
+ let view = view.downcast_mut().unwrap();
+ let mut cx = ViewContext::mutable(cx, view_id);
+ let mut cx = EventContext::new(&mut cx);
+ handler(e, view, &mut cx);
+ cx.handled
} else {
panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ScrollWheel, found {:?}",
@@ -19,8 +19,8 @@ use crate::{
platform,
platform::Platform,
util::CwdBacktrace,
- AppContext, Element, ElementBox, Entity, FontCache, Handle, RenderContext, Subscription,
- TestAppContext, View,
+ AppContext, Drawable, Element, Entity, FontCache, Handle, Subscription, TestAppContext, View,
+ ViewContext,
};
#[cfg(test)]
@@ -242,7 +242,7 @@ impl View for EmptyView {
"empty view"
}
- fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
- Element::boxed(Empty::new())
+ fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
+ Drawable::boxed(Empty::new())
}
}
@@ -7,7 +7,9 @@ use crate::{
},
platform,
platform::FontSystem,
- scene, PaintContext,
+ scene,
+ window::WindowContext,
+ SceneBuilder,
};
use ordered_float::OrderedFloat;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
@@ -271,10 +273,11 @@ impl Line {
pub fn paint(
&self,
+ scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
line_height: f32,
- cx: &mut PaintContext,
+ cx: &mut WindowContext,
) {
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
let baseline_offset = vec2f(0., padding_top + self.layout.ascent);
@@ -331,7 +334,7 @@ impl Line {
}
if let Some((underline_origin, underline_style)) = finished_underline {
- cx.scene.push_underline(scene::Underline {
+ scene.push_underline(scene::Underline {
origin: underline_origin,
width: glyph_origin.x() - underline_origin.x(),
thickness: underline_style.thickness.into(),
@@ -341,14 +344,14 @@ impl Line {
}
if glyph.is_emoji {
- cx.scene.push_image_glyph(scene::ImageGlyph {
+ scene.push_image_glyph(scene::ImageGlyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
origin: glyph_origin,
});
} else {
- cx.scene.push_glyph(scene::Glyph {
+ scene.push_glyph(scene::Glyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
@@ -361,7 +364,7 @@ impl Line {
if let Some((underline_start, underline_style)) = underline.take() {
let line_end_x = origin.x() + self.layout.width;
- cx.scene.push_underline(scene::Underline {
+ scene.push_underline(scene::Underline {
origin: underline_start,
width: line_end_x - underline_start.x(),
color: underline_style.color.unwrap(),
@@ -373,11 +376,12 @@ impl Line {
pub fn paint_wrapped(
&self,
+ scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
line_height: f32,
boundaries: impl IntoIterator<Item = ShapedBoundary>,
- cx: &mut PaintContext,
+ cx: &mut WindowContext,
) {
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
let baseline_origin = vec2f(0., padding_top + self.layout.ascent);
@@ -416,14 +420,14 @@ impl Line {
);
if glyph_bounds.intersects(visible_bounds) {
if glyph.is_emoji {
- cx.scene.push_image_glyph(scene::ImageGlyph {
+ scene.push_image_glyph(scene::ImageGlyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
origin: glyph_bounds.origin() + baseline_origin,
});
} else {
- cx.scene.push_glyph(scene::Glyph {
+ scene.push_glyph(scene::Glyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
@@ -1,13 +1,13 @@
use serde::Deserialize;
use crate::{
- actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, RenderContext,
+ actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, EventContext,
View, ViewContext, WeakViewHandle,
};
pub struct Select {
handle: WeakViewHandle<Self>,
- render_item: Box<dyn Fn(usize, ItemType, bool, &AppContext) -> ElementBox>,
+ render_item: Box<dyn Fn(usize, ItemType, bool, &AppContext) -> Element<Self>>,
selected_item_ix: usize,
item_count: usize,
is_open: bool,
@@ -41,7 +41,7 @@ pub fn init(cx: &mut AppContext) {
}
impl Select {
- pub fn new<F: 'static + Fn(usize, ItemType, bool, &AppContext) -> ElementBox>(
+ pub fn new<F: 'static + Fn(usize, ItemType, bool, &AppContext) -> Element<Self>>(
item_count: usize,
cx: &mut ViewContext<Self>,
render_item: F,
@@ -92,7 +92,7 @@ impl View for Select {
"Select"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
if self.item_count == 0 {
return Empty::new().boxed();
}
@@ -106,7 +106,7 @@ impl View for Select {
Default::default()
};
let mut result = Flex::column().with_child(
- MouseEventHandler::<Header>::new(self.handle.id(), cx, |mouse_state, cx| {
+ MouseEventHandler::<Header, _>::new(self.handle.id(), cx, |mouse_state, cx| {
Container::new((self.render_item)(
self.selected_item_ix,
ItemType::Header,
@@ -116,9 +116,10 @@ impl View for Select {
.with_style(style.header)
.boxed()
})
- .on_click(MouseButton::Left, move |_, cx| {
- cx.dispatch_action(ToggleSelect)
- })
+ .on_click(
+ MouseButton::Left,
+ move |_, _, cx: &mut EventContext<Self>| cx.dispatch_action(ToggleSelect),
+ )
.boxed(),
);
if self.is_open {
@@ -134,21 +135,28 @@ impl View for Select {
let selected_item_ix = this.selected_item_ix;
range.end = range.end.min(this.item_count);
items.extend(range.map(|ix| {
- MouseEventHandler::<Item>::new(ix, cx, |mouse_state, cx| {
- (this.render_item)(
- ix,
- if ix == selected_item_ix {
- ItemType::Selected
- } else {
- ItemType::Unselected
- },
- mouse_state.hovered(),
- cx,
- )
- })
- .on_click(MouseButton::Left, move |_, cx| {
- cx.dispatch_action(SelectItem(ix))
- })
+ MouseEventHandler::<Item, _>::new(
+ ix,
+ cx,
+ |mouse_state, cx| {
+ (this.render_item)(
+ ix,
+ if ix == selected_item_ix {
+ ItemType::Selected
+ } else {
+ ItemType::Unselected
+ },
+ mouse_state.hovered(),
+ cx,
+ )
+ },
+ )
+ .on_click(
+ MouseButton::Left,
+ move |_, _, cx: &mut EventContext<Self>| {
+ cx.dispatch_action(SelectItem(ix))
+ },
+ )
.boxed()
}))
},
@@ -174,23 +174,60 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
}
}
} else {
+ // Pass to the test function the number of app contexts that it needs,
+ // based on its parameter list.
+ let mut cx_vars = proc_macro2::TokenStream::new();
+ let mut cx_teardowns = proc_macro2::TokenStream::new();
let mut inner_fn_args = proc_macro2::TokenStream::new();
- for arg in inner_fn.sig.inputs.iter() {
+ for (ix, arg) in inner_fn.sig.inputs.iter().enumerate() {
if let FnArg::Typed(arg) = arg {
if let Type::Path(ty) = &*arg.ty {
let last_segment = ty.path.segments.last();
if let Some("StdRng") = last_segment.map(|s| s.ident.to_string()).as_deref() {
inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(seed),));
+ continue;
+ }
+ } else if let Type::Reference(ty) = &*arg.ty {
+ if let Type::Path(ty) = &*ty.elem {
+ let last_segment = ty.path.segments.last();
+ match last_segment.map(|s| s.ident.to_string()).as_deref() {
+ Some("AppContext") => {
+ inner_fn_args.extend(quote!(cx,));
+ continue;
+ }
+ Some("TestAppContext") => {
+ let first_entity_id = ix * 100_000;
+ let cx_varname = format_ident!("cx_{}", ix);
+ cx_vars.extend(quote!(
+ let mut #cx_varname = #namespace::TestAppContext::new(
+ foreground_platform.clone(),
+ cx.platform().clone(),
+ deterministic.build_foreground(#ix),
+ deterministic.build_background(),
+ cx.font_cache().clone(),
+ cx.leak_detector(),
+ #first_entity_id,
+ stringify!(#outer_fn_name).to_string(),
+ );
+ ));
+ cx_teardowns.extend(quote!(
+ #cx_varname.update(|cx| cx.remove_all_windows());
+ deterministic.run_until_parked();
+ #cx_varname.update(|cx| cx.clear_globals());
+ ));
+ inner_fn_args.extend(quote!(&mut #cx_varname,));
+ continue;
+ }
+ _ => {}
+ }
}
- } else {
- inner_fn_args.extend(quote!(cx,));
}
- } else {
- return TokenStream::from(
- syn::Error::new_spanned(arg, "invalid argument").into_compile_error(),
- );
}
+
+ return TokenStream::from(
+ syn::Error::new_spanned(arg, "invalid argument").into_compile_error(),
+ );
}
parse_quote! {
@@ -203,7 +240,11 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
#starting_seed as u64,
#max_retries,
#detect_nondeterminism,
- &mut |cx, _, _, seed| #inner_fn_name(#inner_fn_args),
+ &mut |cx, foreground_platform, deterministic, seed| {
+ #cx_vars
+ #inner_fn_name(#inner_fn_args);
+ #cx_teardowns
+ },
#on_failure_fn_name,
stringify!(#outer_fn_name).to_string(),
);
@@ -7,7 +7,6 @@ use std::{
path::{Path, PathBuf},
sync::Arc,
};
-use util::TryFutureExt as _;
use workspace::AppState;
actions!(journal, [NewJournalEntry]);
@@ -44,40 +43,37 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
Ok::<_, std::io::Error>((journal_dir, entry_path))
});
- cx.spawn(|mut cx| {
- async move {
- let (journal_dir, entry_path) = create_entry.await?;
- let (workspace, _) = cx
- .update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))
- .await;
-
- let opened = workspace
- .update(&mut cx, |workspace, cx| {
- workspace.open_paths(vec![entry_path], true, cx)
- })
- .await;
-
- if let Some(Some(Ok(item))) = opened.first() {
- if let Some(editor) = item.downcast::<Editor>() {
- editor.update(&mut cx, |editor, cx| {
- let len = editor.buffer().read(cx).len(cx);
- editor.change_selections(Some(Autoscroll::center()), cx, |s| {
- s.select_ranges([len..len])
- });
- if len > 0 {
- editor.insert("\n\n", cx);
- }
- editor.insert(&entry_heading, cx);
- editor.insert("\n\n", cx);
+ cx.spawn(|mut cx| async move {
+ let (journal_dir, entry_path) = create_entry.await?;
+ let (workspace, _) = cx
+ .update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))
+ .await?;
+
+ let opened = workspace
+ .update(&mut cx, |workspace, cx| {
+ workspace.open_paths(vec![entry_path], true, cx)
+ })?
+ .await;
+
+ if let Some(Some(Ok(item))) = opened.first() {
+ if let Some(editor) = item.downcast::<Editor>() {
+ editor.update(&mut cx, |editor, cx| {
+ let len = editor.buffer().read(cx).len(cx);
+ editor.change_selections(Some(Autoscroll::center()), cx, |s| {
+ s.select_ranges([len..len])
});
- }
+ if len > 0 {
+ editor.insert("\n\n", cx);
+ }
+ editor.insert(&entry_heading, cx);
+ editor.insert("\n\n", cx);
+ })?;
}
-
- anyhow::Ok(())
}
- .log_err()
+
+ anyhow::Ok(())
})
- .detach();
+ .detach_and_log_err(cx);
}
fn journal_dir(settings: &Settings) -> Option<PathBuf> {
@@ -17,5 +17,6 @@ picker = { path = "../picker" }
project = { path = "../project" }
theme = { path = "../theme" }
settings = { path = "../settings" }
+util = { path = "../util" }
workspace = { path = "../workspace" }
anyhow = "1.0"
@@ -2,7 +2,7 @@ use editor::Editor;
use gpui::{
elements::*,
platform::{CursorStyle, MouseButton},
- Entity, RenderContext, Subscription, View, ViewContext, ViewHandle,
+ Entity, Subscription, View, ViewContext, ViewHandle,
};
use settings::Settings;
use std::sync::Arc;
@@ -50,7 +50,7 @@ impl View for ActiveBufferLanguage {
"ActiveBufferLanguage"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
if let Some(active_language) = self.active_language.as_ref() {
let active_language_text = if let Some(active_language_text) = active_language {
active_language_text.to_string()
@@ -58,7 +58,7 @@ impl View for ActiveBufferLanguage {
"Unknown".to_string()
};
- MouseEventHandler::<Self>::new(0, cx, |state, cx| {
+ MouseEventHandler::<Self, Self>::new(0, cx, |state, cx| {
let theme = &cx.global::<Settings>().theme.workspace.status_bar;
let style = theme.active_language.style_for(state, false);
Label::new(active_language_text, style.text.clone())
@@ -67,7 +67,9 @@ impl View for ActiveBufferLanguage {
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(crate::Toggle))
+ .on_click(MouseButton::Left, |_, _, cx| {
+ cx.dispatch_action(crate::Toggle)
+ })
.boxed()
} else {
Empty::new().boxed()
@@ -1,55 +1,64 @@
mod active_buffer_language;
pub use active_buffer_language::ActiveBufferLanguage;
+use anyhow::anyhow;
use editor::Editor;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
-use gpui::{
- actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
- RenderContext, View, ViewContext, ViewHandle,
-};
+use gpui::{actions, elements::*, AppContext, ModelHandle, MouseState, ViewContext};
use language::{Buffer, LanguageRegistry};
-use picker::{Picker, PickerDelegate};
+use picker::{Picker, PickerDelegate, PickerEvent};
use project::Project;
use settings::Settings;
use std::sync::Arc;
+use util::ResultExt;
use workspace::{AppState, Workspace};
actions!(language_selector, [Toggle]);
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
- Picker::<LanguageSelector>::init(cx);
+ Picker::<LanguageSelectorDelegate>::init(cx);
cx.add_action({
let language_registry = app_state.languages.clone();
- move |workspace, _: &Toggle, cx| {
- LanguageSelector::toggle(workspace, language_registry.clone(), cx)
- }
+ move |workspace, _: &Toggle, cx| toggle(workspace, language_registry.clone(), cx)
});
}
-pub enum Event {
- Dismissed,
+fn toggle(
+ workspace: &mut Workspace,
+ registry: Arc<LanguageRegistry>,
+ cx: &mut ViewContext<Workspace>,
+) -> Option<()> {
+ let (_, buffer, _) = workspace
+ .active_item(cx)?
+ .act_as::<Editor>(cx)?
+ .read(cx)
+ .active_excerpt(cx)?;
+ workspace.toggle_modal(cx, |workspace, cx| {
+ cx.add_view(|cx| {
+ Picker::new(
+ LanguageSelectorDelegate::new(buffer, workspace.project().clone(), registry),
+ cx,
+ )
+ })
+ });
+ Some(())
}
-pub struct LanguageSelector {
+pub struct LanguageSelectorDelegate {
buffer: ModelHandle<Buffer>,
project: ModelHandle<Project>,
language_registry: Arc<LanguageRegistry>,
candidates: Vec<StringMatchCandidate>,
matches: Vec<StringMatch>,
- picker: ViewHandle<Picker<Self>>,
selected_index: usize,
}
-impl LanguageSelector {
+impl LanguageSelectorDelegate {
fn new(
buffer: ModelHandle<Buffer>,
project: ModelHandle<Project>,
language_registry: Arc<LanguageRegistry>,
- cx: &mut ViewContext<Self>,
) -> Self {
- let handle = cx.weak_handle();
- let picker = cx.add_view(|cx| Picker::new("Select Language...", handle, cx));
-
let candidates = language_registry
.language_names()
.into_iter()
@@ -73,104 +82,63 @@ impl LanguageSelector {
language_registry,
candidates,
matches,
- picker,
selected_index: 0,
}
}
-
- fn toggle(
- workspace: &mut Workspace,
- registry: Arc<LanguageRegistry>,
- cx: &mut ViewContext<Workspace>,
- ) {
- if let Some((_, buffer, _)) = workspace
- .active_item(cx)
- .and_then(|active_item| active_item.act_as::<Editor>(cx))
- .and_then(|editor| editor.read(cx).active_excerpt(cx))
- {
- workspace.toggle_modal(cx, |workspace, cx| {
- let project = workspace.project().clone();
- let this = cx.add_view(|cx| Self::new(buffer, project, registry, cx));
- cx.subscribe(&this, Self::on_event).detach();
- this
- });
- }
- }
-
- fn on_event(
- workspace: &mut Workspace,
- _: ViewHandle<LanguageSelector>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Dismissed => {
- workspace.dismiss_modal(cx);
- }
- }
- }
}
-impl Entity for LanguageSelector {
- type Event = Event;
-}
-
-impl View for LanguageSelector {
- fn ui_name() -> &'static str {
- "LanguageSelector"
- }
-
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
- ChildView::new(&self.picker, cx).boxed()
+impl PickerDelegate for LanguageSelectorDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Select a language...".into()
}
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.is_self_focused() {
- cx.focus(&self.picker);
- }
- }
-}
-
-impl PickerDelegate for LanguageSelector {
fn match_count(&self) -> usize {
self.matches.len()
}
- fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
if let Some(mat) = self.matches.get(self.selected_index) {
let language_name = &self.candidates[mat.candidate_id].string;
let language = self.language_registry.language_for_name(language_name);
- cx.spawn(|this, mut cx| async move {
+ let project = self.project.downgrade();
+ let buffer = self.buffer.downgrade();
+ cx.spawn_weak(|_, mut cx| async move {
let language = language.await?;
- this.update(&mut cx, |this, cx| {
- this.project.update(cx, |project, cx| {
- project.set_language_for_buffer(&this.buffer, language, cx);
- });
+ let project = project
+ .upgrade(&cx)
+ .ok_or_else(|| anyhow!("project was dropped"))?;
+ let buffer = buffer
+ .upgrade(&cx)
+ .ok_or_else(|| anyhow!("buffer was dropped"))?;
+ project.update(&mut cx, |project, cx| {
+ project.set_language_for_buffer(&buffer, language, cx);
});
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
- cx.emit(Event::Dismissed);
+ cx.emit(PickerEvent::Dismiss);
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
- cx.emit(Event::Dismissed);
- }
+ fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn selected_index(&self) -> usize {
self.selected_index
}
- fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
+ fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
+ fn update_matches(
+ &mut self,
+ query: String,
+ cx: &mut ViewContext<Picker<Self>>,
+ ) -> gpui::Task<()> {
let background = cx.background().clone();
let candidates = self.candidates.clone();
- cx.spawn(|this, mut cx| async move {
+ cx.spawn_weak(|this, mut cx| async move {
let matches = if query.is_empty() {
candidates
.into_iter()
@@ -194,13 +162,17 @@ impl PickerDelegate for LanguageSelector {
.await
};
- this.update(&mut cx, |this, cx| {
- this.matches = matches;
- this.selected_index = this
- .selected_index
- .min(this.matches.len().saturating_sub(1));
- cx.notify();
- });
+ if let Some(this) = this.upgrade(&cx) {
+ this.update(&mut cx, |this, cx| {
+ let delegate = this.delegate_mut();
+ delegate.matches = matches;
+ delegate.selected_index = delegate
+ .selected_index
+ .min(delegate.matches.len().saturating_sub(1));
+ cx.notify();
+ })
+ .log_err();
+ }
})
}
@@ -210,7 +182,7 @@ impl PickerDelegate for LanguageSelector {
mouse_state: &mut MouseState,
selected: bool,
cx: &AppContext,
- ) -> ElementBox {
+ ) -> Element<Picker<Self>> {
let settings = cx.global::<Settings>();
let theme = &settings.theme;
let mat = &self.matches[ix];
@@ -4,25 +4,51 @@ use editor::{
};
use fuzzy::StringMatch;
use gpui::{
- actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Entity,
- MouseState, RenderContext, Task, View, ViewContext, ViewHandle,
+ actions, elements::*, geometry::vector::Vector2F, AppContext, MouseState, Task, ViewContext,
+ ViewHandle, WindowContext,
};
use language::Outline;
use ordered_float::OrderedFloat;
-use picker::{Picker, PickerDelegate};
+use picker::{Picker, PickerDelegate, PickerEvent};
use settings::Settings;
-use std::cmp::{self, Reverse};
+use std::{
+ cmp::{self, Reverse},
+ sync::Arc,
+};
use workspace::Workspace;
actions!(outline, [Toggle]);
pub fn init(cx: &mut AppContext) {
- cx.add_action(OutlineView::toggle);
- Picker::<OutlineView>::init(cx);
+ cx.add_action(toggle);
+ OutlineView::init(cx);
+}
+
+fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+ if let Some(editor) = workspace
+ .active_item(cx)
+ .and_then(|item| item.downcast::<Editor>())
+ {
+ let outline = editor
+ .read(cx)
+ .buffer()
+ .read(cx)
+ .snapshot(cx)
+ .outline(Some(cx.global::<Settings>().theme.editor.syntax.as_ref()));
+ if let Some(outline) = outline {
+ workspace.toggle_modal(cx, |_, cx| {
+ cx.add_view(|cx| {
+ OutlineView::new(OutlineViewDelegate::new(outline, editor, cx), cx)
+ .with_max_size(800., 1200.)
+ })
+ });
+ }
+ }
}
-struct OutlineView {
- picker: ViewHandle<Picker<Self>>,
+type OutlineView = Picker<OutlineViewDelegate>;
+
+struct OutlineViewDelegate {
active_editor: ViewHandle<Editor>,
outline: Outline<Anchor>,
selected_match_index: usize,
@@ -31,45 +57,13 @@ struct OutlineView {
last_query: String,
}
-pub enum Event {
- Dismissed,
-}
-
-impl Entity for OutlineView {
- type Event = Event;
-
- fn release(&mut self, cx: &mut AppContext) {
- self.restore_active_editor(cx);
- }
-}
-
-impl View for OutlineView {
- fn ui_name() -> &'static str {
- "OutlineView"
- }
-
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
- ChildView::new(&self.picker, cx).boxed()
- }
-
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.is_self_focused() {
- cx.focus(&self.picker);
- }
- }
-}
-
-impl OutlineView {
+impl OutlineViewDelegate {
fn new(
outline: Outline<Anchor>,
editor: ViewHandle<Editor>,
- cx: &mut ViewContext<Self>,
+ cx: &mut ViewContext<OutlineView>,
) -> Self {
- let handle = cx.weak_handle();
Self {
- picker: cx.add_view(|cx| {
- Picker::new("Search buffer symbols...", handle, cx).with_max_size(800., 1200.)
- }),
last_query: Default::default(),
matches: Default::default(),
selected_match_index: 0,
@@ -79,28 +73,7 @@ impl OutlineView {
}
}
- fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
- if let Some(editor) = workspace
- .active_item(cx)
- .and_then(|item| item.downcast::<Editor>())
- {
- let outline = editor
- .read(cx)
- .buffer()
- .read(cx)
- .snapshot(cx)
- .outline(Some(cx.global::<Settings>().theme.editor.syntax.as_ref()));
- if let Some(outline) = outline {
- workspace.toggle_modal(cx, |_, cx| {
- let view = cx.add_view(|cx| OutlineView::new(outline, editor, cx));
- cx.subscribe(&view, Self::on_event).detach();
- view
- });
- }
- }
- }
-
- fn restore_active_editor(&mut self, cx: &mut AppContext) {
+ fn restore_active_editor(&mut self, cx: &mut WindowContext) {
self.active_editor.update(cx, |editor, cx| {
editor.highlight_rows(None);
if let Some(scroll_position) = self.prev_scroll_position {
@@ -109,7 +82,7 @@ impl OutlineView {
})
}
- fn set_selected_index(&mut self, ix: usize, navigate: bool, cx: &mut ViewContext<Self>) {
+ fn set_selected_index(&mut self, ix: usize, navigate: bool, cx: &mut ViewContext<OutlineView>) {
self.selected_match_index = ix;
if navigate && !self.matches.is_empty() {
let selected_match = &self.matches[self.selected_match_index];
@@ -125,22 +98,14 @@ impl OutlineView {
active_editor.request_autoscroll(Autoscroll::center(), cx);
});
}
- cx.notify();
}
+}
- fn on_event(
- workspace: &mut Workspace,
- _: ViewHandle<Self>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Dismissed => workspace.dismiss_modal(cx),
- }
+impl PickerDelegate for OutlineViewDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Search buffer symbols...".into()
}
-}
-impl PickerDelegate for OutlineView {
fn match_count(&self) -> usize {
self.matches.len()
}
@@ -149,7 +114,7 @@ impl PickerDelegate for OutlineView {
self.selected_match_index
}
- fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
+ fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<OutlineView>) {
self.set_selected_index(ix, true, cx);
}
@@ -157,7 +122,7 @@ impl PickerDelegate for OutlineView {
true
}
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+ fn update_matches(&mut self, query: String, cx: &mut ViewContext<OutlineView>) -> Task<()> {
let selected_index;
if query.is_empty() {
self.restore_active_editor(cx);
@@ -213,7 +178,7 @@ impl PickerDelegate for OutlineView {
Task::ready(())
}
- fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<OutlineView>) {
self.prev_scroll_position.take();
self.active_editor.update(cx, |active_editor, cx| {
if let Some(rows) = active_editor.highlighted_rows() {
@@ -224,12 +189,11 @@ impl PickerDelegate for OutlineView {
});
}
});
- cx.emit(Event::Dismissed);
+ cx.emit(PickerEvent::Dismiss);
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+ fn dismissed(&mut self, cx: &mut ViewContext<OutlineView>) {
self.restore_active_editor(cx);
- cx.emit(Event::Dismissed);
}
fn render_match(
@@ -238,7 +202,7 @@ impl PickerDelegate for OutlineView {
mouse_state: &mut MouseState,
selected: bool,
cx: &AppContext,
- ) -> ElementBox {
+ ) -> Element<Picker<Self>> {
let settings = cx.global::<Settings>();
let string_match = &self.matches[ix];
let style = settings.theme.picker.item.style_for(mouse_state, selected);
@@ -4,43 +4,51 @@ use gpui::{
geometry::vector::{vec2f, Vector2F},
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton},
- AnyViewHandle, AppContext, Axis, Entity, MouseState, RenderContext, Task, View, ViewContext,
- ViewHandle, WeakViewHandle,
+ AnyViewHandle, AppContext, Axis, Element, Entity, MouseState, Task, View, ViewContext,
+ ViewHandle,
};
use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev};
use parking_lot::Mutex;
use std::{cmp, sync::Arc};
+use util::ResultExt;
+use workspace::Modal;
+
+pub enum PickerEvent {
+ Dismiss,
+}
pub struct Picker<D: PickerDelegate> {
- delegate: WeakViewHandle<D>,
+ delegate: D,
query_editor: ViewHandle<Editor>,
list_state: UniformListState,
max_size: Vector2F,
theme: Arc<Mutex<Box<dyn Fn(&theme::Theme) -> theme::Picker>>>,
confirmed: bool,
+ pending_update_matches: Task<Option<()>>,
}
-pub trait PickerDelegate: View {
+pub trait PickerDelegate: Sized + 'static {
+ fn placeholder_text(&self) -> Arc<str>;
fn match_count(&self) -> usize;
fn selected_index(&self) -> usize;
- fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>);
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()>;
- fn confirm(&mut self, cx: &mut ViewContext<Self>);
- fn dismiss(&mut self, cx: &mut ViewContext<Self>);
+ fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
+ fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
+ fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>);
+ fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
fn render_match(
&self,
ix: usize,
state: &mut MouseState,
selected: bool,
cx: &AppContext,
- ) -> ElementBox;
+ ) -> Element<Picker<Self>>;
fn center_selection_after_match_updates(&self) -> bool {
false
}
}
impl<D: PickerDelegate> Entity for Picker<D> {
- type Event = ();
+ type Event = PickerEvent;
}
impl<D: PickerDelegate> View for Picker<D> {
@@ -48,15 +56,10 @@ impl<D: PickerDelegate> View for Picker<D> {
"Picker"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = (self.theme.lock())(&cx.global::<settings::Settings>().theme);
let query = self.query(cx);
- let delegate = self.delegate.clone();
- let match_count = if let Some(delegate) = delegate.upgrade(cx.app) {
- delegate.read(cx).match_count()
- } else {
- 0
- };
+ let match_count = self.delegate.match_count();
let container_style;
let editor_style;
@@ -93,19 +96,16 @@ impl<D: PickerDelegate> View for Picker<D> {
match_count,
cx,
move |this, mut range, items, cx| {
- let delegate = this.delegate.upgrade(cx).unwrap();
- let selected_ix = delegate.read(cx).selected_index();
- range.end = cmp::min(range.end, delegate.read(cx).match_count());
+ let selected_ix = this.delegate.selected_index();
+ range.end = cmp::min(range.end, this.delegate.match_count());
items.extend(range.map(move |ix| {
- MouseEventHandler::<D>::new(ix, cx, |state, cx| {
- delegate
- .read(cx)
- .render_match(ix, state, ix == selected_ix, cx)
+ MouseEventHandler::<D, _>::new(ix, cx, |state, cx| {
+ this.delegate.render_match(ix, state, ix == selected_ix, cx)
})
// Capture mouse events
- .on_down(MouseButton::Left, |_, _| {})
- .on_up(MouseButton::Left, |_, _| {})
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_down(MouseButton::Left, |_, _, _| {})
+ .on_up(MouseButton::Left, |_, _, _| {})
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(SelectIndex(ix))
})
.with_cursor_style(CursorStyle::PointingHand)
@@ -140,6 +140,12 @@ impl<D: PickerDelegate> View for Picker<D> {
}
}
+impl<D: PickerDelegate> Modal for Picker<D> {
+ fn dismiss_on_event(event: &Self::Event) -> bool {
+ matches!(event, PickerEvent::Dismiss)
+ }
+}
+
impl<D: PickerDelegate> Picker<D> {
pub fn init(cx: &mut AppContext) {
cx.add_action(Self::select_first);
@@ -151,14 +157,12 @@ impl<D: PickerDelegate> Picker<D> {
cx.add_action(Self::cancel);
}
- pub fn new<P>(placeholder: P, delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self
- where
- P: Into<Arc<str>>,
- {
+ pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
let theme = Arc::new(Mutex::new(
Box::new(|theme: &theme::Theme| theme.picker.clone())
as Box<dyn Fn(&theme::Theme) -> theme::Picker>,
));
+ let placeholder_text = delegate.placeholder_text();
let query_editor = cx.add_view({
let picker_theme = theme.clone();
|cx| {
@@ -168,26 +172,22 @@ impl<D: PickerDelegate> Picker<D> {
})),
cx,
);
- editor.set_placeholder_text(placeholder, cx);
+ editor.set_placeholder_text(placeholder_text, cx);
editor
}
});
cx.subscribe(&query_editor, Self::on_query_editor_event)
.detach();
- let this = Self {
+ let mut this = Self {
query_editor,
list_state: Default::default(),
delegate,
max_size: vec2f(540., 420.),
theme,
confirmed: false,
+ pending_update_matches: Task::ready(None),
};
- cx.defer(|this, cx| {
- if let Some(delegate) = this.delegate.upgrade(cx) {
- cx.observe(&delegate, |_, _, cx| cx.notify()).detach();
- this.update_matches(String::new(), cx)
- }
- });
+ this.update_matches(String::new(), cx);
this
}
@@ -204,6 +204,14 @@ impl<D: PickerDelegate> Picker<D> {
self
}
+ pub fn delegate(&self) -> &D {
+ &self.delegate
+ }
+
+ pub fn delegate_mut(&mut self) -> &mut D {
+ &mut self.delegate
+ }
+
pub fn query(&self, cx: &AppContext) -> String {
self.query_editor.read(cx).text(cx)
}
@@ -222,119 +230,95 @@ impl<D: PickerDelegate> Picker<D> {
match event {
editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx),
editor::Event::Blurred if !self.confirmed => {
- if let Some(delegate) = self.delegate.upgrade(cx) {
- delegate.update(cx, |delegate, cx| {
- delegate.dismiss(cx);
- })
- }
+ self.dismiss(cx);
}
_ => {}
}
}
pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
- if let Some(delegate) = self.delegate.upgrade(cx) {
- let update = delegate.update(cx, |d, cx| d.update_matches(query, cx));
- cx.spawn(|this, mut cx| async move {
- update.await;
- this.update(&mut cx, |this, cx| {
- if let Some(delegate) = this.delegate.upgrade(cx) {
- let delegate = delegate.read(cx);
- let index = delegate.selected_index();
- let target = if delegate.center_selection_after_match_updates() {
- ScrollTarget::Center(index)
- } else {
- ScrollTarget::Show(index)
- };
- this.list_state.scroll_to(target);
- cx.notify();
- }
- });
- })
- .detach()
- }
+ let update = self.delegate.update_matches(query, cx);
+ self.matches_updated(cx);
+ self.pending_update_matches = cx.spawn_weak(|this, mut cx| async move {
+ update.await;
+ this.upgrade(&cx)?
+ .update(&mut cx, |this, cx| this.matches_updated(cx))
+ .log_err()
+ });
}
- pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
- if let Some(delegate) = self.delegate.upgrade(cx) {
- delegate.update(cx, |delegate, cx| {
- if delegate.match_count() > 0 {
- delegate.set_selected_index(0, cx);
- self.list_state.scroll_to(ScrollTarget::Show(0));
- }
- });
+ fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
+ let index = self.delegate.selected_index();
+ let target = if self.delegate.center_selection_after_match_updates() {
+ ScrollTarget::Center(index)
+ } else {
+ ScrollTarget::Show(index)
+ };
+ self.list_state.scroll_to(target);
+ cx.notify();
+ }
- cx.notify();
+ pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
+ if self.delegate.match_count() > 0 {
+ self.delegate.set_selected_index(0, cx);
+ self.list_state.scroll_to(ScrollTarget::Show(0));
}
+
+ cx.notify();
}
pub fn select_index(&mut self, action: &SelectIndex, cx: &mut ViewContext<Self>) {
- if let Some(delegate) = self.delegate.upgrade(cx) {
- let index = action.0;
- delegate.update(cx, |delegate, cx| {
- if delegate.match_count() > 0 {
- self.confirmed = true;
- delegate.set_selected_index(index, cx);
- delegate.confirm(cx);
- }
- });
+ let index = action.0;
+ if self.delegate.match_count() > 0 {
+ self.confirmed = true;
+ self.delegate.set_selected_index(index, cx);
+ self.delegate.confirm(cx);
}
}
pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
- if let Some(delegate) = self.delegate.upgrade(cx) {
- delegate.update(cx, |delegate, cx| {
- let match_count = delegate.match_count();
- if match_count > 0 {
- let index = match_count - 1;
- delegate.set_selected_index(index, cx);
- self.list_state.scroll_to(ScrollTarget::Show(index));
- }
- });
- cx.notify();
+ let match_count = self.delegate.match_count();
+ if match_count > 0 {
+ let index = match_count - 1;
+ self.delegate.set_selected_index(index, cx);
+ self.list_state.scroll_to(ScrollTarget::Show(index));
}
+ cx.notify();
}
pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
- if let Some(delegate) = self.delegate.upgrade(cx) {
- delegate.update(cx, |delegate, cx| {
- let next_index = delegate.selected_index() + 1;
- if next_index < delegate.match_count() {
- delegate.set_selected_index(next_index, cx);
- self.list_state.scroll_to(ScrollTarget::Show(next_index));
- }
- });
-
- cx.notify();
+ let next_index = self.delegate.selected_index() + 1;
+ if next_index < self.delegate.match_count() {
+ self.delegate.set_selected_index(next_index, cx);
+ self.list_state.scroll_to(ScrollTarget::Show(next_index));
}
+
+ cx.notify();
}
pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
- if let Some(delegate) = self.delegate.upgrade(cx) {
- delegate.update(cx, |delegate, cx| {
- let mut selected_index = delegate.selected_index();
- if selected_index > 0 {
- selected_index -= 1;
- delegate.set_selected_index(selected_index, cx);
- self.list_state
- .scroll_to(ScrollTarget::Show(selected_index));
- }
- });
-
- cx.notify();
+ let mut selected_index = self.delegate.selected_index();
+ if selected_index > 0 {
+ selected_index -= 1;
+ self.delegate.set_selected_index(selected_index, cx);
+ self.list_state
+ .scroll_to(ScrollTarget::Show(selected_index));
}
+
+ cx.notify();
}
- fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
- if let Some(delegate) = self.delegate.upgrade(cx) {
- self.confirmed = true;
- delegate.update(cx, |delegate, cx| delegate.confirm(cx));
- }
+ pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
+ self.confirmed = true;
+ self.delegate.confirm(cx);
}
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
- if let Some(delegate) = self.delegate.upgrade(cx) {
- delegate.update(cx, |delegate, cx| delegate.dismiss(cx));
- }
+ self.dismiss(cx);
+ }
+
+ fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+ cx.emit(PickerEvent::Dismiss);
+ self.delegate.dismissed(cx);
}
}
@@ -13,8 +13,8 @@ use gpui::{
impl_internal_actions,
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, PromptLevel},
- AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, RenderContext, Task, View,
- ViewContext, ViewHandle,
+ AppContext, ClipboardItem, Drawable, Element, Entity, ModelHandle, Task, View, ViewContext,
+ ViewHandle,
};
use menu::{Confirm, SelectNext, SelectPrev};
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
@@ -498,7 +498,7 @@ impl ProjectPanel {
this.update(&mut cx, |this, cx| {
this.edit_state.take();
cx.notify();
- });
+ })?;
let new_entry = new_entry?;
this.update(&mut cx, |this, cx| {
@@ -519,7 +519,7 @@ impl ProjectPanel {
);
}
cx.notify();
- });
+ })?;
Ok(())
}))
}
@@ -655,7 +655,7 @@ impl ProjectPanel {
this.project
.update(cx, |project, cx| project.delete_entry(entry_id, cx))
.ok_or_else(|| anyhow!("no such entry"))
- })?
+ })??
.await
}))
}
@@ -1015,8 +1015,8 @@ impl ProjectPanel {
fn for_each_visible_entry(
&self,
range: Range<usize>,
- cx: &mut RenderContext<ProjectPanel>,
- mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut RenderContext<ProjectPanel>),
+ cx: &mut ViewContext<ProjectPanel>,
+ mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut ViewContext<ProjectPanel>),
) {
let mut ix = 0;
for (worktree_id, visible_worktree_entries) in &self.visible_entries {
@@ -1097,8 +1097,8 @@ impl ProjectPanel {
padding: f32,
row_container_style: ContainerStyle,
style: &ProjectPanelEntry,
- cx: &mut RenderContext<V>,
- ) -> ElementBox {
+ cx: &mut ViewContext<V>,
+ ) -> Element<V> {
let kind = details.kind;
let show_editor = details.is_editing && !details.is_processing;
@@ -1154,9 +1154,8 @@ impl ProjectPanel {
editor: &ViewHandle<Editor>,
dragged_entry_destination: &mut Option<Arc<Path>>,
theme: &theme::ProjectPanel,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
- let this = cx.handle();
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let kind = details.kind;
let path = details.path.clone();
let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width;
@@ -1171,7 +1170,7 @@ impl ProjectPanel {
let show_editor = details.is_editing && !details.is_processing;
- MouseEventHandler::<Self>::new(entry_id.to_usize(), cx, |state, cx| {
+ MouseEventHandler::<Self, _>::new(entry_id.to_usize(), cx, |state, cx| {
let mut style = entry_style.style_for(state, details.is_selected).clone();
if cx
@@ -1201,7 +1200,7 @@ impl ProjectPanel {
cx,
)
})
- .on_click(MouseButton::Left, move |e, cx| {
+ .on_click(MouseButton::Left, move |e, _, cx| {
if !show_editor {
if kind == EntryKind::Dir {
cx.dispatch_action(ToggleExpanded(entry_id))
@@ -1213,13 +1212,13 @@ impl ProjectPanel {
}
}
})
- .on_down(MouseButton::Right, move |e, cx| {
+ .on_down(MouseButton::Right, move |e, _, cx| {
cx.dispatch_action(DeployContextMenu {
entry_id,
position: e.position,
})
})
- .on_up(MouseButton::Left, move |_, cx| {
+ .on_up(MouseButton::Left, move |_, _, cx| {
if let Some((_, dragged_entry)) = cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id())
@@ -1231,27 +1230,23 @@ impl ProjectPanel {
});
}
})
- .on_move(move |_, cx| {
+ .on_move(move |_, this, cx| {
if cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id())
.is_some()
{
- if let Some(this) = this.upgrade(cx.app) {
- this.update(cx.app, |this, _| {
- this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
- path.parent().map(|parent| Arc::from(parent))
- } else {
- Some(path.clone())
- };
- })
- }
+ this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
+ path.parent().map(|parent| Arc::from(parent))
+ } else {
+ Some(path.clone())
+ };
}
})
.as_draggable(entry_id, {
let row_container_style = theme.dragged_entry.container;
- move |_, cx: &mut RenderContext<Workspace>| {
+ move |_, cx: &mut ViewContext<Workspace>| {
let theme = cx.global::<Settings>().theme.clone();
Self::render_entry_visual_element(
&details,
@@ -1273,7 +1268,7 @@ impl View for ProjectPanel {
"ProjectPanel"
}
- fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
+ fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::Element<Self> {
enum ProjectPanel {}
let theme = &cx.global::<Settings>().theme.project_panel;
let mut container_style = theme.container;
@@ -1285,7 +1280,7 @@ impl View for ProjectPanel {
if has_worktree {
Stack::new()
.with_child(
- MouseEventHandler::<ProjectPanel>::new(0, cx, |_, cx| {
+ MouseEventHandler::<ProjectPanel, _>::new(0, cx, |_, cx| {
UniformList::new(
self.list.clone(),
self.visible_entries
@@ -1317,7 +1312,7 @@ impl View for ProjectPanel {
.expanded()
.boxed()
})
- .on_down(MouseButton::Right, move |e, cx| {
+ .on_down(MouseButton::Right, move |e, _, cx| {
// When deploying the context menu anywhere below the last project entry,
// act as if the user clicked the root of the last worktree.
if let Some(entry_id) = last_worktree_root_id {
@@ -1334,7 +1329,7 @@ impl View for ProjectPanel {
} else {
Flex::column()
.with_child(
- MouseEventHandler::<Self>::new(2, cx, {
+ MouseEventHandler::<Self, _>::new(2, cx, {
let button_style = theme.open_project_button.clone();
let context_menu_item_style =
cx.global::<Settings>().theme.context_menu.item.clone();
@@ -1353,7 +1348,7 @@ impl View for ProjectPanel {
.boxed()
}
})
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(workspace::Open)
})
.with_cursor_style(CursorStyle::PointingHand)
@@ -1549,7 +1544,7 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".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 panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx));
select_path(&panel, "root1", cx);
@@ -1571,7 +1566,10 @@ mod tests {
// Add a file with the root folder selected. The filename editor is placed
// before the first file in the root folder.
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
- assert!(panel.read_with(cx, |panel, cx| panel.filename_editor.is_focused(cx)));
+ cx.read_window(window_id, |cx| {
+ let panel = panel.read(cx);
+ assert!(panel.filename_editor.is_focused(cx));
+ });
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
@@ -1943,7 +1941,8 @@ mod tests {
let mut result = Vec::new();
let mut project_entries = HashSet::new();
let mut has_editor = false;
- cx.render(panel, |panel, cx| {
+
+ panel.update(cx, |panel, cx| {
panel.for_each_visible_entry(range, cx, |project_entry, details, _| {
if details.is_editing {
assert!(!has_editor, "duplicate editor entry");
@@ -1,90 +1,63 @@
+use anyhow::anyhow;
use editor::{
combine_syntax_and_fuzzy_match_highlights, scroll::autoscroll::Autoscroll,
styled_runs_for_code_label, Bias, Editor,
};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
- actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
- RenderContext, Task, View, ViewContext, ViewHandle,
+ actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle,
};
use ordered_float::OrderedFloat;
-use picker::{Picker, PickerDelegate};
+use picker::{Picker, PickerDelegate, PickerEvent};
use project::{Project, Symbol};
use settings::Settings;
-use std::{borrow::Cow, cmp::Reverse};
+use std::{borrow::Cow, cmp::Reverse, sync::Arc};
use util::ResultExt;
use workspace::Workspace;
actions!(project_symbols, [Toggle]);
pub fn init(cx: &mut AppContext) {
- cx.add_action(ProjectSymbolsView::toggle);
- Picker::<ProjectSymbolsView>::init(cx);
+ cx.add_action(toggle);
+ ProjectSymbols::init(cx);
}
-pub struct ProjectSymbolsView {
- picker: ViewHandle<Picker<Self>>,
+fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+ workspace.toggle_modal(cx, |workspace, cx| {
+ let project = workspace.project().clone();
+ let workspace = cx.weak_handle();
+ cx.add_view(|cx| ProjectSymbols::new(ProjectSymbolsDelegate::new(workspace, project), cx))
+ });
+}
+
+pub type ProjectSymbols = Picker<ProjectSymbolsDelegate>;
+
+pub struct ProjectSymbolsDelegate {
+ workspace: WeakViewHandle<Workspace>,
project: ModelHandle<Project>,
selected_match_index: usize,
symbols: Vec<Symbol>,
visible_match_candidates: Vec<StringMatchCandidate>,
external_match_candidates: Vec<StringMatchCandidate>,
show_worktree_root_name: bool,
- pending_update: Task<()>,
matches: Vec<StringMatch>,
}
-pub enum Event {
- Dismissed,
- Selected(Symbol),
-}
-
-impl Entity for ProjectSymbolsView {
- type Event = Event;
-}
-
-impl View for ProjectSymbolsView {
- fn ui_name() -> &'static str {
- "ProjectSymbolsView"
- }
-
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
- ChildView::new(&self.picker, cx).boxed()
- }
-
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.is_self_focused() {
- cx.focus(&self.picker);
- }
- }
-}
-
-impl ProjectSymbolsView {
- fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
- let handle = cx.weak_handle();
+impl ProjectSymbolsDelegate {
+ fn new(workspace: WeakViewHandle<Workspace>, project: ModelHandle<Project>) -> Self {
Self {
+ workspace,
project,
- picker: cx.add_view(|cx| Picker::new("Search project symbols...", handle, cx)),
selected_match_index: 0,
symbols: Default::default(),
visible_match_candidates: Default::default(),
external_match_candidates: Default::default(),
matches: Default::default(),
show_worktree_root_name: false,
- pending_update: Task::ready(()),
}
}
- fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
- workspace.toggle_modal(cx, |workspace, cx| {
- let project = workspace.project().clone();
- let symbols = cx.add_view(|cx| Self::new(project, cx));
- cx.subscribe(&symbols, Self::on_event).detach();
- symbols
- });
- }
-
- fn filter(&mut self, query: &str, cx: &mut ViewContext<Self>) {
+ fn filter(&mut self, query: &str, cx: &mut ViewContext<ProjectSymbols>) {
const MAX_MATCHES: usize = 100;
let mut visible_matches = cx.background_executor().block(fuzzy::match_strings(
&self.visible_match_candidates,
@@ -125,60 +98,50 @@ impl ProjectSymbolsView {
self.matches = matches;
self.set_selected_index(0, cx);
- cx.notify();
}
+}
- fn on_event(
- workspace: &mut Workspace,
- _: ViewHandle<Self>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Dismissed => workspace.dismiss_modal(cx),
- Event::Selected(symbol) => {
- let buffer = workspace
- .project()
- .update(cx, |project, cx| project.open_buffer_for_symbol(symbol, cx));
-
- let symbol = symbol.clone();
- cx.spawn(|workspace, mut cx| async move {
- let buffer = buffer.await?;
- workspace.update(&mut cx, |workspace, cx| {
- let position = buffer
- .read(cx)
- .clip_point_utf16(symbol.range.start, Bias::Left);
-
- let editor = workspace.open_project_item::<Editor>(buffer, cx);
- editor.update(cx, |editor, cx| {
- editor.change_selections(Some(Autoscroll::center()), cx, |s| {
- s.select_ranges([position..position])
- });
- });
- });
- Ok::<_, anyhow::Error>(())
- })
- .detach_and_log_err(cx);
- workspace.dismiss_modal(cx);
- }
- }
+impl PickerDelegate for ProjectSymbolsDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Search project symbols...".into()
}
-}
-impl PickerDelegate for ProjectSymbolsView {
- fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<ProjectSymbols>) {
if let Some(symbol) = self
.matches
.get(self.selected_match_index)
.map(|mat| self.symbols[mat.candidate_id].clone())
{
- cx.emit(Event::Selected(symbol));
+ let buffer = self.project.update(cx, |project, cx| {
+ project.open_buffer_for_symbol(&symbol, cx)
+ });
+ let symbol = symbol.clone();
+ let workspace = self.workspace.clone();
+ cx.spawn_weak(|_, mut cx| async move {
+ let buffer = buffer.await?;
+ let workspace = workspace
+ .upgrade(&cx)
+ .ok_or_else(|| anyhow!("workspace was dropped"))?;
+ workspace.update(&mut cx, |workspace, cx| {
+ let position = buffer
+ .read(cx)
+ .clip_point_utf16(symbol.range.start, Bias::Left);
+
+ let editor = workspace.open_project_item::<Editor>(buffer, cx);
+ editor.update(cx, |editor, cx| {
+ editor.change_selections(Some(Autoscroll::center()), cx, |s| {
+ s.select_ranges([position..position])
+ });
+ });
+ })?;
+ Ok::<_, anyhow::Error>(())
+ })
+ .detach_and_log_err(cx);
+ cx.emit(PickerEvent::Dismiss);
}
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
- cx.emit(Event::Dismissed);
- }
+ fn dismissed(&mut self, _cx: &mut ViewContext<ProjectSymbols>) {}
fn match_count(&self) -> usize {
self.matches.len()
@@ -188,23 +151,23 @@ impl PickerDelegate for ProjectSymbolsView {
self.selected_match_index
}
- fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
+ fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<ProjectSymbols>) {
self.selected_match_index = ix;
- cx.notify();
}
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+ fn update_matches(&mut self, query: String, cx: &mut ViewContext<ProjectSymbols>) -> Task<()> {
self.filter(&query, cx);
self.show_worktree_root_name = self.project.read(cx).visible_worktrees(cx).count() > 1;
let symbols = self
.project
.update(cx, |project, cx| project.symbols(&query, cx));
- self.pending_update = cx.spawn_weak(|this, mut cx| async move {
+ cx.spawn_weak(|this, mut cx| async move {
let symbols = symbols.await.log_err();
if let Some(this) = this.upgrade(&cx) {
if let Some(symbols) = symbols {
this.update(&mut cx, |this, cx| {
- let project = this.project.read(cx);
+ let delegate = this.delegate_mut();
+ let project = delegate.project.read(cx);
let (visible_match_candidates, external_match_candidates) = symbols
.iter()
.enumerate()
@@ -221,15 +184,15 @@ impl PickerDelegate for ProjectSymbolsView {
.map_or(false, |e| !e.is_ignored)
});
- this.visible_match_candidates = visible_match_candidates;
- this.external_match_candidates = external_match_candidates;
- this.symbols = symbols;
- this.filter(&query, cx);
- });
+ delegate.visible_match_candidates = visible_match_candidates;
+ delegate.external_match_candidates = external_match_candidates;
+ delegate.symbols = symbols;
+ delegate.filter(&query, cx);
+ })
+ .log_err();
}
}
- });
- Task::ready(())
+ })
}
fn render_match(
@@ -238,7 +201,7 @@ impl PickerDelegate for ProjectSymbolsView {
mouse_state: &mut MouseState,
selected: bool,
cx: &AppContext,
- ) -> ElementBox {
+ ) -> Element<Picker<Self>> {
let string_match = &self.matches[ix];
let settings = cx.global::<Settings>();
let style = &settings.theme.picker.item;
@@ -363,46 +326,53 @@ mod tests {
},
);
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+
// Create the project symbols view.
- let (_, symbols_view) = cx.add_window(|cx| ProjectSymbolsView::new(project.clone(), cx));
- let picker = symbols_view.read_with(cx, |symbols_view, _| symbols_view.picker.clone());
+ let symbols = cx.add_view(&workspace, |cx| {
+ ProjectSymbols::new(
+ ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()),
+ cx,
+ )
+ });
// Spawn multiples updates before the first update completes,
// such that in the end, there are no matches. Testing for regression:
// https://github.com/zed-industries/zed/issues/861
- picker.update(cx, |p, cx| {
+ symbols.update(cx, |p, cx| {
p.update_matches("o".to_string(), cx);
p.update_matches("on".to_string(), cx);
p.update_matches("onex".to_string(), cx);
});
cx.foreground().run_until_parked();
- symbols_view.read_with(cx, |symbols_view, _| {
- assert_eq!(symbols_view.matches.len(), 0);
+ symbols.read_with(cx, |symbols, _| {
+ assert_eq!(symbols.delegate().matches.len(), 0);
});
// Spawn more updates such that in the end, there are matches.
- picker.update(cx, |p, cx| {
+ symbols.update(cx, |p, cx| {
p.update_matches("one".to_string(), cx);
p.update_matches("on".to_string(), cx);
});
cx.foreground().run_until_parked();
- symbols_view.read_with(cx, |symbols_view, _| {
- assert_eq!(symbols_view.matches.len(), 2);
- assert_eq!(symbols_view.matches[0].string, "ton");
- assert_eq!(symbols_view.matches[1].string, "one");
+ symbols.read_with(cx, |symbols, _| {
+ let delegate = symbols.delegate();
+ assert_eq!(delegate.matches.len(), 2);
+ assert_eq!(delegate.matches[0].string, "ton");
+ assert_eq!(delegate.matches[1].string, "one");
});
// Spawn more updates such that in the end, there are again no matches.
- picker.update(cx, |p, cx| {
+ symbols.update(cx, |p, cx| {
p.update_matches("o".to_string(), cx);
p.update_matches("".to_string(), cx);
});
cx.foreground().run_until_parked();
- symbols_view.read_with(cx, |symbols_view, _| {
- assert_eq!(symbols_view.matches.len(), 0);
+ symbols.read_with(cx, |symbols, _| {
+ assert_eq!(symbols.delegate().matches.len(), 0);
});
}
@@ -3,7 +3,7 @@ use std::path::Path;
use fuzzy::StringMatch;
use gpui::{
elements::{Label, LabelStyle},
- Element, ElementBox,
+ Drawable, Element, View,
};
use workspace::WorkspaceLocation;
@@ -42,7 +42,7 @@ impl HighlightedText {
}
}
- pub fn render(self, style: impl Into<LabelStyle>) -> ElementBox {
+ pub fn render<V: View>(self, style: impl Into<LabelStyle>) -> Element<V> {
Label::new(self.text, style)
.with_highlights(self.highlight_positions)
.boxed()
@@ -3,14 +3,15 @@ mod highlighted_workspace_location;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions,
- elements::{ChildView, Flex, ParentElement},
- AnyViewHandle, AppContext, Element, ElementBox, Entity, RenderContext, Task, View, ViewContext,
- ViewHandle,
+ anyhow::Result,
+ elements::{Flex, ParentElement},
+ AppContext, Drawable, Element, Task, ViewContext,
};
use highlighted_workspace_location::HighlightedWorkspaceLocation;
use ordered_float::OrderedFloat;
-use picker::{Picker, PickerDelegate};
+use picker::{Picker, PickerDelegate, PickerEvent};
use settings::Settings;
+use std::sync::Arc;
use workspace::{
notifications::simple_message_notification::MessageNotification, OpenPaths, Workspace,
WorkspaceLocation, WORKSPACE_DB,
@@ -19,101 +20,70 @@ use workspace::{
actions!(projects, [OpenRecent]);
pub fn init(cx: &mut AppContext) {
- cx.add_action(RecentProjectsView::toggle);
- Picker::<RecentProjectsView>::init(cx);
+ cx.add_async_action(toggle);
+ RecentProjects::init(cx);
}
-struct RecentProjectsView {
- picker: ViewHandle<Picker<Self>>,
+fn toggle(
+ _: &mut Workspace,
+ _: &OpenRecent,
+ cx: &mut ViewContext<Workspace>,
+) -> Option<Task<Result<()>>> {
+ Some(cx.spawn(|workspace, mut cx| async move {
+ let workspace_locations: Vec<_> = cx
+ .background()
+ .spawn(async {
+ WORKSPACE_DB
+ .recent_workspaces_on_disk()
+ .await
+ .unwrap_or_default()
+ .into_iter()
+ .map(|(_, location)| location)
+ .collect()
+ })
+ .await;
+
+ workspace.update(&mut cx, |workspace, cx| {
+ if !workspace_locations.is_empty() {
+ workspace.toggle_modal(cx, |_, cx| {
+ cx.add_view(|cx| {
+ RecentProjects::new(RecentProjectsDelegate::new(workspace_locations), cx)
+ .with_max_size(800., 1200.)
+ })
+ });
+ } else {
+ workspace.show_notification(0, cx, |cx| {
+ cx.add_view(|_| MessageNotification::new_message("No recent projects to open."))
+ })
+ }
+ })?;
+ Ok(())
+ }))
+}
+
+type RecentProjects = Picker<RecentProjectsDelegate>;
+
+struct RecentProjectsDelegate {
workspace_locations: Vec<WorkspaceLocation>,
selected_match_index: usize,
matches: Vec<StringMatch>,
}
-impl RecentProjectsView {
- fn new(workspace_locations: Vec<WorkspaceLocation>, cx: &mut ViewContext<Self>) -> Self {
- let handle = cx.weak_handle();
+impl RecentProjectsDelegate {
+ fn new(workspace_locations: Vec<WorkspaceLocation>) -> Self {
Self {
- picker: cx.add_view(|cx| {
- Picker::new("Recent Projects...", handle, cx).with_max_size(800., 1200.)
- }),
workspace_locations,
selected_match_index: 0,
matches: Default::default(),
}
}
-
- fn toggle(_: &mut Workspace, _: &OpenRecent, cx: &mut ViewContext<Workspace>) {
- cx.spawn(|workspace, mut cx| async move {
- let workspace_locations: Vec<_> = cx
- .background()
- .spawn(async {
- WORKSPACE_DB
- .recent_workspaces_on_disk()
- .await
- .unwrap_or_default()
- .into_iter()
- .map(|(_, location)| location)
- .collect()
- })
- .await;
-
- workspace.update(&mut cx, |workspace, cx| {
- if !workspace_locations.is_empty() {
- workspace.toggle_modal(cx, |_, cx| {
- let view = cx.add_view(|cx| Self::new(workspace_locations, cx));
- cx.subscribe(&view, Self::on_event).detach();
- view
- });
- } else {
- workspace.show_notification(0, cx, |cx| {
- cx.add_view(|_| {
- MessageNotification::new_message("No recent projects to open.")
- })
- })
- }
- });
- })
- .detach();
- }
-
- fn on_event(
- workspace: &mut Workspace,
- _: ViewHandle<Self>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Dismissed => workspace.dismiss_modal(cx),
- }
- }
-}
-
-pub enum Event {
- Dismissed,
}
-impl Entity for RecentProjectsView {
- type Event = Event;
-}
-
-impl View for RecentProjectsView {
- fn ui_name() -> &'static str {
- "RecentProjectsView"
+impl PickerDelegate for RecentProjectsDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Recent Projects...".into()
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
- ChildView::new(&self.picker, cx).boxed()
- }
-
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.is_self_focused() {
- cx.focus(&self.picker);
- }
- }
-}
-
-impl PickerDelegate for RecentProjectsView {
fn match_count(&self) -> usize {
self.matches.len()
}
@@ -122,11 +92,15 @@ impl PickerDelegate for RecentProjectsView {
self.selected_match_index
}
- fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Self>) {
+ fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<RecentProjects>) {
self.selected_match_index = ix;
}
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
+ fn update_matches(
+ &mut self,
+ query: String,
+ cx: &mut ViewContext<RecentProjects>,
+ ) -> gpui::Task<()> {
let query = query.trim_start();
let smart_case = query.chars().any(|c| c.is_uppercase());
let candidates = self
@@ -164,19 +138,17 @@ impl PickerDelegate for RecentProjectsView {
Task::ready(())
}
- fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<RecentProjects>) {
if let Some(selected_match) = &self.matches.get(self.selected_index()) {
let workspace_location = &self.workspace_locations[selected_match.candidate_id];
cx.dispatch_action(OpenPaths {
paths: workspace_location.paths().as_ref().clone(),
});
- cx.emit(Event::Dismissed);
+ cx.emit(PickerEvent::Dismiss);
}
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
- cx.emit(Event::Dismissed);
- }
+ fn dismissed(&mut self, _cx: &mut ViewContext<RecentProjects>) {}
fn render_match(
&self,
@@ -184,7 +156,7 @@ impl PickerDelegate for RecentProjectsView {
mouse_state: &mut gpui::MouseState,
selected: bool,
cx: &gpui::AppContext,
- ) -> ElementBox {
+ ) -> Element<Picker<Self>> {
let settings = cx.global::<Settings>();
let string_match = &self.matches[ix];
let style = settings.theme.picker.item.style_for(mouse_state, selected);
@@ -9,13 +9,13 @@ use gpui::{
elements::*,
impl_actions,
platform::{CursorStyle, MouseButton},
- Action, AnyViewHandle, AppContext, Entity, RenderContext, Subscription, Task, View,
- ViewContext, ViewHandle,
+ Action, AnyViewHandle, AppContext, Entity, Subscription, Task, View, ViewContext, ViewHandle,
};
use project::search::SearchQuery;
use serde::Deserialize;
use settings::Settings;
use std::{any::Any, sync::Arc};
+use util::ResultExt;
use workspace::{
item::ItemHandle,
searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle},
@@ -92,7 +92,7 @@ impl View for BufferSearchBar {
}
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = cx.global::<Settings>().theme.clone();
let editor_container = if self.query_contains_error {
theme.search.invalid_editor
@@ -324,8 +324,8 @@ impl BufferSearchBar {
option_supported: bool,
icon: &'static str,
option: SearchOption,
- cx: &mut RenderContext<Self>,
- ) -> Option<ElementBox> {
+ cx: &mut ViewContext<Self>,
+ ) -> Option<Element<Self>> {
if !option_supported {
return None;
}
@@ -333,7 +333,7 @@ impl BufferSearchBar {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let is_active = self.is_search_option_enabled(option);
Some(
- MouseEventHandler::<Self>::new(option as usize, cx, |state, cx| {
+ MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
let style = cx
.global::<Settings>()
.theme
@@ -345,11 +345,11 @@ impl BufferSearchBar {
.with_style(style.container)
.boxed()
})
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_any_action(option.to_toggle_action())
})
.with_cursor_style(CursorStyle::PointingHand)
- .with_tooltip::<Self, _>(
+ .with_tooltip::<Self>(
option as usize,
format!("Toggle {}", option.label()),
Some(option.to_toggle_action()),
@@ -364,8 +364,8 @@ impl BufferSearchBar {
&self,
icon: &'static str,
direction: Direction,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let action: Box<dyn Action>;
let tooltip;
match direction {
@@ -381,7 +381,7 @@ impl BufferSearchBar {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
enum NavButton {}
- MouseEventHandler::<NavButton>::new(direction as usize, cx, |state, cx| {
+ MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
let style = cx
.global::<Settings>()
.theme
@@ -395,10 +395,10 @@ impl BufferSearchBar {
})
.on_click(MouseButton::Left, {
let action = action.boxed_clone();
- move |_, cx| cx.dispatch_any_action(action.boxed_clone())
+ move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
})
.with_cursor_style(CursorStyle::PointingHand)
- .with_tooltip::<NavButton, _>(
+ .with_tooltip::<NavButton>(
direction as usize,
tooltip.to_string(),
Some(action),
@@ -411,14 +411,14 @@ impl BufferSearchBar {
fn render_close_button(
&self,
theme: &theme::Search,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let action = Box::new(Dismiss);
let tooltip = "Dismiss Buffer Search";
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
enum CloseButton {}
- MouseEventHandler::<CloseButton>::new(0, cx, |state, _| {
+ MouseEventHandler::<CloseButton, _>::new(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state, false);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)
@@ -433,10 +433,10 @@ impl BufferSearchBar {
})
.on_click(MouseButton::Left, {
let action = action.boxed_clone();
- move |_, cx| cx.dispatch_any_action(action.boxed_clone())
+ move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
})
.with_cursor_style(CursorStyle::PointingHand)
- .with_tooltip::<CloseButton, _>(0, tooltip.to_string(), Some(action), tooltip_style, cx)
+ .with_tooltip::<CloseButton>(0, tooltip.to_string(), Some(action), tooltip_style, cx)
.boxed()
}
@@ -618,7 +618,8 @@ impl BufferSearchBar {
}
cx.notify();
}
- });
+ })
+ .log_err();
}
}));
}
@@ -12,9 +12,8 @@ use gpui::{
actions,
elements::*,
platform::{CursorStyle, MouseButton},
- Action, AnyViewHandle, AppContext, ElementBox, Entity, ModelContext, ModelHandle,
- RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakModelHandle,
- WeakViewHandle,
+ Action, AnyViewHandle, AppContext, Element, Entity, ModelContext, ModelHandle, Subscription,
+ Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
};
use menu::Confirm;
use project::{search::SearchQuery, Project};
@@ -30,7 +29,7 @@ use std::{
};
use util::ResultExt as _;
use workspace::{
- item::{Item, ItemEvent, ItemHandle},
+ item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
searchable::{Direction, SearchableItem, SearchableItemHandle},
ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
};
@@ -179,7 +178,7 @@ impl View for ProjectSearchView {
"ProjectSearchView"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let model = &self.model.read(cx);
if model.match_ranges.is_empty() {
enum Status {}
@@ -192,7 +191,7 @@ impl View for ProjectSearchView {
} else {
"No results"
};
- MouseEventHandler::<Status>::new(0, cx, |_, _| {
+ MouseEventHandler::<Status, _>::new(0, cx, |_, _| {
Label::new(text, theme.search.results_status.clone())
.aligned()
.contained()
@@ -200,7 +199,7 @@ impl View for ProjectSearchView {
.flex(1., true)
.boxed()
})
- .on_down(MouseButton::Left, |_, cx| {
+ .on_down(MouseButton::Left, |_, _, cx| {
cx.focus_parent_view();
})
.boxed()
@@ -250,12 +249,12 @@ impl Item for ProjectSearchView {
.update(cx, |editor, cx| editor.deactivated(cx));
}
- fn tab_content(
+ fn tab_content<T: View>(
&self,
_detail: Option<usize>,
tab_theme: &theme::Tab,
cx: &AppContext,
- ) -> ElementBox {
+ ) -> Element<T> {
Flex::row()
.with_child(
Svg::new("icons/magnifying_glass_12.svg")
@@ -370,7 +369,7 @@ impl Item for ProjectSearchView {
}
}
- fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
+ fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
self.results_editor.breadcrumbs(theme, cx)
}
@@ -752,8 +751,8 @@ impl ProjectSearchBar {
&self,
icon: &'static str,
direction: Direction,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let action: Box<dyn Action>;
let tooltip;
match direction {
@@ -769,7 +768,7 @@ impl ProjectSearchBar {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
enum NavButton {}
- MouseEventHandler::<NavButton>::new(direction as usize, cx, |state, cx| {
+ MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
let style = &cx
.global::<Settings>()
.theme
@@ -783,10 +782,10 @@ impl ProjectSearchBar {
})
.on_click(MouseButton::Left, {
let action = action.boxed_clone();
- move |_, cx| cx.dispatch_any_action(action.boxed_clone())
+ move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
})
.with_cursor_style(CursorStyle::PointingHand)
- .with_tooltip::<NavButton, _>(
+ .with_tooltip::<NavButton>(
direction as usize,
tooltip.to_string(),
Some(action),
@@ -800,11 +799,11 @@ impl ProjectSearchBar {
&self,
icon: &'static str,
option: SearchOption,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let is_active = self.is_option_enabled(option, cx);
- MouseEventHandler::<Self>::new(option as usize, cx, |state, cx| {
+ MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
let style = &cx
.global::<Settings>()
.theme
@@ -816,11 +815,11 @@ impl ProjectSearchBar {
.with_style(style.container)
.boxed()
})
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_any_action(option.to_toggle_action())
})
.with_cursor_style(CursorStyle::PointingHand)
- .with_tooltip::<Self, _>(
+ .with_tooltip::<Self>(
option as usize,
format!("Toggle {}", option.label()),
Some(option.to_toggle_action()),
@@ -853,7 +852,7 @@ impl View for ProjectSearchBar {
"ProjectSearchBar"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
if let Some(search) = self.active_project_search.as_ref() {
let search = search.read(cx);
let theme = cx.global::<Settings>().theme.clone();
@@ -80,9 +80,25 @@ mod tests {
watch_files, watched_json::watch_settings_file, EditorSettings, Settings, SoftWrap,
};
use fs::FakeFs;
- use gpui::{actions, Action};
+ use gpui::{actions, elements::*, Action, Entity, View, ViewContext, WindowContext};
use theme::ThemeRegistry;
+ struct TestView;
+
+ impl Entity for TestView {
+ type Event = ();
+ }
+
+ impl View for TestView {
+ fn ui_name() -> &'static str {
+ "TestView"
+ }
+
+ fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
+ Empty::new().boxed()
+ }
+ }
+
#[gpui::test]
async fn test_base_keymap(cx: &mut gpui::TestAppContext) {
let executor = cx.background();
@@ -148,8 +164,10 @@ mod tests {
cx.foreground().run_until_parked();
+ let (window_id, _view) = cx.add_window(|_| TestView);
+
// Test loading the keymap base at all
- cx.update(|cx| {
+ cx.read_window(window_id, |cx| {
assert_key_bindings_for(
cx,
vec![("backspace", &A), ("k", &ActivatePreviousPane)],
@@ -177,7 +195,7 @@ mod tests {
cx.foreground().run_until_parked();
- cx.update(|cx| {
+ cx.read_window(window_id, |cx| {
assert_key_bindings_for(
cx,
vec![("backspace", &B), ("k", &ActivatePreviousPane)],
@@ -201,7 +219,7 @@ mod tests {
cx.foreground().run_until_parked();
- cx.update(|cx| {
+ cx.read_window(window_id, |cx| {
assert_key_bindings_for(
cx,
vec![("backspace", &B), ("[", &ActivatePrevItem)],
@@ -211,14 +229,14 @@ mod tests {
}
fn assert_key_bindings_for<'a>(
- cx: &mut AppContext,
+ cx: &WindowContext,
actions: Vec<(&'static str, &'a dyn Action)>,
line: u32,
) {
for (key, action) in actions {
// assert that...
assert!(
- cx.available_actions(0, 0).any(|(_, bound_action, b)| {
+ cx.available_actions(0).any(|(_, bound_action, b)| {
// action names match...
bound_action.name() == action.name()
&& bound_action.namespace() == action.namespace()
@@ -735,7 +735,7 @@ mod tests {
.map_or(5, |o| o.parse().expect("invalid OPERATIONS variable"));
for seed in starting_seed..(starting_seed + num_iterations) {
- dbg!(seed);
+ eprintln!("seed = {}", seed);
let mut rng = StdRng::seed_from_u64(seed);
let rng = &mut rng;
@@ -3,8 +3,8 @@ use gpui::{
elements::*,
impl_internal_actions,
platform::{CursorStyle, MouseButton},
- AppContext, Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle,
- WeakModelHandle, WeakViewHandle,
+ AppContext, Drawable, Element, Entity, View, ViewContext, ViewHandle, WeakModelHandle,
+ WeakViewHandle,
};
use settings::Settings;
use std::any::TypeId;
@@ -42,14 +42,14 @@ impl View for TerminalButton {
"TerminalButton"
}
- fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let workspace = self.workspace.upgrade(cx);
let project = match workspace {
Some(workspace) => workspace.read(cx).project().read(cx),
None => return Empty::new().boxed(),
};
- let focused_view = cx.focused_view_id(cx.window_id());
+ let focused_view = cx.focused_view_id();
let active = focused_view
.map(|view_id| {
cx.view_type_id(cx.window_id(), view_id) == Some(TypeId::of::<TerminalView>())
@@ -62,7 +62,7 @@ impl View for TerminalButton {
Stack::new()
.with_child(
- MouseEventHandler::<Self>::new(0, cx, {
+ MouseEventHandler::<Self, _>::new(0, cx, {
let theme = theme.clone();
move |state, _cx| {
let style = theme
@@ -96,7 +96,7 @@ impl View for TerminalButton {
}
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
if has_terminals {
cx.dispatch_action(DeployTerminalMenu);
} else {
@@ -105,7 +105,7 @@ impl View for TerminalButton {
}
};
})
- .with_tooltip::<Self, _>(
+ .with_tooltip::<Self>(
0,
"Show Terminal".into(),
Some(Box::new(FocusDock)),
@@ -10,8 +10,8 @@ use gpui::{
platform::{CursorStyle, MouseButton},
serde_json::json,
text_layout::{Line, RunStyle},
- Element, ElementBox, EventContext, FontCache, ModelContext, MouseRegion, PaintContext, Quad,
- SizeConstraint, TextLayoutCache, WeakModelHandle, WeakViewHandle,
+ Drawable, Element, EventContext, FontCache, ModelContext, MouseRegion, Quad, SceneBuilder,
+ SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle,
};
use itertools::Itertools;
use language::CursorShape;
@@ -45,7 +45,7 @@ pub struct LayoutState {
size: TerminalSize,
mode: TermMode,
display_offset: usize,
- hyperlink_tooltip: Option<ElementBox>,
+ hyperlink_tooltip: Option<Element<TerminalView>>,
}
///Helper struct for converting data between alacritty's cursor points, and displayed cursor points
@@ -84,10 +84,12 @@ impl LayoutCell {
fn paint(
&self,
+ scene: &mut SceneBuilder,
origin: Vector2F,
layout: &LayoutState,
visible_bounds: RectF,
- cx: &mut PaintContext,
+ _view: &mut TerminalView,
+ cx: &mut ViewContext<TerminalView>,
) {
let pos = {
let point = self.point;
@@ -98,7 +100,7 @@ impl LayoutCell {
};
self.text
- .paint(pos, visible_bounds, layout.size.line_height, cx);
+ .paint(scene, pos, visible_bounds, layout.size.line_height, cx);
}
}
@@ -126,7 +128,14 @@ impl LayoutRect {
}
}
- fn paint(&self, origin: Vector2F, layout: &LayoutState, cx: &mut PaintContext) {
+ fn paint(
+ &self,
+ scene: &mut SceneBuilder,
+ origin: Vector2F,
+ layout: &LayoutState,
+ _view: &mut TerminalView,
+ _cx: &mut ViewContext<TerminalView>,
+ ) {
let position = {
let point = self.point;
vec2f(
@@ -139,7 +148,7 @@ impl LayoutRect {
layout.size.line_height,
);
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: RectF::new(position, size),
background: Some(self.color),
border: Default::default(),
@@ -152,20 +161,17 @@ impl LayoutRect {
///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
pub struct TerminalElement {
terminal: WeakModelHandle<Terminal>,
- view: WeakViewHandle<TerminalView>,
focused: bool,
cursor_visible: bool,
}
impl TerminalElement {
pub fn new(
- view: WeakViewHandle<TerminalView>,
terminal: WeakModelHandle<Terminal>,
focused: bool,
cursor_visible: bool,
) -> TerminalElement {
TerminalElement {
- view,
terminal,
focused,
cursor_visible,
@@ -361,11 +367,11 @@ impl TerminalElement {
connection: WeakModelHandle<Terminal>,
origin: Vector2F,
f: impl Fn(&mut Terminal, Vector2F, E, &mut ModelContext<Terminal>),
- ) -> impl Fn(E, &mut EventContext) {
- move |event, cx| {
+ ) -> impl Fn(E, &mut TerminalView, &mut EventContext<TerminalView>) {
+ move |event, _: &mut TerminalView, cx| {
cx.focus_parent_view();
- if let Some(conn_handle) = connection.upgrade(cx.app) {
- conn_handle.update(cx.app, |terminal, cx| {
+ if let Some(conn_handle) = connection.upgrade(cx) {
+ conn_handle.update(cx, |terminal, cx| {
f(terminal, origin, event, cx);
cx.notify();
@@ -376,15 +382,15 @@ impl TerminalElement {
fn attach_mouse_handlers(
&self,
+ scene: &mut SceneBuilder,
origin: Vector2F,
- view_id: usize,
visible_bounds: RectF,
mode: TermMode,
- cx: &mut PaintContext,
+ cx: &mut ViewContext<TerminalView>,
) {
let connection = self.terminal;
- let mut region = MouseRegion::new::<Self>(view_id, 0, visible_bounds);
+ let mut region = MouseRegion::new::<Self>(cx.view_id(), 0, visible_bounds);
// Terminal Emulator controlled behavior:
region = region
@@ -400,10 +406,10 @@ impl TerminalElement {
),
)
// Update drag selections
- .on_drag(MouseButton::Left, move |event, cx| {
+ .on_drag(MouseButton::Left, move |event, _: &mut TerminalView, cx| {
if cx.is_parent_view_focused() {
- if let Some(conn_handle) = connection.upgrade(cx.app) {
- conn_handle.update(cx.app, |terminal, cx| {
+ if let Some(conn_handle) = connection.upgrade(cx) {
+ conn_handle.update(cx, |terminal, cx| {
terminal.mouse_drag(event, origin);
cx.notify();
})
@@ -422,9 +428,9 @@ impl TerminalElement {
),
)
// Context menu
- .on_click(MouseButton::Right, move |e, cx| {
- let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) {
- conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift))
+ .on_click(MouseButton::Right, move |e, _: &mut TerminalView, cx| {
+ let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx) {
+ conn_handle.update(cx, |terminal, _cx| terminal.mouse_mode(e.shift))
} else {
// If we can't get the model handle, probably can't deploy the context menu
true
@@ -435,20 +441,19 @@ impl TerminalElement {
});
}
})
- .on_move(move |event, cx| {
+ .on_move(move |event, _: &mut TerminalView, cx| {
if cx.is_parent_view_focused() {
- if let Some(conn_handle) = connection.upgrade(cx.app) {
- conn_handle.update(cx.app, |terminal, cx| {
+ if let Some(conn_handle) = connection.upgrade(cx) {
+ conn_handle.update(cx, |terminal, cx| {
terminal.mouse_move(&event, origin);
cx.notify();
})
}
}
})
- .on_scroll(move |event, cx| {
- // cx.focus_parent_view();
- if let Some(conn_handle) = connection.upgrade(cx.app) {
- conn_handle.update(cx.app, |terminal, cx| {
+ .on_scroll(move |event, _: &mut TerminalView, cx| {
+ if let Some(conn_handle) = connection.upgrade(cx) {
+ conn_handle.update(cx, |terminal, cx| {
terminal.scroll_wheel(event, origin);
cx.notify();
})
@@ -501,7 +506,7 @@ impl TerminalElement {
)
}
- cx.scene.push_mouse_region(region);
+ scene.push_mouse_region(region);
}
///Configures a text style from the current settings.
@@ -546,14 +551,15 @@ impl TerminalElement {
}
}
-impl Element for TerminalElement {
+impl Drawable<TerminalView> for TerminalElement {
type LayoutState = LayoutState;
type PaintState = ();
fn layout(
&mut self,
constraint: gpui::SizeConstraint,
- cx: &mut gpui::LayoutContext,
+ view: &mut TerminalView,
+ cx: &mut ViewContext<TerminalView>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
let settings = cx.global::<Settings>();
let font_cache = cx.font_cache();
@@ -581,34 +587,31 @@ impl Element for TerminalElement {
let background_color = terminal_theme.background;
let terminal_handle = self.terminal.upgrade(cx).unwrap();
- let last_hovered_hyperlink = terminal_handle.update(cx.app, |terminal, cx| {
+ let last_hovered_hyperlink = terminal_handle.update(cx, |terminal, cx| {
terminal.set_size(dimensions);
terminal.try_sync(cx);
terminal.last_content.last_hovered_hyperlink.clone()
});
- let view_handle = self.view.clone();
- let hyperlink_tooltip = last_hovered_hyperlink.and_then(|(uri, _, id)| {
- // last_mouse.and_then(|_last_mouse| {
- view_handle.upgrade(cx).map(|handle| {
- let mut tooltip = cx.render(&handle, |_, cx| {
- Overlay::new(
- Empty::new()
- .contained()
- .constrained()
- .with_width(dimensions.width())
- .with_height(dimensions.height())
- .with_tooltip::<TerminalElement, _>(id, uri, None, tooltip_style, cx)
- .boxed(),
- )
- .with_position_mode(gpui::elements::OverlayPositionMode::Local)
- .boxed()
- });
-
- tooltip.layout(SizeConstraint::new(Vector2F::zero(), cx.window_size), cx);
- tooltip
- })
- // })
+ let hyperlink_tooltip = last_hovered_hyperlink.map(|(uri, _, id)| {
+ let mut tooltip = Overlay::new(
+ Empty::new()
+ .contained()
+ .constrained()
+ .with_width(dimensions.width())
+ .with_height(dimensions.height())
+ .with_tooltip::<TerminalElement>(id, uri, None, tooltip_style, cx)
+ .boxed(),
+ )
+ .with_position_mode(gpui::elements::OverlayPositionMode::Local)
+ .boxed();
+
+ tooltip.layout(
+ SizeConstraint::new(Vector2F::zero(), cx.window_size()),
+ view,
+ cx,
+ );
+ tooltip
});
let TerminalContent {
@@ -637,7 +640,7 @@ impl Element for TerminalElement {
cells,
&text_style,
&terminal_theme,
- cx.text_layout_cache,
+ cx.text_layout_cache(),
cx.font_cache(),
last_hovered_hyperlink
.as_ref()
@@ -659,7 +662,7 @@ impl Element for TerminalElement {
terminal_theme.foreground
};
- cx.text_layout_cache.layout_str(
+ cx.text_layout_cache().layout_str(
&str_trxt,
text_style.font_size,
&[(
@@ -717,23 +720,25 @@ impl Element for TerminalElement {
fn paint(
&mut self,
- bounds: gpui::geometry::rect::RectF,
- visible_bounds: gpui::geometry::rect::RectF,
+ scene: &mut SceneBuilder,
+ bounds: RectF,
+ visible_bounds: RectF,
layout: &mut Self::LayoutState,
- cx: &mut gpui::PaintContext,
+ view: &mut TerminalView,
+ cx: &mut ViewContext<TerminalView>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
//Setup element stuff
let clip_bounds = Some(visible_bounds);
- cx.paint_layer(clip_bounds, |cx| {
+ scene.paint_layer(clip_bounds, |scene| {
let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
// Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
- self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, layout.mode, cx);
+ self.attach_mouse_handlers(scene, origin, visible_bounds, layout.mode, cx);
- cx.scene.push_cursor_region(gpui::CursorRegion {
+ scene.push_cursor_region(gpui::CursorRegion {
bounds,
style: if layout.hyperlink_tooltip.is_some() {
CursorStyle::PointingHand
@@ -742,9 +747,9 @@ impl Element for TerminalElement {
},
});
- cx.paint_layer(clip_bounds, |cx| {
+ scene.paint_layer(clip_bounds, |scene| {
//Start with a background color
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: RectF::new(bounds.origin(), bounds.size()),
background: Some(layout.background_color),
border: Default::default(),
@@ -752,12 +757,12 @@ impl Element for TerminalElement {
});
for rect in &layout.rects {
- rect.paint(origin, layout, cx)
+ rect.paint(scene, origin, layout, view, cx)
}
});
//Draw Highlighted Backgrounds
- cx.paint_layer(clip_bounds, |cx| {
+ scene.paint_layer(clip_bounds, |scene| {
for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter()
{
if let Some((start_y, highlighted_range_lines)) =
@@ -771,29 +776,29 @@ impl Element for TerminalElement {
//Copied from editor. TODO: move to theme or something
corner_radius: 0.15 * layout.size.line_height,
};
- hr.paint(bounds, cx.scene);
+ hr.paint(bounds, scene);
}
}
});
//Draw the text cells
- cx.paint_layer(clip_bounds, |cx| {
+ scene.paint_layer(clip_bounds, |scene| {
for cell in &layout.cells {
- cell.paint(origin, layout, visible_bounds, cx);
+ cell.paint(scene, origin, layout, visible_bounds, view, cx);
}
});
//Draw cursor
if self.cursor_visible {
if let Some(cursor) = &layout.cursor {
- cx.paint_layer(clip_bounds, |cx| {
- cursor.paint(origin, cx);
+ scene.paint_layer(clip_bounds, |scene| {
+ cursor.paint(scene, origin, cx);
})
}
}
if let Some(element) = &mut layout.hyperlink_tooltip {
- element.paint(origin, visible_bounds, cx)
+ element.paint(scene, origin, visible_bounds, view, cx)
}
});
}
@@ -804,10 +809,11 @@ impl Element for TerminalElement {
fn debug(
&self,
- _bounds: gpui::geometry::rect::RectF,
- _layout: &Self::LayoutState,
- _paint: &Self::PaintState,
- _cx: &gpui::DebugContext,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &TerminalView,
+ _: &gpui::ViewContext<TerminalView>,
) -> gpui::serde_json::Value {
json!({
"type": "TerminalElement",
@@ -821,7 +827,8 @@ impl Element for TerminalElement {
_: RectF,
layout: &Self::LayoutState,
_: &Self::PaintState,
- _: &gpui::MeasurementContext,
+ _: &TerminalView,
+ _: &gpui::ViewContext<TerminalView>,
) -> Option<RectF> {
// Use the same origin that's passed to `Cursor::paint` in the paint
// method bove.
@@ -13,12 +13,12 @@ use context_menu::{ContextMenu, ContextMenuItem};
use dirs::home_dir;
use gpui::{
actions,
- elements::{AnchorCorner, ChildView, Flex, Label, ParentElement, Stack, Text},
+ elements::{AnchorCorner, ChildView, Flex, Label, ParentElement, Stack},
geometry::vector::Vector2F,
impl_actions, impl_internal_actions,
keymap_matcher::{KeymapContext, Keystroke},
platform::KeyDownEvent,
- AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, Task, View, ViewContext,
+ AnyViewHandle, AppContext, Drawable, Element, Entity, ModelHandle, Task, View, ViewContext,
ViewHandle, WeakViewHandle,
};
use project::{LocalWorktree, Project};
@@ -35,7 +35,7 @@ use terminal::{
};
use util::ResultExt;
use workspace::{
- item::{Item, ItemEvent},
+ item::{BreadcrumbText, Item, ItemEvent},
notifications::NotifyResultExt,
pane, register_deserializable_item,
searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle},
@@ -237,11 +237,7 @@ impl TerminalView {
cx.notify();
}
- pub fn should_show_cursor(
- &self,
- focused: bool,
- cx: &mut gpui::RenderContext<'_, Self>,
- ) -> bool {
+ pub fn should_show_cursor(&self, focused: bool, cx: &mut gpui::ViewContext<Self>) -> bool {
//Don't blink the cursor when not focused, blinking is disabled, or paused
if !focused
|| !self.blinking_on
@@ -284,7 +280,8 @@ impl TerminalView {
async move {
Timer::after(CURSOR_BLINK_INTERVAL).await;
if let Some(this) = this.upgrade(&cx) {
- this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx));
+ this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx))
+ .log_err();
}
}
})
@@ -303,6 +300,7 @@ impl TerminalView {
Timer::after(CURSOR_BLINK_INTERVAL).await;
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
+ .log_err();
}
}
})
@@ -389,19 +387,18 @@ impl View for TerminalView {
"Terminal"
}
- fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Element<Self> {
let terminal_handle = self.terminal.clone().downgrade();
let self_id = cx.view_id();
let focused = cx
- .focused_view_id(cx.window_id())
+ .focused_view_id()
.filter(|view_id| *view_id == self_id)
.is_some();
Stack::new()
.with_child(
TerminalElement::new(
- cx.handle(),
terminal_handle,
focused,
self.should_show_cursor(focused, cx),
@@ -548,12 +545,12 @@ impl Item for TerminalView {
Some(self.terminal().read(cx).title().into())
}
- fn tab_content(
+ fn tab_content<T: View>(
&self,
_detail: Option<usize>,
tab_theme: &theme::Tab,
cx: &gpui::AppContext,
- ) -> ElementBox {
+ ) -> Element<T> {
let title = self.terminal().read(cx).title();
Flex::row()
@@ -615,12 +612,11 @@ impl Item for TerminalView {
ToolbarItemLocation::PrimaryLeft { flex: None }
}
- fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
- Some(vec![Text::new(
- self.terminal().read(cx).breadcrumb_text.clone(),
- theme.workspace.breadcrumbs.default.text.clone(),
- )
- .boxed()])
+ fn breadcrumbs(&self, _: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
+ Some(vec![BreadcrumbText {
+ text: self.terminal().read(cx).breadcrumb_text.clone(),
+ highlights: None,
+ }])
}
fn serialized_item_kind() -> Option<&'static str> {
@@ -11,7 +11,7 @@ use gpui::{
platform,
platform::MouseButton,
scene::MouseClick,
- Action, Element, ElementBox, EventContext, MouseState, RenderContext, View,
+ Action, Drawable, Element, EventContext, MouseState, View, ViewContext,
};
use serde::Deserialize;
@@ -27,13 +27,13 @@ pub struct CheckboxStyle {
pub hovered_and_checked: ContainerStyle,
}
-pub fn checkbox<T: 'static, V: View>(
+pub fn checkbox<Tag: 'static, V: View>(
label: &'static str,
style: &CheckboxStyle,
checked: bool,
- cx: &mut RenderContext<V>,
- change: fn(checked: bool, cx: &mut EventContext) -> (),
-) -> MouseEventHandler<T> {
+ cx: &mut ViewContext<V>,
+ change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
+) -> MouseEventHandler<Tag, V> {
let label = Label::new(label, style.label.text.clone())
.contained()
.with_style(style.label.container)
@@ -42,14 +42,14 @@ pub fn checkbox<T: 'static, V: View>(
checkbox_with_label(label, style, checked, cx, change)
}
-pub fn checkbox_with_label<T: 'static, V: View>(
- label: ElementBox,
+pub fn checkbox_with_label<Tag: 'static, V: View>(
+ label: Element<V>,
style: &CheckboxStyle,
checked: bool,
- cx: &mut RenderContext<V>,
- change: fn(checked: bool, cx: &mut EventContext) -> (),
-) -> MouseEventHandler<T> {
- MouseEventHandler::<T>::new(0, cx, |state, _| {
+ cx: &mut ViewContext<V>,
+ change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
+) -> MouseEventHandler<Tag, V> {
+ MouseEventHandler::new(0, cx, |state, _| {
let indicator = if checked {
svg(&style.icon)
} else {
@@ -82,7 +82,7 @@ pub fn checkbox_with_label<T: 'static, V: View>(
.align_children_center()
.boxed()
})
- .on_click(platform::MouseButton::Left, move |_, cx| {
+ .on_click(platform::MouseButton::Left, move |_, _, cx| {
change(!checked, cx)
})
.with_cursor_style(platform::CursorStyle::PointingHand)
@@ -107,7 +107,7 @@ impl Dimensions {
}
}
-pub fn svg(style: &SvgStyle) -> ConstrainedBox {
+pub fn svg<V: View>(style: &SvgStyle) -> ConstrainedBox<V> {
Svg::new(style.asset.clone())
.with_color(style.color)
.constrained()
@@ -121,7 +121,7 @@ pub struct IconStyle {
container: ContainerStyle,
}
-pub fn icon(style: &IconStyle) -> Container {
+pub fn icon<V: View>(style: &IconStyle) -> Container<V> {
svg(&style.icon).contained().with_style(style.container)
}
@@ -130,12 +130,11 @@ pub fn keystroke_label<V: View>(
label_style: &ContainedText,
keystroke_style: &ContainedText,
action: Box<dyn Action>,
- cx: &mut RenderContext<V>,
-) -> Container {
+ cx: &mut ViewContext<V>,
+) -> Container<V> {
// FIXME: Put the theme in it's own global so we can
// query the keystroke style on our own
keystroke_label_for(
- cx.window_id(),
cx.handle().id(),
label_text,
label_style,
@@ -144,14 +143,13 @@ pub fn keystroke_label<V: View>(
)
}
-pub fn keystroke_label_for(
- window_id: usize,
+pub fn keystroke_label_for<V: View>(
view_id: usize,
label_text: &'static str,
label_style: &ContainedText,
keystroke_style: &ContainedText,
action: Box<dyn Action>,
-) -> Container {
+) -> Container<V> {
Flex::row()
.with_child(
Label::new(label_text, label_style.text.clone())
@@ -160,7 +158,6 @@ pub fn keystroke_label_for(
)
.with_child({
KeystrokeLabel::new(
- window_id,
view_id,
action,
keystroke_style.container,
@@ -180,32 +177,33 @@ pub fn cta_button<L, A, V>(
action: A,
max_width: f32,
style: &ButtonStyle,
- cx: &mut RenderContext<V>,
-) -> ElementBox
+ cx: &mut ViewContext<V>,
+) -> Element<V>
where
L: Into<Cow<'static, str>>,
A: 'static + Action + Clone,
V: View,
{
- cta_button_with_click(label, max_width, style, cx, move |_, cx| {
+ cta_button_with_click::<A, _, _, _>(label, max_width, style, cx, move |_, _, cx| {
cx.dispatch_action(action.clone())
})
.boxed()
}
-pub fn cta_button_with_click<L, V, F>(
+pub fn cta_button_with_click<Tag, L, V, F>(
label: L,
max_width: f32,
style: &ButtonStyle,
- cx: &mut RenderContext<V>,
+ cx: &mut ViewContext<V>,
f: F,
-) -> MouseEventHandler<F>
+) -> MouseEventHandler<Tag, V>
where
+ Tag: 'static,
L: Into<Cow<'static, str>>,
V: View,
- F: Fn(MouseClick, &mut EventContext) + 'static,
+ F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
{
- MouseEventHandler::<F>::new(0, cx, |state, _| {
+ MouseEventHandler::<Tag, V>::new(0, cx, |state, _| {
let style = style.style_for(state, false);
Label::new(label, style.text.to_owned())
.aligned()
@@ -234,16 +232,17 @@ impl ModalStyle {
}
}
-pub fn modal<V, I, F>(
+pub fn modal<Tag, V, I, F>(
title: I,
style: &ModalStyle,
- cx: &mut RenderContext<V>,
+ cx: &mut ViewContext<V>,
build_modal: F,
-) -> ElementBox
+) -> Element<V>
where
+ Tag: 'static,
V: View,
I: Into<Cow<'static, str>>,
- F: FnOnce(&mut gpui::RenderContext<V>) -> ElementBox,
+ F: FnOnce(&mut gpui::ViewContext<V>) -> Element<V>,
{
const TITLEBAR_HEIGHT: f32 = 28.;
// let active = cx.window_is_active(cx.window_id());
@@ -261,13 +260,12 @@ where
)
.boxed(),
// FIXME: Get a better tag type
- MouseEventHandler::<V>::new(999999, cx, |state, _cx| {
+ MouseEventHandler::<Tag, V>::new(999999, cx, |state, _cx| {
let style = style.close_icon.style_for(state, false);
icon(style).boxed()
})
- .on_click(platform::MouseButton::Left, move |_, cx| {
- let window_id = cx.window_id();
- cx.remove_window(window_id);
+ .on_click(platform::MouseButton::Left, move |_, _, cx| {
+ cx.remove_window();
})
.with_cursor_style(platform::CursorStyle::PointingHand)
.aligned()
@@ -1,45 +1,57 @@
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
-use gpui::{
- actions, elements::*, AnyViewHandle, AppContext, Element, ElementBox, Entity, MouseState,
- RenderContext, View, ViewContext, ViewHandle,
-};
-use picker::{Picker, PickerDelegate};
+use gpui::{actions, elements::*, AppContext, Drawable, Element, MouseState, ViewContext};
+use picker::{Picker, PickerDelegate, PickerEvent};
use settings::{settings_file::SettingsFile, Settings};
use staff_mode::StaffMode;
use std::sync::Arc;
use theme::{Theme, ThemeMeta, ThemeRegistry};
+use util::ResultExt;
use workspace::{AppState, Workspace};
-pub struct ThemeSelector {
- registry: Arc<ThemeRegistry>,
- theme_data: Vec<ThemeMeta>,
- matches: Vec<StringMatch>,
- original_theme: Arc<Theme>,
- picker: ViewHandle<Picker<Self>>,
- selection_completed: bool,
- selected_index: usize,
-}
-
actions!(theme_selector, [Toggle, Reload]);
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
- Picker::<ThemeSelector>::init(cx);
cx.add_action({
let theme_registry = app_state.themes.clone();
- move |workspace, _: &Toggle, cx| {
- ThemeSelector::toggle(workspace, theme_registry.clone(), cx)
- }
+ move |workspace, _: &Toggle, cx| toggle(workspace, theme_registry.clone(), cx)
});
+ ThemeSelector::init(cx);
}
-pub enum Event {
- Dismissed,
+fn toggle(workspace: &mut Workspace, themes: Arc<ThemeRegistry>, cx: &mut ViewContext<Workspace>) {
+ workspace.toggle_modal(cx, |_, cx| {
+ cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(themes, cx), cx))
+ });
}
-impl ThemeSelector {
- fn new(registry: Arc<ThemeRegistry>, cx: &mut ViewContext<Self>) -> Self {
- let handle = cx.weak_handle();
- let picker = cx.add_view(|cx| Picker::new("Select Theme...", handle, cx));
+#[cfg(debug_assertions)]
+pub fn reload(themes: Arc<ThemeRegistry>, cx: &mut AppContext) {
+ let current_theme_name = cx.global::<Settings>().theme.meta.name.clone();
+ themes.clear();
+ match themes.get(¤t_theme_name) {
+ Ok(theme) => {
+ ThemeSelectorDelegate::set_theme(theme, cx);
+ log::info!("reloaded theme {}", current_theme_name);
+ }
+ Err(error) => {
+ log::error!("failed to load theme {}: {:?}", current_theme_name, error)
+ }
+ }
+}
+
+pub type ThemeSelector = Picker<ThemeSelectorDelegate>;
+
+pub struct ThemeSelectorDelegate {
+ registry: Arc<ThemeRegistry>,
+ theme_data: Vec<ThemeMeta>,
+ matches: Vec<StringMatch>,
+ original_theme: Arc<Theme>,
+ selection_completed: bool,
+ selected_index: usize,
+}
+
+impl ThemeSelectorDelegate {
+ fn new(registry: Arc<ThemeRegistry>, cx: &mut ViewContext<ThemeSelector>) -> Self {
let settings = cx.global::<Settings>();
let original_theme = settings.theme.clone();
@@ -61,7 +73,6 @@ impl ThemeSelector {
registry,
theme_data: theme_names,
matches,
- picker,
original_theme: original_theme.clone(),
selected_index: 0,
selection_completed: false,
@@ -70,34 +81,7 @@ impl ThemeSelector {
this
}
- fn toggle(
- workspace: &mut Workspace,
- themes: Arc<ThemeRegistry>,
- cx: &mut ViewContext<Workspace>,
- ) {
- workspace.toggle_modal(cx, |_, cx| {
- let this = cx.add_view(|cx| Self::new(themes, cx));
- cx.subscribe(&this, Self::on_event).detach();
- this
- });
- }
-
- #[cfg(debug_assertions)]
- pub fn reload(themes: Arc<ThemeRegistry>, cx: &mut AppContext) {
- let current_theme_name = cx.global::<Settings>().theme.meta.name.clone();
- themes.clear();
- match themes.get(¤t_theme_name) {
- Ok(theme) => {
- Self::set_theme(theme, cx);
- log::info!("reloaded theme {}", current_theme_name);
- }
- Err(error) => {
- log::error!("failed to load theme {}: {:?}", current_theme_name, error)
- }
- }
- }
-
- fn show_selected_theme(&mut self, cx: &mut ViewContext<Self>) {
+ fn show_selected_theme(&mut self, cx: &mut ViewContext<ThemeSelector>) {
if let Some(mat) = self.matches.get(self.selected_index) {
match self.registry.get(&mat.string) {
Ok(theme) => {
@@ -118,19 +102,6 @@ impl ThemeSelector {
.unwrap_or(self.selected_index);
}
- fn on_event(
- workspace: &mut Workspace,
- _: ViewHandle<ThemeSelector>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Dismissed => {
- workspace.dismiss_modal(cx);
- }
- }
- }
-
fn set_theme(theme: Arc<Theme>, cx: &mut AppContext) {
cx.update_global::<Settings, _, _>(|settings, cx| {
settings.theme = theme;
@@ -139,12 +110,16 @@ impl ThemeSelector {
}
}
-impl PickerDelegate for ThemeSelector {
+impl PickerDelegate for ThemeSelectorDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Select Theme...".into()
+ }
+
fn match_count(&self) -> usize {
self.matches.len()
}
- fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<ThemeSelector>) {
self.selection_completed = true;
let theme_name = cx.global::<Settings>().theme.meta.name.clone();
@@ -152,27 +127,30 @@ impl PickerDelegate for ThemeSelector {
settings_content.theme = Some(theme_name);
});
- cx.emit(Event::Dismissed);
+ cx.emit(PickerEvent::Dismiss);
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+ fn dismissed(&mut self, cx: &mut ViewContext<ThemeSelector>) {
if !self.selection_completed {
Self::set_theme(self.original_theme.clone(), cx);
self.selection_completed = true;
}
- cx.emit(Event::Dismissed);
}
fn selected_index(&self) -> usize {
self.selected_index
}
- fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
+ fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<ThemeSelector>) {
self.selected_index = ix;
self.show_selected_theme(cx);
}
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
+ fn update_matches(
+ &mut self,
+ query: String,
+ cx: &mut ViewContext<ThemeSelector>,
+ ) -> gpui::Task<()> {
let background = cx.background().clone();
let candidates = self
.theme_data
@@ -185,7 +163,7 @@ impl PickerDelegate for ThemeSelector {
})
.collect::<Vec<_>>();
- cx.spawn(|this, mut cx| async move {
+ cx.spawn_weak(|this, mut cx| async move {
let matches = if query.is_empty() {
candidates
.into_iter()
@@ -209,14 +187,17 @@ impl PickerDelegate for ThemeSelector {
.await
};
- this.update(&mut cx, |this, cx| {
- this.matches = matches;
- this.selected_index = this
- .selected_index
- .min(this.matches.len().saturating_sub(1));
- this.show_selected_theme(cx);
- cx.notify();
- });
+ if let Some(this) = this.upgrade(&cx) {
+ this.update(&mut cx, |this, cx| {
+ let delegate = this.delegate_mut();
+ delegate.matches = matches;
+ delegate.selected_index = delegate
+ .selected_index
+ .min(delegate.matches.len().saturating_sub(1));
+ delegate.show_selected_theme(cx);
+ })
+ .log_err();
+ }
})
}
@@ -226,7 +207,7 @@ impl PickerDelegate for ThemeSelector {
mouse_state: &mut MouseState,
selected: bool,
cx: &AppContext,
- ) -> ElementBox {
+ ) -> Element<Picker<Self>> {
let settings = cx.global::<Settings>();
let theme = &settings.theme;
let theme_match = &self.matches[ix];
@@ -239,29 +220,3 @@ impl PickerDelegate for ThemeSelector {
.boxed()
}
}
-
-impl Entity for ThemeSelector {
- type Event = Event;
-
- fn release(&mut self, cx: &mut AppContext) {
- if !self.selection_completed {
- Self::set_theme(self.original_theme.clone(), cx);
- }
- }
-}
-
-impl View for ThemeSelector {
- fn ui_name() -> &'static str {
- "ThemeSelector"
- }
-
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
- ChildView::new(&self.picker, cx).boxed()
- }
-
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.is_self_focused() {
- cx.focus(&self.picker);
- }
- }
-}
@@ -2,12 +2,12 @@ use gpui::{
actions,
color::Color,
elements::{
- Canvas, Container, ContainerStyle, ElementBox, Flex, Label, Margin, MouseEventHandler,
+ Canvas, Container, ContainerStyle, Element, Flex, Label, Margin, MouseEventHandler,
Padding, ParentElement,
},
fonts::TextStyle,
- AppContext, Border, Element, Entity, ModelHandle, Quad, RenderContext, Task, View, ViewContext,
- ViewHandle, WeakViewHandle,
+ AppContext, Border, Drawable, Entity, ModelHandle, Quad, Task, View, ViewContext, ViewHandle,
+ WeakViewHandle,
};
use project::Project;
use settings::Settings;
@@ -34,12 +34,12 @@ impl ThemeTestbench {
workspace.add_item(Box::new(view), cx);
}
- fn render_ramps(color_scheme: &ColorScheme) -> Flex {
- fn display_ramp(ramp: &Vec<Color>) -> ElementBox {
+ fn render_ramps(color_scheme: &ColorScheme) -> Flex<Self> {
+ fn display_ramp(ramp: &Vec<Color>) -> Element<ThemeTestbench> {
Flex::row()
.with_children(ramp.iter().cloned().map(|color| {
- Canvas::new(move |bounds, _, cx| {
- cx.scene.push_quad(Quad {
+ Canvas::new(move |scene, bounds, _, _, _| {
+ scene.push_quad(Quad {
bounds,
background: Some(color),
..Default::default()
@@ -67,8 +67,8 @@ impl ThemeTestbench {
fn render_layer(
layer_index: usize,
layer: &Layer,
- cx: &mut RenderContext<'_, Self>,
- ) -> Container {
+ cx: &mut ViewContext<Self>,
+ ) -> Container<Self> {
Flex::column()
.with_child(
Self::render_button_set(0, layer_index, "base", &layer.base, cx)
@@ -123,8 +123,8 @@ impl ThemeTestbench {
layer_index: usize,
set_name: &'static str,
style_set: &StyleSet,
- cx: &mut RenderContext<'_, Self>,
- ) -> Flex {
+ cx: &mut ViewContext<Self>,
+ ) -> Flex<Self> {
Flex::row()
.with_child(Self::render_button(
set_index * 6,
@@ -182,10 +182,10 @@ impl ThemeTestbench {
text: &'static str,
style_set: &StyleSet,
style_override: Option<fn(&StyleSet) -> &Style>,
- cx: &mut RenderContext<'_, Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
enum TestBenchButton {}
- MouseEventHandler::<TestBenchButton>::new(layer_index + button_index, cx, |state, cx| {
+ MouseEventHandler::<TestBenchButton, _>::new(layer_index + button_index, cx, |state, cx| {
let style = if let Some(style_override) = style_override {
style_override(&style_set)
} else if state.clicked().is_some() {
@@ -230,7 +230,7 @@ impl ThemeTestbench {
.boxed()
}
- fn render_label(text: String, style: &Style, cx: &mut RenderContext<'_, Self>) -> Label {
+ fn render_label(text: String, style: &Style, cx: &mut ViewContext<Self>) -> Label {
let settings = cx.global::<Settings>();
let font_cache = cx.font_cache();
let family_id = settings.buffer_font_family;
@@ -262,7 +262,7 @@ impl View for ThemeTestbench {
"ThemeTestbench"
}
- fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
+ fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Element<Self> {
let color_scheme = &cx.global::<Settings>().theme.clone().color_scheme;
Flex::row()
@@ -298,12 +298,12 @@ impl View for ThemeTestbench {
}
impl Item for ThemeTestbench {
- fn tab_content(
+ fn tab_content<T: View>(
&self,
_: Option<usize>,
style: &theme::Tab,
_: &AppContext,
- ) -> gpui::ElementBox {
+ ) -> Element<T> {
Label::new("Theme Testbench", style.label.clone())
.aligned()
.contained()
@@ -104,6 +104,10 @@ pub fn marked_text_ranges_by(
/// ```text
/// one «ˇreversed» selection and one «forwardˇ» selection
/// ```
+///
+/// Any • characters in the input string will be replaced with spaces. This makes
+/// it easier to test cases with trailing spaces, which tend to get trimmed from the
+/// source code.
pub fn marked_text_ranges(
marked_text: &str,
ranges_are_directed: bool,
@@ -114,6 +118,7 @@ pub fn marked_text_ranges(
let mut current_range_start = None;
let mut current_range_cursor = None;
+ let marked_text = marked_text.replace("•", " ");
for (marked_ix, marker) in marked_text.match_indices(&['«', '»', 'ˇ']) {
unmarked_text.push_str(&marked_text[prev_marked_ix..marked_ix]);
let unmarked_len = unmarked_text.len();
@@ -1,8 +1,7 @@
-use editor::{EditorBlurred, EditorFocused, EditorMode, EditorReleased, Event};
+use crate::Vim;
+use editor::{EditorBlurred, EditorFocused, EditorReleased};
use gpui::AppContext;
-use crate::{state::Mode, Vim};
-
pub fn init(cx: &mut AppContext) {
cx.subscribe_global(focused).detach();
cx.subscribe_global(blurred).detach();
@@ -10,69 +9,40 @@ pub fn init(cx: &mut AppContext) {
}
fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
- Vim::update(cx, |vim, cx| {
- if let Some(previously_active_editor) = vim
- .active_editor
- .as_ref()
- .and_then(|editor| editor.upgrade(cx))
- {
- vim.unhook_vim_settings(previously_active_editor, cx);
- }
-
- vim.active_editor = Some(editor.downgrade());
- vim.editor_subscription = Some(cx.subscribe(editor, |editor, event, cx| match event {
- Event::SelectionsChanged { local: true } => {
- let editor = editor.read(cx);
- if editor.leader_replica_id().is_none() {
- let newest_empty = editor.selections.newest::<usize>(cx).is_empty();
- local_selections_changed(newest_empty, cx);
- }
- }
- Event::InputIgnored { text } => {
- Vim::active_editor_input_ignored(text.clone(), cx);
- }
- _ => {}
- }));
-
- if vim.enabled {
- let editor = editor.read(cx);
- let editor_mode = editor.mode();
- let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
-
- if editor_mode == EditorMode::Full && !newest_selection_empty {
- vim.switch_mode(Mode::Visual { line: false }, true, cx);
- }
- }
-
- vim.sync_vim_settings(cx);
+ cx.update_window(editor.window_id(), |cx| {
+ Vim::update(cx, |vim, cx| {
+ vim.update_active_editor(cx, |previously_active_editor, cx| {
+ Vim::unhook_vim_settings(previously_active_editor, cx);
+ });
+ vim.set_active_editor(editor.clone(), cx);
+ });
});
}
fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut AppContext) {
- Vim::update(cx, |vim, cx| {
- if let Some(previous_editor) = vim.active_editor.clone() {
- if previous_editor == editor.clone() {
- vim.active_editor = None;
+ cx.update_window(editor.window_id(), |cx| {
+ Vim::update(cx, |vim, cx| {
+ if let Some(previous_editor) = vim.active_editor.clone() {
+ if previous_editor == editor.clone() {
+ vim.active_editor = None;
+ }
}
- }
- vim.unhook_vim_settings(editor.clone(), cx);
- })
+
+ cx.update_window(editor.window_id(), |cx| {
+ editor.update(cx, |editor, cx| Vim::unhook_vim_settings(editor, cx))
+ });
+ });
+ });
}
fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) {
- cx.update_default_global(|vim: &mut Vim, _| {
- if let Some(previous_editor) = vim.active_editor.clone() {
- if previous_editor == editor.clone() {
- vim.active_editor = None;
+ cx.update_window(editor.window_id(), |cx| {
+ cx.update_default_global(|vim: &mut Vim, _| {
+ if let Some(previous_editor) = vim.active_editor.clone() {
+ if previous_editor == editor.clone() {
+ vim.active_editor = None;
+ }
}
- }
+ });
});
}
-
-fn local_selections_changed(newest_empty: bool, cx: &mut AppContext) {
- Vim::update(cx, |vim, cx| {
- if vim.enabled && vim.state.mode == Mode::Normal && !newest_empty {
- vim.switch_mode(Mode::Visual { line: false }, false, cx)
- }
- })
-}
@@ -5,7 +5,7 @@ use editor::{
display_map::{DisplaySnapshot, ToDisplayPoint},
movement, Bias, CharKind, DisplayPoint, ToOffset,
};
-use gpui::{actions, impl_actions, AppContext};
+use gpui::{actions, impl_actions, AppContext, WindowContext};
use language::{Point, Selection, SelectionGoal};
use serde::Deserialize;
use workspace::Workspace;
@@ -116,7 +116,7 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx))
}
-pub(crate) fn motion(motion: Motion, cx: &mut AppContext) {
+pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
if let Some(Operator::Namespace(_))
| Some(Operator::FindForward { .. })
| Some(Operator::FindBackward { .. }) = Vim::read(cx).active_operator()
@@ -16,7 +16,7 @@ use editor::{
scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
Anchor, Bias, ClipboardSelection, DisplayPoint, Editor,
};
-use gpui::{actions, impl_actions, AppContext, ViewContext};
+use gpui::{actions, impl_actions, AppContext, ViewContext, WindowContext};
use language::{AutoindentMode, Point, SelectionGoal};
use log::error;
use serde::Deserialize;
@@ -94,7 +94,7 @@ pub fn normal_motion(
motion: Motion,
operator: Option<Operator>,
times: usize,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) {
Vim::update(cx, |vim, cx| {
match operator {
@@ -110,7 +110,7 @@ pub fn normal_motion(
});
}
-pub fn normal_object(object: Object, cx: &mut AppContext) {
+pub fn normal_object(object: Object, cx: &mut WindowContext) {
Vim::update(cx, |vim, cx| {
match vim.state.operator_stack.pop() {
Some(Operator::Object { around }) => match vim.state.operator_stack.pop() {
@@ -129,7 +129,7 @@ pub fn normal_object(object: Object, cx: &mut AppContext) {
})
}
-fn move_cursor(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppContext) {
+fn move_cursor(vim: &mut Vim, motion: Motion, times: usize, cx: &mut WindowContext) {
vim.update_active_editor(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_cursors_with(|map, cursor, goal| {
@@ -251,7 +251,7 @@ fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext<Workspace>) {
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
- if let Some(item) = cx.as_mut().read_from_clipboard() {
+ if let Some(item) = cx.read_from_clipboard() {
let mut clipboard_text = Cow::Borrowed(item.text());
if let Some(mut clipboard_selections) =
item.metadata::<Vec<ClipboardSelection>>()
@@ -424,7 +424,7 @@ fn scroll(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContext<Edito
}
}
-pub(crate) fn normal_replace(text: Arc<str>, cx: &mut AppContext) {
+pub(crate) fn normal_replace(text: Arc<str>, cx: &mut WindowContext) {
Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
@@ -570,18 +570,18 @@ mod test {
cx.assert_all(indoc! {"
The ˇquick
-
+
brown fox jumps
overˇ the lazy doˇg"})
.await;
cx.assert(indoc! {"
The quiˇck
-
+
brown"})
.await;
cx.assert(indoc! {"
The quiˇck
-
+
"})
.await;
}
@@ -611,16 +611,16 @@ mod test {
let mut cx = NeovimBackedTestContext::new(cx).await.binding(["e"]);
cx.assert_all(indoc! {"
Thˇe quicˇkˇ-browˇn
-
-
+
+
fox_jumpˇs oveˇr
thˇe"})
.await;
let mut cx = cx.binding(["shift-e"]);
cx.assert_all(indoc! {"
Thˇe quicˇkˇ-browˇn
-
-
+
+
fox_jumpˇs oveˇr
thˇe"})
.await;
@@ -669,7 +669,7 @@ mod test {
["g", "g"],
indoc! {"
The qˇuick
-
+
brown fox jumps
over ˇthe laˇzy dog"},
)
@@ -677,8 +677,8 @@ mod test {
cx.assert_binding_matches(
["g", "g"],
indoc! {"
-
-
+
+
brown fox jumps
over the laˇzy dog"},
)
@@ -687,7 +687,7 @@ mod test {
["2", "g", "g"],
indoc! {"
ˇ
-
+
brown fox jumps
over the lazydog"},
)
@@ -701,7 +701,7 @@ mod test {
["shift-g"],
indoc! {"
The qˇuick
-
+
brown fox jumps
over ˇthe laˇzy dog"},
)
@@ -709,8 +709,8 @@ mod test {
cx.assert_binding_matches(
["shift-g"],
indoc! {"
-
-
+
+
brown fox jumps
over the laˇzy dog"},
)
@@ -719,7 +719,7 @@ mod test {
["2", "shift-g"],
indoc! {"
ˇ
-
+
brown fox jumps
over the lazydog"},
)
@@ -999,7 +999,7 @@ mod test {
let test_case = indoc! {"
ˇaaaˇbˇ ˇbˇ ˇbˇbˇ aˇaaˇbaaa
ˇ ˇbˇaaˇa ˇbˇbˇb
- ˇ
+ ˇ
ˇb
"};
@@ -1017,9 +1017,10 @@ mod test {
let test_case = indoc! {"
ˇaaaˇbˇ ˇbˇ ˇbˇbˇ aˇaaˇbaaa
ˇ ˇbˇaaˇa ˇbˇbˇb
- ˇ
+ ˇ•••
ˇb
- "};
+ "
+ };
for count in 1..=3 {
cx.assert_binding_matches_all([&count.to_string(), "shift-f", "b"], test_case)
@@ -3,10 +3,10 @@ use editor::{
char_kind, display_map::DisplaySnapshot, movement, scroll::autoscroll::Autoscroll, CharKind,
DisplayPoint,
};
-use gpui::AppContext;
+use gpui::WindowContext;
use language::Selection;
-pub fn change_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppContext) {
+pub fn change_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut WindowContext) {
// Some motions ignore failure when switching to normal mode
let mut motion_succeeded = matches!(
motion,
@@ -38,7 +38,7 @@ pub fn change_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppCo
}
}
-pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut AppContext) {
+pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
let mut objects_found = false;
vim.update_active_editor(cx, |editor, cx| {
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
@@ -1,9 +1,9 @@
use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
use collections::{HashMap, HashSet};
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias};
-use gpui::AppContext;
+use gpui::WindowContext;
-pub fn delete_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppContext) {
+pub fn delete_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut WindowContext) {
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
@@ -36,7 +36,7 @@ pub fn delete_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppCo
});
}
-pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut AppContext) {
+pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
@@ -1,8 +1,8 @@
use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
use collections::HashMap;
-use gpui::AppContext;
+use gpui::WindowContext;
-pub fn yank_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppContext) {
+pub fn yank_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut WindowContext) {
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
@@ -25,7 +25,7 @@ pub fn yank_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppCont
});
}
-pub fn yank_object(vim: &mut Vim, object: Object, around: bool, cx: &mut AppContext) {
+pub fn yank_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
@@ -1,7 +1,7 @@
use std::ops::Range;
use editor::{char_kind, display_map::DisplaySnapshot, movement, Bias, CharKind, DisplayPoint};
-use gpui::{actions, impl_actions, AppContext};
+use gpui::{actions, impl_actions, AppContext, WindowContext};
use language::Selection;
use serde::Deserialize;
use workspace::Workspace;
@@ -61,7 +61,7 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| object(Object::AngleBrackets, cx));
}
-fn object(object: Object, cx: &mut AppContext) {
+fn object(object: Object, cx: &mut WindowContext) {
match Vim::read(cx).state.mode {
Mode::Normal => normal_object(object, cx),
Mode::Visual { .. } => visual_object(object, cx),
@@ -434,19 +434,20 @@ mod test {
use crate::test::{ExemptionFeatures, NeovimBackedTestContext};
const WORD_LOCATIONS: &'static str = indoc! {"
- The quick ˇbrowˇnˇ
+ The quick ˇbrowˇnˇ•••
fox ˇjuˇmpsˇ over
- the lazy dogˇ
+ the lazy dogˇ••
ˇ
ˇ
ˇ
- Thˇeˇ-ˇquˇickˇ ˇbrownˇ
- ˇ
- ˇ
+ Thˇeˇ-ˇquˇickˇ ˇbrownˇ•
+ ˇ••
+ ˇ••
ˇ fox-jumpˇs over
- the lazy dogˇ
+ the lazy dogˇ•
ˇ
- "};
+ "
+ };
#[gpui::test]
async fn test_change_word_object(cx: &mut gpui::TestAppContext) {
@@ -527,7 +528,7 @@ mod test {
const SENTENCE_EXAMPLES: &[&'static str] = &[
"ˇThe quick ˇbrownˇ?ˇ ˇFox Jˇumpsˇ!ˇ Ovˇer theˇ lazyˇ.",
indoc! {"
- ˇThe quick ˇbrownˇ
+ ˇThe quick ˇbrownˇ
fox jumps over
the lazy doˇgˇ.ˇ ˇThe quick ˇ
brown fox jumps over
@@ -16,26 +16,20 @@ pub struct VimTestContext<'a> {
impl<'a> VimTestContext<'a> {
pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
- cx.update(|cx| {
- search::init(cx);
- crate::init(cx);
-
- settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
- });
-
let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
-
cx.update(|cx| {
cx.update_global(|settings: &mut Settings, _| {
settings.vim_mode = enabled;
});
- });
+ search::init(cx);
+ crate::init(cx);
- let window_id = cx.window_id;
+ settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
+ });
// Setup search toolbars and keypress hook
cx.update_workspace(|workspace, cx| {
- observe_keystrokes(window_id, cx);
+ observe_keystrokes(cx);
workspace.active_pane().update(cx, |pane, cx| {
pane.toolbar().update(cx, |toolbar, cx| {
let buffer_search_bar = cx.add_view(BufferSearchBar::new);
@@ -82,7 +76,8 @@ impl<'a> VimTestContext<'a> {
}
pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
- self.cx.update(|cx| {
+ let window_id = self.window_id;
+ self.update_window(window_id, |cx| {
Vim::update(cx, |vim, cx| {
vim.switch_mode(mode, false, cx);
})
@@ -13,9 +13,10 @@ mod visual;
use std::sync::Arc;
use collections::CommandPaletteFilter;
-use editor::{Bias, Cancel, Editor, EditorMode};
+use editor::{Bias, Cancel, Editor, EditorMode, Event};
use gpui::{
actions, impl_actions, AppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle,
+ WindowContext,
};
use language::CursorShape;
use motion::Motion;
@@ -65,7 +66,7 @@ pub fn init(cx: &mut AppContext) {
// Otherwise forward cancel on to the editor
let vim = Vim::read(cx);
if vim.state.mode != Mode::Normal || vim.active_operator().is_some() {
- AppContext::defer(cx, |cx| {
+ WindowContext::defer(cx, |cx| {
Vim::update(cx, |state, cx| {
state.switch_mode(Mode::Normal, false, cx);
});
@@ -83,20 +84,20 @@ pub fn init(cx: &mut AppContext) {
Vim::active_editor_input_ignored("\n".into(), cx)
});
- // Sync initial settings with the rest of the app
- Vim::update(cx, |vim, cx| vim.sync_vim_settings(cx));
-
- // Any time settings change, update vim mode to match
+ // Any time settings change, update vim mode to match.
+ cx.update_default_global(|vim: &mut Vim, cx: &mut AppContext| {
+ vim.set_enabled(cx.global::<Settings>().vim_mode, cx)
+ });
cx.observe_global::<Settings, _>(|cx| {
- Vim::update(cx, |state, cx| {
- state.set_enabled(cx.global::<Settings>().vim_mode, cx)
- })
+ cx.update_default_global(|vim: &mut Vim, cx: &mut AppContext| {
+ vim.set_enabled(cx.global::<Settings>().vim_mode, cx)
+ });
})
.detach();
}
-pub fn observe_keystrokes(window_id: usize, cx: &mut AppContext) {
- cx.observe_keystrokes(window_id, |_keystroke, _result, handled_by, cx| {
+pub fn observe_keystrokes(cx: &mut WindowContext) {
+ cx.observe_keystrokes(|_keystroke, _result, handled_by, cx| {
if let Some(handled_by) = handled_by {
// Keystroke is handled by the vim system, so continue forward
// Also short circuit if it is the special cancel action
@@ -135,25 +136,52 @@ impl Vim {
cx.default_global()
}
- fn update<F, S>(cx: &mut AppContext, update: F) -> S
+ fn update<F, S>(cx: &mut WindowContext, update: F) -> S
where
- F: FnOnce(&mut Self, &mut AppContext) -> S,
+ F: FnOnce(&mut Self, &mut WindowContext) -> S,
{
cx.update_default_global(update)
}
+ fn set_active_editor(&mut self, editor: ViewHandle<Editor>, cx: &mut WindowContext) {
+ self.active_editor = Some(editor.downgrade());
+ self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
+ Event::SelectionsChanged { local: true } => {
+ let editor = editor.read(cx);
+ if editor.leader_replica_id().is_none() {
+ let newest_empty = editor.selections.newest::<usize>(cx).is_empty();
+ local_selections_changed(newest_empty, cx);
+ }
+ }
+ Event::InputIgnored { text } => {
+ Vim::active_editor_input_ignored(text.clone(), cx);
+ }
+ _ => {}
+ }));
+
+ if self.enabled {
+ let editor = editor.read(cx);
+ let editor_mode = editor.mode();
+ let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
+
+ if editor_mode == EditorMode::Full && !newest_selection_empty {
+ self.switch_mode(Mode::Visual { line: false }, true, cx);
+ }
+ }
+
+ self.sync_vim_settings(cx);
+ }
+
fn update_active_editor<S>(
&self,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
update: impl FnOnce(&mut Editor, &mut ViewContext<Editor>) -> S,
) -> Option<S> {
- self.active_editor
- .clone()
- .and_then(|ae| ae.upgrade(cx))
- .map(|ae| ae.update(cx, update))
+ let editor = self.active_editor.clone()?.upgrade(cx)?;
+ Some(editor.update(cx, update))
}
- fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut AppContext) {
+ fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
self.state.mode = mode;
self.state.operator_stack.clear();
@@ -165,35 +193,27 @@ impl Vim {
}
// Adjust selections
- if let Some(editor) = self
- .active_editor
- .as_ref()
- .and_then(|editor| editor.upgrade(cx))
- {
- editor.update(cx, |editor, cx| {
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- if self.state.empty_selections_only() {
- let new_head = map.clip_point(selection.head(), Bias::Left);
- selection.collapse_to(new_head, selection.goal)
- } else {
- selection.set_head(
- map.clip_point(selection.head(), Bias::Left),
- selection.goal,
- );
- }
- });
- })
+ self.update_active_editor(cx, |editor, cx| {
+ editor.change_selections(None, cx, |s| {
+ s.move_with(|map, selection| {
+ if self.state.empty_selections_only() {
+ let new_head = map.clip_point(selection.head(), Bias::Left);
+ selection.collapse_to(new_head, selection.goal)
+ } else {
+ selection
+ .set_head(map.clip_point(selection.head(), Bias::Left), selection.goal);
+ }
+ });
})
- }
+ });
}
- fn push_operator(&mut self, operator: Operator, cx: &mut AppContext) {
+ fn push_operator(&mut self, operator: Operator, cx: &mut WindowContext) {
self.state.operator_stack.push(operator);
self.sync_vim_settings(cx);
}
- fn push_number(&mut self, Number(number): &Number, cx: &mut AppContext) {
+ fn push_number(&mut self, Number(number): &Number, cx: &mut WindowContext) {
if let Some(Operator::Number(current_number)) = self.active_operator() {
self.pop_operator(cx);
self.push_operator(Operator::Number(current_number * 10 + *number as usize), cx);
@@ -202,14 +222,14 @@ impl Vim {
}
}
- fn pop_operator(&mut self, cx: &mut AppContext) -> Operator {
+ fn pop_operator(&mut self, cx: &mut WindowContext) -> Operator {
let popped_operator = self.state.operator_stack.pop()
.expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
self.sync_vim_settings(cx);
popped_operator
}
- fn pop_number_operator(&mut self, cx: &mut AppContext) -> usize {
+ fn pop_number_operator(&mut self, cx: &mut WindowContext) -> usize {
let mut times = 1;
if let Some(Operator::Number(number)) = self.active_operator() {
times = number;
@@ -218,7 +238,7 @@ impl Vim {
times
}
- fn clear_operator(&mut self, cx: &mut AppContext) {
+ fn clear_operator(&mut self, cx: &mut WindowContext) {
self.state.operator_stack.clear();
self.sync_vim_settings(cx);
}
@@ -227,7 +247,7 @@ impl Vim {
self.state.operator_stack.last().copied()
}
- fn active_editor_input_ignored(text: Arc<str>, cx: &mut AppContext) {
+ fn active_editor_input_ignored(text: Arc<str>, cx: &mut WindowContext) {
if text.is_empty() {
return;
}
@@ -252,52 +272,63 @@ impl Vim {
if self.enabled != enabled {
self.enabled = enabled;
self.state = Default::default();
- if enabled {
- self.switch_mode(Mode::Normal, false, cx);
- }
- self.sync_vim_settings(cx);
+
+ cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
+ if self.enabled {
+ filter.filtered_namespaces.remove("vim");
+ } else {
+ filter.filtered_namespaces.insert("vim");
+ }
+ });
+
+ cx.update_active_window(|cx| {
+ if self.enabled {
+ let active_editor = cx
+ .root_view()
+ .downcast_ref::<Workspace>()
+ .and_then(|workspace| workspace.read(cx).active_item(cx))
+ .and_then(|item| item.downcast::<Editor>());
+ if let Some(active_editor) = active_editor {
+ self.set_active_editor(active_editor, cx);
+ }
+ self.switch_mode(Mode::Normal, false, cx);
+ }
+ self.sync_vim_settings(cx);
+ });
}
}
- fn sync_vim_settings(&self, cx: &mut AppContext) {
+ fn sync_vim_settings(&self, cx: &mut WindowContext) {
let state = &self.state;
let cursor_shape = state.cursor_shape();
- cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
- if self.enabled {
- filter.filtered_namespaces.remove("vim");
+ self.update_active_editor(cx, |editor, cx| {
+ if self.enabled && editor.mode() == EditorMode::Full {
+ editor.set_cursor_shape(cursor_shape, cx);
+ editor.set_clip_at_line_ends(state.clip_at_line_end(), cx);
+ editor.set_input_enabled(!state.vim_controlled());
+ editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true });
+ let context_layer = state.keymap_context_layer();
+ editor.set_keymap_context_layer::<Self>(context_layer);
} else {
- filter.filtered_namespaces.insert("vim");
+ Self::unhook_vim_settings(editor, cx);
}
});
-
- if let Some(editor) = self
- .active_editor
- .as_ref()
- .and_then(|editor| editor.upgrade(cx))
- {
- if self.enabled && editor.read(cx).mode() == EditorMode::Full {
- editor.update(cx, |editor, cx| {
- editor.set_cursor_shape(cursor_shape, cx);
- editor.set_clip_at_line_ends(state.clip_at_line_end(), cx);
- editor.set_input_enabled(!state.vim_controlled());
- editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true });
- let context_layer = state.keymap_context_layer();
- editor.set_keymap_context_layer::<Self>(context_layer);
- });
- } else {
- self.unhook_vim_settings(editor, cx);
- }
- }
}
- fn unhook_vim_settings(&self, editor: ViewHandle<Editor>, cx: &mut AppContext) {
- editor.update(cx, |editor, cx| {
- editor.set_cursor_shape(CursorShape::Bar, cx);
- editor.set_clip_at_line_ends(false, cx);
- editor.set_input_enabled(true);
- editor.selections.line_mode = false;
- editor.remove_keymap_context_layer::<Self>();
- });
+ fn unhook_vim_settings(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
+ editor.set_cursor_shape(CursorShape::Bar, cx);
+ editor.set_clip_at_line_ends(false, cx);
+ editor.set_input_enabled(true);
+ editor.selections.line_mode = false;
+ editor.remove_keymap_context_layer::<Self>();
}
}
+
+fn local_selections_changed(newest_empty: bool, cx: &mut WindowContext) {
+ Vim::update(cx, |vim, cx| {
+ if vim.enabled && vim.state.mode == Mode::Normal && !newest_empty {
+ vim.switch_mode(Mode::Visual { line: false }, false, cx)
+ }
+ })
+}
@@ -4,7 +4,7 @@ use collections::HashMap;
use editor::{
display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, Bias, ClipboardSelection,
};
-use gpui::{actions, AppContext, ViewContext};
+use gpui::{actions, AppContext, ViewContext, WindowContext};
use language::{AutoindentMode, SelectionGoal};
use workspace::Workspace;
@@ -25,7 +25,7 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(paste);
}
-pub fn visual_motion(motion: Motion, times: usize, cx: &mut AppContext) {
+pub fn visual_motion(motion: Motion, times: usize, cx: &mut WindowContext) {
Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
@@ -56,7 +56,7 @@ pub fn visual_motion(motion: Motion, times: usize, cx: &mut AppContext) {
});
}
-pub fn visual_object(object: Object, cx: &mut AppContext) {
+pub fn visual_object(object: Object, cx: &mut WindowContext) {
Vim::update(cx, |vim, cx| {
if let Operator::Object { around } = vim.pop_operator(cx) {
vim.update_active_editor(cx, |editor, cx| {
@@ -209,7 +209,7 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext<Workspace>
Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
- if let Some(item) = cx.as_mut().read_from_clipboard() {
+ if let Some(item) = cx.read_from_clipboard() {
copy_selections_content(editor, editor.selections.line_mode, cx);
let mut clipboard_text = Cow::Borrowed(item.text());
if let Some(mut clipboard_selections) =
@@ -313,7 +313,7 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext<Workspace>
});
}
-pub(crate) fn visual_replace(text: Arc<str>, line: bool, cx: &mut AppContext) {
+pub(crate) fn visual_replace(text: Arc<str>, line: bool, cx: &mut WindowContext) {
Vim::update(cx, |vim, cx| {
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
@@ -53,46 +53,46 @@
{"Key":"i"}
{"Key":"s"}
{"Get":{"state":"The quick brown? Fox Jumps! ˇ","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"i"}
{"Key":"s"}
{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"i"}
{"Key":"s"}
{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"i"}
{"Key":"s"}
{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"i"}
{"Key":"s"}
{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"i"}
{"Key":"s"}
{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"i"}
{"Key":"s"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog.ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"i"}
{"Key":"s"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog. ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"i"}
{"Key":"s"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog. ˇ\n","mode":"Insert"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇ\n","mode":"Insert"}}
{"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
{"Key":"c"}
{"Key":"i"}
@@ -188,46 +188,46 @@
{"Key":"a"}
{"Key":"s"}
{"Get":{"state":"The quick brown? Fox Jumps!ˇ","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"a"}
{"Key":"s"}
{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"a"}
{"Key":"s"}
{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"a"}
{"Key":"s"}
{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"a"}
{"Key":"s"}
{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"a"}
{"Key":"s"}
{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"a"}
{"Key":"s"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"a"}
{"Key":"s"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
{"Key":"c"}
{"Key":"a"}
{"Key":"s"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
{"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
{"Key":"c"}
{"Key":"a"}
@@ -53,46 +53,46 @@
{"Key":"i"}
{"Key":"s"}
{"Get":{"state":"The quick brown? Fox Jumps!ˇ ","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"i"}
{"Key":"s"}
{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"i"}
{"Key":"s"}
{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"i"}
{"Key":"s"}
{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"i"}
{"Key":"s"}
{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"i"}
{"Key":"s"}
{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"i"}
{"Key":"s"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog.ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"i"}
{"Key":"s"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog.ˇ \n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ \n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"i"}
{"Key":"s"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog.ˇ \n","mode":"Normal"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ \n","mode":"Normal"}}
{"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
{"Key":"d"}
{"Key":"i"}
@@ -188,46 +188,46 @@
{"Key":"a"}
{"Key":"s"}
{"Get":{"state":"The quick brown? Fox Jumpsˇ!","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"a"}
{"Key":"s"}
{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"a"}
{"Key":"s"}
{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"a"}
{"Key":"s"}
{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"a"}
{"Key":"s"}
{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"a"}
{"Key":"s"}
{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"a"}
{"Key":"s"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"a"}
{"Key":"s"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
{"Key":"d"}
{"Key":"a"}
{"Key":"s"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
{"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
{"Key":"d"}
{"Key":"a"}
@@ -1,557 +1,557 @@
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n\nb\n","mode":"Normal"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n"}}
{"Key":"1"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaˇab b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaˇab b bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇ baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n ˇ baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaaˇ bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaaˇ bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaaˇ bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaaˇ bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaaˇ bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaaˇ bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n"}}
{"Key":"1"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n"}}
{"Key":"2"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaaˇ bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaaˇ bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n"}}
{"Key":"2"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n"}}
{"Key":"3"}
{"Key":"f"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n"}}
{"Key":"3"}
{"Key":"t"}
{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n","mode":"Normal"}}
@@ -1,91 +1,59 @@
+use std::sync::Arc;
+
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
actions,
- elements::{ChildView, Element as _, Label},
- AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle,
+ elements::{Drawable as _, Label},
+ AppContext, Task, ViewContext,
};
-use picker::{Picker, PickerDelegate};
+use picker::{Picker, PickerDelegate, PickerEvent};
use settings::{settings_file::SettingsFile, BaseKeymap, Settings};
+use util::ResultExt;
use workspace::Workspace;
-pub struct BaseKeymapSelector {
- matches: Vec<StringMatch>,
- picker: ViewHandle<Picker<Self>>,
- selected_index: usize,
-}
-
actions!(welcome, [ToggleBaseKeymapSelector]);
pub fn init(cx: &mut AppContext) {
- Picker::<BaseKeymapSelector>::init(cx);
- cx.add_action({
- move |workspace, _: &ToggleBaseKeymapSelector, cx| BaseKeymapSelector::toggle(workspace, cx)
- });
+ cx.add_action(toggle);
+ BaseKeymapSelector::init(cx);
}
-pub enum Event {
- Dismissed,
+fn toggle(
+ workspace: &mut Workspace,
+ _: &ToggleBaseKeymapSelector,
+ cx: &mut ViewContext<Workspace>,
+) {
+ workspace.toggle_modal(cx, |_, cx| {
+ cx.add_view(|cx| BaseKeymapSelector::new(BaseKeymapSelectorDelegate::new(cx), cx))
+ });
}
-impl BaseKeymapSelector {
- fn toggle(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
- workspace.toggle_modal(cx, |_, cx| {
- let this = cx.add_view(|cx| Self::new(cx));
- cx.subscribe(&this, Self::on_event).detach();
- this
- });
- }
+pub type BaseKeymapSelector = Picker<BaseKeymapSelectorDelegate>;
- fn new(cx: &mut ViewContext<Self>) -> Self {
+pub struct BaseKeymapSelectorDelegate {
+ matches: Vec<StringMatch>,
+ selected_index: usize,
+}
+
+impl BaseKeymapSelectorDelegate {
+ fn new(cx: &mut ViewContext<BaseKeymapSelector>) -> Self {
let base = cx.global::<Settings>().base_keymap;
let selected_index = BaseKeymap::OPTIONS
.iter()
.position(|(_, value)| *value == base)
.unwrap_or(0);
-
- let this = cx.weak_handle();
Self {
- picker: cx.add_view(|cx| Picker::new("Select a base keymap", this, cx)),
matches: Vec::new(),
selected_index,
}
}
-
- fn on_event(
- workspace: &mut Workspace,
- _: ViewHandle<BaseKeymapSelector>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Dismissed => {
- workspace.dismiss_modal(cx);
- }
- }
- }
}
-impl Entity for BaseKeymapSelector {
- type Event = Event;
-}
-
-impl View for BaseKeymapSelector {
- fn ui_name() -> &'static str {
- "BaseKeymapSelector"
- }
-
- fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
- ChildView::new(&self.picker, cx).boxed()
+impl PickerDelegate for BaseKeymapSelectorDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Select a base keymap...".into()
}
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.is_self_focused() {
- cx.focus(&self.picker);
- }
- }
-}
-
-impl PickerDelegate for BaseKeymapSelector {
fn match_count(&self) -> usize {
self.matches.len()
}
@@ -94,11 +62,15 @@ impl PickerDelegate for BaseKeymapSelector {
self.selected_index
}
- fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
+ fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<BaseKeymapSelector>) {
self.selected_index = ix;
}
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
+ fn update_matches(
+ &mut self,
+ query: String,
+ cx: &mut ViewContext<BaseKeymapSelector>,
+ ) -> Task<()> {
let background = cx.background().clone();
let candidates = BaseKeymap::names()
.enumerate()
@@ -109,7 +81,7 @@ impl PickerDelegate for BaseKeymapSelector {
})
.collect::<Vec<_>>();
- cx.spawn(|this, mut cx| async move {
+ cx.spawn_weak(|this, mut cx| async move {
let matches = if query.is_empty() {
candidates
.into_iter()
@@ -133,27 +105,28 @@ impl PickerDelegate for BaseKeymapSelector {
.await
};
- this.update(&mut cx, |this, cx| {
- this.matches = matches;
- this.selected_index = this
- .selected_index
- .min(this.matches.len().saturating_sub(1));
- cx.notify();
- });
+ if let Some(this) = this.upgrade(&cx) {
+ this.update(&mut cx, |this, _| {
+ let delegate = this.delegate_mut();
+ delegate.matches = matches;
+ delegate.selected_index = delegate
+ .selected_index
+ .min(delegate.matches.len().saturating_sub(1));
+ })
+ .log_err();
+ }
})
}
- fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<BaseKeymapSelector>) {
if let Some(selection) = self.matches.get(self.selected_index) {
let base_keymap = BaseKeymap::from_names(&selection.string);
SettingsFile::update(cx, move |settings| settings.base_keymap = Some(base_keymap));
}
- cx.emit(Event::Dismissed);
+ cx.emit(PickerEvent::Dismiss);
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
- cx.emit(Event::Dismissed)
- }
+ fn dismissed(&mut self, _cx: &mut ViewContext<BaseKeymapSelector>) {}
fn render_match(
&self,
@@ -161,7 +134,7 @@ impl PickerDelegate for BaseKeymapSelector {
mouse_state: &mut gpui::MouseState,
selected: bool,
cx: &gpui::AppContext,
- ) -> gpui::ElementBox {
+ ) -> gpui::Element<Picker<Self>> {
let theme = &cx.global::<Settings>().theme;
let keymap_match = &self.matches[ix];
let style = theme.picker.item.style_for(mouse_state, selected);
@@ -5,7 +5,7 @@ use std::{borrow::Cow, sync::Arc};
use db::kvp::KEY_VALUE_STORE;
use gpui::{
elements::{Flex, Label, ParentElement},
- AppContext, Element, ElementBox, Entity, Subscription, View, ViewContext,
+ AppContext, Drawable, Element, Entity, Subscription, View, ViewContext,
};
use settings::{settings_file::SettingsFile, Settings};
@@ -55,7 +55,7 @@ impl View for WelcomePage {
"WelcomePage"
}
- fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Element<Self> {
let self_handle = cx.handle();
let settings = cx.global::<Settings>();
let theme = settings.theme.clone();
@@ -202,12 +202,12 @@ impl Item for WelcomePage {
Some("Welcome to Zed!".into())
}
- fn tab_content(
+ fn tab_content<T: View>(
&self,
_detail: Option<usize>,
style: &theme::Tab,
_cx: &gpui::AppContext,
- ) -> gpui::ElementBox {
+ ) -> Element<T> {
Flex::row()
.with_child(
Label::new("Welcome to Zed!", style.label.clone())
@@ -9,8 +9,7 @@ use gpui::{
geometry::vector::Vector2F,
impl_internal_actions,
platform::{CursorStyle, MouseButton},
- AppContext, Border, Element, ElementBox, RenderContext, SizeConstraint, ViewContext,
- ViewHandle,
+ AppContext, Border, Drawable, Element, SizeConstraint, ViewContext, ViewHandle,
};
use settings::{DockAnchor, Settings};
use theme::Theme;
@@ -315,8 +314,8 @@ impl Dock {
&self,
theme: &Theme,
anchor: DockAnchor,
- cx: &mut RenderContext<Workspace>,
- ) -> Option<ElementBox> {
+ cx: &mut ViewContext<Workspace>,
+ ) -> Option<Element<Workspace>> {
let style = &theme.workspace.dock;
self.position
@@ -351,7 +350,7 @@ impl Dock {
let resizable = Container::new(ChildView::new(&self.pane, cx).boxed())
.with_style(panel_style)
- .with_resize_handle::<DockResizeHandle, _>(
+ .with_resize_handle::<DockResizeHandle>(
resize_side as usize,
resize_side,
4.,
@@ -363,32 +362,27 @@ impl Dock {
);
let size = resizable.current_size();
- let workspace = cx.handle();
- cx.defer(move |cx| {
- if let Some(workspace) = workspace.upgrade(cx) {
- workspace.update(cx, |workspace, _| {
- workspace.dock.panel_sizes.insert(anchor, size);
- })
- }
+ cx.defer(move |workspace, _| {
+ workspace.dock.panel_sizes.insert(anchor, size);
});
if anchor == DockAnchor::Right {
resizable
.constrained()
- .dynamically(|constraint, cx| {
+ .dynamically(|constraint, _, cx| {
SizeConstraint::new(
Vector2F::new(20., constraint.min.y()),
- Vector2F::new(cx.window_size.x() * 0.8, constraint.max.y()),
+ Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
)
})
.boxed()
} else {
resizable
.constrained()
- .dynamically(|constraint, cx| {
+ .dynamically(|constraint, _, cx| {
SizeConstraint::new(
Vector2F::new(constraint.min.x(), 50.),
- Vector2F::new(constraint.max.x(), cx.window_size.y() * 0.8),
+ Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
)
})
.boxed()
@@ -400,21 +394,21 @@ impl Dock {
Stack::new()
.with_child(
// Render wash under the dock which when clicked hides it
- MouseEventHandler::<ExpandedDockWash>::new(0, cx, |_, _| {
+ MouseEventHandler::<ExpandedDockWash, _>::new(0, cx, |_, _| {
Empty::new()
.contained()
.with_background_color(style.wash_color)
.boxed()
})
.capture_all()
- .on_down(MouseButton::Left, |_, cx| {
+ .on_down(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(HideDock);
})
.with_cursor_style(CursorStyle::Arrow)
.boxed(),
)
.with_child(
- MouseEventHandler::<ExpandedDockPane>::new(0, cx, |_state, cx| {
+ MouseEventHandler::<ExpandedDockPane, _>::new(0, cx, |_state, cx| {
ChildView::new(&self.pane, cx).boxed()
})
// Make sure all events directly under the dock pane
@@ -824,6 +818,8 @@ mod tests {
}
impl<'a> UpdateView for DockTestContext<'a> {
+ type Output<S> = S;
+
fn update_view<T, S>(
&mut self,
handle: &ViewHandle<T>,
@@ -2,7 +2,7 @@ use gpui::{
elements::{Empty, MouseEventHandler, Svg},
platform::CursorStyle,
platform::MouseButton,
- Element, ElementBox, Entity, View, ViewContext, ViewHandle, WeakViewHandle,
+ Drawable, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle,
};
use settings::Settings;
@@ -34,7 +34,7 @@ impl View for ToggleDockButton {
"Dock Toggle"
}
- fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Element<Self> {
let workspace = self.workspace.upgrade(cx);
if workspace.is_none() {
@@ -43,11 +43,11 @@ impl View for ToggleDockButton {
let workspace = workspace.unwrap();
let dock_position = workspace.read(cx).dock.position;
- let dock_pane = workspace.read(cx.app).dock_pane().clone();
+ let dock_pane = workspace.read(cx).dock_pane().clone();
let theme = cx.global::<Settings>().theme.clone();
- let button = MouseEventHandler::<Self>::new(0, cx, {
+ let button = MouseEventHandler::<Self, _>::new(0, cx, {
let theme = theme.clone();
move |state, _| {
let style = theme
@@ -68,17 +68,17 @@ impl View for ToggleDockButton {
}
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_up(MouseButton::Left, move |event, cx| {
- let drop_index = dock_pane.read(cx.app).items_len() + 1;
+ .on_up(MouseButton::Left, move |event, _, cx| {
+ let drop_index = dock_pane.read(cx).items_len() + 1;
handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
});
if dock_position.is_visible() {
button
- .on_click(MouseButton::Left, |_, cx| {
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(HideDock);
})
- .with_tooltip::<Self, _>(
+ .with_tooltip::<Self>(
0,
"Hide Dock".into(),
Some(Box::new(HideDock)),
@@ -87,10 +87,10 @@ impl View for ToggleDockButton {
)
} else {
button
- .on_click(MouseButton::Left, |_, cx| {
+ .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(FocusDock);
})
- .with_tooltip::<Self, _>(
+ .with_tooltip::<Self>(
0,
"Focus Dock".into(),
Some(Box::new(FocusDock)),
@@ -1,8 +1,23 @@
+use crate::{
+ pane, persistence::model::ItemId, searchable::SearchableItemHandle, DelayedDebouncedEditAction,
+ FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace,
+ WorkspaceId,
+};
+use anyhow::{anyhow, Result};
+use client::{proto, Client};
+use gpui::{
+ fonts::HighlightStyle, AnyViewHandle, AppContext, Element, ModelHandle, Task, View,
+ ViewContext, ViewHandle, WeakViewHandle, WindowContext,
+};
+use project::{Project, ProjectEntryId, ProjectPath};
+use settings::{Autosave, Settings};
+use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
borrow::Cow,
cell::RefCell,
fmt,
+ ops::Range,
path::PathBuf,
rc::Rc,
sync::{
@@ -11,24 +26,7 @@ use std::{
},
time::Duration,
};
-
-use anyhow::Result;
-use client::{proto, Client};
-use gpui::{
- AnyViewHandle, AppContext, ElementBox, ModelHandle, Task, View, ViewContext, ViewHandle,
- WeakViewHandle,
-};
-use project::{Project, ProjectEntryId, ProjectPath};
-use settings::{Autosave, Settings};
-use smallvec::SmallVec;
use theme::Theme;
-use util::ResultExt;
-
-use crate::{
- pane, persistence::model::ItemId, searchable::SearchableItemHandle, DelayedDebouncedEditAction,
- FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace,
- WorkspaceId,
-};
#[derive(Eq, PartialEq, Hash)]
pub enum ItemEvent {
@@ -38,6 +36,12 @@ pub enum ItemEvent {
Edit,
}
+// TODO: Combine this with existing HighlightedText struct?
+pub struct BreadcrumbText {
+ pub text: String,
+ pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
+}
+
pub trait Item: View {
fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
@@ -50,8 +54,12 @@ pub trait Item: View {
fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<str>> {
None
}
- fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
- -> ElementBox;
+ fn tab_content<V: View>(
+ &self,
+ detail: Option<usize>,
+ style: &theme::Tab,
+ cx: &AppContext,
+ ) -> Element<V>;
fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {}
fn is_singleton(&self, _cx: &AppContext) -> bool {
false
@@ -133,7 +141,7 @@ pub trait Item: View {
ToolbarItemLocation::Hidden
}
- fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<ElementBox>> {
+ fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
None
}
@@ -162,13 +170,23 @@ pub trait Item: View {
pub trait ItemHandle: 'static + fmt::Debug {
fn subscribe_to_item_events(
&self,
- cx: &mut AppContext,
- handler: Box<dyn Fn(ItemEvent, &mut AppContext)>,
+ cx: &mut WindowContext,
+ handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
) -> gpui::Subscription;
fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option<Cow<'a, str>>;
fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
- fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
- -> ElementBox;
+ fn tab_content(
+ &self,
+ detail: Option<usize>,
+ style: &theme::Tab,
+ cx: &AppContext,
+ ) -> Element<Pane>;
+ fn dragged_tab_content(
+ &self,
+ detail: Option<usize>,
+ style: &theme::Tab,
+ cx: &AppContext,
+ ) -> Element<Workspace>;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>;
@@ -178,7 +196,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
fn clone_on_split(
&self,
workspace_id: WorkspaceId,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Option<Box<dyn ItemHandle>>;
fn added_to_pane(
&self,
@@ -186,27 +204,27 @@ pub trait ItemHandle: 'static + fmt::Debug {
pane: ViewHandle<Pane>,
cx: &mut ViewContext<Workspace>,
);
- fn deactivated(&self, cx: &mut AppContext);
- fn workspace_deactivated(&self, cx: &mut AppContext);
- fn navigate(&self, data: Box<dyn Any>, cx: &mut AppContext) -> bool;
+ fn deactivated(&self, cx: &mut WindowContext);
+ fn workspace_deactivated(&self, cx: &mut WindowContext);
+ fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
fn id(&self) -> usize;
fn window_id(&self) -> usize;
fn as_any(&self) -> &AnyViewHandle;
fn is_dirty(&self, cx: &AppContext) -> bool;
fn has_conflict(&self, cx: &AppContext) -> bool;
fn can_save(&self, cx: &AppContext) -> bool;
- fn save(&self, project: ModelHandle<Project>, cx: &mut AppContext) -> Task<Result<()>>;
+ fn save(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
fn save_as(
&self,
project: ModelHandle<Project>,
abs_path: PathBuf,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Task<Result<()>>;
- fn reload(&self, project: ModelHandle<Project>, cx: &mut AppContext) -> Task<Result<()>>;
+ fn reload(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
fn git_diff_recalc(
&self,
project: ModelHandle<Project>,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Task<Result<()>>;
fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>;
fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
@@ -217,7 +235,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
) -> gpui::Subscription;
fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
- fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
+ fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
fn serialized_item_kind(&self) -> Option<&'static str>;
fn show_toolbar(&self, cx: &AppContext) -> bool;
}
@@ -242,8 +260,8 @@ impl dyn ItemHandle {
impl<T: Item> ItemHandle for ViewHandle<T> {
fn subscribe_to_item_events(
&self,
- cx: &mut AppContext,
- handler: Box<dyn Fn(ItemEvent, &mut AppContext)>,
+ cx: &mut WindowContext,
+ handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
) -> gpui::Subscription {
cx.subscribe(self, move |_, event, cx| {
for item_event in T::to_item_events(event) {
@@ -265,7 +283,16 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
detail: Option<usize>,
style: &theme::Tab,
cx: &AppContext,
- ) -> ElementBox {
+ ) -> Element<Pane> {
+ self.read(cx).tab_content(detail, style, cx)
+ }
+
+ fn dragged_tab_content(
+ &self,
+ detail: Option<usize>,
+ style: &theme::Tab,
+ cx: &AppContext,
+ ) -> Element<Workspace> {
self.read(cx).tab_content(detail, style, cx)
}
@@ -313,7 +340,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
fn clone_on_split(
&self,
workspace_id: WorkspaceId,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Option<Box<dyn ItemHandle>> {
self.update(cx, |item, cx| {
cx.add_option_view(|cx| item.clone_on_split(workspace_id, cx))
@@ -428,16 +455,9 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
{
let delay = Duration::from_millis(milliseconds);
let item = item.clone();
- pending_autosave.fire_new(
- delay,
- workspace,
- cx,
- |project, mut cx| async move {
- cx.update(|cx| Pane::autosave_item(&item, project, cx))
- .await
- .log_err();
- },
- );
+ pending_autosave.fire_new(delay, cx, move |workspace, cx| {
+ Pane::autosave_item(&item, workspace.project().clone(), cx)
+ });
}
let settings = cx.global::<Settings>();
@@ -453,24 +473,26 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
pending_git_update.fire_new(
duration,
- workspace,
cx,
- |project, mut cx| async move {
- cx.update(|cx| item.git_diff_recalc(project, cx))
- .await
- .log_err();
+ move |workspace, cx| {
+ item.git_diff_recalc(workspace.project().clone(), cx)
},
);
} else {
- let project = workspace.project().downgrade();
- cx.spawn_weak(|_, mut cx| async move {
- if let Some(project) = project.upgrade(&cx) {
- cx.update(|cx| item.git_diff_recalc(project, cx))
- .await
- .log_err();
- }
+ cx.spawn_weak(|workspace, mut cx| async move {
+ workspace
+ .upgrade(&cx)
+ .ok_or_else(|| anyhow!("workspace was dropped"))?
+ .update(&mut cx, |workspace, cx| {
+ item.git_diff_recalc(
+ workspace.project().clone(),
+ cx,
+ )
+ })?
+ .await?;
+ anyhow::Ok(())
})
- .detach();
+ .detach_and_log_err(cx);
}
}
@@ -500,15 +522,15 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
});
}
- fn deactivated(&self, cx: &mut AppContext) {
+ fn deactivated(&self, cx: &mut WindowContext) {
self.update(cx, |this, cx| this.deactivated(cx));
}
- fn workspace_deactivated(&self, cx: &mut AppContext) {
+ fn workspace_deactivated(&self, cx: &mut WindowContext) {
self.update(cx, |this, cx| this.workspace_deactivated(cx));
}
- fn navigate(&self, data: Box<dyn Any>, cx: &mut AppContext) -> bool {
+ fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool {
self.update(cx, |this, cx| this.navigate(data, cx))
}
@@ -536,7 +558,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
self.read(cx).can_save(cx)
}
- fn save(&self, project: ModelHandle<Project>, cx: &mut AppContext) -> Task<Result<()>> {
+ fn save(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
self.update(cx, |item, cx| item.save(project, cx))
}
@@ -544,19 +566,19 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
&self,
project: ModelHandle<Project>,
abs_path: PathBuf,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Task<anyhow::Result<()>> {
self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
}
- fn reload(&self, project: ModelHandle<Project>, cx: &mut AppContext) -> Task<Result<()>> {
+ fn reload(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
self.update(cx, |item, cx| item.reload(project, cx))
}
fn git_diff_recalc(
&self,
project: ModelHandle<Project>,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Task<Result<()>> {
self.update(cx, |item, cx| item.git_diff_recalc(project, cx))
}
@@ -591,7 +613,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
self.read(cx).breadcrumb_location()
}
- fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
+ fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
self.read(cx).breadcrumbs(theme, cx)
}
@@ -675,7 +697,7 @@ pub trait FollowableItem: Item {
pub trait FollowableItemHandle: ItemHandle {
fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
- fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut AppContext);
+ fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut WindowContext);
fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
fn add_event_to_update_proto(
&self,
@@ -687,7 +709,7 @@ pub trait FollowableItemHandle: ItemHandle {
&self,
project: &ModelHandle<Project>,
message: proto::update_view::Variant,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Task<Result<()>>;
fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
}
@@ -702,7 +724,7 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
})
}
- fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut AppContext) {
+ fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut WindowContext) {
self.update(cx, |this, cx| {
this.set_leader_replica_id(leader_replica_id, cx)
})
@@ -729,7 +751,7 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
&self,
project: &ModelHandle<Project>,
message: proto::update_view::Variant,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Task<Result<()>> {
self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
}
@@ -748,8 +770,8 @@ pub(crate) mod test {
use super::{Item, ItemEvent};
use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
use gpui::{
- elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, RenderContext, Task,
- View, ViewContext, ViewHandle, WeakViewHandle,
+ elements::Empty, AppContext, Drawable, Element, Entity, ModelHandle, Task, View,
+ ViewContext, ViewHandle, WeakViewHandle,
};
use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
use smallvec::SmallVec;
@@ -907,7 +929,7 @@ pub(crate) mod test {
"TestItem"
}
- fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
Empty::new().boxed()
}
}
@@ -920,7 +942,12 @@ pub(crate) mod test {
})
}
- fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
+ fn tab_content<V: View>(
+ &self,
+ detail: Option<usize>,
+ _: &theme::Tab,
+ _: &AppContext,
+ ) -> Element<V> {
self.tab_detail.set(detail);
Empty::new().boxed()
}
@@ -140,7 +140,7 @@ pub mod simple_message_notification {
elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
impl_actions,
platform::{CursorStyle, MouseButton},
- Action, AppContext, Element, Entity, View, ViewContext,
+ Action, AppContext, Drawable, Entity, View, ViewContext,
};
use menu::Cancel;
use serde::Deserialize;
@@ -229,7 +229,7 @@ pub mod simple_message_notification {
"MessageNotification"
}
- fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
+ fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::Element<Self> {
let theme = cx.global::<Settings>().theme.clone();
let theme = &theme.simple_message_notification;
@@ -244,7 +244,7 @@ pub mod simple_message_notification {
let has_click_action = click_action.is_some();
- MouseEventHandler::<MessageNotificationTag>::new(0, cx, |state, cx| {
+ MouseEventHandler::<MessageNotificationTag, _>::new(0, cx, |state, cx| {
Flex::column()
.with_child(
Flex::row()
@@ -259,7 +259,7 @@ pub mod simple_message_notification {
.boxed(),
)
.with_child(
- MouseEventHandler::<Cancel>::new(0, cx, |state, _| {
+ MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state, false);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)
@@ -274,7 +274,7 @@ pub mod simple_message_notification {
.boxed()
})
.with_padding(Padding::uniform(5.))
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(CancelMessageNotification)
})
.with_cursor_style(CursorStyle::PointingHand)
@@ -312,9 +312,9 @@ pub mod simple_message_notification {
.boxed()
})
// Since we're not using a proper overlay, we have to capture these extra events
- .on_down(MouseButton::Left, |_, _| {})
- .on_up(MouseButton::Left, |_, _| {})
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_down(MouseButton::Left, |_, _, _| {})
+ .on_up(MouseButton::Left, |_, _, _| {})
+ .on_click(MouseButton::Left, move |_, _, cx| {
if let Some(click_action) = click_action.as_ref() {
cx.dispatch_any_action(click_action.boxed_clone());
cx.dispatch_action(CancelMessageNotification)
@@ -7,7 +7,7 @@ use crate::{
toolbar::Toolbar,
Item, NewFile, NewSearch, NewTerminal, Workspace,
};
-use anyhow::Result;
+use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet, VecDeque};
use context_menu::{ContextMenu, ContextMenuItem};
use drag_and_drop::Draggable;
@@ -23,9 +23,8 @@ use gpui::{
impl_actions, impl_internal_actions,
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
- Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
- ModelHandle, MouseRegion, Quad, RenderContext, Task, View, ViewContext, ViewHandle,
- WeakViewHandle,
+ Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle,
+ MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
};
use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize;
@@ -395,7 +394,7 @@ impl Pane {
workspace: &mut Workspace,
pane: Option<ViewHandle<Pane>>,
cx: &mut ViewContext<Workspace>,
- ) -> Task<()> {
+ ) -> Task<Result<()>> {
Self::navigate_history(
workspace,
pane.unwrap_or_else(|| workspace.active_pane().clone()),
@@ -408,7 +407,7 @@ impl Pane {
workspace: &mut Workspace,
pane: Option<ViewHandle<Pane>>,
cx: &mut ViewContext<Workspace>,
- ) -> Task<()> {
+ ) -> Task<Result<()>> {
Self::navigate_history(
workspace,
pane.unwrap_or_else(|| workspace.active_pane().clone()),
@@ -420,7 +419,7 @@ impl Pane {
pub fn reopen_closed_item(
workspace: &mut Workspace,
cx: &mut ViewContext<Workspace>,
- ) -> Task<()> {
+ ) -> Task<Result<()>> {
Self::navigate_history(
workspace,
workspace.active_pane().clone(),
@@ -454,7 +453,7 @@ impl Pane {
pane: ViewHandle<Pane>,
mode: NavigationMode,
cx: &mut ViewContext<Workspace>,
- ) -> Task<()> {
+ ) -> Task<Result<()>> {
cx.focus(&pane);
let to_load = pane.update(cx, |pane, cx| {
@@ -504,47 +503,50 @@ impl Pane {
let task = workspace.load_path(project_path, cx);
cx.spawn(|workspace, mut cx| async move {
let task = task.await;
- if let Some(pane) = pane.upgrade(&cx) {
- let mut navigated = false;
- if let Some((project_entry_id, build_item)) = task.log_err() {
- let prev_active_item_id = pane.update(&mut cx, |pane, _| {
- pane.nav_history.borrow_mut().set_mode(mode);
- pane.active_item().map(|p| p.id())
- });
-
- let item = workspace.update(&mut cx, |workspace, cx| {
- Self::open_item(
- workspace,
- pane.clone(),
- project_entry_id,
- true,
- cx,
- build_item,
- )
- });
-
- pane.update(&mut cx, |pane, cx| {
- navigated |= Some(item.id()) != prev_active_item_id;
- pane.nav_history
- .borrow_mut()
- .set_mode(NavigationMode::Normal);
- if let Some(data) = entry.data {
- navigated |= item.navigate(data, cx);
- }
- });
- }
+ let pane = pane
+ .upgrade(&cx)
+ .ok_or_else(|| anyhow!("pane was dropped"))?;
+ let mut navigated = false;
+ if let Some((project_entry_id, build_item)) = task.log_err() {
+ let prev_active_item_id = pane.update(&mut cx, |pane, _| {
+ pane.nav_history.borrow_mut().set_mode(mode);
+ pane.active_item().map(|p| p.id())
+ })?;
+
+ let item = workspace.update(&mut cx, |workspace, cx| {
+ Self::open_item(
+ workspace,
+ pane.clone(),
+ project_entry_id,
+ true,
+ cx,
+ build_item,
+ )
+ })?;
+
+ pane.update(&mut cx, |pane, cx| {
+ navigated |= Some(item.id()) != prev_active_item_id;
+ pane.nav_history
+ .borrow_mut()
+ .set_mode(NavigationMode::Normal);
+ if let Some(data) = entry.data {
+ navigated |= item.navigate(data, cx);
+ }
+ })?;
+ }
- if !navigated {
- workspace
- .update(&mut cx, |workspace, cx| {
- Self::navigate_history(workspace, pane, mode, cx)
- })
- .await;
- }
+ if !navigated {
+ workspace
+ .update(&mut cx, |workspace, cx| {
+ Self::navigate_history(workspace, pane, mode, cx)
+ })?
+ .await?;
}
+
+ Ok(())
})
} else {
- Task::ready(())
+ Task::ready(Ok(()))
}
}
@@ -1012,10 +1014,10 @@ impl Pane {
if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
pane.remove_item(item_ix, false, cx);
}
- });
+ })?;
}
- pane.update(&mut cx, |_, cx| cx.notify());
+ pane.update(&mut cx, |_, cx| cx.notify())?;
Ok(())
})
}
@@ -1105,10 +1107,10 @@ impl Pane {
CONFLICT_MESSAGE,
&["Overwrite", "Discard", "Cancel"],
)
- });
+ })?;
match answer.next().await {
- Some(0) => cx.update(|cx| item.save(project, cx)).await?,
- Some(1) => cx.update(|cx| item.reload(project, cx)).await?,
+ Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
+ Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
_ => return Ok(false),
}
} else if is_dirty && (can_save || is_singleton) {
@@ -1126,7 +1128,7 @@ impl Pane {
DIRTY_MESSAGE,
&["Save", "Don't Save", "Cancel"],
)
- });
+ })?;
match answer.next().await {
Some(0) => true,
Some(1) => false,
@@ -1138,7 +1140,7 @@ impl Pane {
if should_save {
if can_save {
- cx.update(|cx| item.save(project, cx)).await?;
+ pane.update(cx, |_, cx| item.save(project, cx))?.await?;
} else if is_singleton {
let start_abs_path = project
.read_with(cx, |project, cx| {
@@ -1149,7 +1151,8 @@ impl Pane {
let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
if let Some(abs_path) = abs_path.next().await.flatten() {
- cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
+ pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
+ .await?;
} else {
return Ok(false);
}
@@ -1167,7 +1170,7 @@ impl Pane {
pub fn autosave_item(
item: &dyn ItemHandle,
project: ModelHandle<Project>,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Task<Result<()>> {
if Self::can_autosave_item(item, cx) {
item.save(project, cx)
@@ -1353,10 +1356,10 @@ impl Pane {
});
}
- fn render_tabs(&mut self, cx: &mut RenderContext<Self>) -> impl Element {
+ fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Drawable<Self> {
let theme = cx.global::<Settings>().theme.clone();
- let pane = cx.handle();
+ let pane = cx.handle().downgrade();
let autoscroll = if mem::take(&mut self.autoscroll) {
Some(self.active_item_index)
} else {
@@ -1366,7 +1369,7 @@ impl Pane {
let pane_active = self.is_active;
enum Tabs {}
- let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
+ let mut row = Flex::row().scrollable::<Tabs>(1, autoscroll, cx);
for (ix, (item, detail)) in self
.items
.iter()
@@ -1397,7 +1400,7 @@ impl Pane {
enum Tab {}
let mouse_event_handler =
- MouseEventHandler::<Tab>::new(ix, cx, |_, cx| {
+ MouseEventHandler::<Tab, Pane>::new(ix, cx, |_, cx| {
Self::render_tab(
&item,
pane.clone(),
@@ -1408,13 +1411,13 @@ impl Pane {
cx,
)
})
- .on_down(MouseButton::Left, move |_, cx| {
+ .on_down(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ActivateItem(ix));
})
.on_click(MouseButton::Middle, {
let item = item.clone();
let pane = pane.clone();
- move |_, cx: &mut EventContext| {
+ move |_, _, cx| {
cx.dispatch_action(CloseItemById {
item_id: item.id(),
pane: pane.clone(),
@@ -1423,7 +1426,7 @@ impl Pane {
})
.on_down(
MouseButton::Right,
- move |e, cx| {
+ move |e, _, cx| {
let item = item.clone();
cx.dispatch_action(DeployTabContextMenu {
position: e.position,
@@ -1435,7 +1438,7 @@ impl Pane {
if let Some(tab_tooltip_text) = tab_tooltip_text {
return mouse_event_handler
- .with_tooltip::<Self, _>(
+ .with_tooltip::<Self>(
ix,
tab_tooltip_text,
None,
@@ -1463,9 +1466,9 @@ impl Pane {
let theme = cx.global::<Settings>().theme.clone();
let detail = detail.clone();
- move |dragged_item, cx: &mut RenderContext<Workspace>| {
+ move |dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
let tab_style = &theme.workspace.tab_bar.dragged_tab;
- Self::render_tab(
+ Self::render_dragged_tab(
&dragged_item.item,
dragged_item.pane.clone(),
false,
@@ -1538,16 +1541,41 @@ impl Pane {
tab_details
}
- fn render_tab<V: View>(
+ fn render_tab(
item: &Box<dyn ItemHandle>,
pane: WeakViewHandle<Pane>,
first: bool,
detail: Option<usize>,
hovered: bool,
tab_style: &theme::Tab,
- cx: &mut RenderContext<V>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
let title = item.tab_content(detail, &tab_style, cx);
+ Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
+ }
+
+ fn render_dragged_tab(
+ item: &Box<dyn ItemHandle>,
+ pane: WeakViewHandle<Pane>,
+ first: bool,
+ detail: Option<usize>,
+ hovered: bool,
+ tab_style: &theme::Tab,
+ cx: &mut ViewContext<Workspace>,
+ ) -> Element<Workspace> {
+ let title = item.dragged_tab_content(detail, &tab_style, cx);
+ Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
+ }
+
+ fn render_tab_with_title<T: View>(
+ title: Element<T>,
+ item: &Box<dyn ItemHandle>,
+ pane: WeakViewHandle<Pane>,
+ first: bool,
+ hovered: bool,
+ tab_style: &theme::Tab,
+ cx: &mut ViewContext<T>,
+ ) -> Element<T> {
let mut container = tab_style.container.clone();
if first {
container.border.left = false;
@@ -1566,10 +1594,10 @@ impl Pane {
};
ConstrainedBox::new(
- Canvas::new(move |bounds, _, cx| {
+ Canvas::new(move |scene, bounds, _, _, _| {
if let Some(color) = icon_color {
let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
- cx.scene.push_quad(Quad {
+ scene.push_quad(Quad {
bounds: square,
background: Some(color),
border: Default::default(),
@@ -1603,18 +1631,22 @@ impl Pane {
let item_id = item.id();
enum TabCloseButton {}
let icon = Svg::new("icons/x_mark_8.svg");
- MouseEventHandler::<TabCloseButton>::new(item_id, cx, |mouse_state, _| {
- if mouse_state.hovered() {
- icon.with_color(tab_style.icon_close_active).boxed()
- } else {
- icon.with_color(tab_style.icon_close).boxed()
- }
- })
+ MouseEventHandler::<TabCloseButton, _>::new(
+ item_id,
+ cx,
+ |mouse_state, _| {
+ if mouse_state.hovered() {
+ icon.with_color(tab_style.icon_close_active).boxed()
+ } else {
+ icon.with_color(tab_style.icon_close).boxed()
+ }
+ },
+ )
.with_padding(Padding::uniform(4.))
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, {
let pane = pane.clone();
- move |_, cx| {
+ move |_, _, cx| {
cx.dispatch_action(CloseItemById {
item_id,
pane: pane.clone(),
@@ -1640,8 +1672,8 @@ impl Pane {
fn render_tab_bar_buttons(
&mut self,
theme: &Theme,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Self>,
+ ) -> Element<Self> {
Flex::row()
// New menu
.with_child(render_tab_bar_button(
@@ -1690,7 +1722,7 @@ impl Pane {
.boxed()
}
- fn render_blank_pane(&mut self, theme: &Theme, _cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render_blank_pane(&mut self, theme: &Theme, _cx: &mut ViewContext<Self>) -> Element<Self> {
let background = theme.workspace.background;
Empty::new()
.contained()
@@ -1708,14 +1740,12 @@ impl View for Pane {
"Pane"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
- let this = cx.handle();
-
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
enum MouseNavigationHandler {}
Stack::new()
.with_child(
- MouseEventHandler::<MouseNavigationHandler>::new(0, cx, |_, cx| {
+ MouseEventHandler::<MouseNavigationHandler, _>::new(0, cx, |_, cx| {
let active_item_index = self.active_item_index;
if let Some(active_item) = self.active_item() {
@@ -1727,13 +1757,17 @@ impl View for Pane {
enum TabBarEventHandler {}
stack.add_child(
- MouseEventHandler::<TabBarEventHandler>::new(0, cx, |_, _| {
- Empty::new()
- .contained()
- .with_style(theme.workspace.tab_bar.container)
- .boxed()
- })
- .on_down(MouseButton::Left, move |_, cx| {
+ MouseEventHandler::<TabBarEventHandler, _>::new(
+ 0,
+ cx,
+ |_, _| {
+ Empty::new()
+ .contained()
+ .with_style(theme.workspace.tab_bar.container)
+ .boxed()
+ },
+ )
+ .on_down(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ActivateItem(active_item_index));
})
.boxed(),
@@ -1794,26 +1828,23 @@ impl View for Pane {
dragged_item_receiver::<EmptyPane, _>(0, 0, false, None, cx, |_, cx| {
self.render_blank_pane(&theme, cx)
})
- .on_down(MouseButton::Left, |_, cx| {
+ .on_down(MouseButton::Left, |_, _, cx| {
cx.focus_parent_view();
})
.boxed()
}
})
- .on_down(MouseButton::Navigate(NavigationDirection::Back), {
- let this = this.clone();
- move |_, cx| {
- cx.dispatch_action(GoBack {
- pane: Some(this.clone()),
- });
- }
- })
+ .on_down(
+ MouseButton::Navigate(NavigationDirection::Back),
+ move |_, _, cx| {
+ let pane = cx.weak_handle();
+ cx.dispatch_action(GoBack { pane: Some(pane) });
+ },
+ )
.on_down(MouseButton::Navigate(NavigationDirection::Forward), {
- let this = this.clone();
- move |_, cx| {
- cx.dispatch_action(GoForward {
- pane: Some(this.clone()),
- })
+ move |_, _, cx| {
+ let pane = cx.weak_handle();
+ cx.dispatch_action(GoForward { pane: Some(pane) })
}
})
.boxed(),
@@ -1867,15 +1898,15 @@ impl View for Pane {
fn render_tab_bar_button<A: Action + Clone>(
index: usize,
icon: &'static str,
- cx: &mut RenderContext<Pane>,
+ cx: &mut ViewContext<Pane>,
action: A,
context_menu: Option<ViewHandle<ContextMenu>>,
-) -> ElementBox {
+) -> Element<Pane> {
enum TabBarButton {}
Stack::new()
.with_child(
- MouseEventHandler::<TabBarButton>::new(index, cx, |mouse_state, cx| {
+ MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
let theme = &cx.global::<Settings>().theme.workspace.tab_bar;
let style = theme.pane_button.style_for(mouse_state, false);
Svg::new(icon)
@@ -1889,7 +1920,7 @@ fn render_tab_bar_button<A: Action + Clone>(
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(action.clone());
})
.boxed(),
@@ -1902,15 +1933,15 @@ fn render_tab_bar_button<A: Action + Clone>(
}
impl ItemNavHistory {
- pub fn push<D: 'static + Any>(&self, data: Option<D>, cx: &mut AppContext) {
+ pub fn push<D: 'static + Any>(&self, data: Option<D>, cx: &mut WindowContext) {
self.history.borrow_mut().push(data, self.item.clone(), cx);
}
- pub fn pop_backward(&self, cx: &mut AppContext) -> Option<NavigationEntry> {
+ pub fn pop_backward(&self, cx: &mut WindowContext) -> Option<NavigationEntry> {
self.history.borrow_mut().pop(NavigationMode::GoingBack, cx)
}
- pub fn pop_forward(&self, cx: &mut AppContext) -> Option<NavigationEntry> {
+ pub fn pop_forward(&self, cx: &mut WindowContext) -> Option<NavigationEntry> {
self.history
.borrow_mut()
.pop(NavigationMode::GoingForward, cx)
@@ -1930,7 +1961,7 @@ impl NavHistory {
self.mode = NavigationMode::Normal;
}
- fn pop(&mut self, mode: NavigationMode, cx: &mut AppContext) -> Option<NavigationEntry> {
+ fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
let entry = match mode {
NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
return None
@@ -1950,7 +1981,7 @@ impl NavHistory {
&mut self,
data: Option<D>,
item: Rc<dyn WeakItemHandle>,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) {
match self.mode {
NavigationMode::Disabled => {}
@@ -1995,19 +2026,20 @@ impl NavHistory {
self.did_update(cx);
}
- fn did_update(&self, cx: &mut AppContext) {
+ fn did_update(&self, cx: &mut WindowContext) {
if let Some(pane) = self.pane.upgrade(cx) {
cx.defer(move |cx| pane.update(cx, |pane, cx| pane.history_updated(cx)));
}
}
}
-pub struct PaneBackdrop {
+pub struct PaneBackdrop<V: View> {
child_view: usize,
- child: ElementBox,
+ child: Element<V>,
}
-impl PaneBackdrop {
- pub fn new(pane_item_view: usize, child: ElementBox) -> Self {
+
+impl<V: View> PaneBackdrop<V> {
+ pub fn new(pane_item_view: usize, child: Element<V>) -> Self {
PaneBackdrop {
child,
child_view: pane_item_view,
@@ -2015,7 +2047,7 @@ impl PaneBackdrop {
}
}
-impl Element for PaneBackdrop {
+impl<V: View> Drawable<V> for PaneBackdrop<V> {
type LayoutState = ();
type PaintState = ();
@@ -2023,42 +2055,46 @@ impl Element for PaneBackdrop {
fn layout(
&mut self,
constraint: gpui::SizeConstraint,
- cx: &mut gpui::LayoutContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
- let size = self.child.layout(constraint, cx);
+ let size = self.child.layout(constraint, view, cx);
(size, ())
}
fn paint(
&mut self,
+ scene: &mut gpui::SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut gpui::PaintContext,
+ view: &mut V,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
let background = cx.global::<Settings>().theme.editor.background;
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
- cx.scene.push_quad(gpui::Quad {
+ scene.push_quad(gpui::Quad {
bounds: RectF::new(bounds.origin(), bounds.size()),
background: Some(background),
..Default::default()
});
let child_view_id = self.child_view;
- cx.scene.push_mouse_region(
+ scene.push_mouse_region(
MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
gpui::platform::MouseButton::Left,
- move |_, cx| {
- let window_id = cx.window_id;
- cx.focus(window_id, Some(child_view_id))
+ move |_, _: &mut V, cx| {
+ let window_id = cx.window_id();
+ cx.app_context().focus(window_id, Some(child_view_id))
},
),
);
- cx.paint_layer(Some(bounds), |cx| {
- self.child.paint(bounds.origin(), visible_bounds, cx)
+ scene.paint_layer(Some(bounds), |scene| {
+ self.child
+ .paint(scene, bounds.origin(), visible_bounds, view, cx)
})
}
@@ -2069,9 +2105,10 @@ impl Element for PaneBackdrop {
_visible_bounds: RectF,
_layout: &Self::LayoutState,
_paint: &Self::PaintState,
- cx: &gpui::MeasurementContext,
+ view: &V,
+ cx: &gpui::ViewContext<V>,
) -> Option<RectF> {
- self.child.rect_for_text_range(range_utf16, cx)
+ self.child.rect_for_text_range(range_utf16, view, cx)
}
fn debug(
@@ -2079,12 +2116,13 @@ impl Element for PaneBackdrop {
_bounds: RectF,
_layout: &Self::LayoutState,
_paint: &Self::PaintState,
- cx: &gpui::DebugContext,
+ view: &V,
+ cx: &gpui::ViewContext<V>,
) -> serde_json::Value {
gpui::json::json!({
"type": "Pane Back Drop",
"view": self.child_view,
- "child": self.child.debug(cx),
+ "child": self.child.debug(view, cx),
})
}
}
@@ -5,7 +5,8 @@ use gpui::{
geometry::{rect::RectF, vector::Vector2F},
platform::MouseButton,
scene::MouseUp,
- AppContext, Element, ElementBox, EventContext, MouseState, Quad, RenderContext, WeakViewHandle,
+ AppContext, Drawable, Element, EventContext, MouseState, Quad, View, ViewContext,
+ WeakViewHandle,
};
use project::ProjectEntryId;
use settings::Settings;
@@ -22,14 +23,14 @@ pub fn dragged_item_receiver<Tag, F>(
drop_index: usize,
allow_same_pane: bool,
split_margin: Option<f32>,
- cx: &mut RenderContext<Pane>,
+ cx: &mut ViewContext<Pane>,
render_child: F,
-) -> MouseEventHandler<Tag>
+) -> MouseEventHandler<Tag, Pane>
where
Tag: 'static,
- F: FnOnce(&mut MouseState, &mut RenderContext<Pane>) -> ElementBox,
+ F: FnOnce(&mut MouseState, &mut ViewContext<Pane>) -> Element<Pane>,
{
- MouseEventHandler::<Tag>::above(region_id, cx, |state, cx| {
+ MouseEventHandler::<Tag, _>::above(region_id, cx, |state, cx| {
// Observing hovered will cause a render when the mouse enters regardless
// of if mouse position was accessed before
let drag_position = if state.hovered() {
@@ -48,7 +49,7 @@ where
Stack::new()
.with_child(render_child(state, cx))
.with_children(drag_position.map(|drag_position| {
- Canvas::new(move |bounds, _, cx| {
+ Canvas::new(move |scene, bounds, _, _, cx| {
if bounds.contains_point(drag_position) {
let overlay_region = split_margin
.and_then(|split_margin| {
@@ -58,8 +59,8 @@ where
.map(|(dir, margin)| dir.along_edge(bounds, margin))
.unwrap_or(bounds);
- cx.paint_stacking_context(None, None, |cx| {
- cx.scene.push_quad(Quad {
+ scene.paint_stacking_context(None, None, |scene| {
+ scene.push_quad(Quad {
bounds: overlay_region,
background: Some(overlay_color(cx)),
border: Default::default(),
@@ -73,13 +74,13 @@ where
.boxed()
})
.on_up(MouseButton::Left, {
- let pane = cx.handle();
- move |event, cx| {
+ move |event, _, cx| {
+ let pane = cx.weak_handle();
handle_dropped_item(event, &pane, drop_index, allow_same_pane, split_margin, cx);
cx.notify();
}
})
- .on_move(|_, cx| {
+ .on_move(|_, _, cx| {
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
if drag_and_drop
@@ -96,13 +97,13 @@ where
})
}
-pub fn handle_dropped_item(
+pub fn handle_dropped_item<V: View>(
event: MouseUp,
pane: &WeakViewHandle<Pane>,
index: usize,
allow_same_pane: bool,
split_margin: Option<f32>,
- cx: &mut EventContext,
+ cx: &mut EventContext<V>,
) {
enum Action {
Move(WeakViewHandle<Pane>, usize),
@@ -110,11 +111,11 @@ pub fn handle_dropped_item(
}
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
let action = if let Some((_, dragged_item)) =
- drag_and_drop.currently_dragged::<DraggedItem>(cx.window_id)
+ drag_and_drop.currently_dragged::<DraggedItem>(cx.window_id())
{
Action::Move(dragged_item.pane.clone(), dragged_item.item.id())
} else if let Some((_, project_entry)) =
- drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window_id)
+ drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window_id())
{
Action::Open(*project_entry)
} else {
@@ -5,7 +5,7 @@ use gpui::{
elements::*,
geometry::{rect::RectF, vector::Vector2F},
platform::{CursorStyle, MouseButton},
- Axis, Border, ModelHandle, RenderContext, ViewHandle,
+ Axis, Border, ModelHandle, ViewContext, ViewHandle,
};
use project::Project;
use serde::Deserialize;
@@ -70,8 +70,8 @@ impl PaneGroup {
follower_states: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
- cx: &mut RenderContext<Workspace>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Workspace>,
+ ) -> Element<Workspace> {
self.root.render(
project,
theme,
@@ -131,8 +131,8 @@ impl Member {
follower_states: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
- cx: &mut RenderContext<Workspace>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Workspace>,
+ ) -> Element<Workspace> {
enum FollowIntoExternalProject {}
match self {
@@ -176,7 +176,7 @@ impl Member {
let leader_user = leader.user.clone();
let leader_user_id = leader.user.id;
Some(
- MouseEventHandler::<FollowIntoExternalProject>::new(
+ MouseEventHandler::<FollowIntoExternalProject, _>::new(
pane.id(),
cx,
|_, _| {
@@ -199,7 +199,7 @@ impl Member {
},
)
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(JoinProject {
project_id: leader_project_id,
follow_user_id: leader_user_id,
@@ -366,8 +366,8 @@ impl PaneAxis {
follower_state: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
- cx: &mut RenderContext<Workspace>,
- ) -> ElementBox {
+ cx: &mut ViewContext<Workspace>,
+ ) -> Element<Workspace> {
let last_member_ix = self.members.len() - 1;
Flex::new(self.axis)
.with_children(self.members.iter().enumerate().map(|(ix, member)| {
@@ -131,16 +131,21 @@ impl SerializedPaneGroup {
))
}
SerializedPaneGroup::Pane(serialized_pane) => {
- let pane = workspace.update(cx, |workspace, cx| workspace.add_pane(cx));
+ let pane = workspace
+ .update(cx, |workspace, cx| workspace.add_pane(cx))
+ .log_err()?;
let active = serialized_pane.active;
serialized_pane
.deserialize_to(project, &pane, workspace_id, workspace, cx)
- .await;
+ .await
+ .log_err()?;
if pane.read_with(cx, |pane, _| pane.items_len() != 0) {
Some((Member::Pane(pane.clone()), active.then(|| pane)))
} else {
- workspace.update(cx, |workspace, cx| workspace.remove_pane(pane, cx));
+ workspace
+ .update(cx, |workspace, cx| workspace.remove_pane(pane, cx))
+ .log_err()?;
None
}
}
@@ -166,7 +171,7 @@ impl SerializedPane {
workspace_id: WorkspaceId,
workspace: &ViewHandle<Workspace>,
cx: &mut AsyncAppContext,
- ) {
+ ) -> Result<()> {
let mut active_item_index = None;
for (index, item) in self.children.iter().enumerate() {
let project = project.clone();
@@ -186,14 +191,14 @@ impl SerializedPane {
item.kind
)))
}
- })
+ })?
.await
.log_err();
if let Some(item_handle) = item_handle {
workspace.update(cx, |workspace, cx| {
Pane::add_item(workspace, &pane_handle, item_handle, false, false, None, cx);
- })
+ })?;
}
if item.active {
@@ -204,8 +209,10 @@ impl SerializedPane {
if let Some(active_item_index) = active_item_index {
pane_handle.update(cx, |pane, cx| {
pane.activate_item(active_item_index, false, false, cx);
- })
+ })?;
}
+
+ anyhow::Ok(())
}
}
@@ -2,7 +2,7 @@ use std::any::Any;
use gpui::{
AnyViewHandle, AnyWeakViewHandle, AppContext, Subscription, Task, ViewContext, ViewHandle,
- WeakViewHandle,
+ WeakViewHandle, WindowContext,
};
use project::search::SearchQuery;
@@ -90,29 +90,34 @@ pub trait SearchableItemHandle: ItemHandle {
fn supported_options(&self) -> SearchOptions;
fn subscribe_to_search_events(
&self,
- cx: &mut AppContext,
- handler: Box<dyn Fn(SearchEvent, &mut AppContext)>,
+ cx: &mut WindowContext,
+ handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
) -> Subscription;
- fn clear_matches(&self, cx: &mut AppContext);
- fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut AppContext);
- fn query_suggestion(&self, cx: &mut AppContext) -> String;
- fn activate_match(&self, index: usize, matches: &Vec<Box<dyn Any + Send>>, cx: &mut AppContext);
+ fn clear_matches(&self, cx: &mut WindowContext);
+ fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
+ fn query_suggestion(&self, cx: &mut WindowContext) -> String;
+ fn activate_match(
+ &self,
+ index: usize,
+ matches: &Vec<Box<dyn Any + Send>>,
+ cx: &mut WindowContext,
+ );
fn match_index_for_direction(
&self,
matches: &Vec<Box<dyn Any + Send>>,
current_index: usize,
direction: Direction,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> usize;
fn find_matches(
&self,
query: SearchQuery,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Task<Vec<Box<dyn Any + Send>>>;
fn active_match_index(
&self,
matches: &Vec<Box<dyn Any + Send>>,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Option<usize>;
}
@@ -131,8 +136,8 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
fn subscribe_to_search_events(
&self,
- cx: &mut AppContext,
- handler: Box<dyn Fn(SearchEvent, &mut AppContext)>,
+ cx: &mut WindowContext,
+ handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
) -> Subscription {
cx.subscribe(self, move |_, event, cx| {
if let Some(search_event) = T::to_search_event(event) {
@@ -141,21 +146,21 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
})
}
- fn clear_matches(&self, cx: &mut AppContext) {
+ fn clear_matches(&self, cx: &mut WindowContext) {
self.update(cx, |this, cx| this.clear_matches(cx));
}
- fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut AppContext) {
+ fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) {
let matches = downcast_matches(matches);
self.update(cx, |this, cx| this.update_matches(matches, cx));
}
- fn query_suggestion(&self, cx: &mut AppContext) -> String {
+ fn query_suggestion(&self, cx: &mut WindowContext) -> String {
self.update(cx, |this, cx| this.query_suggestion(cx))
}
fn activate_match(
&self,
index: usize,
matches: &Vec<Box<dyn Any + Send>>,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) {
let matches = downcast_matches(matches);
self.update(cx, |this, cx| this.activate_match(index, matches, cx));
@@ -165,7 +170,7 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
matches: &Vec<Box<dyn Any + Send>>,
current_index: usize,
direction: Direction,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> usize {
let matches = downcast_matches(matches);
self.update(cx, |this, cx| {
@@ -175,7 +180,7 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
fn find_matches(
&self,
query: SearchQuery,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Task<Vec<Box<dyn Any + Send>>> {
let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
cx.foreground().spawn(async {
@@ -189,7 +194,7 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
fn active_match_index(
&self,
matches: &Vec<Box<dyn Any + Send>>,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Option<usize> {
let matches = downcast_matches(matches);
self.update(cx, |this, cx| this.active_match_index(matches, cx))
@@ -2,6 +2,7 @@ use crate::{
item::{Item, ItemEvent},
ItemNavHistory, WorkspaceId,
};
+use anyhow::Result;
use call::participant::{Frame, RemoteVideoTrack};
use client::{proto::PeerId, User};
use futures::StreamExt;
@@ -9,7 +10,7 @@ use gpui::{
elements::*,
geometry::{rect::RectF, vector::vec2f},
platform::MouseButton,
- AppContext, Entity, RenderContext, Task, View, ViewContext,
+ AppContext, Entity, Task, View, ViewContext,
};
use settings::Settings;
use smallvec::SmallVec;
@@ -28,7 +29,7 @@ pub struct SharedScreen {
pub peer_id: PeerId,
user: Arc<User>,
nav_history: Option<ItemNavHistory>,
- _maintain_frame: Task<()>,
+ _maintain_frame: Task<Result<()>>,
}
impl SharedScreen {
@@ -50,9 +51,10 @@ impl SharedScreen {
this.update(&mut cx, |this, cx| {
this.frame = Some(frame);
cx.notify();
- })
+ })?;
}
- this.update(&mut cx, |_, cx| cx.emit(Event::Close));
+ this.update(&mut cx, |_, cx| cx.emit(Event::Close))?;
+ Ok(())
}),
}
}
@@ -67,19 +69,19 @@ impl View for SharedScreen {
"SharedScreen"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
enum Focus {}
let frame = self.frame.clone();
- MouseEventHandler::<Focus>::new(0, cx, |_, cx| {
- Canvas::new(move |bounds, _, cx| {
+ MouseEventHandler::<Focus, _>::new(0, cx, |_, cx| {
+ Canvas::new(move |scene, bounds, _, _, _| {
if let Some(frame) = frame.clone() {
let size = constrain_size_preserving_aspect_ratio(
bounds.size(),
vec2f(frame.width() as f32, frame.height() as f32),
);
let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
- cx.scene.push_surface(gpui::platform::mac::Surface {
+ scene.push_surface(gpui::platform::mac::Surface {
bounds: RectF::new(origin, size),
image_buffer: frame.image(),
});
@@ -89,7 +91,7 @@ impl View for SharedScreen {
.with_style(cx.global::<Settings>().theme.shared_screen)
.boxed()
})
- .on_down(MouseButton::Left, |_, cx| cx.focus_parent_view())
+ .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent_view())
.boxed()
}
}
@@ -104,12 +106,12 @@ impl Item for SharedScreen {
}
}
- fn tab_content(
+ fn tab_content<V: View>(
&self,
_: Option<usize>,
style: &theme::Tab,
_: &AppContext,
- ) -> gpui::ElementBox {
+ ) -> gpui::Element<V> {
Flex::row()
.with_child(
Svg::new("icons/disable_screen_sharing_12.svg")
@@ -1,7 +1,7 @@
use crate::StatusItemView;
use gpui::{
elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle,
- AppContext, Entity, RenderContext, Subscription, View, ViewContext, ViewHandle,
+ AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WindowContext,
};
use serde::Deserialize;
use settings::Settings;
@@ -21,8 +21,8 @@ pub trait SidebarItem: View {
pub trait SidebarItemHandle {
fn id(&self) -> usize;
- fn should_show_badge(&self, cx: &AppContext) -> bool;
- fn is_focused(&self, cx: &AppContext) -> bool;
+ fn should_show_badge(&self, cx: &WindowContext) -> bool;
+ fn is_focused(&self, cx: &WindowContext) -> bool;
fn as_any(&self) -> &AnyViewHandle;
}
@@ -34,11 +34,11 @@ where
self.id()
}
- fn should_show_badge(&self, cx: &AppContext) -> bool {
+ fn should_show_badge(&self, cx: &WindowContext) -> bool {
self.read(cx).should_show_badge(cx)
}
- fn is_focused(&self, cx: &AppContext) -> bool {
+ fn is_focused(&self, cx: &WindowContext) -> bool {
ViewHandle::is_focused(self, cx) || self.read(cx).contains_focused_view(cx)
}
@@ -188,14 +188,14 @@ impl View for Sidebar {
"Sidebar"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
if let Some(active_item) = self.active_item() {
enum ResizeHandleTag {}
let style = &cx.global::<Settings>().theme.workspace.sidebar;
ChildView::new(active_item.as_any(), cx)
.contained()
.with_style(style.container)
- .with_resize_handle::<ResizeHandleTag, _>(
+ .with_resize_handle::<ResizeHandleTag>(
self.sidebar_side as usize,
self.sidebar_side.to_resizable_side(),
4.,
@@ -225,7 +225,7 @@ impl View for SidebarButtons {
"SidebarToggleButton"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = &cx.global::<Settings>().theme;
let tooltip_style = theme.tooltip.clone();
let theme = &theme.workspace.status_bar.sidebar_buttons;
@@ -254,7 +254,7 @@ impl View for SidebarButtons {
sidebar_side,
item_index: ix,
};
- MouseEventHandler::<Self>::new(ix, cx, |state, cx| {
+ MouseEventHandler::<Self, _>::new(ix, cx, |state, cx| {
let is_active = is_open && ix == active_ix;
let style = item_style.style_for(state, is_active);
Stack::new()
@@ -283,9 +283,9 @@ impl View for SidebarButtons {
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, {
let action = action.clone();
- move |_, cx| cx.dispatch_action(action.clone())
+ move |_, _, cx| cx.dispatch_action(action.clone())
})
- .with_tooltip::<Self, _>(
+ .with_tooltip::<Self>(
ix,
tooltip,
Some(Box::new(action)),
@@ -8,8 +8,8 @@ use gpui::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
- AnyViewHandle, AppContext, DebugContext, ElementBox, Entity, LayoutContext, MeasurementContext,
- PaintContext, RenderContext, SizeConstraint, Subscription, View, ViewContext, ViewHandle,
+ AnyViewHandle, Element, Entity, SceneBuilder, SizeConstraint, Subscription, View, ViewContext,
+ ViewHandle, WindowContext,
};
use settings::Settings;
@@ -23,7 +23,11 @@ pub trait StatusItemView: View {
trait StatusItemViewHandle {
fn as_any(&self) -> &AnyViewHandle;
- fn set_active_pane_item(&self, active_pane_item: Option<&dyn ItemHandle>, cx: &mut AppContext);
+ fn set_active_pane_item(
+ &self,
+ active_pane_item: Option<&dyn ItemHandle>,
+ cx: &mut WindowContext,
+ );
}
pub struct StatusBar {
@@ -42,7 +46,7 @@ impl View for StatusBar {
"StatusBar"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = &cx.global::<Settings>().theme.workspace.status_bar;
StatusBarElement {
@@ -125,7 +129,11 @@ impl<T: StatusItemView> StatusItemViewHandle for ViewHandle<T> {
self
}
- fn set_active_pane_item(&self, active_pane_item: Option<&dyn ItemHandle>, cx: &mut AppContext) {
+ fn set_active_pane_item(
+ &self,
+ active_pane_item: Option<&dyn ItemHandle>,
+ cx: &mut WindowContext,
+ ) {
self.update(cx, |this, cx| {
this.set_active_pane_item(active_pane_item, cx)
});
@@ -139,48 +147,53 @@ impl From<&dyn StatusItemViewHandle> for AnyViewHandle {
}
struct StatusBarElement {
- left: ElementBox,
- right: ElementBox,
+ left: Element<StatusBar>,
+ right: Element<StatusBar>,
}
-impl Element for StatusBarElement {
+impl Drawable<StatusBar> for StatusBarElement {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
mut constraint: SizeConstraint,
- cx: &mut LayoutContext,
+ view: &mut StatusBar,
+ cx: &mut ViewContext<StatusBar>,
) -> (Vector2F, Self::LayoutState) {
let max_width = constraint.max.x();
constraint.min = vec2f(0., constraint.min.y());
- let right_size = self.right.layout(constraint, cx);
+ let right_size = self.right.layout(constraint, view, cx);
let constraint = SizeConstraint::new(
vec2f(0., constraint.min.y()),
vec2f(max_width - right_size.x(), constraint.max.y()),
);
- self.left.layout(constraint, cx);
+ self.left.layout(constraint, view, cx);
(vec2f(max_width, right_size.y()), ())
}
fn paint(
&mut self,
+ scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
- cx: &mut PaintContext,
+ view: &mut StatusBar,
+ cx: &mut ViewContext<StatusBar>,
) -> Self::PaintState {
let origin_y = bounds.upper_right().y();
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
let left_origin = vec2f(bounds.lower_left().x(), origin_y);
- self.left.paint(left_origin, visible_bounds, cx);
+ self.left
+ .paint(scene, left_origin, visible_bounds, view, cx);
let right_origin = vec2f(bounds.upper_right().x() - self.right.size().x(), origin_y);
- self.right.paint(right_origin, visible_bounds, cx);
+ self.right
+ .paint(scene, right_origin, visible_bounds, view, cx);
}
fn rect_for_text_range(
@@ -190,7 +203,8 @@ impl Element for StatusBarElement {
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &MeasurementContext,
+ _: &StatusBar,
+ _: &ViewContext<StatusBar>,
) -> Option<RectF> {
None
}
@@ -200,7 +214,8 @@ impl Element for StatusBarElement {
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
- _: &DebugContext,
+ _: &StatusBar,
+ _: &ViewContext<StatusBar>,
) -> serde_json::Value {
json!({
"type": "StatusBarElement",
@@ -1,7 +1,7 @@
use crate::{ItemHandle, Pane};
use gpui::{
elements::*, platform::CursorStyle, platform::MouseButton, Action, AnyViewHandle, AppContext,
- ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
+ Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
};
use settings::Settings;
@@ -21,7 +21,7 @@ pub trait ToolbarItemView: View {
current_location
}
- fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut AppContext) {}
+ fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext<Self>) {}
}
trait ToolbarItemViewHandle {
@@ -30,9 +30,9 @@ trait ToolbarItemViewHandle {
fn set_active_pane_item(
&self,
active_pane_item: Option<&dyn ItemHandle>,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> ToolbarItemLocation;
- fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut AppContext);
+ fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext);
}
#[derive(Copy, Clone, Debug, PartialEq)]
@@ -59,7 +59,7 @@ impl View for Toolbar {
"Toolbar"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = &cx.global::<Settings>().theme.workspace.toolbar;
let mut primary_left_items = Vec::new();
@@ -171,9 +171,9 @@ fn nav_button<A: Action + Clone>(
action: A,
tooltip_action: A,
action_name: &str,
- cx: &mut RenderContext<Toolbar>,
-) -> ElementBox {
- MouseEventHandler::<A>::new(0, cx, |state, _| {
+ cx: &mut ViewContext<Toolbar>,
+) -> Element<Toolbar> {
+ MouseEventHandler::<A, _>::new(0, cx, |state, _| {
let style = if enabled {
style.style_for(state, false)
} else {
@@ -197,10 +197,10 @@ fn nav_button<A: Action + Clone>(
} else {
CursorStyle::default()
})
- .on_click(MouseButton::Left, move |_, cx| {
+ .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(action.clone())
})
- .with_tooltip::<A, _>(
+ .with_tooltip::<A>(
0,
action_name.to_string(),
Some(Box::new(tooltip_action)),
@@ -266,7 +266,7 @@ impl Toolbar {
}
}
- pub fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut AppContext) {
+ pub fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut ViewContext<Self>) {
for (toolbar_item, _) in self.items.iter_mut() {
toolbar_item.pane_focus_update(pane_focused, cx);
}
@@ -295,14 +295,14 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
fn set_active_pane_item(
&self,
active_pane_item: Option<&dyn ItemHandle>,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> ToolbarItemLocation {
self.update(cx, |this, cx| {
this.set_active_pane_item(active_pane_item, cx)
})
}
- fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut AppContext) {
+ fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext) {
self.update(cx, |this, cx| {
this.pane_focus_update(pane_focused, cx);
cx.notify();
@@ -45,8 +45,7 @@ use gpui::{
WindowOptions,
},
Action, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext,
- ModelHandle, RenderContext, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
- WeakViewHandle,
+ ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
use language::LanguageRegistry;
@@ -97,6 +96,10 @@ lazy_static! {
.and_then(parse_pixel_position_env_var);
}
+pub trait Modal: View {
+ fn dismiss_on_event(event: &Self::Event) -> bool;
+}
+
#[derive(Clone, PartialEq)]
pub struct RemoveWorktreeFromProject(pub WorktreeId);
@@ -321,7 +324,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
None
};
cx.update(|cx| open_paths(&action.paths, &app_state, window_id_to_replace, cx))
- .await;
+ .await?;
Ok(())
}))
}
@@ -411,20 +414,18 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
.await
.context("Failed to create CLI symlink");
- cx.update(|cx| {
- workspace.update(cx, |workspace, cx| {
- if matches!(err, Err(_)) {
- err.notify_err(workspace, cx);
- } else {
- workspace.show_notification(1, cx, |cx| {
- cx.add_view(|_| {
- MessageNotification::new_message(
- "Successfully installed the `zed` binary",
- )
- })
- });
- }
- })
+ workspace.update(&mut cx, |workspace, cx| {
+ if matches!(err, Err(_)) {
+ err.notify_err(workspace, cx);
+ } else {
+ workspace.show_notification(1, cx, |cx| {
+ cx.add_view(|_| {
+ MessageNotification::new_message(
+ "Successfully installed the `zed` binary",
+ )
+ })
+ });
+ }
})
})
.detach();
@@ -575,27 +576,19 @@ impl DelayedDebouncedEditAction {
}
}
- fn fire_new<F, Fut>(
- &mut self,
- delay: Duration,
- workspace: &Workspace,
- cx: &mut ViewContext<Workspace>,
- f: F,
- ) where
- F: FnOnce(ModelHandle<Project>, AsyncAppContext) -> Fut + 'static,
- Fut: 'static + Future<Output = ()>,
+ fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, f: F)
+ where
+ F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
{
if let Some(channel) = self.cancel_channel.take() {
_ = channel.send(());
}
- let project = workspace.project().downgrade();
-
let (sender, mut receiver) = oneshot::channel::<()>();
self.cancel_channel = Some(sender);
let previous_task = self.task.take();
- self.task = Some(cx.spawn_weak(|_, cx| async move {
+ self.task = Some(cx.spawn_weak(|workspace, mut cx| async move {
let mut timer = cx.background().timer(delay).fuse();
if let Some(previous_task) = previous_task {
previous_task.await;
@@ -606,8 +599,13 @@ impl DelayedDebouncedEditAction {
_ = timer => {}
}
- if let Some(project) = project.upgrade(&cx) {
- (f)(project, cx).await;
+ if let Some(workspace) = workspace.upgrade(&cx) {
+ if let Some(result) = workspace
+ .update(&mut cx, |workspace, cx| (f)(workspace, cx))
+ .log_err()
+ {
+ result.await.log_err();
+ }
}
}));
}
@@ -648,7 +646,7 @@ pub struct Workspace {
background_actions: BackgroundActions,
_window_subscriptions: [Subscription; 3],
_apply_leader_updates: Task<Result<()>>,
- _observe_current_user: Task<()>,
+ _observe_current_user: Task<Result<()>>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
@@ -702,8 +700,7 @@ impl Workspace {
}
project::Event::Closed => {
- let window_id = cx.window_id();
- cx.remove_window(window_id);
+ cx.remove_window();
}
_ => {}
@@ -743,12 +740,11 @@ impl Workspace {
Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
while stream.recv().await.is_some() {
- cx.update(|cx| {
- if let Some(this) = this.upgrade(cx) {
- this.update(cx, |_, cx| cx.notify());
- }
- })
+ if let Some(this) = this.upgrade(&cx) {
+ this.update(&mut cx, |_, cx| cx.notify())?;
+ }
}
+ anyhow::Ok(())
});
let handle = cx.handle();
@@ -885,7 +881,8 @@ impl Workspace {
);
cx.spawn(|mut cx| async move {
- let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
+ let mut serialized_workspace =
+ persistence::DB.workspace_for_roots(&abs_paths.as_slice());
let paths_to_open = serialized_workspace
.as_ref()
@@ -932,7 +929,7 @@ impl Workspace {
let mut workspace = Workspace::new(
serialized_workspace,
workspace_id,
- project_handle,
+ project_handle.clone(),
app_state.dock_default_item_factory,
app_state.background_actions,
cx,
@@ -941,50 +938,54 @@ impl Workspace {
workspace
};
- let workspace = if let Some(window_id) = requesting_window_id {
- cx.update(|cx| {
- cx.replace_root_view(window_id, |cx| build_workspace(cx, serialized_workspace))
+ let workspace = requesting_window_id
+ .and_then(|window_id| {
+ cx.update(|cx| {
+ cx.replace_root_view(window_id, |cx| {
+ build_workspace(cx, serialized_workspace.take())
+ })
+ })
})
- } else {
- let (bounds, display) = if let Some(bounds) = window_bounds_override {
- (Some(bounds), None)
- } else {
- serialized_workspace
- .as_ref()
- .and_then(|serialized_workspace| {
- let display = serialized_workspace.display?;
- let mut bounds = serialized_workspace.bounds?;
-
- // Stored bounds are relative to the containing display.
- // So convert back to global coordinates if that screen still exists
- if let WindowBounds::Fixed(mut window_bounds) = bounds {
- if let Some(screen) = cx.platform().screen_by_id(display) {
- let screen_bounds = screen.bounds();
- window_bounds.set_origin_x(
- window_bounds.origin_x() + screen_bounds.origin_x(),
- );
- window_bounds.set_origin_y(
- window_bounds.origin_y() + screen_bounds.origin_y(),
- );
- bounds = WindowBounds::Fixed(window_bounds);
- } else {
- // Screen no longer exists. Return none here.
- return None;
+ .unwrap_or_else(|| {
+ let (bounds, display) = if let Some(bounds) = window_bounds_override {
+ (Some(bounds), None)
+ } else {
+ serialized_workspace
+ .as_ref()
+ .and_then(|serialized_workspace| {
+ let display = serialized_workspace.display?;
+ let mut bounds = serialized_workspace.bounds?;
+
+ // Stored bounds are relative to the containing display.
+ // So convert back to global coordinates if that screen still exists
+ if let WindowBounds::Fixed(mut window_bounds) = bounds {
+ if let Some(screen) = cx.platform().screen_by_id(display) {
+ let screen_bounds = screen.bounds();
+ window_bounds.set_origin_x(
+ window_bounds.origin_x() + screen_bounds.origin_x(),
+ );
+ window_bounds.set_origin_y(
+ window_bounds.origin_y() + screen_bounds.origin_y(),
+ );
+ bounds = WindowBounds::Fixed(window_bounds);
+ } else {
+ // Screen no longer exists. Return none here.
+ return None;
+ }
}
- }
- Some((bounds, display))
- })
- .unzip()
- };
+ Some((bounds, display))
+ })
+ .unzip()
+ };
- // Use the serialized workspace to construct the new window
- cx.add_window(
- (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
- |cx| build_workspace(cx, serialized_workspace),
- )
- .1
- };
+ // Use the serialized workspace to construct the new window
+ cx.add_window(
+ (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
+ |cx| build_workspace(cx, serialized_workspace),
+ )
+ .1
+ });
notify_if_database_failed(&workspace, &mut cx);
@@ -1007,6 +1008,7 @@ impl Workspace {
.update(&mut cx, |workspace, cx| {
workspace.open_path(project_path, None, true, cx)
})
+ .log_err()?
.await,
)
} else {
@@ -1068,13 +1070,13 @@ impl Workspace {
app_state: &Arc<AppState>,
cx: &mut ViewContext<Self>,
callback: F,
- ) -> Task<T>
+ ) -> Task<Result<T>>
where
T: 'static,
F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
{
if self.project.read(cx).is_local() {
- Task::Ready(Some(callback(self, cx)))
+ Task::Ready(Some(Ok(callback(self, cx))))
} else {
let task = Self::new_local(Vec::new(), app_state.clone(), None, cx);
cx.spawn(|_vh, mut cx| async move {
@@ -1125,13 +1127,11 @@ impl Workspace {
_: &CloseWindow,
cx: &mut ViewContext<Self>,
) -> Option<Task<Result<()>>> {
+ let window_id = cx.window_id();
let prepare = self.prepare_to_close(false, cx);
- Some(cx.spawn(|this, mut cx| async move {
+ Some(cx.spawn_weak(|_, mut cx| async move {
if prepare.await? {
- this.update(&mut cx, |_, cx| {
- let window_id = cx.window_id();
- cx.remove_window(window_id);
- });
+ cx.remove_window(window_id);
}
Ok(())
}))
@@ -1146,7 +1146,14 @@ impl Workspace {
let window_id = cx.window_id();
let workspace_count = cx
.window_ids()
- .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
+ .collect::<Vec<_>>()
+ .into_iter()
+ .filter_map(|window_id| {
+ cx.app_context()
+ .root_view(window_id)?
+ .clone()
+ .downcast::<Workspace>()
+ })
.count();
cx.spawn(|this, mut cx| async move {
@@ -1155,29 +1162,28 @@ impl Workspace {
&& workspace_count == 1
&& active_call.read_with(&cx, |call, _| call.room().is_some())
{
- let answer = cx
- .prompt(
- window_id,
- PromptLevel::Warning,
- "Do you want to leave the current call?",
- &["Close window and hang up", "Cancel"],
- )
- .next()
- .await;
+ let answer = cx.prompt(
+ window_id,
+ PromptLevel::Warning,
+ "Do you want to leave the current call?",
+ &["Close window and hang up", "Cancel"],
+ );
- if answer == Some(1) {
- return anyhow::Ok(false);
- } else {
- active_call
- .update(&mut cx, |call, cx| call.hang_up(cx))
- .await
- .log_err();
+ if let Some(mut answer) = answer {
+ if answer.next().await == Some(1) {
+ return anyhow::Ok(false);
+ } else {
+ active_call
+ .update(&mut cx, |call, cx| call.hang_up(cx))
+ .await
+ .log_err();
+ }
}
}
}
Ok(this
- .update(&mut cx, |this, cx| this.save_all_internal(true, cx))
+ .update(&mut cx, |this, cx| this.save_all_internal(true, cx))?
.await?)
})
}
@@ -1255,13 +1261,16 @@ impl Workspace {
cx.spawn(|this, mut cx| async move {
let mut project_paths = Vec::new();
for path in &abs_paths {
- project_paths.push(
- this.update(&mut cx, |this, cx| {
+ if let Some(project_path) = this
+ .update(&mut cx, |this, cx| {
Workspace::project_path_for_path(this.project.clone(), path, visible, cx)
})
- .await
- .log_err(),
- );
+ .log_err()
+ {
+ project_paths.push(project_path.await.log_err());
+ } else {
+ project_paths.push(None);
+ }
}
let tasks = abs_paths
@@ -1279,6 +1288,7 @@ impl Workspace {
this.update(&mut cx, |this, cx| {
this.open_path(project_path, None, true, cx)
})
+ .log_err()?
.await,
)
} else {
@@ -1302,14 +1312,15 @@ impl Workspace {
cx.spawn(|this, mut cx| async move {
if let Some(paths) = paths.recv().await.flatten() {
let results = this
- .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))
+ .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
.await;
for result in results.into_iter().flatten() {
result.log_err();
}
}
+ anyhow::Ok(())
})
- .detach();
+ .detach_and_log_err(cx);
}
fn remove_folder_from_project(
@@ -1350,7 +1361,7 @@ impl Workspace {
add_view: F,
) -> Option<ViewHandle<V>>
where
- V: 'static + View,
+ V: 'static + Modal,
F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
{
cx.notify();
@@ -1362,6 +1373,12 @@ impl Workspace {
Some(already_open_modal)
} else {
let modal = add_view(self, cx);
+ cx.subscribe(&modal, |this, _, event, cx| {
+ if V::dismiss_on_event(event) {
+ this.dismiss_modal(cx);
+ }
+ })
+ .detach();
cx.focus(&modal);
self.modal = Some(modal.into_any());
None
@@ -1425,15 +1442,16 @@ impl Workspace {
CONFLICT_MESSAGE,
&["Overwrite", "Cancel"],
);
- cx.spawn(|_, mut cx| async move {
+ cx.spawn(|this, mut cx| async move {
let answer = answer.recv().await;
if answer == Some(0) {
- cx.update(|cx| item.save(project, cx)).await?;
+ this.update(&mut cx, |this, cx| item.save(this.project.clone(), cx))?
+ .await?;
}
Ok(())
})
} else {
- item.save(project, cx)
+ item.save(self.project.clone(), cx)
}
} else if item.is_singleton(cx) {
let worktree = self.worktrees(cx).next();
@@ -1442,9 +1460,10 @@ impl Workspace {
.map_or(Path::new(""), |w| w.abs_path())
.to_path_buf();
let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
- cx.spawn(|_, mut cx| async move {
+ cx.spawn(|this, mut cx| async move {
if let Some(abs_path) = abs_path.recv().await.flatten() {
- cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
+ this.update(&mut cx, |_, cx| item.save_as(project, abs_path, cx))?
+ .await?;
}
Ok(())
})
@@ -1608,14 +1627,7 @@ impl Workspace {
.upgrade(&cx)
.ok_or_else(|| anyhow!("pane was closed"))?;
this.update(&mut cx, |this, cx| {
- Ok(Pane::open_item(
- this,
- pane,
- project_entry_id,
- focus_item,
- cx,
- build_item,
- ))
+ Pane::open_item(this, pane, project_entry_id, focus_item, cx, build_item)
})
})
}
@@ -1632,7 +1644,7 @@ impl Workspace {
> {
let project = self.project().clone();
let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
- cx.as_mut().spawn(|mut cx| async move {
+ cx.spawn(|_, mut cx| async move {
let (project_entry_id, project_item) = project_item.await?;
let build_item = cx.update(|cx| {
cx.default_global::<ProjectItemBuilders>()
@@ -1813,15 +1825,14 @@ impl Workspace {
}
let item = pane.read(cx).active_item()?;
- let maybe_pane_handle =
- if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) {
- let new_pane = self.add_pane(cx);
- Pane::add_item(self, &new_pane, clone, true, true, None, cx);
- self.center.split(&pane, &new_pane, direction).unwrap();
- Some(new_pane)
- } else {
- None
- };
+ let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
+ let new_pane = self.add_pane(cx);
+ Pane::add_item(self, &new_pane, clone, true, true, None, cx);
+ self.center.split(&pane, &new_pane, direction).unwrap();
+ Some(new_pane)
+ } else {
+ None
+ };
cx.notify();
maybe_pane_handle
}
@@ -1979,7 +1990,7 @@ impl Workspace {
None
};
Ok::<_, anyhow::Error>(())
- })?;
+ })??;
Self::add_views_from_leader(
this.clone(),
leader_id,
@@ -1988,7 +1999,7 @@ impl Workspace {
&mut cx,
)
.await?;
- this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx));
+ this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
}
Ok(())
}))
@@ -2064,10 +2075,10 @@ impl Workspace {
self.leader_state.followers.contains(&peer_id)
}
- fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> Element<Self> {
// TODO: There should be a better system in place for this
// (https://github.com/zed-industries/zed/issues/1290)
- let is_fullscreen = cx.window_is_fullscreen(cx.window_id());
+ let is_fullscreen = cx.window_is_fullscreen();
let container_theme = if is_fullscreen {
let mut container_theme = theme.workspace.titlebar.container;
container_theme.padding.left = container_theme.padding.right;
@@ -2078,7 +2089,7 @@ impl Workspace {
enum TitleBar {}
ConstrainedBox::new(
- MouseEventHandler::<TitleBar>::new(0, cx, |_, cx| {
+ MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
Container::new(
Stack::new()
.with_children(
@@ -2091,9 +2102,9 @@ impl Workspace {
.with_style(container_theme)
.boxed()
})
- .on_click(MouseButton::Left, |event, cx| {
+ .on_click(MouseButton::Left, |event, _, cx| {
if event.click_count == 2 {
- cx.zoom_window(cx.window_id());
+ cx.zoom_window();
}
})
.boxed(),
@@ -2164,11 +2175,14 @@ impl Workspace {
}
}
- fn render_disconnected_overlay(&self, cx: &mut RenderContext<Workspace>) -> Option<ElementBox> {
+ fn render_disconnected_overlay(
+ &self,
+ cx: &mut ViewContext<Workspace>,
+ ) -> Option<Element<Workspace>> {
if self.project.read(cx).is_read_only() {
enum DisconnectedOverlay {}
Some(
- MouseEventHandler::<DisconnectedOverlay>::new(0, cx, |_, cx| {
+ MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
let theme = &cx.global::<Settings>().theme;
Label::new(
"Your connection to the remote project has been lost.",
@@ -2192,7 +2206,7 @@ impl Workspace {
&self,
theme: &theme::Workspace,
cx: &AppContext,
- ) -> Option<ElementBox> {
+ ) -> Option<Element<Workspace>> {
if self.notifications.is_empty() {
None
} else {
@@ -2263,7 +2277,7 @@ impl Workspace {
})
.collect(),
})
- })
+ })?
}
async fn handle_unfollow(
@@ -2278,7 +2292,7 @@ impl Workspace {
.remove(&envelope.original_sender_id()?);
cx.notify();
Ok(())
- })
+ })?
}
async fn handle_update_followers(
@@ -2315,7 +2329,7 @@ impl Workspace {
}
}
anyhow::Ok(())
- })?;
+ })??;
}
proto::update_followers::Variant::UpdateView(update_view) => {
let variant = update_view
@@ -2336,7 +2350,7 @@ impl Workspace {
}
}
anyhow::Ok(())
- })?;
+ })??;
try_join_all(tasks).await.log_err();
}
proto::update_followers::Variant::CreateView(view) => {
@@ -2351,7 +2365,7 @@ impl Workspace {
Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
}
}
- this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
+ this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
Ok(())
}
@@ -2421,7 +2435,7 @@ impl Workspace {
}
Some(())
- });
+ })?;
}
Ok(())
}
@@ -2703,7 +2717,7 @@ impl Workspace {
&workspace,
&mut cx,
)
- .await;
+ .await?;
// Traverse the splits tree and add to things
let center_group = serialized_workspace
@@ -2755,13 +2769,14 @@ impl Workspace {
});
cx.notify();
- });
+ })?;
// Serialize ourself to make sure our timestamps and any pane / item changes are replicated
workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))
}
+ anyhow::Ok(())
})
- .detach();
+ .detach_and_log_err(cx);
}
#[cfg(any(test, feature = "test-support"))]
@@ -2771,8 +2786,8 @@ impl Workspace {
}
fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAppContext) {
- if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
- workspace.update(cx, |workspace, cx| {
+ workspace.update(cx, |workspace, cx| {
+ if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
workspace.show_notification_once(0, cx, |cx| {
cx.add_view(|_| {
MessageNotification::new(
@@ -2784,11 +2799,9 @@ fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAp
)
})
});
- });
- } else {
- let backup_path = (*db::BACKUP_DB_PATH).read();
- if let Some(backup_path) = &*backup_path {
- workspace.update(cx, |workspace, cx| {
+ } else {
+ let backup_path = (*db::BACKUP_DB_PATH).read();
+ if let Some(backup_path) = &*backup_path {
workspace.show_notification_once(0, cx, |cx| {
cx.add_view(|_| {
let backup_path = backup_path.to_string_lossy();
@@ -2806,9 +2819,9 @@ fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAp
)
})
});
- });
+ }
}
- }
+ }).log_err();
}
impl Entity for Workspace {
@@ -2820,7 +2833,7 @@ impl View for Workspace {
"Workspace"
}
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = cx.global::<Settings>().theme.clone();
Stack::new()
.with_child(
@@ -2836,11 +2849,11 @@ impl View for Workspace {
Some(
ChildView::new(&self.left_sidebar, cx)
.constrained()
- .dynamically(|constraint, cx| {
+ .dynamically(|constraint, _, cx| {
SizeConstraint::new(
Vector2F::new(20., constraint.min.y()),
Vector2F::new(
- cx.window_size.x() * 0.8,
+ cx.window_size().x() * 0.8,
constraint.max.y(),
),
)
@@ -2882,11 +2895,11 @@ impl View for Workspace {
Some(
ChildView::new(&self.right_sidebar, cx)
.constrained()
- .dynamically(|constraint, cx| {
+ .dynamically(|constraint, _, cx| {
SizeConstraint::new(
Vector2F::new(20., constraint.min.y()),
Vector2F::new(
- cx.window_size.x() * 0.8,
+ cx.window_size().x() * 0.8,
constraint.max.y(),
),
)
@@ -3006,12 +3019,21 @@ pub fn activate_workspace_for_project(
predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
) -> Option<ViewHandle<Workspace>> {
for window_id in cx.window_ids().collect::<Vec<_>>() {
- if let Some(workspace_handle) = cx.root_view(window_id)?.downcast_ref::<Workspace>() {
- let project = workspace_handle.read(cx).project.clone();
- if project.update(cx, &predicate) {
- cx.activate_window(window_id);
- return Some(workspace_handle.clone());
- }
+ let handle = cx
+ .update_window(window_id, |cx| {
+ if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
+ let project = workspace_handle.read(cx).project.clone();
+ if project.update(cx, &predicate) {
+ cx.activate_window();
+ return Some(workspace_handle.clone());
+ }
+ }
+ None
+ })
+ .flatten();
+
+ if handle.is_some() {
+ return handle;
}
}
None
@@ -3027,10 +3049,12 @@ pub fn open_paths(
app_state: &Arc<AppState>,
requesting_window_id: Option<usize>,
cx: &mut AppContext,
-) -> Task<(
- ViewHandle<Workspace>,
- Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
-)> {
+) -> Task<
+ Result<(
+ ViewHandle<Workspace>,
+ Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
+ )>,
+> {
log::info!("open paths {:?}", abs_paths);
// Open paths in existing workspace if possible
@@ -3041,14 +3065,14 @@ pub fn open_paths(
let abs_paths = abs_paths.to_vec();
cx.spawn(|mut cx| async move {
if let Some(existing) = existing {
- (
+ Ok((
existing.clone(),
existing
.update(&mut cx, |workspace, cx| {
workspace.open_paths(abs_paths, true, cx)
- })
+ })?
.await,
- )
+ ))
} else {
let contains_directory =
futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
@@ -3066,9 +3090,9 @@ pub fn open_paths(
if contains_directory {
workspace.toggle_sidebar(SidebarSide::Left, cx);
}
- });
+ })?;
- (workspace, items)
+ anyhow::Ok((workspace, items))
})
})
.await
@@ -3085,11 +3109,13 @@ pub fn open_new(
cx.spawn(|mut cx| async move {
let (workspace, opened_paths) = task.await;
- workspace.update(&mut cx, |workspace, cx| {
- if opened_paths.is_empty() {
- init(workspace, cx)
- }
- })
+ workspace
+ .update(&mut cx, |workspace, cx| {
+ if opened_paths.is_empty() {
+ init(workspace, cx)
+ }
+ })
+ .log_err();
})
}
@@ -3713,7 +3739,8 @@ mod tests {
.update(cx, |workspace, cx| {
Pane::go_back(workspace, Some(pane.clone()), cx)
})
- .await;
+ .await
+ .unwrap();
assert_eq!(*toolbar_notify_count.borrow(), 3);
pane.read_with(cx, |pane, _| {
@@ -545,7 +545,7 @@ async fn watch_themes(
.await
.log_err()?;
if output.status.success() {
- cx.update(|cx| theme_selector::ThemeSelector::reload(themes.clone(), cx))
+ cx.update(|cx| theme_selector::reload(themes.clone(), cx))
} else {
eprintln!(
"build script failed {}",
@@ -631,71 +631,87 @@ async fn handle_cli_connection(
} else {
paths
};
- let (workspace, items) = cx
- .update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
- .await;
let mut errored = false;
- let mut item_release_futures = Vec::new();
- cx.update(|cx| {
- for (item, path) in items.into_iter().zip(&paths) {
- match item {
- Some(Ok(item)) => {
- let released = oneshot::channel();
- item.on_release(
- cx,
- Box::new(move |_| {
- let _ = released.0.send(());
- }),
- )
- .detach();
- item_release_futures.push(released.1);
+ match cx
+ .update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
+ .await
+ {
+ Ok((workspace, items)) => {
+ let mut item_release_futures = Vec::new();
+ cx.update(|cx| {
+ for (item, path) in items.into_iter().zip(&paths) {
+ match item {
+ Some(Ok(item)) => {
+ let released = oneshot::channel();
+ item.on_release(
+ cx,
+ Box::new(move |_| {
+ let _ = released.0.send(());
+ }),
+ )
+ .detach();
+ item_release_futures.push(released.1);
+ }
+ Some(Err(err)) => {
+ responses
+ .send(CliResponse::Stderr {
+ message: format!(
+ "error opening {:?}: {}",
+ path, err
+ ),
+ })
+ .log_err();
+ errored = true;
+ }
+ None => {}
+ }
}
- Some(Err(err)) => {
- responses
- .send(CliResponse::Stderr {
- message: format!("error opening {:?}: {}", path, err),
- })
- .log_err();
- errored = true;
+ });
+
+ if wait {
+ let background = cx.background();
+ let wait = async move {
+ if paths.is_empty() {
+ let (done_tx, done_rx) = oneshot::channel();
+ let _subscription = cx.update(|cx| {
+ cx.observe_release(&workspace, move |_, _| {
+ let _ = done_tx.send(());
+ })
+ });
+ drop(workspace);
+ let _ = done_rx.await;
+ } else {
+ let _ =
+ futures::future::try_join_all(item_release_futures).await;
+ };
}
- None => {}
- }
- }
- });
-
- if wait {
- let background = cx.background();
- let wait = async move {
- if paths.is_empty() {
- let (done_tx, done_rx) = oneshot::channel();
- let _subscription = cx.update(|cx| {
- cx.observe_release(&workspace, move |_, _| {
- let _ = done_tx.send(());
- })
- });
- drop(workspace);
- let _ = done_rx.await;
- } else {
- let _ = futures::future::try_join_all(item_release_futures).await;
- };
- }
- .fuse();
- futures::pin_mut!(wait);
-
- loop {
- // Repeatedly check if CLI is still open to avoid wasting resources
- // waiting for files or workspaces to close.
- let mut timer = background.timer(Duration::from_secs(1)).fuse();
- futures::select_biased! {
- _ = wait => break,
- _ = timer => {
- if responses.send(CliResponse::Ping).is_err() {
- break;
+ .fuse();
+ futures::pin_mut!(wait);
+
+ loop {
+ // Repeatedly check if CLI is still open to avoid wasting resources
+ // waiting for files or workspaces to close.
+ let mut timer = background.timer(Duration::from_secs(1)).fuse();
+ futures::select_biased! {
+ _ = wait => break,
+ _ = timer => {
+ if responses.send(CliResponse::Ping).is_err() {
+ break;
+ }
+ }
}
}
}
}
+ Err(error) => {
+ errored = true;
+ responses
+ .send(CliResponse::Stderr {
+ message: format!("error opening {:?}: {}", paths, error),
+ })
+ .log_err();
+ }
}
responses
@@ -105,7 +105,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
.titlebar_item()
.and_then(|item| item.downcast::<CollabTitlebarItem>())
{
- cx.as_mut().defer(move |cx| {
+ cx.defer(move |_, cx| {
item.update(cx, |item, cx| {
item.toggle_contacts_popover(&Default::default(), cx);
});
@@ -247,10 +247,10 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
cx,
);
})
- })
- .await;
+ })?
+ .await
})
- .detach();
+ .detach_and_log_err(cx);
}
});
cx.add_action(
@@ -333,8 +333,7 @@ pub fn initialize_workspace(
auto_update::notify_of_any_new_update(cx.weak_handle(), cx);
- let window_id = cx.window_id();
- vim::observe_keystrokes(window_id, cx);
+ vim::observe_keystrokes(cx);
cx.on_window_should_close(|workspace, cx| {
if let Some(task) = workspace.close(&Default::default(), cx) {
@@ -380,17 +379,18 @@ fn restart(_: &Restart, cx: &mut gpui::AppContext) {
let should_confirm = cx.global::<Settings>().confirm_quit;
cx.spawn(|mut cx| async move {
if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
- let answer = cx
- .prompt(
- workspace.window_id(),
- PromptLevel::Info,
- "Are you sure you want to restart?",
- &["Restart", "Cancel"],
- )
- .next()
- .await;
- if answer != Some(0) {
- return Ok(());
+ let answer = cx.prompt(
+ workspace.window_id(),
+ PromptLevel::Info,
+ "Are you sure you want to restart?",
+ &["Restart", "Cancel"],
+ );
+
+ if let Some(mut answer) = answer {
+ let answer = answer.next().await;
+ if answer != Some(0) {
+ return Ok(());
+ }
}
}
@@ -399,7 +399,7 @@ fn restart(_: &Restart, cx: &mut gpui::AppContext) {
if !workspace
.update(&mut cx, |workspace, cx| {
workspace.prepare_to_close(true, cx)
- })
+ })?
.await?
{
return Ok(());
@@ -424,17 +424,18 @@ fn quit(_: &Quit, cx: &mut gpui::AppContext) {
let should_confirm = cx.global::<Settings>().confirm_quit;
cx.spawn(|mut cx| async move {
if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
- let answer = cx
- .prompt(
- workspace.window_id(),
- PromptLevel::Info,
- "Are you sure you want to quit?",
- &["Quit", "Cancel"],
- )
- .next()
- .await;
- if answer != Some(0) {
- return Ok(());
+ let answer = cx.prompt(
+ workspace.window_id(),
+ PromptLevel::Info,
+ "Are you sure you want to quit?",
+ &["Quit", "Cancel"],
+ );
+
+ if let Some(mut answer) = answer {
+ let answer = answer.next().await;
+ if answer != Some(0) {
+ return Ok(());
+ }
}
}
@@ -443,7 +444,7 @@ fn quit(_: &Quit, cx: &mut gpui::AppContext) {
if !workspace
.update(&mut cx, |workspace, cx| {
workspace.prepare_to_close(true, cx)
- })
+ })?
.await?
{
return Ok(());
@@ -480,8 +481,8 @@ fn open_config_file(
workspace.with_local_workspace(&app_state, cx, |workspace, cx| {
workspace.open_paths(vec![path.to_path_buf()], false, cx)
})
- })
- .await
+ })?
+ .await?
.await;
Ok::<_, anyhow::Error>(())
})
@@ -520,25 +521,25 @@ fn open_log_file(
.flat_map(|line| [line, "\n"])
.collect::<String>();
- workspace.update(&mut cx, |workspace, cx| {
- let project = workspace.project().clone();
- let buffer = project
- .update(cx, |project, cx| project.create_buffer("", None, cx))
- .expect("creating buffers on a local workspace always succeeds");
- buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx));
-
- let buffer = cx.add_model(|cx| {
- MultiBuffer::singleton(buffer, cx).with_title("Log".into())
- });
- workspace.add_item(
- Box::new(
- cx.add_view(|cx| {
+ workspace
+ .update(&mut cx, |workspace, cx| {
+ let project = workspace.project().clone();
+ let buffer = project
+ .update(cx, |project, cx| project.create_buffer("", None, cx))
+ .expect("creating buffers on a local workspace always succeeds");
+ buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx));
+
+ let buffer = cx.add_model(|cx| {
+ MultiBuffer::singleton(buffer, cx).with_title("Log".into())
+ });
+ workspace.add_item(
+ Box::new(cx.add_view(|cx| {
Editor::for_multibuffer(buffer, Some(project), cx)
- }),
- ),
- cx,
- );
- });
+ })),
+ cx,
+ );
+ })
+ .log_err();
}
})
.detach();
@@ -600,7 +601,7 @@ fn open_telemetry_log_file(
Box::new(cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
cx,
);
- });
+ }).log_err()?;
Some(())
})
@@ -641,10 +642,10 @@ fn open_bundled_file(
cx,
);
})
- })
- .await;
+ })?
+ .await
})
- .detach();
+ .detach_and_log_err(cx);
}
#[cfg(test)]
@@ -705,14 +706,16 @@ mod tests {
cx,
)
})
- .await;
+ .await
+ .unwrap();
assert_eq!(cx.window_ids().len(), 1);
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(cx.window_ids().len(), 1);
let workspace_1 = cx
- .root_view(cx.window_ids()[0])
+ .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
.unwrap()
.downcast::<Workspace>()
.unwrap();
@@ -730,7 +733,8 @@ mod tests {
cx,
)
})
- .await;
+ .await
+ .unwrap();
assert_eq!(cx.window_ids().len(), 2);
// Replace existing windows
@@ -743,15 +747,16 @@ mod tests {
cx,
)
})
- .await;
+ .await
+ .unwrap();
assert_eq!(cx.window_ids().len(), 2);
let workspace_1 = cx
- .root_view(window_id)
+ .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
.unwrap()
.clone()
.downcast::<Workspace>()
.unwrap();
- workspace_1.read_with(cx, |workspace, cx| {
+ workspace_1.update(cx, |workspace, cx| {
assert_eq!(
workspace
.worktrees(cx)
@@ -774,14 +779,14 @@ mod tests {
.await;
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(cx.window_ids().len(), 1);
// When opening the workspace, the window is not in a edited state.
let workspace = cx
- .root_view(cx.window_ids()[0])
+ .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
.unwrap()
- .clone()
.downcast::<Workspace>()
.unwrap();
let editor = workspace.read_with(cx, |workspace, cx| {
@@ -817,7 +822,8 @@ mod tests {
// Opening the buffer again doesn't impact the window's edited state.
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
- .await;
+ .await
+ .unwrap();
let editor = workspace.read_with(cx, |workspace, cx| {
workspace
.active_item(cx)
@@ -851,11 +857,11 @@ mod tests {
let window_id = *cx.window_ids().first().unwrap();
let workspace = cx
- .root_view(window_id)
+ .read_window(window_id, |cx| cx.root_view().clone())
.unwrap()
- .clone()
.downcast::<Workspace>()
.unwrap();
+
let editor = workspace.update(cx, |workspace, cx| {
workspace
.active_item(cx)
@@ -1019,12 +1025,11 @@ mod tests {
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
// Open a file within an existing worktree.
- cx.update(|cx| {
- workspace.update(cx, |view, cx| {
+ workspace
+ .update(cx, |view, cx| {
view.open_paths(vec!["/dir1/a.txt".into()], true, cx)
})
- })
- .await;
+ .await;
cx.read(|cx| {
assert_eq!(
workspace
@@ -1043,12 +1048,11 @@ mod tests {
});
// Open a file outside of any existing worktree.
- cx.update(|cx| {
- workspace.update(cx, |view, cx| {
+ workspace
+ .update(cx, |view, cx| {
view.open_paths(vec!["/dir2/b.txt".into()], true, cx)
})
- })
- .await;
+ .await;
cx.read(|cx| {
let worktree_roots = workspace
.read(cx)
@@ -1079,12 +1083,11 @@ mod tests {
});
// Ensure opening a directory and one of its children only adds one worktree.
- cx.update(|cx| {
- workspace.update(cx, |view, cx| {
+ workspace
+ .update(cx, |view, cx| {
view.open_paths(vec!["/dir3".into(), "/dir3/c.txt".into()], true, cx)
})
- })
- .await;
+ .await;
cx.read(|cx| {
let worktree_roots = workspace
.read(cx)
@@ -1115,12 +1118,11 @@ mod tests {
});
// Ensure opening invisibly a file outside an existing worktree adds a new, invisible worktree.
- cx.update(|cx| {
- workspace.update(cx, |view, cx| {
+ workspace
+ .update(cx, |view, cx| {
view.open_paths(vec!["/d.txt".into()], false, cx)
})
- })
- .await;
+ .await;
cx.read(|cx| {
let worktree_roots = workspace
.read(cx)
@@ -1178,19 +1180,18 @@ mod tests {
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
// Open a file within an existing worktree.
- cx.update(|cx| {
- workspace.update(cx, |view, cx| {
+ workspace
+ .update(cx, |view, cx| {
view.open_paths(vec![PathBuf::from("/root/a.txt")], true, cx)
})
- })
- .await;
+ .await;
let editor = cx.read(|cx| {
let pane = workspace.read(cx).active_pane().read(cx);
let item = pane.active_item().unwrap();
item.downcast::<Editor>().unwrap()
});
- cx.update(|cx| editor.update(cx, |editor, cx| editor.handle_input("x", cx)));
+ editor.update(cx, |editor, cx| editor.handle_input("x", cx));
app_state
.fs
.as_fake()
@@ -1487,7 +1488,8 @@ mod tests {
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file3.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1495,7 +1497,8 @@ mod tests {
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file2.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1503,7 +1506,8 @@ mod tests {
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file1.clone(), DisplayPoint::new(10, 0), 0.)
@@ -1511,7 +1515,8 @@ mod tests {
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file1.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1520,7 +1525,8 @@ mod tests {
// Go back one more time and ensure we don't navigate past the first item in the history.
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file1.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1528,7 +1534,8 @@ mod tests {
workspace
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file1.clone(), DisplayPoint::new(10, 0), 0.)
@@ -1536,7 +1543,8 @@ mod tests {
workspace
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file2.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1554,7 +1562,8 @@ mod tests {
.unwrap();
workspace
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file3.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1562,7 +1571,8 @@ mod tests {
workspace
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file3.clone(), DisplayPoint::new(16, 0), 12.5)
@@ -1570,7 +1580,8 @@ mod tests {
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file3.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1592,14 +1603,16 @@ mod tests {
.unwrap();
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file1.clone(), DisplayPoint::new(10, 0), 0.)
);
workspace
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file3.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1642,14 +1655,16 @@ mod tests {
});
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file1.clone(), DisplayPoint::new(2, 0), 0.)
);
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(
active_location(&workspace, cx),
(file1.clone(), DisplayPoint::new(3, 0), 0.)
@@ -1763,61 +1778,84 @@ mod tests {
// Reopen all the closed items, ensuring they are reopened in the same order
// in which they were closed.
- workspace.update(cx, Pane::reopen_closed_item).await;
+ workspace
+ .update(cx, Pane::reopen_closed_item)
+ .await
+ .unwrap();
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
- workspace.update(cx, Pane::reopen_closed_item).await;
+ workspace
+ .update(cx, Pane::reopen_closed_item)
+ .await
+ .unwrap();
assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
- workspace.update(cx, Pane::reopen_closed_item).await;
+ workspace
+ .update(cx, Pane::reopen_closed_item)
+ .await
+ .unwrap();
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
- workspace.update(cx, Pane::reopen_closed_item).await;
+ workspace
+ .update(cx, Pane::reopen_closed_item)
+ .await
+ .unwrap();
assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
// Reopening past the last closed item is a no-op.
- workspace.update(cx, Pane::reopen_closed_item).await;
+ workspace
+ .update(cx, Pane::reopen_closed_item)
+ .await
+ .unwrap();
assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
// Reopening closed items doesn't interfere with navigation history.
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
- .await;
+ .await
+ .unwrap();
assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
fn active_path(