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",
"settings",
"theme",
"util",
"workspace",
]

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
use gpui::{
elements::*, platform::MouseButton, AppContext, Entity, RenderContext, Subscription, View,
ViewContext, ViewHandle,
elements::*, platform::MouseButton, AppContext, Entity, Subscription, View, ViewContext,
ViewHandle,
};
use itertools::Itertools;
use search::ProjectSearchView;
@ -41,7 +41,7 @@ impl View for Breadcrumbs {
"Breadcrumbs"
}
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let active_item = match &self.active_item {
Some(active_item) => active_item,
None => return Empty::new().boxed(),
@ -54,10 +54,22 @@ impl View for Breadcrumbs {
let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
Some(breadcrumbs) => breadcrumbs,
None => return Empty::new().boxed(),
};
}
.into_iter()
.map(|breadcrumb| {
let text = Text::new(
breadcrumb.text,
theme.workspace.breadcrumbs.default.text.clone(),
);
if let Some(highlights) = breadcrumb.highlights {
text.with_highlights(highlights).boxed()
} else {
text.boxed()
}
});
let crumbs = Flex::row()
.with_children(Itertools::intersperse_with(breadcrumbs.into_iter(), || {
.with_children(Itertools::intersperse_with(breadcrumbs, || {
Label::new("", style.default.text.clone()).boxed()
}))
.constrained()
@ -72,14 +84,14 @@ impl View for Breadcrumbs {
.boxed();
}
MouseEventHandler::<Breadcrumbs>::new(0, cx, |state, _| {
MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
let style = style.style_for(state, false);
crumbs.with_style(style.container).boxed()
})
.on_click(MouseButton::Left, |_, cx| {
.on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(outline::Toggle);
})
.with_tooltip::<Breadcrumbs, _>(
.with_tooltip::<Breadcrumbs>(
0,
"Show symbol outline".to_owned(),
Some(Box::new(outline::Toggle)),
@ -136,7 +148,7 @@ impl ToolbarItemView for Breadcrumbs {
}
}
fn pane_focus_update(&mut self, pane_focused: bool, _: &mut gpui::AppContext) {
fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
self.pane_focused = pane_focused;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

@ -78,8 +78,18 @@ pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform,
move |action| {
let mut cx = cx.borrow_mut();
if let Some(main_window_id) = cx.platform.main_window_id() {
if let Some(view_id) = cx.focused_view_id(main_window_id) {
cx.handle_dispatch_action_from_effect(main_window_id, Some(view_id), action);
let dispatched = cx
.update_window(main_window_id, |cx| {
if let Some(view_id) = cx.focused_view_id() {
cx.handle_dispatch_action_from_effect(Some(view_id), action);
true
} else {
false
}
})
.unwrap_or(false);
if dispatched {
return;
}
}

View file

@ -1,6 +1,6 @@
use std::{
any::Any,
cell::RefCell,
marker::PhantomData,
mem,
path::PathBuf,
rc::Rc,
@ -21,10 +21,10 @@ use crate::{
geometry::vector::Vector2F,
keymap_matcher::Keystroke,
platform,
platform::{Appearance, Event, InputHandler, KeyDownEvent, Platform},
platform::{Event, InputHandler, KeyDownEvent, Platform},
Action, AnyViewHandle, AppContext, Entity, FontCache, Handle, ModelContext, ModelHandle,
ReadModelWith, ReadViewWith, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext,
ViewHandle, WeakHandle,
ReadModelWith, ReadViewWith, Subscription, Task, UpdateModel, UpdateView, View, ViewContext,
ViewHandle, WeakHandle, WindowContext,
};
use collections::BTreeMap;
@ -75,7 +75,7 @@ impl TestAppContext {
pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) {
let mut cx = self.cx.borrow_mut();
if let Some(view_id) = cx.focused_view_id(window_id) {
if let Some(view_id) = cx.windows.get(&window_id).and_then(|w| w.focused_view_id) {
cx.handle_dispatch_action_from_effect(window_id, Some(view_id), &action);
}
}
@ -85,31 +85,27 @@ impl TestAppContext {
}
pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
let handled = self.cx.borrow_mut().update(|cx| {
let presenter = cx
.presenters_and_platform_windows
.get(&window_id)
.unwrap()
.0
.clone();
if cx.dispatch_keystroke(window_id, &keystroke) {
let handled = self
.cx
.borrow_mut()
.update_window(window_id, |cx| {
if cx.dispatch_keystroke(&keystroke) {
return true;
}
if presenter.borrow_mut().dispatch_event(
if cx.dispatch_event(
Event::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(),
is_held,
}),
false,
cx,
) {
return true;
}
false
});
})
.unwrap_or(false);
if !handled && !keystroke.cmd && !keystroke.ctrl {
WindowInputHandler {
@ -120,6 +116,22 @@ impl TestAppContext {
}
}
pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
&self,
window_id: usize,
callback: F,
) -> Option<T> {
self.cx.borrow().read_window(window_id, callback)
}
pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
window_id: usize,
callback: F,
) -> Option<T> {
self.cx.borrow_mut().update_window(window_id, callback)
}
pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
where
T: Entity,
@ -149,12 +161,28 @@ impl TestAppContext {
self.cx.borrow_mut().add_view(parent_handle, build_view)
}
pub fn window_ids(&self) -> Vec<usize> {
self.cx.borrow().window_ids().collect()
pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
where
E: Any,
F: 'static + FnMut(&mut AppContext),
{
self.cx.borrow_mut().observe_global::<E, F>(callback)
}
pub fn root_view(&self, window_id: usize) -> Option<AnyViewHandle> {
self.cx.borrow().root_view(window_id)
pub fn set_global<T: 'static>(&mut self, state: T) {
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 {
@ -172,27 +200,6 @@ impl TestAppContext {
result
}
pub fn render<F, V, T>(&mut self, handle: &ViewHandle<V>, f: F) -> T
where
F: FnOnce(&mut V, &mut RenderContext<V>) -> T,
V: View,
{
handle.update(&mut *self.cx.borrow_mut(), |view, cx| {
let mut render_cx = RenderContext {
app: cx,
window_id: handle.window_id(),
view_id: handle.id(),
view_type: PhantomData,
titlebar_height: 0.,
hovered_region_ids: Default::default(),
clicked_region_ids: None,
refreshing: false,
appearance: Appearance::Light,
};
f(view, &mut render_cx)
})
}
pub fn to_async(&self) -> AsyncAppContext {
AsyncAppContext(self.cx.clone())
}
@ -245,7 +252,7 @@ impl TestAppContext {
use postage::prelude::Sink as _;
let mut done_tx = self
.window_mut(window_id)
.platform_window_mut(window_id)
.pending_prompts
.borrow_mut()
.pop_front()
@ -254,20 +261,23 @@ impl TestAppContext {
}
pub fn has_pending_prompt(&self, window_id: usize) -> bool {
let window = self.window_mut(window_id);
let window = self.platform_window_mut(window_id);
let prompts = window.pending_prompts.borrow_mut();
!prompts.is_empty()
}
pub fn current_window_title(&self, window_id: usize) -> Option<String> {
self.window_mut(window_id).title.clone()
self.platform_window_mut(window_id).title.clone()
}
pub fn simulate_window_close(&self, window_id: usize) -> bool {
let handler = self.window_mut(window_id).should_close_handler.take();
let handler = self
.platform_window_mut(window_id)
.should_close_handler
.take();
if let Some(mut handler) = handler {
let should_close = handler();
self.window_mut(window_id).should_close_handler = Some(handler);
self.platform_window_mut(window_id).should_close_handler = Some(handler);
should_close
} else {
false
@ -275,47 +285,37 @@ impl TestAppContext {
}
pub fn simulate_window_resize(&self, window_id: usize, size: Vector2F) {
let mut window = self.window_mut(window_id);
let mut window = self.platform_window_mut(window_id);
window.size = size;
let mut handlers = mem::take(&mut window.resize_handlers);
drop(window);
for handler in &mut handlers {
handler();
}
self.window_mut(window_id).resize_handlers = handlers;
self.platform_window_mut(window_id).resize_handlers = handlers;
}
pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
let mut handlers = BTreeMap::new();
{
let mut cx = self.cx.borrow_mut();
for (window_id, (_, window)) in &mut cx.presenters_and_platform_windows {
let window = window
.as_any_mut()
.downcast_mut::<platform::test::Window>()
.unwrap();
handlers.insert(
*window_id,
mem::take(&mut window.active_status_change_handlers),
);
}
};
let mut handlers = handlers.into_iter().collect::<Vec<_>>();
handlers.sort_unstable_by_key(|(window_id, _)| Some(*window_id) == to_activate);
self.cx.borrow_mut().update(|cx| {
let other_window_ids = cx
.windows
.keys()
.filter(|window_id| Some(**window_id) != to_activate)
.copied()
.collect::<Vec<_>>();
for (window_id, mut window_handlers) in handlers {
for window_handler in &mut window_handlers {
window_handler(Some(window_id) == to_activate);
for window_id in other_window_ids {
cx.window_changed_active_status(window_id, false)
}
self.window_mut(window_id)
.active_status_change_handlers
.extend(window_handlers);
if let Some(to_activate) = to_activate {
cx.window_changed_active_status(to_activate, true)
}
});
}
pub fn is_window_edited(&self, window_id: usize) -> bool {
self.window_mut(window_id).edited
self.platform_window_mut(window_id).edited
}
pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
@ -338,13 +338,11 @@ impl TestAppContext {
self.assert_dropped(weak);
}
fn window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
fn platform_window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
std::cell::RefMut::map(self.cx.borrow_mut(), |state| {
let (_, window) = state
.presenters_and_platform_windows
.get_mut(&window_id)
.unwrap();
let window = state.windows.get_mut(&window_id).unwrap();
let test_window = window
.platform_window
.as_any_mut()
.downcast_mut::<platform::test::Window>()
.unwrap();
@ -406,6 +404,8 @@ impl ReadModelWith for TestAppContext {
}
impl UpdateView for TestAppContext {
type Output<S> = S;
fn update_view<T, S>(
&mut self,
handle: &ViewHandle<T>,
@ -414,7 +414,10 @@ impl UpdateView for TestAppContext {
where
T: View,
{
self.cx.borrow_mut().update_view(handle, update)
self.cx
.borrow_mut()
.update_window(handle.window_id, |cx| cx.update_view(handle, update))
.unwrap()
}
}
@ -579,22 +582,20 @@ impl<T: View> ViewHandle<T> {
let timeout_duration = cx.condition_duration();
let mut cx = cx.cx.borrow_mut();
let subscriptions = self.update(&mut *cx, |_, cx| {
(
let subscriptions = (
cx.observe(self, {
let mut tx = tx.clone();
move |_, _, _| {
move |_, _| {
tx.blocking_send(()).ok();
}
}),
cx.subscribe(self, {
let mut tx = tx.clone();
move |_, _, _, _| {
move |_, _, _| {
tx.blocking_send(()).ok();
}
}),
)
});
);
let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
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 crate::{platform::InputHandler, AnyView, AppContext};
use crate::{platform::InputHandler, window::WindowContext, AnyView, AppContext};
pub struct WindowInputHandler {
pub app: Rc<RefCell<AppContext>>,
@ -12,7 +12,7 @@ pub struct WindowInputHandler {
impl WindowInputHandler {
fn read_focused_view<T, F>(&self, f: F) -> Option<T>
where
F: FnOnce(&dyn AnyView, &AppContext) -> T,
F: FnOnce(&dyn AnyView, &WindowContext) -> T,
{
// Input-related application hooks are sometimes called by the OS during
// a call to a window-manipulation API, like prompting the user for file
@ -20,26 +20,26 @@ impl WindowInputHandler {
// InputHandler methods need to fail gracefully.
//
// See https://github.com/zed-industries/community/issues/444
let app = self.app.try_borrow().ok()?;
let view_id = app.focused_view_id(self.window_id)?;
let view = app.views.get(&(self.window_id, view_id))?;
let result = f(view.as_ref(), &app);
let mut app = self.app.try_borrow_mut().ok()?;
app.update_window(self.window_id, |cx| {
let view_id = cx.window.focused_view_id?;
let view = cx.views.get(&(self.window_id, view_id))?;
let result = f(view.as_ref(), &cx);
Some(result)
})
.flatten()
}
fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
where
F: FnOnce(usize, usize, &mut dyn AnyView, &mut AppContext) -> T,
F: FnOnce(&mut dyn AnyView, &mut WindowContext, usize) -> T,
{
let mut app = self.app.try_borrow_mut().ok()?;
app.update(|app| {
let view_id = app.focused_view_id(self.window_id)?;
let mut view = app.views.remove(&(self.window_id, view_id))?;
let result = f(self.window_id, view_id, view.as_mut(), &mut *app);
app.views.insert((self.window_id, view_id), view);
Some(result)
app.update_window(self.window_id, |cx| {
let view_id = cx.window.focused_view_id?;
cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
})
.flatten()
}
}
@ -55,8 +55,8 @@ impl InputHandler for WindowInputHandler {
}
fn replace_text_in_range(&mut self, range: Option<Range<usize>>, text: &str) {
self.update_focused_view(|window_id, view_id, view, cx| {
view.replace_text_in_range(range, text, cx, window_id, view_id);
self.update_focused_view(|view, cx, view_id| {
view.replace_text_in_range(range, text, cx, view_id);
});
}
@ -66,8 +66,8 @@ impl InputHandler for WindowInputHandler {
}
fn unmark_text(&mut self) {
self.update_focused_view(|window_id, view_id, view, cx| {
view.unmark_text(cx, window_id, view_id);
self.update_focused_view(|view, cx, view_id| {
view.unmark_text(cx, view_id);
});
}
@ -77,22 +77,15 @@ impl InputHandler for WindowInputHandler {
new_text: &str,
new_selected_range: Option<Range<usize>>,
) {
self.update_focused_view(|window_id, view_id, view, cx| {
view.replace_and_mark_text_in_range(
range,
new_text,
new_selected_range,
cx,
window_id,
view_id,
);
self.update_focused_view(|view, cx, view_id| {
view.replace_and_mark_text_in_range(range, new_text, new_selected_range, cx, view_id);
});
}
fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
let app = self.app.borrow();
let (presenter, _) = app.presenters_and_platform_windows.get(&self.window_id)?;
let presenter = presenter.borrow();
presenter.rect_for_text_range(range_utf16, &app)
self.app
.borrow_mut()
.update_window(self.window_id, |cx| cx.rect_for_text_range(range_utf16))
.flatten()
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -601,7 +601,7 @@ impl platform::Platform for MacPlatform {
Window::main_window_id()
}
fn add_status_item(&self) -> Box<dyn platform::Window> {
fn add_status_item(&self, _id: usize) -> Box<dyn platform::Window> {
Box::new(StatusItem::add(self.fonts()))
}

View file

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

File diff suppressed because it is too large Load diff

View file

@ -236,6 +236,19 @@ impl SceneBuilder {
self.scale_factor
}
pub fn paint_stacking_context<F>(
&mut self,
clip_bounds: Option<RectF>,
z_index: Option<usize>,
f: F,
) where
F: FnOnce(&mut Self),
{
self.push_stacking_context(clip_bounds, z_index);
f(self);
self.pop_stacking_context();
}
pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>, z_index: Option<usize>) {
let z_index = z_index.unwrap_or_else(|| self.active_stacking_context().z_index + 1);
self.active_stacking_context_stack
@ -249,6 +262,15 @@ impl SceneBuilder {
assert!(!self.active_stacking_context_stack.is_empty());
}
pub fn paint_layer<F>(&mut self, clip_bounds: Option<RectF>, f: F)
where
F: FnOnce(&mut Self),
{
self.push_layer(clip_bounds);
f(self);
self.pop_layer();
}
pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {
self.active_stacking_context().push_layer(clip_bounds);
}

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

View file

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

View file

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

View file

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

View file

@ -174,24 +174,61 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
}
}
} else {
// Pass to the test function the number of app contexts that it needs,
// based on its parameter list.
let mut cx_vars = proc_macro2::TokenStream::new();
let mut cx_teardowns = proc_macro2::TokenStream::new();
let mut inner_fn_args = proc_macro2::TokenStream::new();
for arg in inner_fn.sig.inputs.iter() {
for (ix, arg) in inner_fn.sig.inputs.iter().enumerate() {
if let FnArg::Typed(arg) = arg {
if let Type::Path(ty) = &*arg.ty {
let last_segment = ty.path.segments.last();
if let Some("StdRng") = last_segment.map(|s| s.ident.to_string()).as_deref() {
inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(seed),));
continue;
}
} else {
} 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;
}
} else {
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;
}
_ => {}
}
}
}
}
return TokenStream::from(
syn::Error::new_spanned(arg, "invalid argument").into_compile_error(),
);
}
}
parse_quote! {
#[test]
@ -203,7 +240,11 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
#starting_seed as u64,
#max_retries,
#detect_nondeterminism,
&mut |cx, _, _, seed| #inner_fn_name(#inner_fn_args),
&mut |cx, foreground_platform, deterministic, seed| {
#cx_vars
#inner_fn_name(#inner_fn_args);
#cx_teardowns
},
#on_failure_fn_name,
stringify!(#outer_fn_name).to_string(),
);

View file

@ -7,7 +7,6 @@ use std::{
path::{Path, PathBuf},
sync::Arc,
};
use util::TryFutureExt as _;
use workspace::AppState;
actions!(journal, [NewJournalEntry]);
@ -44,17 +43,16 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
Ok::<_, std::io::Error>((journal_dir, entry_path))
});
cx.spawn(|mut cx| {
async move {
cx.spawn(|mut cx| async move {
let (journal_dir, entry_path) = create_entry.await?;
let (workspace, _) = cx
.update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))
.await;
.await?;
let opened = workspace
.update(&mut cx, |workspace, cx| {
workspace.open_paths(vec![entry_path], true, cx)
})
})?
.await;
if let Some(Some(Ok(item))) = opened.first() {
@ -69,15 +67,13 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
}
editor.insert(&entry_heading, cx);
editor.insert("\n\n", cx);
});
})?;
}
}
anyhow::Ok(())
}
.log_err()
})
.detach();
.detach_and_log_err(cx);
}
fn journal_dir(settings: &Settings) -> Option<PathBuf> {

View file

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

View file

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

View file

@ -1,55 +1,64 @@
mod active_buffer_language;
pub use active_buffer_language::ActiveBufferLanguage;
use anyhow::anyhow;
use editor::Editor;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
RenderContext, View, ViewContext, ViewHandle,
};
use gpui::{actions, elements::*, AppContext, ModelHandle, MouseState, ViewContext};
use language::{Buffer, LanguageRegistry};
use picker::{Picker, PickerDelegate};
use picker::{Picker, PickerDelegate, PickerEvent};
use project::Project;
use settings::Settings;
use std::sync::Arc;
use util::ResultExt;
use workspace::{AppState, Workspace};
actions!(language_selector, [Toggle]);
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
Picker::<LanguageSelector>::init(cx);
Picker::<LanguageSelectorDelegate>::init(cx);
cx.add_action({
let language_registry = app_state.languages.clone();
move |workspace, _: &Toggle, cx| {
LanguageSelector::toggle(workspace, language_registry.clone(), cx)
}
move |workspace, _: &Toggle, cx| toggle(workspace, language_registry.clone(), cx)
});
}
pub enum Event {
Dismissed,
fn toggle(
workspace: &mut Workspace,
registry: Arc<LanguageRegistry>,
cx: &mut ViewContext<Workspace>,
) -> Option<()> {
let (_, buffer, _) = workspace
.active_item(cx)?
.act_as::<Editor>(cx)?
.read(cx)
.active_excerpt(cx)?;
workspace.toggle_modal(cx, |workspace, cx| {
cx.add_view(|cx| {
Picker::new(
LanguageSelectorDelegate::new(buffer, workspace.project().clone(), registry),
cx,
)
})
});
Some(())
}
pub struct LanguageSelector {
pub struct LanguageSelectorDelegate {
buffer: ModelHandle<Buffer>,
project: ModelHandle<Project>,
language_registry: Arc<LanguageRegistry>,
candidates: Vec<StringMatchCandidate>,
matches: Vec<StringMatch>,
picker: ViewHandle<Picker<Self>>,
selected_index: usize,
}
impl LanguageSelector {
impl LanguageSelectorDelegate {
fn new(
buffer: ModelHandle<Buffer>,
project: ModelHandle<Project>,
language_registry: Arc<LanguageRegistry>,
cx: &mut ViewContext<Self>,
) -> Self {
let handle = cx.weak_handle();
let picker = cx.add_view(|cx| Picker::new("Select Language...", handle, cx));
let candidates = language_registry
.language_names()
.into_iter()
@ -73,104 +82,63 @@ impl LanguageSelector {
language_registry,
candidates,
matches,
picker,
selected_index: 0,
}
}
fn toggle(
workspace: &mut Workspace,
registry: Arc<LanguageRegistry>,
cx: &mut ViewContext<Workspace>,
) {
if let Some((_, buffer, _)) = workspace
.active_item(cx)
.and_then(|active_item| active_item.act_as::<Editor>(cx))
.and_then(|editor| editor.read(cx).active_excerpt(cx))
{
workspace.toggle_modal(cx, |workspace, cx| {
let project = workspace.project().clone();
let this = cx.add_view(|cx| Self::new(buffer, project, registry, cx));
cx.subscribe(&this, Self::on_event).detach();
this
});
}
}
fn on_event(
workspace: &mut Workspace,
_: ViewHandle<LanguageSelector>,
event: &Event,
cx: &mut ViewContext<Workspace>,
) {
match event {
Event::Dismissed => {
workspace.dismiss_modal(cx);
}
}
}
impl PickerDelegate for LanguageSelectorDelegate {
fn placeholder_text(&self) -> Arc<str> {
"Select a language...".into()
}
impl Entity for LanguageSelector {
type Event = Event;
}
impl View for LanguageSelector {
fn ui_name() -> &'static str {
"LanguageSelector"
}
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
ChildView::new(&self.picker, cx).boxed()
}
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
if cx.is_self_focused() {
cx.focus(&self.picker);
}
}
}
impl PickerDelegate for LanguageSelector {
fn match_count(&self) -> usize {
self.matches.len()
}
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
if let Some(mat) = self.matches.get(self.selected_index) {
let language_name = &self.candidates[mat.candidate_id].string;
let language = self.language_registry.language_for_name(language_name);
cx.spawn(|this, mut cx| async move {
let project = self.project.downgrade();
let buffer = self.buffer.downgrade();
cx.spawn_weak(|_, mut cx| async move {
let language = language.await?;
this.update(&mut cx, |this, cx| {
this.project.update(cx, |project, cx| {
project.set_language_for_buffer(&this.buffer, language, cx);
});
let project = project
.upgrade(&cx)
.ok_or_else(|| anyhow!("project was dropped"))?;
let buffer = buffer
.upgrade(&cx)
.ok_or_else(|| anyhow!("buffer was dropped"))?;
project.update(&mut cx, |project, cx| {
project.set_language_for_buffer(&buffer, language, cx);
});
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
cx.emit(Event::Dismissed);
cx.emit(PickerEvent::Dismiss);
}
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
cx.emit(Event::Dismissed);
}
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
fn update_matches(
&mut self,
query: String,
cx: &mut ViewContext<Picker<Self>>,
) -> gpui::Task<()> {
let background = cx.background().clone();
let candidates = self.candidates.clone();
cx.spawn(|this, mut cx| async move {
cx.spawn_weak(|this, mut cx| async move {
let matches = if query.is_empty() {
candidates
.into_iter()
@ -194,13 +162,17 @@ impl PickerDelegate for LanguageSelector {
.await
};
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| {
this.matches = matches;
this.selected_index = this
let delegate = this.delegate_mut();
delegate.matches = matches;
delegate.selected_index = delegate
.selected_index
.min(this.matches.len().saturating_sub(1));
.min(delegate.matches.len().saturating_sub(1));
cx.notify();
});
})
.log_err();
}
})
}
@ -210,7 +182,7 @@ impl PickerDelegate for LanguageSelector {
mouse_state: &mut MouseState,
selected: bool,
cx: &AppContext,
) -> ElementBox {
) -> Element<Picker<Self>> {
let settings = cx.global::<Settings>();
let theme = &settings.theme;
let mat = &self.matches[ix];

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ use std::path::Path;
use fuzzy::StringMatch;
use gpui::{
elements::{Label, LabelStyle},
Element, ElementBox,
Drawable, Element, View,
};
use workspace::WorkspaceLocation;
@ -42,7 +42,7 @@ impl HighlightedText {
}
}
pub fn render(self, style: impl Into<LabelStyle>) -> ElementBox {
pub fn render<V: View>(self, style: impl Into<LabelStyle>) -> Element<V> {
Label::new(self.text, style)
.with_highlights(self.highlight_positions)
.boxed()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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