Merge pull request #2372 from zed-industries/window_context_2

Give elements access to their parent views and simplify contexts
This commit is contained in:
Antonio Scandurra 2023-04-21 17:32:14 +02:00 committed by GitHub
commit 238ebafa48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
133 changed files with 8012 additions and 7772 deletions

1
Cargo.lock generated
View file

@ -3371,6 +3371,7 @@ dependencies = [
"project", "project",
"settings", "settings",
"theme", "theme",
"util",
"workspace", "workspace",
] ]

View file

@ -2,10 +2,10 @@ use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
use editor::Editor; use editor::Editor;
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
actions, actions, anyhow,
elements::*, elements::*,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
Action, AppContext, Entity, ModelHandle, RenderContext, View, ViewContext, ViewHandle, Action, AppContext, Entity, ModelHandle, View, ViewContext, ViewHandle,
}; };
use language::{LanguageRegistry, LanguageServerBinaryStatus}; use language::{LanguageRegistry, LanguageServerBinaryStatus};
use project::{LanguageServerProgress, Project}; use project::{LanguageServerProgress, Project};
@ -73,11 +73,12 @@ impl ActivityIndicator {
status: event, status: event,
}); });
cx.notify(); cx.notify();
}); })?;
} else { } else {
break; break;
} }
} }
anyhow::Ok(())
}) })
.detach(); .detach();
cx.observe(&project, |_, _, cx| cx.notify()).detach(); cx.observe(&project, |_, _, cx| cx.notify()).detach();
@ -172,7 +173,7 @@ impl ActivityIndicator {
.flatten() .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. // Show any language server has pending activity.
let mut pending_work = self.pending_language_server_work(cx); let mut pending_work = self.pending_language_server_work(cx);
if let Some(PendingWork { if let Some(PendingWork {
@ -314,14 +315,14 @@ impl View for ActivityIndicator {
"ActivityIndicator" "ActivityIndicator"
} }
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let Content { let Content {
icon, icon,
message, message,
action, action,
} = self.content_to_render(cx); } = 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 let theme = &cx
.global::<Settings>() .global::<Settings>()
.theme .theme
@ -361,7 +362,7 @@ impl View for ActivityIndicator {
if let Some(action) = action { if let Some(action) = action {
element = element element = element
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_any_action(action.boxed_clone()) cx.dispatch_any_action(action.boxed_clone())
}); });
} }

View file

@ -113,7 +113,7 @@ pub fn notify_of_any_new_update(
.read(cx) .read(cx)
.set_should_show_update_notification(false, cx) .set_should_show_update_notification(false, cx)
.detach_and_log_err(cx); .detach_and_log_err(cx);
}); })?;
} }
} }
anyhow::Ok(()) anyhow::Ok(())

View file

@ -2,7 +2,7 @@ use crate::ViewReleaseNotes;
use gpui::{ use gpui::{
elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text}, elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
platform::{AppVersion, CursorStyle, MouseButton}, platform::{AppVersion, CursorStyle, MouseButton},
Element, Entity, View, ViewContext, Drawable, Entity, View, ViewContext,
}; };
use menu::Cancel; use menu::Cancel;
use settings::Settings; use settings::Settings;
@ -26,13 +26,13 @@ impl View for UpdateNotification {
"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 = cx.global::<Settings>().theme.clone();
let theme = &theme.update_notification; let theme = &theme.update_notification;
let app_name = cx.global::<ReleaseChannel>().display_name(); let app_name = cx.global::<ReleaseChannel>().display_name();
MouseEventHandler::<ViewReleaseNotes>::new(0, cx, |state, cx| { MouseEventHandler::<ViewReleaseNotes, _>::new(0, cx, |state, cx| {
Flex::column() Flex::column()
.with_child( .with_child(
Flex::row() Flex::row()
@ -50,7 +50,7 @@ impl View for UpdateNotification {
.boxed(), .boxed(),
) )
.with_child( .with_child(
MouseEventHandler::<Cancel>::new(0, cx, |state, _| { MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state, false); let style = theme.dismiss_button.style_for(state, false);
Svg::new("icons/x_mark_8.svg") Svg::new("icons/x_mark_8.svg")
.with_color(style.color) .with_color(style.color)
@ -65,7 +65,9 @@ impl View for UpdateNotification {
.boxed() .boxed()
}) })
.with_padding(Padding::uniform(5.)) .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() .aligned()
.constrained() .constrained()
.with_height(cx.font_cache().line_height(theme.message.text.font_size)) .with_height(cx.font_cache().line_height(theme.message.text.font_size))
@ -87,7 +89,7 @@ impl View for UpdateNotification {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, cx| { .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(ViewReleaseNotes) cx.dispatch_action(ViewReleaseNotes)
}) })
.boxed() .boxed()

View file

@ -1,6 +1,6 @@
use gpui::{ use gpui::{
elements::*, platform::MouseButton, AppContext, Entity, RenderContext, Subscription, View, elements::*, platform::MouseButton, AppContext, Entity, Subscription, View, ViewContext,
ViewContext, ViewHandle, ViewHandle,
}; };
use itertools::Itertools; use itertools::Itertools;
use search::ProjectSearchView; use search::ProjectSearchView;
@ -41,7 +41,7 @@ impl View for Breadcrumbs {
"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 { let active_item = match &self.active_item {
Some(active_item) => active_item, Some(active_item) => active_item,
None => return Empty::new().boxed(), None => return Empty::new().boxed(),
@ -54,10 +54,22 @@ impl View for Breadcrumbs {
let breadcrumbs = match active_item.breadcrumbs(&theme, cx) { let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
Some(breadcrumbs) => breadcrumbs, Some(breadcrumbs) => breadcrumbs,
None => return Empty::new().boxed(), 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() 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() Label::new("", style.default.text.clone()).boxed()
})) }))
.constrained() .constrained()
@ -72,14 +84,14 @@ impl View for Breadcrumbs {
.boxed(); .boxed();
} }
MouseEventHandler::<Breadcrumbs>::new(0, cx, |state, _| { MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
let style = style.style_for(state, false); let style = style.style_for(state, false);
crumbs.with_style(style.container).boxed() crumbs.with_style(style.container).boxed()
}) })
.on_click(MouseButton::Left, |_, cx| { .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(outline::Toggle); cx.dispatch_action(outline::Toggle);
}) })
.with_tooltip::<Breadcrumbs, _>( .with_tooltip::<Breadcrumbs>(
0, 0,
"Show symbol outline".to_owned(), "Show symbol outline".to_owned(),
Some(Box::new(outline::Toggle)), 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; self.pane_focused = pane_focused;
} }
} }

View file

@ -1470,7 +1470,8 @@ async fn test_host_disconnect(
deterministic.run_until_parked(); deterministic.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared())); 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 let editor_b = workspace_b
.update(cx_b, |workspace, cx| { .update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, cx) workspace.open_path((worktree_id, "b.txt"), None, true, cx)
@ -1479,12 +1480,9 @@ async fn test_host_disconnect(
.unwrap() .unwrap()
.downcast::<Editor>() .downcast::<Editor>()
.unwrap(); .unwrap();
cx_b.read(|cx| { assert!(cx_b
assert_eq!( .read_window(window_id_b, |cx| editor_b.is_focused(cx))
cx.focused_view_id(workspace_b.window_id()), .unwrap());
Some(editor_b.id())
);
});
editor_b.update(cx_b, |editor, cx| editor.insert("X", cx)); editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
assert!(cx_b.is_window_edited(workspace_b.window_id())); 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())); 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. // Ensure client B's edited state is reset and that the whole window is blurred.
cx_b.read(|cx| { cx_b.read_window(window_id_b, |cx| {
assert_eq!(cx.focused_view_id(workspace_b.window_id()), None); assert_eq!(cx.focused_view_id(), None);
}); });
assert!(!cx_b.is_window_edited(workspace_b.window_id())); assert!(!cx_b.is_window_edited(workspace_b.window_id()));
@ -6228,7 +6226,8 @@ async fn test_basic_following(
.update(cx_a, |workspace, cx| { .update(cx_a, |workspace, cx| {
workspace::Pane::go_back(workspace, None, cx) workspace::Pane::go_back(workspace, None, cx)
}) })
.await; .await
.unwrap();
deterministic.run_until_parked(); deterministic.run_until_parked();
workspace_b.read_with(cx_b, |workspace, cx| { workspace_b.read_with(cx_b, |workspace, cx| {
assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id()); 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| { .update(cx_a, |workspace, cx| {
workspace::Pane::go_back(workspace, None, cx) workspace::Pane::go_back(workspace, None, cx)
}) })
.await; .await
.unwrap();
deterministic.run_until_parked(); deterministic.run_until_parked();
workspace_b.read_with(cx_b, |workspace, cx| { workspace_b.read_with(cx_b, |workspace, cx| {
assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b2.id()); 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| { .update(cx_a, |workspace, cx| {
workspace::Pane::go_forward(workspace, None, cx) workspace::Pane::go_forward(workspace, None, cx)
}) })
.await; .await
.unwrap();
deterministic.run_until_parked(); deterministic.run_until_parked();
workspace_b.read_with(cx_b, |workspace, cx| { workspace_b.read_with(cx_b, |workspace, cx| {
assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id()); assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());

View file

@ -16,7 +16,7 @@ use gpui::{
impl_internal_actions, impl_internal_actions,
json::{self, ToJson}, json::{self, ToJson},
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
AppContext, Entity, ImageData, ModelHandle, RenderContext, Subscription, View, ViewContext, AppContext, Entity, ImageData, ModelHandle, SceneBuilder, Subscription, View, ViewContext,
ViewHandle, WeakViewHandle, ViewHandle, WeakViewHandle,
}; };
use settings::Settings; use settings::Settings;
@ -68,7 +68,7 @@ impl View for CollabTitlebarItem {
"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) { let workspace = if let Some(workspace) = self.workspace.upgrade(cx) {
workspace workspace
} else { } else {
@ -325,8 +325,8 @@ impl CollabTitlebarItem {
fn render_toggle_contacts_button( fn render_toggle_contacts_button(
&self, &self,
theme: &Theme, theme: &Theme,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let titlebar = &theme.workspace.titlebar; let titlebar = &theme.workspace.titlebar;
let badge = if self let badge = if self
@ -352,7 +352,7 @@ impl CollabTitlebarItem {
Stack::new() Stack::new()
.with_child( .with_child(
MouseEventHandler::<ToggleContactsMenu>::new(0, cx, |state, _| { MouseEventHandler::<ToggleContactsMenu, Self>::new(0, cx, |state, _| {
let style = titlebar let style = titlebar
.toggle_contacts_button .toggle_contacts_button
.style_for(state, self.contacts_popover.is_some()); .style_for(state, self.contacts_popover.is_some());
@ -369,10 +369,10 @@ impl CollabTitlebarItem {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ToggleContactsMenu); cx.dispatch_action(ToggleContactsMenu);
}) })
.with_tooltip::<ToggleContactsMenu, _>( .with_tooltip::<ToggleContactsMenu>(
0, 0,
"Show contacts menu".into(), "Show contacts menu".into(),
Some(Box::new(ToggleContactsMenu)), Some(Box::new(ToggleContactsMenu)),
@ -390,8 +390,8 @@ impl CollabTitlebarItem {
&self, &self,
theme: &Theme, theme: &Theme,
room: &ModelHandle<Room>, room: &ModelHandle<Room>,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let icon; let icon;
let tooltip; let tooltip;
if room.read(cx).is_screen_sharing() { if room.read(cx).is_screen_sharing() {
@ -403,7 +403,7 @@ impl CollabTitlebarItem {
} }
let titlebar = &theme.workspace.titlebar; 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); let style = titlebar.call_control.style_for(state, false);
Svg::new(icon) Svg::new(icon)
.with_color(style.color) .with_color(style.color)
@ -418,10 +418,10 @@ impl CollabTitlebarItem {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ToggleScreenSharing); cx.dispatch_action(ToggleScreenSharing);
}) })
.with_tooltip::<ToggleScreenSharing, _>( .with_tooltip::<ToggleScreenSharing>(
0, 0,
tooltip.into(), tooltip.into(),
Some(Box::new(ToggleScreenSharing)), Some(Box::new(ToggleScreenSharing)),
@ -436,8 +436,8 @@ impl CollabTitlebarItem {
&self, &self,
workspace: &ViewHandle<Workspace>, workspace: &ViewHandle<Workspace>,
theme: &Theme, theme: &Theme,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<ElementBox> { ) -> Option<Element<Self>> {
let project = workspace.read(cx).project(); let project = workspace.read(cx).project();
if project.read(cx).is_remote() { if project.read(cx).is_remote() {
return None; return None;
@ -457,7 +457,7 @@ impl CollabTitlebarItem {
Some( Some(
Stack::new() Stack::new()
.with_child( .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 //TODO: Ensure this button has consistant width for both text variations
let style = titlebar.share_button.style_for(state, false); let style = titlebar.share_button.style_for(state, false);
Label::new(label, style.text.clone()) Label::new(label, style.text.clone())
@ -466,14 +466,14 @@ impl CollabTitlebarItem {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
if is_shared { if is_shared {
cx.dispatch_action(UnshareProject); cx.dispatch_action(UnshareProject);
} else { } else {
cx.dispatch_action(ShareProject); cx.dispatch_action(ShareProject);
} }
}) })
.with_tooltip::<ShareUnshare, _>( .with_tooltip::<ShareUnshare>(
0, 0,
tooltip.to_owned(), tooltip.to_owned(),
None, 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; let titlebar = &theme.workspace.titlebar;
Stack::new() Stack::new()
.with_child( .with_child(
MouseEventHandler::<ToggleUserMenu>::new(0, cx, |state, _| { MouseEventHandler::<ToggleUserMenu, Self>::new(0, cx, |state, _| {
let style = titlebar.call_control.style_for(state, false); let style = titlebar.call_control.style_for(state, false);
Svg::new("icons/ellipsis_14.svg") Svg::new("icons/ellipsis_14.svg")
.with_color(style.color) .with_color(style.color)
@ -509,10 +509,10 @@ impl CollabTitlebarItem {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ToggleUserMenu); cx.dispatch_action(ToggleUserMenu);
}) })
.with_tooltip::<ToggleUserMenu, _>( .with_tooltip::<ToggleUserMenu>(
0, 0,
"Toggle user menu".to_owned(), "Toggle user menu".to_owned(),
Some(Box::new(ToggleUserMenu)), Some(Box::new(ToggleUserMenu)),
@ -533,9 +533,9 @@ impl CollabTitlebarItem {
.boxed() .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; 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); let style = titlebar.sign_in_prompt.style_for(state, false);
Label::new("Sign In", style.text.clone()) Label::new("Sign In", style.text.clone())
.contained() .contained()
@ -543,7 +543,7 @@ impl CollabTitlebarItem {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(SignIn); cx.dispatch_action(SignIn);
}) })
.boxed() .boxed()
@ -552,8 +552,8 @@ impl CollabTitlebarItem {
fn render_contacts_popover_host<'a>( fn render_contacts_popover_host<'a>(
&'a self, &'a self,
_theme: &'a theme::Titlebar, _theme: &'a theme::Titlebar,
cx: &'a RenderContext<Self>, cx: &'a ViewContext<Self>,
) -> Option<ElementBox> { ) -> Option<Element<Self>> {
self.contacts_popover.as_ref().map(|popover| { self.contacts_popover.as_ref().map(|popover| {
Overlay::new(ChildView::new(popover, cx).boxed()) Overlay::new(ChildView::new(popover, cx).boxed())
.with_fit_mode(OverlayFitMode::SwitchAnchor) .with_fit_mode(OverlayFitMode::SwitchAnchor)
@ -571,8 +571,8 @@ impl CollabTitlebarItem {
workspace: &ViewHandle<Workspace>, workspace: &ViewHandle<Workspace>,
theme: &Theme, theme: &Theme,
room: &ModelHandle<Room>, room: &ModelHandle<Room>,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> Vec<ElementBox> { ) -> Vec<Element<Self>> {
let mut participants = room let mut participants = room
.read(cx) .read(cx)
.remote_participants() .remote_participants()
@ -613,8 +613,8 @@ impl CollabTitlebarItem {
theme: &Theme, theme: &Theme,
user: &Arc<User>, user: &Arc<User>,
peer_id: PeerId, peer_id: PeerId,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let replica_id = workspace.read(cx).project().read(cx).replica_id(); let replica_id = workspace.read(cx).project().read(cx).replica_id();
Container::new(self.render_face_pile( Container::new(self.render_face_pile(
user, user,
@ -637,8 +637,8 @@ impl CollabTitlebarItem {
location: Option<ParticipantLocation>, location: Option<ParticipantLocation>,
workspace: &ViewHandle<Workspace>, workspace: &ViewHandle<Workspace>,
theme: &Theme, theme: &Theme,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let project_id = workspace.read(cx).project().read(cx).remote_id(); let project_id = workspace.read(cx).project().read(cx).remote_id();
let room = ActiveCall::global(cx).read(cx).room(); let room = ActiveCall::global(cx).read(cx).room();
let is_being_followed = workspace.read(cx).is_being_followed(peer_id); 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(location) = location {
if let Some(replica_id) = replica_id { if let Some(replica_id) = replica_id {
content = content = MouseEventHandler::<ToggleFollow, Self>::new(
MouseEventHandler::<ToggleFollow>::new(replica_id.into(), cx, move |_, _| { replica_id.into(),
content cx,
}) move |_, _| content,
.with_cursor_style(CursorStyle::PointingHand) )
.on_click(MouseButton::Left, move |_, cx| { .with_cursor_style(CursorStyle::PointingHand)
cx.dispatch_action(ToggleFollow(peer_id)) .on_click(MouseButton::Left, move |_, _, cx| {
}) cx.dispatch_action(ToggleFollow(peer_id))
.with_tooltip::<ToggleFollow, _>( })
peer_id.as_u64() as usize, .with_tooltip::<ToggleFollow>(
if is_being_followed { peer_id.as_u64() as usize,
format!("Unfollow {}", user.github_login) if is_being_followed {
} else { format!("Unfollow {}", user.github_login)
format!("Follow {}", user.github_login) } else {
}, format!("Follow {}", user.github_login)
Some(Box::new(FollowNextCollaborator)), },
theme.tooltip.clone(), Some(Box::new(FollowNextCollaborator)),
cx, theme.tooltip.clone(),
) cx,
.boxed(); )
.boxed();
} else if let ParticipantLocation::SharedProject { project_id } = location { } else if let ParticipantLocation::SharedProject { project_id } = location {
let user_id = user.id; let user_id = user.id;
content = MouseEventHandler::<JoinProject>::new( content = MouseEventHandler::<JoinProject, Self>::new(
peer_id.as_u64() as usize, peer_id.as_u64() as usize,
cx, cx,
move |_, _| content, move |_, _| content,
) )
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(JoinProject { cx.dispatch_action(JoinProject {
project_id, project_id,
follow_user_id: user_id, follow_user_id: user_id,
}) })
}) })
.with_tooltip::<JoinProject, _>( .with_tooltip::<JoinProject>(
peer_id.as_u64() as usize, peer_id.as_u64() as usize,
format!("Follow {} into external project", user.github_login), format!("Follow {} into external project", user.github_login),
Some(Box::new(FollowNextCollaborator)), Some(Box::new(FollowNextCollaborator)),
@ -800,7 +801,7 @@ impl CollabTitlebarItem {
workspace: &ViewHandle<Workspace>, workspace: &ViewHandle<Workspace>,
location: Option<ParticipantLocation>, location: Option<ParticipantLocation>,
mut style: AvatarStyle, mut style: AvatarStyle,
cx: &RenderContext<Self>, cx: &ViewContext<Self>,
) -> AvatarStyle { ) -> AvatarStyle {
if let Some(location) = location { if let Some(location) = location {
if let ParticipantLocation::SharedProject { project_id } = location { if let ParticipantLocation::SharedProject { project_id } = location {
@ -815,11 +816,11 @@ impl CollabTitlebarItem {
style style
} }
fn render_face( fn render_face<V: View>(
avatar: Arc<ImageData>, avatar: Arc<ImageData>,
avatar_style: AvatarStyle, avatar_style: AvatarStyle,
background_color: Color, background_color: Color,
) -> ElementBox { ) -> Element<V> {
Image::from_data(avatar) Image::from_data(avatar)
.with_style(avatar_style.image) .with_style(avatar_style.image)
.aligned() .aligned()
@ -836,8 +837,8 @@ impl CollabTitlebarItem {
fn render_connection_status( fn render_connection_status(
&self, &self,
status: &client::Status, status: &client::Status,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<ElementBox> { ) -> Option<Element<Self>> {
enum ConnectionStatusButton {} enum ConnectionStatusButton {}
let theme = &cx.global::<Settings>().theme.clone(); let theme = &cx.global::<Settings>().theme.clone();
@ -863,7 +864,7 @@ impl CollabTitlebarItem {
.boxed(), .boxed(),
), ),
client::Status::UpgradeRequired => Some( client::Status::UpgradeRequired => Some(
MouseEventHandler::<ConnectionStatusButton>::new(0, cx, |_, _| { MouseEventHandler::<ConnectionStatusButton, Self>::new(0, cx, |_, _| {
Label::new( Label::new(
"Please update Zed to collaborate", "Please update Zed to collaborate",
theme.workspace.titlebar.outdated_warning.text.clone(), theme.workspace.titlebar.outdated_warning.text.clone(),
@ -874,7 +875,7 @@ impl CollabTitlebarItem {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, cx| { .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(auto_update::Check); cx.dispatch_action(auto_update::Check);
}) })
.boxed(), .boxed(),
@ -894,7 +895,7 @@ impl AvatarRibbon {
} }
} }
impl Element for AvatarRibbon { impl Drawable<CollabTitlebarItem> for AvatarRibbon {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
@ -902,17 +903,20 @@ impl Element for AvatarRibbon {
fn layout( fn layout(
&mut self, &mut self,
constraint: gpui::SizeConstraint, constraint: gpui::SizeConstraint,
_: &mut gpui::LayoutContext, _: &mut CollabTitlebarItem,
_: &mut ViewContext<CollabTitlebarItem>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
(constraint.max, ()) (constraint.max, ())
} }
fn paint( fn paint(
&mut self, &mut self,
bounds: gpui::geometry::rect::RectF, scene: &mut SceneBuilder,
_: gpui::geometry::rect::RectF, bounds: RectF,
_: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
cx: &mut gpui::PaintContext, _: &mut CollabTitlebarItem,
_: &mut ViewContext<CollabTitlebarItem>,
) -> Self::PaintState { ) -> Self::PaintState {
let mut path = PathBuilder::new(); let mut path = PathBuilder::new();
path.reset(bounds.lower_left()); path.reset(bounds.lower_left());
@ -923,7 +927,7 @@ impl Element for AvatarRibbon {
path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.)); path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
path.curve_to(bounds.lower_right(), bounds.upper_right()); path.curve_to(bounds.lower_right(), bounds.upper_right());
path.line_to(bounds.lower_left()); 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( fn rect_for_text_range(
@ -933,17 +937,19 @@ impl Element for AvatarRibbon {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &gpui::MeasurementContext, _: &CollabTitlebarItem,
_: &ViewContext<CollabTitlebarItem>,
) -> Option<RectF> { ) -> Option<RectF> {
None None
} }
fn debug( fn debug(
&self, &self,
bounds: gpui::geometry::rect::RectF, bounds: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &gpui::DebugContext, _: &CollabTitlebarItem,
_: &ViewContext<CollabTitlebarItem>,
) -> gpui::json::Value { ) -> gpui::json::Value {
json::json!({ json::json!({
"type": "AvatarRibbon", "type": "AvatarRibbon",

View file

@ -126,7 +126,7 @@ fn join_project(action: &JoinProject, app_state: Arc<AppState>, cx: &mut AppCont
} }
} }
} }
}); })?;
anyhow::Ok(()) anyhow::Ok(())
}) })

View file

@ -1,10 +1,7 @@
use call::ActiveCall; use call::ActiveCall;
use client::UserStore; use client::UserStore;
use gpui::Action; use gpui::Action;
use gpui::{ use gpui::{actions, elements::*, platform::MouseButton, Entity, ModelHandle, View, ViewContext};
actions, elements::*, platform::MouseButton, Entity, ModelHandle, RenderContext, View,
ViewContext,
};
use settings::Settings; use settings::Settings;
use crate::collab_titlebar_item::ToggleCollaboratorList; use crate::collab_titlebar_item::ToggleCollaboratorList;
@ -21,7 +18,7 @@ enum Collaborator {
actions!(collaborator_list_popover, [NoOp]); actions!(collaborator_list_popover, [NoOp]);
pub(crate) struct CollaboratorListPopover { pub(crate) struct CollaboratorListPopover {
list_state: ListState, list_state: ListState<Self>,
} }
impl Entity for CollaboratorListPopover { impl Entity for CollaboratorListPopover {
@ -33,10 +30,10 @@ impl View for CollaboratorListPopover {
"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(); let theme = cx.global::<Settings>().theme.clone();
MouseEventHandler::<Self>::new(0, cx, |_, _| { MouseEventHandler::<Self, Self>::new(0, cx, |_, _| {
List::new(self.list_state.clone()) List::new(self.list_state.clone())
.contained() .contained()
.with_style(theme.contacts_popover.container) //TODO: Change the name of this theme key .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) .with_height(theme.contacts_popover.height)
.boxed() .boxed()
}) })
.on_down_out(MouseButton::Left, move |_, cx| { .on_down_out(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ToggleCollaboratorList); cx.dispatch_action(ToggleCollaboratorList);
}) })
.boxed() .boxed()
@ -83,7 +80,6 @@ impl CollaboratorListPopover {
collaborators.len(), collaborators.len(),
Orientation::Top, Orientation::Top,
0., 0.,
cx,
move |_, index, cx| match &collaborators[index] { move |_, index, cx| match &collaborators[index] {
Collaborator::SelfUser { username } => render_collaborator_list_entry( Collaborator::SelfUser { username } => render_collaborator_list_entry(
index, index,
@ -120,8 +116,8 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
icon: Svg, icon: Svg,
icon_action: IA, icon_action: IA,
icon_tooltip: String, icon_tooltip: String,
cx: &mut RenderContext<CollaboratorListPopover>, cx: &mut ViewContext<CollaboratorListPopover>,
) -> ElementBox { ) -> Element<CollaboratorListPopover> {
enum Username {} enum Username {}
enum UsernameTooltip {} enum UsernameTooltip {}
enum Icon {} 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 username_theme = theme.contact_list.contact_username.text.clone();
let tooltip_theme = theme.tooltip.clone(); let tooltip_theme = theme.tooltip.clone();
let username = MouseEventHandler::<Username>::new(index, cx, |_, _| { let username =
Label::new(username.to_owned(), username_theme.clone()).boxed() 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() { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(username_action); if let Some(username_action) = username_action.clone() {
} cx.dispatch_action(username_action);
}); }
});
Flex::row() Flex::row()
.with_child(if let Some(username_tooltip) = username_tooltip { .with_child(if let Some(username_tooltip) = username_tooltip {
username username
.with_tooltip::<UsernameTooltip, _>( .with_tooltip::<UsernameTooltip>(
index, index,
username_tooltip, username_tooltip,
None, None,
@ -155,11 +152,11 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
username.boxed() username.boxed()
}) })
.with_child( .with_child(
MouseEventHandler::<Icon>::new(index, cx, |_, _| icon.boxed()) MouseEventHandler::<Icon, CollaboratorListPopover>::new(index, cx, |_, _| icon.boxed())
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(icon_action.clone()) 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(),
) )
.boxed() .boxed()

View file

@ -1,49 +1,41 @@
use client::{ContactRequestStatus, User, UserStore}; use client::{ContactRequestStatus, User, UserStore};
use gpui::{ use gpui::{elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext};
elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, RenderContext, Task, use picker::{Picker, PickerDelegate, PickerEvent};
View, ViewContext, ViewHandle,
};
use picker::{Picker, PickerDelegate};
use settings::Settings; use settings::Settings;
use std::sync::Arc; use std::sync::Arc;
use util::TryFutureExt; use util::TryFutureExt;
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
Picker::<ContactFinder>::init(cx); Picker::<ContactFinderDelegate>::init(cx);
} }
pub struct ContactFinder { pub type ContactFinder = Picker<ContactFinderDelegate>;
picker: ViewHandle<Picker<Self>>,
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,
)
}
pub struct ContactFinderDelegate {
potential_contacts: Arc<[Arc<User>]>, potential_contacts: Arc<[Arc<User>]>,
user_store: ModelHandle<UserStore>, user_store: ModelHandle<UserStore>,
selected_index: usize, selected_index: usize,
} }
pub enum Event { impl PickerDelegate for ContactFinderDelegate {
Dismissed, fn placeholder_text(&self) -> Arc<str> {
} "Search collaborator by username...".into()
impl Entity for ContactFinder {
type Event = Event;
}
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 ContactFinder {
fn match_count(&self) -> usize { fn match_count(&self) -> usize {
self.potential_contacts.len() self.potential_contacts.len()
} }
@ -52,22 +44,22 @@ impl PickerDelegate for ContactFinder {
self.selected_index 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; 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 let search_users = self
.user_store .user_store
.update(cx, |store, cx| store.fuzzy_search_users(query, cx)); .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 { async {
let potential_contacts = search_users.await?; let potential_contacts = search_users.await?;
this.update(&mut cx, |this, cx| { picker.update(&mut cx, |picker, cx| {
this.potential_contacts = potential_contacts.into(); picker.delegate_mut().potential_contacts = potential_contacts.into();
cx.notify(); cx.notify();
}); })?;
anyhow::Ok(()) anyhow::Ok(())
} }
.log_err() .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) { if let Some(user) = self.potential_contacts.get(self.selected_index) {
let user_store = self.user_store.read(cx); let user_store = self.user_store.read(cx);
match user_store.contact_request_status(user) { match user_store.contact_request_status(user) {
@ -94,8 +86,8 @@ impl PickerDelegate for ContactFinder {
} }
} }
fn dismiss(&mut self, cx: &mut ViewContext<Self>) { fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
cx.emit(Event::Dismissed); cx.emit(PickerEvent::Dismiss);
} }
fn render_match( fn render_match(
@ -104,7 +96,7 @@ impl PickerDelegate for ContactFinder {
mouse_state: &mut MouseState, mouse_state: &mut MouseState,
selected: bool, selected: bool,
cx: &gpui::AppContext, cx: &gpui::AppContext,
) -> ElementBox { ) -> Element<Picker<Self>> {
let theme = &cx.global::<Settings>().theme; let theme = &cx.global::<Settings>().theme;
let user = &self.potential_contacts[ix]; let user = &self.potential_contacts[ix];
let request_status = self.user_store.read(cx).contact_request_status(user); let request_status = self.user_store.read(cx).contact_request_status(user);
@ -164,28 +156,3 @@ impl PickerDelegate for ContactFinder {
.boxed() .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
}
}

View file

@ -11,7 +11,7 @@ use gpui::{
impl_actions, impl_internal_actions, impl_actions, impl_internal_actions,
keymap_matcher::KeymapContext, keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, PromptLevel}, 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 menu::{Confirm, SelectNext, SelectPrev};
use project::Project; use project::Project;
@ -159,7 +159,7 @@ pub enum Event {
pub struct ContactList { pub struct ContactList {
entries: Vec<ContactEntry>, entries: Vec<ContactEntry>,
match_candidates: Vec<StringMatchCandidate>, match_candidates: Vec<StringMatchCandidate>,
list_state: ListState, list_state: ListState<Self>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
user_store: ModelHandle<UserStore>, user_store: ModelHandle<UserStore>,
filter_editor: ViewHandle<Editor>, filter_editor: ViewHandle<Editor>,
@ -202,7 +202,7 @@ impl ContactList {
}) })
.detach(); .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 theme = cx.global::<Settings>().theme.clone();
let is_selected = this.selection == Some(ix); let is_selected = this.selection == Some(ix);
let current_project_id = this.project.read(cx).remote_id(); let current_project_id = this.project.read(cx).remote_id();
@ -748,7 +748,7 @@ impl ContactList {
is_pending: bool, is_pending: bool,
is_selected: bool, is_selected: bool,
theme: &theme::ContactList, theme: &theme::ContactList,
) -> ElementBox { ) -> Element<Self> {
Flex::row() Flex::row()
.with_children(user.avatar.clone().map(|avatar| { .with_children(user.avatar.clone().map(|avatar| {
Image::from_data(avatar) Image::from_data(avatar)
@ -799,8 +799,8 @@ impl ContactList {
is_last: bool, is_last: bool,
is_selected: bool, is_selected: bool,
theme: &theme::ContactList, theme: &theme::ContactList,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let font_cache = cx.font_cache(); let font_cache = cx.font_cache();
let host_avatar_height = theme let host_avatar_height = theme
.contact_avatar .contact_avatar
@ -819,7 +819,7 @@ impl ContactList {
worktree_root_names.join(", ") 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 tree_branch = *tree_branch.style_for(mouse_state, is_selected);
let row = theme.project_row.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( .with_child(
Stack::new() Stack::new()
.with_child( .with_child(
Canvas::new(move |bounds, _, cx| { Canvas::new(move |scene, bounds, _, _, _| {
let start_x = bounds.min_x() + (bounds.width() / 2.) let start_x = bounds.min_x() + (bounds.width() / 2.)
- (tree_branch.width / 2.); - (tree_branch.width / 2.);
let end_x = bounds.max_x(); let end_x = bounds.max_x();
let start_y = bounds.min_y(); let start_y = bounds.min_y();
let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.); 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( bounds: RectF::from_points(
vec2f(start_x, start_y), vec2f(start_x, start_y),
vec2f( vec2f(
@ -846,7 +846,7 @@ impl ContactList {
border: gpui::Border::default(), border: gpui::Border::default(),
corner_radius: 0., corner_radius: 0.,
}); });
cx.scene.push_quad(gpui::Quad { scene.push_quad(gpui::Quad {
bounds: RectF::from_points( bounds: RectF::from_points(
vec2f(start_x, end_y), vec2f(start_x, end_y),
vec2f(end_x, end_y + tree_branch.width), vec2f(end_x, end_y + tree_branch.width),
@ -882,7 +882,7 @@ impl ContactList {
} else { } else {
CursorStyle::Arrow CursorStyle::Arrow
}) })
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
if !is_current { if !is_current {
cx.dispatch_global_action(JoinProject { cx.dispatch_global_action(JoinProject {
project_id, project_id,
@ -898,8 +898,8 @@ impl ContactList {
is_last: bool, is_last: bool,
is_selected: bool, is_selected: bool,
theme: &theme::ContactList, theme: &theme::ContactList,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let font_cache = cx.font_cache(); let font_cache = cx.font_cache();
let host_avatar_height = theme let host_avatar_height = theme
.contact_avatar .contact_avatar
@ -913,7 +913,7 @@ impl ContactList {
let baseline_offset = let baseline_offset =
row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.; 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, peer_id.as_u64() as usize,
cx, cx,
|mouse_state, _| { |mouse_state, _| {
@ -924,7 +924,7 @@ impl ContactList {
.with_child( .with_child(
Stack::new() Stack::new()
.with_child( .with_child(
Canvas::new(move |bounds, _, cx| { Canvas::new(move |scene, bounds, _, _, _| {
let start_x = bounds.min_x() + (bounds.width() / 2.) let start_x = bounds.min_x() + (bounds.width() / 2.)
- (tree_branch.width / 2.); - (tree_branch.width / 2.);
let end_x = bounds.max_x(); let end_x = bounds.max_x();
@ -932,7 +932,7 @@ impl ContactList {
let end_y = let end_y =
bounds.min_y() + baseline_offset - (cap_height / 2.); bounds.min_y() + baseline_offset - (cap_height / 2.);
cx.scene.push_quad(gpui::Quad { scene.push_quad(gpui::Quad {
bounds: RectF::from_points( bounds: RectF::from_points(
vec2f(start_x, start_y), vec2f(start_x, start_y),
vec2f( vec2f(
@ -944,7 +944,7 @@ impl ContactList {
border: gpui::Border::default(), border: gpui::Border::default(),
corner_radius: 0., corner_radius: 0.,
}); });
cx.scene.push_quad(gpui::Quad { scene.push_quad(gpui::Quad {
bounds: RectF::from_points( bounds: RectF::from_points(
vec2f(start_x, end_y), vec2f(start_x, end_y),
vec2f(end_x, end_y + tree_branch.width), vec2f(end_x, end_y + tree_branch.width),
@ -988,7 +988,7 @@ impl ContactList {
}, },
) )
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(OpenSharedScreen { peer_id }); cx.dispatch_action(OpenSharedScreen { peer_id });
}) })
.boxed() .boxed()
@ -999,8 +999,8 @@ impl ContactList {
theme: &theme::ContactList, theme: &theme::ContactList,
is_selected: bool, is_selected: bool,
is_collapsed: bool, is_collapsed: bool,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
enum Header {} enum Header {}
enum LeaveCallContactList {} enum LeaveCallContactList {}
@ -1015,14 +1015,14 @@ impl ContactList {
}; };
let leave_call = if section == Section::ActiveCall { let leave_call = if section == Section::ActiveCall {
Some( Some(
MouseEventHandler::<LeaveCallContactList>::new(0, cx, |state, _| { MouseEventHandler::<LeaveCallContactList, Self>::new(0, cx, |state, _| {
let style = theme.leave_call.style_for(state, false); let style = theme.leave_call.style_for(state, false);
Label::new("Leave Call", style.text.clone()) Label::new("Leave Call", style.text.clone())
.contained() .contained()
.with_style(style.container) .with_style(style.container)
.boxed() .boxed()
}) })
.on_click(MouseButton::Left, |_, cx| cx.dispatch_action(LeaveCall)) .on_click(MouseButton::Left, |_, _, cx| cx.dispatch_action(LeaveCall))
.aligned() .aligned()
.boxed(), .boxed(),
) )
@ -1031,7 +1031,7 @@ impl ContactList {
}; };
let icon_size = theme.section_icon_size; let icon_size = theme.section_icon_size;
MouseEventHandler::<Header>::new(section as usize, cx, |_, _| { MouseEventHandler::<Header, Self>::new(section as usize, cx, |_, _| {
Flex::row() Flex::row()
.with_child( .with_child(
Svg::new(if is_collapsed { Svg::new(if is_collapsed {
@ -1065,7 +1065,7 @@ impl ContactList {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ToggleExpanded(section)) cx.dispatch_action(ToggleExpanded(section))
}) })
.boxed() .boxed()
@ -1077,15 +1077,15 @@ impl ContactList {
project: &ModelHandle<Project>, project: &ModelHandle<Project>,
theme: &theme::ContactList, theme: &theme::ContactList,
is_selected: bool, is_selected: bool,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let online = contact.online; let online = contact.online;
let busy = contact.busy || calling; let busy = contact.busy || calling;
let user_id = contact.user.id; let user_id = contact.user.id;
let github_login = contact.user.github_login.clone(); let github_login = contact.user.github_login.clone();
let initial_project = project.clone(); let initial_project = project.clone();
let mut element = 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() Flex::row()
.with_children(contact.user.avatar.clone().map(|avatar| { .with_children(contact.user.avatar.clone().map(|avatar| {
let status_badge = if contact.online { let status_badge = if contact.online {
@ -1128,7 +1128,7 @@ impl ContactList {
.boxed(), .boxed(),
) )
.with_child( .with_child(
MouseEventHandler::<Cancel>::new( MouseEventHandler::<Cancel, Self>::new(
contact.user.id as usize, contact.user.id as usize,
cx, cx,
|mouse_state, _| { |mouse_state, _| {
@ -1142,7 +1142,7 @@ impl ContactList {
) )
.with_padding(Padding::uniform(2.)) .with_padding(Padding::uniform(2.))
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(RemoveContact { cx.dispatch_action(RemoveContact {
user_id, user_id,
github_login: github_login.clone(), github_login: github_login.clone(),
@ -1172,7 +1172,7 @@ impl ContactList {
) )
.boxed() .boxed()
}) })
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
if online && !busy { if online && !busy {
cx.dispatch_action(Call { cx.dispatch_action(Call {
recipient_user_id: user_id, recipient_user_id: user_id,
@ -1194,8 +1194,8 @@ impl ContactList {
theme: &theme::ContactList, theme: &theme::ContactList,
is_incoming: bool, is_incoming: bool,
is_selected: bool, is_selected: bool,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
enum Decline {} enum Decline {}
enum Accept {} enum Accept {}
enum Cancel {} enum Cancel {}
@ -1228,7 +1228,7 @@ impl ContactList {
if is_incoming { if is_incoming {
row.add_children([ 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 { let button_style = if is_contact_request_pending {
&theme.disabled_button &theme.disabled_button
} else { } else {
@ -1239,7 +1239,7 @@ impl ContactList {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(RespondToContactRequest { cx.dispatch_action(RespondToContactRequest {
user_id, user_id,
accept: false, accept: false,
@ -1248,7 +1248,7 @@ impl ContactList {
.contained() .contained()
.with_margin_right(button_spacing) .with_margin_right(button_spacing)
.boxed(), .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 { let button_style = if is_contact_request_pending {
&theme.disabled_button &theme.disabled_button
} else { } else {
@ -1260,7 +1260,7 @@ impl ContactList {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(RespondToContactRequest { cx.dispatch_action(RespondToContactRequest {
user_id, user_id,
accept: true, accept: true,
@ -1270,7 +1270,7 @@ impl ContactList {
]); ]);
} else { } else {
row.add_child( 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 { let button_style = if is_contact_request_pending {
&theme.disabled_button &theme.disabled_button
} else { } else {
@ -1283,7 +1283,7 @@ impl ContactList {
}) })
.with_padding(Padding::uniform(2.)) .with_padding(Padding::uniform(2.))
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(RemoveContact { cx.dispatch_action(RemoveContact {
user_id, user_id,
github_login: github_login.clone(), github_login: github_login.clone(),
@ -1331,7 +1331,7 @@ impl View for ContactList {
cx cx
} }
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
enum AddContact {} enum AddContact {}
let theme = cx.global::<Settings>().theme.clone(); let theme = cx.global::<Settings>().theme.clone();
@ -1346,7 +1346,7 @@ impl View for ContactList {
.boxed(), .boxed(),
) )
.with_child( .with_child(
MouseEventHandler::<AddContact>::new(0, cx, |_, _| { MouseEventHandler::<AddContact, Self>::new(0, cx, |_, _| {
render_icon_button( render_icon_button(
&theme.contact_list.add_contact_button, &theme.contact_list.add_contact_button,
"icons/user_plus_16.svg", "icons/user_plus_16.svg",
@ -1354,10 +1354,10 @@ impl View for ContactList {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, cx| { .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(contacts_popover::ToggleContactFinder) cx.dispatch_action(contacts_popover::ToggleContactFinder)
}) })
.with_tooltip::<AddContact, _>( .with_tooltip::<AddContact>(
0, 0,
"Search for new contact".into(), "Search for new contact".into(),
None, 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) Svg::new(svg_path)
.with_color(style.color) .with_color(style.color)
.constrained() .constrained()

View file

@ -3,8 +3,7 @@ use std::sync::Arc;
use crate::notifications::render_user_notification; use crate::notifications::render_user_notification;
use client::{ContactEventKind, User, UserStore}; use client::{ContactEventKind, User, UserStore};
use gpui::{ use gpui::{
elements::*, impl_internal_actions, AppContext, Entity, ModelHandle, RenderContext, View, elements::*, impl_internal_actions, AppContext, Entity, ModelHandle, View, ViewContext,
ViewContext,
}; };
use workspace::notifications::Notification; use workspace::notifications::Notification;
@ -43,7 +42,7 @@ impl View for ContactNotification {
"ContactNotification" "ContactNotification"
} }
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
match self.kind { match self.kind {
ContactEventKind::Requested => render_user_notification( ContactEventKind::Requested => render_user_notification(
self.user.clone(), self.user.clone(),

View file

@ -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 client::UserStore;
use gpui::{ use gpui::{
actions, elements::*, platform::MouseButton, AppContext, Entity, ModelHandle, RenderContext, actions, elements::*, platform::MouseButton, AppContext, Entity, ModelHandle, View,
View, ViewContext, ViewHandle, ViewContext, ViewHandle,
}; };
use picker::PickerEvent;
use project::Project; use project::Project;
use settings::Settings; use settings::Settings;
@ -50,19 +55,19 @@ impl ContactsPopover {
fn toggle_contact_finder(&mut self, _: &ToggleContactFinder, cx: &mut ViewContext<Self>) { fn toggle_contact_finder(&mut self, _: &ToggleContactFinder, cx: &mut ViewContext<Self>) {
match &self.child { match &self.child {
Child::ContactList(list) => self.show_contact_finder(list.read(cx).editor_text(cx), cx), Child::ContactList(list) => self.show_contact_finder(list.read(cx).editor_text(cx), cx),
Child::ContactFinder(finder) => { Child::ContactFinder(finder) => self.show_contact_list(finder.read(cx).query(cx), cx),
self.show_contact_list(finder.read(cx).editor_text(cx), cx)
}
} }
} }
fn show_contact_finder(&mut self, editor_text: String, cx: &mut ViewContext<ContactsPopover>) { fn show_contact_finder(&mut self, editor_text: String, cx: &mut ViewContext<ContactsPopover>) {
let child = cx.add_view(|cx| { 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); cx.focus(&child);
self._subscription = Some(cx.subscribe(&child, |_, _, event, cx| match event { 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); self.child = Child::ContactFinder(child);
cx.notify(); cx.notify();
@ -91,14 +96,14 @@ impl View for ContactsPopover {
"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 theme = cx.global::<Settings>().theme.clone();
let child = match &self.child { let child = match &self.child {
Child::ContactList(child) => ChildView::new(child, cx), Child::ContactList(child) => ChildView::new(child, cx),
Child::ContactFinder(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() Flex::column()
.with_child(child.flex(1., true).boxed()) .with_child(child.flex(1., true).boxed())
.contained() .contained()
@ -108,7 +113,7 @@ impl View for ContactsPopover {
.with_height(theme.contacts_popover.height) .with_height(theme.contacts_popover.height)
.boxed() .boxed()
}) })
.on_down_out(MouseButton::Left, move |_, cx| { .on_down_out(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ToggleContactsMenu); cx.dispatch_action(ToggleContactsMenu);
}) })
.boxed() .boxed()

View file

@ -7,12 +7,14 @@ use gpui::{
}, },
json::ToJson, json::ToJson,
serde_json::{self, json}, serde_json::{self, json},
Axis, DebugContext, Element, ElementBox, MeasurementContext, PaintContext, Axis, Drawable, Element, SceneBuilder, ViewContext,
}; };
use crate::CollabTitlebarItem;
pub(crate) struct FacePile { pub(crate) struct FacePile {
overlap: f32, overlap: f32,
faces: Vec<ElementBox>, faces: Vec<Element<CollabTitlebarItem>>,
} }
impl FacePile { impl FacePile {
@ -24,20 +26,21 @@ impl FacePile {
} }
} }
impl Element for FacePile { impl Drawable<CollabTitlebarItem> for FacePile {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: gpui::SizeConstraint, constraint: gpui::SizeConstraint,
cx: &mut gpui::LayoutContext, view: &mut CollabTitlebarItem,
cx: &mut ViewContext<CollabTitlebarItem>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY); debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
let mut width = 0.; let mut width = 0.;
for face in &mut self.faces { 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; width -= self.overlap * self.faces.len().saturating_sub(1) as f32;
@ -46,10 +49,12 @@ impl Element for FacePile {
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
_layout: &mut Self::LayoutState, _layout: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut CollabTitlebarItem,
cx: &mut ViewContext<CollabTitlebarItem>,
) -> Self::PaintState { ) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); 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() { for face in self.faces.iter_mut().rev() {
let size = face.size(); let size = face.size();
origin_x -= size.x(); origin_x -= size.x();
cx.paint_layer(None, |cx| { scene.paint_layer(None, |scene| {
face.paint(vec2f(origin_x, origin_y), visible_bounds, cx); face.paint(scene, vec2f(origin_x, origin_y), visible_bounds, view, cx);
}); });
origin_x += self.overlap; origin_x += self.overlap;
} }
@ -75,7 +80,8 @@ impl Element for FacePile {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &MeasurementContext, _: &CollabTitlebarItem,
_: &ViewContext<CollabTitlebarItem>,
) -> Option<RectF> { ) -> Option<RectF> {
None None
} }
@ -85,7 +91,8 @@ impl Element for FacePile {
bounds: RectF, bounds: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &DebugContext, _: &CollabTitlebarItem,
_: &ViewContext<CollabTitlebarItem>,
) -> serde_json::Value { ) -> serde_json::Value {
json!({ json!({
"type": "FacePile", "type": "FacePile",
@ -94,8 +101,8 @@ impl Element for FacePile {
} }
} }
impl Extend<ElementBox> for FacePile { impl Extend<Element<CollabTitlebarItem>> for FacePile {
fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) { fn extend<T: IntoIterator<Item = Element<CollabTitlebarItem>>>(&mut self, children: T) {
self.faces.extend(children); self.faces.extend(children);
} }
} }

View file

@ -6,7 +6,7 @@ use gpui::{
geometry::{rect::RectF, vector::vec2f}, geometry::{rect::RectF, vector::vec2f},
impl_internal_actions, impl_internal_actions,
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions}, platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
AppContext, Entity, RenderContext, View, ViewContext, AppContext, Element, Entity, View, ViewContext,
}; };
use settings::Settings; use settings::Settings;
use util::ResultExt; 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 theme = &cx.global::<Settings>().theme.incoming_call_notification;
let default_project = proto::ParticipantProject::default(); let default_project = proto::ParticipantProject::default();
let initial_project = self let initial_project = self
@ -165,13 +165,13 @@ impl IncomingCallNotification {
.boxed() .boxed()
} }
fn render_buttons(&self, cx: &mut RenderContext<Self>) -> ElementBox { fn render_buttons(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
enum Accept {} enum Accept {}
enum Decline {} enum Decline {}
Flex::column() Flex::column()
.with_child( .with_child(
MouseEventHandler::<Accept>::new(0, cx, |_, cx| { MouseEventHandler::<Accept, Self>::new(0, cx, |_, cx| {
let theme = &cx.global::<Settings>().theme.incoming_call_notification; let theme = &cx.global::<Settings>().theme.incoming_call_notification;
Label::new("Accept", theme.accept_button.text.clone()) Label::new("Accept", theme.accept_button.text.clone())
.aligned() .aligned()
@ -180,14 +180,14 @@ impl IncomingCallNotification {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, cx| { .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(RespondToCall { accept: true }); cx.dispatch_action(RespondToCall { accept: true });
}) })
.flex(1., true) .flex(1., true)
.boxed(), .boxed(),
) )
.with_child( .with_child(
MouseEventHandler::<Decline>::new(0, cx, |_, cx| { MouseEventHandler::<Decline, Self>::new(0, cx, |_, cx| {
let theme = &cx.global::<Settings>().theme.incoming_call_notification; let theme = &cx.global::<Settings>().theme.incoming_call_notification;
Label::new("Decline", theme.decline_button.text.clone()) Label::new("Decline", theme.decline_button.text.clone())
.aligned() .aligned()
@ -196,7 +196,7 @@ impl IncomingCallNotification {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, cx| { .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(RespondToCall { accept: false }); cx.dispatch_action(RespondToCall { accept: false });
}) })
.flex(1., true) .flex(1., true)
@ -222,7 +222,7 @@ impl View for IncomingCallNotification {
"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 let background = cx
.global::<Settings>() .global::<Settings>()
.theme .theme

View file

@ -2,7 +2,7 @@ use client::User;
use gpui::{ use gpui::{
elements::*, elements::*,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
Action, Element, ElementBox, RenderContext, View, Action, Drawable, Element, View, ViewContext,
}; };
use settings::Settings; use settings::Settings;
use std::sync::Arc; use std::sync::Arc;
@ -16,8 +16,8 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
body: Option<&'static str>, body: Option<&'static str>,
dismiss_action: A, dismiss_action: A,
buttons: Vec<(&'static str, Box<dyn Action>)>, buttons: Vec<(&'static str, Box<dyn Action>)>,
cx: &mut RenderContext<V>, cx: &mut ViewContext<V>,
) -> ElementBox { ) -> Element<V> {
let theme = cx.global::<Settings>().theme.clone(); let theme = cx.global::<Settings>().theme.clone();
let theme = &theme.contact_notification; let theme = &theme.contact_notification;
@ -51,7 +51,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
.boxed(), .boxed(),
) )
.with_child( .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); let style = theme.dismiss_button.style_for(state, false);
Svg::new("icons/x_mark_8.svg") Svg::new("icons/x_mark_8.svg")
.with_color(style.color) .with_color(style.color)
@ -67,7 +67,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(5.)) .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()) cx.dispatch_any_action(dismiss_action.boxed_clone())
}) })
.aligned() .aligned()
@ -96,7 +96,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
Flex::row() Flex::row()
.with_children(buttons.into_iter().enumerate().map( .with_children(buttons.into_iter().enumerate().map(
|(ix, (message, action))| { |(ix, (message, action))| {
MouseEventHandler::<Button>::new(ix, cx, |state, _| { MouseEventHandler::<Button, V>::new(ix, cx, |state, _| {
let button = theme.button.style_for(state, false); let button = theme.button.style_for(state, false);
Label::new(message, button.text.clone()) Label::new(message, button.text.clone())
.contained() .contained()
@ -104,7 +104,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_any_action(action.boxed_clone()) cx.dispatch_any_action(action.boxed_clone())
}) })
.boxed() .boxed()

View file

@ -6,7 +6,7 @@ use gpui::{
elements::*, elements::*,
geometry::{rect::RectF, vector::vec2f}, geometry::{rect::RectF, vector::vec2f},
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions}, platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
AppContext, Entity, RenderContext, View, ViewContext, AppContext, Entity, View, ViewContext,
}; };
use settings::Settings; use settings::Settings;
use std::sync::Arc; use std::sync::Arc;
@ -94,17 +94,15 @@ impl ProjectSharedNotification {
} }
fn join(&mut self, _: &JoinProject, cx: &mut ViewContext<Self>) { fn join(&mut self, _: &JoinProject, cx: &mut ViewContext<Self>) {
let window_id = cx.window_id(); cx.remove_window();
cx.remove_window(window_id);
cx.propagate_action(); cx.propagate_action();
} }
fn dismiss(&mut self, _: &DismissProject, cx: &mut ViewContext<Self>) { fn dismiss(&mut self, _: &DismissProject, cx: &mut ViewContext<Self>) {
let window_id = cx.window_id(); cx.remove_window();
cx.remove_window(window_id);
} }
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; let theme = &cx.global::<Settings>().theme.project_shared_notification;
Flex::row() Flex::row()
.with_children(self.owner.avatar.clone().map(|avatar| { .with_children(self.owner.avatar.clone().map(|avatar| {
@ -164,7 +162,7 @@ impl ProjectSharedNotification {
.boxed() .boxed()
} }
fn render_buttons(&self, cx: &mut RenderContext<Self>) -> ElementBox { fn render_buttons(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
enum Open {} enum Open {}
enum Dismiss {} enum Dismiss {}
@ -173,7 +171,7 @@ impl ProjectSharedNotification {
Flex::column() Flex::column()
.with_child( .with_child(
MouseEventHandler::<Open>::new(0, cx, |_, cx| { MouseEventHandler::<Open, Self>::new(0, cx, |_, cx| {
let theme = &cx.global::<Settings>().theme.project_shared_notification; let theme = &cx.global::<Settings>().theme.project_shared_notification;
Label::new("Open", theme.open_button.text.clone()) Label::new("Open", theme.open_button.text.clone())
.aligned() .aligned()
@ -182,7 +180,7 @@ impl ProjectSharedNotification {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(JoinProject { cx.dispatch_action(JoinProject {
project_id, project_id,
follow_user_id: owner_user_id, follow_user_id: owner_user_id,
@ -192,7 +190,7 @@ impl ProjectSharedNotification {
.boxed(), .boxed(),
) )
.with_child( .with_child(
MouseEventHandler::<Dismiss>::new(0, cx, |_, cx| { MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, cx| {
let theme = &cx.global::<Settings>().theme.project_shared_notification; let theme = &cx.global::<Settings>().theme.project_shared_notification;
Label::new("Dismiss", theme.dismiss_button.text.clone()) Label::new("Dismiss", theme.dismiss_button.text.clone())
.aligned() .aligned()
@ -201,7 +199,7 @@ impl ProjectSharedNotification {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, cx| { .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(DismissProject); cx.dispatch_action(DismissProject);
}) })
.flex(1., true) .flex(1., true)
@ -227,7 +225,7 @@ impl View for ProjectSharedNotification {
"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 let background = cx
.global::<Settings>() .global::<Settings>()
.theme .theme

View file

@ -3,7 +3,7 @@ use gpui::{
color::Color, color::Color,
elements::{MouseEventHandler, Svg}, elements::{MouseEventHandler, Svg},
platform::{Appearance, MouseButton}, platform::{Appearance, MouseButton},
AppContext, Element, ElementBox, Entity, RenderContext, View, AppContext, Drawable, Element, Entity, View, ViewContext,
}; };
use settings::Settings; use settings::Settings;
@ -40,13 +40,13 @@ impl View for SharingStatusIndicator {
"SharingStatusIndicator" "SharingStatusIndicator"
} }
fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let color = match cx.appearance { let color = match cx.window_appearance() {
Appearance::Light | Appearance::VibrantLight => Color::black(), Appearance::Light | Appearance::VibrantLight => Color::black(),
Appearance::Dark | Appearance::VibrantDark => Color::white(), 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") Svg::new("icons/disable_screen_sharing_12.svg")
.with_color(color) .with_color(color)
.constrained() .constrained()
@ -54,7 +54,7 @@ impl View for SharingStatusIndicator {
.aligned() .aligned()
.boxed() .boxed()
}) })
.on_click(MouseButton::Left, |_, cx| { .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(ToggleScreenSharing); cx.dispatch_action(ToggleScreenSharing);
}) })
.boxed() .boxed()

View file

@ -1,26 +1,25 @@
use collections::CommandPaletteFilter; use collections::CommandPaletteFilter;
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, actions, elements::*, keymap_matcher::Keystroke, Action, AppContext, Drawable, MouseState,
elements::{ChildView, Flex, Label, ParentElement}, ViewContext,
keymap_matcher::Keystroke,
Action, AnyViewHandle, AppContext, Element, Entity, MouseState, RenderContext, View,
ViewContext, ViewHandle,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate, PickerEvent};
use settings::Settings; use settings::Settings;
use std::cmp; use std::cmp;
use util::ResultExt;
use workspace::Workspace; use workspace::Workspace;
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
cx.add_action(CommandPalette::toggle); cx.add_action(toggle_command_palette);
Picker::<CommandPalette>::init(cx); CommandPalette::init(cx);
} }
actions!(command_palette, [Toggle]); actions!(command_palette, [Toggle]);
pub struct CommandPalette { pub type CommandPalette = Picker<CommandPaletteDelegate>;
picker: ViewHandle<Picker<Self>>,
pub struct CommandPaletteDelegate {
actions: Vec<Command>, actions: Vec<Command>,
matches: Vec<StringMatch>, matches: Vec<StringMatch>,
selected_ix: usize, selected_ix: usize,
@ -42,11 +41,21 @@ struct Command {
keystrokes: Vec<Keystroke>, keystrokes: Vec<Keystroke>,
} }
impl CommandPalette { fn toggle_command_palette(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
pub fn new(focused_view_id: usize, cx: &mut ViewContext<Self>) -> Self { let workspace = cx.handle();
let this = cx.weak_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 let actions = cx
.available_actions(cx.window_id(), focused_view_id) .available_actions(focused_view_id)
.filter_map(|(name, action, bindings)| { .filter_map(|(name, action, bindings)| {
if cx.has_global::<CommandPaletteFilter>() { if cx.has_global::<CommandPaletteFilter>() {
let filter = cx.global::<CommandPaletteFilter>(); let filter = cx.global::<CommandPaletteFilter>();
@ -67,79 +76,20 @@ impl CommandPalette {
}) })
.collect(); .collect();
let picker = cx.add_view(|cx| Picker::new("Execute a command...", this, cx));
Self { Self {
picker,
actions, actions,
matches: vec![], matches: vec![],
selected_ix: 0, selected_ix: 0,
focused_view_id, 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 { impl PickerDelegate for CommandPaletteDelegate {
type Event = Event; fn placeholder_text(&self) -> std::sync::Arc<str> {
} "Execute a command...".into()
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()
}
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 { fn match_count(&self) -> usize {
self.matches.len() self.matches.len()
} }
@ -148,14 +98,14 @@ impl PickerDelegate for CommandPalette {
self.selected_ix 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; self.selected_ix = ix;
} }
fn update_matches( fn update_matches(
&mut self, &mut self,
query: String, query: String,
cx: &mut gpui::ViewContext<Self>, cx: &mut ViewContext<Picker<Self>>,
) -> gpui::Task<()> { ) -> gpui::Task<()> {
let candidates = self let candidates = self
.actions .actions
@ -167,7 +117,7 @@ impl PickerDelegate for CommandPalette {
char_bag: command.name.chars().collect(), char_bag: command.name.chars().collect(),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
cx.spawn(move |this, mut cx| async move { cx.spawn(move |picker, mut cx| async move {
let matches = if query.is_empty() { let matches = if query.is_empty() {
candidates candidates
.into_iter() .into_iter()
@ -190,32 +140,34 @@ impl PickerDelegate for CommandPalette {
) )
.await .await
}; };
this.update(&mut cx, |this, _| { picker
this.matches = matches; .update(&mut cx, |picker, _| {
if this.matches.is_empty() { let delegate = picker.delegate_mut();
this.selected_ix = 0; delegate.matches = matches;
} else { if delegate.matches.is_empty() {
this.selected_ix = cmp::min(this.selected_ix, this.matches.len() - 1); 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>) { fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
cx.emit(Event::Dismissed);
}
fn confirm(&mut self, cx: &mut ViewContext<Self>) { fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
if !self.matches.is_empty() { 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; let action_ix = self.matches[self.selected_ix].candidate_id;
cx.emit(Event::Confirmed { let action = self.actions.remove(action_ix).action;
window_id: cx.window_id(), cx.defer(move |_, cx| {
focused_view_id: self.focused_view_id, cx.dispatch_any_action_at(window_id, focused_view_id, action);
action: self.actions.remove(action_ix).action,
}); });
} else {
cx.emit(Event::Dismissed);
} }
cx.emit(PickerEvent::Dismiss);
} }
fn render_match( fn render_match(
@ -224,7 +176,7 @@ impl PickerDelegate for CommandPalette {
mouse_state: &mut MouseState, mouse_state: &mut MouseState,
selected: bool, selected: bool,
cx: &gpui::AppContext, cx: &gpui::AppContext,
) -> gpui::ElementBox { ) -> Element<Picker<Self>> {
let mat = &self.matches[ix]; let mat = &self.matches[ix];
let command = &self.actions[mat.candidate_id]; let command = &self.actions[mat.candidate_id];
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
@ -360,7 +312,7 @@ mod tests {
}); });
workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
CommandPalette::toggle(workspace, &Toggle, cx) toggle_command_palette(workspace, &Toggle, cx);
}); });
let palette = workspace.read_with(cx, |workspace, _| { let palette = workspace.read_with(cx, |workspace, _| {
@ -369,13 +321,15 @@ mod tests {
palette palette
.update(cx, |palette, cx| { .update(cx, |palette, cx| {
palette.update_matches("bcksp".to_string(), cx) palette
.delegate_mut()
.update_matches("bcksp".to_string(), cx)
}) })
.await; .await;
palette.update(cx, |palette, cx| { palette.update(cx, |palette, cx| {
assert_eq!(palette.matches[0].string, "editor: backspace"); assert_eq!(palette.delegate().matches[0].string, "editor: backspace");
palette.confirm(cx); palette.confirm(&Default::default(), cx);
}); });
editor.read_with(cx, |editor, cx| { editor.read_with(cx, |editor, cx| {
@ -390,7 +344,7 @@ mod tests {
}); });
workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
CommandPalette::toggle(workspace, &Toggle, cx); toggle_command_palette(workspace, &Toggle, cx);
}); });
// Assert editor command not present // Assert editor command not present
@ -400,10 +354,14 @@ mod tests {
palette palette
.update(cx, |palette, cx| { .update(cx, |palette, cx| {
palette.update_matches("bcksp".to_string(), cx) palette
.delegate_mut()
.update_matches("bcksp".to_string(), cx)
}) })
.await; .await;
palette.update(cx, |palette, _| assert!(palette.matches.is_empty())); palette.update(cx, |palette, _| {
assert!(palette.delegate().matches.is_empty())
});
} }
} }

View file

@ -4,15 +4,13 @@ use gpui::{
impl_internal_actions, impl_internal_actions,
keymap_matcher::KeymapContext, keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
Action, AnyViewHandle, AppContext, Axis, Entity, MouseState, RenderContext, SizeConstraint, Action, AnyViewHandle, AppContext, Axis, Entity, MouseState, SizeConstraint, Subscription,
Subscription, View, ViewContext, View, ViewContext,
}; };
use menu::*; use menu::*;
use settings::Settings; use settings::Settings;
use std::{any::TypeId, borrow::Cow, time::Duration}; use std::{any::TypeId, borrow::Cow, time::Duration};
pub type StaticItem = Box<dyn Fn(&mut AppContext) -> ElementBox>;
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq)]
struct Clicked; struct Clicked;
@ -28,7 +26,10 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(ContextMenu::cancel); 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 { pub enum ContextMenuItemLabel {
String(Cow<'static, str>), String(Cow<'static, str>),
@ -141,7 +142,7 @@ impl View for ContextMenu {
cx cx
} }
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
if !self.visible { if !self.visible {
return Empty::new().boxed(); return Empty::new().boxed();
} }
@ -151,10 +152,10 @@ impl View for ContextMenu {
let expanded_menu = self let expanded_menu = self
.render_menu(cx) .render_menu(cx)
.constrained() .constrained()
.dynamically(move |constraint, cx| { .dynamically(move |constraint, view, cx| {
SizeConstraint::strict_along( SizeConstraint::strict_along(
Axis::Horizontal, Axis::Horizontal,
collapsed_menu.layout(constraint, cx).x(), collapsed_menu.layout(constraint, view, cx).x(),
) )
}) })
.boxed(); .boxed();
@ -209,9 +210,9 @@ impl ContextMenu {
cx.notify(); cx.notify();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
cx.background().timer(Duration::from_millis(50)).await; 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.visible = true;
self.show_count += 1; self.show_count += 1;
if !cx.is_self_focused() { 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(); cx.focus_self();
} else { } else {
@ -327,8 +328,10 @@ impl ContextMenu {
self.position_mode = mode; self.position_mode = mode;
} }
fn render_menu_for_measurement(&self, cx: &mut RenderContext<Self>) -> impl Element { fn render_menu_for_measurement(
let window_id = cx.window_id(); &self,
cx: &mut ViewContext<Self>,
) -> impl Drawable<ContextMenu> {
let style = cx.global::<Settings>().theme.context_menu.clone(); let style = cx.global::<Settings>().theme.context_menu.clone();
Flex::row() Flex::row()
.with_child( .with_child(
@ -386,7 +389,6 @@ impl ContextMenu {
}; };
KeystrokeLabel::new( KeystrokeLabel::new(
window_id,
view_id, view_id,
action.boxed_clone(), action.boxed_clone(),
style.keystroke.container, style.keystroke.container,
@ -414,14 +416,13 @@ impl ContextMenu {
.with_style(style.container) .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 Menu {}
enum MenuItem {} enum MenuItem {}
let style = cx.global::<Settings>().theme.context_menu.clone(); let style = cx.global::<Settings>().theme.context_menu.clone();
let window_id = cx.window_id(); MouseEventHandler::<Menu, ContextMenu>::new(0, cx, |_, cx| {
MouseEventHandler::<Menu>::new(0, cx, |_, cx| {
Flex::column() Flex::column()
.with_children(self.items.iter().enumerate().map(|(ix, item)| { .with_children(self.items.iter().enumerate().map(|(ix, item)| {
match item { match item {
@ -435,7 +436,7 @@ impl ContextMenu {
} }
}; };
MouseEventHandler::<MenuItem>::new(ix, cx, |state, _| { MouseEventHandler::<MenuItem, ContextMenu>::new(ix, cx, |state, _| {
let style = let style =
style.item.style_for(state, Some(ix) == self.selected_index); style.item.style_for(state, Some(ix) == self.selected_index);
@ -452,7 +453,6 @@ impl ContextMenu {
}) })
.with_child({ .with_child({
KeystrokeLabel::new( KeystrokeLabel::new(
window_id,
view_id, view_id,
action.boxed_clone(), action.boxed_clone(),
style.keystroke.container, style.keystroke.container,
@ -466,14 +466,14 @@ impl ContextMenu {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_up(MouseButton::Left, |_, _| {}) // Capture these events .on_up(MouseButton::Left, |_, _, _| {}) // Capture these events
.on_down(MouseButton::Left, |_, _| {}) // Capture these events .on_down(MouseButton::Left, |_, _, _| {}) // Capture these events
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(Clicked); cx.dispatch_action(Clicked);
let window_id = cx.window_id(); let window_id = cx.window_id();
cx.dispatch_any_action_at(window_id, view_id, action.boxed_clone()); cx.dispatch_any_action_at(window_id, view_id, action.boxed_clone());
}) })
.on_drag(MouseButton::Left, |_, _| {}) .on_drag(MouseButton::Left, |_, _, _| {})
.boxed() .boxed()
} }
@ -491,7 +491,7 @@ impl ContextMenu {
.with_style(style.container) .with_style(style.container)
.boxed() .boxed()
}) })
.on_down_out(MouseButton::Left, |_, 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)) .on_down_out(MouseButton::Right, |_, _, cx| cx.dispatch_action(Cancel))
} }
} }

View file

@ -48,8 +48,8 @@ pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, cx: &mut
cx.observe(&copilot, |handle, cx| { cx.observe(&copilot, |handle, cx| {
let status = handle.read(cx).status(); let status = handle.read(cx).status();
cx.update_global::<collections::CommandPaletteFilter, _, _>( cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
move |filter, _cx| match status { match status {
Status::Disabled => { Status::Disabled => {
filter.filtered_namespaces.insert(COPILOT_NAMESPACE); filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
filter.filtered_namespaces.insert(COPILOT_AUTH_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.insert(COPILOT_NAMESPACE);
filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE); filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
} }
}, }
); });
}) })
.detach(); .detach();

View file

@ -4,7 +4,8 @@ use gpui::{
geometry::rect::RectF, geometry::rect::RectF,
impl_internal_actions, impl_internal_actions,
platform::{WindowBounds, WindowKind, WindowOptions}, platform::{WindowBounds, WindowKind, WindowOptions},
AppContext, ClipboardItem, Element, Entity, View, ViewContext, ViewHandle, AnyViewHandle, AppContext, ClipboardItem, Drawable, Element, Entity, View, ViewContext,
ViewHandle,
}; };
use settings::Settings; use settings::Settings;
use theme::ui::modal; use theme::ui::modal;
@ -31,26 +32,32 @@ pub fn init(cx: &mut AppContext) {
match &status { match &status {
crate::Status::SigningIn { prompt } => { crate::Status::SigningIn { prompt } => {
if let Some(code_verification_handle) = code_verification.as_mut() { if let Some(code_verification_handle) = code_verification.as_mut() {
if cx.has_window(code_verification_handle.window_id()) { let window_id = code_verification_handle.window_id();
code_verification_handle.update(cx, |code_verification_view, cx| { if cx.has_window(window_id) {
code_verification_view.set_status(status, cx) 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 { } 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 { } 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 => { Status::Authorized | Status::Unauthorized => {
if let Some(code_verification) = code_verification.as_ref() { if let Some(code_verification) = code_verification.as_ref() {
code_verification.update(cx, |code_verification, cx| { let window_id = code_verification.window_id();
code_verification.set_status(status, cx) cx.update_window(window_id, |cx| {
}); code_verification.update(cx, |code_verification, cx| {
code_verification.set_status(status, cx)
});
cx.platform().activate(true); cx.platform().activate(true);
cx.activate_window(code_verification.window_id()); cx.activate_window();
});
} }
} }
_ => { _ => {
@ -73,8 +80,7 @@ pub fn init(cx: &mut AppContext) {
fn create_copilot_auth_window( fn create_copilot_auth_window(
cx: &mut AppContext, cx: &mut AppContext,
status: &Status, status: &Status,
code_verification: &mut Option<ViewHandle<CopilotCodeVerification>>, ) -> ViewHandle<CopilotCodeVerification> {
) {
let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions(); let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions();
let window_options = WindowOptions { let window_options = WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)), 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| { let (_, view) = cx.add_window(window_options, |_cx| {
CopilotCodeVerification::new(status.clone()) CopilotCodeVerification::new(status.clone())
}); });
*code_verification = Some(view); view
} }
pub struct CopilotCodeVerification { pub struct CopilotCodeVerification {
@ -112,8 +118,8 @@ impl CopilotCodeVerification {
fn render_device_code( fn render_device_code(
data: &PromptUserDeviceFlow, data: &PromptUserDeviceFlow,
style: &theme::Copilot, style: &theme::Copilot,
cx: &mut gpui::RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let copied = cx let copied = cx
.read_from_clipboard() .read_from_clipboard()
.map(|item| item.text() == &data.user_code) .map(|item| item.text() == &data.user_code)
@ -121,7 +127,7 @@ impl CopilotCodeVerification {
let device_code_style = &style.auth.prompting.device_code; 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() Flex::row()
.with_children([ .with_children([
Label::new(data.user_code.clone(), device_code_style.text.clone()) Label::new(data.user_code.clone(), device_code_style.text.clone())
@ -148,7 +154,7 @@ impl CopilotCodeVerification {
}) })
.on_click(gpui::platform::MouseButton::Left, { .on_click(gpui::platform::MouseButton::Left, {
let user_code = data.user_code.clone(); let user_code = data.user_code.clone();
move |_, cx| { move |_, _, cx| {
cx.platform() cx.platform()
.write_to_clipboard(ClipboardItem::new(user_code.clone())); .write_to_clipboard(ClipboardItem::new(user_code.clone()));
cx.notify(); cx.notify();
@ -162,8 +168,10 @@ impl CopilotCodeVerification {
connect_clicked: bool, connect_clicked: bool,
data: &PromptUserDeviceFlow, data: &PromptUserDeviceFlow,
style: &theme::Copilot, style: &theme::Copilot,
cx: &mut gpui::RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
enum ConnectButton {}
Flex::column() Flex::column()
.with_children([ .with_children([
Flex::column() Flex::column()
@ -205,7 +213,7 @@ impl CopilotCodeVerification {
.contained() .contained()
.with_style(style.auth.prompting.hint.container.clone()) .with_style(style.auth.prompting.hint.container.clone())
.boxed(), .boxed(),
theme::ui::cta_button_with_click( theme::ui::cta_button_with_click::<ConnectButton, _, _, _>(
if connect_clicked { if connect_clicked {
"Waiting for connection..." "Waiting for connection..."
} else { } else {
@ -216,7 +224,7 @@ impl CopilotCodeVerification {
cx, cx,
{ {
let verification_uri = data.verification_uri.clone(); let verification_uri = data.verification_uri.clone();
move |_, cx| { move |_, _, cx| {
cx.platform().open_url(&verification_uri); cx.platform().open_url(&verification_uri);
cx.dispatch_action(ClickedConnect) cx.dispatch_action(ClickedConnect)
} }
@ -227,10 +235,9 @@ impl CopilotCodeVerification {
.align_children_center() .align_children_center()
.boxed() .boxed()
} }
fn render_enabled_modal( fn render_enabled_modal(style: &theme::Copilot, cx: &mut ViewContext<Self>) -> Element<Self> {
style: &theme::Copilot, enum DoneButton {}
cx: &mut gpui::RenderContext<Self>,
) -> ElementBox {
let enabled_style = &style.auth.authorized; let enabled_style = &style.auth.authorized;
Flex::column() Flex::column()
.with_children([ .with_children([
@ -261,15 +268,12 @@ impl CopilotCodeVerification {
.contained() .contained()
.with_style(enabled_style.hint.container) .with_style(enabled_style.hint.container)
.boxed(), .boxed(),
theme::ui::cta_button_with_click( theme::ui::cta_button_with_click::<DoneButton, _, _, _>(
"Done", "Done",
style.auth.content_width, style.auth.content_width,
&style.auth.cta_button, &style.auth.cta_button,
cx, cx,
|_, cx| { |_, _, cx| cx.remove_window(),
let window_id = cx.window_id();
cx.remove_window(window_id)
},
) )
.boxed(), .boxed(),
]) ])
@ -278,8 +282,8 @@ impl CopilotCodeVerification {
} }
fn render_unauthorized_modal( fn render_unauthorized_modal(
style: &theme::Copilot, style: &theme::Copilot,
cx: &mut gpui::RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let unauthorized_style = &style.auth.not_authorized; let unauthorized_style = &style.auth.not_authorized;
Flex::column() Flex::column()
@ -322,14 +326,13 @@ impl CopilotCodeVerification {
.contained() .contained()
.with_style(unauthorized_style.warning.container) .with_style(unauthorized_style.warning.container)
.boxed(), .boxed(),
theme::ui::cta_button_with_click( theme::ui::cta_button_with_click::<CopilotCodeVerification, _, _, _>(
"Subscribe on GitHub", "Subscribe on GitHub",
style.auth.content_width, style.auth.content_width,
&style.auth.cta_button, &style.auth.cta_button,
cx, cx,
|_, cx| { |_, _, cx| {
let window_id = cx.window_id(); cx.remove_window();
cx.remove_window(window_id);
cx.platform().open_url(COPILOT_SIGN_UP_URL) cx.platform().open_url(COPILOT_SIGN_UP_URL)
}, },
) )
@ -349,18 +352,20 @@ impl View for CopilotCodeVerification {
"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() 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() 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(); 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() Flex::column()
.with_children([ .with_children([
theme::ui::icon(&style.copilot.auth.header).boxed(), theme::ui::icon(&style.copilot.auth.header).boxed(),

View file

@ -6,8 +6,7 @@ use gpui::{
elements::*, elements::*,
impl_internal_actions, impl_internal_actions,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
AppContext, Element, ElementBox, Entity, MouseState, RenderContext, Subscription, View, AppContext, Drawable, Element, Entity, MouseState, Subscription, View, ViewContext, ViewHandle,
ViewContext, ViewHandle,
}; };
use settings::{settings_file::SettingsFile, Settings}; use settings::{settings_file::SettingsFile, Settings};
use workspace::{ use workspace::{
@ -156,7 +155,7 @@ impl View for CopilotButton {
"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>(); let settings = cx.global::<Settings>();
if !settings.features.copilot { if !settings.features.copilot {
@ -176,7 +175,7 @@ impl View for CopilotButton {
Stack::new() Stack::new()
.with_child( .with_child(
MouseEventHandler::<Self>::new(0, cx, { MouseEventHandler::<Self, _>::new(0, cx, {
let theme = theme.clone(); let theme = theme.clone();
let status = status.clone(); let status = status.clone();
move |state, _cx| { move |state, _cx| {
@ -218,7 +217,7 @@ impl View for CopilotButton {
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, { .on_click(MouseButton::Left, {
let status = status.clone(); let status = status.clone();
move |_, cx| match status { move |_, _, cx| match status {
Status::Authorized => cx.dispatch_action(DeployCopilotMenu), Status::Authorized => cx.dispatch_action(DeployCopilotMenu),
Status::Error(ref e) => cx.dispatch_action(workspace::Toast::new_action( Status::Error(ref e) => cx.dispatch_action(workspace::Toast::new_action(
COPILOT_ERROR_TOAST_ID, COPILOT_ERROR_TOAST_ID,
@ -229,13 +228,7 @@ impl View for CopilotButton {
_ => cx.dispatch_action(DeployCopilotStartMenu), _ => cx.dispatch_action(DeployCopilotStartMenu),
} }
}) })
.with_tooltip::<Self, _>( .with_tooltip::<Self>(0, "GitHub Copilot".into(), None, theme.tooltip.clone(), cx)
0,
"GitHub Copilot".into(),
None,
theme.tooltip.clone(),
cx,
)
.boxed(), .boxed(),
) )
.with_child( .with_child(

View file

@ -11,8 +11,7 @@ use editor::{
}; };
use gpui::{ use gpui::{
actions, elements::*, fonts::TextStyle, impl_internal_actions, serde_json, AnyViewHandle, actions, elements::*, fonts::TextStyle, impl_internal_actions, serde_json, AnyViewHandle,
AppContext, Entity, ModelHandle, RenderContext, Task, View, ViewContext, ViewHandle, AppContext, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle,
WeakViewHandle,
}; };
use language::{ use language::{
Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
@ -90,7 +89,7 @@ impl View for ProjectDiagnosticsEditor {
"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() { if self.path_states.is_empty() {
let theme = &cx.global::<Settings>().theme.project_diagnostics; let theme = &cx.global::<Settings>().theme.project_diagnostics;
Label::new("No problems in workspace", theme.empty_message.clone()) Label::new("No problems in workspace", theme.empty_message.clone())
@ -223,7 +222,7 @@ impl ProjectDiagnosticsEditor {
.await?; .await?;
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.populate_excerpts(path, language_server_id, buffer, cx) this.populate_excerpts(path, language_server_id, buffer, cx)
}) })?;
} }
Result::<_, anyhow::Error>::Ok(()) Result::<_, anyhow::Error>::Ok(())
} }
@ -531,12 +530,12 @@ impl ProjectDiagnosticsEditor {
} }
impl Item for ProjectDiagnosticsEditor { impl Item for ProjectDiagnosticsEditor {
fn tab_content( fn tab_content<T: View>(
&self, &self,
_detail: Option<usize>, _detail: Option<usize>,
style: &theme::Tab, style: &theme::Tab,
cx: &AppContext, cx: &AppContext,
) -> ElementBox { ) -> Element<T> {
render_summary( render_summary(
&self.summary, &self.summary,
&style.label.text, &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, summary: &DiagnosticSummary,
text_style: &TextStyle, text_style: &TextStyle,
theme: &theme::ProjectDiagnostics, theme: &theme::ProjectDiagnostics,
) -> ElementBox { ) -> Element<T> {
if summary.error_count == 0 && summary.warning_count == 0 { if summary.error_count == 0 && summary.warning_count == 0 {
Label::new("No problems", text_style.clone()).boxed() Label::new("No problems", text_style.clone()).boxed()
} else { } else {
@ -804,7 +803,7 @@ mod tests {
display_map::{BlockContext, TransformBlock}, display_map::{BlockContext, TransformBlock},
DisplayPoint, DisplayPoint,
}; };
use gpui::TestAppContext; use gpui::{TestAppContext, WindowContext};
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped}; use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
use project::FakeFs; use project::FakeFs;
use serde_json::json; use serde_json::json;
@ -1479,10 +1478,8 @@ mod tests {
}); });
} }
fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut AppContext) -> Vec<(u32, String)> { fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut WindowContext) -> Vec<(u32, String)> {
let mut presenter = cx.build_presenter(editor.id(), 0., Default::default()); editor.update(cx, |editor, cx| {
let mut cx = presenter.build_layout_context(Default::default(), false, cx);
cx.render(editor, |editor, cx| {
let snapshot = editor.snapshot(cx); let snapshot = editor.snapshot(cx);
snapshot snapshot
.blocks_in_range(0..snapshot.max_point().row()) .blocks_in_range(0..snapshot.max_point().row())
@ -1490,7 +1487,7 @@ mod tests {
let name = match block { let name = match block {
TransformBlock::Custom(block) => block TransformBlock::Custom(block) => block
.render(&mut BlockContext { .render(&mut BlockContext {
cx, view_context: cx,
anchor_x: 0., anchor_x: 0.,
scroll_x: 0., scroll_x: 0.,
gutter_padding: 0., gutter_padding: 0.,

View file

@ -3,8 +3,8 @@ use editor::{Editor, GoToDiagnostic};
use gpui::{ use gpui::{
elements::*, elements::*,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
serde_json, AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext, serde_json, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle,
ViewHandle, WeakViewHandle, WeakViewHandle,
}; };
use language::Diagnostic; use language::Diagnostic;
use lsp::LanguageServerId; use lsp::LanguageServerId;
@ -85,14 +85,14 @@ impl View for DiagnosticIndicator {
"DiagnosticIndicator" "DiagnosticIndicator"
} }
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
enum Summary {} enum Summary {}
enum Message {} enum Message {}
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone(); let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let in_progress = !self.in_progress_checks.is_empty(); let in_progress = !self.in_progress_checks.is_empty();
let mut element = Flex::row().with_child( let mut element = Flex::row().with_child(
MouseEventHandler::<Summary>::new(0, cx, |state, cx| { MouseEventHandler::<Summary, _>::new(0, cx, |state, cx| {
let style = cx let style = cx
.global::<Settings>() .global::<Settings>()
.theme .theme
@ -164,8 +164,10 @@ impl View for DiagnosticIndicator {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, cx| cx.dispatch_action(crate::Deploy)) .on_click(MouseButton::Left, |_, _, cx| {
.with_tooltip::<Summary, _>( cx.dispatch_action(crate::Deploy)
})
.with_tooltip::<Summary>(
0, 0,
"Project Diagnostics".to_string(), "Project Diagnostics".to_string(),
Some(Box::new(crate::Deploy)), Some(Box::new(crate::Deploy)),
@ -190,7 +192,7 @@ impl View for DiagnosticIndicator {
} else if let Some(diagnostic) = &self.current_diagnostic { } else if let Some(diagnostic) = &self.current_diagnostic {
let message_style = style.diagnostic_message.clone(); let message_style = style.diagnostic_message.clone();
element.add_child( element.add_child(
MouseEventHandler::<Message>::new(1, cx, |state, _| { MouseEventHandler::<Message, _>::new(1, cx, |state, _| {
Label::new( Label::new(
diagnostic.message.split('\n').next().unwrap().to_string(), diagnostic.message.split('\n').next().unwrap().to_string(),
message_style.style_for(state, false).text.clone(), message_style.style_for(state, false).text.clone(),
@ -201,7 +203,7 @@ impl View for DiagnosticIndicator {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, cx| { .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(GoToDiagnostic) cx.dispatch_action(GoToDiagnostic)
}) })
.boxed(), .boxed(),

View file

@ -6,7 +6,7 @@ use gpui::{
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
scene::{MouseDown, MouseDrag}, scene::{MouseDown, MouseDrag},
AppContext, Element, ElementBox, EventContext, RenderContext, View, WeakViewHandle, Drawable, Element, View, ViewContext, WeakViewHandle, WindowContext,
}; };
const DEAD_ZONE: f32 = 4.; const DEAD_ZONE: f32 = 4.;
@ -26,7 +26,7 @@ enum State<V: View> {
region_offset: Vector2F, region_offset: Vector2F,
region: RectF, region: RectF,
payload: Rc<dyn Any + 'static>, 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, 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, _| { cx.update_global(|this: &mut Self, _| {
this.currently_dragged = Some(State::Down { this.currently_dragged = Some(State::Down {
region_offset: event.position - event.region.origin(), region_offset: event.position - event.region.origin(),
@ -123,8 +123,8 @@ impl<V: View> DragAndDrop<V> {
pub fn dragging<T: Any>( pub fn dragging<T: Any>(
event: MouseDrag, event: MouseDrag,
payload: Rc<T>, payload: Rc<T>,
cx: &mut EventContext, cx: &mut WindowContext,
render: Rc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>, render: Rc<impl 'static + Fn(&T, &mut ViewContext<V>) -> Element<V>>,
) { ) {
let window_id = cx.window_id(); let window_id = cx.window_id();
cx.update_global(|this: &mut Self, cx| { 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 {} enum DraggedElementHandler {}
cx.global::<Self>() cx.global::<Self>()
.currently_dragged .currently_dragged
@ -202,20 +202,22 @@ impl<V: View> DragAndDrop<V> {
let position = position - region_offset; let position = position - region_offset;
Some( Some(
Overlay::new( Overlay::new(
MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| { MouseEventHandler::<DraggedElementHandler, V>::new(
render(payload, cx) 0,
}) cx,
|_, cx| render(payload, cx),
)
.with_cursor_style(CursorStyle::Arrow) .with_cursor_style(CursorStyle::Arrow)
.on_up(MouseButton::Left, |_, cx| { .on_up(MouseButton::Left, |_, _, cx| {
cx.defer(|cx| { cx.window_context().defer(|cx| {
cx.update_global::<Self, _, _>(|this, cx| { cx.update_global::<Self, _, _>(|this, cx| {
this.finish_dragging(cx) this.finish_dragging(cx)
}); });
}); });
cx.propagate_event(); cx.propagate_event();
}) })
.on_up_out(MouseButton::Left, |_, cx| { .on_up_out(MouseButton::Left, |_, _, cx| {
cx.defer(|cx| { cx.window_context().defer(|cx| {
cx.update_global::<Self, _, _>(|this, cx| { cx.update_global::<Self, _, _>(|this, cx| {
this.finish_dragging(cx) this.finish_dragging(cx)
}); });
@ -234,22 +236,22 @@ impl<V: View> DragAndDrop<V> {
} }
State::Canceled => Some( State::Canceled => Some(
MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, _| { MouseEventHandler::<DraggedElementHandler, V>::new(0, cx, |_, _| {
Empty::new() Empty::new()
.constrained() .constrained()
.with_width(0.) .with_width(0.)
.with_height(0.) .with_height(0.)
.boxed() .boxed()
}) })
.on_up(MouseButton::Left, |_, cx| { .on_up(MouseButton::Left, |_, _, cx| {
cx.defer(|cx| { cx.window_context().defer(|cx| {
cx.update_global::<Self, _, _>(|this, _| { cx.update_global::<Self, _, _>(|this, _| {
this.currently_dragged = None; this.currently_dragged = None;
}); });
}); });
}) })
.on_up_out(MouseButton::Left, |_, cx| { .on_up_out(MouseButton::Left, |_, _, cx| {
cx.defer(|cx| { cx.window_context().defer(|cx| {
cx.update_global::<Self, _, _>(|this, _| { cx.update_global::<Self, _, _>(|this, _| {
this.currently_dragged = None; 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 { if let Some(State::Dragging {
payload, window_id, .. payload, window_id, ..
}) = &self.currently_dragged }) = &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() { if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
self.notify_containers_for_window(window_id, cx); 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| { self.containers.retain(|container| {
if let Some(container) = container.upgrade(cx) { if let Some(container) = container.upgrade(cx) {
if container.window_id() == window_id { if container.window_id() == window_id {
@ -294,35 +296,35 @@ impl<V: View> DragAndDrop<V> {
} }
} }
pub trait Draggable { pub trait Draggable<V: View> {
fn as_draggable<V: View, P: Any>( fn as_draggable<D: View, P: Any>(
self, self,
payload: P, payload: P,
render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox, render: impl 'static + Fn(&P, &mut ViewContext<D>) -> Element<D>,
) -> Self ) -> Self
where where
Self: Sized; Self: Sized;
} }
impl<Tag> Draggable for MouseEventHandler<Tag> { impl<Tag, V: View> Draggable<V> for MouseEventHandler<Tag, V> {
fn as_draggable<V: View, P: Any>( fn as_draggable<D: View, P: Any>(
self, self,
payload: P, payload: P,
render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox, render: impl 'static + Fn(&P, &mut ViewContext<D>) -> Element<D>,
) -> Self ) -> Self
where where
Self: Sized, Self: Sized,
{ {
let payload = Rc::new(payload); let payload = Rc::new(payload);
let render = Rc::new(render); let render = Rc::new(render);
self.on_down(MouseButton::Left, move |e, cx| { self.on_down(MouseButton::Left, move |e, _, cx| {
cx.propagate_event(); 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 payload = payload.clone();
let render = render.clone(); let render = render.clone();
DragAndDrop::<V>::dragging(e, payload, cx, render) DragAndDrop::<D>::dragging(e, payload, cx, render)
}) })
} }
} }

View file

@ -2,9 +2,9 @@ use super::{
wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot}, wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
TextHighlights, TextHighlights,
}; };
use crate::{Anchor, ExcerptId, ExcerptRange, ToPoint as _}; use crate::{Anchor, Editor, ExcerptId, ExcerptRange, ToPoint as _};
use collections::{Bound, HashMap, HashSet}; use collections::{Bound, HashMap, HashSet};
use gpui::{fonts::HighlightStyle, ElementBox, RenderContext}; use gpui::{fonts::HighlightStyle, Element, ViewContext};
use language::{BufferSnapshot, Chunk, Patch, Point}; use language::{BufferSnapshot, Chunk, Patch, Point};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{
@ -50,7 +50,7 @@ struct BlockRow(u32);
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
struct WrapRow(u32); 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 { pub struct Block {
id: BlockId, id: BlockId,
@ -69,7 +69,7 @@ where
pub position: P, pub position: P,
pub height: u8, pub height: u8,
pub style: BlockStyle, pub style: BlockStyle,
pub render: Arc<dyn Fn(&mut BlockContext) -> ElementBox>, pub render: Arc<dyn Fn(&mut BlockContext) -> Element<Editor>>,
pub disposition: BlockDisposition, pub disposition: BlockDisposition,
} }
@ -80,8 +80,8 @@ pub enum BlockStyle {
Sticky, Sticky,
} }
pub struct BlockContext<'a, 'b> { pub struct BlockContext<'a, 'b, 'c, 'd> {
pub cx: &'b mut RenderContext<'a, crate::Editor>, pub view_context: &'d mut ViewContext<'a, 'b, 'c, Editor>,
pub anchor_x: f32, pub anchor_x: f32,
pub scroll_x: f32, pub scroll_x: f32,
pub gutter_width: f32, pub gutter_width: f32,
@ -932,22 +932,22 @@ impl BlockDisposition {
} }
} }
impl<'a, 'b> Deref for BlockContext<'a, 'b> { impl<'a, 'b, 'c, 'd> Deref for BlockContext<'a, 'b, 'c, 'd> {
type Target = RenderContext<'a, crate::Editor>; type Target = ViewContext<'a, 'b, 'c, Editor>;
fn deref(&self) -> &Self::Target { 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 { fn deref_mut(&mut self) -> &mut Self::Target {
self.cx self.view_context
} }
} }
impl Block { impl Block {
pub fn render(&self, cx: &mut BlockContext) -> ElementBox { pub fn render(&self, cx: &mut BlockContext) -> Element<Editor> {
self.render.lock()(cx) self.render.lock()(cx)
} }
@ -994,7 +994,7 @@ mod tests {
use crate::display_map::suggestion_map::SuggestionMap; use crate::display_map::suggestion_map::SuggestionMap;
use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
use crate::multi_buffer::MultiBuffer; use crate::multi_buffer::MultiBuffer;
use gpui::{elements::Empty, Element}; use gpui::{elements::Empty, Drawable};
use rand::prelude::*; use rand::prelude::*;
use settings::Settings; use settings::Settings;
use std::env; use std::env;

View file

@ -41,8 +41,8 @@ use gpui::{
keymap_matcher::KeymapContext, keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
serde_json::{self, json}, serde_json::{self, json},
AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Drawable, Element, Entity,
ModelHandle, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
}; };
use highlight_matching_bracket::refresh_matching_bracket_highlights; use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HideHover, HoverState}; use hover_popover::{hide_hover, HideHover, HoverState};
@ -724,8 +724,8 @@ impl ContextMenu {
&self, &self,
cursor_position: DisplayPoint, cursor_position: DisplayPoint,
style: EditorStyle, style: EditorStyle,
cx: &mut RenderContext<Editor>, cx: &mut ViewContext<Editor>,
) -> (DisplayPoint, ElementBox) { ) -> (DisplayPoint, Element<Editor>) {
match self { match self {
ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)), ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)),
ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
@ -777,7 +777,7 @@ impl CompletionsMenu {
!self.matches.is_empty() !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 {} enum CompletionTag {}
let completions = self.completions.clone(); let completions = self.completions.clone();
@ -794,7 +794,7 @@ impl CompletionsMenu {
let completion = &completions[mat.candidate_id]; let completion = &completions[mat.candidate_id];
let item_ix = start_ix + ix; let item_ix = start_ix + ix;
items.push( items.push(
MouseEventHandler::<CompletionTag>::new( MouseEventHandler::<CompletionTag, _>::new(
mat.candidate_id, mat.candidate_id,
cx, cx,
|state, _| { |state, _| {
@ -823,7 +823,7 @@ impl CompletionsMenu {
}, },
) )
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_down(MouseButton::Left, move |_, cx| { .on_down(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ConfirmCompletion { cx.dispatch_action(ConfirmCompletion {
item_ix: Some(item_ix), item_ix: Some(item_ix),
}); });
@ -953,8 +953,8 @@ impl CodeActionsMenu {
&self, &self,
mut cursor_position: DisplayPoint, mut cursor_position: DisplayPoint,
style: EditorStyle, style: EditorStyle,
cx: &mut RenderContext<Editor>, cx: &mut ViewContext<Editor>,
) -> (DisplayPoint, ElementBox) { ) -> (DisplayPoint, Element<Editor>) {
enum ActionTag {} enum ActionTag {}
let container_style = style.autocomplete.container; let container_style = style.autocomplete.container;
@ -969,7 +969,7 @@ impl CodeActionsMenu {
for (ix, action) in actions[range].iter().enumerate() { for (ix, action) in actions[range].iter().enumerate() {
let item_ix = start_ix + ix; let item_ix = start_ix + ix;
items.push( items.push(
MouseEventHandler::<ActionTag>::new(item_ix, cx, |state, _| { MouseEventHandler::<ActionTag, _>::new(item_ix, cx, |state, _| {
let item_style = if item_ix == selected_item { let item_style = if item_ix == selected_item {
style.autocomplete.selected_item style.autocomplete.selected_item
} else if state.hovered() { } else if state.hovered() {
@ -985,7 +985,7 @@ impl CodeActionsMenu {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_down(MouseButton::Left, move |_, cx| { .on_down(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(ConfirmCodeAction { cx.dispatch_action(ConfirmCodeAction {
item_ix: Some(item_ix), item_ix: Some(item_ix),
}); });
@ -1349,7 +1349,7 @@ impl Editor {
self.buffer().read(cx).title(cx) 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 { EditorSnapshot {
mode: self.mode, mode: self.mode,
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)), display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
@ -2518,7 +2518,7 @@ impl Editor {
this.update_visible_copilot_suggestion(cx); this.update_visible_copilot_suggestion(cx);
} }
} }
}); })?;
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
} }
@ -2655,11 +2655,13 @@ impl Editor {
prev_task.await; prev_task.await;
task = this task = this
.upgrade(&cx) .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.upgrade(&cx)
this.update(&mut cx, |this, cx| { .ok_or_else(|| anyhow!("editor dropped"))?
.update(&mut cx, |this, cx| {
if this.focused { if this.focused {
if let Some((buffer, actions)) = this.available_code_actions.clone() { if let Some((buffer, actions)) = this.available_code_actions.clone() {
this.show_context_menu( this.show_context_menu(
@ -2674,8 +2676,8 @@ impl Editor {
); );
} }
} }
}) })?;
}
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
}) })
.detach_and_log_err(cx); .detach_and_log_err(cx);
@ -2787,7 +2789,7 @@ impl Editor {
cx, cx,
); );
}); });
}); })?;
Ok(()) Ok(())
} }
@ -2818,6 +2820,7 @@ impl Editor {
}); });
cx.notify(); cx.notify();
}) })
.log_err();
} }
})); }));
None None
@ -2907,7 +2910,8 @@ impl Editor {
cx, cx,
); );
cx.notify(); cx.notify();
}); })
.log_err();
} }
})); }));
None None
@ -2953,20 +2957,21 @@ impl Editor {
.flatten() .flatten()
.collect_vec(); .collect_vec();
this.upgrade(&cx)?.update(&mut cx, |this, cx| { this.upgrade(&cx)?
if !completions.is_empty() { .update(&mut cx, |this, cx| {
this.copilot_state.cycled = false; if !completions.is_empty() {
this.copilot_state.pending_cycling_refresh = Task::ready(None); this.copilot_state.cycled = false;
this.copilot_state.completions.clear(); this.copilot_state.pending_cycling_refresh = Task::ready(None);
this.copilot_state.active_completion_index = 0; this.copilot_state.completions.clear();
this.copilot_state.excerpt_id = Some(cursor.excerpt_id); this.copilot_state.active_completion_index = 0;
for completion in completions { this.copilot_state.excerpt_id = Some(cursor.excerpt_id);
this.copilot_state.push_completion(completion); 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(()) Some(())
}); });
@ -2997,14 +3002,16 @@ impl Editor {
}) })
.await; .await;
this.upgrade(&cx)?.update(&mut cx, |this, cx| { this.upgrade(&cx)?
this.copilot_state.cycled = true; .update(&mut cx, |this, cx| {
for completion in completions.log_err().into_iter().flatten() { this.copilot_state.cycled = true;
this.copilot_state.push_completion(completion); 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.copilot_state.cycle_completions(direction);
}); this.update_visible_copilot_suggestion(cx);
})
.log_err()?;
Some(()) Some(())
}); });
@ -3123,19 +3130,19 @@ impl Editor {
&self, &self,
style: &EditorStyle, style: &EditorStyle,
active: bool, active: bool,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<ElementBox> { ) -> Option<Element<Self>> {
if self.available_code_actions.is_some() { if self.available_code_actions.is_some() {
enum CodeActions {} enum CodeActions {}
Some( Some(
MouseEventHandler::<CodeActions>::new(0, cx, |state, _| { MouseEventHandler::<CodeActions, _>::new(0, cx, |state, _| {
Svg::new("icons/bolt_8.svg") Svg::new("icons/bolt_8.svg")
.with_color(style.code_actions.indicator.style_for(state, active).color) .with_color(style.code_actions.indicator.style_for(state, active).color)
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(3.)) .with_padding(Padding::uniform(3.))
.on_down(MouseButton::Left, |_, cx| { .on_down(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(ToggleCodeActions { cx.dispatch_action(ToggleCodeActions {
deployed_from_indicator: true, deployed_from_indicator: true,
}); });
@ -3154,8 +3161,8 @@ impl Editor {
gutter_hovered: bool, gutter_hovered: bool,
line_height: f32, line_height: f32,
gutter_margin: f32, gutter_margin: f32,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> Vec<Option<ElementBox>> { ) -> Vec<Option<Element<Self>>> {
enum FoldIndicators {} enum FoldIndicators {}
let style = style.folds.clone(); let style = style.folds.clone();
@ -3167,10 +3174,10 @@ impl Editor {
fold_data fold_data
.map(|(fold_status, buffer_row, active)| { .map(|(fold_status, buffer_row, active)| {
(active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| { (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
MouseEventHandler::<FoldIndicators>::new( MouseEventHandler::<FoldIndicators, _>::new(
ix as usize, ix as usize,
cx, cx,
|mouse_state, _| -> ElementBox { |mouse_state, _| -> Element<Editor> {
Svg::new(match fold_status { Svg::new(match fold_status {
FoldStatus::Folded => style.folded_icon.clone(), FoldStatus::Folded => style.folded_icon.clone(),
FoldStatus::Foldable => style.foldable_icon.clone(), FoldStatus::Foldable => style.foldable_icon.clone(),
@ -3197,7 +3204,7 @@ impl Editor {
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(3.)) .with_padding(Padding::uniform(3.))
.on_click(MouseButton::Left, { .on_click(MouseButton::Left, {
move |_, cx| { move |_, _, cx| {
cx.dispatch_any_action(match fold_status { cx.dispatch_any_action(match fold_status {
FoldStatus::Folded => Box::new(UnfoldAt { buffer_row }), FoldStatus::Folded => Box::new(UnfoldAt { buffer_row }),
FoldStatus::Foldable => Box::new(FoldAt { buffer_row }), FoldStatus::Foldable => Box::new(FoldAt { buffer_row }),
@ -3222,8 +3229,8 @@ impl Editor {
&self, &self,
cursor_position: DisplayPoint, cursor_position: DisplayPoint,
style: EditorStyle, style: EditorStyle,
cx: &mut RenderContext<Editor>, cx: &mut ViewContext<Editor>,
) -> Option<(DisplayPoint, ElementBox)> { ) -> Option<(DisplayPoint, Element<Editor>)> {
self.context_menu self.context_menu
.as_ref() .as_ref()
.map(|menu| menu.render(cursor_position, style, cx)) .map(|menu| menu.render(cursor_position, style, cx))
@ -4106,7 +4113,7 @@ impl Editor {
pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) { pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
self.transact(cx, |this, cx| { 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()); let mut clipboard_text = Cow::Borrowed(item.text());
if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() { if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
let old_selections = this.selections.all::<usize>(cx); let old_selections = this.selections.all::<usize>(cx);
@ -5610,7 +5617,7 @@ impl Editor {
let definitions = definitions.await?; let definitions = definitions.await?;
workspace.update(&mut cx, |workspace, cx| { workspace.update(&mut cx, |workspace, cx| {
Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx); Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx);
}); })?;
Ok::<(), anyhow::Error>(()) Ok::<(), anyhow::Error>(())
}) })
@ -5711,7 +5718,7 @@ impl Editor {
Self::open_locations_in_multibuffer( Self::open_locations_in_multibuffer(
workspace, locations, replica_id, title, cx, workspace, locations, replica_id, title, cx,
); );
}); })?;
Ok(()) Ok(())
}, },
@ -5899,7 +5906,7 @@ impl Editor {
editor: rename_editor, editor: rename_editor,
block_id, block_id,
}); });
}); })?;
} }
Ok(()) Ok(())
@ -5945,7 +5952,7 @@ impl Editor {
editor.update(&mut cx, |editor, cx| { editor.update(&mut cx, |editor, cx| {
editor.refresh_document_highlights(cx); editor.refresh_document_highlights(cx);
}); })?;
Ok(()) Ok(())
})) }))
} }
@ -5988,7 +5995,7 @@ impl Editor {
self.pending_rename.as_ref() 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 { let project = match &self.project {
Some(project) => project.clone(), Some(project) => project.clone(),
None => return None, None => return None,
@ -6001,7 +6008,7 @@ impl Editor {
&mut self, &mut self,
project: ModelHandle<Project>, project: ModelHandle<Project>,
trigger: FormatTrigger, trigger: FormatTrigger,
cx: &mut ViewContext<'_, Self>, cx: &mut ViewContext<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let buffer = self.buffer().clone(); let buffer = self.buffer().clone();
let buffers = buffer.read(cx).all_buffers(); let buffers = buffer.read(cx).all_buffers();
@ -6747,9 +6754,16 @@ impl Editor {
let position = action.position; let position = action.position;
let anchor = action.anchor; let anchor = action.anchor;
cx.spawn_weak(|_, mut cx| async move { 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| { 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 buffer = buffer.read(cx);
let cursor = if buffer.can_resolve(&anchor) { let cursor = if buffer.can_resolve(&anchor) {
language::ToPoint::to_point(&anchor, buffer) language::ToPoint::to_point(&anchor, buffer)
@ -6763,11 +6777,11 @@ impl Editor {
}); });
editor.nav_history = nav_history; editor.nav_history = nav_history;
Some(()) anyhow::Ok(())
})?; })??;
Some(()) anyhow::Ok(())
}) })
.detach() .detach_and_log_err(cx);
} }
fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> { fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
@ -6993,7 +7007,7 @@ impl Entity for Editor {
} }
impl View 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 style = self.style(cx);
let font_changed = self.display_map.update(cx, |map, cx| { let font_changed = self.display_map.update(cx, |map, cx| {
map.set_fold_ellipses_color(style.folds.ellipses.text_color); map.set_fold_ellipses_color(style.folds.ellipses.text_color);
@ -7001,19 +7015,14 @@ impl View for Editor {
}); });
if font_changed { if font_changed {
let handle = self.handle.clone(); cx.defer(move |editor, cx: &mut ViewContext<Editor>| {
cx.defer(move |cx| { hide_hover(editor, &HideHover, cx);
if let Some(editor) = handle.upgrade(cx) { hide_link_definition(editor, cx);
editor.update(cx, |editor, cx| {
hide_hover(editor, &HideHover, cx);
hide_link_definition(editor, cx);
})
}
}); });
} }
Stack::new() 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()) .with_child(ChildView::new(&self.mouse_context_menu, cx).boxed())
.boxed() .boxed()
} }

View file

@ -9,7 +9,7 @@ use gpui::{
executor::Deterministic, executor::Deterministic,
geometry::{rect::RectF, vector::vec2f}, geometry::{rect::RectF, vector::vec2f},
platform::{WindowBounds, WindowOptions}, platform::{WindowBounds, WindowOptions},
serde_json, serde_json, TestAppContext,
}; };
use indoc::indoc; use indoc::indoc;
use language::{BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point}; use language::{BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point};
@ -23,13 +23,13 @@ use util::{
test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker}, test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
}; };
use workspace::{ use workspace::{
item::{FollowableItem, ItemHandle}, item::{FollowableItem, Item, ItemHandle},
NavigationEntry, Pane, ViewId, NavigationEntry, Pane, ViewId,
}; };
#[gpui::test] #[gpui::test]
fn test_edit_events(cx: &mut AppContext) { fn test_edit_events(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = cx.add_model(|cx| { let buffer = cx.add_model(|cx| {
let mut buffer = language::Buffer::new(0, "123456", cx); let mut buffer = language::Buffer::new(0, "123456", cx);
buffer.set_group_interval(Duration::from_secs(1)); 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 events = Rc::new(RefCell::new(Vec::new()));
let (_, editor1) = cx.add_window(Default::default(), { let (_, editor1) = cx.add_window({
let events = events.clone(); let events = events.clone();
|cx| { |cx| {
cx.subscribe(&cx.handle(), move |_, _, event, _| { cx.subscribe(&cx.handle(), move |_, _, event, _| {
@ -52,7 +52,7 @@ fn test_edit_events(cx: &mut AppContext) {
Editor::for_buffer(buffer.clone(), None, cx) Editor::for_buffer(buffer.clone(), None, cx)
} }
}); });
let (_, editor2) = cx.add_window(Default::default(), { let (_, editor2) = cx.add_window({
let events = events.clone(); let events = events.clone();
|cx| { |cx| {
cx.subscribe(&cx.handle(), move |_, _, event, _| { cx.subscribe(&cx.handle(), move |_, _, event, _| {
@ -155,13 +155,13 @@ fn test_edit_events(cx: &mut AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_undo_redo_with_selection_restoration(cx: &mut AppContext) { fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let mut now = Instant::now(); let mut now = Instant::now();
let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx)); 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 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.update(cx, |editor, cx| {
editor.start_transaction_at(now, cx); editor.start_transaction_at(now, cx);
@ -225,8 +225,8 @@ fn test_undo_redo_with_selection_restoration(cx: &mut AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_ime_composition(cx: &mut AppContext) { fn test_ime_composition(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = cx.add_model(|cx| { let buffer = cx.add_model(|cx| {
let mut buffer = language::Buffer::new(0, "abcde", cx); let mut buffer = language::Buffer::new(0, "abcde", cx);
// Ensure automatic grouping doesn't occur. // 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)); 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); let mut editor = build_editor(buffer.clone(), cx);
// Start a new IME composition. // Start a new IME composition.
@ -327,11 +327,13 @@ fn test_ime_composition(cx: &mut AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_selection_with_mouse(cx: &mut gpui::AppContext) { fn test_selection_with_mouse(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); 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(|cx| {
let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
build_editor(buffer, cx)
});
editor.update(cx, |view, cx| { editor.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(2, 2), false, 1, 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] #[gpui::test]
fn test_canceling_pending_selection(cx: &mut gpui::AppContext) { fn test_canceling_pending_selection(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
build_editor(buffer, cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(2, 2), false, 1, 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] #[gpui::test]
fn test_clone(cx: &mut gpui::AppContext) { fn test_clone(cx: &mut TestAppContext) {
let (text, selection_ranges) = marked_text_ranges( let (text, selection_ranges) = marked_text_ranges(
indoc! {" indoc! {"
one one
@ -435,10 +439,12 @@ fn test_clone(cx: &mut gpui::AppContext) {
"}, "},
true, true,
); );
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple(&text, 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.update(cx, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone())); 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<_>>(), snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
); );
assert_set_eq!( assert_set_eq!(
cloned_editor.read(cx).selections.ranges::<Point>(cx), cloned_editor.read_with(cx, |editor, cx| editor.selections.ranges::<Point>(cx)),
editor.read(cx).selections.ranges(cx) editor.read_with(cx, |editor, cx| editor.selections.ranges(cx))
); );
assert_set_eq!( assert_set_eq!(
cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)), cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)),
@ -480,19 +486,19 @@ fn test_clone(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_navigation_history(cx: &mut gpui::AppContext) { fn test_navigation_history(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
cx.set_global(DragAndDrop::<Workspace>::default()); cx.set_global(DragAndDrop::<Workspace>::default());
use workspace::item::Item; use workspace::item::Item;
let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(0, None, || &[], cx)); let (_, pane) = cx.add_window(|cx| Pane::new(0, None, || &[], cx));
let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
cx.add_view(&pane, |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 mut editor = build_editor(buffer.clone(), cx);
let handle = cx.handle(); let handle = cx.handle();
editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&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) editor.nav_history.as_mut().unwrap().pop_backward(cx)
} }
@ -590,10 +596,12 @@ fn test_navigation_history(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_cancel(cx: &mut gpui::AppContext) { fn test_cancel(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
build_editor(buffer, cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx); view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
@ -630,30 +638,32 @@ fn test_cancel(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_fold_action(cx: &mut gpui::AppContext) { fn test_fold_action(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple( let (_, view) = cx.add_window(|cx| {
&" let buffer = MultiBuffer::build_simple(
impl Foo { &"
// Hello! impl Foo {
// Hello!
fn a() { fn a() {
1 1
} }
fn b() { fn b() {
2 2
} }
fn c() { fn c() {
3 3
}
} }
} "
" .unindent(),
.unindent(), cx,
cx, );
); build_editor(buffer.clone(), cx)
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx)); });
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| { view.change_selections(None, cx, |s| {
@ -712,15 +722,15 @@ fn test_fold_action(cx: &mut gpui::AppContext) {
); );
view.unfold_lines(&UnfoldLines, cx); 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] #[gpui::test]
fn test_move_cursor(cx: &mut gpui::AppContext) { fn test_move_cursor(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx); let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx)); let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
buffer.update(cx, |buffer, cx| { buffer.update(cx, |buffer, cx| {
buffer.edit( buffer.edit(
@ -732,7 +742,6 @@ fn test_move_cursor(cx: &mut gpui::AppContext) {
cx, cx,
); );
}); });
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
assert_eq!( assert_eq!(
view.selections.display_ranges(cx), view.selections.display_ranges(cx),
@ -793,10 +802,12 @@ fn test_move_cursor(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_move_cursor_multibyte(cx: &mut gpui::AppContext) { fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx)); let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
build_editor(buffer.clone(), cx)
});
assert_eq!('ⓐ'.len_utf8(), 3); assert_eq!('ⓐ'.len_utf8(), 3);
assert_eq!('α'.len_utf8(), 2); assert_eq!('α'.len_utf8(), 2);
@ -895,10 +906,12 @@ fn test_move_cursor_multibyte(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_move_cursor_different_line_lengths(cx: &mut gpui::AppContext) { fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx)); let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
build_editor(buffer.clone(), cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| { view.change_selections(None, cx, |s| {
s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]); 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] #[gpui::test]
fn test_beginning_end_of_line(cx: &mut gpui::AppContext) { fn test_beginning_end_of_line(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple("abc\n def", cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); let buffer = MultiBuffer::build_simple("abc\n def", cx);
build_editor(buffer, cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| { view.change_selections(None, cx, |s| {
s.select_display_ranges([ s.select_display_ranges([
@ -1102,10 +1117,12 @@ fn test_beginning_end_of_line(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_prev_next_word_boundary(cx: &mut gpui::AppContext) { fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| 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(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, 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.update(cx, |view, cx| {
view.change_selections(None, cx, |s| { view.change_selections(None, cx, |s| {
s.select_display_ranges([ s.select_display_ranges([
@ -1151,10 +1168,12 @@ fn test_prev_next_word_boundary(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::AppContext) { fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| 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(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, 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.update(cx, |view, cx| {
view.set_wrap_width(Some(140.), 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] #[gpui::test]
fn test_delete_to_word_boundary(cx: &mut gpui::AppContext) { fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple("one two three four", cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx)); let buffer = MultiBuffer::build_simple("one two three four", cx);
build_editor(buffer.clone(), cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| { 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); 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.update(cx, |view, cx| {
view.change_selections(None, cx, |s| { view.change_selections(None, cx, |s| {
s.select_display_ranges([ 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); 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] #[gpui::test]
fn test_newline(cx: &mut gpui::AppContext) { fn test_newline(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx)); let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
build_editor(buffer.clone(), cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| { view.change_selections(None, cx, |s| {
@ -1385,24 +1406,23 @@ fn test_newline(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_newline_with_old_selections(cx: &mut gpui::AppContext) { fn test_newline_with_old_selections(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple( let (_, editor) = cx.add_window(|cx| {
" let buffer = MultiBuffer::build_simple(
a "
b( a
X b(
) X
c( )
X c(
) X
" )
.unindent() "
.as_str(), .unindent()
cx, .as_str(),
); cx,
);
let (_, editor) = cx.add_window(Default::default(), |cx| {
let mut editor = build_editor(buffer.clone(), cx); let mut editor = build_editor(buffer.clone(), cx);
editor.change_selections(None, cx, |s| { editor.change_selections(None, cx, |s| {
s.select_ranges([ s.select_ranges([
@ -1413,28 +1433,27 @@ fn test_newline_with_old_selections(cx: &mut gpui::AppContext) {
editor 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| { 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!( assert_eq!(
editor.selections.ranges(cx), editor.selections.ranges(cx),
&[ &[
@ -1566,22 +1585,21 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_insert_with_old_selections(cx: &mut gpui::AppContext) { fn test_insert_with_old_selections(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx); let (_, editor) = cx.add_window(|cx| {
let (_, editor) = cx.add_window(Default::default(), |cx| { let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
let mut editor = build_editor(buffer.clone(), 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.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
editor 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| { 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],); assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
editor.insert("Z", cx); editor.insert("Z", cx);
@ -1885,24 +1903,26 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_indent_outdent_with_excerpts(cx: &mut gpui::AppContext) { fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
cx.set_global( cx.update(|cx| {
Settings::test(cx) cx.set_global(
.with_language_defaults( Settings::test(cx)
"TOML", .with_language_defaults(
EditorSettings { "TOML",
tab_size: Some(2.try_into().unwrap()), EditorSettings {
..Default::default() tab_size: Some(2.try_into().unwrap()),
}, ..Default::default()
) },
.with_language_defaults( )
"Rust", .with_language_defaults(
EditorSettings { "Rust",
tab_size: Some(4.try_into().unwrap()), EditorSettings {
..Default::default() tab_size: Some(4.try_into().unwrap()),
}, ..Default::default()
), },
); ),
);
});
let toml_language = Arc::new(Language::new( let toml_language = Arc::new(Language::new(
LanguageConfig { LanguageConfig {
name: "TOML".into(), name: "TOML".into(),
@ -1944,7 +1964,7 @@ fn test_indent_outdent_with_excerpts(cx: &mut gpui::AppContext) {
multibuffer multibuffer
}); });
cx.add_window(Default::default(), |cx| { cx.add_window(|cx| {
let mut editor = build_editor(multibuffer, cx); let mut editor = build_editor(multibuffer, cx);
assert_eq!( assert_eq!(
@ -2071,10 +2091,12 @@ async fn test_delete(cx: &mut gpui::TestAppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_delete_line(cx: &mut gpui::AppContext) { fn test_delete_line(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
build_editor(buffer, cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| { view.change_selections(None, cx, |s| {
s.select_display_ranges([ s.select_display_ranges([
@ -2094,9 +2116,11 @@ fn test_delete_line(cx: &mut gpui::AppContext) {
); );
}); });
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
build_editor(buffer, cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| { view.change_selections(None, cx, |s| {
s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)]) 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] #[gpui::test]
fn test_duplicate_line(cx: &mut gpui::AppContext) { fn test_duplicate_line(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
build_editor(buffer, cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| { view.change_selections(None, cx, |s| {
s.select_display_ranges([ 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(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
build_editor(buffer, cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| { view.change_selections(None, cx, |s| {
s.select_display_ranges([ s.select_display_ranges([
@ -2159,10 +2187,12 @@ fn test_duplicate_line(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_move_line_up_down(cx: &mut gpui::AppContext) { fn test_move_line_up_down(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
build_editor(buffer, cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.fold_ranges( view.fold_ranges(
vec![ vec![
@ -2255,12 +2285,14 @@ fn test_move_line_up_down(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_move_line_up_down_with_blocks(cx: &mut gpui::AppContext) { fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); let (_, editor) = cx.add_window(|cx| {
let snapshot = buffer.read(cx).snapshot(cx); let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); build_editor(buffer, cx)
});
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
let snapshot = editor.buffer.read(cx).snapshot(cx);
editor.insert_blocks( editor.insert_blocks(
[BlockProperties { [BlockProperties {
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
@ -2279,11 +2311,11 @@ fn test_move_line_up_down_with_blocks(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_transpose(cx: &mut gpui::AppContext) { fn test_transpose(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
_ = cx _ = cx
.add_window(Default::default(), |cx| { .add_window(|cx| {
let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx); let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
editor.change_selections(None, cx, |s| s.select_ranges([1..1])); editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
@ -2304,7 +2336,7 @@ fn test_transpose(cx: &mut gpui::AppContext) {
.1; .1;
_ = cx _ = cx
.add_window(Default::default(), |cx| { .add_window(|cx| {
let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx); let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
editor.change_selections(None, cx, |s| s.select_ranges([3..3])); editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
@ -2330,7 +2362,7 @@ fn test_transpose(cx: &mut gpui::AppContext) {
.1; .1;
_ = cx _ = cx
.add_window(Default::default(), |cx| { .add_window(|cx| {
let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), 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])); 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; .1;
_ = cx _ = cx
.add_window(Default::default(), |cx| { .add_window(|cx| {
let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx); let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
editor.change_selections(None, cx, |s| s.select_ranges([4..4])); 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] #[gpui::test]
fn test_select_all(cx: &mut gpui::AppContext) { fn test_select_all(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
build_editor(buffer, cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.select_all(&SelectAll, cx); view.select_all(&SelectAll, cx);
assert_eq!( assert_eq!(
@ -2587,10 +2621,12 @@ fn test_select_all(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_select_line(cx: &mut gpui::AppContext) { fn test_select_line(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
build_editor(buffer, cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| { view.change_selections(None, cx, |s| {
s.select_display_ranges([ s.select_display_ranges([
@ -2631,10 +2667,12 @@ fn test_select_line(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_split_selection_into_lines(cx: &mut gpui::AppContext) { fn test_split_selection_into_lines(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
build_editor(buffer, cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.fold_ranges( view.fold_ranges(
vec![ vec![
@ -2699,10 +2737,12 @@ fn test_split_selection_into_lines(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_add_selection_above_below(cx: &mut gpui::AppContext) { fn test_add_selection_above_below(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx); let (_, view) = cx.add_window(|cx| {
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
build_editor(buffer, cx)
});
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| { 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)); editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(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 fake_server
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move { .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!( assert_eq!(
@ -4057,7 +4097,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
futures::future::pending::<()>().await; futures::future::pending::<()>().await;
unreachable!() 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().advance_clock(super::FORMAT_TIMEOUT);
cx.foreground().start_waiting(); cx.foreground().start_waiting();
save.await.unwrap(); 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 fake_server
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move { .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!( 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)); editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(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 fake_server
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move { .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
assert_eq!( assert_eq!(
@ -4173,7 +4213,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
unreachable!() 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().advance_clock(super::FORMAT_TIMEOUT);
cx.foreground().start_waiting(); cx.foreground().start_waiting();
save.await.unwrap(); 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 fake_server
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move { .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
assert_eq!( assert_eq!(
@ -4977,8 +5017,8 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_editing_disjoint_excerpts(cx: &mut gpui::AppContext) { fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); 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 buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
let multibuffer = cx.add_model(|cx| { let multibuffer = cx.add_model(|cx| {
let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0);
@ -4996,12 +5036,11 @@ fn test_editing_disjoint_excerpts(cx: &mut gpui::AppContext) {
], ],
cx, cx,
); );
assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
multibuffer multibuffer
}); });
assert_eq!(multibuffer.read(cx).read(cx).text(), "aaaa\nbbbb"); let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
assert_eq!(view.text(cx), "aaaa\nbbbb"); assert_eq!(view.text(cx), "aaaa\nbbbb");
view.change_selections(None, cx, |s| { view.change_selections(None, cx, |s| {
@ -5024,8 +5063,8 @@ fn test_editing_disjoint_excerpts(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_editing_overlapping_excerpts(cx: &mut gpui::AppContext) { fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); cx.update(|cx| cx.set_global(Settings::test(cx)));
let markers = vec![('[', ']').into(), ('(', ')').into()]; let markers = vec![('[', ']').into(), ('(', ')').into()];
let (initial_text, mut excerpt_ranges) = marked_text_ranges_by( let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
indoc! {" indoc! {"
@ -5049,7 +5088,7 @@ fn test_editing_overlapping_excerpts(cx: &mut gpui::AppContext) {
multibuffer 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| { view.update(cx, |view, cx| {
let (expected_text, selection_ranges) = marked_text_ranges( let (expected_text, selection_ranges) = marked_text_ranges(
indoc! {" indoc! {"
@ -5097,8 +5136,8 @@ fn test_editing_overlapping_excerpts(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_refresh_selections(cx: &mut gpui::AppContext) { fn test_refresh_selections(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); 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 buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
let mut excerpt1_id = None; let mut excerpt1_id = None;
let multibuffer = cx.add_model(|cx| { let multibuffer = cx.add_model(|cx| {
@ -5120,13 +5159,11 @@ fn test_refresh_selections(cx: &mut gpui::AppContext) {
) )
.into_iter() .into_iter()
.next(); .next();
assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
multibuffer multibuffer
}); });
assert_eq!(
multibuffer.read(cx).read(cx).text(), let (_, editor) = cx.add_window(|cx| {
"aaaa\nbbbb\nbbbb\ncccc"
);
let (_, editor) = cx.add_window(Default::default(), |cx| {
let mut editor = build_editor(multibuffer.clone(), cx); let mut editor = build_editor(multibuffer.clone(), cx);
let snapshot = editor.snapshot(cx); let snapshot = editor.snapshot(cx);
editor.change_selections(None, cx, |s| { editor.change_selections(None, cx, |s| {
@ -5183,8 +5220,8 @@ fn test_refresh_selections(cx: &mut gpui::AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::AppContext) { fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
cx.set_global(Settings::test(cx)); 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 buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
let mut excerpt1_id = None; let mut excerpt1_id = None;
let multibuffer = cx.add_model(|cx| { let multibuffer = cx.add_model(|cx| {
@ -5206,13 +5243,11 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::AppContext)
) )
.into_iter() .into_iter()
.next(); .next();
assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
multibuffer multibuffer
}); });
assert_eq!(
multibuffer.read(cx).read(cx).text(), let (_, editor) = cx.add_window(|cx| {
"aaaa\nbbbb\nbbbb\ncccc"
);
let (_, editor) = cx.add_window(Default::default(), |cx| {
let mut editor = build_editor(multibuffer.clone(), cx); let mut editor = build_editor(multibuffer.clone(), cx);
let snapshot = editor.snapshot(cx); let snapshot = editor.snapshot(cx);
editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, 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] #[gpui::test]
fn test_highlighted_ranges(cx: &mut gpui::AppContext) { fn test_highlighted_ranges(cx: &mut TestAppContext) {
let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx); cx.update(|cx| cx.set_global(Settings::test(cx)));
let (_, editor) = cx.add_window(|cx| {
cx.set_global(Settings::test(cx)); let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx)); build_editor(buffer.clone(), cx)
});
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
struct Type1; struct Type1;
struct Type2; struct Type2;
let buffer = buffer.read(cx).snapshot(cx); let buffer = editor.buffer.read(cx).snapshot(cx);
let anchor_range = let anchor_range =
|range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end); |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@ use gpui::{
elements::{Flex, MouseEventHandler, Padding, Text}, elements::{Flex, MouseEventHandler, Padding, Text},
impl_internal_actions, impl_internal_actions,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
AppContext, Axis, Element, ElementBox, ModelHandle, RenderContext, Task, ViewContext, AppContext, Axis, Drawable, Element, ModelHandle, Task, ViewContext,
}; };
use language::{Bias, DiagnosticEntry, DiagnosticSeverity}; use language::{Bias, DiagnosticEntry, DiagnosticSeverity};
use project::{HoverBlock, Project}; use project::{HoverBlock, Project};
@ -208,7 +208,7 @@ fn show_hover(
local_diagnostic, local_diagnostic,
primary_diagnostic, primary_diagnostic,
}); });
}); })?;
} }
// Construct new hover popover from hover request // Construct new hover popover from hover request
@ -254,7 +254,7 @@ fn show_hover(
this.hover_state.info_popover = hover_popover; this.hover_state.info_popover = hover_popover;
cx.notify(); cx.notify();
}); })?;
} }
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
} }
@ -282,8 +282,8 @@ impl HoverState {
snapshot: &EditorSnapshot, snapshot: &EditorSnapshot,
style: &EditorStyle, style: &EditorStyle,
visible_rows: Range<u32>, visible_rows: Range<u32>,
cx: &mut RenderContext<Editor>, cx: &mut ViewContext<Editor>,
) -> Option<(DisplayPoint, Vec<ElementBox>)> { ) -> Option<(DisplayPoint, Vec<Element<Editor>>)> {
// If there is a diagnostic, position the popovers based on that. // If there is a diagnostic, position the popovers based on that.
// Otherwise use the start of the hover range // Otherwise use the start of the hover range
let anchor = self let anchor = self
@ -323,9 +323,9 @@ pub struct InfoPopover {
} }
impl InfoPopover { impl InfoPopover {
pub fn render(&self, style: &EditorStyle, cx: &mut RenderContext<Editor>) -> ElementBox { pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> Element<Editor> {
MouseEventHandler::<InfoPopover>::new(0, cx, |_, cx| { MouseEventHandler::<InfoPopover, _>::new(0, cx, |_, cx| {
let mut flex = Flex::new(Axis::Vertical).scrollable::<HoverBlock, _>(1, None, cx); let mut flex = Flex::new(Axis::Vertical).scrollable::<HoverBlock>(1, None, cx);
flex.extend(self.contents.iter().map(|content| { flex.extend(self.contents.iter().map(|content| {
let languages = self.project.read(cx).languages(); let languages = self.project.read(cx).languages();
if let Some(language) = content.language.clone().and_then(|language| { if let Some(language) = content.language.clone().and_then(|language| {
@ -360,7 +360,7 @@ impl InfoPopover {
.with_style(style.hover_popover.container) .with_style(style.hover_popover.container)
.boxed() .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_cursor_style(CursorStyle::Arrow)
.with_padding(Padding { .with_padding(Padding {
bottom: HOVER_POPOVER_GAP, bottom: HOVER_POPOVER_GAP,
@ -378,7 +378,7 @@ pub struct DiagnosticPopover {
} }
impl 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 {} enum PrimaryDiagnostic {}
let mut text_style = style.hover_popover.prose.clone(); let mut text_style = style.hover_popover.prose.clone();
@ -394,7 +394,7 @@ impl DiagnosticPopover {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone(); 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) Text::new(self.local_diagnostic.diagnostic.message.clone(), text_style)
.with_soft_wrap(true) .with_soft_wrap(true)
.contained() .contained()
@ -406,12 +406,12 @@ impl DiagnosticPopover {
bottom: HOVER_POPOVER_GAP, bottom: HOVER_POPOVER_GAP,
..Default::default() ..Default::default()
}) })
.on_move(|_, _| {}) // Consume move events so they don't reach regions underneath. .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath.
.on_click(MouseButton::Left, |_, cx| { .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(GoToDiagnostic) cx.dispatch_action(GoToDiagnostic)
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<PrimaryDiagnostic, _>( .with_tooltip::<PrimaryDiagnostic>(
0, 0,
"Go To Diagnostic".to_string(), "Go To Diagnostic".to_string(),
Some(Box::new(crate::GoToDiagnostic)), Some(Box::new(crate::GoToDiagnostic)),

View file

@ -8,7 +8,7 @@ use collections::HashSet;
use futures::future::try_join_all; use futures::future::try_join_all;
use gpui::{ use gpui::{
elements::*, geometry::vector::vec2f, AppContext, AsyncAppContext, Entity, ModelHandle, elements::*, geometry::vector::vec2f, AppContext, AsyncAppContext, Entity, ModelHandle,
RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
}; };
use language::{ use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
@ -28,7 +28,7 @@ use std::{
}; };
use text::Selection; use text::Selection;
use util::{ResultExt, TryFutureExt}; use util::{ResultExt, TryFutureExt};
use workspace::item::FollowableItemHandle; use workspace::item::{BreadcrumbText, FollowableItemHandle};
use workspace::{ use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem}, item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, 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| { pane.update(&mut cx, |_, cx| {
let multibuffer = cx.add_model(|cx| { let multibuffer = cx.add_model(|cx| {
let mut multibuffer; let mut multibuffer;
@ -121,8 +123,8 @@ impl FollowableItem for Editor {
editor.remote_id = Some(remote_id); editor.remote_id = Some(remote_id);
editor editor
}) })
}) })?
}); };
update_editor_from_message( update_editor_from_message(
editor.clone(), editor.clone(),
@ -367,7 +369,7 @@ async fn update_editor_from_message(
multibuffer.remove_excerpts(removed_excerpt_ids, cx); multibuffer.remove_excerpts(removed_excerpt_ids, cx);
}); });
}); })?;
// Deserialize the editor state. // Deserialize the editor state.
let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| { 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 .scroll_top_anchor
.and_then(|anchor| deserialize_anchor(&buffer, anchor)); .and_then(|anchor| deserialize_anchor(&buffer, anchor));
anyhow::Ok((selections, pending_selection, scroll_top_anchor)) anyhow::Ok((selections, pending_selection, scroll_top_anchor))
})?; })??;
// Wait until the buffer has received all of the operations referenced by // Wait until the buffer has received all of the operations referenced by
// the editor's new state. // the editor's new state.
@ -399,7 +401,7 @@ async fn update_editor_from_message(
cx, cx,
) )
}) })
}) })?
.await?; .await?;
// Update the editor's state. // Update the editor's state.
@ -416,7 +418,7 @@ async fn update_editor_from_message(
cx, cx,
); );
} }
}); })?;
Ok(()) Ok(())
} }
@ -556,12 +558,12 @@ impl Item for Editor {
} }
} }
fn tab_content( fn tab_content<T: View>(
&self, &self,
detail: Option<usize>, detail: Option<usize>,
style: &theme::Tab, style: &theme::Tab,
cx: &AppContext, cx: &AppContext,
) -> ElementBox { ) -> Element<T> {
Flex::row() Flex::row()
.with_child( .with_child(
Label::new(self.title(cx).to_string(), style.label.clone()) Label::new(self.title(cx).to_string(), style.label.clone())
@ -641,7 +643,7 @@ impl Item for Editor {
self.report_event("save editor", cx); self.report_event("save editor", cx);
let format = self.perform_format(project.clone(), FormatTrigger::Save, cx); let format = self.perform_format(project.clone(), FormatTrigger::Save, cx);
let buffers = self.buffer().clone().read(cx).all_buffers(); 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?; format.await?;
if buffers.len() == 1 { if buffers.len() == 1 {
@ -705,7 +707,7 @@ impl Item for Editor {
let transaction = reload_buffers.log_err().await; let transaction = reload_buffers.log_err().await;
this.update(&mut cx, |editor, cx| { this.update(&mut cx, |editor, cx| {
editor.request_autoscroll(Autoscroll::fit(), cx) editor.request_autoscroll(Autoscroll::fit(), cx)
}); })?;
buffer.update(&mut cx, |buffer, _| { buffer.update(&mut cx, |buffer, _| {
if let Some(transaction) = transaction { if let Some(transaction) = transaction {
if !buffer.is_singleton() { if !buffer.is_singleton() {
@ -762,7 +764,7 @@ impl Item for Editor {
ToolbarItemLocation::PrimaryLeft { flex: None } 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 cursor = self.selections.newest_anchor().head();
let multibuffer = &self.buffer().read(cx); let multibuffer = &self.buffer().read(cx);
let (buffer_id, symbols) = let (buffer_id, symbols) =
@ -782,15 +784,13 @@ impl Item for Editor {
.map(|path| path.to_string_lossy().to_string()) .map(|path| path.to_string_lossy().to_string())
.unwrap_or_else(|| "untitled".to_string()); .unwrap_or_else(|| "untitled".to_string());
let filename_label = Label::new(filename, theme.workspace.breadcrumbs.default.text.clone()); let mut breadcrumbs = vec![BreadcrumbText {
let mut breadcrumbs = vec![filename_label.boxed()]; text: filename,
breadcrumbs.extend(symbols.into_iter().map(|symbol| { highlights: None,
Text::new( }];
symbol.text, breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
theme.workspace.breadcrumbs.default.text.clone(), text: symbol.text,
) highlights: Some(symbol.highlight_ranges),
.with_highlights(symbol.highlight_ranges)
.boxed()
})); }));
Some(breadcrumbs) Some(breadcrumbs)
} }
@ -1113,7 +1113,7 @@ impl View for CursorPosition {
"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 { if let Some(position) = self.position {
let theme = &cx.global::<Settings>().theme.workspace.status_bar; let theme = &cx.global::<Settings>().theme.workspace.status_bar;
let mut text = format!("{},{}", position.row + 1, position.column + 1); let mut text = format!("{},{}", position.row + 1, position.column + 1);

View file

@ -261,7 +261,7 @@ pub fn show_link_definition(
hide_link_definition(this, cx); hide_link_definition(this, cx);
} }
} }
}) })?;
} }
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())

View file

@ -248,10 +248,12 @@ impl ScrollManager {
self.hide_scrollbar_task = Some(cx.spawn_weak(|editor, mut cx| async move { self.hide_scrollbar_task = Some(cx.spawn_weak(|editor, mut cx| async move {
cx.background().timer(SCROLLBAR_SHOW_INTERVAL).await; cx.background().timer(SCROLLBAR_SHOW_INTERVAL).await;
if let Some(editor) = editor.upgrade(&cx) { if let Some(editor) = editor.upgrade(&cx) {
editor.update(&mut cx, |editor, cx| { editor
editor.scroll_manager.show_scrollbars = false; .update(&mut cx, |editor, cx| {
cx.notify(); editor.scroll_manager.show_scrollbars = false;
}); cx.notify();
})
.log_err();
} }
})); }));
} else { } else {

View file

@ -34,11 +34,10 @@ impl<'a> EditorTestContext<'a> {
crate::init(cx); crate::init(cx);
let (window_id, editor) = cx.add_window(Default::default(), |cx| { let (window_id, editor) = cx.add_window(Default::default(), |cx| {
cx.focus_self();
build_editor(MultiBuffer::build_simple("", cx), cx) build_editor(MultiBuffer::build_simple("", cx), cx)
}); });
editor.update(cx, |_, cx| cx.focus_self());
(window_id, editor) (window_id, editor)
}); });

View file

@ -1,7 +1,7 @@
use gpui::{ use gpui::{
elements::*, elements::*,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
Entity, RenderContext, View, ViewContext, Entity, View, ViewContext,
}; };
use settings::Settings; use settings::Settings;
use workspace::{item::ItemHandle, StatusItemView}; use workspace::{item::ItemHandle, StatusItemView};
@ -27,12 +27,12 @@ impl View for DeployFeedbackButton {
"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 active = self.active;
let theme = cx.global::<Settings>().theme.clone(); let theme = cx.global::<Settings>().theme.clone();
Stack::new() Stack::new()
.with_child( .with_child(
MouseEventHandler::<Self>::new(0, cx, |state, _| { MouseEventHandler::<Self, Self>::new(0, cx, |state, _| {
let style = &theme let style = &theme
.workspace .workspace
.status_bar .status_bar
@ -53,12 +53,12 @@ impl View for DeployFeedbackButton {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
if !active { if !active {
cx.dispatch_action(GiveFeedback) cx.dispatch_action(GiveFeedback)
} }
}) })
.with_tooltip::<Self, _>( .with_tooltip::<Self>(
0, 0,
"Send Feedback".into(), "Send Feedback".into(),
Some(Box::new(GiveFeedback)), Some(Box::new(GiveFeedback)),

View file

@ -13,8 +13,8 @@ use gpui::{
actions, actions,
elements::{ChildView, Flex, Label, ParentElement, Svg}, elements::{ChildView, Flex, Label, ParentElement, Svg},
platform::PromptLevel, platform::PromptLevel,
serde_json, AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, RenderContext, serde_json, AnyViewHandle, AppContext, Drawable, Element, Entity, ModelHandle, Task, View,
Task, View, ViewContext, ViewHandle, ViewContext, ViewHandle,
}; };
use isahc::Request; use isahc::Request;
use language::Buffer; use language::Buffer;
@ -134,24 +134,21 @@ impl FeedbackEditor {
if answer == Some(0) { if answer == Some(0) {
match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await { match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await {
Ok(_) => { Ok(_) => {
cx.update(|cx| { this.update(&mut cx, |_, cx| {
this.update(cx, |_, cx| { cx.dispatch_action(workspace::CloseActiveItem);
cx.dispatch_action(workspace::CloseActiveItem); })
}) .log_err();
});
} }
Err(error) => { Err(error) => {
log::error!("{}", error); log::error!("{}", error);
this.update(&mut cx, |_, cx| {
cx.update(|cx| { cx.prompt(
this.update(cx, |_, cx| { PromptLevel::Critical,
cx.prompt( FEEDBACK_SUBMISSION_ERROR_TEXT,
PromptLevel::Critical, &["OK"],
FEEDBACK_SUBMISSION_ERROR_TEXT, );
&["OK"], })
); .log_err();
})
});
} }
} }
} }
@ -221,10 +218,10 @@ impl FeedbackEditor {
.add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx)); .add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx));
workspace.add_item(Box::new(feedback_editor), 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" "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() ChildView::new(&self.editor, cx).boxed()
} }
@ -253,7 +250,12 @@ impl Item for FeedbackEditor {
Some("Send Feedback".into()) 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() Flex::row()
.with_child( .with_child(
Svg::new("icons/feedback_16.svg") Svg::new("icons/feedback_16.svg")

View file

@ -1,7 +1,7 @@
use gpui::{ use gpui::{
elements::{Flex, Label, MouseEventHandler, ParentElement, Text}, elements::{Flex, Label, MouseEventHandler, ParentElement, Text},
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle, Drawable, Element, Entity, View, ViewContext, ViewHandle,
}; };
use settings::Settings; use settings::Settings;
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView}; use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
@ -29,7 +29,7 @@ impl View for FeedbackInfoText {
"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(); let theme = cx.global::<Settings>().theme.clone();
Flex::row() Flex::row()
@ -43,7 +43,7 @@ impl View for FeedbackInfoText {
.boxed(), .boxed(),
) )
.with_child( .with_child(
MouseEventHandler::<OpenZedCommunityRepo>::new(0, cx, |state, _| { MouseEventHandler::<OpenZedCommunityRepo, Self>::new(0, cx, |state, _| {
let contained_text = if state.hovered() { let contained_text = if state.hovered() {
&theme.feedback.link_text_hover &theme.feedback.link_text_hover
} else { } else {
@ -58,7 +58,7 @@ impl View for FeedbackInfoText {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, cx| { .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(OpenZedCommunityRepo) cx.dispatch_action(OpenZedCommunityRepo)
}) })
.boxed(), .boxed(),

View file

@ -1,7 +1,7 @@
use gpui::{ use gpui::{
elements::{Label, MouseEventHandler}, elements::{Label, MouseEventHandler},
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle, Drawable, Element, Entity, View, ViewContext, ViewHandle,
}; };
use settings::Settings; use settings::Settings;
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView}; use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
@ -29,10 +29,10 @@ impl View for SubmitFeedbackButton {
"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(); let theme = cx.global::<Settings>().theme.clone();
enum SubmitFeedbackButton {} 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); let style = theme.feedback.submit_button.style_for(state, false);
Label::new("Submit as Markdown", style.text.clone()) Label::new("Submit as Markdown", style.text.clone())
.contained() .contained()
@ -40,13 +40,13 @@ impl View for SubmitFeedbackButton {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, cx| { .on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(SubmitFeedback) cx.dispatch_action(SubmitFeedback)
}) })
.aligned() .aligned()
.contained() .contained()
.with_margin_left(theme.feedback.button_margin) .with_margin_left(theme.feedback.button_margin)
.with_tooltip::<Self, _>( .with_tooltip::<Self>(
0, 0,
"cmd-s".into(), "cmd-s".into(),
Some(Box::new(SubmitFeedback)), Some(Box::new(SubmitFeedback)),

View file

@ -1,7 +1,6 @@
use fuzzy::PathMatch; use fuzzy::PathMatch;
use gpui::{ use gpui::{
actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle,
RenderContext, Task, View, ViewContext, ViewHandle,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId}; use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
@ -13,12 +12,14 @@ use std::{
Arc, Arc,
}, },
}; };
use util::post_inc; use util::{post_inc, ResultExt};
use workspace::Workspace; use workspace::Workspace;
pub struct FileFinder { pub type FileFinder = Picker<FileFinderDelegate>;
pub struct FileFinderDelegate {
workspace: WeakViewHandle<Workspace>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
picker: ViewHandle<Picker<Self>>,
search_count: usize, search_count: usize,
latest_search_id: usize, latest_search_id: usize,
latest_search_did_cancel: bool, latest_search_did_cancel: bool,
@ -32,8 +33,26 @@ pub struct FileFinder {
actions!(file_finder, [Toggle]); actions!(file_finder, [Toggle]);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
cx.add_action(FileFinder::toggle); cx.add_action(toggle_file_finder);
Picker::<FileFinder>::init(cx); 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 { pub enum Event {
@ -41,27 +60,7 @@ pub enum Event {
Dismissed, Dismissed,
} }
impl Entity for FileFinder { impl FileFinderDelegate {
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 {
fn labels_for_match(&self, path_match: &PathMatch) -> (String, Vec<usize>, String, Vec<usize>) { fn labels_for_match(&self, path_match: &PathMatch) -> (String, Vec<usize>, String, Vec<usize>) {
let path = &path_match.path; let path = &path_match.path;
let path_string = path.to_string_lossy(); let path_string = path.to_string_lossy();
@ -88,48 +87,19 @@ impl FileFinder {
(file_name, file_name_positions, full_path, path_positions) (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( pub fn new(
workspace: WeakViewHandle<Workspace>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
relative_to: Option<Arc<Path>>, relative_to: Option<Arc<Path>>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<FileFinder>,
) -> Self { ) -> Self {
let handle = cx.weak_handle(); cx.observe(&project, |picker, _, cx| {
cx.observe(&project, Self::project_updated).detach(); picker.update_matches(picker.query(cx), cx);
})
.detach();
Self { Self {
workspace,
project, project,
picker: cx.add_view(|cx| Picker::new("Search project files...", handle, cx)),
search_count: 0, search_count: 0,
latest_search_id: 0, latest_search_id: 0,
latest_search_did_cancel: false, latest_search_did_cancel: false,
@ -141,12 +111,7 @@ impl FileFinder {
} }
} }
fn project_updated(&mut self, _: ModelHandle<Project>, cx: &mut ViewContext<Self>) { fn spawn_search(&mut self, query: String, cx: &mut ViewContext<FileFinder>) -> Task<()> {
self.spawn_search(self.picker.read(cx).query(cx), cx)
.detach();
}
fn spawn_search(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
let relative_to = self.relative_to.clone(); let relative_to = self.relative_to.clone();
let worktrees = self let worktrees = self
.project .project
@ -172,7 +137,7 @@ impl FileFinder {
self.cancel_flag.store(true, atomic::Ordering::Relaxed); self.cancel_flag.store(true, atomic::Ordering::Relaxed);
self.cancel_flag = Arc::new(AtomicBool::new(false)); self.cancel_flag = Arc::new(AtomicBool::new(false));
let cancel_flag = self.cancel_flag.clone(); 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( let matches = fuzzy::match_path_sets(
candidate_sets.as_slice(), candidate_sets.as_slice(),
&query, &query,
@ -184,9 +149,13 @@ impl FileFinder {
) )
.await; .await;
let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed); let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
this.update(&mut cx, |this, cx| { picker
this.set_matches(search_id, did_cancel, query, matches, cx) .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, did_cancel: bool,
query: String, query: String,
matches: Vec<PathMatch>, matches: Vec<PathMatch>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<FileFinder>,
) { ) {
if search_id >= self.latest_search_id { if search_id >= self.latest_search_id {
self.latest_search_id = search_id; self.latest_search_id = search_id;
@ -208,12 +177,15 @@ impl FileFinder {
self.latest_search_query = query; self.latest_search_query = query;
self.latest_search_did_cancel = did_cancel; self.latest_search_did_cancel = did_cancel;
cx.notify(); 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 { fn match_count(&self) -> usize {
self.matches.len() self.matches.len()
} }
@ -231,13 +203,13 @@ impl PickerDelegate for FileFinder {
0 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]; let mat = &self.matches[ix];
self.selected = Some((mat.worktree_id, mat.path.clone())); self.selected = Some((mat.worktree_id, mat.path.clone()));
cx.notify(); 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() { if query.is_empty() {
self.latest_search_id = post_inc(&mut self.search_count); self.latest_search_id = post_inc(&mut self.search_count);
self.matches.clear(); 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()) { if let Some(m) = self.matches.get(self.selected_index()) {
cx.emit(Event::Selected(ProjectPath { if let Some(workspace) = self.workspace.upgrade(cx) {
worktree_id: WorktreeId::from_usize(m.worktree_id), let project_path = ProjectPath {
path: m.path.clone(), 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>) { fn dismissed(&mut self, _: &mut ViewContext<FileFinder>) {}
cx.emit(Event::Dismissed);
}
fn render_match( fn render_match(
&self, &self,
@ -267,7 +246,7 @@ impl PickerDelegate for FileFinder {
mouse_state: &mut MouseState, mouse_state: &mut MouseState,
selected: bool, selected: bool,
cx: &AppContext, cx: &AppContext,
) -> ElementBox { ) -> Element<Picker<Self>> {
let path_match = &self.matches[ix]; let path_match = &self.matches[ix];
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
let style = settings.theme.picker.item.style_for(mouse_state, selected); 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()); let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
finder finder
.update(cx, |finder, cx| { .update(cx, |finder, cx| {
finder.update_matches("bna".to_string(), cx) finder.delegate_mut().update_matches("bna".to_string(), cx)
}) })
.await; .await;
finder.read_with(cx, |finder, _| { 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()); 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 project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = let (_, finder) = cx.add_window(|cx| {
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
cx,
),
cx,
)
});
let query = "hi".to_string(); let query = "hi".to_string();
finder finder
.update(cx, |f, cx| f.spawn_search(query.clone(), cx)) .update(cx, |f, cx| f.delegate_mut().spawn_search(query.clone(), cx))
.await; .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| { 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, // Simulate a search being cancelled after the time limit,
// returning only a subset of the matches that would have been found. // returning only a subset of the matches that would have been found.
drop(finder.spawn_search(query.clone(), cx)); drop(delegate.spawn_search(query.clone(), cx));
finder.set_matches( delegate.set_matches(
finder.latest_search_id, delegate.latest_search_id,
true, // did-cancel true, // did-cancel
query.clone(), query.clone(),
vec![matches[1].clone(), matches[3].clone()], vec![matches[1].clone(), matches[3].clone()],
@ -408,16 +397,16 @@ mod tests {
); );
// Simulate another cancellation. // Simulate another cancellation.
drop(finder.spawn_search(query.clone(), cx)); drop(delegate.spawn_search(query.clone(), cx));
finder.set_matches( delegate.set_matches(
finder.latest_search_id, delegate.latest_search_id,
true, // did-cancel true, // did-cancel
query.clone(), query.clone(),
vec![matches[0].clone(), matches[2].clone(), matches[3].clone()], vec![matches[0].clone(), matches[2].clone(), matches[3].clone()],
cx, cx,
); );
assert_eq!(finder.matches, matches[0..4]) assert_eq!(delegate.matches, matches[0..4])
}); });
} }
@ -458,12 +447,21 @@ mod tests {
) )
.await; .await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = let (_, finder) = cx.add_window(|cx| {
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
cx,
),
cx,
)
});
finder finder
.update(cx, |f, cx| f.spawn_search("hi".into(), cx)) .update(cx, |f, cx| f.delegate_mut().spawn_search("hi".into(), cx))
.await; .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] #[gpui::test]
@ -482,20 +480,30 @@ mod tests {
) )
.await; .await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = let (_, finder) = cx.add_window(|cx| {
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, 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 // Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file. // is included in the matching, because the worktree is a single file.
finder finder
.update(cx, |f, cx| f.spawn_search("thf".into(), cx)) .update(cx, |f, cx| f.delegate_mut().spawn_search("thf".into(), cx))
.await; .await;
cx.read(|cx| { cx.read(|cx| {
let finder = finder.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) = 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, "the-file");
assert_eq!(file_name_positions, &[0, 1, 4]); assert_eq!(file_name_positions, &[0, 1, 4]);
assert_eq!(full_path, "the-file"); 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 // Since the worktree root is a file, searching for its name followed by a slash does
// not match anything. // not match anything.
finder finder
.update(cx, |f, cx| f.spawn_search("thf/".into(), cx)) .update(cx, |f, cx| f.delegate_mut().spawn_search("thf/".into(), cx))
.await; .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] #[gpui::test]
@ -535,22 +543,32 @@ mod tests {
.await; .await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = let (_, finder) = cx.add_window(|cx| {
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, 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. // Run a search that matches two files with the same relative path.
finder 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; .await;
// Can switch between different matches with the same relative path. // Can switch between different matches with the same relative path.
finder.update(cx, |f, cx| { finder.update(cx, |finder, cx| {
assert_eq!(f.matches.len(), 2); let delegate = finder.delegate_mut();
assert_eq!(f.selected_index(), 0); assert_eq!(delegate.matches.len(), 2);
f.set_selected_index(1, cx); assert_eq!(delegate.selected_index(), 0);
assert_eq!(f.selected_index(), 1); delegate.set_selected_index(1, cx);
f.set_selected_index(0, cx); assert_eq!(delegate.selected_index(), 1);
assert_eq!(f.selected_index(), 0); 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 // 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 // so that one should be sorted earlier
let b_path = Some(Arc::from(Path::new("/root/dir2/b.txt"))); let b_path = Some(Arc::from(Path::new("/root/dir2/b.txt")));
let (_, finder) = let (_, finder) = cx.add_window(|cx| {
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), b_path, cx)); Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
b_path,
cx,
),
cx,
)
});
finder 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; .await;
finder.read_with(cx, |f, _| { finder.read_with(cx, |f, _| {
assert_eq!(f.matches[0].path.as_ref(), Path::new("dir2/a.txt")); let delegate = f.delegate();
assert_eq!(f.matches[1].path.as_ref(), Path::new("dir1/a.txt")); 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 project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = let (_, finder) = cx.add_window(|cx| {
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
cx,
),
cx,
)
});
finder finder
.update(cx, |f, cx| f.spawn_search("dir".into(), cx)) .update(cx, |f, cx| f.delegate_mut().spawn_search("dir".into(), cx))
.await; .await;
cx.read(|cx| { cx.read(|cx| {
let finder = finder.read(cx); let finder = finder.read(cx);
assert_eq!(finder.matches.len(), 0); assert_eq!(finder.delegate().matches.len(), 0);
}); });
} }
} }

View file

@ -3,12 +3,12 @@ use std::sync::Arc;
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, DisplayPoint, Editor}; use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
use gpui::{ use gpui::{
actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Axis, Entity, actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Axis, Entity,
RenderContext, View, ViewContext, ViewHandle, View, ViewContext, ViewHandle,
}; };
use menu::{Cancel, Confirm}; use menu::{Cancel, Confirm};
use settings::Settings; use settings::Settings;
use text::{Bias, Point}; use text::{Bias, Point};
use workspace::Workspace; use workspace::{Modal, Workspace};
actions!(go_to_line, [Toggle]); actions!(go_to_line, [Toggle]);
@ -65,11 +65,7 @@ impl GoToLine {
.active_item(cx) .active_item(cx)
.and_then(|active_item| active_item.downcast::<Editor>()) .and_then(|active_item| active_item.downcast::<Editor>())
{ {
workspace.toggle_modal(cx, |_, cx| { workspace.toggle_modal(cx, |_, cx| cx.add_view(|cx| GoToLine::new(editor, cx)));
let view = cx.add_view(|cx| GoToLine::new(editor, cx));
cx.subscribe(&view, Self::on_event).detach();
view
});
} }
} }
@ -91,17 +87,6 @@ impl GoToLine {
cx.emit(Event::Dismissed); 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( fn on_line_editor_event(
&mut self, &mut self,
_: ViewHandle<Editor>, _: ViewHandle<Editor>,
@ -142,12 +127,14 @@ impl Entity for GoToLine {
fn release(&mut self, cx: &mut AppContext) { fn release(&mut self, cx: &mut AppContext) {
let scroll_position = self.prev_scroll_position.take(); let scroll_position = self.prev_scroll_position.take();
self.active_editor.update(cx, |editor, cx| { cx.update_window(self.active_editor.window_id(), |cx| {
editor.highlight_rows(None); self.active_editor.update(cx, |editor, cx| {
if let Some(scroll_position) = scroll_position { editor.highlight_rows(None);
editor.set_scroll_position(scroll_position, cx); if let Some(scroll_position) = scroll_position {
} editor.set_scroll_position(scroll_position, cx);
}) }
})
});
} }
} }
@ -156,7 +143,7 @@ impl View for GoToLine {
"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 theme = &cx.global::<Settings>().theme.picker;
let label = format!( let label = format!(
@ -192,3 +179,9 @@ impl View for GoToLine {
cx.focus(&self.line_editor); cx.focus(&self.line_editor);
} }
} }
impl Modal for GoToLine {
fn dismiss_on_event(event: &Self::Event) -> bool {
matches!(event, Event::Dismissed)
}
}

View file

@ -2,7 +2,7 @@ use gpui::{
color::Color, color::Color,
fonts::{Properties, Weight}, fonts::{Properties, Weight},
text_layout::RunStyle, text_layout::RunStyle,
DebugContext, Element as _, MeasurementContext, Quad, Drawable, Element, Quad, SceneBuilder, View, ViewContext,
}; };
use log::LevelFilter; use log::LevelFilter;
use pathfinder_geometry::rect::RectF; use pathfinder_geometry::rect::RectF;
@ -30,12 +30,12 @@ impl gpui::View for TextView {
"View" "View"
} }
fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox { fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Element<TextView> {
TextElement.boxed() TextElement.boxed()
} }
} }
impl gpui::Element for TextElement { impl<V: View> Drawable<V> for TextElement {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
@ -43,17 +43,20 @@ impl gpui::Element for TextElement {
fn layout( fn layout(
&mut self, &mut self,
constraint: gpui::SizeConstraint, constraint: gpui::SizeConstraint,
_: &mut gpui::LayoutContext, _: &mut V,
_: &mut ViewContext<V>,
) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) { ) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) {
(constraint.max, ()) (constraint.max, ())
} }
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
cx: &mut gpui::PaintContext, _: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
let font_size = 12.; let font_size = 12.;
let family = cx let family = cx
@ -84,7 +87,7 @@ impl gpui::Element for TextElement {
}; };
let text = "Hello world!"; let text = "Hello world!";
let line = cx.text_layout_cache.layout_str( let line = cx.text_layout_cache().layout_str(
text, text,
font_size, font_size,
&[ &[
@ -96,12 +99,12 @@ impl gpui::Element for TextElement {
], ],
); );
cx.scene.push_quad(Quad { scene.push_quad(Quad {
bounds, bounds,
background: Some(Color::white()), background: Some(Color::white()),
..Default::default() ..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( fn rect_for_text_range(
@ -111,7 +114,8 @@ impl gpui::Element for TextElement {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &MeasurementContext, _: &V,
_: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
None None
} }
@ -121,7 +125,8 @@ impl gpui::Element for TextElement {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &DebugContext, _: &V,
_: &ViewContext<V>,
) -> gpui::json::Value { ) -> gpui::json::Value {
todo!() todo!()
} }

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,3 @@
use crate::AppContext;
use collections::{BTreeMap, HashMap, HashSet}; use collections::{BTreeMap, HashMap, HashSet};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
@ -93,12 +92,10 @@ impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> {
drop(callbacks); drop(callbacks);
} }
pub fn emit<C: FnMut(&mut F, &mut AppContext) -> bool>( pub fn emit<C>(&mut self, key: K, mut call_callback: C)
&mut self, where
key: K, C: FnMut(&mut F) -> bool,
cx: &mut AppContext, {
mut call_callback: C,
) {
let callbacks = self.internal.lock().callbacks.remove(&key); let callbacks = self.internal.lock().callbacks.remove(&key);
if let Some(callbacks) = callbacks { if let Some(callbacks) = callbacks {
for (subscription_id, mut callback) in callbacks { for (subscription_id, mut callback) in callbacks {
@ -110,7 +107,7 @@ impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> {
} }
drop(this); 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 // If this callback's subscription was dropped while invoking the callback
// itself, or if the callback returns false, then just drop the callback. // itself, or if the callback returns false, then just drop the callback.

View file

@ -78,8 +78,18 @@ pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform,
move |action| { move |action| {
let mut cx = cx.borrow_mut(); let mut cx = cx.borrow_mut();
if let Some(main_window_id) = cx.platform.main_window_id() { if let Some(main_window_id) = cx.platform.main_window_id() {
if let Some(view_id) = cx.focused_view_id(main_window_id) { let dispatched = cx
cx.handle_dispatch_action_from_effect(main_window_id, Some(view_id), action); .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; return;
} }
} }

View file

@ -1,6 +1,6 @@
use std::{ use std::{
any::Any,
cell::RefCell, cell::RefCell,
marker::PhantomData,
mem, mem,
path::PathBuf, path::PathBuf,
rc::Rc, rc::Rc,
@ -21,10 +21,10 @@ use crate::{
geometry::vector::Vector2F, geometry::vector::Vector2F,
keymap_matcher::Keystroke, keymap_matcher::Keystroke,
platform, platform,
platform::{Appearance, Event, InputHandler, KeyDownEvent, Platform}, platform::{Event, InputHandler, KeyDownEvent, Platform},
Action, AnyViewHandle, AppContext, Entity, FontCache, Handle, ModelContext, ModelHandle, Action, AnyViewHandle, AppContext, Entity, FontCache, Handle, ModelContext, ModelHandle,
ReadModelWith, ReadViewWith, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ReadModelWith, ReadViewWith, Subscription, Task, UpdateModel, UpdateView, View, ViewContext,
ViewHandle, WeakHandle, ViewHandle, WeakHandle, WindowContext,
}; };
use collections::BTreeMap; use collections::BTreeMap;
@ -75,7 +75,7 @@ impl TestAppContext {
pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) { pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) {
let mut cx = self.cx.borrow_mut(); 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); 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) { pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
let handled = self.cx.borrow_mut().update(|cx| { let handled = self
let presenter = cx .cx
.presenters_and_platform_windows .borrow_mut()
.get(&window_id) .update_window(window_id, |cx| {
.unwrap() if cx.dispatch_keystroke(&keystroke) {
.0 return true;
.clone(); }
if cx.dispatch_keystroke(window_id, &keystroke) { if cx.dispatch_event(
return true; Event::KeyDown(KeyDownEvent {
} keystroke: keystroke.clone(),
is_held,
}),
false,
) {
return true;
}
if presenter.borrow_mut().dispatch_event( false
Event::KeyDown(KeyDownEvent { })
keystroke: keystroke.clone(), .unwrap_or(false);
is_held,
}),
false,
cx,
) {
return true;
}
false
});
if !handled && !keystroke.cmd && !keystroke.ctrl { if !handled && !keystroke.cmd && !keystroke.ctrl {
WindowInputHandler { 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> pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
where where
T: Entity, T: Entity,
@ -149,12 +161,28 @@ impl TestAppContext {
self.cx.borrow_mut().add_view(parent_handle, build_view) self.cx.borrow_mut().add_view(parent_handle, build_view)
} }
pub fn window_ids(&self) -> Vec<usize> { pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
self.cx.borrow().window_ids().collect() where
E: Any,
F: 'static + FnMut(&mut AppContext),
{
self.cx.borrow_mut().observe_global::<E, F>(callback)
} }
pub fn root_view(&self, window_id: usize) -> Option<AnyViewHandle> { pub fn set_global<T: 'static>(&mut self, state: T) {
self.cx.borrow().root_view(window_id) self.cx.borrow_mut().set_global(state);
}
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 { pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
@ -172,27 +200,6 @@ impl TestAppContext {
result 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 { pub fn to_async(&self) -> AsyncAppContext {
AsyncAppContext(self.cx.clone()) AsyncAppContext(self.cx.clone())
} }
@ -245,7 +252,7 @@ impl TestAppContext {
use postage::prelude::Sink as _; use postage::prelude::Sink as _;
let mut done_tx = self let mut done_tx = self
.window_mut(window_id) .platform_window_mut(window_id)
.pending_prompts .pending_prompts
.borrow_mut() .borrow_mut()
.pop_front() .pop_front()
@ -254,20 +261,23 @@ impl TestAppContext {
} }
pub fn has_pending_prompt(&self, window_id: usize) -> bool { 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(); let prompts = window.pending_prompts.borrow_mut();
!prompts.is_empty() !prompts.is_empty()
} }
pub fn current_window_title(&self, window_id: usize) -> Option<String> { 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 { 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 { if let Some(mut handler) = handler {
let should_close = 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 should_close
} else { } else {
false false
@ -275,47 +285,37 @@ impl TestAppContext {
} }
pub fn simulate_window_resize(&self, window_id: usize, size: Vector2F) { 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; window.size = size;
let mut handlers = mem::take(&mut window.resize_handlers); let mut handlers = mem::take(&mut window.resize_handlers);
drop(window); drop(window);
for handler in &mut handlers { for handler in &mut handlers {
handler(); 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>) { pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
let mut handlers = BTreeMap::new(); self.cx.borrow_mut().update(|cx| {
{ let other_window_ids = cx
let mut cx = self.cx.borrow_mut(); .windows
for (window_id, (_, window)) in &mut cx.presenters_and_platform_windows { .keys()
let window = window .filter(|window_id| Some(**window_id) != to_activate)
.as_any_mut() .copied()
.downcast_mut::<platform::test::Window>() .collect::<Vec<_>>();
.unwrap();
handlers.insert(
*window_id,
mem::take(&mut window.active_status_change_handlers),
);
}
};
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_id in other_window_ids {
for window_handler in &mut window_handlers { cx.window_changed_active_status(window_id, false)
window_handler(Some(window_id) == to_activate);
} }
self.window_mut(window_id) if let Some(to_activate) = to_activate {
.active_status_change_handlers cx.window_changed_active_status(to_activate, true)
.extend(window_handlers); }
} });
} }
pub fn is_window_edited(&self, window_id: usize) -> bool { 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>> { pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
@ -338,13 +338,11 @@ impl TestAppContext {
self.assert_dropped(weak); 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| { std::cell::RefMut::map(self.cx.borrow_mut(), |state| {
let (_, window) = state let window = state.windows.get_mut(&window_id).unwrap();
.presenters_and_platform_windows
.get_mut(&window_id)
.unwrap();
let test_window = window let test_window = window
.platform_window
.as_any_mut() .as_any_mut()
.downcast_mut::<platform::test::Window>() .downcast_mut::<platform::test::Window>()
.unwrap(); .unwrap();
@ -406,6 +404,8 @@ impl ReadModelWith for TestAppContext {
} }
impl UpdateView for TestAppContext { impl UpdateView for TestAppContext {
type Output<S> = S;
fn update_view<T, S>( fn update_view<T, S>(
&mut self, &mut self,
handle: &ViewHandle<T>, handle: &ViewHandle<T>,
@ -414,7 +414,10 @@ impl UpdateView for TestAppContext {
where where
T: View, 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 timeout_duration = cx.condition_duration();
let mut cx = cx.cx.borrow_mut(); let mut cx = cx.cx.borrow_mut();
let subscriptions = self.update(&mut *cx, |_, cx| { let subscriptions = (
( cx.observe(self, {
cx.observe(self, { let mut tx = tx.clone();
let mut tx = tx.clone(); move |_, _| {
move |_, _, _| { tx.blocking_send(()).ok();
tx.blocking_send(()).ok(); }
} }),
}), cx.subscribe(self, {
cx.subscribe(self, { let mut tx = tx.clone();
let mut tx = tx.clone(); move |_, _, _| {
move |_, _, _, _| { tx.blocking_send(()).ok();
tx.blocking_send(()).ok(); }
} }),
}), );
)
});
let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap(); let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
let handle = self.downgrade(); let handle = self.downgrade();

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@ use std::{cell::RefCell, ops::Range, rc::Rc};
use pathfinder_geometry::rect::RectF; use pathfinder_geometry::rect::RectF;
use crate::{platform::InputHandler, AnyView, AppContext}; use crate::{platform::InputHandler, window::WindowContext, AnyView, AppContext};
pub struct WindowInputHandler { pub struct WindowInputHandler {
pub app: Rc<RefCell<AppContext>>, pub app: Rc<RefCell<AppContext>>,
@ -12,7 +12,7 @@ pub struct WindowInputHandler {
impl WindowInputHandler { impl WindowInputHandler {
fn read_focused_view<T, F>(&self, f: F) -> Option<T> fn read_focused_view<T, F>(&self, f: F) -> Option<T>
where where
F: FnOnce(&dyn AnyView, &AppContext) -> T, F: FnOnce(&dyn AnyView, &WindowContext) -> T,
{ {
// Input-related application hooks are sometimes called by the OS during // Input-related application hooks are sometimes called by the OS during
// a call to a window-manipulation API, like prompting the user for file // 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. // InputHandler methods need to fail gracefully.
// //
// See https://github.com/zed-industries/community/issues/444 // See https://github.com/zed-industries/community/issues/444
let app = self.app.try_borrow().ok()?; let mut app = self.app.try_borrow_mut().ok()?;
app.update_window(self.window_id, |cx| {
let view_id = app.focused_view_id(self.window_id)?; let view_id = cx.window.focused_view_id?;
let view = app.views.get(&(self.window_id, view_id))?; let view = cx.views.get(&(self.window_id, view_id))?;
let result = f(view.as_ref(), &app); let result = f(view.as_ref(), &cx);
Some(result) Some(result)
})
.flatten()
} }
fn update_focused_view<T, F>(&mut self, f: F) -> Option<T> fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
where 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()?; let mut app = self.app.try_borrow_mut().ok()?;
app.update(|app| { app.update_window(self.window_id, |cx| {
let view_id = app.focused_view_id(self.window_id)?; let view_id = cx.window.focused_view_id?;
let mut view = app.views.remove(&(self.window_id, view_id))?; cx.update_any_view(view_id, |view, cx| f(view, cx, 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)
}) })
.flatten()
} }
} }
@ -55,8 +55,8 @@ impl InputHandler for WindowInputHandler {
} }
fn replace_text_in_range(&mut self, range: Option<Range<usize>>, text: &str) { fn replace_text_in_range(&mut self, range: Option<Range<usize>>, text: &str) {
self.update_focused_view(|window_id, view_id, view, cx| { self.update_focused_view(|view, cx, view_id| {
view.replace_text_in_range(range, text, cx, window_id, 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) { fn unmark_text(&mut self) {
self.update_focused_view(|window_id, view_id, view, cx| { self.update_focused_view(|view, cx, view_id| {
view.unmark_text(cx, window_id, view_id); view.unmark_text(cx, view_id);
}); });
} }
@ -77,22 +77,15 @@ impl InputHandler for WindowInputHandler {
new_text: &str, new_text: &str,
new_selected_range: Option<Range<usize>>, new_selected_range: Option<Range<usize>>,
) { ) {
self.update_focused_view(|window_id, view_id, view, cx| { self.update_focused_view(|view, cx, view_id| {
view.replace_and_mark_text_in_range( view.replace_and_mark_text_in_range(range, new_text, new_selected_range, cx, view_id);
range,
new_text,
new_selected_range,
cx,
window_id,
view_id,
);
}); });
} }
fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> { fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
let app = self.app.borrow(); self.app
let (presenter, _) = app.presenters_and_platform_windows.get(&self.window_id)?; .borrow_mut()
let presenter = presenter.borrow(); .update_window(self.window_id, |cx| cx.rect_for_text_range(range_utf16))
presenter.rect_for_text_range(range_utf16, &app) .flatten()
} }
} }

View file

@ -25,59 +25,47 @@ pub use self::{
keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*, resizable::*, keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*, resizable::*,
stack::*, svg::*, text::*, tooltip::*, uniform_list::*, stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
}; };
pub use crate::window::ChildView;
use self::{clipped::Clipped, expanded::Expanded}; use self::{clipped::Clipped, expanded::Expanded};
pub use crate::presenter::ChildView;
use crate::{ use crate::{
geometry::{ geometry::{
rect::RectF, rect::RectF,
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
json, json, Action, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle, WindowContext,
presenter::MeasurementContext,
Action, DebugContext, EventContext, LayoutContext, PaintContext, RenderContext, SizeConstraint,
View,
}; };
use anyhow::{anyhow, Result};
use core::panic; use core::panic;
use json::ToJson; use json::ToJson;
use std::{ use std::{
any::Any, any::Any,
borrow::Cow, borrow::Cow,
cell::RefCell, marker::PhantomData,
mem, mem,
ops::{Deref, DerefMut, Range}, ops::{Deref, DerefMut, Range},
rc::Rc,
}; };
use util::ResultExt;
trait AnyElement { pub trait Drawable<V: View> {
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 {
type LayoutState; type LayoutState;
type PaintState; type PaintState;
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState); ) -> (Vector2F, Self::LayoutState);
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
layout: &mut Self::LayoutState, layout: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState; ) -> Self::PaintState;
fn rect_for_text_range( fn rect_for_text_range(
@ -87,7 +75,8 @@ pub trait Element {
visible_bounds: RectF, visible_bounds: RectF,
layout: &Self::LayoutState, layout: &Self::LayoutState,
paint: &Self::PaintState, paint: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF>; ) -> Option<RectF>;
fn metadata(&self) -> Option<&dyn Any> { fn metadata(&self) -> Option<&dyn Any> {
@ -99,104 +88,117 @@ pub trait Element {
bounds: RectF, bounds: RectF,
layout: &Self::LayoutState, layout: &Self::LayoutState,
paint: &Self::PaintState, paint: &Self::PaintState,
cx: &DebugContext, view: &V,
cx: &ViewContext<V>,
) -> serde_json::Value; ) -> serde_json::Value;
fn boxed(self) -> ElementBox fn boxed(self) -> Element<V>
where where
Self: 'static + Sized, Self: 'static + Sized,
{ {
ElementBox(ElementRc { Element {
drawable: Box::new(Lifecycle::Init { element: self }),
view_type: PhantomData,
name: None, name: None,
element: Rc::new(RefCell::new(Lifecycle::Init { element: self })), }
})
} }
fn named(self, name: impl Into<Cow<'static, str>>) -> ElementBox fn into_root(self, cx: &ViewContext<V>) -> RootElement<V>
where where
Self: 'static + Sized, Self: 'static + Sized,
{ {
ElementBox(ElementRc { RootElement {
name: Some(name.into()), element: self.boxed(),
element: Rc::new(RefCell::new(Lifecycle::Init { element: self })), view: cx.handle().downgrade(),
}) }
} }
fn constrained(self) -> ConstrainedBox fn named(self, name: impl Into<Cow<'static, str>>) -> Element<V>
where
Self: 'static + Sized,
{
Element {
drawable: Box::new(Lifecycle::Init { element: self }),
view_type: PhantomData,
name: Some(name.into()),
}
}
fn constrained(self) -> ConstrainedBox<V>
where where
Self: 'static + Sized, Self: 'static + Sized,
{ {
ConstrainedBox::new(self.boxed()) ConstrainedBox::new(self.boxed())
} }
fn aligned(self) -> Align fn aligned(self) -> Align<V>
where where
Self: 'static + Sized, Self: 'static + Sized,
{ {
Align::new(self.boxed()) Align::new(self.boxed())
} }
fn clipped(self) -> Clipped fn clipped(self) -> Clipped<V>
where where
Self: 'static + Sized, Self: 'static + Sized,
{ {
Clipped::new(self.boxed()) Clipped::new(self.boxed())
} }
fn contained(self) -> Container fn contained(self) -> Container<V>
where where
Self: 'static + Sized, Self: 'static + Sized,
{ {
Container::new(self.boxed()) Container::new(self.boxed())
} }
fn expanded(self) -> Expanded fn expanded(self) -> Expanded<V>
where where
Self: 'static + Sized, Self: 'static + Sized,
{ {
Expanded::new(self.boxed()) Expanded::new(self.boxed())
} }
fn flex(self, flex: f32, expanded: bool) -> FlexItem fn flex(self, flex: f32, expanded: bool) -> FlexItem<V>
where where
Self: 'static + Sized, Self: 'static + Sized,
{ {
FlexItem::new(self.boxed()).flex(flex, expanded) FlexItem::new(self.boxed()).flex(flex, expanded)
} }
fn flex_float(self) -> FlexItem fn flex_float(self) -> FlexItem<V>
where where
Self: 'static + Sized, Self: 'static + Sized,
{ {
FlexItem::new(self.boxed()).float() FlexItem::new(self.boxed()).float()
} }
fn with_tooltip<Tag: 'static, T: View>( fn with_tooltip<Tag: 'static>(
self, self,
id: usize, id: usize,
text: String, text: String,
action: Option<Box<dyn Action>>, action: Option<Box<dyn Action>>,
style: TooltipStyle, style: TooltipStyle,
cx: &mut RenderContext<T>, cx: &mut ViewContext<V>,
) -> Tooltip ) -> Tooltip<V>
where where
Self: 'static + Sized, 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, self,
element_id: usize, element_id: usize,
side: Side, side: Side,
handle_size: f32, handle_size: f32,
initial_size: f32, initial_size: f32,
cx: &mut RenderContext<T>, cx: &mut ViewContext<V>,
) -> Resizable ) -> Resizable<V>
where where
Self: 'static + Sized, Self: 'static + Sized,
{ {
Resizable::new::<Tag, T>( Resizable::new::<Tag, V>(
self.boxed(), self.boxed(),
element_id, element_id,
side, 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, Empty,
Init { Init {
element: T, element: E,
}, },
PostLayout { PostLayout {
element: T, element: E,
constraint: SizeConstraint, constraint: SizeConstraint,
size: Vector2F, size: Vector2F,
layout: T::LayoutState, layout: E::LayoutState,
}, },
PostPaint { PostPaint {
element: T, element: E,
constraint: SizeConstraint, constraint: SizeConstraint,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
layout: T::LayoutState, layout: E::LayoutState,
paint: T::PaintState, paint: E::PaintState,
}, },
} }
pub struct ElementBox(ElementRc); impl<V: View, E: Drawable<V>> AnyDrawable<V> for Lifecycle<V, E> {
fn layout(
#[derive(Clone)] &mut self,
pub struct ElementRc { constraint: SizeConstraint,
name: Option<Cow<'static, str>>, view: &mut V,
element: Rc<RefCell<dyn AnyElement>>, cx: &mut ViewContext<V>,
} ) -> Vector2F {
impl<T: Element> AnyElement for Lifecycle<T> {
fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F {
let result; let result;
*self = match mem::take(self) { *self = match mem::take(self) {
Lifecycle::Empty => unreachable!(), Lifecycle::Empty => unreachable!(),
Lifecycle::Init { mut element } Lifecycle::Init { mut element }
| Lifecycle::PostLayout { mut element, .. } | Lifecycle::PostLayout { mut element, .. }
| Lifecycle::PostPaint { 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.x().is_finite());
debug_assert!(size.y().is_finite()); debug_assert!(size.y().is_finite());
@ -260,7 +290,14 @@ impl<T: Element> AnyElement for Lifecycle<T> {
result 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) { *self = match mem::take(self) {
Lifecycle::PostLayout { Lifecycle::PostLayout {
mut element, mut element,
@ -269,7 +306,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
mut layout, mut layout,
} => { } => {
let bounds = RectF::new(origin, size); 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 { Lifecycle::PostPaint {
element, element,
constraint, constraint,
@ -287,7 +324,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
.. ..
} => { } => {
let bounds = RectF::new(origin, bounds.size()); 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 { Lifecycle::PostPaint {
element, element,
constraint, constraint,
@ -307,7 +344,8 @@ impl<T: Element> AnyElement for Lifecycle<T> {
fn rect_for_text_range( fn rect_for_text_range(
&self, &self,
range_utf16: Range<usize>, range_utf16: Range<usize>,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
if let Lifecycle::PostPaint { if let Lifecycle::PostPaint {
element, element,
@ -318,7 +356,15 @@ impl<T: Element> AnyElement for Lifecycle<T> {
.. ..
} = self } = 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 { } else {
None 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 { match self {
Lifecycle::PostPaint { Lifecycle::PostPaint {
element, element,
@ -351,7 +397,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
layout, layout,
paint, 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 { if let json::Value::Object(map) = &mut value {
let mut new_map: crate::json::Map<String, serde_json::Value> = let mut new_map: crate::json::Map<String, serde_json::Value> =
Default::default(); 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 { fn default() -> Self {
Self::Empty Self::Empty
} }
} }
impl ElementBox { pub struct Element<V: View> {
drawable: Box<dyn AnyDrawable<V>>,
view_type: PhantomData<V>,
name: Option<Cow<'static, str>>,
}
impl<V: View> Element<V> {
pub fn name(&self) -> Option<&str> { pub fn name(&self) -> Option<&str> {
self.0.name.as_deref() self.name.as_deref()
} }
pub fn metadata<T: 'static>(&self) -> Option<&T> { pub fn metadata<T: 'static>(&self) -> Option<&T> {
let element = unsafe { &*self.0.element.as_ptr() }; self.drawable
element.metadata().and_then(|m| m.downcast_ref()) .metadata()
} .and_then(|data| data.downcast_ref::<T>())
}
impl Clone for ElementBox {
fn clone(&self) -> Self {
ElementBox(self.0.clone())
}
}
impl From<ElementBox> for ElementRc {
fn from(val: ElementBox) -> Self {
val.0
}
}
impl Deref for ElementBox {
type Target = ElementRc;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ElementBox {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl ElementRc {
pub fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F {
self.element.borrow_mut().layout(constraint, cx)
} }
pub fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext) { pub fn layout(
self.element.borrow_mut().paint(origin, visible_bounds, cx); &mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut ViewContext<V>,
) -> Vector2F {
self.drawable.layout(constraint, view, 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( pub fn rect_for_text_range(
&self, &self,
range_utf16: Range<usize>, range_utf16: Range<usize>,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> 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 { pub fn size(&self) -> Vector2F {
self.element.borrow().size() self.drawable.size()
} }
pub fn debug(&self, cx: &DebugContext) -> json::Value { pub fn debug(&self, view: &V, cx: &ViewContext<V>) -> json::Value {
let mut value = self.element.borrow().debug(cx); let mut value = self.drawable.debug(view, cx);
if let Some(name) = &self.name { if let Some(name) = &self.name {
if let json::Value::Object(map) = &mut value { if let json::Value::Object(map) = &mut value {
@ -457,31 +495,248 @@ impl ElementRc {
T: 'static, T: 'static,
F: FnOnce(Option<&T>) -> R, F: FnOnce(Option<&T>) -> R,
{ {
let element = self.element.borrow(); f(self.drawable.metadata().and_then(|m| m.downcast_ref()))
f(element.metadata().and_then(|m| m.downcast_ref()))
} }
} }
pub trait ParentElement<'a>: Extend<ElementBox> + Sized { pub struct RootElement<V: View> {
fn add_children(&mut self, children: impl IntoIterator<Item = ElementBox>) { 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, V: View>: Extend<Element<V>> + Sized {
fn add_children(&mut self, children: impl IntoIterator<Item = Element<V>>) {
self.extend(children); self.extend(children);
} }
fn add_child(&mut self, child: ElementBox) { fn add_child(&mut self, child: Element<V>) {
self.add_children(Some(child)); 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.add_children(children);
self self
} }
fn with_child(self, child: ElementBox) -> Self { fn with_child(self, child: Element<V>) -> Self {
self.with_children(Some(child)) 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 { pub fn constrain_size_preserving_aspect_ratio(max_size: Vector2F, size: Vector2F) -> Vector2F {
if max_size.x().is_infinite() && max_size.y().is_infinite() { if max_size.x().is_infinite() && max_size.y().is_infinite() {

View file

@ -1,20 +1,18 @@
use crate::{ use crate::{
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json, json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
presenter::MeasurementContext,
DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
}; };
use json::ToJson; use json::ToJson;
use serde_json::json; use serde_json::json;
pub struct Align { pub struct Align<V: View> {
child: ElementBox, child: Element<V>,
alignment: Vector2F, alignment: Vector2F,
} }
impl Align { impl<V: View> Align<V> {
pub fn new(child: ElementBox) -> Self { pub fn new(child: Element<V>) -> Self {
Self { Self {
child, child,
alignment: Vector2F::zero(), alignment: Vector2F::zero(),
@ -42,18 +40,19 @@ impl Align {
} }
} }
impl Element for Align { impl<V: View> Drawable<V> for Align<V> {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
mut constraint: SizeConstraint, mut constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.max; let mut size = constraint.max;
constraint.min = Vector2F::zero(); 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() { if size.x().is_infinite() {
size.set_x(child_size.x()); size.set_x(child_size.x());
} }
@ -65,10 +64,12 @@ impl Element for Align {
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
let my_center = bounds.size() / 2.; let my_center = bounds.size() / 2.;
let my_target = my_center + my_center * self.alignment; 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; let child_target = child_center + child_center * self.alignment;
self.child.paint( self.child.paint(
scene,
bounds.origin() - (child_target - my_target), bounds.origin() - (child_target - my_target),
visible_bounds, visible_bounds,
view,
cx, cx,
); );
} }
@ -90,9 +93,10 @@ impl Element for Align {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
self.child.rect_for_text_range(range_utf16, cx) self.child.rect_for_text_range(range_utf16, view, cx)
} }
fn debug( fn debug(
@ -100,13 +104,14 @@ impl Element for Align {
bounds: pathfinder_geometry::rect::RectF, bounds: pathfinder_geometry::rect::RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &DebugContext, view: &V,
cx: &ViewContext<V>,
) -> json::Value { ) -> json::Value {
json!({ json!({
"type": "Align", "type": "Align",
"bounds": bounds.to_json(), "bounds": bounds.to_json(),
"alignment": self.alignment.to_json(), "alignment": self.alignment.to_json(),
"child": self.child.debug(cx), "child": self.child.debug(view, cx),
}) })
} }
} }

View file

@ -1,8 +1,9 @@
use super::Element; use std::marker::PhantomData;
use super::Drawable;
use crate::{ use crate::{
json::{self, json}, json::{self, json},
presenter::MeasurementContext, SceneBuilder, View, ViewContext,
DebugContext, PaintContext,
}; };
use json::ToJson; use json::ToJson;
use pathfinder_geometry::{ use pathfinder_geometry::{
@ -10,20 +11,21 @@ use pathfinder_geometry::{
vector::{vec2f, Vector2F}, 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 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 { 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 where
F: FnMut(RectF, RectF, &mut PaintContext), F: FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
{ {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
@ -31,7 +33,8 @@ where
fn layout( fn layout(
&mut self, &mut self,
constraint: crate::SizeConstraint, constraint: crate::SizeConstraint,
_: &mut crate::LayoutContext, _: &mut V,
_: &mut crate::ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let x = if constraint.max.x().is_finite() { let x = if constraint.max.x().is_finite() {
constraint.max.x() constraint.max.x()
@ -48,12 +51,14 @@ where
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
self.0(bounds, visible_bounds, cx) self.0(scene, bounds, visible_bounds, view, cx)
} }
fn rect_for_text_range( fn rect_for_text_range(
@ -63,7 +68,8 @@ where
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &MeasurementContext, _: &V,
_: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
None None
} }
@ -73,7 +79,8 @@ where
bounds: RectF, bounds: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &DebugContext, _: &V,
_: &ViewContext<V>,
) -> json::Value { ) -> json::Value {
json!({"type": "Canvas", "bounds": bounds.to_json()}) json!({"type": "Canvas", "bounds": bounds.to_json()})
} }

View file

@ -3,43 +3,44 @@ use std::ops::Range;
use pathfinder_geometry::{rect::RectF, vector::Vector2F}; use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use serde_json::json; use serde_json::json;
use crate::{ use crate::{json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext};
json, DebugContext, Element, ElementBox, LayoutContext, MeasurementContext, PaintContext,
SizeConstraint,
};
pub struct Clipped { pub struct Clipped<V: View> {
child: ElementBox, child: Element<V>,
} }
impl Clipped { impl<V: View> Clipped<V> {
pub fn new(child: ElementBox) -> Self { pub fn new(child: Element<V>) -> Self {
Self { child } Self { child }
} }
} }
impl Element for Clipped { impl<V: View> Drawable<V> for Clipped<V> {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, cx), ()) (self.child.layout(constraint, view, cx), ())
} }
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
cx.scene.push_layer(Some(bounds)); scene.paint_layer(Some(bounds), |scene| {
self.child.paint(bounds.origin(), visible_bounds, cx); self.child
cx.scene.pop_layer(); .paint(scene, bounds.origin(), visible_bounds, view, cx)
})
} }
fn rect_for_text_range( fn rect_for_text_range(
@ -49,9 +50,10 @@ impl Element for Clipped {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
self.child.rect_for_text_range(range_utf16, cx) self.child.rect_for_text_range(range_utf16, view, cx)
} }
fn debug( fn debug(
@ -59,11 +61,12 @@ impl Element for Clipped {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &DebugContext, view: &V,
cx: &ViewContext<V>,
) -> json::Value { ) -> json::Value {
json!({ json!({
"type": "Clipped", "type": "Clipped",
"child": self.child.debug(cx) "child": self.child.debug(view, cx)
}) })
} }
} }

View file

@ -5,22 +5,20 @@ use serde_json::json;
use crate::{ use crate::{
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json, json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
presenter::MeasurementContext,
DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
}; };
pub struct ConstrainedBox { pub struct ConstrainedBox<V: View> {
child: ElementBox, child: Element<V>,
constraint: Constraint, constraint: Constraint<V>,
} }
pub enum Constraint { pub enum Constraint<V: View> {
Static(SizeConstraint), 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 { fn to_json(&self) -> serde_json::Value {
match self { match self {
Constraint::Static(constraint) => constraint.to_json(), Constraint::Static(constraint) => constraint.to_json(),
@ -29,8 +27,8 @@ impl ToJson for Constraint {
} }
} }
impl ConstrainedBox { impl<V: View> ConstrainedBox<V> {
pub fn new(child: ElementBox) -> Self { pub fn new(child: Element<V>) -> Self {
Self { Self {
child, child,
constraint: Constraint::Static(Default::default()), constraint: Constraint::Static(Default::default()),
@ -39,7 +37,7 @@ impl ConstrainedBox {
pub fn dynamically( pub fn dynamically(
mut self, mut self,
constraint: impl 'static + FnMut(SizeConstraint, &mut LayoutContext) -> SizeConstraint, constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint,
) -> Self { ) -> Self {
self.constraint = Constraint::Dynamic(Box::new(constraint)); self.constraint = Constraint::Dynamic(Box::new(constraint));
self self
@ -120,41 +118,48 @@ impl ConstrainedBox {
fn constraint( fn constraint(
&mut self, &mut self,
input_constraint: SizeConstraint, input_constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> SizeConstraint { ) -> SizeConstraint {
match &mut self.constraint { match &mut self.constraint {
Constraint::Static(constraint) => *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 LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
mut parent_constraint: SizeConstraint, mut parent_constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (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.min = parent_constraint.min.max(constraint.min);
parent_constraint.max = parent_constraint.max.min(constraint.max); parent_constraint.max = parent_constraint.max.min(constraint.max);
parent_constraint.max = parent_constraint.max.max(parent_constraint.min); 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, ()) (size, ())
} }
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
cx.paint_layer(Some(visible_bounds), |cx| { scene.paint_layer(Some(visible_bounds), |scene| {
self.child.paint(bounds.origin(), visible_bounds, cx); self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
}) })
} }
@ -165,9 +170,10 @@ impl Element for ConstrainedBox {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
self.child.rect_for_text_range(range_utf16, cx) self.child.rect_for_text_range(range_utf16, view, cx)
} }
fn debug( fn debug(
@ -175,8 +181,9 @@ impl Element for ConstrainedBox {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &DebugContext, view: &V,
cx: &ViewContext<V>,
) -> json::Value { ) -> 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)})
} }
} }

View file

@ -9,9 +9,8 @@ use crate::{
}, },
json::ToJson, json::ToJson,
platform::CursorStyle, platform::CursorStyle,
presenter::MeasurementContext,
scene::{self, Border, CursorRegion, Quad}, scene::{self, Border, CursorRegion, Quad},
Element, ElementBox, LayoutContext, PaintContext, SizeConstraint, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
}; };
use serde::Deserialize; use serde::Deserialize;
use serde_json::json; use serde_json::json;
@ -36,13 +35,13 @@ pub struct ContainerStyle {
pub cursor: Option<CursorStyle>, pub cursor: Option<CursorStyle>,
} }
pub struct Container { pub struct Container<V: View> {
child: ElementBox, child: Element<V>,
style: ContainerStyle, style: ContainerStyle,
} }
impl Container { impl<V: View> Container<V> {
pub fn new(child: ElementBox) -> Self { pub fn new(child: Element<V>) -> Self {
Self { Self {
child, child,
style: Default::default(), style: Default::default(),
@ -185,14 +184,15 @@ impl Container {
} }
} }
impl Element for Container { impl<V: View> Drawable<V> for Container<V> {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let mut size_buffer = self.margin_size() + self.padding_size(); let mut size_buffer = self.margin_size() + self.padding_size();
if !self.style.border.overlay { if !self.style.border.overlay {
@ -202,16 +202,18 @@ impl Element for Container {
min: (constraint.min - size_buffer).max(Vector2F::zero()), min: (constraint.min - size_buffer).max(Vector2F::zero()),
max: (constraint.max - 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, ()) (child_size + size_buffer, ())
} }
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
let quad_bounds = RectF::from_points( let quad_bounds = RectF::from_points(
bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top), 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() { 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, bounds: quad_bounds + shadow.offset,
corner_radius: self.style.corner_radius, corner_radius: self.style.corner_radius,
sigma: shadow.blur, sigma: shadow.blur,
@ -229,7 +231,7 @@ impl Element for Container {
if let Some(hit_bounds) = quad_bounds.intersection(visible_bounds) { if let Some(hit_bounds) = quad_bounds.intersection(visible_bounds) {
if let Some(style) = self.style.cursor { if let Some(style) = self.style.cursor {
cx.scene.push_cursor_region(CursorRegion { scene.push_cursor_region(CursorRegion {
bounds: hit_bounds, bounds: hit_bounds,
style, style,
}); });
@ -240,25 +242,26 @@ impl Element for Container {
quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top); quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);
if self.style.border.overlay { if self.style.border.overlay {
cx.scene.push_quad(Quad { scene.push_quad(Quad {
bounds: quad_bounds, bounds: quad_bounds,
background: self.style.background_color, background: self.style.background_color,
border: Default::default(), border: Default::default(),
corner_radius: self.style.corner_radius, 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); scene.push_layer(None);
cx.scene.push_quad(Quad { scene.push_quad(Quad {
bounds: quad_bounds, bounds: quad_bounds,
background: self.style.overlay_color, background: self.style.overlay_color,
border: self.style.border, border: self.style.border,
corner_radius: self.style.corner_radius, corner_radius: self.style.corner_radius,
}); });
cx.scene.pop_layer(); scene.pop_layer();
} else { } else {
cx.scene.push_quad(Quad { scene.push_quad(Quad {
bounds: quad_bounds, bounds: quad_bounds,
background: self.style.background_color, background: self.style.background_color,
border: self.style.border, border: self.style.border,
@ -270,17 +273,18 @@ impl Element for Container {
self.style.border.left_width(), self.style.border.left_width(),
self.style.border.top_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() { if self.style.overlay_color.is_some() {
cx.scene.push_layer(None); scene.push_layer(None);
cx.scene.push_quad(Quad { scene.push_quad(Quad {
bounds: quad_bounds, bounds: quad_bounds,
background: self.style.overlay_color, background: self.style.overlay_color,
border: Default::default(), border: Default::default(),
corner_radius: 0., corner_radius: 0.,
}); });
cx.scene.pop_layer(); scene.pop_layer();
} }
} }
} }
@ -292,9 +296,10 @@ impl Element for Container {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
self.child.rect_for_text_range(range_utf16, cx) self.child.rect_for_text_range(range_utf16, view, cx)
} }
fn debug( fn debug(
@ -302,13 +307,14 @@ impl Element for Container {
bounds: RectF, bounds: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &crate::DebugContext, view: &V,
cx: &ViewContext<V>,
) -> serde_json::Value { ) -> serde_json::Value {
json!({ json!({
"type": "Container", "type": "Container",
"bounds": bounds.to_json(), "bounds": bounds.to_json(),
"details": self.style.to_json(), "details": self.style.to_json(),
"child": self.child.debug(cx), "child": self.child.debug(view, cx),
}) })
} }
} }

View file

@ -6,10 +6,9 @@ use crate::{
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
json::{json, ToJson}, json::{json, ToJson},
presenter::MeasurementContext, SceneBuilder, View, ViewContext,
DebugContext,
}; };
use crate::{Element, LayoutContext, PaintContext, SizeConstraint}; use crate::{Drawable, SizeConstraint};
#[derive(Default)] #[derive(Default)]
pub struct Empty { pub struct Empty {
@ -27,14 +26,15 @@ impl Empty {
} }
} }
impl Element for Empty { impl<V: View> Drawable<V> for Empty {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
_: &mut LayoutContext, _: &mut V,
_: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let x = if constraint.max.x().is_finite() && !self.collapsed { let x = if constraint.max.x().is_finite() && !self.collapsed {
constraint.max.x() constraint.max.x()
@ -52,10 +52,12 @@ impl Element for Empty {
fn paint( fn paint(
&mut self, &mut self,
_: &mut SceneBuilder,
_: RectF, _: RectF,
_: RectF, _: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
_: &mut PaintContext, _: &mut V,
_: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
} }
@ -66,7 +68,8 @@ impl Element for Empty {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &MeasurementContext, _: &V,
_: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
None None
} }
@ -76,7 +79,8 @@ impl Element for Empty {
bounds: RectF, bounds: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &DebugContext, _: &V,
_: &ViewContext<V>,
) -> serde_json::Value { ) -> serde_json::Value {
json!({ json!({
"type": "Empty", "type": "Empty",

View file

@ -2,20 +2,18 @@ use std::ops::Range;
use crate::{ use crate::{
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json, json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
presenter::MeasurementContext,
DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
}; };
use serde_json::json; use serde_json::json;
pub struct Expanded { pub struct Expanded<V: View> {
child: ElementBox, child: Element<V>,
full_width: bool, full_width: bool,
full_height: bool, full_height: bool,
} }
impl Expanded { impl<V: View> Expanded<V> {
pub fn new(child: ElementBox) -> Self { pub fn new(child: Element<V>) -> Self {
Self { Self {
child, child,
full_width: true, full_width: true,
@ -36,14 +34,15 @@ impl Expanded {
} }
} }
impl Element for Expanded { impl<V: View> Drawable<V> for Expanded<V> {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
mut constraint: SizeConstraint, mut constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
if self.full_width { if self.full_width {
constraint.min.set_x(constraint.max.x()); constraint.min.set_x(constraint.max.x());
@ -51,18 +50,21 @@ impl Element for Expanded {
if self.full_height { if self.full_height {
constraint.min.set_y(constraint.max.y()); constraint.min.set_y(constraint.max.y());
} }
let size = self.child.layout(constraint, cx); let size = self.child.layout(constraint, view, cx);
(size, ()) (size, ())
} }
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> 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( fn rect_for_text_range(
@ -72,9 +74,10 @@ impl Element for Expanded {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
self.child.rect_for_text_range(range_utf16, cx) self.child.rect_for_text_range(range_utf16, view, cx)
} }
fn debug( fn debug(
@ -82,13 +85,14 @@ impl Element for Expanded {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &DebugContext, view: &V,
cx: &ViewContext<V>,
) -> json::Value { ) -> json::Value {
json!({ json!({
"type": "Expanded", "type": "Expanded",
"full_width": self.full_width, "full_width": self.full_width,
"full_height": self.full_height, "full_height": self.full_height,
"child": self.child.debug(cx) "child": self.child.debug(view, cx)
}) })
} }
} }

View file

@ -2,9 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
use crate::{ use crate::{
json::{self, ToJson, Value}, json::{self, ToJson, Value},
presenter::MeasurementContext, Axis, Drawable, Element, ElementStateHandle, SceneBuilder, SizeConstraint, Vector2FExt, View,
Axis, DebugContext, Element, ElementBox, ElementStateHandle, LayoutContext, PaintContext, ViewContext,
RenderContext, SizeConstraint, Vector2FExt, View,
}; };
use pathfinder_geometry::{ use pathfinder_geometry::{
rect::RectF, rect::RectF,
@ -18,14 +17,14 @@ struct ScrollState {
scroll_position: Cell<f32>, scroll_position: Cell<f32>,
} }
pub struct Flex { pub struct Flex<V: View> {
axis: Axis, axis: Axis,
children: Vec<ElementBox>, children: Vec<Element<V>>,
scroll_state: Option<(ElementStateHandle<Rc<ScrollState>>, usize)>, scroll_state: Option<(ElementStateHandle<Rc<ScrollState>>, usize)>,
child_alignment: f32, child_alignment: f32,
} }
impl Flex { impl<V: View> Flex<V> {
pub fn new(axis: Axis) -> Self { pub fn new(axis: Axis) -> Self {
Self { Self {
axis, axis,
@ -52,15 +51,14 @@ impl Flex {
self self
} }
pub fn scrollable<Tag, V>( pub fn scrollable<Tag>(
mut self, mut self,
element_id: usize, element_id: usize,
scroll_to: Option<usize>, scroll_to: Option<usize>,
cx: &mut RenderContext<V>, cx: &mut ViewContext<V>,
) -> Self ) -> Self
where where
Tag: 'static, Tag: 'static,
V: View,
{ {
let scroll_state = cx.default_element_state::<Tag, Rc<ScrollState>>(element_id); let scroll_state = cx.default_element_state::<Tag, Rc<ScrollState>>(element_id);
scroll_state.read(cx).scroll_to.set(scroll_to); scroll_state.read(cx).scroll_to.set(scroll_to);
@ -75,7 +73,8 @@ impl Flex {
remaining_space: &mut f32, remaining_space: &mut f32,
remaining_flex: &mut f32, remaining_flex: &mut f32,
cross_axis_max: &mut f32, cross_axis_max: &mut f32,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) { ) {
let cross_axis = self.axis.invert(); let cross_axis = self.axis.invert();
for child in &mut self.children { for child in &mut self.children {
@ -102,7 +101,7 @@ impl Flex {
vec2f(constraint.max.x(), child_max), 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_space -= child_size.along(self.axis);
*remaining_flex -= flex; *remaining_flex -= flex;
*cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
@ -112,20 +111,21 @@ impl Flex {
} }
} }
impl Extend<ElementBox> for Flex { impl<V: View> Extend<Element<V>> for Flex<V> {
fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) { fn extend<T: IntoIterator<Item = Element<V>>>(&mut self, children: T) {
self.children.extend(children); self.children.extend(children);
} }
} }
impl Element for Flex { impl<V: View> Drawable<V> for Flex<V> {
type LayoutState = f32; type LayoutState = f32;
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let mut total_flex = None; let mut total_flex = None;
let mut fixed_space = 0.0; let mut fixed_space = 0.0;
@ -150,7 +150,7 @@ impl Element for Flex {
vec2f(constraint.max.x(), INFINITY), 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); fixed_space += size.along(self.axis);
cross_axis_max = cross_axis_max.max(size.along(cross_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_space,
&mut remaining_flex, &mut remaining_flex,
&mut cross_axis_max, &mut cross_axis_max,
view,
cx, cx,
); );
self.layout_flex_children( self.layout_flex_children(
@ -176,6 +177,7 @@ impl Element for Flex {
&mut remaining_space, &mut remaining_space,
&mut remaining_flex, &mut remaining_flex,
&mut cross_axis_max, &mut cross_axis_max,
view,
cx, cx,
); );
@ -247,26 +249,28 @@ impl Element for Flex {
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
remaining_space: &mut Self::LayoutState, remaining_space: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
let mut remaining_space = *remaining_space; let mut remaining_space = *remaining_space;
let overflowing = remaining_space < 0.; let overflowing = remaining_space < 0.;
if overflowing { if overflowing {
cx.scene.push_layer(Some(visible_bounds)); scene.push_layer(Some(visible_bounds));
} }
if let Some(scroll_state) = &self.scroll_state { 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) crate::MouseRegion::new::<Self>(scroll_state.1, 0, bounds)
.on_scroll({ .on_scroll({
let scroll_state = scroll_state.0.read(cx).clone(); let scroll_state = scroll_state.0.read(cx).clone();
let axis = self.axis; let axis = self.axis;
move |e, cx| { move |e, _: &mut V, cx| {
if remaining_space < 0. { if remaining_space < 0. {
let scroll_delta = e.delta.raw(); 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 aligned_child_origin
}; };
child.paint(aligned_child_origin, visible_bounds, cx); child.paint(scene, aligned_child_origin, visible_bounds, view, cx);
match self.axis { match self.axis {
Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
@ -352,7 +356,7 @@ impl Element for Flex {
} }
if overflowing { if overflowing {
cx.scene.pop_layer(); scene.pop_layer();
} }
} }
@ -363,11 +367,12 @@ impl Element for Flex {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
self.children self.children
.iter() .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( fn debug(
@ -375,13 +380,14 @@ impl Element for Flex {
bounds: RectF, bounds: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &DebugContext, view: &V,
cx: &ViewContext<V>,
) -> json::Value { ) -> json::Value {
json!({ json!({
"type": "Flex", "type": "Flex",
"bounds": bounds.to_json(), "bounds": bounds.to_json(),
"axis": self.axis.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, float: bool,
} }
pub struct FlexItem { pub struct FlexItem<V: View> {
metadata: FlexParentData, metadata: FlexParentData,
child: ElementBox, child: Element<V>,
} }
impl FlexItem { impl<V: View> FlexItem<V> {
pub fn new(child: ElementBox) -> Self { pub fn new(child: Element<V>) -> Self {
FlexItem { FlexItem {
metadata: FlexParentData { metadata: FlexParentData {
flex: None, flex: None,
@ -418,27 +424,31 @@ impl FlexItem {
} }
} }
impl Element for FlexItem { impl<V: View> Drawable<V> for FlexItem<V> {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, cx); let size = self.child.layout(constraint, view, cx);
(size, ()) (size, ())
} }
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> 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( fn rect_for_text_range(
@ -448,9 +458,10 @@ impl Element for FlexItem {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> 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> { fn metadata(&self) -> Option<&dyn Any> {
@ -462,12 +473,13 @@ impl Element for FlexItem {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &DebugContext, view: &V,
cx: &ViewContext<V>,
) -> Value { ) -> Value {
json!({ json!({
"type": "Flexible", "type": "Flexible",
"flex": self.metadata.flex, "flex": self.metadata.flex,
"child": self.child.debug(cx) "child": self.child.debug(view, cx)
}) })
} }
} }

View file

@ -3,17 +3,16 @@ use std::ops::Range;
use crate::{ use crate::{
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json::json, json::json,
presenter::MeasurementContext, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
}; };
pub struct Hook { pub struct Hook<V: View> {
child: ElementBox, child: Element<V>,
after_layout: Option<Box<dyn FnMut(Vector2F, &mut LayoutContext)>>, after_layout: Option<Box<dyn FnMut(Vector2F, &mut ViewContext<V>)>>,
} }
impl Hook { impl<V: View> Hook<V> {
pub fn new(child: ElementBox) -> Self { pub fn new(child: Element<V>) -> Self {
Self { Self {
child, child,
after_layout: None, after_layout: None,
@ -22,23 +21,24 @@ impl Hook {
pub fn on_after_layout( pub fn on_after_layout(
mut self, mut self,
f: impl 'static + FnMut(Vector2F, &mut LayoutContext), f: impl 'static + FnMut(Vector2F, &mut ViewContext<V>),
) -> Self { ) -> Self {
self.after_layout = Some(Box::new(f)); self.after_layout = Some(Box::new(f));
self self
} }
} }
impl Element for Hook { impl<V: View> Drawable<V> for Hook<V> {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (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() { if let Some(handler) = self.after_layout.as_mut() {
handler(size, cx); handler(size, cx);
} }
@ -47,12 +47,15 @@ impl Element for Hook {
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &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( fn rect_for_text_range(
@ -62,9 +65,10 @@ impl Element for Hook {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
self.child.rect_for_text_range(range_utf16, cx) self.child.rect_for_text_range(range_utf16, view, cx)
} }
fn debug( fn debug(
@ -72,11 +76,12 @@ impl Element for Hook {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &DebugContext, view: &V,
cx: &ViewContext<V>,
) -> serde_json::Value { ) -> serde_json::Value {
json!({ json!({
"type": "Hooks", "type": "Hooks",
"child": self.child.debug(cx), "child": self.child.debug(view, cx),
}) })
} }
} }

View file

@ -5,8 +5,7 @@ use crate::{
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
json::{json, ToJson}, json::{json, ToJson},
presenter::MeasurementContext, scene, Border, Drawable, ImageData, SceneBuilder, SizeConstraint, View, ViewContext,
scene, Border, DebugContext, Element, ImageData, LayoutContext, PaintContext, SizeConstraint,
}; };
use serde::Deserialize; use serde::Deserialize;
use std::{ops::Range, sync::Arc}; 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 LayoutState = Option<Arc<ImageData>>;
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, _: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let data = match &self.source { let data = match &self.source {
ImageSource::Path(path) => match cx.asset_cache.png(path) { ImageSource::Path(path) => match cx.asset_cache.png(path) {
@ -90,13 +90,15 @@ impl Element for Image {
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
_: RectF, _: RectF,
layout: &mut Self::LayoutState, layout: &mut Self::LayoutState,
cx: &mut PaintContext, _: &mut V,
_: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
if let Some(data) = layout { if let Some(data) = layout {
cx.scene.push_image(scene::Image { scene.push_image(scene::Image {
bounds, bounds,
border: self.style.border, border: self.style.border,
corner_radius: self.style.corner_radius, corner_radius: self.style.corner_radius,
@ -113,7 +115,8 @@ impl Element for Image {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &MeasurementContext, _: &V,
_: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
None None
} }
@ -123,7 +126,8 @@ impl Element for Image {
bounds: RectF, bounds: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &DebugContext, _: &V,
_: &ViewContext<V>,
) -> serde_json::Value { ) -> serde_json::Value {
json!({ json!({
"type": "Image", "type": "Image",

View file

@ -2,7 +2,7 @@ use crate::{
elements::*, elements::*,
fonts::TextStyle, fonts::TextStyle,
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
Action, ElementBox, LayoutContext, PaintContext, SizeConstraint, Action, Element, SizeConstraint,
}; };
use serde_json::json; use serde_json::json;
@ -12,20 +12,17 @@ pub struct KeystrokeLabel {
action: Box<dyn Action>, action: Box<dyn Action>,
container_style: ContainerStyle, container_style: ContainerStyle,
text_style: TextStyle, text_style: TextStyle,
window_id: usize,
view_id: usize, view_id: usize,
} }
impl KeystrokeLabel { impl KeystrokeLabel {
pub fn new( pub fn new(
window_id: usize,
view_id: usize, view_id: usize,
action: Box<dyn Action>, action: Box<dyn Action>,
container_style: ContainerStyle, container_style: ContainerStyle,
text_style: TextStyle, text_style: TextStyle,
) -> Self { ) -> Self {
Self { Self {
window_id,
view_id, view_id,
action, action,
container_style, container_style,
@ -34,18 +31,18 @@ impl KeystrokeLabel {
} }
} }
impl Element for KeystrokeLabel { impl<V: View> Drawable<V> for KeystrokeLabel {
type LayoutState = ElementBox; type LayoutState = Element<V>;
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
) -> (Vector2F, ElementBox) { cx: &mut ViewContext<V>,
) -> (Vector2F, Element<V>) {
let mut element = if let Some(keystrokes) = let mut element = if let Some(keystrokes) =
cx.app cx.keystrokes_for_action(self.view_id, self.action.as_ref())
.keystrokes_for_action(self.window_id, self.view_id, self.action.as_ref())
{ {
Flex::row() Flex::row()
.with_children(keystrokes.iter().map(|keystroke| { .with_children(keystrokes.iter().map(|keystroke| {
@ -59,18 +56,20 @@ impl Element for KeystrokeLabel {
Empty::new().collapsed().boxed() Empty::new().collapsed().boxed()
}; };
let size = element.layout(constraint, cx); let size = element.layout(constraint, view, cx);
(size, element) (size, element)
} }
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
element: &mut ElementBox, element: &mut Element<V>,
cx: &mut PaintContext, 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( fn rect_for_text_range(
@ -80,7 +79,8 @@ impl Element for KeystrokeLabel {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &MeasurementContext, _: &V,
_: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
None None
} }
@ -88,14 +88,15 @@ impl Element for KeystrokeLabel {
fn debug( fn debug(
&self, &self,
_: RectF, _: RectF,
element: &ElementBox, element: &Element<V>,
_: &(), _: &(),
cx: &crate::DebugContext, view: &V,
cx: &ViewContext<V>,
) -> serde_json::Value { ) -> serde_json::Value {
json!({ json!({
"type": "KeystrokeLabel", "type": "KeystrokeLabel",
"action": self.action.name(), "action": self.action.name(),
"child": element.debug(cx) "child": element.debug(view, cx)
}) })
} }
} }

View file

@ -7,9 +7,8 @@ use crate::{
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
json::{ToJson, Value}, json::{ToJson, Value},
presenter::MeasurementContext,
text_layout::{Line, RunStyle}, text_layout::{Line, RunStyle},
DebugContext, Element, LayoutContext, PaintContext, SizeConstraint, Drawable, SceneBuilder, SizeConstraint, View, ViewContext,
}; };
use serde::Deserialize; use serde::Deserialize;
use serde_json::json; 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 LayoutState = Line;
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, _: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let runs = self.compute_runs(); let runs = self.compute_runs();
let line = let line = cx.text_layout_cache().layout_str(
cx.text_layout_cache &self.text,
.layout_str(&self.text, self.style.text.font_size, runs.as_slice()); self.style.text.font_size,
runs.as_slice(),
);
let size = vec2f( let size = vec2f(
line.width() line.width()
@ -155,12 +157,20 @@ impl Element for Label {
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
line: &mut Self::LayoutState, line: &mut Self::LayoutState,
cx: &mut PaintContext, _: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> 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( fn rect_for_text_range(
@ -170,7 +180,8 @@ impl Element for Label {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &MeasurementContext, _: &V,
_: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
None None
} }
@ -180,7 +191,8 @@ impl Element for Label {
bounds: RectF, bounds: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &DebugContext, _: &V,
_: &ViewContext<V>,
) -> Value { ) -> Value {
json!({ json!({
"type": "Label", "type": "Label",

View file

@ -4,19 +4,16 @@ use crate::{
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
json::json, json::json,
presenter::MeasurementContext, Drawable, Element, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
DebugContext, Element, ElementBox, ElementRc, EventContext, LayoutContext, MouseRegion,
PaintContext, RenderContext, 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}; use sum_tree::{Bias, SumTree};
pub struct List { pub struct List<V: View> {
state: ListState, state: ListState<V>,
} }
#[derive(Clone)] pub struct ListState<V: View>(Rc<RefCell<StateInner<V>>>);
pub struct ListState(Rc<RefCell<StateInner>>);
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Orientation { pub enum Orientation {
@ -24,16 +21,16 @@ pub enum Orientation {
Bottom, Bottom,
} }
struct StateInner { struct StateInner<V: View> {
last_layout_width: Option<f32>, 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>, rendered_range: Range<usize>,
items: SumTree<ListItem>, items: SumTree<ListItem<V>>,
logical_scroll_top: Option<ListOffset>, logical_scroll_top: Option<ListOffset>,
orientation: Orientation, orientation: Orientation,
overdraw: f32, overdraw: f32,
#[allow(clippy::type_complexity)] #[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)] #[derive(Clone, Copy, Debug, Default, PartialEq)]
@ -42,14 +39,23 @@ pub struct ListOffset {
pub offset_in_item: f32, pub offset_in_item: f32,
} }
#[derive(Clone)] enum ListItem<V: View> {
enum ListItem {
Unrendered, Unrendered,
Rendered(ElementRc), Rendered(Rc<RefCell<Element<V>>>),
Removed(f32), 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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Unrendered => write!(f, "Unrendered"), Self::Unrendered => write!(f, "Unrendered"),
@ -79,20 +85,21 @@ struct UnrenderedCount(usize);
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct Height(f32); struct Height(f32);
impl List { impl<V: View> List<V> {
pub fn new(state: ListState) -> Self { pub fn new(state: ListState<V>) -> Self {
Self { state } Self { state }
} }
} }
impl Element for List { impl<V: View> Drawable<V> for List<V> {
type LayoutState = ListOffset; type LayoutState = ListOffset;
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let state = &mut *self.state.0.borrow_mut(); let state = &mut *self.state.0.borrow_mut();
let size = constraint.max; let size = constraint.max;
@ -134,9 +141,10 @@ impl Element for List {
scroll_top.item_ix + ix, scroll_top.item_ix + ix,
existing_element, existing_element,
item_constraint, item_constraint,
view,
cx, cx,
) { ) {
rendered_height += element.size().y(); rendered_height += element.borrow().size().y();
rendered_items.push_back(ListItem::Rendered(element)); rendered_items.push_back(ListItem::Rendered(element));
} }
} }
@ -151,9 +159,9 @@ impl Element for List {
cursor.prev(&()); cursor.prev(&());
if cursor.item().is_some() { if cursor.item().is_some() {
if let Some(element) = 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)); rendered_items.push_front(ListItem::Rendered(element));
} }
} else { } else {
@ -187,9 +195,9 @@ impl Element for List {
cursor.prev(&()); cursor.prev(&());
if let Some(item) = cursor.item() { if let Some(item) = cursor.item() {
if let Some(element) = 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)); rendered_items.push_front(ListItem::Rendered(element));
} }
} else { } else {
@ -241,25 +249,27 @@ impl Element for List {
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
scroll_top: &mut ListOffset, scroll_top: &mut ListOffset,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) { ) {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default(); let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
cx.scene.push_layer(Some(visible_bounds)); scene.push_layer(Some(visible_bounds));
scene.push_mouse_region(
cx.scene.push_mouse_region( MouseRegion::new::<Self>(cx.view_id(), 0, bounds).on_scroll({
MouseRegion::new::<Self>(cx.current_view_id(), 0, bounds).on_scroll({
let state = self.state.clone(); let state = self.state.clone();
let height = bounds.height(); let height = bounds.height();
let scroll_top = scroll_top.clone(); let scroll_top = scroll_top.clone();
move |e, cx| { move |e, view, cx| {
state.0.borrow_mut().scroll( state.0.borrow_mut().scroll(
&scroll_top, &scroll_top,
height, height,
*e.platform_event.delta.raw(), *e.platform_event.delta.raw(),
e.platform_event.delta.precise(), e.platform_event.delta.precise(),
view,
cx, cx,
) )
} }
@ -267,11 +277,13 @@ impl Element for List {
); );
let state = &mut *self.state.0.borrow_mut(); let state = &mut *self.state.0.borrow_mut();
for (mut element, origin) in state.visible_elements(bounds, scroll_top) { for (element, origin) in state.visible_elements(bounds, scroll_top) {
element.paint(origin, visible_bounds, cx); element
.borrow_mut()
.paint(scene, origin, visible_bounds, view, cx);
} }
cx.scene.pop_layer(); scene.pop_layer();
} }
fn rect_for_text_range( fn rect_for_text_range(
@ -281,7 +293,8 @@ impl Element for List {
_: RectF, _: RectF,
scroll_top: &Self::LayoutState, scroll_top: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
let state = self.state.0.borrow(); let state = self.state.0.borrow();
let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item); 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 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); 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(&()); cursor.next(&());
} else { } else {
unreachable!(); unreachable!();
@ -312,12 +329,13 @@ impl Element for List {
bounds: RectF, bounds: RectF,
scroll_top: &Self::LayoutState, scroll_top: &Self::LayoutState,
_: &(), _: &(),
cx: &DebugContext, view: &V,
cx: &ViewContext<V>,
) -> serde_json::Value { ) -> serde_json::Value {
let state = self.state.0.borrow_mut(); let state = self.state.0.borrow_mut();
let visible_elements = state let visible_elements = state
.visible_elements(bounds, scroll_top) .visible_elements(bounds, scroll_top)
.map(|e| e.0.debug(cx)) .map(|e| e.0.borrow().debug(view, cx))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let visible_range = scroll_top.item_ix..(scroll_top.item_ix + visible_elements.len()); let visible_range = scroll_top.item_ix..(scroll_top.item_ix + visible_elements.len());
json!({ json!({
@ -328,27 +346,22 @@ impl Element for List {
} }
} }
impl ListState { impl<V: View> ListState<V> {
pub fn new<F, V>( pub fn new<F>(
element_count: usize, element_count: usize,
orientation: Orientation, orientation: Orientation,
overdraw: f32, overdraw: f32,
cx: &mut ViewContext<V>, render_item: F,
mut render_item: F,
) -> Self ) -> Self
where where
V: View, 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(); let mut items = SumTree::new();
items.extend((0..element_count).map(|_| ListItem::Unrendered), &()); items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
let handle = cx.weak_handle();
Self(Rc::new(RefCell::new(StateInner { Self(Rc::new(RefCell::new(StateInner {
last_layout_width: None, last_layout_width: None,
render_item: Box::new(move |ix, cx| { render_item: Box::new(render_item),
let handle = handle.upgrade(cx)?;
Some(cx.render(&handle, |view, cx| render_item(view, ix, cx)))
}),
rendered_range: 0..0, rendered_range: 0..0,
items, items,
logical_scroll_top: None, logical_scroll_top: None,
@ -406,7 +419,7 @@ impl ListState {
pub fn set_scroll_handler( pub fn set_scroll_handler(
&mut self, &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)) 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( fn render_item(
&mut self, &mut self,
ix: usize, ix: usize,
existing_element: Option<&ListItem>, existing_element: Option<&ListItem<V>>,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
) -> Option<ElementRc> { cx: &mut ViewContext<V>,
) -> Option<Rc<RefCell<Element<V>>>> {
if let Some(ListItem::Rendered(element)) = existing_element { if let Some(ListItem::Rendered(element)) = existing_element {
Some(element.clone()) Some(element.clone())
} else { } else {
let mut element = (self.render_item)(ix, cx)?; let mut element = (self.render_item)(view, ix, cx);
element.layout(constraint, cx); element.layout(constraint, view, cx);
Some(element.into()) Some(Rc::new(RefCell::new(element)))
} }
} }
@ -455,7 +475,7 @@ impl StateInner {
&'a self, &'a self,
bounds: RectF, bounds: RectF,
scroll_top: &ListOffset, 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 item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
let mut cursor = self.items.cursor::<Count>(); let mut cursor = self.items.cursor::<Count>();
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
@ -467,7 +487,7 @@ impl StateInner {
if let ListItem::Rendered(element) = item { if let ListItem::Rendered(element) = item {
let result = (element.clone(), item_origin); 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(&()); cursor.next(&());
return Some(result); return Some(result);
} }
@ -485,7 +505,8 @@ impl StateInner {
height: f32, height: f32,
mut delta: Vector2F, mut delta: Vector2F,
precise: bool, precise: bool,
cx: &mut EventContext, view: &mut V,
cx: &mut ViewContext<V>,
) { ) {
if !precise { if !precise {
delta *= 20.; delta *= 20.;
@ -511,7 +532,7 @@ impl StateInner {
if self.scroll_handler.is_some() { if self.scroll_handler.is_some() {
let visible_range = self.visible_range(height, scroll_top); 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(); cx.notify();
@ -538,17 +559,17 @@ impl StateInner {
} }
} }
impl ListItem { impl<V: View> ListItem<V> {
fn remove(&self) -> Self { fn remove(&self) -> Self {
match self { match self {
ListItem::Unrendered => ListItem::Unrendered, 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), ListItem::Removed(height) => ListItem::Removed(*height),
} }
} }
} }
impl sum_tree::Item for ListItem { impl<V: View> sum_tree::Item for ListItem<V> {
type Summary = ListItemSummary; type Summary = ListItemSummary;
fn summary(&self) -> Self::Summary { fn summary(&self) -> Self::Summary {
@ -563,7 +584,7 @@ impl sum_tree::Item for ListItem {
count: 1, count: 1,
rendered_count: 1, rendered_count: 1,
unrendered_count: 0, unrendered_count: 0,
height: element.size().y(), height: element.borrow().size().y(),
}, },
ListItem::Removed(height) => ListItemSummary { ListItem::Removed(height) => ListItemSummary {
count: 1, count: 1,
@ -631,264 +652,261 @@ mod tests {
#[crate::test(self)] #[crate::test(self)]
fn test_layout(cx: &mut crate::AppContext) { fn test_layout(cx: &mut crate::AppContext) {
let mut presenter = cx.build_presenter(0, 0., Default::default()); cx.add_window(Default::default(), |cx| {
let (_, view) = cx.add_window(Default::default(), |_| TestView); let mut view = TestView;
let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)); let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
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 state = view.update(cx, |_, cx| {
ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, cx, {
let elements = elements.clone(); let elements = elements.clone();
move |_, ix, _| { move |_, ix, _| {
let (id, height) = elements.borrow()[ix]; let (id, height) = elements.borrow()[ix];
TestElement::new(id, height).boxed() TestElement::new(id, height).boxed()
} }
}) });
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 view,
cx,
);
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
}); });
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.
}
);
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.
}
);
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.);
} }
#[crate::test(self, iterations = 10, seed = 0)] #[crate::test(self, iterations = 10)]
fn test_random(cx: &mut crate::AppContext, mut rng: StdRng) { fn test_random(cx: &mut crate::AppContext, mut rng: StdRng) {
let operations = env::var("OPERATIONS") let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10); .unwrap_or(10);
let (_, view) = cx.add_window(Default::default(), |_| TestView); cx.add_window(Default::default(), |cx| {
let mut presenter = cx.build_presenter(0, 0., Default::default()); 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 = view.update(cx, |_, cx| { let mut next_id = 0;
ListState::new(elements.borrow().len(), orientation, overdraw, cx, { 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(); let elements = elements.clone();
move |_, ix, _| { move |_, ix, _| {
let (id, height) = elements.borrow()[ix]; let (id, height) = elements.borrow()[ix];
TestElement::new(id, height).boxed() TestElement::new(id, height).boxed()
} }
}) });
});
let mut width = rng.gen_range(0..=2000) as f32 / 2.; let mut width = rng.gen_range(0..=2000) as f32 / 2.;
let mut height = 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!("orientation: {:?}", orientation);
log::info!("overdraw: {}", overdraw); log::info!("overdraw: {}", overdraw);
log::info!("elements: {:?}", elements.borrow()); log::info!("elements: {:?}", elements.borrow());
log::info!("size: ({:?}, {:?})", width, height); log::info!("size: ({:?}, {:?})", width, height);
log::info!("=================="); log::info!("==================");
let mut last_logical_scroll_top = None; let mut last_logical_scroll_top = None;
for _ in 0..operations { for _ in 0..operations {
match rng.gen_range(0..=100) { match rng.gen_range(0..=100) {
0..=29 if last_logical_scroll_top.is_some() => { 0..=29 if last_logical_scroll_top.is_some() => {
let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw)); let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
log::info!( log::info!(
"Scrolling by {:?}, previous scroll top: {:?}", "Scrolling by {:?}, previous scroll top: {:?}",
delta, delta,
last_logical_scroll_top.unwrap() last_logical_scroll_top.unwrap()
); );
state.0.borrow_mut().scroll( state.0.borrow_mut().scroll(
last_logical_scroll_top.as_ref().unwrap(), last_logical_scroll_top.as_ref().unwrap(),
height, height,
delta, delta,
true, true,
&mut presenter.build_event_context(&mut Default::default(), cx), &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);
});
}
}
}
} }
30..=34 => {
width = rng.gen_range(0..=2000) as f32 / 2.; let mut list = List::new(state.clone());
log::info!("changing width: {:?}", width); let window_size = vec2f(width, height);
} let (size, logical_scroll_top) = list.layout(
35..=54 => { SizeConstraint::new(vec2f(0., 0.), window_size),
height = rng.gen_range(0..=1000) as f32 / 2.; &mut view,
log::info!("changing height: {:?}", height); cx,
} );
_ => { assert_eq!(size, window_size);
let mut elements = elements.borrow_mut(); last_logical_scroll_top = Some(logical_scroll_top);
let end_ix = rng.gen_range(0..=elements.len());
let start_ix = rng.gen_range(0..=end_ix); let state = state.0.borrow();
let new_elements = (0..rng.gen_range(0..10)) log::info!("items {:?}", state.items.items(&()));
.map(|_| {
let id = next_id; let scroll_top = state.scroll_top(&logical_scroll_top);
next_id += 1; let rendered_top = (scroll_top - overdraw).max(0.);
(id, rng.gen_range(0..=200) as f32 / 2.) let rendered_bottom = scroll_top + height + overdraw;
}) let mut item_top = 0.;
.collect::<Vec<_>>();
log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements); log::info!(
state.splice(start_ix..end_ix, new_elements.len()); "rendered top {:?}, rendered bottom {:?}, scroll top {:?}",
elements.splice(start_ix..end_ix, new_elements); rendered_top,
for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() { rendered_bottom,
if let ListItem::Rendered(element) = item { scroll_top,
let (expected_id, _) = elements[ix]; );
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>| { element.with_metadata(|metadata: Option<&usize>| {
assert_eq!(*metadata.unwrap(), expected_id); 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;
}
}
}
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);
} }
} }
} }
} }
let mut list = List::new(state.clone()); view
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;
}
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;
}
}
}
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);
}
}
}
}
} }
struct TestView; struct TestView;
@ -902,7 +920,7 @@ mod tests {
"TestView" "TestView"
} }
fn render(&mut self, _: &mut RenderContext<'_, Self>) -> ElementBox { fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
Empty::new().boxed() Empty::new().boxed()
} }
} }
@ -921,15 +939,28 @@ mod tests {
} }
} }
impl Element for TestElement { impl<V: View> Drawable<V> for TestElement {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout(&mut self, _: SizeConstraint, _: &mut LayoutContext) -> (Vector2F, ()) { fn layout(
&mut self,
_: SizeConstraint,
_: &mut V,
_: &mut ViewContext<V>,
) -> (Vector2F, ()) {
(self.size, ()) (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!() todo!()
} }
@ -940,12 +971,13 @@ mod tests {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &MeasurementContext, _: &V,
_: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
todo!() todo!()
} }
fn debug(&self, _: RectF, _: &(), _: &(), _: &DebugContext) -> serde_json::Value { fn debug(&self, _: RectF, _: &(), _: &(), _: &V, _: &ViewContext<V>) -> serde_json::Value {
self.id.into() self.id.into()
} }

View file

@ -10,14 +10,14 @@ use crate::{
CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover, CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover,
MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
}, },
DebugContext, Element, ElementBox, EventContext, LayoutContext, MeasurementContext, Drawable, Element, EventContext, MouseRegion, MouseState, SceneBuilder, SizeConstraint, View,
MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View, ViewContext,
}; };
use serde_json::json; use serde_json::json;
use std::{marker::PhantomData, ops::Range}; use std::{marker::PhantomData, ops::Range};
pub struct MouseEventHandler<Tag: 'static> { pub struct MouseEventHandler<Tag: 'static, V: View> {
child: ElementBox, child: Element<V>,
region_id: usize, region_id: usize,
cursor_style: Option<CursorStyle>, cursor_style: Option<CursorStyle>,
handlers: HandlerSet, 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 /// Element which provides a render_child callback with a MouseState and paints a mouse
/// region under (or above) it for easy mouse event handling. /// region under (or above) it for easy mouse event handling.
impl<Tag> MouseEventHandler<Tag> { impl<Tag, V: View> MouseEventHandler<Tag, V> {
pub fn new<V, F>(region_id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self pub fn new<F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
where where
V: View, 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 mut mouse_state = cx.mouse_state::<Tag>(region_id);
let child = render_child(&mut mouse_state, cx); 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 /// 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 /// for drag and drop handling and similar events which should be captured before the child
/// gets the opportunity /// 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 where
V: View, 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); let mut handler = Self::new(region_id, cx, render_child);
handler.above = true; handler.above = true;
@ -78,14 +78,17 @@ impl<Tag> MouseEventHandler<Tag> {
self 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.handlers = self.handlers.on_move(handler);
self self
} }
pub fn on_move_out( pub fn on_move_out(
mut self, mut self,
handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static, handler: impl Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
) -> Self { ) -> Self {
self.handlers = self.handlers.on_move_out(handler); self.handlers = self.handlers.on_move_out(handler);
self self
@ -94,7 +97,7 @@ impl<Tag> MouseEventHandler<Tag> {
pub fn on_down( pub fn on_down(
mut self, mut self,
button: MouseButton, button: MouseButton,
handler: impl Fn(MouseDown, &mut EventContext) + 'static, handler: impl Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
) -> Self { ) -> Self {
self.handlers = self.handlers.on_down(button, handler); self.handlers = self.handlers.on_down(button, handler);
self self
@ -103,7 +106,7 @@ impl<Tag> MouseEventHandler<Tag> {
pub fn on_up( pub fn on_up(
mut self, mut self,
button: MouseButton, button: MouseButton,
handler: impl Fn(MouseUp, &mut EventContext) + 'static, handler: impl Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
) -> Self { ) -> Self {
self.handlers = self.handlers.on_up(button, handler); self.handlers = self.handlers.on_up(button, handler);
self self
@ -112,7 +115,7 @@ impl<Tag> MouseEventHandler<Tag> {
pub fn on_click( pub fn on_click(
mut self, mut self,
button: MouseButton, button: MouseButton,
handler: impl Fn(MouseClick, &mut EventContext) + 'static, handler: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
) -> Self { ) -> Self {
self.handlers = self.handlers.on_click(button, handler); self.handlers = self.handlers.on_click(button, handler);
self self
@ -121,7 +124,7 @@ impl<Tag> MouseEventHandler<Tag> {
pub fn on_down_out( pub fn on_down_out(
mut self, mut self,
button: MouseButton, button: MouseButton,
handler: impl Fn(MouseDownOut, &mut EventContext) + 'static, handler: impl Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
) -> Self { ) -> Self {
self.handlers = self.handlers.on_down_out(button, handler); self.handlers = self.handlers.on_down_out(button, handler);
self self
@ -130,7 +133,7 @@ impl<Tag> MouseEventHandler<Tag> {
pub fn on_up_out( pub fn on_up_out(
mut self, mut self,
button: MouseButton, button: MouseButton,
handler: impl Fn(MouseUpOut, &mut EventContext) + 'static, handler: impl Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
) -> Self { ) -> Self {
self.handlers = self.handlers.on_up_out(button, handler); self.handlers = self.handlers.on_up_out(button, handler);
self self
@ -139,20 +142,23 @@ impl<Tag> MouseEventHandler<Tag> {
pub fn on_drag( pub fn on_drag(
mut self, mut self,
button: MouseButton, button: MouseButton,
handler: impl Fn(MouseDrag, &mut EventContext) + 'static, handler: impl Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
) -> Self { ) -> Self {
self.handlers = self.handlers.on_drag(button, handler); self.handlers = self.handlers.on_drag(button, handler);
self 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.handlers = self.handlers.on_hover(handler);
self self
} }
pub fn on_scroll( pub fn on_scroll(
mut self, mut self,
handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static, handler: impl Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
) -> Self { ) -> Self {
self.handlers = self.handlers.on_scroll(handler); self.handlers = self.handlers.on_scroll(handler);
self self
@ -176,19 +182,25 @@ impl<Tag> MouseEventHandler<Tag> {
.round_out() .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 visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
let hit_bounds = self.hit_bounds(visible_bounds); let hit_bounds = self.hit_bounds(visible_bounds);
if let Some(style) = self.cursor_style { if let Some(style) = self.cursor_style {
cx.scene.push_cursor_region(CursorRegion { scene.push_cursor_region(CursorRegion {
bounds: hit_bounds, bounds: hit_bounds,
style, style,
}); });
} }
cx.scene.push_mouse_region( scene.push_mouse_region(
MouseRegion::from_handlers::<Tag>( MouseRegion::from_handlers::<Tag>(
cx.current_view_id(), cx.view_id(),
self.region_id, self.region_id,
hit_bounds, hit_bounds,
self.handlers.clone(), 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 LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, cx), ()) (self.child.layout(constraint, view, cx), ())
} }
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
if self.above { 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| { scene.paint_layer(None, |scene| {
self.paint_regions(bounds, visible_bounds, cx); self.paint_regions(scene, bounds, visible_bounds, cx);
}); });
} else { } else {
self.paint_regions(bounds, visible_bounds, cx); self.paint_regions(scene, bounds, visible_bounds, cx);
self.child.paint(bounds.origin(), visible_bounds, cx); self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
} }
} }
@ -238,9 +255,10 @@ impl<Tag> Element for MouseEventHandler<Tag> {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
self.child.rect_for_text_range(range_utf16, cx) self.child.rect_for_text_range(range_utf16, view, cx)
} }
fn debug( fn debug(
@ -248,11 +266,12 @@ impl<Tag> Element for MouseEventHandler<Tag> {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &DebugContext, view: &V,
cx: &ViewContext<V>,
) -> serde_json::Value { ) -> serde_json::Value {
json!({ json!({
"type": "MouseEventHandler", "type": "MouseEventHandler",
"child": self.child.debug(cx), "child": self.child.debug(view, cx),
}) })
} }
} }

View file

@ -3,14 +3,12 @@ use std::ops::Range;
use crate::{ use crate::{
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json::ToJson, json::ToJson,
presenter::MeasurementContext, Axis, Drawable, Element, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
Axis, DebugContext, Element, ElementBox, LayoutContext, MouseRegion, PaintContext,
SizeConstraint,
}; };
use serde_json::json; use serde_json::json;
pub struct Overlay { pub struct Overlay<V: View> {
child: ElementBox, child: Element<V>,
anchor_position: Option<Vector2F>, anchor_position: Option<Vector2F>,
anchor_corner: AnchorCorner, anchor_corner: AnchorCorner,
fit_mode: OverlayFitMode, fit_mode: OverlayFitMode,
@ -74,8 +72,8 @@ impl AnchorCorner {
} }
} }
impl Overlay { impl<V: View> Overlay<V> {
pub fn new(child: ElementBox) -> Self { pub fn new(child: Element<V>) -> Self {
Self { Self {
child, child,
anchor_position: None, 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 LayoutState = Vector2F;
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let constraint = if self.anchor_position.is_some() { let constraint = if self.anchor_position.is_some() {
SizeConstraint::new(Vector2F::zero(), cx.window_size) SizeConstraint::new(Vector2F::zero(), cx.window_size())
} else { } else {
constraint constraint
}; };
let size = self.child.layout(constraint, cx); let size = self.child.layout(constraint, view, cx);
(Vector2F::zero(), size) (Vector2F::zero(), size)
} }
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
_: RectF, _: RectF,
size: &mut Self::LayoutState, size: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) { ) {
let (anchor_position, mut bounds) = match self.position_mode { let (anchor_position, mut bounds) = match self.position_mode {
OverlayPositionMode::Window => { OverlayPositionMode::Window => {
@ -162,9 +163,9 @@ impl Element for Overlay {
OverlayFitMode::SnapToWindow => { OverlayFitMode::SnapToWindow => {
// Snap the horizontal edges of the overlay to the horizontal edges of the window if // Snap the horizontal edges of the overlay to the horizontal edges of the window if
// its horizontal bounds overflow // 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(); 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); bounds = RectF::from_points(lower_right - *size, lower_right);
} else if bounds.min_x() < 0. { } else if bounds.min_x() < 0. {
let mut upper_left = bounds.origin(); 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 // Snap the vertical edges of the overlay to the vertical edges of the window if
// its vertical bounds overflow. // 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(); 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); bounds = RectF::from_points(lower_right - *size, lower_right);
} else if bounds.min_y() < 0. { } else if bounds.min_y() < 0. {
let mut upper_left = bounds.origin(); let mut upper_left = bounds.origin();
@ -187,11 +188,11 @@ impl Element for Overlay {
OverlayFitMode::SwitchAnchor => { OverlayFitMode::SwitchAnchor => {
let mut anchor_corner = self.anchor_corner; 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); 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); anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
} }
@ -211,21 +212,22 @@ impl Element for Overlay {
OverlayFitMode::None => {} OverlayFitMode::None => {}
} }
cx.paint_stacking_context(None, self.z_index, |cx| { scene.paint_stacking_context(None, self.z_index, |scene| {
if self.hoverable { if self.hoverable {
enum OverlayHoverCapture {} enum OverlayHoverCapture {}
// Block hovers in lower stacking contexts // Block hovers in lower stacking contexts
cx.scene scene.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>( cx.view_id(),
cx.current_view_id(), cx.view_id(),
cx.current_view_id(), bounds,
bounds, ));
));
} }
self.child.paint( self.child.paint(
scene,
bounds.origin(), bounds.origin(),
RectF::new(Vector2F::zero(), cx.window_size), RectF::new(Vector2F::zero(), cx.window_size()),
view,
cx, cx,
); );
}); });
@ -238,9 +240,10 @@ impl Element for Overlay {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
self.child.rect_for_text_range(range_utf16, cx) self.child.rect_for_text_range(range_utf16, view, cx)
} }
fn debug( fn debug(
@ -248,12 +251,13 @@ impl Element for Overlay {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &DebugContext, view: &V,
cx: &ViewContext<V>,
) -> serde_json::Value { ) -> serde_json::Value {
json!({ json!({
"type": "Overlay", "type": "Overlay",
"abs_position": self.anchor_position.to_json(), "abs_position": self.anchor_position.to_json(),
"child": self.child.debug(cx), "child": self.child.debug(view, cx),
}) })
} }
} }

View file

@ -7,7 +7,7 @@ use crate::{
geometry::rect::RectF, geometry::rect::RectF,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
scene::MouseDrag, scene::MouseDrag,
Axis, Element, ElementBox, ElementStateHandle, MouseRegion, RenderContext, View, Axis, Drawable, Element, ElementStateHandle, MouseRegion, SceneBuilder, View, ViewContext,
}; };
use super::{ConstrainedBox, Hook}; use super::{ConstrainedBox, Hook};
@ -75,22 +75,22 @@ struct ResizeHandleState {
custom_dimension: Cell<f32>, custom_dimension: Cell<f32>,
} }
pub struct Resizable { pub struct Resizable<V: View> {
side: Side, side: Side,
handle_size: f32, handle_size: f32,
child: ElementBox, child: Element<V>,
state: Rc<ResizeHandleState>, state: Rc<ResizeHandleState>,
_state_handle: ElementStateHandle<Rc<ResizeHandleState>>, _state_handle: ElementStateHandle<Rc<ResizeHandleState>>,
} }
impl Resizable { impl<V: View> Resizable<V> {
pub fn new<Tag: 'static, T: View>( pub fn new<Tag: 'static, T: View>(
child: ElementBox, child: Element<V>,
element_id: usize, element_id: usize,
side: Side, side: Side,
handle_size: f32, handle_size: f32,
initial_size: f32, initial_size: f32,
cx: &mut RenderContext<T>, cx: &mut ViewContext<V>,
) -> Self { ) -> Self {
let state_handle = cx.element_state::<Tag, Rc<ResizeHandleState>>( let state_handle = cx.element_state::<Tag, Rc<ResizeHandleState>>(
element_id, element_id,
@ -132,51 +132,50 @@ impl Resizable {
} }
} }
impl Element for Resizable { impl<V: View> Drawable<V> for Resizable<V> {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: crate::SizeConstraint, constraint: crate::SizeConstraint,
cx: &mut crate::LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, cx), ()) (self.child.layout(constraint, view, cx), ())
} }
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: pathfinder_geometry::rect::RectF, bounds: pathfinder_geometry::rect::RectF,
visible_bounds: pathfinder_geometry::rect::RectF, visible_bounds: pathfinder_geometry::rect::RectF,
_child_size: &mut Self::LayoutState, _child_size: &mut Self::LayoutState,
cx: &mut crate::PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> 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); let handle_region = self.side.of_rect(bounds, self.handle_size);
enum ResizeHandle {} enum ResizeHandle {}
cx.scene.push_mouse_region( scene.push_mouse_region(
MouseRegion::new::<ResizeHandle>( MouseRegion::new::<ResizeHandle>(cx.view_id(), self.side as usize, handle_region)
cx.current_view_id(), .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
self.side as usize, .on_drag(MouseButton::Left, {
handle_region, let state = self.state.clone();
) let side = self.side;
.on_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere move |e, _: &mut V, cx| {
.on_drag(MouseButton::Left, { let prev_width = state.actual_dimension.get();
let state = self.state.clone(); state
let side = self.side; .custom_dimension
move |e, cx| { .set(0f32.max(prev_width + side.compute_delta(e)).round());
let prev_width = state.actual_dimension.get(); cx.notify();
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, bounds: handle_region,
style: match self.side.axis() { style: match self.side.axis() {
Axis::Horizontal => CursorStyle::ResizeLeftRight, 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( fn rect_for_text_range(
@ -196,9 +196,10 @@ impl Element for Resizable {
_visible_bounds: pathfinder_geometry::rect::RectF, _visible_bounds: pathfinder_geometry::rect::RectF,
_layout: &Self::LayoutState, _layout: &Self::LayoutState,
_paint: &Self::PaintState, _paint: &Self::PaintState,
cx: &crate::MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<pathfinder_geometry::rect::RectF> { ) -> 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( fn debug(
@ -206,10 +207,11 @@ impl Element for Resizable {
_bounds: pathfinder_geometry::rect::RectF, _bounds: pathfinder_geometry::rect::RectF,
_layout: &Self::LayoutState, _layout: &Self::LayoutState,
_paint: &Self::PaintState, _paint: &Self::PaintState,
cx: &crate::DebugContext, view: &V,
cx: &ViewContext<V>,
) -> serde_json::Value { ) -> serde_json::Value {
json!({ json!({
"child": self.child.debug(cx), "child": self.child.debug(view, cx),
}) })
} }
} }

View file

@ -3,41 +3,48 @@ use std::ops::Range;
use crate::{ use crate::{
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json::{self, json, ToJson}, json::{self, json, ToJson},
presenter::MeasurementContext, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
}; };
/// Element which renders it's children in a stack on top of each other. /// Element which renders it's children in a stack on top of each other.
/// The first child determines the size of the others. /// The first child determines the size of the others.
#[derive(Default)] pub struct Stack<V: View> {
pub struct Stack { children: Vec<Element<V>>,
children: Vec<ElementBox>,
} }
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 { pub fn new() -> Self {
Self::default() Self::default()
} }
} }
impl Element for Stack { impl<V: View> Drawable<V> for Stack<V> {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
mut constraint: SizeConstraint, mut constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.min; let mut size = constraint.min;
let mut children = self.children.iter_mut(); let mut children = self.children.iter_mut();
if let Some(bottom_child) = children.next() { if let Some(bottom_child) = children.next() {
size = bottom_child.layout(constraint, cx); size = bottom_child.layout(constraint, view, cx);
constraint = SizeConstraint::strict(size); constraint = SizeConstraint::strict(size);
} }
for child in children { for child in children {
child.layout(constraint, cx); child.layout(constraint, view, cx);
} }
(size, ()) (size, ())
@ -45,14 +52,16 @@ impl Element for Stack {
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
for child in &mut self.children { for child in &mut self.children {
cx.paint_layer(None, |cx| { scene.paint_layer(None, |scene| {
child.paint(bounds.origin(), visible_bounds, cx); child.paint(scene, bounds.origin(), visible_bounds, view, cx);
}); });
} }
} }
@ -64,12 +73,13 @@ impl Element for Stack {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
self.children self.children
.iter() .iter()
.rev() .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( fn debug(
@ -77,18 +87,19 @@ impl Element for Stack {
bounds: RectF, bounds: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &DebugContext, view: &V,
cx: &ViewContext<V>,
) -> json::Value { ) -> json::Value {
json!({ json!({
"type": "Stack", "type": "Stack",
"bounds": bounds.to_json(), "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 { impl<V: View> Extend<Element<V>> for Stack<V> {
fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) { fn extend<T: IntoIterator<Item = Element<V>>>(&mut self, children: T) {
self.children.extend(children) self.children.extend(children)
} }
} }

View file

@ -8,8 +8,7 @@ use crate::{
rect::RectF, rect::RectF,
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
presenter::MeasurementContext, scene, Drawable, SceneBuilder, SizeConstraint, View, ViewContext,
scene, DebugContext, Element, LayoutContext, PaintContext, SizeConstraint,
}; };
pub struct Svg { 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 LayoutState = Option<usvg::Tree>;
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, _: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
match cx.asset_cache.svg(&self.path) { match cx.asset_cache.svg(&self.path) {
Ok(tree) => { Ok(tree) => {
@ -58,13 +58,15 @@ impl Element for Svg {
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
_visible_bounds: RectF, _visible_bounds: RectF,
svg: &mut Self::LayoutState, svg: &mut Self::LayoutState,
cx: &mut PaintContext, _: &mut V,
_: &mut ViewContext<V>,
) { ) {
if let Some(svg) = svg.clone() { if let Some(svg) = svg.clone() {
cx.scene.push_icon(scene::Icon { scene.push_icon(scene::Icon {
bounds, bounds,
svg, svg,
path: self.path.clone(), path: self.path.clone(),
@ -80,7 +82,8 @@ impl Element for Svg {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &MeasurementContext, _: &V,
_: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
None None
} }
@ -90,7 +93,8 @@ impl Element for Svg {
bounds: RectF, bounds: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &DebugContext, _: &V,
_: &ViewContext<V>,
) -> serde_json::Value { ) -> serde_json::Value {
json!({ json!({
"type": "Svg", "type": "Svg",

View file

@ -6,9 +6,8 @@ use crate::{
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
json::{ToJson, Value}, json::{ToJson, Value},
presenter::MeasurementContext,
text_layout::{Line, RunStyle, ShapedBoundary}, text_layout::{Line, RunStyle, ShapedBoundary},
DebugContext, Element, FontCache, LayoutContext, PaintContext, SizeConstraint, TextLayoutCache, Drawable, FontCache, SceneBuilder, SizeConstraint, TextLayoutCache, View, ViewContext,
}; };
use log::warn; use log::warn;
use serde_json::json; 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 LayoutState = LayoutState;
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, _: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
// Convert the string and highlight ranges into an iterator of highlighted chunks. // 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( let shaped_lines = layout_highlighted_chunks(
chunks, chunks,
&self.style, &self.style,
cx.text_layout_cache, cx.text_layout_cache(),
cx.font_cache, &cx.font_cache,
usize::MAX, usize::MAX,
self.text.matches('\n').count() + 1, self.text.matches('\n').count() + 1,
); );
@ -143,10 +143,12 @@ impl Element for Text {
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
layout: &mut Self::LayoutState, layout: &mut Self::LayoutState,
cx: &mut PaintContext, _: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
let mut origin = bounds.origin(); let mut origin = bounds.origin();
let empty = Vec::new(); let empty = Vec::new();
@ -163,6 +165,7 @@ impl Element for Text {
if boundaries.intersects(visible_bounds) { if boundaries.intersects(visible_bounds) {
if self.soft_wrap { if self.soft_wrap {
line.paint_wrapped( line.paint_wrapped(
scene,
origin, origin,
visible_bounds, visible_bounds,
layout.line_height, layout.line_height,
@ -170,7 +173,7 @@ impl Element for Text {
cx, cx,
); );
} else { } 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()); origin.set_y(boundaries.max_y());
@ -184,7 +187,8 @@ impl Element for Text {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &MeasurementContext, _: &V,
_: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
None None
} }
@ -194,7 +198,8 @@ impl Element for Text {
bounds: RectF, bounds: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &DebugContext, _: &V,
_: &ViewContext<V>,
) -> Value { ) -> Value {
json!({ json!({
"type": "Text", "type": "Text",
@ -208,9 +213,9 @@ impl Element for Text {
/// Perform text layout on a series of highlighted chunks of text. /// Perform text layout on a series of highlighted chunks of text.
pub fn layout_highlighted_chunks<'a>( pub fn layout_highlighted_chunks<'a>(
chunks: impl Iterator<Item = (&'a str, Option<HighlightStyle>)>, chunks: impl Iterator<Item = (&'a str, Option<HighlightStyle>)>,
text_style: &'a TextStyle, text_style: &TextStyle,
text_layout_cache: &'a TextLayoutCache, text_layout_cache: &TextLayoutCache,
font_cache: &'a Arc<FontCache>, font_cache: &Arc<FontCache>,
max_line_len: usize, max_line_len: usize,
max_line_count: usize, max_line_count: usize,
) -> Vec<Line> { ) -> Vec<Line> {
@ -271,20 +276,23 @@ pub fn layout_highlighted_chunks<'a>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; 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)] #[crate::test(self)]
fn test_soft_wrapping_with_carriage_returns(cx: &mut AppContext) { fn test_soft_wrapping_with_carriage_returns(cx: &mut AppContext) {
let (window_id, _) = cx.add_window(Default::default(), |_| TestView); cx.add_window(Default::default(), |cx| {
let mut presenter = cx.build_presenter(window_id, Default::default(), Default::default()); let mut view = TestView;
fonts::with_font_cache(cx.font_cache().clone(), || { fonts::with_font_cache(cx.font_cache().clone(), || {
let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true); let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
let (_, state) = text.layout( let (_, state) = text.layout(
SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)), SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
&mut presenter.build_layout_context(Default::default(), false, cx), &mut view,
); cx,
assert_eq!(state.shaped_lines.len(), 2); );
assert_eq!(state.wrap_boundaries.len(), 2); assert_eq!(state.shaped_lines.len(), 2);
assert_eq!(state.wrap_boundaries.len(), 2);
});
view
}); });
} }
@ -299,7 +307,7 @@ mod tests {
"TestView" "TestView"
} }
fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
Empty::new().boxed() Empty::new().boxed()
} }
} }

View file

@ -1,14 +1,12 @@
use super::{ use super::{
ContainerStyle, Element, ElementBox, Flex, KeystrokeLabel, MouseEventHandler, Overlay, ContainerStyle, Drawable, Element, Flex, KeystrokeLabel, MouseEventHandler, Overlay,
OverlayFitMode, ParentElement, Text, OverlayFitMode, ParentElement, Text,
}; };
use crate::{ use crate::{
fonts::TextStyle, fonts::TextStyle,
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json::json, json::json,
presenter::MeasurementContext, Action, Axis, ElementStateHandle, SceneBuilder, SizeConstraint, Task, View, ViewContext,
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint,
Task, View,
}; };
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
@ -17,12 +15,13 @@ use std::{
rc::Rc, rc::Rc,
time::Duration, time::Duration,
}; };
use util::ResultExt;
const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(500); const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(500);
pub struct Tooltip { pub struct Tooltip<V: View> {
child: ElementBox, child: Element<V>,
tooltip: Option<ElementBox>, tooltip: Option<Element<V>>,
_state: ElementStateHandle<Rc<TooltipState>>, _state: ElementStateHandle<Rc<TooltipState>>,
} }
@ -50,24 +49,23 @@ pub struct KeystrokeStyle {
text: TextStyle, text: TextStyle,
} }
impl Tooltip { impl<V: View> Tooltip<V> {
pub fn new<Tag: 'static, T: View>( pub fn new<Tag: 'static, T: View>(
id: usize, id: usize,
text: String, text: String,
action: Option<Box<dyn Action>>, action: Option<Box<dyn Action>>,
style: TooltipStyle, style: TooltipStyle,
child: ElementBox, child: Element<V>,
cx: &mut RenderContext<T>, cx: &mut ViewContext<V>,
) -> Self { ) -> Self {
struct ElementState<Tag>(Tag); struct ElementState<Tag>(Tag);
struct MouseEventHandlerState<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_handle = cx.default_element_state::<ElementState<Tag>, Rc<TooltipState>>(id);
let state = state_handle.read(cx).clone(); let state = state_handle.read(cx).clone();
let tooltip = if state.visible.get() { let tooltip = if state.visible.get() {
let mut collapsed_tooltip = Self::render_tooltip( let mut collapsed_tooltip = Self::render_tooltip(
cx.window_id,
focused_view_id, focused_view_id,
text.clone(), text.clone(),
style.clone(), style.clone(),
@ -77,12 +75,12 @@ impl Tooltip {
.boxed(); .boxed();
Some( Some(
Overlay::new( 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() .constrained()
.dynamically(move |constraint, cx| { .dynamically(move |constraint, view, cx| {
SizeConstraint::strict_along( SizeConstraint::strict_along(
Axis::Vertical, Axis::Vertical,
collapsed_tooltip.layout(constraint, cx).y(), collapsed_tooltip.layout(constraint, view, cx).y(),
) )
}) })
.boxed(), .boxed(),
@ -94,32 +92,31 @@ impl Tooltip {
} else { } else {
None None
}; };
let child = MouseEventHandler::<MouseEventHandlerState<Tag>>::new(id, cx, |_, _| child) let child = MouseEventHandler::<MouseEventHandlerState<Tag>, _>::new(id, cx, |_, _| child)
.on_hover(move |e, cx| { .on_hover(move |e, _, cx| {
let position = e.position; let position = e.position;
let window_id = cx.window_id(); if e.started {
if let Some(view_id) = cx.view_id() { if !state.visible.get() {
if e.started { state.position.set(position);
if !state.visible.get() {
state.position.set(position);
let mut debounce = state.debounce.borrow_mut(); let mut debounce = state.debounce.borrow_mut();
if debounce.is_none() { if debounce.is_none() {
*debounce = Some(cx.spawn({ *debounce = Some(cx.spawn_weak({
let state = state.clone(); let state = state.clone();
|mut cx| async move { |view, mut cx| async move {
cx.background().timer(DEBOUNCE_TIMEOUT).await; cx.background().timer(DEBOUNCE_TIMEOUT).await;
state.visible.set(true); state.visible.set(true);
cx.update(|cx| cx.notify_view(window_id, view_id)); 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(); .boxed();
@ -131,13 +128,12 @@ impl Tooltip {
} }
pub fn render_tooltip( pub fn render_tooltip(
window_id: usize,
focused_view_id: Option<usize>, focused_view_id: Option<usize>,
text: String, text: String,
style: TooltipStyle, style: TooltipStyle,
action: Option<Box<dyn Action>>, action: Option<Box<dyn Action>>,
measure: bool, measure: bool,
) -> impl Element { ) -> impl Drawable<V> {
Flex::row() Flex::row()
.with_child({ .with_child({
let text = if let Some(max_text_width) = style.max_text_width { let text = if let Some(max_text_width) = style.max_text_width {
@ -156,7 +152,6 @@ impl Tooltip {
}) })
.with_children(action.and_then(|action| { .with_children(action.and_then(|action| {
let keystroke_label = KeystrokeLabel::new( let keystroke_label = KeystrokeLabel::new(
window_id,
focused_view_id?, focused_view_id?,
action, action,
style.keystroke.container, style.keystroke.container,
@ -173,32 +168,40 @@ impl Tooltip {
} }
} }
impl Element for Tooltip { impl<V: View> Drawable<V> for Tooltip<V> {
type LayoutState = (); type LayoutState = ();
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (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() { 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, ()) (size, ())
} }
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
_: &mut Self::LayoutState, _: &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() { 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, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
self.child.rect_for_text_range(range, cx) self.child.rect_for_text_range(range, view, cx)
} }
fn debug( fn debug(
@ -219,11 +223,12 @@ impl Element for Tooltip {
_: RectF, _: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &crate::DebugContext, view: &V,
cx: &ViewContext<V>,
) -> serde_json::Value { ) -> serde_json::Value {
json!({ json!({
"child": self.child.debug(cx), "child": self.child.debug(view, cx),
"tooltip": self.tooltip.as_ref().map(|t| t.debug(cx)), "tooltip": self.tooltip.as_ref().map(|t| t.debug(view, cx)),
}) })
} }
} }

View file

@ -1,4 +1,4 @@
use super::{Element, EventContext, LayoutContext, PaintContext, SizeConstraint}; use super::{Drawable, SizeConstraint};
use crate::{ use crate::{
geometry::{ geometry::{
rect::RectF, rect::RectF,
@ -6,9 +6,7 @@ use crate::{
}, },
json::{self, json}, json::{self, json},
platform::ScrollWheelEvent, platform::ScrollWheelEvent,
presenter::MeasurementContext, Element, MouseRegion, SceneBuilder, View, ViewContext,
scene::MouseScrollWheel,
ElementBox, MouseRegion, RenderContext, View,
}; };
use json::ToJson; use json::ToJson;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@ -38,45 +36,38 @@ struct StateInner {
scroll_to: Option<ScrollTarget>, scroll_to: Option<ScrollTarget>,
} }
pub struct LayoutState { pub struct LayoutState<V: View> {
scroll_max: f32, scroll_max: f32,
item_height: f32, item_height: f32,
items: Vec<ElementBox>, items: Vec<Element<V>>,
} }
pub struct UniformList { pub struct UniformList<V: View> {
state: UniformListState, state: UniformListState,
item_count: usize, item_count: usize,
#[allow(clippy::type_complexity)] #[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_top: f32,
padding_bottom: f32, padding_bottom: f32,
get_width_from_item: Option<usize>, get_width_from_item: Option<usize>,
view_id: usize, view_id: usize,
} }
impl UniformList { impl<V: View> UniformList<V> {
pub fn new<F, V>( pub fn new<F>(
state: UniformListState, state: UniformListState,
item_count: usize, item_count: usize,
cx: &mut RenderContext<V>, cx: &mut ViewContext<V>,
append_items: F, append_items: F,
) -> Self ) -> Self
where where
V: View, 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 { Self {
state, state,
item_count, item_count,
append_items: Box::new(move |range, items, cx| { append_items: Box::new(append_items),
if let Some(handle) = handle.upgrade(cx) {
cx.render(&handle, |view, cx| {
append_items(view, range, items, cx);
});
}
}),
padding_top: 0., padding_top: 0.,
padding_bottom: 0., padding_bottom: 0.,
get_width_from_item: None, get_width_from_item: None,
@ -105,7 +96,7 @@ impl UniformList {
mut delta: Vector2F, mut delta: Vector2F,
precise: bool, precise: bool,
scroll_max: f32, scroll_max: f32,
cx: &mut EventContext, cx: &mut ViewContext<V>,
) -> bool { ) -> bool {
if !precise { if !precise {
delta *= 20.; delta *= 20.;
@ -160,14 +151,15 @@ impl UniformList {
} }
} }
impl Element for UniformList { impl<V: View> Drawable<V> for UniformList<V> {
type LayoutState = LayoutState; type LayoutState = LayoutState<V>;
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
if constraint.max.y().is_infinite() { if constraint.max.y().is_infinite() {
unimplemented!( unimplemented!(
@ -194,18 +186,18 @@ impl Element for UniformList {
let sample_item_ix; let sample_item_ix;
let sample_item; let sample_item;
if let Some(sample_ix) = self.get_width_from_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; sample_item_ix = sample_ix;
if let Some(mut item) = items.pop() { 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()); size.set_x(item_size.x());
sample_item = item; sample_item = item;
} else { } else {
return no_items; return no_items;
} }
} else { } else {
(self.append_items)(0..1, &mut items, cx); (self.append_items)(view, 0..1, &mut items, cx);
sample_item_ix = 0; sample_item_ix = 0;
if let Some(mut item) = items.pop() { if let Some(mut item) = items.pop() {
item_size = item.layout( item_size = item.layout(
@ -213,6 +205,7 @@ impl Element for UniformList {
vec2f(constraint.max.x(), 0.0), vec2f(constraint.max.x(), 0.0),
vec2f(constraint.max.x(), f32::INFINITY), vec2f(constraint.max.x(), f32::INFINITY),
), ),
view,
cx, cx,
); );
item_size.set_x(size.x()); item_size.set_x(size.x());
@ -249,20 +242,20 @@ impl Element for UniformList {
if (start..end).contains(&sample_item_ix) { if (start..end).contains(&sample_item_ix) {
if sample_item_ix > start { 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); items.push(sample_item);
if sample_item_ix < end { 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 { } else {
(self.append_items)(start..end, &mut items, cx); (self.append_items)(view, start..end, &mut items, cx);
} }
for item in &mut items { 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() { if item_size.x() > size.x() {
size.set_x(item_size.x()); size.set_x(item_size.x());
} }
@ -280,27 +273,25 @@ impl Element for UniformList {
fn paint( fn paint(
&mut self, &mut self,
scene: &mut SceneBuilder,
bounds: RectF, bounds: RectF,
visible_bounds: RectF, visible_bounds: RectF,
layout: &mut Self::LayoutState, layout: &mut Self::LayoutState,
cx: &mut PaintContext, view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState { ) -> Self::PaintState {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default(); 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({ MouseRegion::new::<Self>(self.view_id, 0, visible_bounds).on_scroll({
let scroll_max = layout.scroll_max; let scroll_max = layout.scroll_max;
let state = self.state.clone(); let state = self.state.clone();
move |MouseScrollWheel { move |event, _, cx| {
platform_event: let ScrollWheelEvent {
ScrollWheelEvent { position, delta, ..
position, delta, .. } = event.platform_event;
},
..
},
cx| {
if !Self::scroll( if !Self::scroll(
state.clone(), state.clone(),
position, position,
@ -322,11 +313,11 @@ impl Element for UniformList {
); );
for item in &mut layout.items { 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); item_origin += vec2f(0.0, layout.item_height);
} }
cx.scene.pop_layer(); scene.pop_layer();
} }
fn rect_for_text_range( fn rect_for_text_range(
@ -336,12 +327,13 @@ impl Element for UniformList {
_: RectF, _: RectF,
layout: &Self::LayoutState, layout: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &MeasurementContext, view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
layout layout
.items .items
.iter() .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( fn debug(
@ -349,14 +341,15 @@ impl Element for UniformList {
bounds: RectF, bounds: RectF,
layout: &Self::LayoutState, layout: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &crate::DebugContext, view: &V,
cx: &ViewContext<V>,
) -> json::Value { ) -> json::Value {
json!({ json!({
"type": "UniformList", "type": "UniformList",
"bounds": bounds.to_json(), "bounds": bounds.to_json(),
"scroll_max": layout.scroll_max, "scroll_max": layout.scroll_max,
"item_height": layout.item_height, "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>>()
}) })
} }

View file

@ -14,13 +14,12 @@ mod clipboard;
pub use clipboard::ClipboardItem; pub use clipboard::ClipboardItem;
pub mod fonts; pub mod fonts;
pub mod geometry; pub mod geometry;
mod presenter;
pub mod scene; pub mod scene;
pub use scene::{Border, CursorRegion, MouseRegion, MouseRegionId, Quad, Scene, SceneBuilder}; pub use scene::{Border, CursorRegion, MouseRegion, MouseRegionId, Quad, Scene, SceneBuilder};
pub mod text_layout; pub mod text_layout;
pub use text_layout::TextLayoutCache; pub use text_layout::TextLayoutCache;
mod util; mod util;
pub use elements::{Element, ElementBox, ElementRc}; pub use elements::{Drawable, Element};
pub mod executor; pub mod executor;
pub use executor::Task; pub use executor::Task;
pub mod color; pub mod color;
@ -28,10 +27,7 @@ pub mod json;
pub mod keymap_matcher; pub mod keymap_matcher;
pub mod platform; pub mod platform;
pub use gpui_macros::test; pub use gpui_macros::test;
pub use presenter::{ pub use window::{Axis, SizeConstraint, Vector2FExt, WindowContext};
Axis, DebugContext, EventContext, LayoutContext, MeasurementContext, PaintContext,
SizeConstraint, Vector2FExt,
};
pub use anyhow; pub use anyhow;
pub use serde_json; pub use serde_json;

View file

@ -63,7 +63,7 @@ pub trait Platform: Send + Sync {
) -> Box<dyn Window>; ) -> Box<dyn Window>;
fn main_window_id(&self) -> Option<usize>; 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 write_to_clipboard(&self, item: ClipboardItem);
fn read_from_clipboard(&self) -> Option<ClipboardItem>; fn read_from_clipboard(&self) -> Option<ClipboardItem>;

View file

@ -601,7 +601,7 @@ impl platform::Platform for MacPlatform {
Window::main_window_id() 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())) Box::new(StatusItem::add(self.fonts()))
} }

View file

@ -102,6 +102,7 @@ pub struct Platform {
fonts: Arc<dyn super::FontSystem>, fonts: Arc<dyn super::FontSystem>,
current_clipboard_item: Mutex<Option<ClipboardItem>>, current_clipboard_item: Mutex<Option<ClipboardItem>>,
cursor: Mutex<CursorStyle>, cursor: Mutex<CursorStyle>,
active_window_id: Arc<Mutex<Option<usize>>>,
} }
impl Platform { impl Platform {
@ -111,6 +112,7 @@ impl Platform {
fonts: Arc::new(super::current::FontSystem::new()), fonts: Arc::new(super::current::FontSystem::new()),
current_clipboard_item: Default::default(), current_clipboard_item: Default::default(),
cursor: Mutex::new(CursorStyle::Arrow), cursor: Mutex::new(CursorStyle::Arrow),
active_window_id: Default::default(),
} }
} }
} }
@ -144,22 +146,31 @@ impl super::Platform for Platform {
fn open_window( fn open_window(
&self, &self,
_: usize, id: usize,
options: super::WindowOptions, options: super::WindowOptions,
_executor: Rc<super::executor::Foreground>, _executor: Rc<super::executor::Foreground>,
) -> Box<dyn super::Window> { ) -> Box<dyn super::Window> {
Box::new(Window::new(match options.bounds { *self.active_window_id.lock() = Some(id);
WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.), Box::new(Window::new(
WindowBounds::Fixed(rect) => rect.size(), 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> { fn main_window_id(&self) -> Option<usize> {
None self.active_window_id.lock().clone()
} }
fn add_status_item(&self) -> Box<dyn crate::platform::Window> { fn add_status_item(&self, id: usize) -> Box<dyn crate::platform::Window> {
Box::new(Window::new(vec2f(24., 24.))) Box::new(Window::new(
id,
vec2f(24., 24.),
self.active_window_id.clone(),
))
} }
fn write_to_clipboard(&self, item: ClipboardItem) { fn write_to_clipboard(&self, item: ClipboardItem) {
@ -245,6 +256,7 @@ impl super::Screen for Screen {
} }
pub struct Window { pub struct Window {
id: usize,
pub(crate) size: Vector2F, pub(crate) size: Vector2F,
scale_factor: f32, scale_factor: f32,
current_scene: Option<crate::Scene>, current_scene: Option<crate::Scene>,
@ -258,11 +270,13 @@ pub struct Window {
pub(crate) title: Option<String>, pub(crate) title: Option<String>,
pub(crate) edited: bool, pub(crate) edited: bool,
pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>, pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
active_window_id: Arc<Mutex<Option<usize>>>,
} }
impl Window { impl Window {
fn new(size: Vector2F) -> Self { pub fn new(id: usize, size: Vector2F, active_window_id: Arc<Mutex<Option<usize>>>) -> Self {
Self { Self {
id,
size, size,
event_handlers: Default::default(), event_handlers: Default::default(),
resize_handlers: Default::default(), resize_handlers: Default::default(),
@ -276,6 +290,7 @@ impl Window {
title: None, title: None,
edited: false, edited: false,
pending_prompts: Default::default(), pending_prompts: Default::default(),
active_window_id,
} }
} }
@ -326,7 +341,9 @@ impl super::Window for Window {
done_rx done_rx
} }
fn activate(&self) {} fn activate(&self) {
*self.active_window_id.lock() = Some(self.id);
}
fn set_title(&mut self, title: &str) { fn set_title(&mut self, title: &str) {
self.title = Some(title.to_string()) self.title = Some(title.to_string())

File diff suppressed because it is too large Load diff

View file

@ -236,6 +236,19 @@ impl SceneBuilder {
self.scale_factor 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>) { 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); let z_index = z_index.unwrap_or_else(|| self.active_stacking_context().z_index + 1);
self.active_stacking_context_stack self.active_stacking_context_stack
@ -249,6 +262,15 @@ impl SceneBuilder {
assert!(!self.active_stacking_context_stack.is_empty()); 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>) { pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {
self.active_stacking_context().push_layer(clip_bounds); self.active_stacking_context().push_layer(clip_bounds);
} }

View file

@ -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 collections::HashMap;
use pathfinder_geometry::rect::RectF; use pathfinder_geometry::rect::RectF;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{
use crate::{platform::MouseButton, EventContext}; any::{Any, TypeId},
fmt::Debug,
mem::Discriminant,
rc::Rc,
};
use super::{ use super::{
mouse_event::{ mouse_event::{
@ -60,82 +62,92 @@ impl MouseRegion {
} }
} }
pub fn on_down( pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
mut self, where
button: MouseButton, V: View,
handler: impl Fn(MouseDown, &mut EventContext) + 'static, F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
) -> Self { {
self.handlers = self.handlers.on_down(button, handler); self.handlers = self.handlers.on_down(button, handler);
self self
} }
pub fn on_up( pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
mut self, where
button: MouseButton, V: View,
handler: impl Fn(MouseUp, &mut EventContext) + 'static, F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
) -> Self { {
self.handlers = self.handlers.on_up(button, handler); self.handlers = self.handlers.on_up(button, handler);
self self
} }
pub fn on_click( pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
mut self, where
button: MouseButton, V: View,
handler: impl Fn(MouseClick, &mut EventContext) + 'static, F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
) -> Self { {
self.handlers = self.handlers.on_click(button, handler); self.handlers = self.handlers.on_click(button, handler);
self self
} }
pub fn on_down_out( pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
mut self, where
button: MouseButton, V: View,
handler: impl Fn(MouseDownOut, &mut EventContext) + 'static, F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
) -> Self { {
self.handlers = self.handlers.on_down_out(button, handler); self.handlers = self.handlers.on_down_out(button, handler);
self self
} }
pub fn on_up_out( pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
mut self, where
button: MouseButton, V: View,
handler: impl Fn(MouseUpOut, &mut EventContext) + 'static, F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
) -> Self { {
self.handlers = self.handlers.on_up_out(button, handler); self.handlers = self.handlers.on_up_out(button, handler);
self self
} }
pub fn on_drag( pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
mut self, where
button: MouseButton, V: View,
handler: impl Fn(MouseDrag, &mut EventContext) + 'static, F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
) -> Self { {
self.handlers = self.handlers.on_drag(button, handler); self.handlers = self.handlers.on_drag(button, handler);
self 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.handlers = self.handlers.on_hover(handler);
self 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.handlers = self.handlers.on_move(handler);
self self
} }
pub fn on_move_out( pub fn on_move_out<V, F>(mut self, handler: F) -> Self
mut self, where
handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static, V: View,
) -> Self { F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
{
self.handlers = self.handlers.on_move_out(handler); self.handlers = self.handlers.on_move_out(handler);
self self
} }
pub fn on_scroll( pub fn on_scroll<V, F>(mut self, handler: F) -> Self
mut self, where
handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static, V: View,
) -> Self { F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
{
self.handlers = self.handlers.on_scroll(handler); self.handlers = self.handlers.on_scroll(handler);
self 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)] #[derive(Clone, PartialEq, Eq, Hash)]
pub struct HandlerKey { pub struct HandlerKey {
@ -211,41 +223,41 @@ impl HandlerSet {
set.insert( set.insert(
HandlerKey::new(MouseEvent::move_disc(), None), HandlerKey::new(MouseEvent::move_disc(), None),
SmallVec::from_buf([Rc::new(|_, _| {})]), SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
); );
set.insert( set.insert(
HandlerKey::new(MouseEvent::hover_disc(), None), HandlerKey::new(MouseEvent::hover_disc(), None),
SmallVec::from_buf([Rc::new(|_, _| {})]), SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
); );
for button in MouseButton::all() { for button in MouseButton::all() {
set.insert( set.insert(
HandlerKey::new(MouseEvent::drag_disc(), Some(button)), HandlerKey::new(MouseEvent::drag_disc(), Some(button)),
SmallVec::from_buf([Rc::new(|_, _| {})]), SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
); );
set.insert( set.insert(
HandlerKey::new(MouseEvent::down_disc(), Some(button)), HandlerKey::new(MouseEvent::down_disc(), Some(button)),
SmallVec::from_buf([Rc::new(|_, _| {})]), SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
); );
set.insert( set.insert(
HandlerKey::new(MouseEvent::up_disc(), Some(button)), HandlerKey::new(MouseEvent::up_disc(), Some(button)),
SmallVec::from_buf([Rc::new(|_, _| {})]), SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
); );
set.insert( set.insert(
HandlerKey::new(MouseEvent::click_disc(), Some(button)), HandlerKey::new(MouseEvent::click_disc(), Some(button)),
SmallVec::from_buf([Rc::new(|_, _| {})]), SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
); );
set.insert( set.insert(
HandlerKey::new(MouseEvent::down_out_disc(), Some(button)), HandlerKey::new(MouseEvent::down_out_disc(), Some(button)),
SmallVec::from_buf([Rc::new(|_, _| {})]), SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
); );
set.insert( set.insert(
HandlerKey::new(MouseEvent::up_out_disc(), Some(button)), HandlerKey::new(MouseEvent::up_out_disc(), Some(button)),
SmallVec::from_buf([Rc::new(|_, _| {})]), SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
); );
} }
set.insert( set.insert(
HandlerKey::new(MouseEvent::scroll_wheel_disc(), None), HandlerKey::new(MouseEvent::scroll_wheel_disc(), None),
SmallVec::from_buf([Rc::new(|_, _| {})]), SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
); );
HandlerSet { set } 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, 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 { 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 { } else {
panic!( panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}", "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}",
@ -297,14 +317,19 @@ impl HandlerSet {
self self
} }
pub fn on_move_out( pub fn on_move_out<V, F>(mut self, handler: F) -> Self
mut self, where
handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static, V: View,
) -> Self { F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
{
self.insert(MouseEvent::move_out_disc(), None, 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 { 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 { } else {
panic!( panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::MoveOut, found {:?}", "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::MoveOut, found {:?}",
@ -314,15 +339,19 @@ impl HandlerSet {
self self
} }
pub fn on_down( pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
mut self, where
button: MouseButton, V: View,
handler: impl Fn(MouseDown, &mut EventContext) + 'static, F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
) -> Self { {
self.insert(MouseEvent::down_disc(), Some(button), 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 { 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 { } else {
panic!( panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}", "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}",
@ -332,15 +361,19 @@ impl HandlerSet {
self self
} }
pub fn on_up( pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
mut self, where
button: MouseButton, V: View,
handler: impl Fn(MouseUp, &mut EventContext) + 'static, F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
) -> Self { {
self.insert(MouseEvent::up_disc(), Some(button), 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 { 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 { } else {
panic!( panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}", "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}",
@ -350,15 +383,19 @@ impl HandlerSet {
self self
} }
pub fn on_click( pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
mut self, where
button: MouseButton, V: View,
handler: impl Fn(MouseClick, &mut EventContext) + 'static, F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
) -> Self { {
self.insert(MouseEvent::click_disc(), Some(button), 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 { 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 { } else {
panic!( panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}", "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}",
@ -368,15 +405,19 @@ impl HandlerSet {
self self
} }
pub fn on_down_out( pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
mut self, where
button: MouseButton, V: View,
handler: impl Fn(MouseDownOut, &mut EventContext) + 'static, F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
) -> Self { {
self.insert(MouseEvent::down_out_disc(), Some(button), 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 { 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 { } else {
panic!( panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}", "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}",
@ -386,15 +427,19 @@ impl HandlerSet {
self self
} }
pub fn on_up_out( pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
mut self, where
button: MouseButton, V: View,
handler: impl Fn(MouseUpOut, &mut EventContext) + 'static, F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
) -> Self { {
self.insert(MouseEvent::up_out_disc(), Some(button), 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 { 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 { } else {
panic!( panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}", "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}",
@ -404,15 +449,19 @@ impl HandlerSet {
self self
} }
pub fn on_drag( pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
mut self, where
button: MouseButton, V: View,
handler: impl Fn(MouseDrag, &mut EventContext) + 'static, F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
) -> Self { {
self.insert(MouseEvent::drag_disc(), Some(button), 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 { 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 { } else {
panic!( panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}", "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}",
@ -422,11 +471,19 @@ impl HandlerSet {
self 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, 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 { 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 { } else {
panic!( panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}", "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}",
@ -436,14 +493,19 @@ impl HandlerSet {
self self
} }
pub fn on_scroll( pub fn on_scroll<V, F>(mut self, handler: F) -> Self
mut self, where
handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static, V: View,
) -> Self { F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
{
self.insert(MouseEvent::scroll_wheel_disc(), None, 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 { 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 { } else {
panic!( panic!(
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ScrollWheel, found {:?}", "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ScrollWheel, found {:?}",

View file

@ -19,8 +19,8 @@ use crate::{
platform, platform,
platform::Platform, platform::Platform,
util::CwdBacktrace, util::CwdBacktrace,
AppContext, Element, ElementBox, Entity, FontCache, Handle, RenderContext, Subscription, AppContext, Drawable, Element, Entity, FontCache, Handle, Subscription, TestAppContext, View,
TestAppContext, View, ViewContext,
}; };
#[cfg(test)] #[cfg(test)]
@ -242,7 +242,7 @@ impl View for EmptyView {
"empty view" "empty view"
} }
fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
Element::boxed(Empty::new()) Drawable::boxed(Empty::new())
} }
} }

View file

@ -7,7 +7,9 @@ use crate::{
}, },
platform, platform,
platform::FontSystem, platform::FontSystem,
scene, PaintContext, scene,
window::WindowContext,
SceneBuilder,
}; };
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
@ -271,10 +273,11 @@ impl Line {
pub fn paint( pub fn paint(
&self, &self,
scene: &mut SceneBuilder,
origin: Vector2F, origin: Vector2F,
visible_bounds: RectF, visible_bounds: RectF,
line_height: f32, line_height: f32,
cx: &mut PaintContext, cx: &mut WindowContext,
) { ) {
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.; let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
let baseline_offset = vec2f(0., padding_top + self.layout.ascent); 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 { if let Some((underline_origin, underline_style)) = finished_underline {
cx.scene.push_underline(scene::Underline { scene.push_underline(scene::Underline {
origin: underline_origin, origin: underline_origin,
width: glyph_origin.x() - underline_origin.x(), width: glyph_origin.x() - underline_origin.x(),
thickness: underline_style.thickness.into(), thickness: underline_style.thickness.into(),
@ -341,14 +344,14 @@ impl Line {
} }
if glyph.is_emoji { if glyph.is_emoji {
cx.scene.push_image_glyph(scene::ImageGlyph { scene.push_image_glyph(scene::ImageGlyph {
font_id: run.font_id, font_id: run.font_id,
font_size: self.layout.font_size, font_size: self.layout.font_size,
id: glyph.id, id: glyph.id,
origin: glyph_origin, origin: glyph_origin,
}); });
} else { } else {
cx.scene.push_glyph(scene::Glyph { scene.push_glyph(scene::Glyph {
font_id: run.font_id, font_id: run.font_id,
font_size: self.layout.font_size, font_size: self.layout.font_size,
id: glyph.id, id: glyph.id,
@ -361,7 +364,7 @@ impl Line {
if let Some((underline_start, underline_style)) = underline.take() { if let Some((underline_start, underline_style)) = underline.take() {
let line_end_x = origin.x() + self.layout.width; let line_end_x = origin.x() + self.layout.width;
cx.scene.push_underline(scene::Underline { scene.push_underline(scene::Underline {
origin: underline_start, origin: underline_start,
width: line_end_x - underline_start.x(), width: line_end_x - underline_start.x(),
color: underline_style.color.unwrap(), color: underline_style.color.unwrap(),
@ -373,11 +376,12 @@ impl Line {
pub fn paint_wrapped( pub fn paint_wrapped(
&self, &self,
scene: &mut SceneBuilder,
origin: Vector2F, origin: Vector2F,
visible_bounds: RectF, visible_bounds: RectF,
line_height: f32, line_height: f32,
boundaries: impl IntoIterator<Item = ShapedBoundary>, boundaries: impl IntoIterator<Item = ShapedBoundary>,
cx: &mut PaintContext, cx: &mut WindowContext,
) { ) {
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.; let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
let baseline_origin = vec2f(0., padding_top + self.layout.ascent); let baseline_origin = vec2f(0., padding_top + self.layout.ascent);
@ -416,14 +420,14 @@ impl Line {
); );
if glyph_bounds.intersects(visible_bounds) { if glyph_bounds.intersects(visible_bounds) {
if glyph.is_emoji { if glyph.is_emoji {
cx.scene.push_image_glyph(scene::ImageGlyph { scene.push_image_glyph(scene::ImageGlyph {
font_id: run.font_id, font_id: run.font_id,
font_size: self.layout.font_size, font_size: self.layout.font_size,
id: glyph.id, id: glyph.id,
origin: glyph_bounds.origin() + baseline_origin, origin: glyph_bounds.origin() + baseline_origin,
}); });
} else { } else {
cx.scene.push_glyph(scene::Glyph { scene.push_glyph(scene::Glyph {
font_id: run.font_id, font_id: run.font_id,
font_size: self.layout.font_size, font_size: self.layout.font_size,
id: glyph.id, id: glyph.id,

View file

@ -1,13 +1,13 @@
use serde::Deserialize; use serde::Deserialize;
use crate::{ use crate::{
actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, RenderContext, actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, EventContext,
View, ViewContext, WeakViewHandle, View, ViewContext, WeakViewHandle,
}; };
pub struct Select { pub struct Select {
handle: WeakViewHandle<Self>, 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, selected_item_ix: usize,
item_count: usize, item_count: usize,
is_open: bool, is_open: bool,
@ -41,7 +41,7 @@ pub fn init(cx: &mut AppContext) {
} }
impl Select { 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, item_count: usize,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
render_item: F, render_item: F,
@ -92,7 +92,7 @@ impl View for Select {
"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 { if self.item_count == 0 {
return Empty::new().boxed(); return Empty::new().boxed();
} }
@ -106,7 +106,7 @@ impl View for Select {
Default::default() Default::default()
}; };
let mut result = Flex::column().with_child( 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)( Container::new((self.render_item)(
self.selected_item_ix, self.selected_item_ix,
ItemType::Header, ItemType::Header,
@ -116,9 +116,10 @@ impl View for Select {
.with_style(style.header) .with_style(style.header)
.boxed() .boxed()
}) })
.on_click(MouseButton::Left, move |_, cx| { .on_click(
cx.dispatch_action(ToggleSelect) MouseButton::Left,
}) move |_, _, cx: &mut EventContext<Self>| cx.dispatch_action(ToggleSelect),
)
.boxed(), .boxed(),
); );
if self.is_open { if self.is_open {
@ -134,21 +135,28 @@ impl View for Select {
let selected_item_ix = this.selected_item_ix; let selected_item_ix = this.selected_item_ix;
range.end = range.end.min(this.item_count); range.end = range.end.min(this.item_count);
items.extend(range.map(|ix| { items.extend(range.map(|ix| {
MouseEventHandler::<Item>::new(ix, cx, |mouse_state, cx| { MouseEventHandler::<Item, _>::new(
(this.render_item)( ix,
ix, cx,
if ix == selected_item_ix { |mouse_state, cx| {
ItemType::Selected (this.render_item)(
} else { ix,
ItemType::Unselected if ix == selected_item_ix {
}, ItemType::Selected
mouse_state.hovered(), } else {
cx, ItemType::Unselected
) },
}) mouse_state.hovered(),
.on_click(MouseButton::Left, move |_, cx| { cx,
cx.dispatch_action(SelectItem(ix)) )
}) },
)
.on_click(
MouseButton::Left,
move |_, _, cx: &mut EventContext<Self>| {
cx.dispatch_action(SelectItem(ix))
},
)
.boxed() .boxed()
})) }))
}, },

View file

@ -174,23 +174,60 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
} }
} }
} else { } 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(); 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 FnArg::Typed(arg) = arg {
if let Type::Path(ty) = &*arg.ty { if let Type::Path(ty) = &*arg.ty {
let last_segment = ty.path.segments.last(); let last_segment = ty.path.segments.last();
if let Some("StdRng") = last_segment.map(|s| s.ident.to_string()).as_deref() { 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),)); 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! { parse_quote! {
@ -203,7 +240,11 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
#starting_seed as u64, #starting_seed as u64,
#max_retries, #max_retries,
#detect_nondeterminism, #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, #on_failure_fn_name,
stringify!(#outer_fn_name).to_string(), stringify!(#outer_fn_name).to_string(),
); );

View file

@ -7,7 +7,6 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
}; };
use util::TryFutureExt as _;
use workspace::AppState; use workspace::AppState;
actions!(journal, [NewJournalEntry]); 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)) Ok::<_, std::io::Error>((journal_dir, entry_path))
}); });
cx.spawn(|mut cx| { cx.spawn(|mut cx| async move {
async move { let (journal_dir, entry_path) = create_entry.await?;
let (journal_dir, entry_path) = create_entry.await?; let (workspace, _) = cx
let (workspace, _) = cx .update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))
.update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx)) .await?;
.await;
let opened = workspace let opened = workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
workspace.open_paths(vec![entry_path], true, cx) workspace.open_paths(vec![entry_path], true, cx)
}) })?
.await; .await;
if let Some(Some(Ok(item))) = opened.first() { if let Some(Some(Ok(item))) = opened.first() {
if let Some(editor) = item.downcast::<Editor>() { if let Some(editor) = item.downcast::<Editor>() {
editor.update(&mut cx, |editor, cx| { editor.update(&mut cx, |editor, cx| {
let len = editor.buffer().read(cx).len(cx); let len = editor.buffer().read(cx).len(cx);
editor.change_selections(Some(Autoscroll::center()), cx, |s| { editor.change_selections(Some(Autoscroll::center()), cx, |s| {
s.select_ranges([len..len]) s.select_ranges([len..len])
});
if len > 0 {
editor.insert("\n\n", cx);
}
editor.insert(&entry_heading, cx);
editor.insert("\n\n", cx);
}); });
} 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> { fn journal_dir(settings: &Settings) -> Option<PathBuf> {

View file

@ -17,5 +17,6 @@ picker = { path = "../picker" }
project = { path = "../project" } project = { path = "../project" }
theme = { path = "../theme" } theme = { path = "../theme" }
settings = { path = "../settings" } settings = { path = "../settings" }
util = { path = "../util" }
workspace = { path = "../workspace" } workspace = { path = "../workspace" }
anyhow = "1.0" anyhow = "1.0"

View file

@ -2,7 +2,7 @@ use editor::Editor;
use gpui::{ use gpui::{
elements::*, elements::*,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
Entity, RenderContext, Subscription, View, ViewContext, ViewHandle, Entity, Subscription, View, ViewContext, ViewHandle,
}; };
use settings::Settings; use settings::Settings;
use std::sync::Arc; use std::sync::Arc;
@ -50,7 +50,7 @@ impl View for ActiveBufferLanguage {
"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() { if let Some(active_language) = self.active_language.as_ref() {
let active_language_text = if let Some(active_language_text) = active_language { let active_language_text = if let Some(active_language_text) = active_language {
active_language_text.to_string() active_language_text.to_string()
@ -58,7 +58,7 @@ impl View for ActiveBufferLanguage {
"Unknown".to_string() "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 theme = &cx.global::<Settings>().theme.workspace.status_bar;
let style = theme.active_language.style_for(state, false); let style = theme.active_language.style_for(state, false);
Label::new(active_language_text, style.text.clone()) Label::new(active_language_text, style.text.clone())
@ -67,7 +67,9 @@ impl View for ActiveBufferLanguage {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .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() .boxed()
} else { } else {
Empty::new().boxed() Empty::new().boxed()

View file

@ -1,55 +1,64 @@
mod active_buffer_language; mod active_buffer_language;
pub use active_buffer_language::ActiveBufferLanguage; pub use active_buffer_language::ActiveBufferLanguage;
use anyhow::anyhow;
use editor::Editor; use editor::Editor;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{actions, elements::*, AppContext, ModelHandle, MouseState, ViewContext};
actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
RenderContext, View, ViewContext, ViewHandle,
};
use language::{Buffer, LanguageRegistry}; use language::{Buffer, LanguageRegistry};
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate, PickerEvent};
use project::Project; use project::Project;
use settings::Settings; use settings::Settings;
use std::sync::Arc; use std::sync::Arc;
use util::ResultExt;
use workspace::{AppState, Workspace}; use workspace::{AppState, Workspace};
actions!(language_selector, [Toggle]); actions!(language_selector, [Toggle]);
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) { pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
Picker::<LanguageSelector>::init(cx); Picker::<LanguageSelectorDelegate>::init(cx);
cx.add_action({ cx.add_action({
let language_registry = app_state.languages.clone(); let language_registry = app_state.languages.clone();
move |workspace, _: &Toggle, cx| { move |workspace, _: &Toggle, cx| toggle(workspace, language_registry.clone(), cx)
LanguageSelector::toggle(workspace, language_registry.clone(), cx)
}
}); });
} }
pub enum Event { fn toggle(
Dismissed, 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>, buffer: ModelHandle<Buffer>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
candidates: Vec<StringMatchCandidate>, candidates: Vec<StringMatchCandidate>,
matches: Vec<StringMatch>, matches: Vec<StringMatch>,
picker: ViewHandle<Picker<Self>>,
selected_index: usize, selected_index: usize,
} }
impl LanguageSelector { impl LanguageSelectorDelegate {
fn new( fn new(
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
let handle = cx.weak_handle();
let picker = cx.add_view(|cx| Picker::new("Select Language...", handle, cx));
let candidates = language_registry let candidates = language_registry
.language_names() .language_names()
.into_iter() .into_iter()
@ -73,104 +82,63 @@ impl LanguageSelector {
language_registry, language_registry,
candidates, candidates,
matches, matches,
picker,
selected_index: 0, 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 { impl PickerDelegate for LanguageSelectorDelegate {
type Event = Event; fn placeholder_text(&self) -> Arc<str> {
} "Select a language...".into()
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()
}
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 { fn match_count(&self) -> usize {
self.matches.len() 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) { if let Some(mat) = self.matches.get(self.selected_index) {
let language_name = &self.candidates[mat.candidate_id].string; let language_name = &self.candidates[mat.candidate_id].string;
let language = self.language_registry.language_for_name(language_name); 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?; let language = language.await?;
this.update(&mut cx, |this, cx| { let project = project
this.project.update(cx, |project, cx| { .upgrade(&cx)
project.set_language_for_buffer(&this.buffer, language, 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(()) anyhow::Ok(())
}) })
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
cx.emit(Event::Dismissed); cx.emit(PickerEvent::Dismiss);
} }
fn dismiss(&mut self, cx: &mut ViewContext<Self>) { fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
cx.emit(Event::Dismissed);
}
fn selected_index(&self) -> usize { fn selected_index(&self) -> usize {
self.selected_index 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; 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 background = cx.background().clone();
let candidates = self.candidates.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() { let matches = if query.is_empty() {
candidates candidates
.into_iter() .into_iter()
@ -194,13 +162,17 @@ impl PickerDelegate for LanguageSelector {
.await .await
}; };
this.update(&mut cx, |this, cx| { if let Some(this) = this.upgrade(&cx) {
this.matches = matches; this.update(&mut cx, |this, cx| {
this.selected_index = this let delegate = this.delegate_mut();
.selected_index delegate.matches = matches;
.min(this.matches.len().saturating_sub(1)); delegate.selected_index = delegate
cx.notify(); .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, mouse_state: &mut MouseState,
selected: bool, selected: bool,
cx: &AppContext, cx: &AppContext,
) -> ElementBox { ) -> Element<Picker<Self>> {
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
let theme = &settings.theme; let theme = &settings.theme;
let mat = &self.matches[ix]; let mat = &self.matches[ix];

View file

@ -4,25 +4,51 @@ use editor::{
}; };
use fuzzy::StringMatch; use fuzzy::StringMatch;
use gpui::{ use gpui::{
actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Entity, actions, elements::*, geometry::vector::Vector2F, AppContext, MouseState, Task, ViewContext,
MouseState, RenderContext, Task, View, ViewContext, ViewHandle, ViewHandle, WindowContext,
}; };
use language::Outline; use language::Outline;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate, PickerEvent};
use settings::Settings; use settings::Settings;
use std::cmp::{self, Reverse}; use std::{
cmp::{self, Reverse},
sync::Arc,
};
use workspace::Workspace; use workspace::Workspace;
actions!(outline, [Toggle]); actions!(outline, [Toggle]);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
cx.add_action(OutlineView::toggle); cx.add_action(toggle);
Picker::<OutlineView>::init(cx); OutlineView::init(cx);
} }
struct OutlineView { fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
picker: ViewHandle<Picker<Self>>, 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.)
})
});
}
}
}
type OutlineView = Picker<OutlineViewDelegate>;
struct OutlineViewDelegate {
active_editor: ViewHandle<Editor>, active_editor: ViewHandle<Editor>,
outline: Outline<Anchor>, outline: Outline<Anchor>,
selected_match_index: usize, selected_match_index: usize,
@ -31,45 +57,13 @@ struct OutlineView {
last_query: String, last_query: String,
} }
pub enum Event { impl OutlineViewDelegate {
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 {
fn new( fn new(
outline: Outline<Anchor>, outline: Outline<Anchor>,
editor: ViewHandle<Editor>, editor: ViewHandle<Editor>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<OutlineView>,
) -> Self { ) -> Self {
let handle = cx.weak_handle();
Self { Self {
picker: cx.add_view(|cx| {
Picker::new("Search buffer symbols...", handle, cx).with_max_size(800., 1200.)
}),
last_query: Default::default(), last_query: Default::default(),
matches: Default::default(), matches: Default::default(),
selected_match_index: 0, selected_match_index: 0,
@ -79,28 +73,7 @@ impl OutlineView {
} }
} }
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) { fn restore_active_editor(&mut self, cx: &mut WindowContext) {
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) {
self.active_editor.update(cx, |editor, cx| { self.active_editor.update(cx, |editor, cx| {
editor.highlight_rows(None); editor.highlight_rows(None);
if let Some(scroll_position) = self.prev_scroll_position { 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; self.selected_match_index = ix;
if navigate && !self.matches.is_empty() { if navigate && !self.matches.is_empty() {
let selected_match = &self.matches[self.selected_match_index]; let selected_match = &self.matches[self.selected_match_index];
@ -125,22 +98,14 @@ impl OutlineView {
active_editor.request_autoscroll(Autoscroll::center(), cx); 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 OutlineView { impl PickerDelegate for OutlineViewDelegate {
fn placeholder_text(&self) -> Arc<str> {
"Search buffer symbols...".into()
}
fn match_count(&self) -> usize { fn match_count(&self) -> usize {
self.matches.len() self.matches.len()
} }
@ -149,7 +114,7 @@ impl PickerDelegate for OutlineView {
self.selected_match_index 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); self.set_selected_index(ix, true, cx);
} }
@ -157,7 +122,7 @@ impl PickerDelegate for OutlineView {
true 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; let selected_index;
if query.is_empty() { if query.is_empty() {
self.restore_active_editor(cx); self.restore_active_editor(cx);
@ -213,7 +178,7 @@ impl PickerDelegate for OutlineView {
Task::ready(()) Task::ready(())
} }
fn confirm(&mut self, cx: &mut ViewContext<Self>) { fn confirm(&mut self, cx: &mut ViewContext<OutlineView>) {
self.prev_scroll_position.take(); self.prev_scroll_position.take();
self.active_editor.update(cx, |active_editor, cx| { self.active_editor.update(cx, |active_editor, cx| {
if let Some(rows) = active_editor.highlighted_rows() { 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); self.restore_active_editor(cx);
cx.emit(Event::Dismissed);
} }
fn render_match( fn render_match(
@ -238,7 +202,7 @@ impl PickerDelegate for OutlineView {
mouse_state: &mut MouseState, mouse_state: &mut MouseState,
selected: bool, selected: bool,
cx: &AppContext, cx: &AppContext,
) -> ElementBox { ) -> Element<Picker<Self>> {
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
let string_match = &self.matches[ix]; let string_match = &self.matches[ix];
let style = settings.theme.picker.item.style_for(mouse_state, selected); let style = settings.theme.picker.item.style_for(mouse_state, selected);

View file

@ -4,43 +4,51 @@ use gpui::{
geometry::vector::{vec2f, Vector2F}, geometry::vector::{vec2f, Vector2F},
keymap_matcher::KeymapContext, keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
AnyViewHandle, AppContext, Axis, Entity, MouseState, RenderContext, Task, View, ViewContext, AnyViewHandle, AppContext, Axis, Element, Entity, MouseState, Task, View, ViewContext,
ViewHandle, WeakViewHandle, ViewHandle,
}; };
use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev}; use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{cmp, sync::Arc}; use std::{cmp, sync::Arc};
use util::ResultExt;
use workspace::Modal;
pub enum PickerEvent {
Dismiss,
}
pub struct Picker<D: PickerDelegate> { pub struct Picker<D: PickerDelegate> {
delegate: WeakViewHandle<D>, delegate: D,
query_editor: ViewHandle<Editor>, query_editor: ViewHandle<Editor>,
list_state: UniformListState, list_state: UniformListState,
max_size: Vector2F, max_size: Vector2F,
theme: Arc<Mutex<Box<dyn Fn(&theme::Theme) -> theme::Picker>>>, theme: Arc<Mutex<Box<dyn Fn(&theme::Theme) -> theme::Picker>>>,
confirmed: bool, 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 match_count(&self) -> usize;
fn selected_index(&self) -> usize; fn selected_index(&self) -> usize;
fn set_selected_index(&mut self, ix: usize, 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<Self>) -> Task<()>; fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
fn confirm(&mut self, cx: &mut ViewContext<Self>); fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>);
fn dismiss(&mut self, cx: &mut ViewContext<Self>); fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
state: &mut MouseState, state: &mut MouseState,
selected: bool, selected: bool,
cx: &AppContext, cx: &AppContext,
) -> ElementBox; ) -> Element<Picker<Self>>;
fn center_selection_after_match_updates(&self) -> bool { fn center_selection_after_match_updates(&self) -> bool {
false false
} }
} }
impl<D: PickerDelegate> Entity for Picker<D> { impl<D: PickerDelegate> Entity for Picker<D> {
type Event = (); type Event = PickerEvent;
} }
impl<D: PickerDelegate> View for Picker<D> { impl<D: PickerDelegate> View for Picker<D> {
@ -48,15 +56,10 @@ impl<D: PickerDelegate> View for Picker<D> {
"Picker" "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 theme = (self.theme.lock())(&cx.global::<settings::Settings>().theme);
let query = self.query(cx); let query = self.query(cx);
let delegate = self.delegate.clone(); let match_count = self.delegate.match_count();
let match_count = if let Some(delegate) = delegate.upgrade(cx.app) {
delegate.read(cx).match_count()
} else {
0
};
let container_style; let container_style;
let editor_style; let editor_style;
@ -93,19 +96,16 @@ impl<D: PickerDelegate> View for Picker<D> {
match_count, match_count,
cx, cx,
move |this, mut range, items, cx| { move |this, mut range, items, cx| {
let delegate = this.delegate.upgrade(cx).unwrap(); let selected_ix = this.delegate.selected_index();
let selected_ix = delegate.read(cx).selected_index(); range.end = cmp::min(range.end, this.delegate.match_count());
range.end = cmp::min(range.end, delegate.read(cx).match_count());
items.extend(range.map(move |ix| { items.extend(range.map(move |ix| {
MouseEventHandler::<D>::new(ix, cx, |state, cx| { MouseEventHandler::<D, _>::new(ix, cx, |state, cx| {
delegate this.delegate.render_match(ix, state, ix == selected_ix, cx)
.read(cx)
.render_match(ix, state, ix == selected_ix, cx)
}) })
// Capture mouse events // Capture mouse events
.on_down(MouseButton::Left, |_, _| {}) .on_down(MouseButton::Left, |_, _, _| {})
.on_up(MouseButton::Left, |_, _| {}) .on_up(MouseButton::Left, |_, _, _| {})
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(SelectIndex(ix)) cx.dispatch_action(SelectIndex(ix))
}) })
.with_cursor_style(CursorStyle::PointingHand) .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> { impl<D: PickerDelegate> Picker<D> {
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
cx.add_action(Self::select_first); cx.add_action(Self::select_first);
@ -151,14 +157,12 @@ impl<D: PickerDelegate> Picker<D> {
cx.add_action(Self::cancel); cx.add_action(Self::cancel);
} }
pub fn new<P>(placeholder: P, delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
where
P: Into<Arc<str>>,
{
let theme = Arc::new(Mutex::new( let theme = Arc::new(Mutex::new(
Box::new(|theme: &theme::Theme| theme.picker.clone()) Box::new(|theme: &theme::Theme| theme.picker.clone())
as Box<dyn Fn(&theme::Theme) -> theme::Picker>, as Box<dyn Fn(&theme::Theme) -> theme::Picker>,
)); ));
let placeholder_text = delegate.placeholder_text();
let query_editor = cx.add_view({ let query_editor = cx.add_view({
let picker_theme = theme.clone(); let picker_theme = theme.clone();
|cx| { |cx| {
@ -168,26 +172,22 @@ impl<D: PickerDelegate> Picker<D> {
})), })),
cx, cx,
); );
editor.set_placeholder_text(placeholder, cx); editor.set_placeholder_text(placeholder_text, cx);
editor editor
} }
}); });
cx.subscribe(&query_editor, Self::on_query_editor_event) cx.subscribe(&query_editor, Self::on_query_editor_event)
.detach(); .detach();
let this = Self { let mut this = Self {
query_editor, query_editor,
list_state: Default::default(), list_state: Default::default(),
delegate, delegate,
max_size: vec2f(540., 420.), max_size: vec2f(540., 420.),
theme, theme,
confirmed: false, confirmed: false,
pending_update_matches: Task::ready(None),
}; };
cx.defer(|this, cx| { this.update_matches(String::new(), cx);
if let Some(delegate) = this.delegate.upgrade(cx) {
cx.observe(&delegate, |_, _, cx| cx.notify()).detach();
this.update_matches(String::new(), cx)
}
});
this this
} }
@ -204,6 +204,14 @@ impl<D: PickerDelegate> Picker<D> {
self 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 { pub fn query(&self, cx: &AppContext) -> String {
self.query_editor.read(cx).text(cx) self.query_editor.read(cx).text(cx)
} }
@ -222,119 +230,95 @@ impl<D: PickerDelegate> Picker<D> {
match event { match event {
editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx), editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx),
editor::Event::Blurred if !self.confirmed => { editor::Event::Blurred if !self.confirmed => {
if let Some(delegate) = self.delegate.upgrade(cx) { self.dismiss(cx);
delegate.update(cx, |delegate, cx| {
delegate.dismiss(cx);
})
}
} }
_ => {} _ => {}
} }
} }
pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) { pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
if let Some(delegate) = self.delegate.upgrade(cx) { let update = self.delegate.update_matches(query, cx);
let update = delegate.update(cx, |d, cx| d.update_matches(query, cx)); self.matches_updated(cx);
cx.spawn(|this, mut cx| async move { self.pending_update_matches = cx.spawn_weak(|this, mut cx| async move {
update.await; update.await;
this.update(&mut cx, |this, cx| { this.upgrade(&cx)?
if let Some(delegate) = this.delegate.upgrade(cx) { .update(&mut cx, |this, cx| this.matches_updated(cx))
let delegate = delegate.read(cx); .log_err()
let index = delegate.selected_index(); });
let target = if delegate.center_selection_after_match_updates() { }
ScrollTarget::Center(index)
} else { fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
ScrollTarget::Show(index) let index = self.delegate.selected_index();
}; let target = if self.delegate.center_selection_after_match_updates() {
this.list_state.scroll_to(target); ScrollTarget::Center(index)
cx.notify(); } else {
} ScrollTarget::Show(index)
}); };
}) self.list_state.scroll_to(target);
.detach() cx.notify();
}
} }
pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) { pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
if let Some(delegate) = self.delegate.upgrade(cx) { if self.delegate.match_count() > 0 {
delegate.update(cx, |delegate, cx| { self.delegate.set_selected_index(0, cx);
if delegate.match_count() > 0 { self.list_state.scroll_to(ScrollTarget::Show(0));
delegate.set_selected_index(0, cx);
self.list_state.scroll_to(ScrollTarget::Show(0));
}
});
cx.notify();
} }
cx.notify();
} }
pub fn select_index(&mut self, action: &SelectIndex, cx: &mut ViewContext<Self>) { pub fn select_index(&mut self, action: &SelectIndex, cx: &mut ViewContext<Self>) {
if let Some(delegate) = self.delegate.upgrade(cx) { let index = action.0;
let index = action.0; if self.delegate.match_count() > 0 {
delegate.update(cx, |delegate, cx| { self.confirmed = true;
if delegate.match_count() > 0 { self.delegate.set_selected_index(index, cx);
self.confirmed = true; self.delegate.confirm(cx);
delegate.set_selected_index(index, cx);
delegate.confirm(cx);
}
});
} }
} }
pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) { pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
if let Some(delegate) = self.delegate.upgrade(cx) { let match_count = self.delegate.match_count();
delegate.update(cx, |delegate, cx| { if match_count > 0 {
let match_count = delegate.match_count(); let index = match_count - 1;
if match_count > 0 { self.delegate.set_selected_index(index, cx);
let index = match_count - 1; self.list_state.scroll_to(ScrollTarget::Show(index));
delegate.set_selected_index(index, cx);
self.list_state.scroll_to(ScrollTarget::Show(index));
}
});
cx.notify();
} }
cx.notify();
} }
pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) { pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
if let Some(delegate) = self.delegate.upgrade(cx) { let next_index = self.delegate.selected_index() + 1;
delegate.update(cx, |delegate, cx| { if next_index < self.delegate.match_count() {
let next_index = delegate.selected_index() + 1; self.delegate.set_selected_index(next_index, cx);
if next_index < delegate.match_count() { self.list_state.scroll_to(ScrollTarget::Show(next_index));
delegate.set_selected_index(next_index, cx);
self.list_state.scroll_to(ScrollTarget::Show(next_index));
}
});
cx.notify();
} }
cx.notify();
} }
pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) { pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
if let Some(delegate) = self.delegate.upgrade(cx) { let mut selected_index = self.delegate.selected_index();
delegate.update(cx, |delegate, cx| { if selected_index > 0 {
let mut selected_index = delegate.selected_index(); selected_index -= 1;
if selected_index > 0 { self.delegate.set_selected_index(selected_index, cx);
selected_index -= 1; self.list_state
delegate.set_selected_index(selected_index, cx); .scroll_to(ScrollTarget::Show(selected_index));
self.list_state
.scroll_to(ScrollTarget::Show(selected_index));
}
});
cx.notify();
} }
cx.notify();
} }
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) { pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
if let Some(delegate) = self.delegate.upgrade(cx) { self.confirmed = true;
self.confirmed = true; self.delegate.confirm(cx);
delegate.update(cx, |delegate, cx| delegate.confirm(cx));
}
} }
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) { fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
if let Some(delegate) = self.delegate.upgrade(cx) { self.dismiss(cx);
delegate.update(cx, |delegate, cx| delegate.dismiss(cx)); }
}
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
cx.emit(PickerEvent::Dismiss);
self.delegate.dismissed(cx);
} }
} }

View file

@ -13,8 +13,8 @@ use gpui::{
impl_internal_actions, impl_internal_actions,
keymap_matcher::KeymapContext, keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, PromptLevel}, platform::{CursorStyle, MouseButton, PromptLevel},
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, RenderContext, Task, View, AppContext, ClipboardItem, Drawable, Element, Entity, ModelHandle, Task, View, ViewContext,
ViewContext, ViewHandle, ViewHandle,
}; };
use menu::{Confirm, SelectNext, SelectPrev}; use menu::{Confirm, SelectNext, SelectPrev};
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
@ -498,7 +498,7 @@ impl ProjectPanel {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.edit_state.take(); this.edit_state.take();
cx.notify(); cx.notify();
}); })?;
let new_entry = new_entry?; let new_entry = new_entry?;
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
@ -519,7 +519,7 @@ impl ProjectPanel {
); );
} }
cx.notify(); cx.notify();
}); })?;
Ok(()) Ok(())
})) }))
} }
@ -655,7 +655,7 @@ impl ProjectPanel {
this.project this.project
.update(cx, |project, cx| project.delete_entry(entry_id, cx)) .update(cx, |project, cx| project.delete_entry(entry_id, cx))
.ok_or_else(|| anyhow!("no such entry")) .ok_or_else(|| anyhow!("no such entry"))
})? })??
.await .await
})) }))
} }
@ -1015,8 +1015,8 @@ impl ProjectPanel {
fn for_each_visible_entry( fn for_each_visible_entry(
&self, &self,
range: Range<usize>, range: Range<usize>,
cx: &mut RenderContext<ProjectPanel>, cx: &mut ViewContext<ProjectPanel>,
mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut RenderContext<ProjectPanel>), mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut ViewContext<ProjectPanel>),
) { ) {
let mut ix = 0; let mut ix = 0;
for (worktree_id, visible_worktree_entries) in &self.visible_entries { for (worktree_id, visible_worktree_entries) in &self.visible_entries {
@ -1097,8 +1097,8 @@ impl ProjectPanel {
padding: f32, padding: f32,
row_container_style: ContainerStyle, row_container_style: ContainerStyle,
style: &ProjectPanelEntry, style: &ProjectPanelEntry,
cx: &mut RenderContext<V>, cx: &mut ViewContext<V>,
) -> ElementBox { ) -> Element<V> {
let kind = details.kind; let kind = details.kind;
let show_editor = details.is_editing && !details.is_processing; let show_editor = details.is_editing && !details.is_processing;
@ -1154,9 +1154,8 @@ impl ProjectPanel {
editor: &ViewHandle<Editor>, editor: &ViewHandle<Editor>,
dragged_entry_destination: &mut Option<Arc<Path>>, dragged_entry_destination: &mut Option<Arc<Path>>,
theme: &theme::ProjectPanel, theme: &theme::ProjectPanel,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let this = cx.handle();
let kind = details.kind; let kind = details.kind;
let path = details.path.clone(); let path = details.path.clone();
let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width; 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; 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(); let mut style = entry_style.style_for(state, details.is_selected).clone();
if cx if cx
@ -1201,7 +1200,7 @@ impl ProjectPanel {
cx, cx,
) )
}) })
.on_click(MouseButton::Left, move |e, cx| { .on_click(MouseButton::Left, move |e, _, cx| {
if !show_editor { if !show_editor {
if kind == EntryKind::Dir { if kind == EntryKind::Dir {
cx.dispatch_action(ToggleExpanded(entry_id)) 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 { cx.dispatch_action(DeployContextMenu {
entry_id, entry_id,
position: e.position, position: e.position,
}) })
}) })
.on_up(MouseButton::Left, move |_, cx| { .on_up(MouseButton::Left, move |_, _, cx| {
if let Some((_, dragged_entry)) = cx if let Some((_, dragged_entry)) = cx
.global::<DragAndDrop<Workspace>>() .global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id()) .currently_dragged::<ProjectEntryId>(cx.window_id())
@ -1231,27 +1230,23 @@ impl ProjectPanel {
}); });
} }
}) })
.on_move(move |_, cx| { .on_move(move |_, this, cx| {
if cx if cx
.global::<DragAndDrop<Workspace>>() .global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id()) .currently_dragged::<ProjectEntryId>(cx.window_id())
.is_some() .is_some()
{ {
if let Some(this) = this.upgrade(cx.app) { this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
this.update(cx.app, |this, _| { path.parent().map(|parent| Arc::from(parent))
this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) { } else {
path.parent().map(|parent| Arc::from(parent)) Some(path.clone())
} else { };
Some(path.clone())
};
})
}
} }
}) })
.as_draggable(entry_id, { .as_draggable(entry_id, {
let row_container_style = theme.dragged_entry.container; 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(); let theme = cx.global::<Settings>().theme.clone();
Self::render_entry_visual_element( Self::render_entry_visual_element(
&details, &details,
@ -1273,7 +1268,7 @@ impl View for ProjectPanel {
"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 {} enum ProjectPanel {}
let theme = &cx.global::<Settings>().theme.project_panel; let theme = &cx.global::<Settings>().theme.project_panel;
let mut container_style = theme.container; let mut container_style = theme.container;
@ -1285,7 +1280,7 @@ impl View for ProjectPanel {
if has_worktree { if has_worktree {
Stack::new() Stack::new()
.with_child( .with_child(
MouseEventHandler::<ProjectPanel>::new(0, cx, |_, cx| { MouseEventHandler::<ProjectPanel, _>::new(0, cx, |_, cx| {
UniformList::new( UniformList::new(
self.list.clone(), self.list.clone(),
self.visible_entries self.visible_entries
@ -1317,7 +1312,7 @@ impl View for ProjectPanel {
.expanded() .expanded()
.boxed() .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, // When deploying the context menu anywhere below the last project entry,
// act as if the user clicked the root of the last worktree. // act as if the user clicked the root of the last worktree.
if let Some(entry_id) = last_worktree_root_id { if let Some(entry_id) = last_worktree_root_id {
@ -1334,7 +1329,7 @@ impl View for ProjectPanel {
} else { } else {
Flex::column() Flex::column()
.with_child( .with_child(
MouseEventHandler::<Self>::new(2, cx, { MouseEventHandler::<Self, _>::new(2, cx, {
let button_style = theme.open_project_button.clone(); let button_style = theme.open_project_button.clone();
let context_menu_item_style = let context_menu_item_style =
cx.global::<Settings>().theme.context_menu.item.clone(); cx.global::<Settings>().theme.context_menu.item.clone();
@ -1353,7 +1348,7 @@ impl View for ProjectPanel {
.boxed() .boxed()
} }
}) })
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(workspace::Open) cx.dispatch_action(workspace::Open)
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
@ -1549,7 +1544,7 @@ mod tests {
.await; .await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).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)); let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx));
select_path(&panel, "root1", 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 // Add a file with the root folder selected. The filename editor is placed
// before the first file in the root folder. // before the first file in the root folder.
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx)); 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!( assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx), visible_entries_as_strings(&panel, 0..10, cx),
&[ &[
@ -1943,7 +1941,8 @@ mod tests {
let mut result = Vec::new(); let mut result = Vec::new();
let mut project_entries = HashSet::new(); let mut project_entries = HashSet::new();
let mut has_editor = false; 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, _| { panel.for_each_visible_entry(range, cx, |project_entry, details, _| {
if details.is_editing { if details.is_editing {
assert!(!has_editor, "duplicate editor entry"); assert!(!has_editor, "duplicate editor entry");

View file

@ -1,90 +1,63 @@
use anyhow::anyhow;
use editor::{ use editor::{
combine_syntax_and_fuzzy_match_highlights, scroll::autoscroll::Autoscroll, combine_syntax_and_fuzzy_match_highlights, scroll::autoscroll::Autoscroll,
styled_runs_for_code_label, Bias, Editor, styled_runs_for_code_label, Bias, Editor,
}; };
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle,
RenderContext, Task, View, ViewContext, ViewHandle,
}; };
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate, PickerEvent};
use project::{Project, Symbol}; use project::{Project, Symbol};
use settings::Settings; use settings::Settings;
use std::{borrow::Cow, cmp::Reverse}; use std::{borrow::Cow, cmp::Reverse, sync::Arc};
use util::ResultExt; use util::ResultExt;
use workspace::Workspace; use workspace::Workspace;
actions!(project_symbols, [Toggle]); actions!(project_symbols, [Toggle]);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
cx.add_action(ProjectSymbolsView::toggle); cx.add_action(toggle);
Picker::<ProjectSymbolsView>::init(cx); ProjectSymbols::init(cx);
} }
pub struct ProjectSymbolsView { fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
picker: ViewHandle<Picker<Self>>, 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>, project: ModelHandle<Project>,
selected_match_index: usize, selected_match_index: usize,
symbols: Vec<Symbol>, symbols: Vec<Symbol>,
visible_match_candidates: Vec<StringMatchCandidate>, visible_match_candidates: Vec<StringMatchCandidate>,
external_match_candidates: Vec<StringMatchCandidate>, external_match_candidates: Vec<StringMatchCandidate>,
show_worktree_root_name: bool, show_worktree_root_name: bool,
pending_update: Task<()>,
matches: Vec<StringMatch>, matches: Vec<StringMatch>,
} }
pub enum Event { impl ProjectSymbolsDelegate {
Dismissed, fn new(workspace: WeakViewHandle<Workspace>, project: ModelHandle<Project>) -> Self {
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();
Self { Self {
workspace,
project, project,
picker: cx.add_view(|cx| Picker::new("Search project symbols...", handle, cx)),
selected_match_index: 0, selected_match_index: 0,
symbols: Default::default(), symbols: Default::default(),
visible_match_candidates: Default::default(), visible_match_candidates: Default::default(),
external_match_candidates: Default::default(), external_match_candidates: Default::default(),
matches: Default::default(), matches: Default::default(),
show_worktree_root_name: false, show_worktree_root_name: false,
pending_update: Task::ready(()),
} }
} }
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) { fn filter(&mut self, query: &str, cx: &mut ViewContext<ProjectSymbols>) {
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>) {
const MAX_MATCHES: usize = 100; const MAX_MATCHES: usize = 100;
let mut visible_matches = cx.background_executor().block(fuzzy::match_strings( let mut visible_matches = cx.background_executor().block(fuzzy::match_strings(
&self.visible_match_candidates, &self.visible_match_candidates,
@ -125,60 +98,50 @@ impl ProjectSymbolsView {
self.matches = matches; self.matches = matches;
self.set_selected_index(0, cx); 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 ProjectSymbolsView { impl PickerDelegate for ProjectSymbolsDelegate {
fn confirm(&mut self, cx: &mut ViewContext<Self>) { fn placeholder_text(&self) -> Arc<str> {
"Search project symbols...".into()
}
fn confirm(&mut self, cx: &mut ViewContext<ProjectSymbols>) {
if let Some(symbol) = self if let Some(symbol) = self
.matches .matches
.get(self.selected_match_index) .get(self.selected_match_index)
.map(|mat| self.symbols[mat.candidate_id].clone()) .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>) { fn dismissed(&mut self, _cx: &mut ViewContext<ProjectSymbols>) {}
cx.emit(Event::Dismissed);
}
fn match_count(&self) -> usize { fn match_count(&self) -> usize {
self.matches.len() self.matches.len()
@ -188,23 +151,23 @@ impl PickerDelegate for ProjectSymbolsView {
self.selected_match_index 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; 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.filter(&query, cx);
self.show_worktree_root_name = self.project.read(cx).visible_worktrees(cx).count() > 1; self.show_worktree_root_name = self.project.read(cx).visible_worktrees(cx).count() > 1;
let symbols = self let symbols = self
.project .project
.update(cx, |project, cx| project.symbols(&query, cx)); .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(); let symbols = symbols.await.log_err();
if let Some(this) = this.upgrade(&cx) { if let Some(this) = this.upgrade(&cx) {
if let Some(symbols) = symbols { if let Some(symbols) = symbols {
this.update(&mut cx, |this, cx| { 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 let (visible_match_candidates, external_match_candidates) = symbols
.iter() .iter()
.enumerate() .enumerate()
@ -221,15 +184,15 @@ impl PickerDelegate for ProjectSymbolsView {
.map_or(false, |e| !e.is_ignored) .map_or(false, |e| !e.is_ignored)
}); });
this.visible_match_candidates = visible_match_candidates; delegate.visible_match_candidates = visible_match_candidates;
this.external_match_candidates = external_match_candidates; delegate.external_match_candidates = external_match_candidates;
this.symbols = symbols; delegate.symbols = symbols;
this.filter(&query, cx); delegate.filter(&query, cx);
}); })
.log_err();
} }
} }
}); })
Task::ready(())
} }
fn render_match( fn render_match(
@ -238,7 +201,7 @@ impl PickerDelegate for ProjectSymbolsView {
mouse_state: &mut MouseState, mouse_state: &mut MouseState,
selected: bool, selected: bool,
cx: &AppContext, cx: &AppContext,
) -> ElementBox { ) -> Element<Picker<Self>> {
let string_match = &self.matches[ix]; let string_match = &self.matches[ix];
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
let style = &settings.theme.picker.item; 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. // Create the project symbols view.
let (_, symbols_view) = cx.add_window(|cx| ProjectSymbolsView::new(project.clone(), cx)); let symbols = cx.add_view(&workspace, |cx| {
let picker = symbols_view.read_with(cx, |symbols_view, _| symbols_view.picker.clone()); ProjectSymbols::new(
ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()),
cx,
)
});
// Spawn multiples updates before the first update completes, // Spawn multiples updates before the first update completes,
// such that in the end, there are no matches. Testing for regression: // such that in the end, there are no matches. Testing for regression:
// https://github.com/zed-industries/zed/issues/861 // 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("o".to_string(), cx);
p.update_matches("on".to_string(), cx); p.update_matches("on".to_string(), cx);
p.update_matches("onex".to_string(), cx); p.update_matches("onex".to_string(), cx);
}); });
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
symbols_view.read_with(cx, |symbols_view, _| { symbols.read_with(cx, |symbols, _| {
assert_eq!(symbols_view.matches.len(), 0); assert_eq!(symbols.delegate().matches.len(), 0);
}); });
// Spawn more updates such that in the end, there are matches. // 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("one".to_string(), cx);
p.update_matches("on".to_string(), cx); p.update_matches("on".to_string(), cx);
}); });
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
symbols_view.read_with(cx, |symbols_view, _| { symbols.read_with(cx, |symbols, _| {
assert_eq!(symbols_view.matches.len(), 2); let delegate = symbols.delegate();
assert_eq!(symbols_view.matches[0].string, "ton"); assert_eq!(delegate.matches.len(), 2);
assert_eq!(symbols_view.matches[1].string, "one"); 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. // 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("o".to_string(), cx);
p.update_matches("".to_string(), cx); p.update_matches("".to_string(), cx);
}); });
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
symbols_view.read_with(cx, |symbols_view, _| { symbols.read_with(cx, |symbols, _| {
assert_eq!(symbols_view.matches.len(), 0); assert_eq!(symbols.delegate().matches.len(), 0);
}); });
} }

View file

@ -3,7 +3,7 @@ use std::path::Path;
use fuzzy::StringMatch; use fuzzy::StringMatch;
use gpui::{ use gpui::{
elements::{Label, LabelStyle}, elements::{Label, LabelStyle},
Element, ElementBox, Drawable, Element, View,
}; };
use workspace::WorkspaceLocation; 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) Label::new(self.text, style)
.with_highlights(self.highlight_positions) .with_highlights(self.highlight_positions)
.boxed() .boxed()

View file

@ -3,14 +3,15 @@ mod highlighted_workspace_location;
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, actions,
elements::{ChildView, Flex, ParentElement}, anyhow::Result,
AnyViewHandle, AppContext, Element, ElementBox, Entity, RenderContext, Task, View, ViewContext, elements::{Flex, ParentElement},
ViewHandle, AppContext, Drawable, Element, Task, ViewContext,
}; };
use highlighted_workspace_location::HighlightedWorkspaceLocation; use highlighted_workspace_location::HighlightedWorkspaceLocation;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate, PickerEvent};
use settings::Settings; use settings::Settings;
use std::sync::Arc;
use workspace::{ use workspace::{
notifications::simple_message_notification::MessageNotification, OpenPaths, Workspace, notifications::simple_message_notification::MessageNotification, OpenPaths, Workspace,
WorkspaceLocation, WORKSPACE_DB, WorkspaceLocation, WORKSPACE_DB,
@ -19,101 +20,70 @@ use workspace::{
actions!(projects, [OpenRecent]); actions!(projects, [OpenRecent]);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
cx.add_action(RecentProjectsView::toggle); cx.add_async_action(toggle);
Picker::<RecentProjectsView>::init(cx); RecentProjects::init(cx);
} }
struct RecentProjectsView { fn toggle(
picker: ViewHandle<Picker<Self>>, _: &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>, workspace_locations: Vec<WorkspaceLocation>,
selected_match_index: usize, selected_match_index: usize,
matches: Vec<StringMatch>, matches: Vec<StringMatch>,
} }
impl RecentProjectsView { impl RecentProjectsDelegate {
fn new(workspace_locations: Vec<WorkspaceLocation>, cx: &mut ViewContext<Self>) -> Self { fn new(workspace_locations: Vec<WorkspaceLocation>) -> Self {
let handle = cx.weak_handle();
Self { Self {
picker: cx.add_view(|cx| {
Picker::new("Recent Projects...", handle, cx).with_max_size(800., 1200.)
}),
workspace_locations, workspace_locations,
selected_match_index: 0, selected_match_index: 0,
matches: Default::default(), 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 { impl PickerDelegate for RecentProjectsDelegate {
Dismissed, fn placeholder_text(&self) -> Arc<str> {
} "Recent Projects...".into()
impl Entity for RecentProjectsView {
type Event = Event;
}
impl View for RecentProjectsView {
fn ui_name() -> &'static str {
"RecentProjectsView"
} }
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 { fn match_count(&self) -> usize {
self.matches.len() self.matches.len()
} }
@ -122,11 +92,15 @@ impl PickerDelegate for RecentProjectsView {
self.selected_match_index 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; 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 query = query.trim_start();
let smart_case = query.chars().any(|c| c.is_uppercase()); let smart_case = query.chars().any(|c| c.is_uppercase());
let candidates = self let candidates = self
@ -164,19 +138,17 @@ impl PickerDelegate for RecentProjectsView {
Task::ready(()) 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()) { if let Some(selected_match) = &self.matches.get(self.selected_index()) {
let workspace_location = &self.workspace_locations[selected_match.candidate_id]; let workspace_location = &self.workspace_locations[selected_match.candidate_id];
cx.dispatch_action(OpenPaths { cx.dispatch_action(OpenPaths {
paths: workspace_location.paths().as_ref().clone(), paths: workspace_location.paths().as_ref().clone(),
}); });
cx.emit(Event::Dismissed); cx.emit(PickerEvent::Dismiss);
} }
} }
fn dismiss(&mut self, cx: &mut ViewContext<Self>) { fn dismissed(&mut self, _cx: &mut ViewContext<RecentProjects>) {}
cx.emit(Event::Dismissed);
}
fn render_match( fn render_match(
&self, &self,
@ -184,7 +156,7 @@ impl PickerDelegate for RecentProjectsView {
mouse_state: &mut gpui::MouseState, mouse_state: &mut gpui::MouseState,
selected: bool, selected: bool,
cx: &gpui::AppContext, cx: &gpui::AppContext,
) -> ElementBox { ) -> Element<Picker<Self>> {
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
let string_match = &self.matches[ix]; let string_match = &self.matches[ix];
let style = settings.theme.picker.item.style_for(mouse_state, selected); let style = settings.theme.picker.item.style_for(mouse_state, selected);

View file

@ -9,13 +9,13 @@ use gpui::{
elements::*, elements::*,
impl_actions, impl_actions,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
Action, AnyViewHandle, AppContext, Entity, RenderContext, Subscription, Task, View, Action, AnyViewHandle, AppContext, Entity, Subscription, Task, View, ViewContext, ViewHandle,
ViewContext, ViewHandle,
}; };
use project::search::SearchQuery; use project::search::SearchQuery;
use serde::Deserialize; use serde::Deserialize;
use settings::Settings; use settings::Settings;
use std::{any::Any, sync::Arc}; use std::{any::Any, sync::Arc};
use util::ResultExt;
use workspace::{ use workspace::{
item::ItemHandle, item::ItemHandle,
searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle}, 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 theme = cx.global::<Settings>().theme.clone();
let editor_container = if self.query_contains_error { let editor_container = if self.query_contains_error {
theme.search.invalid_editor theme.search.invalid_editor
@ -324,8 +324,8 @@ impl BufferSearchBar {
option_supported: bool, option_supported: bool,
icon: &'static str, icon: &'static str,
option: SearchOption, option: SearchOption,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<ElementBox> { ) -> Option<Element<Self>> {
if !option_supported { if !option_supported {
return None; return None;
} }
@ -333,7 +333,7 @@ impl BufferSearchBar {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone(); let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let is_active = self.is_search_option_enabled(option); let is_active = self.is_search_option_enabled(option);
Some( Some(
MouseEventHandler::<Self>::new(option as usize, cx, |state, cx| { MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
let style = cx let style = cx
.global::<Settings>() .global::<Settings>()
.theme .theme
@ -345,11 +345,11 @@ impl BufferSearchBar {
.with_style(style.container) .with_style(style.container)
.boxed() .boxed()
}) })
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_any_action(option.to_toggle_action()) cx.dispatch_any_action(option.to_toggle_action())
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<Self, _>( .with_tooltip::<Self>(
option as usize, option as usize,
format!("Toggle {}", option.label()), format!("Toggle {}", option.label()),
Some(option.to_toggle_action()), Some(option.to_toggle_action()),
@ -364,8 +364,8 @@ impl BufferSearchBar {
&self, &self,
icon: &'static str, icon: &'static str,
direction: Direction, direction: Direction,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let action: Box<dyn Action>; let action: Box<dyn Action>;
let tooltip; let tooltip;
match direction { match direction {
@ -381,7 +381,7 @@ impl BufferSearchBar {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone(); let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
enum NavButton {} enum NavButton {}
MouseEventHandler::<NavButton>::new(direction as usize, cx, |state, cx| { MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
let style = cx let style = cx
.global::<Settings>() .global::<Settings>()
.theme .theme
@ -395,10 +395,10 @@ impl BufferSearchBar {
}) })
.on_click(MouseButton::Left, { .on_click(MouseButton::Left, {
let action = action.boxed_clone(); 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_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<NavButton, _>( .with_tooltip::<NavButton>(
direction as usize, direction as usize,
tooltip.to_string(), tooltip.to_string(),
Some(action), Some(action),
@ -411,14 +411,14 @@ impl BufferSearchBar {
fn render_close_button( fn render_close_button(
&self, &self,
theme: &theme::Search, theme: &theme::Search,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let action = Box::new(Dismiss); let action = Box::new(Dismiss);
let tooltip = "Dismiss Buffer Search"; let tooltip = "Dismiss Buffer Search";
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone(); let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
enum CloseButton {} enum CloseButton {}
MouseEventHandler::<CloseButton>::new(0, cx, |state, _| { MouseEventHandler::<CloseButton, _>::new(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state, false); let style = theme.dismiss_button.style_for(state, false);
Svg::new("icons/x_mark_8.svg") Svg::new("icons/x_mark_8.svg")
.with_color(style.color) .with_color(style.color)
@ -433,10 +433,10 @@ impl BufferSearchBar {
}) })
.on_click(MouseButton::Left, { .on_click(MouseButton::Left, {
let action = action.boxed_clone(); 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_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() .boxed()
} }
@ -618,7 +618,8 @@ impl BufferSearchBar {
} }
cx.notify(); cx.notify();
} }
}); })
.log_err();
} }
})); }));
} }

View file

@ -12,9 +12,8 @@ use gpui::{
actions, actions,
elements::*, elements::*,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
Action, AnyViewHandle, AppContext, ElementBox, Entity, ModelContext, ModelHandle, Action, AnyViewHandle, AppContext, Element, Entity, ModelContext, ModelHandle, Subscription,
RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakModelHandle, Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
WeakViewHandle,
}; };
use menu::Confirm; use menu::Confirm;
use project::{search::SearchQuery, Project}; use project::{search::SearchQuery, Project};
@ -30,7 +29,7 @@ use std::{
}; };
use util::ResultExt as _; use util::ResultExt as _;
use workspace::{ use workspace::{
item::{Item, ItemEvent, ItemHandle}, item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
searchable::{Direction, SearchableItem, SearchableItemHandle}, searchable::{Direction, SearchableItem, SearchableItemHandle},
ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
}; };
@ -179,7 +178,7 @@ impl View for ProjectSearchView {
"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); let model = &self.model.read(cx);
if model.match_ranges.is_empty() { if model.match_ranges.is_empty() {
enum Status {} enum Status {}
@ -192,7 +191,7 @@ impl View for ProjectSearchView {
} else { } else {
"No results" "No results"
}; };
MouseEventHandler::<Status>::new(0, cx, |_, _| { MouseEventHandler::<Status, _>::new(0, cx, |_, _| {
Label::new(text, theme.search.results_status.clone()) Label::new(text, theme.search.results_status.clone())
.aligned() .aligned()
.contained() .contained()
@ -200,7 +199,7 @@ impl View for ProjectSearchView {
.flex(1., true) .flex(1., true)
.boxed() .boxed()
}) })
.on_down(MouseButton::Left, |_, cx| { .on_down(MouseButton::Left, |_, _, cx| {
cx.focus_parent_view(); cx.focus_parent_view();
}) })
.boxed() .boxed()
@ -250,12 +249,12 @@ impl Item for ProjectSearchView {
.update(cx, |editor, cx| editor.deactivated(cx)); .update(cx, |editor, cx| editor.deactivated(cx));
} }
fn tab_content( fn tab_content<T: View>(
&self, &self,
_detail: Option<usize>, _detail: Option<usize>,
tab_theme: &theme::Tab, tab_theme: &theme::Tab,
cx: &AppContext, cx: &AppContext,
) -> ElementBox { ) -> Element<T> {
Flex::row() Flex::row()
.with_child( .with_child(
Svg::new("icons/magnifying_glass_12.svg") 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) self.results_editor.breadcrumbs(theme, cx)
} }
@ -752,8 +751,8 @@ impl ProjectSearchBar {
&self, &self,
icon: &'static str, icon: &'static str,
direction: Direction, direction: Direction,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let action: Box<dyn Action>; let action: Box<dyn Action>;
let tooltip; let tooltip;
match direction { match direction {
@ -769,7 +768,7 @@ impl ProjectSearchBar {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone(); let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
enum NavButton {} enum NavButton {}
MouseEventHandler::<NavButton>::new(direction as usize, cx, |state, cx| { MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
let style = &cx let style = &cx
.global::<Settings>() .global::<Settings>()
.theme .theme
@ -783,10 +782,10 @@ impl ProjectSearchBar {
}) })
.on_click(MouseButton::Left, { .on_click(MouseButton::Left, {
let action = action.boxed_clone(); 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_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<NavButton, _>( .with_tooltip::<NavButton>(
direction as usize, direction as usize,
tooltip.to_string(), tooltip.to_string(),
Some(action), Some(action),
@ -800,11 +799,11 @@ impl ProjectSearchBar {
&self, &self,
icon: &'static str, icon: &'static str,
option: SearchOption, option: SearchOption,
cx: &mut RenderContext<Self>, cx: &mut ViewContext<Self>,
) -> ElementBox { ) -> Element<Self> {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone(); let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let is_active = self.is_option_enabled(option, cx); 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 let style = &cx
.global::<Settings>() .global::<Settings>()
.theme .theme
@ -816,11 +815,11 @@ impl ProjectSearchBar {
.with_style(style.container) .with_style(style.container)
.boxed() .boxed()
}) })
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_any_action(option.to_toggle_action()) cx.dispatch_any_action(option.to_toggle_action())
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<Self, _>( .with_tooltip::<Self>(
option as usize, option as usize,
format!("Toggle {}", option.label()), format!("Toggle {}", option.label()),
Some(option.to_toggle_action()), Some(option.to_toggle_action()),
@ -853,7 +852,7 @@ impl View for ProjectSearchBar {
"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() { if let Some(search) = self.active_project_search.as_ref() {
let search = search.read(cx); let search = search.read(cx);
let theme = cx.global::<Settings>().theme.clone(); let theme = cx.global::<Settings>().theme.clone();

View file

@ -80,9 +80,25 @@ mod tests {
watch_files, watched_json::watch_settings_file, EditorSettings, Settings, SoftWrap, watch_files, watched_json::watch_settings_file, EditorSettings, Settings, SoftWrap,
}; };
use fs::FakeFs; use fs::FakeFs;
use gpui::{actions, Action}; use gpui::{actions, elements::*, Action, Entity, View, ViewContext, WindowContext};
use theme::ThemeRegistry; 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] #[gpui::test]
async fn test_base_keymap(cx: &mut gpui::TestAppContext) { async fn test_base_keymap(cx: &mut gpui::TestAppContext) {
let executor = cx.background(); let executor = cx.background();
@ -148,8 +164,10 @@ mod tests {
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
let (window_id, _view) = cx.add_window(|_| TestView);
// Test loading the keymap base at all // Test loading the keymap base at all
cx.update(|cx| { cx.read_window(window_id, |cx| {
assert_key_bindings_for( assert_key_bindings_for(
cx, cx,
vec![("backspace", &A), ("k", &ActivatePreviousPane)], vec![("backspace", &A), ("k", &ActivatePreviousPane)],
@ -177,7 +195,7 @@ mod tests {
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.update(|cx| { cx.read_window(window_id, |cx| {
assert_key_bindings_for( assert_key_bindings_for(
cx, cx,
vec![("backspace", &B), ("k", &ActivatePreviousPane)], vec![("backspace", &B), ("k", &ActivatePreviousPane)],
@ -201,7 +219,7 @@ mod tests {
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
cx.update(|cx| { cx.read_window(window_id, |cx| {
assert_key_bindings_for( assert_key_bindings_for(
cx, cx,
vec![("backspace", &B), ("[", &ActivatePrevItem)], vec![("backspace", &B), ("[", &ActivatePrevItem)],
@ -211,14 +229,14 @@ mod tests {
} }
fn assert_key_bindings_for<'a>( fn assert_key_bindings_for<'a>(
cx: &mut AppContext, cx: &WindowContext,
actions: Vec<(&'static str, &'a dyn Action)>, actions: Vec<(&'static str, &'a dyn Action)>,
line: u32, line: u32,
) { ) {
for (key, action) in actions { for (key, action) in actions {
// assert that... // assert that...
assert!( assert!(
cx.available_actions(0, 0).any(|(_, bound_action, b)| { cx.available_actions(0).any(|(_, bound_action, b)| {
// action names match... // action names match...
bound_action.name() == action.name() bound_action.name() == action.name()
&& bound_action.namespace() == action.namespace() && bound_action.namespace() == action.namespace()

View file

@ -735,7 +735,7 @@ mod tests {
.map_or(5, |o| o.parse().expect("invalid OPERATIONS variable")); .map_or(5, |o| o.parse().expect("invalid OPERATIONS variable"));
for seed in starting_seed..(starting_seed + num_iterations) { for seed in starting_seed..(starting_seed + num_iterations) {
dbg!(seed); eprintln!("seed = {}", seed);
let mut rng = StdRng::seed_from_u64(seed); let mut rng = StdRng::seed_from_u64(seed);
let rng = &mut rng; let rng = &mut rng;

View file

@ -3,8 +3,8 @@ use gpui::{
elements::*, elements::*,
impl_internal_actions, impl_internal_actions,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
AppContext, Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle, AppContext, Drawable, Element, Entity, View, ViewContext, ViewHandle, WeakModelHandle,
WeakModelHandle, WeakViewHandle, WeakViewHandle,
}; };
use settings::Settings; use settings::Settings;
use std::any::TypeId; use std::any::TypeId;
@ -42,14 +42,14 @@ impl View for TerminalButton {
"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 workspace = self.workspace.upgrade(cx);
let project = match workspace { let project = match workspace {
Some(workspace) => workspace.read(cx).project().read(cx), Some(workspace) => workspace.read(cx).project().read(cx),
None => return Empty::new().boxed(), 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 let active = focused_view
.map(|view_id| { .map(|view_id| {
cx.view_type_id(cx.window_id(), view_id) == Some(TypeId::of::<TerminalView>()) cx.view_type_id(cx.window_id(), view_id) == Some(TypeId::of::<TerminalView>())
@ -62,7 +62,7 @@ impl View for TerminalButton {
Stack::new() Stack::new()
.with_child( .with_child(
MouseEventHandler::<Self>::new(0, cx, { MouseEventHandler::<Self, _>::new(0, cx, {
let theme = theme.clone(); let theme = theme.clone();
move |state, _cx| { move |state, _cx| {
let style = theme let style = theme
@ -96,7 +96,7 @@ impl View for TerminalButton {
} }
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| { .on_click(MouseButton::Left, move |_, _, cx| {
if has_terminals { if has_terminals {
cx.dispatch_action(DeployTerminalMenu); cx.dispatch_action(DeployTerminalMenu);
} else { } else {
@ -105,7 +105,7 @@ impl View for TerminalButton {
} }
}; };
}) })
.with_tooltip::<Self, _>( .with_tooltip::<Self>(
0, 0,
"Show Terminal".into(), "Show Terminal".into(),
Some(Box::new(FocusDock)), Some(Box::new(FocusDock)),

View file

@ -10,8 +10,8 @@ use gpui::{
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
serde_json::json, serde_json::json,
text_layout::{Line, RunStyle}, text_layout::{Line, RunStyle},
Element, ElementBox, EventContext, FontCache, ModelContext, MouseRegion, PaintContext, Quad, Drawable, Element, EventContext, FontCache, ModelContext, MouseRegion, Quad, SceneBuilder,
SizeConstraint, TextLayoutCache, WeakModelHandle, WeakViewHandle, SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle,
}; };
use itertools::Itertools; use itertools::Itertools;
use language::CursorShape; use language::CursorShape;
@ -45,7 +45,7 @@ pub struct LayoutState {
size: TerminalSize, size: TerminalSize,
mode: TermMode, mode: TermMode,
display_offset: usize, 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 ///Helper struct for converting data between alacritty's cursor points, and displayed cursor points
@ -84,10 +84,12 @@ impl LayoutCell {
fn paint( fn paint(
&self, &self,
scene: &mut SceneBuilder,
origin: Vector2F, origin: Vector2F,
layout: &LayoutState, layout: &LayoutState,
visible_bounds: RectF, visible_bounds: RectF,
cx: &mut PaintContext, _view: &mut TerminalView,
cx: &mut ViewContext<TerminalView>,
) { ) {
let pos = { let pos = {
let point = self.point; let point = self.point;
@ -98,7 +100,7 @@ impl LayoutCell {
}; };
self.text 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 position = {
let point = self.point; let point = self.point;
vec2f( vec2f(
@ -139,7 +148,7 @@ impl LayoutRect {
layout.size.line_height, layout.size.line_height,
); );
cx.scene.push_quad(Quad { scene.push_quad(Quad {
bounds: RectF::new(position, size), bounds: RectF::new(position, size),
background: Some(self.color), background: Some(self.color),
border: Default::default(), 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? ///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 { pub struct TerminalElement {
terminal: WeakModelHandle<Terminal>, terminal: WeakModelHandle<Terminal>,
view: WeakViewHandle<TerminalView>,
focused: bool, focused: bool,
cursor_visible: bool, cursor_visible: bool,
} }
impl TerminalElement { impl TerminalElement {
pub fn new( pub fn new(
view: WeakViewHandle<TerminalView>,
terminal: WeakModelHandle<Terminal>, terminal: WeakModelHandle<Terminal>,
focused: bool, focused: bool,
cursor_visible: bool, cursor_visible: bool,
) -> TerminalElement { ) -> TerminalElement {
TerminalElement { TerminalElement {
view,
terminal, terminal,
focused, focused,
cursor_visible, cursor_visible,
@ -361,11 +367,11 @@ impl TerminalElement {
connection: WeakModelHandle<Terminal>, connection: WeakModelHandle<Terminal>,
origin: Vector2F, origin: Vector2F,
f: impl Fn(&mut Terminal, Vector2F, E, &mut ModelContext<Terminal>), f: impl Fn(&mut Terminal, Vector2F, E, &mut ModelContext<Terminal>),
) -> impl Fn(E, &mut EventContext) { ) -> impl Fn(E, &mut TerminalView, &mut EventContext<TerminalView>) {
move |event, cx| { move |event, _: &mut TerminalView, cx| {
cx.focus_parent_view(); cx.focus_parent_view();
if let Some(conn_handle) = connection.upgrade(cx.app) { if let Some(conn_handle) = connection.upgrade(cx) {
conn_handle.update(cx.app, |terminal, cx| { conn_handle.update(cx, |terminal, cx| {
f(terminal, origin, event, cx); f(terminal, origin, event, cx);
cx.notify(); cx.notify();
@ -376,15 +382,15 @@ impl TerminalElement {
fn attach_mouse_handlers( fn attach_mouse_handlers(
&self, &self,
scene: &mut SceneBuilder,
origin: Vector2F, origin: Vector2F,
view_id: usize,
visible_bounds: RectF, visible_bounds: RectF,
mode: TermMode, mode: TermMode,
cx: &mut PaintContext, cx: &mut ViewContext<TerminalView>,
) { ) {
let connection = self.terminal; 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: // Terminal Emulator controlled behavior:
region = region region = region
@ -400,10 +406,10 @@ impl TerminalElement {
), ),
) )
// Update drag selections // 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 cx.is_parent_view_focused() {
if let Some(conn_handle) = connection.upgrade(cx.app) { if let Some(conn_handle) = connection.upgrade(cx) {
conn_handle.update(cx.app, |terminal, cx| { conn_handle.update(cx, |terminal, cx| {
terminal.mouse_drag(event, origin); terminal.mouse_drag(event, origin);
cx.notify(); cx.notify();
}) })
@ -422,9 +428,9 @@ impl TerminalElement {
), ),
) )
// Context menu // Context menu
.on_click(MouseButton::Right, move |e, cx| { .on_click(MouseButton::Right, move |e, _: &mut TerminalView, cx| {
let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) { let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx) {
conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift)) conn_handle.update(cx, |terminal, _cx| terminal.mouse_mode(e.shift))
} else { } else {
// If we can't get the model handle, probably can't deploy the context menu // If we can't get the model handle, probably can't deploy the context menu
true 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 cx.is_parent_view_focused() {
if let Some(conn_handle) = connection.upgrade(cx.app) { if let Some(conn_handle) = connection.upgrade(cx) {
conn_handle.update(cx.app, |terminal, cx| { conn_handle.update(cx, |terminal, cx| {
terminal.mouse_move(&event, origin); terminal.mouse_move(&event, origin);
cx.notify(); cx.notify();
}) })
} }
} }
}) })
.on_scroll(move |event, cx| { .on_scroll(move |event, _: &mut TerminalView, cx| {
// cx.focus_parent_view(); if let Some(conn_handle) = connection.upgrade(cx) {
if let Some(conn_handle) = connection.upgrade(cx.app) { conn_handle.update(cx, |terminal, cx| {
conn_handle.update(cx.app, |terminal, cx| {
terminal.scroll_wheel(event, origin); terminal.scroll_wheel(event, origin);
cx.notify(); 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. ///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 LayoutState = LayoutState;
type PaintState = (); type PaintState = ();
fn layout( fn layout(
&mut self, &mut self,
constraint: gpui::SizeConstraint, constraint: gpui::SizeConstraint,
cx: &mut gpui::LayoutContext, view: &mut TerminalView,
cx: &mut ViewContext<TerminalView>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
let font_cache = cx.font_cache(); let font_cache = cx.font_cache();
@ -581,34 +587,31 @@ impl Element for TerminalElement {
let background_color = terminal_theme.background; let background_color = terminal_theme.background;
let terminal_handle = self.terminal.upgrade(cx).unwrap(); 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.set_size(dimensions);
terminal.try_sync(cx); terminal.try_sync(cx);
terminal.last_content.last_hovered_hyperlink.clone() terminal.last_content.last_hovered_hyperlink.clone()
}); });
let view_handle = self.view.clone(); let hyperlink_tooltip = last_hovered_hyperlink.map(|(uri, _, id)| {
let hyperlink_tooltip = last_hovered_hyperlink.and_then(|(uri, _, id)| { let mut tooltip = Overlay::new(
// last_mouse.and_then(|_last_mouse| { Empty::new()
view_handle.upgrade(cx).map(|handle| { .contained()
let mut tooltip = cx.render(&handle, |_, cx| { .constrained()
Overlay::new( .with_width(dimensions.width())
Empty::new() .with_height(dimensions.height())
.contained() .with_tooltip::<TerminalElement>(id, uri, None, tooltip_style, cx)
.constrained() .boxed(),
.with_width(dimensions.width()) )
.with_height(dimensions.height()) .with_position_mode(gpui::elements::OverlayPositionMode::Local)
.with_tooltip::<TerminalElement, _>(id, uri, None, tooltip_style, cx) .boxed();
.boxed(),
)
.with_position_mode(gpui::elements::OverlayPositionMode::Local)
.boxed()
});
tooltip.layout(SizeConstraint::new(Vector2F::zero(), cx.window_size), cx); tooltip.layout(
tooltip SizeConstraint::new(Vector2F::zero(), cx.window_size()),
}) view,
// }) cx,
);
tooltip
}); });
let TerminalContent { let TerminalContent {
@ -637,7 +640,7 @@ impl Element for TerminalElement {
cells, cells,
&text_style, &text_style,
&terminal_theme, &terminal_theme,
cx.text_layout_cache, cx.text_layout_cache(),
cx.font_cache(), cx.font_cache(),
last_hovered_hyperlink last_hovered_hyperlink
.as_ref() .as_ref()
@ -659,7 +662,7 @@ impl Element for TerminalElement {
terminal_theme.foreground terminal_theme.foreground
}; };
cx.text_layout_cache.layout_str( cx.text_layout_cache().layout_str(
&str_trxt, &str_trxt,
text_style.font_size, text_style.font_size,
&[( &[(
@ -717,23 +720,25 @@ impl Element for TerminalElement {
fn paint( fn paint(
&mut self, &mut self,
bounds: gpui::geometry::rect::RectF, scene: &mut SceneBuilder,
visible_bounds: gpui::geometry::rect::RectF, bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState, layout: &mut Self::LayoutState,
cx: &mut gpui::PaintContext, view: &mut TerminalView,
cx: &mut ViewContext<TerminalView>,
) -> Self::PaintState { ) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
//Setup element stuff //Setup element stuff
let clip_bounds = Some(visible_bounds); 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.); 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 // 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, bounds,
style: if layout.hyperlink_tooltip.is_some() { style: if layout.hyperlink_tooltip.is_some() {
CursorStyle::PointingHand 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 //Start with a background color
cx.scene.push_quad(Quad { scene.push_quad(Quad {
bounds: RectF::new(bounds.origin(), bounds.size()), bounds: RectF::new(bounds.origin(), bounds.size()),
background: Some(layout.background_color), background: Some(layout.background_color),
border: Default::default(), border: Default::default(),
@ -752,12 +757,12 @@ impl Element for TerminalElement {
}); });
for rect in &layout.rects { for rect in &layout.rects {
rect.paint(origin, layout, cx) rect.paint(scene, origin, layout, view, cx)
} }
}); });
//Draw Highlighted Backgrounds //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() for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter()
{ {
if let Some((start_y, highlighted_range_lines)) = 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 //Copied from editor. TODO: move to theme or something
corner_radius: 0.15 * layout.size.line_height, corner_radius: 0.15 * layout.size.line_height,
}; };
hr.paint(bounds, cx.scene); hr.paint(bounds, scene);
} }
} }
}); });
//Draw the text cells //Draw the text cells
cx.paint_layer(clip_bounds, |cx| { scene.paint_layer(clip_bounds, |scene| {
for cell in &layout.cells { for cell in &layout.cells {
cell.paint(origin, layout, visible_bounds, cx); cell.paint(scene, origin, layout, visible_bounds, view, cx);
} }
}); });
//Draw cursor //Draw cursor
if self.cursor_visible { if self.cursor_visible {
if let Some(cursor) = &layout.cursor { if let Some(cursor) = &layout.cursor {
cx.paint_layer(clip_bounds, |cx| { scene.paint_layer(clip_bounds, |scene| {
cursor.paint(origin, cx); cursor.paint(scene, origin, cx);
}) })
} }
} }
if let Some(element) = &mut layout.hyperlink_tooltip { 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( fn debug(
&self, &self,
_bounds: gpui::geometry::rect::RectF, _: RectF,
_layout: &Self::LayoutState, _: &Self::LayoutState,
_paint: &Self::PaintState, _: &Self::PaintState,
_cx: &gpui::DebugContext, _: &TerminalView,
_: &gpui::ViewContext<TerminalView>,
) -> gpui::serde_json::Value { ) -> gpui::serde_json::Value {
json!({ json!({
"type": "TerminalElement", "type": "TerminalElement",
@ -821,7 +827,8 @@ impl Element for TerminalElement {
_: RectF, _: RectF,
layout: &Self::LayoutState, layout: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
_: &gpui::MeasurementContext, _: &TerminalView,
_: &gpui::ViewContext<TerminalView>,
) -> Option<RectF> { ) -> Option<RectF> {
// Use the same origin that's passed to `Cursor::paint` in the paint // Use the same origin that's passed to `Cursor::paint` in the paint
// method bove. // method bove.

View file

@ -13,12 +13,12 @@ use context_menu::{ContextMenu, ContextMenuItem};
use dirs::home_dir; use dirs::home_dir;
use gpui::{ use gpui::{
actions, actions,
elements::{AnchorCorner, ChildView, Flex, Label, ParentElement, Stack, Text}, elements::{AnchorCorner, ChildView, Flex, Label, ParentElement, Stack},
geometry::vector::Vector2F, geometry::vector::Vector2F,
impl_actions, impl_internal_actions, impl_actions, impl_internal_actions,
keymap_matcher::{KeymapContext, Keystroke}, keymap_matcher::{KeymapContext, Keystroke},
platform::KeyDownEvent, platform::KeyDownEvent,
AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, Task, View, ViewContext, AnyViewHandle, AppContext, Drawable, Element, Entity, ModelHandle, Task, View, ViewContext,
ViewHandle, WeakViewHandle, ViewHandle, WeakViewHandle,
}; };
use project::{LocalWorktree, Project}; use project::{LocalWorktree, Project};
@ -35,7 +35,7 @@ use terminal::{
}; };
use util::ResultExt; use util::ResultExt;
use workspace::{ use workspace::{
item::{Item, ItemEvent}, item::{BreadcrumbText, Item, ItemEvent},
notifications::NotifyResultExt, notifications::NotifyResultExt,
pane, register_deserializable_item, pane, register_deserializable_item,
searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle}, searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle},
@ -237,11 +237,7 @@ impl TerminalView {
cx.notify(); cx.notify();
} }
pub fn should_show_cursor( pub fn should_show_cursor(&self, focused: bool, cx: &mut gpui::ViewContext<Self>) -> bool {
&self,
focused: bool,
cx: &mut gpui::RenderContext<'_, Self>,
) -> bool {
//Don't blink the cursor when not focused, blinking is disabled, or paused //Don't blink the cursor when not focused, blinking is disabled, or paused
if !focused if !focused
|| !self.blinking_on || !self.blinking_on
@ -284,7 +280,8 @@ impl TerminalView {
async move { async move {
Timer::after(CURSOR_BLINK_INTERVAL).await; Timer::after(CURSOR_BLINK_INTERVAL).await;
if let Some(this) = this.upgrade(&cx) { 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; Timer::after(CURSOR_BLINK_INTERVAL).await;
if let Some(this) = this.upgrade(&cx) { if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx)) this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
.log_err();
} }
} }
}) })
@ -389,19 +387,18 @@ impl View for TerminalView {
"Terminal" "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 terminal_handle = self.terminal.clone().downgrade();
let self_id = cx.view_id(); let self_id = cx.view_id();
let focused = cx let focused = cx
.focused_view_id(cx.window_id()) .focused_view_id()
.filter(|view_id| *view_id == self_id) .filter(|view_id| *view_id == self_id)
.is_some(); .is_some();
Stack::new() Stack::new()
.with_child( .with_child(
TerminalElement::new( TerminalElement::new(
cx.handle(),
terminal_handle, terminal_handle,
focused, focused,
self.should_show_cursor(focused, cx), self.should_show_cursor(focused, cx),
@ -548,12 +545,12 @@ impl Item for TerminalView {
Some(self.terminal().read(cx).title().into()) Some(self.terminal().read(cx).title().into())
} }
fn tab_content( fn tab_content<T: View>(
&self, &self,
_detail: Option<usize>, _detail: Option<usize>,
tab_theme: &theme::Tab, tab_theme: &theme::Tab,
cx: &gpui::AppContext, cx: &gpui::AppContext,
) -> ElementBox { ) -> Element<T> {
let title = self.terminal().read(cx).title(); let title = self.terminal().read(cx).title();
Flex::row() Flex::row()
@ -615,12 +612,11 @@ impl Item for TerminalView {
ToolbarItemLocation::PrimaryLeft { flex: None } ToolbarItemLocation::PrimaryLeft { flex: None }
} }
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> { fn breadcrumbs(&self, _: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
Some(vec![Text::new( Some(vec![BreadcrumbText {
self.terminal().read(cx).breadcrumb_text.clone(), text: self.terminal().read(cx).breadcrumb_text.clone(),
theme.workspace.breadcrumbs.default.text.clone(), highlights: None,
) }])
.boxed()])
} }
fn serialized_item_kind() -> Option<&'static str> { fn serialized_item_kind() -> Option<&'static str> {

View file

@ -11,7 +11,7 @@ use gpui::{
platform, platform,
platform::MouseButton, platform::MouseButton,
scene::MouseClick, scene::MouseClick,
Action, Element, ElementBox, EventContext, MouseState, RenderContext, View, Action, Drawable, Element, EventContext, MouseState, View, ViewContext,
}; };
use serde::Deserialize; use serde::Deserialize;
@ -27,13 +27,13 @@ pub struct CheckboxStyle {
pub hovered_and_checked: ContainerStyle, pub hovered_and_checked: ContainerStyle,
} }
pub fn checkbox<T: 'static, V: View>( pub fn checkbox<Tag: 'static, V: View>(
label: &'static str, label: &'static str,
style: &CheckboxStyle, style: &CheckboxStyle,
checked: bool, checked: bool,
cx: &mut RenderContext<V>, cx: &mut ViewContext<V>,
change: fn(checked: bool, cx: &mut EventContext) -> (), change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
) -> MouseEventHandler<T> { ) -> MouseEventHandler<Tag, V> {
let label = Label::new(label, style.label.text.clone()) let label = Label::new(label, style.label.text.clone())
.contained() .contained()
.with_style(style.label.container) .with_style(style.label.container)
@ -42,14 +42,14 @@ pub fn checkbox<T: 'static, V: View>(
checkbox_with_label(label, style, checked, cx, change) checkbox_with_label(label, style, checked, cx, change)
} }
pub fn checkbox_with_label<T: 'static, V: View>( pub fn checkbox_with_label<Tag: 'static, V: View>(
label: ElementBox, label: Element<V>,
style: &CheckboxStyle, style: &CheckboxStyle,
checked: bool, checked: bool,
cx: &mut RenderContext<V>, cx: &mut ViewContext<V>,
change: fn(checked: bool, cx: &mut EventContext) -> (), change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
) -> MouseEventHandler<T> { ) -> MouseEventHandler<Tag, V> {
MouseEventHandler::<T>::new(0, cx, |state, _| { MouseEventHandler::new(0, cx, |state, _| {
let indicator = if checked { let indicator = if checked {
svg(&style.icon) svg(&style.icon)
} else { } else {
@ -82,7 +82,7 @@ pub fn checkbox_with_label<T: 'static, V: View>(
.align_children_center() .align_children_center()
.boxed() .boxed()
}) })
.on_click(platform::MouseButton::Left, move |_, cx| { .on_click(platform::MouseButton::Left, move |_, _, cx| {
change(!checked, cx) change(!checked, cx)
}) })
.with_cursor_style(platform::CursorStyle::PointingHand) .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()) Svg::new(style.asset.clone())
.with_color(style.color) .with_color(style.color)
.constrained() .constrained()
@ -121,7 +121,7 @@ pub struct IconStyle {
container: ContainerStyle, 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) svg(&style.icon).contained().with_style(style.container)
} }
@ -130,12 +130,11 @@ pub fn keystroke_label<V: View>(
label_style: &ContainedText, label_style: &ContainedText,
keystroke_style: &ContainedText, keystroke_style: &ContainedText,
action: Box<dyn Action>, action: Box<dyn Action>,
cx: &mut RenderContext<V>, cx: &mut ViewContext<V>,
) -> Container { ) -> Container<V> {
// FIXME: Put the theme in it's own global so we can // FIXME: Put the theme in it's own global so we can
// query the keystroke style on our own // query the keystroke style on our own
keystroke_label_for( keystroke_label_for(
cx.window_id(),
cx.handle().id(), cx.handle().id(),
label_text, label_text,
label_style, label_style,
@ -144,14 +143,13 @@ pub fn keystroke_label<V: View>(
) )
} }
pub fn keystroke_label_for( pub fn keystroke_label_for<V: View>(
window_id: usize,
view_id: usize, view_id: usize,
label_text: &'static str, label_text: &'static str,
label_style: &ContainedText, label_style: &ContainedText,
keystroke_style: &ContainedText, keystroke_style: &ContainedText,
action: Box<dyn Action>, action: Box<dyn Action>,
) -> Container { ) -> Container<V> {
Flex::row() Flex::row()
.with_child( .with_child(
Label::new(label_text, label_style.text.clone()) Label::new(label_text, label_style.text.clone())
@ -160,7 +158,6 @@ pub fn keystroke_label_for(
) )
.with_child({ .with_child({
KeystrokeLabel::new( KeystrokeLabel::new(
window_id,
view_id, view_id,
action, action,
keystroke_style.container, keystroke_style.container,
@ -180,32 +177,33 @@ pub fn cta_button<L, A, V>(
action: A, action: A,
max_width: f32, max_width: f32,
style: &ButtonStyle, style: &ButtonStyle,
cx: &mut RenderContext<V>, cx: &mut ViewContext<V>,
) -> ElementBox ) -> Element<V>
where where
L: Into<Cow<'static, str>>, L: Into<Cow<'static, str>>,
A: 'static + Action + Clone, A: 'static + Action + Clone,
V: View, 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()) cx.dispatch_action(action.clone())
}) })
.boxed() .boxed()
} }
pub fn cta_button_with_click<L, V, F>( pub fn cta_button_with_click<Tag, L, V, F>(
label: L, label: L,
max_width: f32, max_width: f32,
style: &ButtonStyle, style: &ButtonStyle,
cx: &mut RenderContext<V>, cx: &mut ViewContext<V>,
f: F, f: F,
) -> MouseEventHandler<F> ) -> MouseEventHandler<Tag, V>
where where
Tag: 'static,
L: Into<Cow<'static, str>>, L: Into<Cow<'static, str>>,
V: View, 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); let style = style.style_for(state, false);
Label::new(label, style.text.to_owned()) Label::new(label, style.text.to_owned())
.aligned() .aligned()
@ -234,16 +232,17 @@ impl ModalStyle {
} }
} }
pub fn modal<V, I, F>( pub fn modal<Tag, V, I, F>(
title: I, title: I,
style: &ModalStyle, style: &ModalStyle,
cx: &mut RenderContext<V>, cx: &mut ViewContext<V>,
build_modal: F, build_modal: F,
) -> ElementBox ) -> Element<V>
where where
Tag: 'static,
V: View, V: View,
I: Into<Cow<'static, str>>, 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.; const TITLEBAR_HEIGHT: f32 = 28.;
// let active = cx.window_is_active(cx.window_id()); // let active = cx.window_is_active(cx.window_id());
@ -261,13 +260,12 @@ where
) )
.boxed(), .boxed(),
// FIXME: Get a better tag type // 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); let style = style.close_icon.style_for(state, false);
icon(style).boxed() icon(style).boxed()
}) })
.on_click(platform::MouseButton::Left, move |_, cx| { .on_click(platform::MouseButton::Left, move |_, _, cx| {
let window_id = cx.window_id(); cx.remove_window();
cx.remove_window(window_id);
}) })
.with_cursor_style(platform::CursorStyle::PointingHand) .with_cursor_style(platform::CursorStyle::PointingHand)
.aligned() .aligned()

View file

@ -1,45 +1,57 @@
use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{actions, elements::*, AppContext, Drawable, Element, MouseState, ViewContext};
actions, elements::*, AnyViewHandle, AppContext, Element, ElementBox, Entity, MouseState, use picker::{Picker, PickerDelegate, PickerEvent};
RenderContext, View, ViewContext, ViewHandle,
};
use picker::{Picker, PickerDelegate};
use settings::{settings_file::SettingsFile, Settings}; use settings::{settings_file::SettingsFile, Settings};
use staff_mode::StaffMode; use staff_mode::StaffMode;
use std::sync::Arc; use std::sync::Arc;
use theme::{Theme, ThemeMeta, ThemeRegistry}; use theme::{Theme, ThemeMeta, ThemeRegistry};
use util::ResultExt;
use workspace::{AppState, Workspace}; 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]); actions!(theme_selector, [Toggle, Reload]);
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) { pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
Picker::<ThemeSelector>::init(cx);
cx.add_action({ cx.add_action({
let theme_registry = app_state.themes.clone(); let theme_registry = app_state.themes.clone();
move |workspace, _: &Toggle, cx| { move |workspace, _: &Toggle, cx| toggle(workspace, theme_registry.clone(), cx)
ThemeSelector::toggle(workspace, theme_registry.clone(), cx) });
} ThemeSelector::init(cx);
}
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))
}); });
} }
pub enum Event { #[cfg(debug_assertions)]
Dismissed, 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(&current_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)
}
}
} }
impl ThemeSelector { pub type ThemeSelector = Picker<ThemeSelectorDelegate>;
fn new(registry: Arc<ThemeRegistry>, cx: &mut ViewContext<Self>) -> Self {
let handle = cx.weak_handle(); pub struct ThemeSelectorDelegate {
let picker = cx.add_view(|cx| Picker::new("Select Theme...", handle, cx)); 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 settings = cx.global::<Settings>();
let original_theme = settings.theme.clone(); let original_theme = settings.theme.clone();
@ -61,7 +73,6 @@ impl ThemeSelector {
registry, registry,
theme_data: theme_names, theme_data: theme_names,
matches, matches,
picker,
original_theme: original_theme.clone(), original_theme: original_theme.clone(),
selected_index: 0, selected_index: 0,
selection_completed: false, selection_completed: false,
@ -70,34 +81,7 @@ impl ThemeSelector {
this this
} }
fn toggle( fn show_selected_theme(&mut self, cx: &mut ViewContext<ThemeSelector>) {
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(&current_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>) {
if let Some(mat) = self.matches.get(self.selected_index) { if let Some(mat) = self.matches.get(self.selected_index) {
match self.registry.get(&mat.string) { match self.registry.get(&mat.string) {
Ok(theme) => { Ok(theme) => {
@ -118,19 +102,6 @@ impl ThemeSelector {
.unwrap_or(self.selected_index); .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) { fn set_theme(theme: Arc<Theme>, cx: &mut AppContext) {
cx.update_global::<Settings, _, _>(|settings, cx| { cx.update_global::<Settings, _, _>(|settings, cx| {
settings.theme = theme; 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 { fn match_count(&self) -> usize {
self.matches.len() self.matches.len()
} }
fn confirm(&mut self, cx: &mut ViewContext<Self>) { fn confirm(&mut self, cx: &mut ViewContext<ThemeSelector>) {
self.selection_completed = true; self.selection_completed = true;
let theme_name = cx.global::<Settings>().theme.meta.name.clone(); let theme_name = cx.global::<Settings>().theme.meta.name.clone();
@ -152,27 +127,30 @@ impl PickerDelegate for ThemeSelector {
settings_content.theme = Some(theme_name); 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 { if !self.selection_completed {
Self::set_theme(self.original_theme.clone(), cx); Self::set_theme(self.original_theme.clone(), cx);
self.selection_completed = true; self.selection_completed = true;
} }
cx.emit(Event::Dismissed);
} }
fn selected_index(&self) -> usize { fn selected_index(&self) -> usize {
self.selected_index 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.selected_index = ix;
self.show_selected_theme(cx); 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 background = cx.background().clone();
let candidates = self let candidates = self
.theme_data .theme_data
@ -185,7 +163,7 @@ impl PickerDelegate for ThemeSelector {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
cx.spawn(|this, mut cx| async move { cx.spawn_weak(|this, mut cx| async move {
let matches = if query.is_empty() { let matches = if query.is_empty() {
candidates candidates
.into_iter() .into_iter()
@ -209,14 +187,17 @@ impl PickerDelegate for ThemeSelector {
.await .await
}; };
this.update(&mut cx, |this, cx| { if let Some(this) = this.upgrade(&cx) {
this.matches = matches; this.update(&mut cx, |this, cx| {
this.selected_index = this let delegate = this.delegate_mut();
.selected_index delegate.matches = matches;
.min(this.matches.len().saturating_sub(1)); delegate.selected_index = delegate
this.show_selected_theme(cx); .selected_index
cx.notify(); .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, mouse_state: &mut MouseState,
selected: bool, selected: bool,
cx: &AppContext, cx: &AppContext,
) -> ElementBox { ) -> Element<Picker<Self>> {
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
let theme = &settings.theme; let theme = &settings.theme;
let theme_match = &self.matches[ix]; let theme_match = &self.matches[ix];
@ -239,29 +220,3 @@ impl PickerDelegate for ThemeSelector {
.boxed() .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);
}
}
}

Some files were not shown because too many files have changed in this diff Show more