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:
Generated
+1
@@ -3371,6 +3371,7 @@ dependencies = [
|
||||
"project",
|
||||
"settings",
|
||||
"theme",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
|
||||
use editor::Editor;
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
actions,
|
||||
actions, anyhow,
|
||||
elements::*,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Action, AppContext, Entity, ModelHandle, RenderContext, View, ViewContext, ViewHandle,
|
||||
Action, AppContext, Entity, ModelHandle, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus};
|
||||
use project::{LanguageServerProgress, Project};
|
||||
@@ -73,11 +73,12 @@ impl ActivityIndicator {
|
||||
status: event,
|
||||
});
|
||||
cx.notify();
|
||||
});
|
||||
})?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
cx.observe(&project, |_, _, cx| cx.notify()).detach();
|
||||
@@ -172,7 +173,7 @@ impl ActivityIndicator {
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn content_to_render(&mut self, cx: &mut RenderContext<Self>) -> Content {
|
||||
fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Content {
|
||||
// Show any language server has pending activity.
|
||||
let mut pending_work = self.pending_language_server_work(cx);
|
||||
if let Some(PendingWork {
|
||||
@@ -314,14 +315,14 @@ impl View for ActivityIndicator {
|
||||
"ActivityIndicator"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let Content {
|
||||
icon,
|
||||
message,
|
||||
action,
|
||||
} = self.content_to_render(cx);
|
||||
|
||||
let mut element = MouseEventHandler::<Self>::new(0, cx, |state, cx| {
|
||||
let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
|
||||
let theme = &cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
@@ -361,7 +362,7 @@ impl View for ActivityIndicator {
|
||||
if let Some(action) = action {
|
||||
element = element
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_any_action(action.boxed_clone())
|
||||
});
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ pub fn notify_of_any_new_update(
|
||||
.read(cx)
|
||||
.set_should_show_update_notification(false, cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
})?;
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::ViewReleaseNotes;
|
||||
use gpui::{
|
||||
elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
|
||||
platform::{AppVersion, CursorStyle, MouseButton},
|
||||
Element, Entity, View, ViewContext,
|
||||
Drawable, Entity, View, ViewContext,
|
||||
};
|
||||
use menu::Cancel;
|
||||
use settings::Settings;
|
||||
@@ -26,13 +26,13 @@ impl View for UpdateNotification {
|
||||
"UpdateNotification"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::Element<Self> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = &theme.update_notification;
|
||||
|
||||
let app_name = cx.global::<ReleaseChannel>().display_name();
|
||||
|
||||
MouseEventHandler::<ViewReleaseNotes>::new(0, cx, |state, cx| {
|
||||
MouseEventHandler::<ViewReleaseNotes, _>::new(0, cx, |state, cx| {
|
||||
Flex::column()
|
||||
.with_child(
|
||||
Flex::row()
|
||||
@@ -50,7 +50,7 @@ impl View for UpdateNotification {
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Cancel>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
|
||||
let style = theme.dismiss_button.style_for(state, false);
|
||||
Svg::new("icons/x_mark_8.svg")
|
||||
.with_color(style.color)
|
||||
@@ -65,7 +65,9 @@ impl View for UpdateNotification {
|
||||
.boxed()
|
||||
})
|
||||
.with_padding(Padding::uniform(5.))
|
||||
.on_click(MouseButton::Left, move |_, cx| cx.dispatch_action(Cancel))
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(Cancel)
|
||||
})
|
||||
.aligned()
|
||||
.constrained()
|
||||
.with_height(cx.font_cache().line_height(theme.message.text.font_size))
|
||||
@@ -87,7 +89,7 @@ impl View for UpdateNotification {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, |_, cx| {
|
||||
.on_click(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(ViewReleaseNotes)
|
||||
})
|
||||
.boxed()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{
|
||||
elements::*, platform::MouseButton, AppContext, Entity, RenderContext, Subscription, View,
|
||||
ViewContext, ViewHandle,
|
||||
elements::*, platform::MouseButton, AppContext, Entity, Subscription, View, ViewContext,
|
||||
ViewHandle,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use search::ProjectSearchView;
|
||||
@@ -41,7 +41,7 @@ impl View for Breadcrumbs {
|
||||
"Breadcrumbs"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let active_item = match &self.active_item {
|
||||
Some(active_item) => active_item,
|
||||
None => return Empty::new().boxed(),
|
||||
@@ -54,10 +54,22 @@ impl View for Breadcrumbs {
|
||||
let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
|
||||
Some(breadcrumbs) => breadcrumbs,
|
||||
None => return Empty::new().boxed(),
|
||||
};
|
||||
}
|
||||
.into_iter()
|
||||
.map(|breadcrumb| {
|
||||
let text = Text::new(
|
||||
breadcrumb.text,
|
||||
theme.workspace.breadcrumbs.default.text.clone(),
|
||||
);
|
||||
if let Some(highlights) = breadcrumb.highlights {
|
||||
text.with_highlights(highlights).boxed()
|
||||
} else {
|
||||
text.boxed()
|
||||
}
|
||||
});
|
||||
|
||||
let crumbs = Flex::row()
|
||||
.with_children(Itertools::intersperse_with(breadcrumbs.into_iter(), || {
|
||||
.with_children(Itertools::intersperse_with(breadcrumbs, || {
|
||||
Label::new(" 〉 ", style.default.text.clone()).boxed()
|
||||
}))
|
||||
.constrained()
|
||||
@@ -72,14 +84,14 @@ impl View for Breadcrumbs {
|
||||
.boxed();
|
||||
}
|
||||
|
||||
MouseEventHandler::<Breadcrumbs>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
|
||||
let style = style.style_for(state, false);
|
||||
crumbs.with_style(style.container).boxed()
|
||||
})
|
||||
.on_click(MouseButton::Left, |_, cx| {
|
||||
.on_click(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(outline::Toggle);
|
||||
})
|
||||
.with_tooltip::<Breadcrumbs, _>(
|
||||
.with_tooltip::<Breadcrumbs>(
|
||||
0,
|
||||
"Show symbol outline".to_owned(),
|
||||
Some(Box::new(outline::Toggle)),
|
||||
@@ -136,7 +148,7 @@ impl ToolbarItemView for Breadcrumbs {
|
||||
}
|
||||
}
|
||||
|
||||
fn pane_focus_update(&mut self, pane_focused: bool, _: &mut gpui::AppContext) {
|
||||
fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
|
||||
self.pane_focused = pane_focused;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1470,7 +1470,8 @@ async fn test_host_disconnect(
|
||||
deterministic.run_until_parked();
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
|
||||
|
||||
let (_, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
|
||||
let (window_id_b, workspace_b) =
|
||||
cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
|
||||
let editor_b = workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "b.txt"), None, true, cx)
|
||||
@@ -1479,12 +1480,9 @@ async fn test_host_disconnect(
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
cx_b.read(|cx| {
|
||||
assert_eq!(
|
||||
cx.focused_view_id(workspace_b.window_id()),
|
||||
Some(editor_b.id())
|
||||
);
|
||||
});
|
||||
assert!(cx_b
|
||||
.read_window(window_id_b, |cx| editor_b.is_focused(cx))
|
||||
.unwrap());
|
||||
editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
|
||||
assert!(cx_b.is_window_edited(workspace_b.window_id()));
|
||||
|
||||
@@ -1498,8 +1496,8 @@ async fn test_host_disconnect(
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
|
||||
|
||||
// Ensure client B's edited state is reset and that the whole window is blurred.
|
||||
cx_b.read(|cx| {
|
||||
assert_eq!(cx.focused_view_id(workspace_b.window_id()), None);
|
||||
cx_b.read_window(window_id_b, |cx| {
|
||||
assert_eq!(cx.focused_view_id(), None);
|
||||
});
|
||||
assert!(!cx_b.is_window_edited(workspace_b.window_id()));
|
||||
|
||||
@@ -6228,7 +6226,8 @@ async fn test_basic_following(
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace::Pane::go_back(workspace, None, cx)
|
||||
})
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
deterministic.run_until_parked();
|
||||
workspace_b.read_with(cx_b, |workspace, cx| {
|
||||
assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
|
||||
@@ -6238,7 +6237,8 @@ async fn test_basic_following(
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace::Pane::go_back(workspace, None, cx)
|
||||
})
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
deterministic.run_until_parked();
|
||||
workspace_b.read_with(cx_b, |workspace, cx| {
|
||||
assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b2.id());
|
||||
@@ -6248,7 +6248,8 @@ async fn test_basic_following(
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace::Pane::go_forward(workspace, None, cx)
|
||||
})
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
deterministic.run_until_parked();
|
||||
workspace_b.read_with(cx_b, |workspace, cx| {
|
||||
assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
|
||||
|
||||
@@ -16,7 +16,7 @@ use gpui::{
|
||||
impl_internal_actions,
|
||||
json::{self, ToJson},
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AppContext, Entity, ImageData, ModelHandle, RenderContext, Subscription, View, ViewContext,
|
||||
AppContext, Entity, ImageData, ModelHandle, SceneBuilder, Subscription, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
@@ -68,7 +68,7 @@ impl View for CollabTitlebarItem {
|
||||
"CollabTitlebarItem"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let workspace = if let Some(workspace) = self.workspace.upgrade(cx) {
|
||||
workspace
|
||||
} else {
|
||||
@@ -325,8 +325,8 @@ impl CollabTitlebarItem {
|
||||
fn render_toggle_contacts_button(
|
||||
&self,
|
||||
theme: &Theme,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let titlebar = &theme.workspace.titlebar;
|
||||
|
||||
let badge = if self
|
||||
@@ -352,7 +352,7 @@ impl CollabTitlebarItem {
|
||||
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ToggleContactsMenu>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<ToggleContactsMenu, Self>::new(0, cx, |state, _| {
|
||||
let style = titlebar
|
||||
.toggle_contacts_button
|
||||
.style_for(state, self.contacts_popover.is_some());
|
||||
@@ -369,10 +369,10 @@ impl CollabTitlebarItem {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(ToggleContactsMenu);
|
||||
})
|
||||
.with_tooltip::<ToggleContactsMenu, _>(
|
||||
.with_tooltip::<ToggleContactsMenu>(
|
||||
0,
|
||||
"Show contacts menu".into(),
|
||||
Some(Box::new(ToggleContactsMenu)),
|
||||
@@ -390,8 +390,8 @@ impl CollabTitlebarItem {
|
||||
&self,
|
||||
theme: &Theme,
|
||||
room: &ModelHandle<Room>,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let icon;
|
||||
let tooltip;
|
||||
if room.read(cx).is_screen_sharing() {
|
||||
@@ -403,7 +403,7 @@ impl CollabTitlebarItem {
|
||||
}
|
||||
|
||||
let titlebar = &theme.workspace.titlebar;
|
||||
MouseEventHandler::<ToggleScreenSharing>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<ToggleScreenSharing, Self>::new(0, cx, |state, _| {
|
||||
let style = titlebar.call_control.style_for(state, false);
|
||||
Svg::new(icon)
|
||||
.with_color(style.color)
|
||||
@@ -418,10 +418,10 @@ impl CollabTitlebarItem {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(ToggleScreenSharing);
|
||||
})
|
||||
.with_tooltip::<ToggleScreenSharing, _>(
|
||||
.with_tooltip::<ToggleScreenSharing>(
|
||||
0,
|
||||
tooltip.into(),
|
||||
Some(Box::new(ToggleScreenSharing)),
|
||||
@@ -436,8 +436,8 @@ impl CollabTitlebarItem {
|
||||
&self,
|
||||
workspace: &ViewHandle<Workspace>,
|
||||
theme: &Theme,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> Option<ElementBox> {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Element<Self>> {
|
||||
let project = workspace.read(cx).project();
|
||||
if project.read(cx).is_remote() {
|
||||
return None;
|
||||
@@ -457,7 +457,7 @@ impl CollabTitlebarItem {
|
||||
Some(
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ShareUnshare>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<ShareUnshare, Self>::new(0, cx, |state, _| {
|
||||
//TODO: Ensure this button has consistant width for both text variations
|
||||
let style = titlebar.share_button.style_for(state, false);
|
||||
Label::new(label, style.text.clone())
|
||||
@@ -466,14 +466,14 @@ impl CollabTitlebarItem {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
if is_shared {
|
||||
cx.dispatch_action(UnshareProject);
|
||||
} else {
|
||||
cx.dispatch_action(ShareProject);
|
||||
}
|
||||
})
|
||||
.with_tooltip::<ShareUnshare, _>(
|
||||
.with_tooltip::<ShareUnshare>(
|
||||
0,
|
||||
tooltip.to_owned(),
|
||||
None,
|
||||
@@ -489,12 +489,12 @@ impl CollabTitlebarItem {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_user_menu_button(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render_user_menu_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let titlebar = &theme.workspace.titlebar;
|
||||
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ToggleUserMenu>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<ToggleUserMenu, Self>::new(0, cx, |state, _| {
|
||||
let style = titlebar.call_control.style_for(state, false);
|
||||
Svg::new("icons/ellipsis_14.svg")
|
||||
.with_color(style.color)
|
||||
@@ -509,10 +509,10 @@ impl CollabTitlebarItem {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(ToggleUserMenu);
|
||||
})
|
||||
.with_tooltip::<ToggleUserMenu, _>(
|
||||
.with_tooltip::<ToggleUserMenu>(
|
||||
0,
|
||||
"Toggle user menu".to_owned(),
|
||||
Some(Box::new(ToggleUserMenu)),
|
||||
@@ -533,9 +533,9 @@ impl CollabTitlebarItem {
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn render_sign_in_button(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let titlebar = &theme.workspace.titlebar;
|
||||
MouseEventHandler::<SignIn>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<SignIn, Self>::new(0, cx, |state, _| {
|
||||
let style = titlebar.sign_in_prompt.style_for(state, false);
|
||||
Label::new("Sign In", style.text.clone())
|
||||
.contained()
|
||||
@@ -543,7 +543,7 @@ impl CollabTitlebarItem {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(SignIn);
|
||||
})
|
||||
.boxed()
|
||||
@@ -552,8 +552,8 @@ impl CollabTitlebarItem {
|
||||
fn render_contacts_popover_host<'a>(
|
||||
&'a self,
|
||||
_theme: &'a theme::Titlebar,
|
||||
cx: &'a RenderContext<Self>,
|
||||
) -> Option<ElementBox> {
|
||||
cx: &'a ViewContext<Self>,
|
||||
) -> Option<Element<Self>> {
|
||||
self.contacts_popover.as_ref().map(|popover| {
|
||||
Overlay::new(ChildView::new(popover, cx).boxed())
|
||||
.with_fit_mode(OverlayFitMode::SwitchAnchor)
|
||||
@@ -571,8 +571,8 @@ impl CollabTitlebarItem {
|
||||
workspace: &ViewHandle<Workspace>,
|
||||
theme: &Theme,
|
||||
room: &ModelHandle<Room>,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> Vec<ElementBox> {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Vec<Element<Self>> {
|
||||
let mut participants = room
|
||||
.read(cx)
|
||||
.remote_participants()
|
||||
@@ -613,8 +613,8 @@ impl CollabTitlebarItem {
|
||||
theme: &Theme,
|
||||
user: &Arc<User>,
|
||||
peer_id: PeerId,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let replica_id = workspace.read(cx).project().read(cx).replica_id();
|
||||
Container::new(self.render_face_pile(
|
||||
user,
|
||||
@@ -637,8 +637,8 @@ impl CollabTitlebarItem {
|
||||
location: Option<ParticipantLocation>,
|
||||
workspace: &ViewHandle<Workspace>,
|
||||
theme: &Theme,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let project_id = workspace.read(cx).project().read(cx).remote_id();
|
||||
let room = ActiveCall::global(cx).read(cx).room();
|
||||
let is_being_followed = workspace.read(cx).is_being_followed(peer_id);
|
||||
@@ -749,41 +749,42 @@ impl CollabTitlebarItem {
|
||||
|
||||
if let Some(location) = location {
|
||||
if let Some(replica_id) = replica_id {
|
||||
content =
|
||||
MouseEventHandler::<ToggleFollow>::new(replica_id.into(), cx, move |_, _| {
|
||||
content
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
cx.dispatch_action(ToggleFollow(peer_id))
|
||||
})
|
||||
.with_tooltip::<ToggleFollow, _>(
|
||||
peer_id.as_u64() as usize,
|
||||
if is_being_followed {
|
||||
format!("Unfollow {}", user.github_login)
|
||||
} else {
|
||||
format!("Follow {}", user.github_login)
|
||||
},
|
||||
Some(Box::new(FollowNextCollaborator)),
|
||||
theme.tooltip.clone(),
|
||||
cx,
|
||||
)
|
||||
.boxed();
|
||||
content = MouseEventHandler::<ToggleFollow, Self>::new(
|
||||
replica_id.into(),
|
||||
cx,
|
||||
move |_, _| content,
|
||||
)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(ToggleFollow(peer_id))
|
||||
})
|
||||
.with_tooltip::<ToggleFollow>(
|
||||
peer_id.as_u64() as usize,
|
||||
if is_being_followed {
|
||||
format!("Unfollow {}", user.github_login)
|
||||
} else {
|
||||
format!("Follow {}", user.github_login)
|
||||
},
|
||||
Some(Box::new(FollowNextCollaborator)),
|
||||
theme.tooltip.clone(),
|
||||
cx,
|
||||
)
|
||||
.boxed();
|
||||
} else if let ParticipantLocation::SharedProject { project_id } = location {
|
||||
let user_id = user.id;
|
||||
content = MouseEventHandler::<JoinProject>::new(
|
||||
content = MouseEventHandler::<JoinProject, Self>::new(
|
||||
peer_id.as_u64() as usize,
|
||||
cx,
|
||||
move |_, _| content,
|
||||
)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(JoinProject {
|
||||
project_id,
|
||||
follow_user_id: user_id,
|
||||
})
|
||||
})
|
||||
.with_tooltip::<JoinProject, _>(
|
||||
.with_tooltip::<JoinProject>(
|
||||
peer_id.as_u64() as usize,
|
||||
format!("Follow {} into external project", user.github_login),
|
||||
Some(Box::new(FollowNextCollaborator)),
|
||||
@@ -800,7 +801,7 @@ impl CollabTitlebarItem {
|
||||
workspace: &ViewHandle<Workspace>,
|
||||
location: Option<ParticipantLocation>,
|
||||
mut style: AvatarStyle,
|
||||
cx: &RenderContext<Self>,
|
||||
cx: &ViewContext<Self>,
|
||||
) -> AvatarStyle {
|
||||
if let Some(location) = location {
|
||||
if let ParticipantLocation::SharedProject { project_id } = location {
|
||||
@@ -815,11 +816,11 @@ impl CollabTitlebarItem {
|
||||
style
|
||||
}
|
||||
|
||||
fn render_face(
|
||||
fn render_face<V: View>(
|
||||
avatar: Arc<ImageData>,
|
||||
avatar_style: AvatarStyle,
|
||||
background_color: Color,
|
||||
) -> ElementBox {
|
||||
) -> Element<V> {
|
||||
Image::from_data(avatar)
|
||||
.with_style(avatar_style.image)
|
||||
.aligned()
|
||||
@@ -836,8 +837,8 @@ impl CollabTitlebarItem {
|
||||
fn render_connection_status(
|
||||
&self,
|
||||
status: &client::Status,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> Option<ElementBox> {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Element<Self>> {
|
||||
enum ConnectionStatusButton {}
|
||||
|
||||
let theme = &cx.global::<Settings>().theme.clone();
|
||||
@@ -863,7 +864,7 @@ impl CollabTitlebarItem {
|
||||
.boxed(),
|
||||
),
|
||||
client::Status::UpgradeRequired => Some(
|
||||
MouseEventHandler::<ConnectionStatusButton>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::<ConnectionStatusButton, Self>::new(0, cx, |_, _| {
|
||||
Label::new(
|
||||
"Please update Zed to collaborate",
|
||||
theme.workspace.titlebar.outdated_warning.text.clone(),
|
||||
@@ -874,7 +875,7 @@ impl CollabTitlebarItem {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, |_, cx| {
|
||||
.on_click(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(auto_update::Check);
|
||||
})
|
||||
.boxed(),
|
||||
@@ -894,7 +895,7 @@ impl AvatarRibbon {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for AvatarRibbon {
|
||||
impl Drawable<CollabTitlebarItem> for AvatarRibbon {
|
||||
type LayoutState = ();
|
||||
|
||||
type PaintState = ();
|
||||
@@ -902,17 +903,20 @@ impl Element for AvatarRibbon {
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: gpui::SizeConstraint,
|
||||
_: &mut gpui::LayoutContext,
|
||||
_: &mut CollabTitlebarItem,
|
||||
_: &mut ViewContext<CollabTitlebarItem>,
|
||||
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
|
||||
(constraint.max, ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: gpui::geometry::rect::RectF,
|
||||
_: gpui::geometry::rect::RectF,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
_: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut gpui::PaintContext,
|
||||
_: &mut CollabTitlebarItem,
|
||||
_: &mut ViewContext<CollabTitlebarItem>,
|
||||
) -> Self::PaintState {
|
||||
let mut path = PathBuilder::new();
|
||||
path.reset(bounds.lower_left());
|
||||
@@ -923,7 +927,7 @@ impl Element for AvatarRibbon {
|
||||
path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
|
||||
path.curve_to(bounds.lower_right(), bounds.upper_right());
|
||||
path.line_to(bounds.lower_left());
|
||||
cx.scene.push_path(path.build(self.color, None));
|
||||
scene.push_path(path.build(self.color, None));
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
@@ -933,17 +937,19 @@ impl Element for AvatarRibbon {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &gpui::MeasurementContext,
|
||||
_: &CollabTitlebarItem,
|
||||
_: &ViewContext<CollabTitlebarItem>,
|
||||
) -> Option<RectF> {
|
||||
None
|
||||
}
|
||||
|
||||
fn debug(
|
||||
&self,
|
||||
bounds: gpui::geometry::rect::RectF,
|
||||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &gpui::DebugContext,
|
||||
_: &CollabTitlebarItem,
|
||||
_: &ViewContext<CollabTitlebarItem>,
|
||||
) -> gpui::json::Value {
|
||||
json::json!({
|
||||
"type": "AvatarRibbon",
|
||||
|
||||
@@ -126,7 +126,7 @@ fn join_project(action: &JoinProject, app_state: Arc<AppState>, cx: &mut AppCont
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use call::ActiveCall;
|
||||
use client::UserStore;
|
||||
use gpui::Action;
|
||||
use gpui::{
|
||||
actions, elements::*, platform::MouseButton, Entity, ModelHandle, RenderContext, View,
|
||||
ViewContext,
|
||||
};
|
||||
use gpui::{actions, elements::*, platform::MouseButton, Entity, ModelHandle, View, ViewContext};
|
||||
use settings::Settings;
|
||||
|
||||
use crate::collab_titlebar_item::ToggleCollaboratorList;
|
||||
@@ -21,7 +18,7 @@ enum Collaborator {
|
||||
actions!(collaborator_list_popover, [NoOp]);
|
||||
|
||||
pub(crate) struct CollaboratorListPopover {
|
||||
list_state: ListState,
|
||||
list_state: ListState<Self>,
|
||||
}
|
||||
|
||||
impl Entity for CollaboratorListPopover {
|
||||
@@ -33,10 +30,10 @@ impl View for CollaboratorListPopover {
|
||||
"CollaboratorListPopover"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
|
||||
MouseEventHandler::<Self>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::<Self, Self>::new(0, cx, |_, _| {
|
||||
List::new(self.list_state.clone())
|
||||
.contained()
|
||||
.with_style(theme.contacts_popover.container) //TODO: Change the name of this theme key
|
||||
@@ -45,7 +42,7 @@ impl View for CollaboratorListPopover {
|
||||
.with_height(theme.contacts_popover.height)
|
||||
.boxed()
|
||||
})
|
||||
.on_down_out(MouseButton::Left, move |_, cx| {
|
||||
.on_down_out(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(ToggleCollaboratorList);
|
||||
})
|
||||
.boxed()
|
||||
@@ -83,7 +80,6 @@ impl CollaboratorListPopover {
|
||||
collaborators.len(),
|
||||
Orientation::Top,
|
||||
0.,
|
||||
cx,
|
||||
move |_, index, cx| match &collaborators[index] {
|
||||
Collaborator::SelfUser { username } => render_collaborator_list_entry(
|
||||
index,
|
||||
@@ -120,8 +116,8 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
|
||||
icon: Svg,
|
||||
icon_action: IA,
|
||||
icon_tooltip: String,
|
||||
cx: &mut RenderContext<CollaboratorListPopover>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<CollaboratorListPopover>,
|
||||
) -> Element<CollaboratorListPopover> {
|
||||
enum Username {}
|
||||
enum UsernameTooltip {}
|
||||
enum Icon {}
|
||||
@@ -131,19 +127,20 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
|
||||
let username_theme = theme.contact_list.contact_username.text.clone();
|
||||
let tooltip_theme = theme.tooltip.clone();
|
||||
|
||||
let username = MouseEventHandler::<Username>::new(index, cx, |_, _| {
|
||||
Label::new(username.to_owned(), username_theme.clone()).boxed()
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
if let Some(username_action) = username_action.clone() {
|
||||
cx.dispatch_action(username_action);
|
||||
}
|
||||
});
|
||||
let username =
|
||||
MouseEventHandler::<Username, CollaboratorListPopover>::new(index, cx, |_, _| {
|
||||
Label::new(username.to_owned(), username_theme.clone()).boxed()
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
if let Some(username_action) = username_action.clone() {
|
||||
cx.dispatch_action(username_action);
|
||||
}
|
||||
});
|
||||
|
||||
Flex::row()
|
||||
.with_child(if let Some(username_tooltip) = username_tooltip {
|
||||
username
|
||||
.with_tooltip::<UsernameTooltip, _>(
|
||||
.with_tooltip::<UsernameTooltip>(
|
||||
index,
|
||||
username_tooltip,
|
||||
None,
|
||||
@@ -155,11 +152,11 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
|
||||
username.boxed()
|
||||
})
|
||||
.with_child(
|
||||
MouseEventHandler::<Icon>::new(index, cx, |_, _| icon.boxed())
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
MouseEventHandler::<Icon, CollaboratorListPopover>::new(index, cx, |_, _| icon.boxed())
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(icon_action.clone())
|
||||
})
|
||||
.with_tooltip::<IconTooltip, _>(index, icon_tooltip, None, tooltip_theme, cx)
|
||||
.with_tooltip::<IconTooltip>(index, icon_tooltip, None, tooltip_theme, cx)
|
||||
.boxed(),
|
||||
)
|
||||
.boxed()
|
||||
|
||||
@@ -1,49 +1,41 @@
|
||||
use client::{ContactRequestStatus, User, UserStore};
|
||||
use gpui::{
|
||||
elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, RenderContext, Task,
|
||||
View, ViewContext, ViewHandle,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use gpui::{elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext};
|
||||
use picker::{Picker, PickerDelegate, PickerEvent};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use util::TryFutureExt;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
Picker::<ContactFinder>::init(cx);
|
||||
Picker::<ContactFinderDelegate>::init(cx);
|
||||
}
|
||||
|
||||
pub struct ContactFinder {
|
||||
picker: ViewHandle<Picker<Self>>,
|
||||
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 Entity for ContactFinder {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
impl View for ContactFinder {
|
||||
fn ui_name() -> &'static str {
|
||||
"ContactFinder"
|
||||
impl PickerDelegate for ContactFinderDelegate {
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
"Search collaborator by username...".into()
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
ChildView::new(&self.picker, cx).boxed()
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
if cx.is_self_focused() {
|
||||
cx.focus(&self.picker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for ContactFinder {
|
||||
fn match_count(&self) -> usize {
|
||||
self.potential_contacts.len()
|
||||
}
|
||||
@@ -52,22 +44,22 @@ impl PickerDelegate for ContactFinder {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
|
||||
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
let search_users = self
|
||||
.user_store
|
||||
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
async {
|
||||
let potential_contacts = search_users.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.potential_contacts = potential_contacts.into();
|
||||
picker.update(&mut cx, |picker, cx| {
|
||||
picker.delegate_mut().potential_contacts = potential_contacts.into();
|
||||
cx.notify();
|
||||
});
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err()
|
||||
@@ -75,7 +67,7 @@ impl PickerDelegate for ContactFinder {
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if let Some(user) = self.potential_contacts.get(self.selected_index) {
|
||||
let user_store = self.user_store.read(cx);
|
||||
match user_store.contact_request_status(user) {
|
||||
@@ -94,8 +86,8 @@ impl PickerDelegate for ContactFinder {
|
||||
}
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(Event::Dismissed);
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
cx.emit(PickerEvent::Dismiss);
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
@@ -104,7 +96,7 @@ impl PickerDelegate for ContactFinder {
|
||||
mouse_state: &mut MouseState,
|
||||
selected: bool,
|
||||
cx: &gpui::AppContext,
|
||||
) -> ElementBox {
|
||||
) -> Element<Picker<Self>> {
|
||||
let theme = &cx.global::<Settings>().theme;
|
||||
let user = &self.potential_contacts[ix];
|
||||
let request_status = self.user_store.read(cx).contact_request_status(user);
|
||||
@@ -164,28 +156,3 @@ impl PickerDelegate for ContactFinder {
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl ContactFinder {
|
||||
pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let this = cx.weak_handle();
|
||||
Self {
|
||||
picker: cx.add_view(|cx| {
|
||||
Picker::new("Search collaborator by username...", this, cx)
|
||||
.with_theme(|theme| theme.contact_finder.picker.clone())
|
||||
}),
|
||||
potential_contacts: Arc::from([]),
|
||||
user_store,
|
||||
selected_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn editor_text(&self, cx: &AppContext) -> String {
|
||||
self.picker.read(cx).query(cx)
|
||||
}
|
||||
|
||||
pub fn with_editor_text(self, editor_text: String, cx: &mut ViewContext<Self>) -> Self {
|
||||
self.picker
|
||||
.update(cx, |picker, cx| picker.set_query(editor_text, cx));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use gpui::{
|
||||
impl_actions, impl_internal_actions,
|
||||
keymap_matcher::KeymapContext,
|
||||
platform::{CursorStyle, MouseButton, PromptLevel},
|
||||
AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext, ViewHandle,
|
||||
AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use menu::{Confirm, SelectNext, SelectPrev};
|
||||
use project::Project;
|
||||
@@ -159,7 +159,7 @@ pub enum Event {
|
||||
pub struct ContactList {
|
||||
entries: Vec<ContactEntry>,
|
||||
match_candidates: Vec<StringMatchCandidate>,
|
||||
list_state: ListState,
|
||||
list_state: ListState<Self>,
|
||||
project: ModelHandle<Project>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
filter_editor: ViewHandle<Editor>,
|
||||
@@ -202,7 +202,7 @@ impl ContactList {
|
||||
})
|
||||
.detach();
|
||||
|
||||
let list_state = ListState::new(0, Orientation::Top, 1000., cx, move |this, ix, cx| {
|
||||
let list_state = ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let is_selected = this.selection == Some(ix);
|
||||
let current_project_id = this.project.read(cx).remote_id();
|
||||
@@ -748,7 +748,7 @@ impl ContactList {
|
||||
is_pending: bool,
|
||||
is_selected: bool,
|
||||
theme: &theme::ContactList,
|
||||
) -> ElementBox {
|
||||
) -> Element<Self> {
|
||||
Flex::row()
|
||||
.with_children(user.avatar.clone().map(|avatar| {
|
||||
Image::from_data(avatar)
|
||||
@@ -799,8 +799,8 @@ impl ContactList {
|
||||
is_last: bool,
|
||||
is_selected: bool,
|
||||
theme: &theme::ContactList,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let font_cache = cx.font_cache();
|
||||
let host_avatar_height = theme
|
||||
.contact_avatar
|
||||
@@ -819,7 +819,7 @@ impl ContactList {
|
||||
worktree_root_names.join(", ")
|
||||
};
|
||||
|
||||
MouseEventHandler::<JoinProject>::new(project_id as usize, cx, |mouse_state, _| {
|
||||
MouseEventHandler::<JoinProject, Self>::new(project_id as usize, cx, |mouse_state, _| {
|
||||
let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
|
||||
let row = theme.project_row.style_for(mouse_state, is_selected);
|
||||
|
||||
@@ -827,14 +827,14 @@ impl ContactList {
|
||||
.with_child(
|
||||
Stack::new()
|
||||
.with_child(
|
||||
Canvas::new(move |bounds, _, cx| {
|
||||
Canvas::new(move |scene, bounds, _, _, _| {
|
||||
let start_x = bounds.min_x() + (bounds.width() / 2.)
|
||||
- (tree_branch.width / 2.);
|
||||
let end_x = bounds.max_x();
|
||||
let start_y = bounds.min_y();
|
||||
let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
|
||||
|
||||
cx.scene.push_quad(gpui::Quad {
|
||||
scene.push_quad(gpui::Quad {
|
||||
bounds: RectF::from_points(
|
||||
vec2f(start_x, start_y),
|
||||
vec2f(
|
||||
@@ -846,7 +846,7 @@ impl ContactList {
|
||||
border: gpui::Border::default(),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
cx.scene.push_quad(gpui::Quad {
|
||||
scene.push_quad(gpui::Quad {
|
||||
bounds: RectF::from_points(
|
||||
vec2f(start_x, end_y),
|
||||
vec2f(end_x, end_y + tree_branch.width),
|
||||
@@ -882,7 +882,7 @@ impl ContactList {
|
||||
} else {
|
||||
CursorStyle::Arrow
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
if !is_current {
|
||||
cx.dispatch_global_action(JoinProject {
|
||||
project_id,
|
||||
@@ -898,8 +898,8 @@ impl ContactList {
|
||||
is_last: bool,
|
||||
is_selected: bool,
|
||||
theme: &theme::ContactList,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let font_cache = cx.font_cache();
|
||||
let host_avatar_height = theme
|
||||
.contact_avatar
|
||||
@@ -913,7 +913,7 @@ impl ContactList {
|
||||
let baseline_offset =
|
||||
row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
|
||||
|
||||
MouseEventHandler::<OpenSharedScreen>::new(
|
||||
MouseEventHandler::<OpenSharedScreen, Self>::new(
|
||||
peer_id.as_u64() as usize,
|
||||
cx,
|
||||
|mouse_state, _| {
|
||||
@@ -924,7 +924,7 @@ impl ContactList {
|
||||
.with_child(
|
||||
Stack::new()
|
||||
.with_child(
|
||||
Canvas::new(move |bounds, _, cx| {
|
||||
Canvas::new(move |scene, bounds, _, _, _| {
|
||||
let start_x = bounds.min_x() + (bounds.width() / 2.)
|
||||
- (tree_branch.width / 2.);
|
||||
let end_x = bounds.max_x();
|
||||
@@ -932,7 +932,7 @@ impl ContactList {
|
||||
let end_y =
|
||||
bounds.min_y() + baseline_offset - (cap_height / 2.);
|
||||
|
||||
cx.scene.push_quad(gpui::Quad {
|
||||
scene.push_quad(gpui::Quad {
|
||||
bounds: RectF::from_points(
|
||||
vec2f(start_x, start_y),
|
||||
vec2f(
|
||||
@@ -944,7 +944,7 @@ impl ContactList {
|
||||
border: gpui::Border::default(),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
cx.scene.push_quad(gpui::Quad {
|
||||
scene.push_quad(gpui::Quad {
|
||||
bounds: RectF::from_points(
|
||||
vec2f(start_x, end_y),
|
||||
vec2f(end_x, end_y + tree_branch.width),
|
||||
@@ -988,7 +988,7 @@ impl ContactList {
|
||||
},
|
||||
)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(OpenSharedScreen { peer_id });
|
||||
})
|
||||
.boxed()
|
||||
@@ -999,8 +999,8 @@ impl ContactList {
|
||||
theme: &theme::ContactList,
|
||||
is_selected: bool,
|
||||
is_collapsed: bool,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
enum Header {}
|
||||
enum LeaveCallContactList {}
|
||||
|
||||
@@ -1015,14 +1015,14 @@ impl ContactList {
|
||||
};
|
||||
let leave_call = if section == Section::ActiveCall {
|
||||
Some(
|
||||
MouseEventHandler::<LeaveCallContactList>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<LeaveCallContactList, Self>::new(0, cx, |state, _| {
|
||||
let style = theme.leave_call.style_for(state, false);
|
||||
Label::new("Leave Call", style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(MouseButton::Left, |_, cx| cx.dispatch_action(LeaveCall))
|
||||
.on_click(MouseButton::Left, |_, _, cx| cx.dispatch_action(LeaveCall))
|
||||
.aligned()
|
||||
.boxed(),
|
||||
)
|
||||
@@ -1031,7 +1031,7 @@ impl ContactList {
|
||||
};
|
||||
|
||||
let icon_size = theme.section_icon_size;
|
||||
MouseEventHandler::<Header>::new(section as usize, cx, |_, _| {
|
||||
MouseEventHandler::<Header, Self>::new(section as usize, cx, |_, _| {
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Svg::new(if is_collapsed {
|
||||
@@ -1065,7 +1065,7 @@ impl ContactList {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(ToggleExpanded(section))
|
||||
})
|
||||
.boxed()
|
||||
@@ -1077,15 +1077,15 @@ impl ContactList {
|
||||
project: &ModelHandle<Project>,
|
||||
theme: &theme::ContactList,
|
||||
is_selected: bool,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let online = contact.online;
|
||||
let busy = contact.busy || calling;
|
||||
let user_id = contact.user.id;
|
||||
let github_login = contact.user.github_login.clone();
|
||||
let initial_project = project.clone();
|
||||
let mut element =
|
||||
MouseEventHandler::<Contact>::new(contact.user.id as usize, cx, |_, cx| {
|
||||
MouseEventHandler::<Contact, Self>::new(contact.user.id as usize, cx, |_, cx| {
|
||||
Flex::row()
|
||||
.with_children(contact.user.avatar.clone().map(|avatar| {
|
||||
let status_badge = if contact.online {
|
||||
@@ -1128,7 +1128,7 @@ impl ContactList {
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Cancel>::new(
|
||||
MouseEventHandler::<Cancel, Self>::new(
|
||||
contact.user.id as usize,
|
||||
cx,
|
||||
|mouse_state, _| {
|
||||
@@ -1142,7 +1142,7 @@ impl ContactList {
|
||||
)
|
||||
.with_padding(Padding::uniform(2.))
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(RemoveContact {
|
||||
user_id,
|
||||
github_login: github_login.clone(),
|
||||
@@ -1172,7 +1172,7 @@ impl ContactList {
|
||||
)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
if online && !busy {
|
||||
cx.dispatch_action(Call {
|
||||
recipient_user_id: user_id,
|
||||
@@ -1194,8 +1194,8 @@ impl ContactList {
|
||||
theme: &theme::ContactList,
|
||||
is_incoming: bool,
|
||||
is_selected: bool,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
enum Decline {}
|
||||
enum Accept {}
|
||||
enum Cancel {}
|
||||
@@ -1228,7 +1228,7 @@ impl ContactList {
|
||||
|
||||
if is_incoming {
|
||||
row.add_children([
|
||||
MouseEventHandler::<Decline>::new(user.id as usize, cx, |mouse_state, _| {
|
||||
MouseEventHandler::<Decline, Self>::new(user.id as usize, cx, |mouse_state, _| {
|
||||
let button_style = if is_contact_request_pending {
|
||||
&theme.disabled_button
|
||||
} else {
|
||||
@@ -1239,7 +1239,7 @@ impl ContactList {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(RespondToContactRequest {
|
||||
user_id,
|
||||
accept: false,
|
||||
@@ -1248,7 +1248,7 @@ impl ContactList {
|
||||
.contained()
|
||||
.with_margin_right(button_spacing)
|
||||
.boxed(),
|
||||
MouseEventHandler::<Accept>::new(user.id as usize, cx, |mouse_state, _| {
|
||||
MouseEventHandler::<Accept, Self>::new(user.id as usize, cx, |mouse_state, _| {
|
||||
let button_style = if is_contact_request_pending {
|
||||
&theme.disabled_button
|
||||
} else {
|
||||
@@ -1260,7 +1260,7 @@ impl ContactList {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(RespondToContactRequest {
|
||||
user_id,
|
||||
accept: true,
|
||||
@@ -1270,7 +1270,7 @@ impl ContactList {
|
||||
]);
|
||||
} else {
|
||||
row.add_child(
|
||||
MouseEventHandler::<Cancel>::new(user.id as usize, cx, |mouse_state, _| {
|
||||
MouseEventHandler::<Cancel, Self>::new(user.id as usize, cx, |mouse_state, _| {
|
||||
let button_style = if is_contact_request_pending {
|
||||
&theme.disabled_button
|
||||
} else {
|
||||
@@ -1283,7 +1283,7 @@ impl ContactList {
|
||||
})
|
||||
.with_padding(Padding::uniform(2.))
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(RemoveContact {
|
||||
user_id,
|
||||
github_login: github_login.clone(),
|
||||
@@ -1331,7 +1331,7 @@ impl View for ContactList {
|
||||
cx
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
enum AddContact {}
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
|
||||
@@ -1346,7 +1346,7 @@ impl View for ContactList {
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<AddContact>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::<AddContact, Self>::new(0, cx, |_, _| {
|
||||
render_icon_button(
|
||||
&theme.contact_list.add_contact_button,
|
||||
"icons/user_plus_16.svg",
|
||||
@@ -1354,10 +1354,10 @@ impl View for ContactList {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, |_, cx| {
|
||||
.on_click(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(contacts_popover::ToggleContactFinder)
|
||||
})
|
||||
.with_tooltip::<AddContact, _>(
|
||||
.with_tooltip::<AddContact>(
|
||||
0,
|
||||
"Search for new contact".into(),
|
||||
None,
|
||||
@@ -1387,7 +1387,7 @@ impl View for ContactList {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Element {
|
||||
fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Drawable<ContactList> {
|
||||
Svg::new(svg_path)
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
|
||||
@@ -3,8 +3,7 @@ use std::sync::Arc;
|
||||
use crate::notifications::render_user_notification;
|
||||
use client::{ContactEventKind, User, UserStore};
|
||||
use gpui::{
|
||||
elements::*, impl_internal_actions, AppContext, Entity, ModelHandle, RenderContext, View,
|
||||
ViewContext,
|
||||
elements::*, impl_internal_actions, AppContext, Entity, ModelHandle, View, ViewContext,
|
||||
};
|
||||
use workspace::notifications::Notification;
|
||||
|
||||
@@ -43,7 +42,7 @@ impl View for ContactNotification {
|
||||
"ContactNotification"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
match self.kind {
|
||||
ContactEventKind::Requested => render_user_notification(
|
||||
self.user.clone(),
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
use crate::{contact_finder::ContactFinder, contact_list::ContactList, ToggleContactsMenu};
|
||||
use crate::{
|
||||
contact_finder::{build_contact_finder, ContactFinder},
|
||||
contact_list::ContactList,
|
||||
ToggleContactsMenu,
|
||||
};
|
||||
use client::UserStore;
|
||||
use gpui::{
|
||||
actions, elements::*, platform::MouseButton, AppContext, Entity, ModelHandle, RenderContext,
|
||||
View, ViewContext, ViewHandle,
|
||||
actions, elements::*, platform::MouseButton, AppContext, Entity, ModelHandle, View,
|
||||
ViewContext, ViewHandle,
|
||||
};
|
||||
use picker::PickerEvent;
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
|
||||
@@ -50,19 +55,19 @@ impl ContactsPopover {
|
||||
fn toggle_contact_finder(&mut self, _: &ToggleContactFinder, cx: &mut ViewContext<Self>) {
|
||||
match &self.child {
|
||||
Child::ContactList(list) => self.show_contact_finder(list.read(cx).editor_text(cx), cx),
|
||||
Child::ContactFinder(finder) => {
|
||||
self.show_contact_list(finder.read(cx).editor_text(cx), cx)
|
||||
}
|
||||
Child::ContactFinder(finder) => self.show_contact_list(finder.read(cx).query(cx), cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn show_contact_finder(&mut self, editor_text: String, cx: &mut ViewContext<ContactsPopover>) {
|
||||
let child = cx.add_view(|cx| {
|
||||
ContactFinder::new(self.user_store.clone(), cx).with_editor_text(editor_text, cx)
|
||||
let finder = build_contact_finder(self.user_store.clone(), cx);
|
||||
finder.set_query(editor_text, cx);
|
||||
finder
|
||||
});
|
||||
cx.focus(&child);
|
||||
self._subscription = Some(cx.subscribe(&child, |_, _, event, cx| match event {
|
||||
crate::contact_finder::Event::Dismissed => cx.emit(Event::Dismissed),
|
||||
PickerEvent::Dismiss => cx.emit(Event::Dismissed),
|
||||
}));
|
||||
self.child = Child::ContactFinder(child);
|
||||
cx.notify();
|
||||
@@ -91,14 +96,14 @@ impl View for ContactsPopover {
|
||||
"ContactsPopover"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let child = match &self.child {
|
||||
Child::ContactList(child) => ChildView::new(child, cx),
|
||||
Child::ContactFinder(child) => ChildView::new(child, cx),
|
||||
};
|
||||
|
||||
MouseEventHandler::<ContactsPopover>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::<ContactsPopover, Self>::new(0, cx, |_, _| {
|
||||
Flex::column()
|
||||
.with_child(child.flex(1., true).boxed())
|
||||
.contained()
|
||||
@@ -108,7 +113,7 @@ impl View for ContactsPopover {
|
||||
.with_height(theme.contacts_popover.height)
|
||||
.boxed()
|
||||
})
|
||||
.on_down_out(MouseButton::Left, move |_, cx| {
|
||||
.on_down_out(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(ToggleContactsMenu);
|
||||
})
|
||||
.boxed()
|
||||
|
||||
@@ -7,12 +7,14 @@ use gpui::{
|
||||
},
|
||||
json::ToJson,
|
||||
serde_json::{self, json},
|
||||
Axis, DebugContext, Element, ElementBox, MeasurementContext, PaintContext,
|
||||
Axis, Drawable, Element, SceneBuilder, ViewContext,
|
||||
};
|
||||
|
||||
use crate::CollabTitlebarItem;
|
||||
|
||||
pub(crate) struct FacePile {
|
||||
overlap: f32,
|
||||
faces: Vec<ElementBox>,
|
||||
faces: Vec<Element<CollabTitlebarItem>>,
|
||||
}
|
||||
|
||||
impl FacePile {
|
||||
@@ -24,20 +26,21 @@ impl FacePile {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for FacePile {
|
||||
impl Drawable<CollabTitlebarItem> for FacePile {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: gpui::SizeConstraint,
|
||||
cx: &mut gpui::LayoutContext,
|
||||
view: &mut CollabTitlebarItem,
|
||||
cx: &mut ViewContext<CollabTitlebarItem>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
|
||||
|
||||
let mut width = 0.;
|
||||
for face in &mut self.faces {
|
||||
width += face.layout(constraint, cx).x();
|
||||
width += face.layout(constraint, view, cx).x();
|
||||
}
|
||||
width -= self.overlap * self.faces.len().saturating_sub(1) as f32;
|
||||
|
||||
@@ -46,10 +49,12 @@ impl Element for FacePile {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_layout: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut CollabTitlebarItem,
|
||||
cx: &mut ViewContext<CollabTitlebarItem>,
|
||||
) -> Self::PaintState {
|
||||
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
|
||||
|
||||
@@ -59,8 +64,8 @@ impl Element for FacePile {
|
||||
for face in self.faces.iter_mut().rev() {
|
||||
let size = face.size();
|
||||
origin_x -= size.x();
|
||||
cx.paint_layer(None, |cx| {
|
||||
face.paint(vec2f(origin_x, origin_y), visible_bounds, cx);
|
||||
scene.paint_layer(None, |scene| {
|
||||
face.paint(scene, vec2f(origin_x, origin_y), visible_bounds, view, cx);
|
||||
});
|
||||
origin_x += self.overlap;
|
||||
}
|
||||
@@ -75,7 +80,8 @@ impl Element for FacePile {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &MeasurementContext,
|
||||
_: &CollabTitlebarItem,
|
||||
_: &ViewContext<CollabTitlebarItem>,
|
||||
) -> Option<RectF> {
|
||||
None
|
||||
}
|
||||
@@ -85,7 +91,8 @@ impl Element for FacePile {
|
||||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &DebugContext,
|
||||
_: &CollabTitlebarItem,
|
||||
_: &ViewContext<CollabTitlebarItem>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "FacePile",
|
||||
@@ -94,8 +101,8 @@ impl Element for FacePile {
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<ElementBox> for FacePile {
|
||||
fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
|
||||
impl Extend<Element<CollabTitlebarItem>> for FacePile {
|
||||
fn extend<T: IntoIterator<Item = Element<CollabTitlebarItem>>>(&mut self, children: T) {
|
||||
self.faces.extend(children);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use gpui::{
|
||||
geometry::{rect::RectF, vector::vec2f},
|
||||
impl_internal_actions,
|
||||
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
|
||||
AppContext, Entity, RenderContext, View, ViewContext,
|
||||
AppContext, Element, Entity, View, ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
use util::ResultExt;
|
||||
@@ -99,7 +99,7 @@ impl IncomingCallNotification {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_caller(&self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render_caller(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
|
||||
let default_project = proto::ParticipantProject::default();
|
||||
let initial_project = self
|
||||
@@ -165,13 +165,13 @@ impl IncomingCallNotification {
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn render_buttons(&self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
enum Accept {}
|
||||
enum Decline {}
|
||||
|
||||
Flex::column()
|
||||
.with_child(
|
||||
MouseEventHandler::<Accept>::new(0, cx, |_, cx| {
|
||||
MouseEventHandler::<Accept, Self>::new(0, cx, |_, cx| {
|
||||
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
|
||||
Label::new("Accept", theme.accept_button.text.clone())
|
||||
.aligned()
|
||||
@@ -180,14 +180,14 @@ impl IncomingCallNotification {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, |_, cx| {
|
||||
.on_click(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(RespondToCall { accept: true });
|
||||
})
|
||||
.flex(1., true)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Decline>::new(0, cx, |_, cx| {
|
||||
MouseEventHandler::<Decline, Self>::new(0, cx, |_, cx| {
|
||||
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
|
||||
Label::new("Decline", theme.decline_button.text.clone())
|
||||
.aligned()
|
||||
@@ -196,7 +196,7 @@ impl IncomingCallNotification {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, |_, cx| {
|
||||
.on_click(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(RespondToCall { accept: false });
|
||||
})
|
||||
.flex(1., true)
|
||||
@@ -222,7 +222,7 @@ impl View for IncomingCallNotification {
|
||||
"IncomingCallNotification"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let background = cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
|
||||
@@ -2,7 +2,7 @@ use client::User;
|
||||
use gpui::{
|
||||
elements::*,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Action, Element, ElementBox, RenderContext, View,
|
||||
Action, Drawable, Element, View, ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
@@ -16,8 +16,8 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
|
||||
body: Option<&'static str>,
|
||||
dismiss_action: A,
|
||||
buttons: Vec<(&'static str, Box<dyn Action>)>,
|
||||
cx: &mut RenderContext<V>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Element<V> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = &theme.contact_notification;
|
||||
|
||||
@@ -51,7 +51,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Dismiss>::new(user.id as usize, cx, |state, _| {
|
||||
MouseEventHandler::<Dismiss, V>::new(user.id as usize, cx, |state, _| {
|
||||
let style = theme.dismiss_button.style_for(state, false);
|
||||
Svg::new("icons/x_mark_8.svg")
|
||||
.with_color(style.color)
|
||||
@@ -67,7 +67,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.with_padding(Padding::uniform(5.))
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_any_action(dismiss_action.boxed_clone())
|
||||
})
|
||||
.aligned()
|
||||
@@ -96,7 +96,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
|
||||
Flex::row()
|
||||
.with_children(buttons.into_iter().enumerate().map(
|
||||
|(ix, (message, action))| {
|
||||
MouseEventHandler::<Button>::new(ix, cx, |state, _| {
|
||||
MouseEventHandler::<Button, V>::new(ix, cx, |state, _| {
|
||||
let button = theme.button.style_for(state, false);
|
||||
Label::new(message, button.text.clone())
|
||||
.contained()
|
||||
@@ -104,7 +104,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_any_action(action.boxed_clone())
|
||||
})
|
||||
.boxed()
|
||||
|
||||
@@ -6,7 +6,7 @@ use gpui::{
|
||||
elements::*,
|
||||
geometry::{rect::RectF, vector::vec2f},
|
||||
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
|
||||
AppContext, Entity, RenderContext, View, ViewContext,
|
||||
AppContext, Entity, View, ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
@@ -94,17 +94,15 @@ impl ProjectSharedNotification {
|
||||
}
|
||||
|
||||
fn join(&mut self, _: &JoinProject, cx: &mut ViewContext<Self>) {
|
||||
let window_id = cx.window_id();
|
||||
cx.remove_window(window_id);
|
||||
cx.remove_window();
|
||||
cx.propagate_action();
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, _: &DismissProject, cx: &mut ViewContext<Self>) {
|
||||
let window_id = cx.window_id();
|
||||
cx.remove_window(window_id);
|
||||
cx.remove_window();
|
||||
}
|
||||
|
||||
fn render_owner(&self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render_owner(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let theme = &cx.global::<Settings>().theme.project_shared_notification;
|
||||
Flex::row()
|
||||
.with_children(self.owner.avatar.clone().map(|avatar| {
|
||||
@@ -164,7 +162,7 @@ impl ProjectSharedNotification {
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn render_buttons(&self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
enum Open {}
|
||||
enum Dismiss {}
|
||||
|
||||
@@ -173,7 +171,7 @@ impl ProjectSharedNotification {
|
||||
|
||||
Flex::column()
|
||||
.with_child(
|
||||
MouseEventHandler::<Open>::new(0, cx, |_, cx| {
|
||||
MouseEventHandler::<Open, Self>::new(0, cx, |_, cx| {
|
||||
let theme = &cx.global::<Settings>().theme.project_shared_notification;
|
||||
Label::new("Open", theme.open_button.text.clone())
|
||||
.aligned()
|
||||
@@ -182,7 +180,7 @@ impl ProjectSharedNotification {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(JoinProject {
|
||||
project_id,
|
||||
follow_user_id: owner_user_id,
|
||||
@@ -192,7 +190,7 @@ impl ProjectSharedNotification {
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Dismiss>::new(0, cx, |_, cx| {
|
||||
MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, cx| {
|
||||
let theme = &cx.global::<Settings>().theme.project_shared_notification;
|
||||
Label::new("Dismiss", theme.dismiss_button.text.clone())
|
||||
.aligned()
|
||||
@@ -201,7 +199,7 @@ impl ProjectSharedNotification {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, |_, cx| {
|
||||
.on_click(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(DismissProject);
|
||||
})
|
||||
.flex(1., true)
|
||||
@@ -227,7 +225,7 @@ impl View for ProjectSharedNotification {
|
||||
"ProjectSharedNotification"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::Element<Self> {
|
||||
let background = cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
|
||||
@@ -3,7 +3,7 @@ use gpui::{
|
||||
color::Color,
|
||||
elements::{MouseEventHandler, Svg},
|
||||
platform::{Appearance, MouseButton},
|
||||
AppContext, Element, ElementBox, Entity, RenderContext, View,
|
||||
AppContext, Drawable, Element, Entity, View, ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
|
||||
@@ -40,13 +40,13 @@ impl View for SharingStatusIndicator {
|
||||
"SharingStatusIndicator"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
|
||||
let color = match cx.appearance {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let color = match cx.window_appearance() {
|
||||
Appearance::Light | Appearance::VibrantLight => Color::black(),
|
||||
Appearance::Dark | Appearance::VibrantDark => Color::white(),
|
||||
};
|
||||
|
||||
MouseEventHandler::<Self>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::<Self, Self>::new(0, cx, |_, _| {
|
||||
Svg::new("icons/disable_screen_sharing_12.svg")
|
||||
.with_color(color)
|
||||
.constrained()
|
||||
@@ -54,7 +54,7 @@ impl View for SharingStatusIndicator {
|
||||
.aligned()
|
||||
.boxed()
|
||||
})
|
||||
.on_click(MouseButton::Left, |_, cx| {
|
||||
.on_click(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(ToggleScreenSharing);
|
||||
})
|
||||
.boxed()
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
use collections::CommandPaletteFilter;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::{ChildView, Flex, Label, ParentElement},
|
||||
keymap_matcher::Keystroke,
|
||||
Action, AnyViewHandle, AppContext, Element, Entity, MouseState, RenderContext, View,
|
||||
ViewContext, ViewHandle,
|
||||
actions, elements::*, keymap_matcher::Keystroke, Action, AppContext, Drawable, MouseState,
|
||||
ViewContext,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use picker::{Picker, PickerDelegate, PickerEvent};
|
||||
use settings::Settings;
|
||||
use std::cmp;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(CommandPalette::toggle);
|
||||
Picker::<CommandPalette>::init(cx);
|
||||
cx.add_action(toggle_command_palette);
|
||||
CommandPalette::init(cx);
|
||||
}
|
||||
|
||||
actions!(command_palette, [Toggle]);
|
||||
|
||||
pub struct CommandPalette {
|
||||
picker: ViewHandle<Picker<Self>>,
|
||||
pub type CommandPalette = Picker<CommandPaletteDelegate>;
|
||||
|
||||
pub struct CommandPaletteDelegate {
|
||||
actions: Vec<Command>,
|
||||
matches: Vec<StringMatch>,
|
||||
selected_ix: usize,
|
||||
@@ -42,11 +41,21 @@ struct Command {
|
||||
keystrokes: Vec<Keystroke>,
|
||||
}
|
||||
|
||||
impl CommandPalette {
|
||||
pub fn new(focused_view_id: usize, cx: &mut ViewContext<Self>) -> Self {
|
||||
let this = cx.weak_handle();
|
||||
fn toggle_command_palette(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||
let workspace = cx.handle();
|
||||
let focused_view_id = cx.focused_view_id().unwrap_or_else(|| workspace.id());
|
||||
|
||||
cx.defer(move |workspace, cx| {
|
||||
workspace.toggle_modal(cx, |_, cx| {
|
||||
cx.add_view(|cx| Picker::new(CommandPaletteDelegate::new(focused_view_id, cx), cx))
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
impl CommandPaletteDelegate {
|
||||
pub fn new(focused_view_id: usize, cx: &mut ViewContext<Picker<Self>>) -> Self {
|
||||
let actions = cx
|
||||
.available_actions(cx.window_id(), focused_view_id)
|
||||
.available_actions(focused_view_id)
|
||||
.filter_map(|(name, action, bindings)| {
|
||||
if cx.has_global::<CommandPaletteFilter>() {
|
||||
let filter = cx.global::<CommandPaletteFilter>();
|
||||
@@ -67,79 +76,20 @@ impl CommandPalette {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let picker = cx.add_view(|cx| Picker::new("Execute a command...", this, cx));
|
||||
Self {
|
||||
picker,
|
||||
actions,
|
||||
matches: vec![],
|
||||
selected_ix: 0,
|
||||
focused_view_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||
let workspace = cx.handle();
|
||||
let window_id = cx.window_id();
|
||||
let focused_view_id = cx
|
||||
.focused_view_id(window_id)
|
||||
.unwrap_or_else(|| workspace.id());
|
||||
|
||||
cx.as_mut().defer(move |cx| {
|
||||
let this = cx.add_view(&workspace, |cx| Self::new(focused_view_id, cx));
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.toggle_modal(cx, |_, cx| {
|
||||
cx.subscribe(&this, Self::on_event).detach();
|
||||
this
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
workspace: &mut Workspace,
|
||||
_: ViewHandle<Self>,
|
||||
event: &Event,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
match event {
|
||||
Event::Dismissed => workspace.dismiss_modal(cx),
|
||||
Event::Confirmed {
|
||||
window_id,
|
||||
focused_view_id,
|
||||
action,
|
||||
} => {
|
||||
let window_id = *window_id;
|
||||
let focused_view_id = *focused_view_id;
|
||||
let action = action.boxed_clone();
|
||||
workspace.dismiss_modal(cx);
|
||||
cx.as_mut()
|
||||
.defer(move |cx| cx.dispatch_any_action_at(window_id, focused_view_id, action))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for CommandPalette {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
impl View for CommandPalette {
|
||||
fn ui_name() -> &'static str {
|
||||
"CommandPalette"
|
||||
impl PickerDelegate for CommandPaletteDelegate {
|
||||
fn placeholder_text(&self) -> std::sync::Arc<str> {
|
||||
"Execute a command...".into()
|
||||
}
|
||||
|
||||
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;
|
||||
} else {
|
||||
this.selected_ix = cmp::min(this.selected_ix, this.matches.len() - 1);
|
||||
}
|
||||
});
|
||||
picker
|
||||
.update(&mut cx, |picker, _| {
|
||||
let delegate = picker.delegate_mut();
|
||||
delegate.matches = matches;
|
||||
if delegate.matches.is_empty() {
|
||||
delegate.selected_ix = 0;
|
||||
} else {
|
||||
delegate.selected_ix =
|
||||
cmp::min(delegate.selected_ix, delegate.matches.len() - 1);
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(Event::Dismissed);
|
||||
}
|
||||
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if !self.matches.is_empty() {
|
||||
let window_id = cx.window_id();
|
||||
let focused_view_id = self.focused_view_id;
|
||||
let action_ix = self.matches[self.selected_ix].candidate_id;
|
||||
cx.emit(Event::Confirmed {
|
||||
window_id: cx.window_id(),
|
||||
focused_view_id: self.focused_view_id,
|
||||
action: self.actions.remove(action_ix).action,
|
||||
let action = self.actions.remove(action_ix).action;
|
||||
cx.defer(move |_, cx| {
|
||||
cx.dispatch_any_action_at(window_id, focused_view_id, action);
|
||||
});
|
||||
} else {
|
||||
cx.emit(Event::Dismissed);
|
||||
}
|
||||
cx.emit(PickerEvent::Dismiss);
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
@@ -224,7 +176,7 @@ impl PickerDelegate for CommandPalette {
|
||||
mouse_state: &mut MouseState,
|
||||
selected: bool,
|
||||
cx: &gpui::AppContext,
|
||||
) -> gpui::ElementBox {
|
||||
) -> Element<Picker<Self>> {
|
||||
let mat = &self.matches[ix];
|
||||
let command = &self.actions[mat.candidate_id];
|
||||
let settings = cx.global::<Settings>();
|
||||
@@ -360,7 +312,7 @@ mod tests {
|
||||
});
|
||||
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
CommandPalette::toggle(workspace, &Toggle, cx)
|
||||
toggle_command_palette(workspace, &Toggle, cx);
|
||||
});
|
||||
|
||||
let palette = workspace.read_with(cx, |workspace, _| {
|
||||
@@ -369,13 +321,15 @@ mod tests {
|
||||
|
||||
palette
|
||||
.update(cx, |palette, cx| {
|
||||
palette.update_matches("bcksp".to_string(), cx)
|
||||
palette
|
||||
.delegate_mut()
|
||||
.update_matches("bcksp".to_string(), cx)
|
||||
})
|
||||
.await;
|
||||
|
||||
palette.update(cx, |palette, cx| {
|
||||
assert_eq!(palette.matches[0].string, "editor: backspace");
|
||||
palette.confirm(cx);
|
||||
assert_eq!(palette.delegate().matches[0].string, "editor: backspace");
|
||||
palette.confirm(&Default::default(), cx);
|
||||
});
|
||||
|
||||
editor.read_with(cx, |editor, cx| {
|
||||
@@ -390,7 +344,7 @@ mod tests {
|
||||
});
|
||||
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
CommandPalette::toggle(workspace, &Toggle, cx);
|
||||
toggle_command_palette(workspace, &Toggle, cx);
|
||||
});
|
||||
|
||||
// Assert editor command not present
|
||||
@@ -400,10 +354,14 @@ mod tests {
|
||||
|
||||
palette
|
||||
.update(cx, |palette, cx| {
|
||||
palette.update_matches("bcksp".to_string(), cx)
|
||||
palette
|
||||
.delegate_mut()
|
||||
.update_matches("bcksp".to_string(), cx)
|
||||
})
|
||||
.await;
|
||||
|
||||
palette.update(cx, |palette, _| assert!(palette.matches.is_empty()));
|
||||
palette.update(cx, |palette, _| {
|
||||
assert!(palette.delegate().matches.is_empty())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,13 @@ use gpui::{
|
||||
impl_internal_actions,
|
||||
keymap_matcher::KeymapContext,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Action, AnyViewHandle, AppContext, Axis, Entity, MouseState, RenderContext, SizeConstraint,
|
||||
Subscription, View, ViewContext,
|
||||
Action, AnyViewHandle, AppContext, Axis, Entity, MouseState, SizeConstraint, Subscription,
|
||||
View, ViewContext,
|
||||
};
|
||||
use menu::*;
|
||||
use settings::Settings;
|
||||
use std::{any::TypeId, borrow::Cow, time::Duration};
|
||||
|
||||
pub type StaticItem = Box<dyn Fn(&mut AppContext) -> ElementBox>;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
struct Clicked;
|
||||
|
||||
@@ -28,7 +26,10 @@ pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(ContextMenu::cancel);
|
||||
}
|
||||
|
||||
type ContextMenuItemBuilder = Box<dyn Fn(&mut MouseState, &theme::ContextMenuItem) -> ElementBox>;
|
||||
pub type StaticItem = Box<dyn Fn(&mut AppContext) -> Element<ContextMenu>>;
|
||||
|
||||
type ContextMenuItemBuilder =
|
||||
Box<dyn Fn(&mut MouseState, &theme::ContextMenuItem) -> Element<ContextMenu>>;
|
||||
|
||||
pub enum ContextMenuItemLabel {
|
||||
String(Cow<'static, str>),
|
||||
@@ -141,7 +142,7 @@ impl View for ContextMenu {
|
||||
cx
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
if !self.visible {
|
||||
return Empty::new().boxed();
|
||||
}
|
||||
@@ -151,10 +152,10 @@ impl View for ContextMenu {
|
||||
let expanded_menu = self
|
||||
.render_menu(cx)
|
||||
.constrained()
|
||||
.dynamically(move |constraint, cx| {
|
||||
.dynamically(move |constraint, view, cx| {
|
||||
SizeConstraint::strict_along(
|
||||
Axis::Horizontal,
|
||||
collapsed_menu.layout(constraint, cx).x(),
|
||||
collapsed_menu.layout(constraint, view, cx).x(),
|
||||
)
|
||||
})
|
||||
.boxed();
|
||||
@@ -209,9 +210,9 @@ impl ContextMenu {
|
||||
cx.notify();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.background().timer(Duration::from_millis(50)).await;
|
||||
this.update(&mut cx, |this, cx| this.cancel(&Default::default(), cx));
|
||||
this.update(&mut cx, |this, cx| this.cancel(&Default::default(), cx))
|
||||
})
|
||||
.detach();
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,7 +315,7 @@ impl ContextMenu {
|
||||
self.visible = true;
|
||||
self.show_count += 1;
|
||||
if !cx.is_self_focused() {
|
||||
self.previously_focused_view_id = cx.focused_view_id(cx.window_id());
|
||||
self.previously_focused_view_id = cx.focused_view_id();
|
||||
}
|
||||
cx.focus_self();
|
||||
} else {
|
||||
@@ -327,8 +328,10 @@ impl ContextMenu {
|
||||
self.position_mode = mode;
|
||||
}
|
||||
|
||||
fn render_menu_for_measurement(&self, cx: &mut RenderContext<Self>) -> impl Element {
|
||||
let window_id = cx.window_id();
|
||||
fn render_menu_for_measurement(
|
||||
&self,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl Drawable<ContextMenu> {
|
||||
let style = cx.global::<Settings>().theme.context_menu.clone();
|
||||
Flex::row()
|
||||
.with_child(
|
||||
@@ -386,7 +389,6 @@ impl ContextMenu {
|
||||
};
|
||||
|
||||
KeystrokeLabel::new(
|
||||
window_id,
|
||||
view_id,
|
||||
action.boxed_clone(),
|
||||
style.keystroke.container,
|
||||
@@ -414,14 +416,13 @@ impl ContextMenu {
|
||||
.with_style(style.container)
|
||||
}
|
||||
|
||||
fn render_menu(&self, cx: &mut RenderContext<Self>) -> impl Element {
|
||||
fn render_menu(&self, cx: &mut ViewContext<Self>) -> impl Drawable<ContextMenu> {
|
||||
enum Menu {}
|
||||
enum MenuItem {}
|
||||
|
||||
let style = cx.global::<Settings>().theme.context_menu.clone();
|
||||
|
||||
let window_id = cx.window_id();
|
||||
MouseEventHandler::<Menu>::new(0, cx, |_, cx| {
|
||||
MouseEventHandler::<Menu, ContextMenu>::new(0, cx, |_, cx| {
|
||||
Flex::column()
|
||||
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
|
||||
match item {
|
||||
@@ -435,7 +436,7 @@ impl ContextMenu {
|
||||
}
|
||||
};
|
||||
|
||||
MouseEventHandler::<MenuItem>::new(ix, cx, |state, _| {
|
||||
MouseEventHandler::<MenuItem, ContextMenu>::new(ix, cx, |state, _| {
|
||||
let style =
|
||||
style.item.style_for(state, Some(ix) == self.selected_index);
|
||||
|
||||
@@ -452,7 +453,6 @@ impl ContextMenu {
|
||||
})
|
||||
.with_child({
|
||||
KeystrokeLabel::new(
|
||||
window_id,
|
||||
view_id,
|
||||
action.boxed_clone(),
|
||||
style.keystroke.container,
|
||||
@@ -466,14 +466,14 @@ impl ContextMenu {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_up(MouseButton::Left, |_, _| {}) // Capture these events
|
||||
.on_down(MouseButton::Left, |_, _| {}) // Capture these events
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_up(MouseButton::Left, |_, _, _| {}) // Capture these events
|
||||
.on_down(MouseButton::Left, |_, _, _| {}) // Capture these events
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(Clicked);
|
||||
let window_id = cx.window_id();
|
||||
cx.dispatch_any_action_at(window_id, view_id, action.boxed_clone());
|
||||
})
|
||||
.on_drag(MouseButton::Left, |_, _| {})
|
||||
.on_drag(MouseButton::Left, |_, _, _| {})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
@@ -491,7 +491,7 @@ impl ContextMenu {
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.on_down_out(MouseButton::Left, |_, cx| cx.dispatch_action(Cancel))
|
||||
.on_down_out(MouseButton::Right, |_, cx| cx.dispatch_action(Cancel))
|
||||
.on_down_out(MouseButton::Left, |_, _, cx| cx.dispatch_action(Cancel))
|
||||
.on_down_out(MouseButton::Right, |_, _, cx| cx.dispatch_action(Cancel))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, cx: &mut
|
||||
|
||||
cx.observe(&copilot, |handle, cx| {
|
||||
let status = handle.read(cx).status();
|
||||
cx.update_global::<collections::CommandPaletteFilter, _, _>(
|
||||
move |filter, _cx| match status {
|
||||
cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
|
||||
match status {
|
||||
Status::Disabled => {
|
||||
filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
|
||||
filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE);
|
||||
@@ -62,8 +62,8 @@ pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, cx: &mut
|
||||
filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
|
||||
filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ use gpui::{
|
||||
geometry::rect::RectF,
|
||||
impl_internal_actions,
|
||||
platform::{WindowBounds, WindowKind, WindowOptions},
|
||||
AppContext, ClipboardItem, Element, Entity, View, ViewContext, ViewHandle,
|
||||
AnyViewHandle, AppContext, ClipboardItem, Drawable, Element, Entity, View, ViewContext,
|
||||
ViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use theme::ui::modal;
|
||||
@@ -31,26 +32,32 @@ pub fn init(cx: &mut AppContext) {
|
||||
match &status {
|
||||
crate::Status::SigningIn { prompt } => {
|
||||
if let Some(code_verification_handle) = code_verification.as_mut() {
|
||||
if cx.has_window(code_verification_handle.window_id()) {
|
||||
code_verification_handle.update(cx, |code_verification_view, cx| {
|
||||
code_verification_view.set_status(status, cx)
|
||||
let window_id = code_verification_handle.window_id();
|
||||
if cx.has_window(window_id) {
|
||||
cx.update_window(window_id, |cx| {
|
||||
code_verification_handle.update(cx, |code_verification, cx| {
|
||||
code_verification.set_status(status, cx)
|
||||
});
|
||||
cx.activate_window();
|
||||
});
|
||||
cx.activate_window(code_verification_handle.window_id());
|
||||
} else {
|
||||
create_copilot_auth_window(cx, &status, &mut code_verification);
|
||||
code_verification = Some(create_copilot_auth_window(cx, &status));
|
||||
}
|
||||
} else if let Some(_prompt) = prompt {
|
||||
create_copilot_auth_window(cx, &status, &mut code_verification);
|
||||
code_verification = Some(create_copilot_auth_window(cx, &status));
|
||||
}
|
||||
}
|
||||
Status::Authorized | Status::Unauthorized => {
|
||||
if let Some(code_verification) = code_verification.as_ref() {
|
||||
code_verification.update(cx, |code_verification, cx| {
|
||||
code_verification.set_status(status, cx)
|
||||
});
|
||||
let window_id = code_verification.window_id();
|
||||
cx.update_window(window_id, |cx| {
|
||||
code_verification.update(cx, |code_verification, cx| {
|
||||
code_verification.set_status(status, cx)
|
||||
});
|
||||
|
||||
cx.platform().activate(true);
|
||||
cx.activate_window(code_verification.window_id());
|
||||
cx.platform().activate(true);
|
||||
cx.activate_window();
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -73,8 +80,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
fn create_copilot_auth_window(
|
||||
cx: &mut AppContext,
|
||||
status: &Status,
|
||||
code_verification: &mut Option<ViewHandle<CopilotCodeVerification>>,
|
||||
) {
|
||||
) -> ViewHandle<CopilotCodeVerification> {
|
||||
let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions();
|
||||
let window_options = WindowOptions {
|
||||
bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
|
||||
@@ -88,7 +94,7 @@ fn create_copilot_auth_window(
|
||||
let (_, view) = cx.add_window(window_options, |_cx| {
|
||||
CopilotCodeVerification::new(status.clone())
|
||||
});
|
||||
*code_verification = Some(view);
|
||||
view
|
||||
}
|
||||
|
||||
pub struct CopilotCodeVerification {
|
||||
@@ -112,8 +118,8 @@ impl CopilotCodeVerification {
|
||||
fn render_device_code(
|
||||
data: &PromptUserDeviceFlow,
|
||||
style: &theme::Copilot,
|
||||
cx: &mut gpui::RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let copied = cx
|
||||
.read_from_clipboard()
|
||||
.map(|item| item.text() == &data.user_code)
|
||||
@@ -121,7 +127,7 @@ impl CopilotCodeVerification {
|
||||
|
||||
let device_code_style = &style.auth.prompting.device_code;
|
||||
|
||||
MouseEventHandler::<Self>::new(0, cx, |state, _cx| {
|
||||
MouseEventHandler::<Self, _>::new(0, cx, |state, _cx| {
|
||||
Flex::row()
|
||||
.with_children([
|
||||
Label::new(data.user_code.clone(), device_code_style.text.clone())
|
||||
@@ -148,7 +154,7 @@ impl CopilotCodeVerification {
|
||||
})
|
||||
.on_click(gpui::platform::MouseButton::Left, {
|
||||
let user_code = data.user_code.clone();
|
||||
move |_, cx| {
|
||||
move |_, _, cx| {
|
||||
cx.platform()
|
||||
.write_to_clipboard(ClipboardItem::new(user_code.clone()));
|
||||
cx.notify();
|
||||
@@ -162,8 +168,10 @@ impl CopilotCodeVerification {
|
||||
connect_clicked: bool,
|
||||
data: &PromptUserDeviceFlow,
|
||||
style: &theme::Copilot,
|
||||
cx: &mut gpui::RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
enum ConnectButton {}
|
||||
|
||||
Flex::column()
|
||||
.with_children([
|
||||
Flex::column()
|
||||
@@ -205,7 +213,7 @@ impl CopilotCodeVerification {
|
||||
.contained()
|
||||
.with_style(style.auth.prompting.hint.container.clone())
|
||||
.boxed(),
|
||||
theme::ui::cta_button_with_click(
|
||||
theme::ui::cta_button_with_click::<ConnectButton, _, _, _>(
|
||||
if connect_clicked {
|
||||
"Waiting for connection..."
|
||||
} else {
|
||||
@@ -216,7 +224,7 @@ impl CopilotCodeVerification {
|
||||
cx,
|
||||
{
|
||||
let verification_uri = data.verification_uri.clone();
|
||||
move |_, cx| {
|
||||
move |_, _, cx| {
|
||||
cx.platform().open_url(&verification_uri);
|
||||
cx.dispatch_action(ClickedConnect)
|
||||
}
|
||||
@@ -227,10 +235,9 @@ impl CopilotCodeVerification {
|
||||
.align_children_center()
|
||||
.boxed()
|
||||
}
|
||||
fn render_enabled_modal(
|
||||
style: &theme::Copilot,
|
||||
cx: &mut gpui::RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
fn render_enabled_modal(style: &theme::Copilot, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
enum DoneButton {}
|
||||
|
||||
let enabled_style = &style.auth.authorized;
|
||||
Flex::column()
|
||||
.with_children([
|
||||
@@ -261,15 +268,12 @@ impl CopilotCodeVerification {
|
||||
.contained()
|
||||
.with_style(enabled_style.hint.container)
|
||||
.boxed(),
|
||||
theme::ui::cta_button_with_click(
|
||||
theme::ui::cta_button_with_click::<DoneButton, _, _, _>(
|
||||
"Done",
|
||||
style.auth.content_width,
|
||||
&style.auth.cta_button,
|
||||
cx,
|
||||
|_, cx| {
|
||||
let window_id = cx.window_id();
|
||||
cx.remove_window(window_id)
|
||||
},
|
||||
|_, _, cx| cx.remove_window(),
|
||||
)
|
||||
.boxed(),
|
||||
])
|
||||
@@ -278,8 +282,8 @@ impl CopilotCodeVerification {
|
||||
}
|
||||
fn render_unauthorized_modal(
|
||||
style: &theme::Copilot,
|
||||
cx: &mut gpui::RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let unauthorized_style = &style.auth.not_authorized;
|
||||
|
||||
Flex::column()
|
||||
@@ -322,14 +326,13 @@ impl CopilotCodeVerification {
|
||||
.contained()
|
||||
.with_style(unauthorized_style.warning.container)
|
||||
.boxed(),
|
||||
theme::ui::cta_button_with_click(
|
||||
theme::ui::cta_button_with_click::<CopilotCodeVerification, _, _, _>(
|
||||
"Subscribe on GitHub",
|
||||
style.auth.content_width,
|
||||
&style.auth.cta_button,
|
||||
cx,
|
||||
|_, cx| {
|
||||
let window_id = cx.window_id();
|
||||
cx.remove_window(window_id);
|
||||
|_, _, cx| {
|
||||
cx.remove_window();
|
||||
cx.platform().open_url(COPILOT_SIGN_UP_URL)
|
||||
},
|
||||
)
|
||||
@@ -349,18 +352,20 @@ impl View for CopilotCodeVerification {
|
||||
"CopilotCodeVerification"
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext<Self>) {
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext<Self>) {
|
||||
fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
enum ConnectModal {}
|
||||
|
||||
let style = cx.global::<Settings>().theme.clone();
|
||||
|
||||
modal("Connect Copilot to Zed", &style.copilot.modal, cx, |cx| {
|
||||
modal::<ConnectModal, _, _, _>("Connect Copilot to Zed", &style.copilot.modal, cx, |cx| {
|
||||
Flex::column()
|
||||
.with_children([
|
||||
theme::ui::icon(&style.copilot.auth.header).boxed(),
|
||||
|
||||
@@ -6,8 +6,7 @@ use gpui::{
|
||||
elements::*,
|
||||
impl_internal_actions,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AppContext, Element, ElementBox, Entity, MouseState, RenderContext, Subscription, View,
|
||||
ViewContext, ViewHandle,
|
||||
AppContext, Drawable, Element, Entity, MouseState, Subscription, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use settings::{settings_file::SettingsFile, Settings};
|
||||
use workspace::{
|
||||
@@ -156,7 +155,7 @@ impl View for CopilotButton {
|
||||
"CopilotButton"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let settings = cx.global::<Settings>();
|
||||
|
||||
if !settings.features.copilot {
|
||||
@@ -176,7 +175,7 @@ impl View for CopilotButton {
|
||||
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<Self>::new(0, cx, {
|
||||
MouseEventHandler::<Self, _>::new(0, cx, {
|
||||
let theme = theme.clone();
|
||||
let status = status.clone();
|
||||
move |state, _cx| {
|
||||
@@ -218,7 +217,7 @@ impl View for CopilotButton {
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, {
|
||||
let status = status.clone();
|
||||
move |_, cx| match status {
|
||||
move |_, _, cx| match status {
|
||||
Status::Authorized => cx.dispatch_action(DeployCopilotMenu),
|
||||
Status::Error(ref e) => cx.dispatch_action(workspace::Toast::new_action(
|
||||
COPILOT_ERROR_TOAST_ID,
|
||||
@@ -229,13 +228,7 @@ impl View for CopilotButton {
|
||||
_ => cx.dispatch_action(DeployCopilotStartMenu),
|
||||
}
|
||||
})
|
||||
.with_tooltip::<Self, _>(
|
||||
0,
|
||||
"GitHub Copilot".into(),
|
||||
None,
|
||||
theme.tooltip.clone(),
|
||||
cx,
|
||||
)
|
||||
.with_tooltip::<Self>(0, "GitHub Copilot".into(), None, theme.tooltip.clone(), cx)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
|
||||
@@ -11,8 +11,7 @@ use editor::{
|
||||
};
|
||||
use gpui::{
|
||||
actions, elements::*, fonts::TextStyle, impl_internal_actions, serde_json, AnyViewHandle,
|
||||
AppContext, Entity, ModelHandle, RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
WeakViewHandle,
|
||||
AppContext, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use language::{
|
||||
Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
|
||||
@@ -90,7 +89,7 @@ impl View for ProjectDiagnosticsEditor {
|
||||
"ProjectDiagnosticsEditor"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
if self.path_states.is_empty() {
|
||||
let theme = &cx.global::<Settings>().theme.project_diagnostics;
|
||||
Label::new("No problems in workspace", theme.empty_message.clone())
|
||||
@@ -223,7 +222,7 @@ impl ProjectDiagnosticsEditor {
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.populate_excerpts(path, language_server_id, buffer, cx)
|
||||
})
|
||||
})?;
|
||||
}
|
||||
Result::<_, anyhow::Error>::Ok(())
|
||||
}
|
||||
@@ -531,12 +530,12 @@ impl ProjectDiagnosticsEditor {
|
||||
}
|
||||
|
||||
impl Item for ProjectDiagnosticsEditor {
|
||||
fn tab_content(
|
||||
fn tab_content<T: View>(
|
||||
&self,
|
||||
_detail: Option<usize>,
|
||||
style: &theme::Tab,
|
||||
cx: &AppContext,
|
||||
) -> ElementBox {
|
||||
) -> Element<T> {
|
||||
render_summary(
|
||||
&self.summary,
|
||||
&style.label.text,
|
||||
@@ -726,11 +725,11 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn render_summary(
|
||||
pub(crate) fn render_summary<T: View>(
|
||||
summary: &DiagnosticSummary,
|
||||
text_style: &TextStyle,
|
||||
theme: &theme::ProjectDiagnostics,
|
||||
) -> ElementBox {
|
||||
) -> Element<T> {
|
||||
if summary.error_count == 0 && summary.warning_count == 0 {
|
||||
Label::new("No problems", text_style.clone()).boxed()
|
||||
} else {
|
||||
@@ -804,7 +803,7 @@ mod tests {
|
||||
display_map::{BlockContext, TransformBlock},
|
||||
DisplayPoint,
|
||||
};
|
||||
use gpui::TestAppContext;
|
||||
use gpui::{TestAppContext, WindowContext};
|
||||
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
|
||||
use project::FakeFs;
|
||||
use serde_json::json;
|
||||
@@ -1479,10 +1478,8 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut AppContext) -> Vec<(u32, String)> {
|
||||
let mut presenter = cx.build_presenter(editor.id(), 0., Default::default());
|
||||
let mut cx = presenter.build_layout_context(Default::default(), false, cx);
|
||||
cx.render(editor, |editor, cx| {
|
||||
fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut WindowContext) -> Vec<(u32, String)> {
|
||||
editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
snapshot
|
||||
.blocks_in_range(0..snapshot.max_point().row())
|
||||
@@ -1490,7 +1487,7 @@ mod tests {
|
||||
let name = match block {
|
||||
TransformBlock::Custom(block) => block
|
||||
.render(&mut BlockContext {
|
||||
cx,
|
||||
view_context: cx,
|
||||
anchor_x: 0.,
|
||||
scroll_x: 0.,
|
||||
gutter_padding: 0.,
|
||||
|
||||
@@ -3,8 +3,8 @@ use editor::{Editor, GoToDiagnostic};
|
||||
use gpui::{
|
||||
elements::*,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
serde_json, AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
serde_json, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle,
|
||||
WeakViewHandle,
|
||||
};
|
||||
use language::Diagnostic;
|
||||
use lsp::LanguageServerId;
|
||||
@@ -85,14 +85,14 @@ impl View for DiagnosticIndicator {
|
||||
"DiagnosticIndicator"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
enum Summary {}
|
||||
enum Message {}
|
||||
|
||||
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
|
||||
let in_progress = !self.in_progress_checks.is_empty();
|
||||
let mut element = Flex::row().with_child(
|
||||
MouseEventHandler::<Summary>::new(0, cx, |state, cx| {
|
||||
MouseEventHandler::<Summary, _>::new(0, cx, |state, cx| {
|
||||
let style = cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
@@ -164,8 +164,10 @@ impl View for DiagnosticIndicator {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, |_, cx| cx.dispatch_action(crate::Deploy))
|
||||
.with_tooltip::<Summary, _>(
|
||||
.on_click(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(crate::Deploy)
|
||||
})
|
||||
.with_tooltip::<Summary>(
|
||||
0,
|
||||
"Project Diagnostics".to_string(),
|
||||
Some(Box::new(crate::Deploy)),
|
||||
@@ -190,7 +192,7 @@ impl View for DiagnosticIndicator {
|
||||
} else if let Some(diagnostic) = &self.current_diagnostic {
|
||||
let message_style = style.diagnostic_message.clone();
|
||||
element.add_child(
|
||||
MouseEventHandler::<Message>::new(1, cx, |state, _| {
|
||||
MouseEventHandler::<Message, _>::new(1, cx, |state, _| {
|
||||
Label::new(
|
||||
diagnostic.message.split('\n').next().unwrap().to_string(),
|
||||
message_style.style_for(state, false).text.clone(),
|
||||
@@ -201,7 +203,7 @@ impl View for DiagnosticIndicator {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, |_, cx| {
|
||||
.on_click(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(GoToDiagnostic)
|
||||
})
|
||||
.boxed(),
|
||||
|
||||
@@ -6,7 +6,7 @@ use gpui::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
platform::{CursorStyle, MouseButton},
|
||||
scene::{MouseDown, MouseDrag},
|
||||
AppContext, Element, ElementBox, EventContext, RenderContext, View, WeakViewHandle,
|
||||
Drawable, Element, View, ViewContext, WeakViewHandle, WindowContext,
|
||||
};
|
||||
|
||||
const DEAD_ZONE: f32 = 4.;
|
||||
@@ -26,7 +26,7 @@ enum State<V: View> {
|
||||
region_offset: Vector2F,
|
||||
region: RectF,
|
||||
payload: Rc<dyn Any + 'static>,
|
||||
render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
|
||||
render: Rc<dyn Fn(Rc<dyn Any>, &mut ViewContext<V>) -> Element<V>>,
|
||||
},
|
||||
Canceled,
|
||||
}
|
||||
@@ -111,7 +111,7 @@ impl<V: View> DragAndDrop<V> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn drag_started(event: MouseDown, cx: &mut EventContext) {
|
||||
pub fn drag_started(event: MouseDown, cx: &mut WindowContext) {
|
||||
cx.update_global(|this: &mut Self, _| {
|
||||
this.currently_dragged = Some(State::Down {
|
||||
region_offset: event.position - event.region.origin(),
|
||||
@@ -123,8 +123,8 @@ impl<V: View> DragAndDrop<V> {
|
||||
pub fn dragging<T: Any>(
|
||||
event: MouseDrag,
|
||||
payload: Rc<T>,
|
||||
cx: &mut EventContext,
|
||||
render: Rc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>,
|
||||
cx: &mut WindowContext,
|
||||
render: Rc<impl 'static + Fn(&T, &mut ViewContext<V>) -> Element<V>>,
|
||||
) {
|
||||
let window_id = cx.window_id();
|
||||
cx.update_global(|this: &mut Self, cx| {
|
||||
@@ -178,7 +178,7 @@ impl<V: View> DragAndDrop<V> {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn render(cx: &mut RenderContext<V>) -> Option<ElementBox> {
|
||||
pub fn render(cx: &mut ViewContext<V>) -> Option<Element<V>> {
|
||||
enum DraggedElementHandler {}
|
||||
cx.global::<Self>()
|
||||
.currently_dragged
|
||||
@@ -202,20 +202,22 @@ impl<V: View> DragAndDrop<V> {
|
||||
let position = position - region_offset;
|
||||
Some(
|
||||
Overlay::new(
|
||||
MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
|
||||
render(payload, cx)
|
||||
})
|
||||
MouseEventHandler::<DraggedElementHandler, V>::new(
|
||||
0,
|
||||
cx,
|
||||
|_, cx| render(payload, cx),
|
||||
)
|
||||
.with_cursor_style(CursorStyle::Arrow)
|
||||
.on_up(MouseButton::Left, |_, cx| {
|
||||
cx.defer(|cx| {
|
||||
.on_up(MouseButton::Left, |_, _, cx| {
|
||||
cx.window_context().defer(|cx| {
|
||||
cx.update_global::<Self, _, _>(|this, cx| {
|
||||
this.finish_dragging(cx)
|
||||
});
|
||||
});
|
||||
cx.propagate_event();
|
||||
})
|
||||
.on_up_out(MouseButton::Left, |_, cx| {
|
||||
cx.defer(|cx| {
|
||||
.on_up_out(MouseButton::Left, |_, _, cx| {
|
||||
cx.window_context().defer(|cx| {
|
||||
cx.update_global::<Self, _, _>(|this, cx| {
|
||||
this.finish_dragging(cx)
|
||||
});
|
||||
@@ -234,22 +236,22 @@ impl<V: View> DragAndDrop<V> {
|
||||
}
|
||||
|
||||
State::Canceled => Some(
|
||||
MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::<DraggedElementHandler, V>::new(0, cx, |_, _| {
|
||||
Empty::new()
|
||||
.constrained()
|
||||
.with_width(0.)
|
||||
.with_height(0.)
|
||||
.boxed()
|
||||
})
|
||||
.on_up(MouseButton::Left, |_, cx| {
|
||||
cx.defer(|cx| {
|
||||
.on_up(MouseButton::Left, |_, _, cx| {
|
||||
cx.window_context().defer(|cx| {
|
||||
cx.update_global::<Self, _, _>(|this, _| {
|
||||
this.currently_dragged = None;
|
||||
});
|
||||
});
|
||||
})
|
||||
.on_up_out(MouseButton::Left, |_, cx| {
|
||||
cx.defer(|cx| {
|
||||
.on_up_out(MouseButton::Left, |_, _, cx| {
|
||||
cx.window_context().defer(|cx| {
|
||||
cx.update_global::<Self, _, _>(|this, _| {
|
||||
this.currently_dragged = None;
|
||||
});
|
||||
@@ -261,7 +263,7 @@ impl<V: View> DragAndDrop<V> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cancel_dragging<P: Any>(&mut self, cx: &mut AppContext) {
|
||||
pub fn cancel_dragging<P: Any>(&mut self, cx: &mut WindowContext) {
|
||||
if let Some(State::Dragging {
|
||||
payload, window_id, ..
|
||||
}) = &self.currently_dragged
|
||||
@@ -274,13 +276,13 @@ impl<V: View> DragAndDrop<V> {
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_dragging(&mut self, cx: &mut AppContext) {
|
||||
fn finish_dragging(&mut self, cx: &mut WindowContext) {
|
||||
if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
|
||||
self.notify_containers_for_window(window_id, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut AppContext) {
|
||||
fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut WindowContext) {
|
||||
self.containers.retain(|container| {
|
||||
if let Some(container) = container.upgrade(cx) {
|
||||
if container.window_id() == window_id {
|
||||
@@ -294,35 +296,35 @@ impl<V: View> DragAndDrop<V> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Draggable {
|
||||
fn as_draggable<V: View, P: Any>(
|
||||
pub trait Draggable<V: View> {
|
||||
fn as_draggable<D: View, P: Any>(
|
||||
self,
|
||||
payload: P,
|
||||
render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
|
||||
render: impl 'static + Fn(&P, &mut ViewContext<D>) -> Element<D>,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl<Tag> Draggable for MouseEventHandler<Tag> {
|
||||
fn as_draggable<V: View, P: Any>(
|
||||
impl<Tag, V: View> Draggable<V> for MouseEventHandler<Tag, V> {
|
||||
fn as_draggable<D: View, P: Any>(
|
||||
self,
|
||||
payload: P,
|
||||
render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
|
||||
render: impl 'static + Fn(&P, &mut ViewContext<D>) -> Element<D>,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let payload = Rc::new(payload);
|
||||
let render = Rc::new(render);
|
||||
self.on_down(MouseButton::Left, move |e, cx| {
|
||||
self.on_down(MouseButton::Left, move |e, _, cx| {
|
||||
cx.propagate_event();
|
||||
DragAndDrop::<V>::drag_started(e, cx);
|
||||
DragAndDrop::<D>::drag_started(e, cx);
|
||||
})
|
||||
.on_drag(MouseButton::Left, move |e, cx| {
|
||||
.on_drag(MouseButton::Left, move |e, _, cx| {
|
||||
let payload = payload.clone();
|
||||
let render = render.clone();
|
||||
DragAndDrop::<V>::dragging(e, payload, cx, render)
|
||||
DragAndDrop::<D>::dragging(e, payload, cx, render)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ use super::{
|
||||
wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
|
||||
TextHighlights,
|
||||
};
|
||||
use crate::{Anchor, ExcerptId, ExcerptRange, ToPoint as _};
|
||||
use crate::{Anchor, Editor, ExcerptId, ExcerptRange, ToPoint as _};
|
||||
use collections::{Bound, HashMap, HashSet};
|
||||
use gpui::{fonts::HighlightStyle, ElementBox, RenderContext};
|
||||
use gpui::{fonts::HighlightStyle, Element, ViewContext};
|
||||
use language::{BufferSnapshot, Chunk, Patch, Point};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
@@ -50,7 +50,7 @@ struct BlockRow(u32);
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
struct WrapRow(u32);
|
||||
|
||||
pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> ElementBox>;
|
||||
pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> Element<Editor>>;
|
||||
|
||||
pub struct Block {
|
||||
id: BlockId,
|
||||
@@ -69,7 +69,7 @@ where
|
||||
pub position: P,
|
||||
pub height: u8,
|
||||
pub style: BlockStyle,
|
||||
pub render: Arc<dyn Fn(&mut BlockContext) -> ElementBox>,
|
||||
pub render: Arc<dyn Fn(&mut BlockContext) -> Element<Editor>>,
|
||||
pub disposition: BlockDisposition,
|
||||
}
|
||||
|
||||
@@ -80,8 +80,8 @@ pub enum BlockStyle {
|
||||
Sticky,
|
||||
}
|
||||
|
||||
pub struct BlockContext<'a, 'b> {
|
||||
pub cx: &'b mut RenderContext<'a, crate::Editor>,
|
||||
pub struct BlockContext<'a, 'b, 'c, 'd> {
|
||||
pub view_context: &'d mut ViewContext<'a, 'b, 'c, Editor>,
|
||||
pub anchor_x: f32,
|
||||
pub scroll_x: f32,
|
||||
pub gutter_width: f32,
|
||||
@@ -932,22 +932,22 @@ impl BlockDisposition {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Deref for BlockContext<'a, 'b> {
|
||||
type Target = RenderContext<'a, crate::Editor>;
|
||||
impl<'a, 'b, 'c, 'd> Deref for BlockContext<'a, 'b, 'c, 'd> {
|
||||
type Target = ViewContext<'a, 'b, 'c, Editor>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.cx
|
||||
self.view_context
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> DerefMut for BlockContext<'a, 'b> {
|
||||
impl DerefMut for BlockContext<'_, '_, '_, '_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.cx
|
||||
self.view_context
|
||||
}
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn render(&self, cx: &mut BlockContext) -> ElementBox {
|
||||
pub fn render(&self, cx: &mut BlockContext) -> Element<Editor> {
|
||||
self.render.lock()(cx)
|
||||
}
|
||||
|
||||
@@ -994,7 +994,7 @@ mod tests {
|
||||
use crate::display_map::suggestion_map::SuggestionMap;
|
||||
use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
|
||||
use crate::multi_buffer::MultiBuffer;
|
||||
use gpui::{elements::Empty, Element};
|
||||
use gpui::{elements::Empty, Drawable};
|
||||
use rand::prelude::*;
|
||||
use settings::Settings;
|
||||
use std::env;
|
||||
|
||||
+84
-75
@@ -41,8 +41,8 @@ use gpui::{
|
||||
keymap_matcher::KeymapContext,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
serde_json::{self, json},
|
||||
AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity,
|
||||
ModelHandle, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Drawable, Element, Entity,
|
||||
ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_popover::{hide_hover, HideHover, HoverState};
|
||||
@@ -724,8 +724,8 @@ impl ContextMenu {
|
||||
&self,
|
||||
cursor_position: DisplayPoint,
|
||||
style: EditorStyle,
|
||||
cx: &mut RenderContext<Editor>,
|
||||
) -> (DisplayPoint, ElementBox) {
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> (DisplayPoint, Element<Editor>) {
|
||||
match self {
|
||||
ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)),
|
||||
ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
|
||||
@@ -777,7 +777,7 @@ impl CompletionsMenu {
|
||||
!self.matches.is_empty()
|
||||
}
|
||||
|
||||
fn render(&self, style: EditorStyle, cx: &mut RenderContext<Editor>) -> ElementBox {
|
||||
fn render(&self, style: EditorStyle, cx: &mut ViewContext<Editor>) -> Element<Editor> {
|
||||
enum CompletionTag {}
|
||||
|
||||
let completions = self.completions.clone();
|
||||
@@ -794,7 +794,7 @@ impl CompletionsMenu {
|
||||
let completion = &completions[mat.candidate_id];
|
||||
let item_ix = start_ix + ix;
|
||||
items.push(
|
||||
MouseEventHandler::<CompletionTag>::new(
|
||||
MouseEventHandler::<CompletionTag, _>::new(
|
||||
mat.candidate_id,
|
||||
cx,
|
||||
|state, _| {
|
||||
@@ -823,7 +823,7 @@ impl CompletionsMenu {
|
||||
},
|
||||
)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_down(MouseButton::Left, move |_, cx| {
|
||||
.on_down(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(ConfirmCompletion {
|
||||
item_ix: Some(item_ix),
|
||||
});
|
||||
@@ -953,8 +953,8 @@ impl CodeActionsMenu {
|
||||
&self,
|
||||
mut cursor_position: DisplayPoint,
|
||||
style: EditorStyle,
|
||||
cx: &mut RenderContext<Editor>,
|
||||
) -> (DisplayPoint, ElementBox) {
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> (DisplayPoint, Element<Editor>) {
|
||||
enum ActionTag {}
|
||||
|
||||
let container_style = style.autocomplete.container;
|
||||
@@ -969,7 +969,7 @@ impl CodeActionsMenu {
|
||||
for (ix, action) in actions[range].iter().enumerate() {
|
||||
let item_ix = start_ix + ix;
|
||||
items.push(
|
||||
MouseEventHandler::<ActionTag>::new(item_ix, cx, |state, _| {
|
||||
MouseEventHandler::<ActionTag, _>::new(item_ix, cx, |state, _| {
|
||||
let item_style = if item_ix == selected_item {
|
||||
style.autocomplete.selected_item
|
||||
} else if state.hovered() {
|
||||
@@ -985,7 +985,7 @@ impl CodeActionsMenu {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_down(MouseButton::Left, move |_, cx| {
|
||||
.on_down(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(ConfirmCodeAction {
|
||||
item_ix: Some(item_ix),
|
||||
});
|
||||
@@ -1349,7 +1349,7 @@ impl Editor {
|
||||
self.buffer().read(cx).title(cx)
|
||||
}
|
||||
|
||||
pub fn snapshot(&mut self, cx: &mut AppContext) -> EditorSnapshot {
|
||||
pub fn snapshot(&mut self, cx: &mut WindowContext) -> EditorSnapshot {
|
||||
EditorSnapshot {
|
||||
mode: self.mode,
|
||||
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
|
||||
@@ -2518,7 +2518,7 @@ impl Editor {
|
||||
this.update_visible_copilot_suggestion(cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
})?;
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}
|
||||
@@ -2655,11 +2655,13 @@ impl Editor {
|
||||
prev_task.await;
|
||||
task = this
|
||||
.upgrade(&cx)
|
||||
.and_then(|this| this.update(&mut cx, |this, _| this.code_actions_task.take()));
|
||||
.ok_or_else(|| anyhow!("editor dropped"))?
|
||||
.update(&mut cx, |this, _| this.code_actions_task.take())?;
|
||||
}
|
||||
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("editor dropped"))?
|
||||
.update(&mut cx, |this, cx| {
|
||||
if this.focused {
|
||||
if let Some((buffer, actions)) = this.available_code_actions.clone() {
|
||||
this.show_context_menu(
|
||||
@@ -2674,8 +2676,8 @@ impl Editor {
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
@@ -2787,7 +2789,7 @@ impl Editor {
|
||||
cx,
|
||||
);
|
||||
});
|
||||
});
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2818,6 +2820,7 @@ impl Editor {
|
||||
});
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}));
|
||||
None
|
||||
@@ -2907,7 +2910,8 @@ impl Editor {
|
||||
cx,
|
||||
);
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}));
|
||||
None
|
||||
@@ -2953,20 +2957,21 @@ impl Editor {
|
||||
.flatten()
|
||||
.collect_vec();
|
||||
|
||||
this.upgrade(&cx)?.update(&mut cx, |this, cx| {
|
||||
if !completions.is_empty() {
|
||||
this.copilot_state.cycled = false;
|
||||
this.copilot_state.pending_cycling_refresh = Task::ready(None);
|
||||
this.copilot_state.completions.clear();
|
||||
this.copilot_state.active_completion_index = 0;
|
||||
this.copilot_state.excerpt_id = Some(cursor.excerpt_id);
|
||||
for completion in completions {
|
||||
this.copilot_state.push_completion(completion);
|
||||
this.upgrade(&cx)?
|
||||
.update(&mut cx, |this, cx| {
|
||||
if !completions.is_empty() {
|
||||
this.copilot_state.cycled = false;
|
||||
this.copilot_state.pending_cycling_refresh = Task::ready(None);
|
||||
this.copilot_state.completions.clear();
|
||||
this.copilot_state.active_completion_index = 0;
|
||||
this.copilot_state.excerpt_id = Some(cursor.excerpt_id);
|
||||
for completion in completions {
|
||||
this.copilot_state.push_completion(completion);
|
||||
}
|
||||
this.update_visible_copilot_suggestion(cx);
|
||||
}
|
||||
this.update_visible_copilot_suggestion(cx);
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
.log_err()?;
|
||||
Some(())
|
||||
});
|
||||
|
||||
@@ -2997,14 +3002,16 @@ impl Editor {
|
||||
})
|
||||
.await;
|
||||
|
||||
this.upgrade(&cx)?.update(&mut cx, |this, cx| {
|
||||
this.copilot_state.cycled = true;
|
||||
for completion in completions.log_err().into_iter().flatten() {
|
||||
this.copilot_state.push_completion(completion);
|
||||
}
|
||||
this.copilot_state.cycle_completions(direction);
|
||||
this.update_visible_copilot_suggestion(cx);
|
||||
});
|
||||
this.upgrade(&cx)?
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.copilot_state.cycled = true;
|
||||
for completion in completions.log_err().into_iter().flatten() {
|
||||
this.copilot_state.push_completion(completion);
|
||||
}
|
||||
this.copilot_state.cycle_completions(direction);
|
||||
this.update_visible_copilot_suggestion(cx);
|
||||
})
|
||||
.log_err()?;
|
||||
|
||||
Some(())
|
||||
});
|
||||
@@ -3123,19 +3130,19 @@ impl Editor {
|
||||
&self,
|
||||
style: &EditorStyle,
|
||||
active: bool,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> Option<ElementBox> {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Element<Self>> {
|
||||
if self.available_code_actions.is_some() {
|
||||
enum CodeActions {}
|
||||
Some(
|
||||
MouseEventHandler::<CodeActions>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<CodeActions, _>::new(0, cx, |state, _| {
|
||||
Svg::new("icons/bolt_8.svg")
|
||||
.with_color(style.code_actions.indicator.style_for(state, active).color)
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.with_padding(Padding::uniform(3.))
|
||||
.on_down(MouseButton::Left, |_, cx| {
|
||||
.on_down(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(ToggleCodeActions {
|
||||
deployed_from_indicator: true,
|
||||
});
|
||||
@@ -3154,8 +3161,8 @@ impl Editor {
|
||||
gutter_hovered: bool,
|
||||
line_height: f32,
|
||||
gutter_margin: f32,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> Vec<Option<ElementBox>> {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Vec<Option<Element<Self>>> {
|
||||
enum FoldIndicators {}
|
||||
|
||||
let style = style.folds.clone();
|
||||
@@ -3167,10 +3174,10 @@ impl Editor {
|
||||
fold_data
|
||||
.map(|(fold_status, buffer_row, active)| {
|
||||
(active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
|
||||
MouseEventHandler::<FoldIndicators>::new(
|
||||
MouseEventHandler::<FoldIndicators, _>::new(
|
||||
ix as usize,
|
||||
cx,
|
||||
|mouse_state, _| -> ElementBox {
|
||||
|mouse_state, _| -> Element<Editor> {
|
||||
Svg::new(match fold_status {
|
||||
FoldStatus::Folded => style.folded_icon.clone(),
|
||||
FoldStatus::Foldable => style.foldable_icon.clone(),
|
||||
@@ -3197,7 +3204,7 @@ impl Editor {
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.with_padding(Padding::uniform(3.))
|
||||
.on_click(MouseButton::Left, {
|
||||
move |_, cx| {
|
||||
move |_, _, cx| {
|
||||
cx.dispatch_any_action(match fold_status {
|
||||
FoldStatus::Folded => Box::new(UnfoldAt { buffer_row }),
|
||||
FoldStatus::Foldable => Box::new(FoldAt { buffer_row }),
|
||||
@@ -3222,8 +3229,8 @@ impl Editor {
|
||||
&self,
|
||||
cursor_position: DisplayPoint,
|
||||
style: EditorStyle,
|
||||
cx: &mut RenderContext<Editor>,
|
||||
) -> Option<(DisplayPoint, ElementBox)> {
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Option<(DisplayPoint, Element<Editor>)> {
|
||||
self.context_menu
|
||||
.as_ref()
|
||||
.map(|menu| menu.render(cursor_position, style, cx))
|
||||
@@ -4106,7 +4113,7 @@ impl Editor {
|
||||
|
||||
pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||
self.transact(cx, |this, cx| {
|
||||
if let Some(item) = cx.as_mut().read_from_clipboard() {
|
||||
if let Some(item) = cx.read_from_clipboard() {
|
||||
let mut clipboard_text = Cow::Borrowed(item.text());
|
||||
if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
|
||||
let old_selections = this.selections.all::<usize>(cx);
|
||||
@@ -5610,7 +5617,7 @@ impl Editor {
|
||||
let definitions = definitions.await?;
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx);
|
||||
});
|
||||
})?;
|
||||
|
||||
Ok::<(), anyhow::Error>(())
|
||||
})
|
||||
@@ -5711,7 +5718,7 @@ impl Editor {
|
||||
Self::open_locations_in_multibuffer(
|
||||
workspace, locations, replica_id, title, cx,
|
||||
);
|
||||
});
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
@@ -5899,7 +5906,7 @@ impl Editor {
|
||||
editor: rename_editor,
|
||||
block_id,
|
||||
});
|
||||
});
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -5945,7 +5952,7 @@ impl Editor {
|
||||
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
editor.refresh_document_highlights(cx);
|
||||
});
|
||||
})?;
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
@@ -5988,7 +5995,7 @@ impl Editor {
|
||||
self.pending_rename.as_ref()
|
||||
}
|
||||
|
||||
fn format(&mut self, _: &Format, cx: &mut ViewContext<'_, Self>) -> Option<Task<Result<()>>> {
|
||||
fn format(&mut self, _: &Format, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
|
||||
let project = match &self.project {
|
||||
Some(project) => project.clone(),
|
||||
None => return None,
|
||||
@@ -6001,7 +6008,7 @@ impl Editor {
|
||||
&mut self,
|
||||
project: ModelHandle<Project>,
|
||||
trigger: FormatTrigger,
|
||||
cx: &mut ViewContext<'_, Self>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let buffer = self.buffer().clone();
|
||||
let buffers = buffer.read(cx).all_buffers();
|
||||
@@ -6747,9 +6754,16 @@ impl Editor {
|
||||
let position = action.position;
|
||||
let anchor = action.anchor;
|
||||
cx.spawn_weak(|_, mut cx| async move {
|
||||
let editor = editor.await.log_err()?.downcast::<Editor>()?;
|
||||
let editor = editor
|
||||
.await?
|
||||
.downcast::<Editor>()
|
||||
.ok_or_else(|| anyhow!("opened item was not an editor"))?;
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx).as_singleton()?;
|
||||
let buffer = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.ok_or_else(|| anyhow!("cannot jump in a multi-buffer"))?;
|
||||
let buffer = buffer.read(cx);
|
||||
let cursor = if buffer.can_resolve(&anchor) {
|
||||
language::ToPoint::to_point(&anchor, buffer)
|
||||
@@ -6763,11 +6777,11 @@ impl Editor {
|
||||
});
|
||||
editor.nav_history = nav_history;
|
||||
|
||||
Some(())
|
||||
})?;
|
||||
Some(())
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach()
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
|
||||
@@ -6993,7 +7007,7 @@ impl Entity for Editor {
|
||||
}
|
||||
|
||||
impl View for Editor {
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let style = self.style(cx);
|
||||
let font_changed = self.display_map.update(cx, |map, cx| {
|
||||
map.set_fold_ellipses_color(style.folds.ellipses.text_color);
|
||||
@@ -7001,19 +7015,14 @@ impl View for Editor {
|
||||
});
|
||||
|
||||
if font_changed {
|
||||
let handle = self.handle.clone();
|
||||
cx.defer(move |cx| {
|
||||
if let Some(editor) = handle.upgrade(cx) {
|
||||
editor.update(cx, |editor, cx| {
|
||||
hide_hover(editor, &HideHover, cx);
|
||||
hide_link_definition(editor, cx);
|
||||
})
|
||||
}
|
||||
cx.defer(move |editor, cx: &mut ViewContext<Editor>| {
|
||||
hide_hover(editor, &HideHover, cx);
|
||||
hide_link_definition(editor, cx);
|
||||
});
|
||||
}
|
||||
|
||||
Stack::new()
|
||||
.with_child(EditorElement::new(self.handle.clone(), style.clone()).boxed())
|
||||
.with_child(EditorElement::new(style.clone()).boxed())
|
||||
.with_child(ChildView::new(&self.mouse_context_menu, cx).boxed())
|
||||
.boxed()
|
||||
}
|
||||
|
||||
+276
-240
@@ -9,7 +9,7 @@ use gpui::{
|
||||
executor::Deterministic,
|
||||
geometry::{rect::RectF, vector::vec2f},
|
||||
platform::{WindowBounds, WindowOptions},
|
||||
serde_json,
|
||||
serde_json, TestAppContext,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language::{BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point};
|
||||
@@ -23,13 +23,13 @@ use util::{
|
||||
test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
|
||||
};
|
||||
use workspace::{
|
||||
item::{FollowableItem, ItemHandle},
|
||||
item::{FollowableItem, Item, ItemHandle},
|
||||
NavigationEntry, Pane, ViewId,
|
||||
};
|
||||
|
||||
#[gpui::test]
|
||||
fn test_edit_events(cx: &mut AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn test_edit_events(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let buffer = cx.add_model(|cx| {
|
||||
let mut buffer = language::Buffer::new(0, "123456", cx);
|
||||
buffer.set_group_interval(Duration::from_secs(1));
|
||||
@@ -37,7 +37,7 @@ fn test_edit_events(cx: &mut AppContext) {
|
||||
});
|
||||
|
||||
let events = Rc::new(RefCell::new(Vec::new()));
|
||||
let (_, editor1) = cx.add_window(Default::default(), {
|
||||
let (_, editor1) = cx.add_window({
|
||||
let events = events.clone();
|
||||
|cx| {
|
||||
cx.subscribe(&cx.handle(), move |_, _, event, _| {
|
||||
@@ -52,7 +52,7 @@ fn test_edit_events(cx: &mut AppContext) {
|
||||
Editor::for_buffer(buffer.clone(), None, cx)
|
||||
}
|
||||
});
|
||||
let (_, editor2) = cx.add_window(Default::default(), {
|
||||
let (_, editor2) = cx.add_window({
|
||||
let events = events.clone();
|
||||
|cx| {
|
||||
cx.subscribe(&cx.handle(), move |_, _, event, _| {
|
||||
@@ -155,13 +155,13 @@ fn test_edit_events(cx: &mut AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_undo_redo_with_selection_restoration(cx: &mut AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let mut now = Instant::now();
|
||||
let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
|
||||
let group_interval = buffer.read(cx).transaction_group_interval();
|
||||
let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
|
||||
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
|
||||
let (_, editor) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.start_transaction_at(now, cx);
|
||||
@@ -225,8 +225,8 @@ fn test_undo_redo_with_selection_restoration(cx: &mut AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_ime_composition(cx: &mut AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn test_ime_composition(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let buffer = cx.add_model(|cx| {
|
||||
let mut buffer = language::Buffer::new(0, "abcde", cx);
|
||||
// Ensure automatic grouping doesn't occur.
|
||||
@@ -235,7 +235,7 @@ fn test_ime_composition(cx: &mut AppContext) {
|
||||
});
|
||||
|
||||
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
cx.add_window(Default::default(), |cx| {
|
||||
cx.add_window(|cx| {
|
||||
let mut editor = build_editor(buffer.clone(), cx);
|
||||
|
||||
// Start a new IME composition.
|
||||
@@ -327,11 +327,13 @@ fn test_ime_composition(cx: &mut AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_selection_with_mouse(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn test_selection_with_mouse(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
|
||||
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
|
||||
let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
let (_, editor) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
editor.update(cx, |view, cx| {
|
||||
view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
|
||||
});
|
||||
@@ -392,10 +394,12 @@ fn test_selection_with_mouse(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_canceling_pending_selection(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
fn test_canceling_pending_selection(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
|
||||
@@ -424,7 +428,7 @@ fn test_canceling_pending_selection(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_clone(cx: &mut gpui::AppContext) {
|
||||
fn test_clone(cx: &mut TestAppContext) {
|
||||
let (text, selection_ranges) = marked_text_ranges(
|
||||
indoc! {"
|
||||
one
|
||||
@@ -435,10 +439,12 @@ fn test_clone(cx: &mut gpui::AppContext) {
|
||||
"},
|
||||
true,
|
||||
);
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&text, cx);
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
|
||||
let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
let (_, editor) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(&text, cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
|
||||
@@ -470,8 +476,8 @@ fn test_clone(cx: &mut gpui::AppContext) {
|
||||
snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
|
||||
);
|
||||
assert_set_eq!(
|
||||
cloned_editor.read(cx).selections.ranges::<Point>(cx),
|
||||
editor.read(cx).selections.ranges(cx)
|
||||
cloned_editor.read_with(cx, |editor, cx| editor.selections.ranges::<Point>(cx)),
|
||||
editor.read_with(cx, |editor, cx| editor.selections.ranges(cx))
|
||||
);
|
||||
assert_set_eq!(
|
||||
cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)),
|
||||
@@ -480,19 +486,19 @@ fn test_clone(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_navigation_history(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn test_navigation_history(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
cx.set_global(DragAndDrop::<Workspace>::default());
|
||||
use workspace::item::Item;
|
||||
let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(0, None, || &[], cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
|
||||
let (_, pane) = cx.add_window(|cx| Pane::new(0, None, || &[], cx));
|
||||
|
||||
cx.add_view(&pane, |cx| {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
|
||||
let mut editor = build_editor(buffer.clone(), cx);
|
||||
let handle = cx.handle();
|
||||
editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
|
||||
|
||||
fn pop_history(editor: &mut Editor, cx: &mut AppContext) -> Option<NavigationEntry> {
|
||||
fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
|
||||
editor.nav_history.as_mut().unwrap().pop_backward(cx)
|
||||
}
|
||||
|
||||
@@ -590,10 +596,12 @@ fn test_navigation_history(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_cancel(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
fn test_cancel(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
|
||||
@@ -630,30 +638,32 @@ fn test_cancel(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_fold_action(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(
|
||||
&"
|
||||
impl Foo {
|
||||
// Hello!
|
||||
fn test_fold_action(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(
|
||||
&"
|
||||
impl Foo {
|
||||
// Hello!
|
||||
|
||||
fn a() {
|
||||
1
|
||||
}
|
||||
fn a() {
|
||||
1
|
||||
}
|
||||
|
||||
fn b() {
|
||||
2
|
||||
}
|
||||
fn b() {
|
||||
2
|
||||
}
|
||||
|
||||
fn c() {
|
||||
3
|
||||
fn c() {
|
||||
3
|
||||
}
|
||||
}
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
build_editor(buffer.clone(), cx)
|
||||
});
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.change_selections(None, cx, |s| {
|
||||
@@ -712,15 +722,15 @@ fn test_fold_action(cx: &mut gpui::AppContext) {
|
||||
);
|
||||
|
||||
view.unfold_lines(&UnfoldLines, cx);
|
||||
assert_eq!(view.display_text(cx), buffer.read(cx).read(cx).text());
|
||||
assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_move_cursor(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
|
||||
fn test_move_cursor(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
|
||||
let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
|
||||
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
@@ -732,7 +742,6 @@ fn test_move_cursor(cx: &mut gpui::AppContext) {
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
assert_eq!(
|
||||
view.selections.display_ranges(cx),
|
||||
@@ -793,10 +802,12 @@ fn test_move_cursor(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_move_cursor_multibyte(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
|
||||
fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
|
||||
build_editor(buffer.clone(), cx)
|
||||
});
|
||||
|
||||
assert_eq!('ⓐ'.len_utf8(), 3);
|
||||
assert_eq!('α'.len_utf8(), 2);
|
||||
@@ -895,10 +906,12 @@ fn test_move_cursor_multibyte(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_move_cursor_different_line_lengths(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
|
||||
fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
|
||||
build_editor(buffer.clone(), cx)
|
||||
});
|
||||
view.update(cx, |view, cx| {
|
||||
view.change_selections(None, cx, |s| {
|
||||
s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
|
||||
@@ -942,10 +955,12 @@ fn test_move_cursor_different_line_lengths(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_beginning_end_of_line(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("abc\n def", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
fn test_beginning_end_of_line(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("abc\n def", cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
view.update(cx, |view, cx| {
|
||||
view.change_selections(None, cx, |s| {
|
||||
s.select_display_ranges([
|
||||
@@ -1102,10 +1117,12 @@ fn test_beginning_end_of_line(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_prev_next_word_boundary(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
view.update(cx, |view, cx| {
|
||||
view.change_selections(None, cx, |s| {
|
||||
s.select_display_ranges([
|
||||
@@ -1151,10 +1168,12 @@ fn test_prev_next_word_boundary(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.set_wrap_width(Some(140.), cx);
|
||||
@@ -1330,10 +1349,12 @@ async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_delete_to_word_boundary(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("one two three four", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
|
||||
fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("one two three four", cx);
|
||||
build_editor(buffer.clone(), cx)
|
||||
});
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.change_selections(None, cx, |s| {
|
||||
@@ -1345,10 +1366,9 @@ fn test_delete_to_word_boundary(cx: &mut gpui::AppContext) {
|
||||
])
|
||||
});
|
||||
view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
|
||||
assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
|
||||
});
|
||||
|
||||
assert_eq!(buffer.read(cx).read(cx).text(), "e two te four");
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.change_selections(None, cx, |s| {
|
||||
s.select_display_ranges([
|
||||
@@ -1359,16 +1379,17 @@ fn test_delete_to_word_boundary(cx: &mut gpui::AppContext) {
|
||||
])
|
||||
});
|
||||
view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
|
||||
assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
|
||||
});
|
||||
|
||||
assert_eq!(buffer.read(cx).read(cx).text(), "e t te our");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_newline(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
|
||||
fn test_newline(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
|
||||
build_editor(buffer.clone(), cx)
|
||||
});
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.change_selections(None, cx, |s| {
|
||||
@@ -1385,24 +1406,23 @@ fn test_newline(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_newline_with_old_selections(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(
|
||||
"
|
||||
a
|
||||
b(
|
||||
X
|
||||
)
|
||||
c(
|
||||
X
|
||||
)
|
||||
"
|
||||
.unindent()
|
||||
.as_str(),
|
||||
cx,
|
||||
);
|
||||
|
||||
let (_, editor) = cx.add_window(Default::default(), |cx| {
|
||||
fn test_newline_with_old_selections(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, editor) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(
|
||||
"
|
||||
a
|
||||
b(
|
||||
X
|
||||
)
|
||||
c(
|
||||
X
|
||||
)
|
||||
"
|
||||
.unindent()
|
||||
.as_str(),
|
||||
cx,
|
||||
);
|
||||
let mut editor = build_editor(buffer.clone(), cx);
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([
|
||||
@@ -1413,28 +1433,27 @@ fn test_newline_with_old_selections(cx: &mut gpui::AppContext) {
|
||||
editor
|
||||
});
|
||||
|
||||
// Edit the buffer directly, deleting ranges surrounding the editor's selections
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
[
|
||||
(Point::new(1, 2)..Point::new(3, 0), ""),
|
||||
(Point::new(4, 2)..Point::new(6, 0), ""),
|
||||
],
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.read(cx).text(),
|
||||
"
|
||||
a
|
||||
b()
|
||||
c()
|
||||
"
|
||||
.unindent()
|
||||
);
|
||||
});
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
// Edit the buffer directly, deleting ranges surrounding the editor's selections
|
||||
editor.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
[
|
||||
(Point::new(1, 2)..Point::new(3, 0), ""),
|
||||
(Point::new(4, 2)..Point::new(6, 0), ""),
|
||||
],
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.read(cx).text(),
|
||||
"
|
||||
a
|
||||
b()
|
||||
c()
|
||||
"
|
||||
.unindent()
|
||||
);
|
||||
});
|
||||
assert_eq!(
|
||||
editor.selections.ranges(cx),
|
||||
&[
|
||||
@@ -1566,22 +1585,21 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_insert_with_old_selections(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
|
||||
let (_, editor) = cx.add_window(Default::default(), |cx| {
|
||||
fn test_insert_with_old_selections(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, editor) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
|
||||
let mut editor = build_editor(buffer.clone(), cx);
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
|
||||
editor
|
||||
});
|
||||
|
||||
// Edit the buffer directly, deleting ranges surrounding the editor's selections
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
|
||||
assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
|
||||
});
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
// Edit the buffer directly, deleting ranges surrounding the editor's selections
|
||||
editor.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
|
||||
assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
|
||||
});
|
||||
assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
|
||||
|
||||
editor.insert("Z", cx);
|
||||
@@ -1885,24 +1903,26 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_indent_outdent_with_excerpts(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(
|
||||
Settings::test(cx)
|
||||
.with_language_defaults(
|
||||
"TOML",
|
||||
EditorSettings {
|
||||
tab_size: Some(2.try_into().unwrap()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.with_language_defaults(
|
||||
"Rust",
|
||||
EditorSettings {
|
||||
tab_size: Some(4.try_into().unwrap()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
);
|
||||
fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
cx.set_global(
|
||||
Settings::test(cx)
|
||||
.with_language_defaults(
|
||||
"TOML",
|
||||
EditorSettings {
|
||||
tab_size: Some(2.try_into().unwrap()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.with_language_defaults(
|
||||
"Rust",
|
||||
EditorSettings {
|
||||
tab_size: Some(4.try_into().unwrap()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
let toml_language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "TOML".into(),
|
||||
@@ -1944,7 +1964,7 @@ fn test_indent_outdent_with_excerpts(cx: &mut gpui::AppContext) {
|
||||
multibuffer
|
||||
});
|
||||
|
||||
cx.add_window(Default::default(), |cx| {
|
||||
cx.add_window(|cx| {
|
||||
let mut editor = build_editor(multibuffer, cx);
|
||||
|
||||
assert_eq!(
|
||||
@@ -2071,10 +2091,12 @@ async fn test_delete(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_delete_line(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
fn test_delete_line(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
view.update(cx, |view, cx| {
|
||||
view.change_selections(None, cx, |s| {
|
||||
s.select_display_ranges([
|
||||
@@ -2094,9 +2116,11 @@ fn test_delete_line(cx: &mut gpui::AppContext) {
|
||||
);
|
||||
});
|
||||
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
view.update(cx, |view, cx| {
|
||||
view.change_selections(None, cx, |s| {
|
||||
s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
|
||||
@@ -2111,10 +2135,12 @@ fn test_delete_line(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_duplicate_line(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
fn test_duplicate_line(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
view.update(cx, |view, cx| {
|
||||
view.change_selections(None, cx, |s| {
|
||||
s.select_display_ranges([
|
||||
@@ -2137,8 +2163,10 @@ fn test_duplicate_line(cx: &mut gpui::AppContext) {
|
||||
);
|
||||
});
|
||||
|
||||
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
view.update(cx, |view, cx| {
|
||||
view.change_selections(None, cx, |s| {
|
||||
s.select_display_ranges([
|
||||
@@ -2159,10 +2187,12 @@ fn test_duplicate_line(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_move_line_up_down(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
fn test_move_line_up_down(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
view.update(cx, |view, cx| {
|
||||
view.fold_ranges(
|
||||
vec![
|
||||
@@ -2255,12 +2285,14 @@ fn test_move_line_up_down(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_move_line_up_down_with_blocks(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
|
||||
let snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, editor) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.buffer.read(cx).snapshot(cx);
|
||||
editor.insert_blocks(
|
||||
[BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
@@ -2279,11 +2311,11 @@ fn test_move_line_up_down_with_blocks(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_transpose(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn test_transpose(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
|
||||
_ = cx
|
||||
.add_window(Default::default(), |cx| {
|
||||
.add_window(|cx| {
|
||||
let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
|
||||
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
|
||||
@@ -2304,7 +2336,7 @@ fn test_transpose(cx: &mut gpui::AppContext) {
|
||||
.1;
|
||||
|
||||
_ = cx
|
||||
.add_window(Default::default(), |cx| {
|
||||
.add_window(|cx| {
|
||||
let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
|
||||
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
|
||||
@@ -2330,7 +2362,7 @@ fn test_transpose(cx: &mut gpui::AppContext) {
|
||||
.1;
|
||||
|
||||
_ = cx
|
||||
.add_window(Default::default(), |cx| {
|
||||
.add_window(|cx| {
|
||||
let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
|
||||
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
|
||||
@@ -2359,7 +2391,7 @@ fn test_transpose(cx: &mut gpui::AppContext) {
|
||||
.1;
|
||||
|
||||
_ = cx
|
||||
.add_window(Default::default(), |cx| {
|
||||
.add_window(|cx| {
|
||||
let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
|
||||
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
|
||||
@@ -2573,10 +2605,12 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_select_all(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
fn test_select_all(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
view.update(cx, |view, cx| {
|
||||
view.select_all(&SelectAll, cx);
|
||||
assert_eq!(
|
||||
@@ -2587,10 +2621,12 @@ fn test_select_all(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_select_line(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
fn test_select_line(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
view.update(cx, |view, cx| {
|
||||
view.change_selections(None, cx, |s| {
|
||||
s.select_display_ranges([
|
||||
@@ -2631,10 +2667,12 @@ fn test_select_line(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_split_selection_into_lines(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
fn test_split_selection_into_lines(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
view.update(cx, |view, cx| {
|
||||
view.fold_ranges(
|
||||
vec![
|
||||
@@ -2699,10 +2737,12 @@ fn test_split_selection_into_lines(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_add_selection_above_below(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
fn test_add_selection_above_below(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, view) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.change_selections(None, cx, |s| {
|
||||
@@ -4022,7 +4062,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
|
||||
assert!(cx.read(|cx| editor.is_dirty(cx)));
|
||||
|
||||
let save = cx.update(|cx| editor.save(project.clone(), cx));
|
||||
let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
|
||||
fake_server
|
||||
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||
assert_eq!(
|
||||
@@ -4057,7 +4097,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
futures::future::pending::<()>().await;
|
||||
unreachable!()
|
||||
});
|
||||
let save = cx.update(|cx| editor.save(project.clone(), cx));
|
||||
let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
|
||||
cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
|
||||
cx.foreground().start_waiting();
|
||||
save.await.unwrap();
|
||||
@@ -4080,7 +4120,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
})
|
||||
});
|
||||
|
||||
let save = cx.update(|cx| editor.save(project.clone(), cx));
|
||||
let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
|
||||
fake_server
|
||||
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||
assert_eq!(
|
||||
@@ -4136,7 +4176,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
|
||||
assert!(cx.read(|cx| editor.is_dirty(cx)));
|
||||
|
||||
let save = cx.update(|cx| editor.save(project.clone(), cx));
|
||||
let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
|
||||
fake_server
|
||||
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
|
||||
assert_eq!(
|
||||
@@ -4173,7 +4213,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
unreachable!()
|
||||
},
|
||||
);
|
||||
let save = cx.update(|cx| editor.save(project.clone(), cx));
|
||||
let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
|
||||
cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
|
||||
cx.foreground().start_waiting();
|
||||
save.await.unwrap();
|
||||
@@ -4196,7 +4236,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
})
|
||||
});
|
||||
|
||||
let save = cx.update(|cx| editor.save(project.clone(), cx));
|
||||
let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
|
||||
fake_server
|
||||
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
|
||||
assert_eq!(
|
||||
@@ -4977,8 +5017,8 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_editing_disjoint_excerpts(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
|
||||
let multibuffer = cx.add_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0);
|
||||
@@ -4996,12 +5036,11 @@ fn test_editing_disjoint_excerpts(cx: &mut gpui::AppContext) {
|
||||
],
|
||||
cx,
|
||||
);
|
||||
assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
|
||||
multibuffer
|
||||
});
|
||||
|
||||
assert_eq!(multibuffer.read(cx).read(cx).text(), "aaaa\nbbbb");
|
||||
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
|
||||
let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
|
||||
view.update(cx, |view, cx| {
|
||||
assert_eq!(view.text(cx), "aaaa\nbbbb");
|
||||
view.change_selections(None, cx, |s| {
|
||||
@@ -5024,8 +5063,8 @@ fn test_editing_disjoint_excerpts(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_editing_overlapping_excerpts(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let markers = vec![('[', ']').into(), ('(', ')').into()];
|
||||
let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
|
||||
indoc! {"
|
||||
@@ -5049,7 +5088,7 @@ fn test_editing_overlapping_excerpts(cx: &mut gpui::AppContext) {
|
||||
multibuffer
|
||||
});
|
||||
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
|
||||
let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
|
||||
view.update(cx, |view, cx| {
|
||||
let (expected_text, selection_ranges) = marked_text_ranges(
|
||||
indoc! {"
|
||||
@@ -5097,8 +5136,8 @@ fn test_editing_overlapping_excerpts(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_refresh_selections(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn test_refresh_selections(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
|
||||
let mut excerpt1_id = None;
|
||||
let multibuffer = cx.add_model(|cx| {
|
||||
@@ -5120,13 +5159,11 @@ fn test_refresh_selections(cx: &mut gpui::AppContext) {
|
||||
)
|
||||
.into_iter()
|
||||
.next();
|
||||
assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
|
||||
multibuffer
|
||||
});
|
||||
assert_eq!(
|
||||
multibuffer.read(cx).read(cx).text(),
|
||||
"aaaa\nbbbb\nbbbb\ncccc"
|
||||
);
|
||||
let (_, editor) = cx.add_window(Default::default(), |cx| {
|
||||
|
||||
let (_, editor) = cx.add_window(|cx| {
|
||||
let mut editor = build_editor(multibuffer.clone(), cx);
|
||||
let snapshot = editor.snapshot(cx);
|
||||
editor.change_selections(None, cx, |s| {
|
||||
@@ -5183,8 +5220,8 @@ fn test_refresh_selections(cx: &mut gpui::AppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
|
||||
let mut excerpt1_id = None;
|
||||
let multibuffer = cx.add_model(|cx| {
|
||||
@@ -5206,13 +5243,11 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::AppContext)
|
||||
)
|
||||
.into_iter()
|
||||
.next();
|
||||
assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
|
||||
multibuffer
|
||||
});
|
||||
assert_eq!(
|
||||
multibuffer.read(cx).read(cx).text(),
|
||||
"aaaa\nbbbb\nbbbb\ncccc"
|
||||
);
|
||||
let (_, editor) = cx.add_window(Default::default(), |cx| {
|
||||
|
||||
let (_, editor) = cx.add_window(|cx| {
|
||||
let mut editor = build_editor(multibuffer.clone(), cx);
|
||||
let snapshot = editor.snapshot(cx);
|
||||
editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
|
||||
@@ -5316,17 +5351,18 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_highlighted_ranges(cx: &mut gpui::AppContext) {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
|
||||
|
||||
cx.set_global(Settings::test(cx));
|
||||
let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
|
||||
fn test_highlighted_ranges(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let (_, editor) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
|
||||
build_editor(buffer.clone(), cx)
|
||||
});
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
struct Type1;
|
||||
struct Type2;
|
||||
|
||||
let buffer = buffer.read(cx).snapshot(cx);
|
||||
let buffer = editor.buffer.read(cx).snapshot(cx);
|
||||
|
||||
let anchor_range =
|
||||
|range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
|
||||
|
||||
+461
-481
File diff suppressed because it is too large
Load Diff
@@ -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)),
|
||||
|
||||
+24
-24
@@ -8,7 +8,7 @@ use collections::HashSet;
|
||||
use futures::future::try_join_all;
|
||||
use gpui::{
|
||||
elements::*, geometry::vector::vec2f, AppContext, AsyncAppContext, Entity, ModelHandle,
|
||||
RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use language::{
|
||||
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
|
||||
@@ -28,7 +28,7 @@ use std::{
|
||||
};
|
||||
use text::Selection;
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use workspace::item::FollowableItemHandle;
|
||||
use workspace::item::{BreadcrumbText, FollowableItemHandle};
|
||||
use workspace::{
|
||||
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
|
||||
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
||||
@@ -80,7 +80,9 @@ impl FollowableItem for Editor {
|
||||
})
|
||||
});
|
||||
|
||||
let editor = editor.unwrap_or_else(|| {
|
||||
let editor = if let Some(editor) = editor {
|
||||
editor
|
||||
} else {
|
||||
pane.update(&mut cx, |_, cx| {
|
||||
let multibuffer = cx.add_model(|cx| {
|
||||
let mut multibuffer;
|
||||
@@ -121,8 +123,8 @@ impl FollowableItem for Editor {
|
||||
editor.remote_id = Some(remote_id);
|
||||
editor
|
||||
})
|
||||
})
|
||||
});
|
||||
})?
|
||||
};
|
||||
|
||||
update_editor_from_message(
|
||||
editor.clone(),
|
||||
@@ -367,7 +369,7 @@ async fn update_editor_from_message(
|
||||
|
||||
multibuffer.remove_excerpts(removed_excerpt_ids, cx);
|
||||
});
|
||||
});
|
||||
})?;
|
||||
|
||||
// Deserialize the editor state.
|
||||
let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
|
||||
@@ -384,7 +386,7 @@ async fn update_editor_from_message(
|
||||
.scroll_top_anchor
|
||||
.and_then(|anchor| deserialize_anchor(&buffer, anchor));
|
||||
anyhow::Ok((selections, pending_selection, scroll_top_anchor))
|
||||
})?;
|
||||
})??;
|
||||
|
||||
// Wait until the buffer has received all of the operations referenced by
|
||||
// the editor's new state.
|
||||
@@ -399,7 +401,7 @@ async fn update_editor_from_message(
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
})?
|
||||
.await?;
|
||||
|
||||
// Update the editor's state.
|
||||
@@ -416,7 +418,7 @@ async fn update_editor_from_message(
|
||||
cx,
|
||||
);
|
||||
}
|
||||
});
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -556,12 +558,12 @@ impl Item for Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn tab_content(
|
||||
fn tab_content<T: View>(
|
||||
&self,
|
||||
detail: Option<usize>,
|
||||
style: &theme::Tab,
|
||||
cx: &AppContext,
|
||||
) -> ElementBox {
|
||||
) -> Element<T> {
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Label::new(self.title(cx).to_string(), style.label.clone())
|
||||
@@ -641,7 +643,7 @@ impl Item for Editor {
|
||||
self.report_event("save editor", cx);
|
||||
let format = self.perform_format(project.clone(), FormatTrigger::Save, cx);
|
||||
let buffers = self.buffer().clone().read(cx).all_buffers();
|
||||
cx.as_mut().spawn(|mut cx| async move {
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
format.await?;
|
||||
|
||||
if buffers.len() == 1 {
|
||||
@@ -705,7 +707,7 @@ impl Item for Editor {
|
||||
let transaction = reload_buffers.log_err().await;
|
||||
this.update(&mut cx, |editor, cx| {
|
||||
editor.request_autoscroll(Autoscroll::fit(), cx)
|
||||
});
|
||||
})?;
|
||||
buffer.update(&mut cx, |buffer, _| {
|
||||
if let Some(transaction) = transaction {
|
||||
if !buffer.is_singleton() {
|
||||
@@ -762,7 +764,7 @@ impl Item for Editor {
|
||||
ToolbarItemLocation::PrimaryLeft { flex: None }
|
||||
}
|
||||
|
||||
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
|
||||
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
|
||||
let cursor = self.selections.newest_anchor().head();
|
||||
let multibuffer = &self.buffer().read(cx);
|
||||
let (buffer_id, symbols) =
|
||||
@@ -782,15 +784,13 @@ impl Item for Editor {
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "untitled".to_string());
|
||||
|
||||
let filename_label = Label::new(filename, theme.workspace.breadcrumbs.default.text.clone());
|
||||
let mut breadcrumbs = vec![filename_label.boxed()];
|
||||
breadcrumbs.extend(symbols.into_iter().map(|symbol| {
|
||||
Text::new(
|
||||
symbol.text,
|
||||
theme.workspace.breadcrumbs.default.text.clone(),
|
||||
)
|
||||
.with_highlights(symbol.highlight_ranges)
|
||||
.boxed()
|
||||
let mut breadcrumbs = vec![BreadcrumbText {
|
||||
text: filename,
|
||||
highlights: None,
|
||||
}];
|
||||
breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
|
||||
text: symbol.text,
|
||||
highlights: Some(symbol.highlight_ranges),
|
||||
}));
|
||||
Some(breadcrumbs)
|
||||
}
|
||||
@@ -1113,7 +1113,7 @@ impl View for CursorPosition {
|
||||
"CursorPosition"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
if let Some(position) = self.position {
|
||||
let theme = &cx.global::<Settings>().theme.workspace.status_bar;
|
||||
let mut text = format!("{},{}", position.row + 1, position.column + 1);
|
||||
|
||||
@@ -261,7 +261,7 @@ pub fn show_link_definition(
|
||||
hide_link_definition(this, cx);
|
||||
}
|
||||
}
|
||||
})
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
|
||||
@@ -248,10 +248,12 @@ impl ScrollManager {
|
||||
self.hide_scrollbar_task = Some(cx.spawn_weak(|editor, mut cx| async move {
|
||||
cx.background().timer(SCROLLBAR_SHOW_INTERVAL).await;
|
||||
if let Some(editor) = editor.upgrade(&cx) {
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
editor.scroll_manager.show_scrollbars = false;
|
||||
cx.notify();
|
||||
});
|
||||
editor
|
||||
.update(&mut cx, |editor, cx| {
|
||||
editor.scroll_manager.show_scrollbars = false;
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
|
||||
@@ -34,11 +34,10 @@ impl<'a> EditorTestContext<'a> {
|
||||
crate::init(cx);
|
||||
|
||||
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
||||
cx.focus_self();
|
||||
build_editor(MultiBuffer::build_simple("", cx), cx)
|
||||
});
|
||||
|
||||
editor.update(cx, |_, cx| cx.focus_self());
|
||||
|
||||
(window_id, editor)
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use gpui::{
|
||||
elements::*,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Entity, RenderContext, View, ViewContext,
|
||||
Entity, View, ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
use workspace::{item::ItemHandle, StatusItemView};
|
||||
@@ -27,12 +27,12 @@ impl View for DeployFeedbackButton {
|
||||
"DeployFeedbackButton"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let active = self.active;
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<Self>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<Self, Self>::new(0, cx, |state, _| {
|
||||
let style = &theme
|
||||
.workspace
|
||||
.status_bar
|
||||
@@ -53,12 +53,12 @@ impl View for DeployFeedbackButton {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
if !active {
|
||||
cx.dispatch_action(GiveFeedback)
|
||||
}
|
||||
})
|
||||
.with_tooltip::<Self, _>(
|
||||
.with_tooltip::<Self>(
|
||||
0,
|
||||
"Send Feedback".into(),
|
||||
Some(Box::new(GiveFeedback)),
|
||||
|
||||
@@ -13,8 +13,8 @@ use gpui::{
|
||||
actions,
|
||||
elements::{ChildView, Flex, Label, ParentElement, Svg},
|
||||
platform::PromptLevel,
|
||||
serde_json, AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, RenderContext,
|
||||
Task, View, ViewContext, ViewHandle,
|
||||
serde_json, AnyViewHandle, AppContext, Drawable, Element, Entity, ModelHandle, Task, View,
|
||||
ViewContext, ViewHandle,
|
||||
};
|
||||
use isahc::Request;
|
||||
use language::Buffer;
|
||||
@@ -134,24 +134,21 @@ impl FeedbackEditor {
|
||||
if answer == Some(0) {
|
||||
match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await {
|
||||
Ok(_) => {
|
||||
cx.update(|cx| {
|
||||
this.update(cx, |_, cx| {
|
||||
cx.dispatch_action(workspace::CloseActiveItem);
|
||||
})
|
||||
});
|
||||
this.update(&mut cx, |_, cx| {
|
||||
cx.dispatch_action(workspace::CloseActiveItem);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!("{}", error);
|
||||
|
||||
cx.update(|cx| {
|
||||
this.update(cx, |_, cx| {
|
||||
cx.prompt(
|
||||
PromptLevel::Critical,
|
||||
FEEDBACK_SUBMISSION_ERROR_TEXT,
|
||||
&["OK"],
|
||||
);
|
||||
})
|
||||
});
|
||||
this.update(&mut cx, |_, cx| {
|
||||
cx.prompt(
|
||||
PromptLevel::Critical,
|
||||
FEEDBACK_SUBMISSION_ERROR_TEXT,
|
||||
&["OK"],
|
||||
);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,10 +218,10 @@ impl FeedbackEditor {
|
||||
.add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx));
|
||||
workspace.add_item(Box::new(feedback_editor), cx);
|
||||
})
|
||||
})
|
||||
.await;
|
||||
})?
|
||||
.await
|
||||
})
|
||||
.detach();
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +230,7 @@ impl View for FeedbackEditor {
|
||||
"FeedbackEditor"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
ChildView::new(&self.editor, cx).boxed()
|
||||
}
|
||||
|
||||
@@ -253,7 +250,12 @@ impl Item for FeedbackEditor {
|
||||
Some("Send Feedback".into())
|
||||
}
|
||||
|
||||
fn tab_content(&self, _: Option<usize>, style: &theme::Tab, _: &AppContext) -> ElementBox {
|
||||
fn tab_content<T: View>(
|
||||
&self,
|
||||
_: Option<usize>,
|
||||
style: &theme::Tab,
|
||||
_: &AppContext,
|
||||
) -> Element<T> {
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Svg::new("icons/feedback_16.svg")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use gpui::{
|
||||
elements::{Flex, Label, MouseEventHandler, ParentElement, Text},
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle,
|
||||
Drawable, Element, Entity, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
|
||||
@@ -29,7 +29,7 @@ impl View for FeedbackInfoText {
|
||||
"FeedbackInfoText"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
|
||||
Flex::row()
|
||||
@@ -43,7 +43,7 @@ impl View for FeedbackInfoText {
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<OpenZedCommunityRepo>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<OpenZedCommunityRepo, Self>::new(0, cx, |state, _| {
|
||||
let contained_text = if state.hovered() {
|
||||
&theme.feedback.link_text_hover
|
||||
} else {
|
||||
@@ -58,7 +58,7 @@ impl View for FeedbackInfoText {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, |_, cx| {
|
||||
.on_click(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(OpenZedCommunityRepo)
|
||||
})
|
||||
.boxed(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use gpui::{
|
||||
elements::{Label, MouseEventHandler},
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle,
|
||||
Drawable, Element, Entity, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
|
||||
@@ -29,10 +29,10 @@ impl View for SubmitFeedbackButton {
|
||||
"SubmitFeedbackButton"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
enum SubmitFeedbackButton {}
|
||||
MouseEventHandler::<SubmitFeedbackButton>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
|
||||
let style = theme.feedback.submit_button.style_for(state, false);
|
||||
Label::new("Submit as Markdown", style.text.clone())
|
||||
.contained()
|
||||
@@ -40,13 +40,13 @@ impl View for SubmitFeedbackButton {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, |_, cx| {
|
||||
.on_click(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(SubmitFeedback)
|
||||
})
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_margin_left(theme.feedback.button_margin)
|
||||
.with_tooltip::<Self, _>(
|
||||
.with_tooltip::<Self>(
|
||||
0,
|
||||
"cmd-s".into(),
|
||||
Some(Box::new(SubmitFeedback)),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{
|
||||
actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
|
||||
RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
|
||||
@@ -13,12 +12,14 @@ use std::{
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use util::post_inc;
|
||||
use util::{post_inc, ResultExt};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct FileFinder {
|
||||
pub type FileFinder = Picker<FileFinderDelegate>;
|
||||
|
||||
pub struct FileFinderDelegate {
|
||||
workspace: WeakViewHandle<Workspace>,
|
||||
project: ModelHandle<Project>,
|
||||
picker: ViewHandle<Picker<Self>>,
|
||||
search_count: usize,
|
||||
latest_search_id: usize,
|
||||
latest_search_did_cancel: bool,
|
||||
@@ -32,8 +33,26 @@ pub struct FileFinder {
|
||||
actions!(file_finder, [Toggle]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(FileFinder::toggle);
|
||||
Picker::<FileFinder>::init(cx);
|
||||
cx.add_action(toggle_file_finder);
|
||||
FileFinder::init(cx);
|
||||
}
|
||||
|
||||
fn toggle_file_finder(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||
workspace.toggle_modal(cx, |workspace, cx| {
|
||||
let relative_to = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.project_path(cx))
|
||||
.map(|project_path| project_path.path.clone());
|
||||
let project = workspace.project().clone();
|
||||
let workspace = cx.handle().downgrade();
|
||||
let finder = cx.add_view(|cx| {
|
||||
Picker::new(
|
||||
FileFinderDelegate::new(workspace, project, relative_to, cx),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
finder
|
||||
});
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
@@ -41,27 +60,7 @@ pub enum Event {
|
||||
Dismissed,
|
||||
}
|
||||
|
||||
impl Entity for FileFinder {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
impl View for FileFinder {
|
||||
fn ui_name() -> &'static str {
|
||||
"FileFinder"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
ChildView::new(&self.picker, cx).boxed()
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
if cx.is_self_focused() {
|
||||
cx.focus(&self.picker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileFinder {
|
||||
impl FileFinderDelegate {
|
||||
fn labels_for_match(&self, path_match: &PathMatch) -> (String, Vec<usize>, String, Vec<usize>) {
|
||||
let path = &path_match.path;
|
||||
let path_string = path.to_string_lossy();
|
||||
@@ -88,48 +87,19 @@ impl FileFinder {
|
||||
(file_name, file_name_positions, full_path, path_positions)
|
||||
}
|
||||
|
||||
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||
workspace.toggle_modal(cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let relative_to = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.project_path(cx))
|
||||
.map(|project_path| project_path.path.clone());
|
||||
let finder = cx.add_view(|cx| Self::new(project, relative_to, cx));
|
||||
cx.subscribe(&finder, Self::on_event).detach();
|
||||
finder
|
||||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
workspace: &mut Workspace,
|
||||
_: ViewHandle<FileFinder>,
|
||||
event: &Event,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
match event {
|
||||
Event::Selected(project_path) => {
|
||||
workspace
|
||||
.open_path(project_path.clone(), None, true, cx)
|
||||
.detach_and_log_err(cx);
|
||||
workspace.dismiss_modal(cx);
|
||||
}
|
||||
Event::Dismissed => {
|
||||
workspace.dismiss_modal(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
workspace: WeakViewHandle<Workspace>,
|
||||
project: ModelHandle<Project>,
|
||||
relative_to: Option<Arc<Path>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut ViewContext<FileFinder>,
|
||||
) -> Self {
|
||||
let handle = cx.weak_handle();
|
||||
cx.observe(&project, Self::project_updated).detach();
|
||||
cx.observe(&project, |picker, _, cx| {
|
||||
picker.update_matches(picker.query(cx), cx);
|
||||
})
|
||||
.detach();
|
||||
Self {
|
||||
workspace,
|
||||
project,
|
||||
picker: cx.add_view(|cx| Picker::new("Search project files...", handle, cx)),
|
||||
search_count: 0,
|
||||
latest_search_id: 0,
|
||||
latest_search_did_cancel: false,
|
||||
@@ -141,12 +111,7 @@ impl FileFinder {
|
||||
}
|
||||
}
|
||||
|
||||
fn project_updated(&mut self, _: ModelHandle<Project>, cx: &mut ViewContext<Self>) {
|
||||
self.spawn_search(self.picker.read(cx).query(cx), cx)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn spawn_search(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
|
||||
fn spawn_search(&mut self, query: String, cx: &mut ViewContext<FileFinder>) -> Task<()> {
|
||||
let relative_to = self.relative_to.clone();
|
||||
let worktrees = self
|
||||
.project
|
||||
@@ -172,7 +137,7 @@ impl FileFinder {
|
||||
self.cancel_flag.store(true, atomic::Ordering::Relaxed);
|
||||
self.cancel_flag = Arc::new(AtomicBool::new(false));
|
||||
let cancel_flag = self.cancel_flag.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
let matches = fuzzy::match_path_sets(
|
||||
candidate_sets.as_slice(),
|
||||
&query,
|
||||
@@ -184,9 +149,13 @@ impl FileFinder {
|
||||
)
|
||||
.await;
|
||||
let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.set_matches(search_id, did_cancel, query, matches, cx)
|
||||
});
|
||||
picker
|
||||
.update(&mut cx, |picker, cx| {
|
||||
picker
|
||||
.delegate_mut()
|
||||
.set_matches(search_id, did_cancel, query, matches, cx)
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
}
|
||||
|
||||
@@ -196,7 +165,7 @@ impl FileFinder {
|
||||
did_cancel: bool,
|
||||
query: String,
|
||||
matches: Vec<PathMatch>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut ViewContext<FileFinder>,
|
||||
) {
|
||||
if search_id >= self.latest_search_id {
|
||||
self.latest_search_id = search_id;
|
||||
@@ -208,12 +177,15 @@ impl FileFinder {
|
||||
self.latest_search_query = query;
|
||||
self.latest_search_did_cancel = did_cancel;
|
||||
cx.notify();
|
||||
self.picker.update(cx, |_, cx| cx.notify());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for FileFinder {
|
||||
impl PickerDelegate for FileFinderDelegate {
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
"Search project files...".into()
|
||||
}
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
self.matches.len()
|
||||
}
|
||||
@@ -231,13 +203,13 @@ impl PickerDelegate for FileFinder {
|
||||
0
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<FileFinder>) {
|
||||
let mat = &self.matches[ix];
|
||||
self.selected = Some((mat.worktree_id, mat.path.clone()));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<FileFinder>) -> Task<()> {
|
||||
if query.is_empty() {
|
||||
self.latest_search_id = post_inc(&mut self.search_count);
|
||||
self.matches.clear();
|
||||
@@ -248,18 +220,25 @@ impl PickerDelegate for FileFinder {
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn confirm(&mut self, cx: &mut ViewContext<FileFinder>) {
|
||||
if let Some(m) = self.matches.get(self.selected_index()) {
|
||||
cx.emit(Event::Selected(ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(m.worktree_id),
|
||||
path: m.path.clone(),
|
||||
}));
|
||||
if let Some(workspace) = self.workspace.upgrade(cx) {
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(m.worktree_id),
|
||||
path: m.path.clone(),
|
||||
};
|
||||
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.open_path(project_path.clone(), None, true, cx)
|
||||
.detach_and_log_err(cx);
|
||||
workspace.dismiss_modal(cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(Event::Dismissed);
|
||||
}
|
||||
fn dismissed(&mut self, _: &mut ViewContext<FileFinder>) {}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
@@ -267,7 +246,7 @@ impl PickerDelegate for FileFinder {
|
||||
mouse_state: &mut MouseState,
|
||||
selected: bool,
|
||||
cx: &AppContext,
|
||||
) -> ElementBox {
|
||||
) -> Element<Picker<Self>> {
|
||||
let path_match = &self.matches[ix];
|
||||
let settings = cx.global::<Settings>();
|
||||
let style = settings.theme.picker.item.style_for(mouse_state, selected);
|
||||
@@ -335,11 +314,11 @@ mod tests {
|
||||
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
|
||||
finder
|
||||
.update(cx, |finder, cx| {
|
||||
finder.update_matches("bna".to_string(), cx)
|
||||
finder.delegate_mut().update_matches("bna".to_string(), cx)
|
||||
})
|
||||
.await;
|
||||
finder.read_with(cx, |finder, _| {
|
||||
assert_eq!(finder.matches.len(), 2);
|
||||
assert_eq!(finder.delegate().matches.len(), 2);
|
||||
});
|
||||
|
||||
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
|
||||
@@ -384,23 +363,33 @@ mod tests {
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
|
||||
let (_, finder) =
|
||||
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
|
||||
let (_, finder) = cx.add_window(|cx| {
|
||||
Picker::new(
|
||||
FileFinderDelegate::new(
|
||||
workspace.downgrade(),
|
||||
workspace.read(cx).project().clone(),
|
||||
None,
|
||||
cx,
|
||||
),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let query = "hi".to_string();
|
||||
finder
|
||||
.update(cx, |f, cx| f.spawn_search(query.clone(), cx))
|
||||
.update(cx, |f, cx| f.delegate_mut().spawn_search(query.clone(), cx))
|
||||
.await;
|
||||
finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 5));
|
||||
finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 5));
|
||||
|
||||
finder.update(cx, |finder, cx| {
|
||||
let matches = finder.matches.clone();
|
||||
let delegate = finder.delegate_mut();
|
||||
let matches = delegate.matches.clone();
|
||||
|
||||
// Simulate a search being cancelled after the time limit,
|
||||
// returning only a subset of the matches that would have been found.
|
||||
drop(finder.spawn_search(query.clone(), cx));
|
||||
finder.set_matches(
|
||||
finder.latest_search_id,
|
||||
drop(delegate.spawn_search(query.clone(), cx));
|
||||
delegate.set_matches(
|
||||
delegate.latest_search_id,
|
||||
true, // did-cancel
|
||||
query.clone(),
|
||||
vec![matches[1].clone(), matches[3].clone()],
|
||||
@@ -408,16 +397,16 @@ mod tests {
|
||||
);
|
||||
|
||||
// Simulate another cancellation.
|
||||
drop(finder.spawn_search(query.clone(), cx));
|
||||
finder.set_matches(
|
||||
finder.latest_search_id,
|
||||
drop(delegate.spawn_search(query.clone(), cx));
|
||||
delegate.set_matches(
|
||||
delegate.latest_search_id,
|
||||
true, // did-cancel
|
||||
query.clone(),
|
||||
vec![matches[0].clone(), matches[2].clone(), matches[3].clone()],
|
||||
cx,
|
||||
);
|
||||
|
||||
assert_eq!(finder.matches, matches[0..4])
|
||||
assert_eq!(delegate.matches, matches[0..4])
|
||||
});
|
||||
}
|
||||
|
||||
@@ -458,12 +447,21 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
|
||||
let (_, finder) =
|
||||
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
|
||||
let (_, finder) = cx.add_window(|cx| {
|
||||
Picker::new(
|
||||
FileFinderDelegate::new(
|
||||
workspace.downgrade(),
|
||||
workspace.read(cx).project().clone(),
|
||||
None,
|
||||
cx,
|
||||
),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
finder
|
||||
.update(cx, |f, cx| f.spawn_search("hi".into(), cx))
|
||||
.update(cx, |f, cx| f.delegate_mut().spawn_search("hi".into(), cx))
|
||||
.await;
|
||||
finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 7));
|
||||
finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 7));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -482,20 +480,30 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
|
||||
let (_, finder) =
|
||||
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
|
||||
let (_, finder) = cx.add_window(|cx| {
|
||||
Picker::new(
|
||||
FileFinderDelegate::new(
|
||||
workspace.downgrade(),
|
||||
workspace.read(cx).project().clone(),
|
||||
None,
|
||||
cx,
|
||||
),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
// Even though there is only one worktree, that worktree's filename
|
||||
// is included in the matching, because the worktree is a single file.
|
||||
finder
|
||||
.update(cx, |f, cx| f.spawn_search("thf".into(), cx))
|
||||
.update(cx, |f, cx| f.delegate_mut().spawn_search("thf".into(), cx))
|
||||
.await;
|
||||
cx.read(|cx| {
|
||||
let finder = finder.read(cx);
|
||||
assert_eq!(finder.matches.len(), 1);
|
||||
let delegate = finder.delegate();
|
||||
assert_eq!(delegate.matches.len(), 1);
|
||||
|
||||
let (file_name, file_name_positions, full_path, full_path_positions) =
|
||||
finder.labels_for_match(&finder.matches[0]);
|
||||
delegate.labels_for_match(&delegate.matches[0]);
|
||||
assert_eq!(file_name, "the-file");
|
||||
assert_eq!(file_name_positions, &[0, 1, 4]);
|
||||
assert_eq!(full_path, "the-file");
|
||||
@@ -505,9 +513,9 @@ mod tests {
|
||||
// Since the worktree root is a file, searching for its name followed by a slash does
|
||||
// not match anything.
|
||||
finder
|
||||
.update(cx, |f, cx| f.spawn_search("thf/".into(), cx))
|
||||
.update(cx, |f, cx| f.delegate_mut().spawn_search("thf/".into(), cx))
|
||||
.await;
|
||||
finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 0));
|
||||
finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -535,22 +543,32 @@ mod tests {
|
||||
.await;
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
|
||||
|
||||
let (_, finder) =
|
||||
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
|
||||
let (_, finder) = cx.add_window(|cx| {
|
||||
Picker::new(
|
||||
FileFinderDelegate::new(
|
||||
workspace.downgrade(),
|
||||
workspace.read(cx).project().clone(),
|
||||
None,
|
||||
cx,
|
||||
),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
// Run a search that matches two files with the same relative path.
|
||||
finder
|
||||
.update(cx, |f, cx| f.spawn_search("a.t".into(), cx))
|
||||
.update(cx, |f, cx| f.delegate_mut().spawn_search("a.t".into(), cx))
|
||||
.await;
|
||||
|
||||
// Can switch between different matches with the same relative path.
|
||||
finder.update(cx, |f, cx| {
|
||||
assert_eq!(f.matches.len(), 2);
|
||||
assert_eq!(f.selected_index(), 0);
|
||||
f.set_selected_index(1, cx);
|
||||
assert_eq!(f.selected_index(), 1);
|
||||
f.set_selected_index(0, cx);
|
||||
assert_eq!(f.selected_index(), 0);
|
||||
finder.update(cx, |finder, cx| {
|
||||
let delegate = finder.delegate_mut();
|
||||
assert_eq!(delegate.matches.len(), 2);
|
||||
assert_eq!(delegate.selected_index(), 0);
|
||||
delegate.set_selected_index(1, cx);
|
||||
assert_eq!(delegate.selected_index(), 1);
|
||||
delegate.set_selected_index(0, cx);
|
||||
assert_eq!(delegate.selected_index(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -581,16 +599,28 @@ mod tests {
|
||||
// first when they have the same name. In this case, b.txt is closer to dir2's a.txt
|
||||
// so that one should be sorted earlier
|
||||
let b_path = Some(Arc::from(Path::new("/root/dir2/b.txt")));
|
||||
let (_, finder) =
|
||||
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), b_path, cx));
|
||||
let (_, finder) = cx.add_window(|cx| {
|
||||
Picker::new(
|
||||
FileFinderDelegate::new(
|
||||
workspace.downgrade(),
|
||||
workspace.read(cx).project().clone(),
|
||||
b_path,
|
||||
cx,
|
||||
),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
finder
|
||||
.update(cx, |f, cx| f.spawn_search("a.txt".into(), cx))
|
||||
.update(cx, |f, cx| {
|
||||
f.delegate_mut().spawn_search("a.txt".into(), cx)
|
||||
})
|
||||
.await;
|
||||
|
||||
finder.read_with(cx, |f, _| {
|
||||
assert_eq!(f.matches[0].path.as_ref(), Path::new("dir2/a.txt"));
|
||||
assert_eq!(f.matches[1].path.as_ref(), Path::new("dir1/a.txt"));
|
||||
let delegate = f.delegate();
|
||||
assert_eq!(delegate.matches[0].path.as_ref(), Path::new("dir2/a.txt"));
|
||||
assert_eq!(delegate.matches[1].path.as_ref(), Path::new("dir1/a.txt"));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -613,14 +643,23 @@ mod tests {
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
|
||||
let (_, finder) =
|
||||
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
|
||||
let (_, finder) = cx.add_window(|cx| {
|
||||
Picker::new(
|
||||
FileFinderDelegate::new(
|
||||
workspace.downgrade(),
|
||||
workspace.read(cx).project().clone(),
|
||||
None,
|
||||
cx,
|
||||
),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
finder
|
||||
.update(cx, |f, cx| f.spawn_search("dir".into(), cx))
|
||||
.update(cx, |f, cx| f.delegate_mut().spawn_search("dir".into(), cx))
|
||||
.await;
|
||||
cx.read(|cx| {
|
||||
let finder = finder.read(cx);
|
||||
assert_eq!(finder.matches.len(), 0);
|
||||
assert_eq!(finder.delegate().matches.len(), 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ use std::sync::Arc;
|
||||
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
|
||||
use gpui::{
|
||||
actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Axis, Entity,
|
||||
RenderContext, View, ViewContext, ViewHandle,
|
||||
View, ViewContext, ViewHandle,
|
||||
};
|
||||
use menu::{Cancel, Confirm};
|
||||
use settings::Settings;
|
||||
use text::{Bias, Point};
|
||||
use workspace::Workspace;
|
||||
use workspace::{Modal, Workspace};
|
||||
|
||||
actions!(go_to_line, [Toggle]);
|
||||
|
||||
@@ -65,11 +65,7 @@ impl GoToLine {
|
||||
.active_item(cx)
|
||||
.and_then(|active_item| active_item.downcast::<Editor>())
|
||||
{
|
||||
workspace.toggle_modal(cx, |_, cx| {
|
||||
let view = cx.add_view(|cx| GoToLine::new(editor, cx));
|
||||
cx.subscribe(&view, Self::on_event).detach();
|
||||
view
|
||||
});
|
||||
workspace.toggle_modal(cx, |_, cx| cx.add_view(|cx| GoToLine::new(editor, cx)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,17 +87,6 @@ impl GoToLine {
|
||||
cx.emit(Event::Dismissed);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
workspace: &mut Workspace,
|
||||
_: ViewHandle<Self>,
|
||||
event: &Event,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
match event {
|
||||
Event::Dismissed => workspace.dismiss_modal(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_line_editor_event(
|
||||
&mut self,
|
||||
_: ViewHandle<Editor>,
|
||||
@@ -142,12 +127,14 @@ impl Entity for GoToLine {
|
||||
|
||||
fn release(&mut self, cx: &mut AppContext) {
|
||||
let scroll_position = self.prev_scroll_position.take();
|
||||
self.active_editor.update(cx, |editor, cx| {
|
||||
editor.highlight_rows(None);
|
||||
if let Some(scroll_position) = scroll_position {
|
||||
editor.set_scroll_position(scroll_position, cx);
|
||||
}
|
||||
})
|
||||
cx.update_window(self.active_editor.window_id(), |cx| {
|
||||
self.active_editor.update(cx, |editor, cx| {
|
||||
editor.highlight_rows(None);
|
||||
if let Some(scroll_position) = scroll_position {
|
||||
editor.set_scroll_position(scroll_position, cx);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +143,7 @@ impl View for GoToLine {
|
||||
"GoToLine"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let theme = &cx.global::<Settings>().theme.picker;
|
||||
|
||||
let label = format!(
|
||||
@@ -192,3 +179,9 @@ impl View for GoToLine {
|
||||
cx.focus(&self.line_editor);
|
||||
}
|
||||
}
|
||||
|
||||
impl Modal for GoToLine {
|
||||
fn dismiss_on_event(event: &Self::Event) -> bool {
|
||||
matches!(event, Event::Dismissed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use gpui::{
|
||||
color::Color,
|
||||
fonts::{Properties, Weight},
|
||||
text_layout::RunStyle,
|
||||
DebugContext, Element as _, MeasurementContext, Quad,
|
||||
Drawable, Element, Quad, SceneBuilder, View, ViewContext,
|
||||
};
|
||||
use log::LevelFilter;
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
@@ -30,12 +30,12 @@ impl gpui::View for TextView {
|
||||
"View"
|
||||
}
|
||||
|
||||
fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
|
||||
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Element<TextView> {
|
||||
TextElement.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::Element for TextElement {
|
||||
impl<V: View> Drawable<V> for TextElement {
|
||||
type LayoutState = ();
|
||||
|
||||
type PaintState = ();
|
||||
@@ -43,17 +43,20 @@ impl gpui::Element for TextElement {
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: gpui::SizeConstraint,
|
||||
_: &mut gpui::LayoutContext,
|
||||
_: &mut V,
|
||||
_: &mut ViewContext<V>,
|
||||
) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) {
|
||||
(constraint.max, ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut gpui::PaintContext,
|
||||
_: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let font_size = 12.;
|
||||
let family = cx
|
||||
@@ -84,7 +87,7 @@ impl gpui::Element for TextElement {
|
||||
};
|
||||
|
||||
let text = "Hello world!";
|
||||
let line = cx.text_layout_cache.layout_str(
|
||||
let line = cx.text_layout_cache().layout_str(
|
||||
text,
|
||||
font_size,
|
||||
&[
|
||||
@@ -96,12 +99,12 @@ impl gpui::Element for TextElement {
|
||||
],
|
||||
);
|
||||
|
||||
cx.scene.push_quad(Quad {
|
||||
scene.push_quad(Quad {
|
||||
bounds,
|
||||
background: Some(Color::white()),
|
||||
..Default::default()
|
||||
});
|
||||
line.paint(bounds.origin(), visible_bounds, bounds.height(), cx);
|
||||
line.paint(scene, bounds.origin(), visible_bounds, bounds.height(), cx);
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
@@ -111,7 +114,8 @@ impl gpui::Element for TextElement {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &MeasurementContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
None
|
||||
}
|
||||
@@ -121,7 +125,8 @@ impl gpui::Element for TextElement {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &DebugContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> gpui::json::Value {
|
||||
todo!()
|
||||
}
|
||||
|
||||
+1001
-1654
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
||||
use crate::AppContext;
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
@@ -93,12 +92,10 @@ impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> {
|
||||
drop(callbacks);
|
||||
}
|
||||
|
||||
pub fn emit<C: FnMut(&mut F, &mut AppContext) -> bool>(
|
||||
&mut self,
|
||||
key: K,
|
||||
cx: &mut AppContext,
|
||||
mut call_callback: C,
|
||||
) {
|
||||
pub fn emit<C>(&mut self, key: K, mut call_callback: C)
|
||||
where
|
||||
C: FnMut(&mut F) -> bool,
|
||||
{
|
||||
let callbacks = self.internal.lock().callbacks.remove(&key);
|
||||
if let Some(callbacks) = callbacks {
|
||||
for (subscription_id, mut callback) in callbacks {
|
||||
@@ -110,7 +107,7 @@ impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> {
|
||||
}
|
||||
|
||||
drop(this);
|
||||
let alive = call_callback(&mut callback, cx);
|
||||
let alive = call_callback(&mut callback);
|
||||
|
||||
// If this callback's subscription was dropped while invoking the callback
|
||||
// itself, or if the callback returns false, then just drop the callback.
|
||||
|
||||
@@ -78,8 +78,18 @@ pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform,
|
||||
move |action| {
|
||||
let mut cx = cx.borrow_mut();
|
||||
if let Some(main_window_id) = cx.platform.main_window_id() {
|
||||
if let Some(view_id) = cx.focused_view_id(main_window_id) {
|
||||
cx.handle_dispatch_action_from_effect(main_window_id, Some(view_id), action);
|
||||
let dispatched = cx
|
||||
.update_window(main_window_id, |cx| {
|
||||
if let Some(view_id) = cx.focused_view_id() {
|
||||
cx.handle_dispatch_action_from_effect(Some(view_id), action);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if dispatched {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
@@ -21,10 +21,10 @@ use crate::{
|
||||
geometry::vector::Vector2F,
|
||||
keymap_matcher::Keystroke,
|
||||
platform,
|
||||
platform::{Appearance, Event, InputHandler, KeyDownEvent, Platform},
|
||||
platform::{Event, InputHandler, KeyDownEvent, Platform},
|
||||
Action, AnyViewHandle, AppContext, Entity, FontCache, Handle, ModelContext, ModelHandle,
|
||||
ReadModelWith, ReadViewWith, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext,
|
||||
ViewHandle, WeakHandle,
|
||||
ReadModelWith, ReadViewWith, Subscription, Task, UpdateModel, UpdateView, View, ViewContext,
|
||||
ViewHandle, WeakHandle, WindowContext,
|
||||
};
|
||||
use collections::BTreeMap;
|
||||
|
||||
@@ -75,7 +75,7 @@ impl TestAppContext {
|
||||
|
||||
pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) {
|
||||
let mut cx = self.cx.borrow_mut();
|
||||
if let Some(view_id) = cx.focused_view_id(window_id) {
|
||||
if let Some(view_id) = cx.windows.get(&window_id).and_then(|w| w.focused_view_id) {
|
||||
cx.handle_dispatch_action_from_effect(window_id, Some(view_id), &action);
|
||||
}
|
||||
}
|
||||
@@ -85,31 +85,27 @@ impl TestAppContext {
|
||||
}
|
||||
|
||||
pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
|
||||
let handled = self.cx.borrow_mut().update(|cx| {
|
||||
let presenter = cx
|
||||
.presenters_and_platform_windows
|
||||
.get(&window_id)
|
||||
.unwrap()
|
||||
.0
|
||||
.clone();
|
||||
let handled = self
|
||||
.cx
|
||||
.borrow_mut()
|
||||
.update_window(window_id, |cx| {
|
||||
if cx.dispatch_keystroke(&keystroke) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if cx.dispatch_keystroke(window_id, &keystroke) {
|
||||
return true;
|
||||
}
|
||||
if cx.dispatch_event(
|
||||
Event::KeyDown(KeyDownEvent {
|
||||
keystroke: keystroke.clone(),
|
||||
is_held,
|
||||
}),
|
||||
false,
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if presenter.borrow_mut().dispatch_event(
|
||||
Event::KeyDown(KeyDownEvent {
|
||||
keystroke: keystroke.clone(),
|
||||
is_held,
|
||||
}),
|
||||
false,
|
||||
cx,
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
});
|
||||
false
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if !handled && !keystroke.cmd && !keystroke.ctrl {
|
||||
WindowInputHandler {
|
||||
@@ -120,6 +116,22 @@ impl TestAppContext {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
|
||||
&self,
|
||||
window_id: usize,
|
||||
callback: F,
|
||||
) -> Option<T> {
|
||||
self.cx.borrow().read_window(window_id, callback)
|
||||
}
|
||||
|
||||
pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
|
||||
&mut self,
|
||||
window_id: usize,
|
||||
callback: F,
|
||||
) -> Option<T> {
|
||||
self.cx.borrow_mut().update_window(window_id, callback)
|
||||
}
|
||||
|
||||
pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
|
||||
where
|
||||
T: Entity,
|
||||
@@ -149,12 +161,28 @@ impl TestAppContext {
|
||||
self.cx.borrow_mut().add_view(parent_handle, build_view)
|
||||
}
|
||||
|
||||
pub fn window_ids(&self) -> Vec<usize> {
|
||||
self.cx.borrow().window_ids().collect()
|
||||
pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
|
||||
where
|
||||
E: Any,
|
||||
F: 'static + FnMut(&mut AppContext),
|
||||
{
|
||||
self.cx.borrow_mut().observe_global::<E, F>(callback)
|
||||
}
|
||||
|
||||
pub fn 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| {
|
||||
(
|
||||
cx.observe(self, {
|
||||
let mut tx = tx.clone();
|
||||
move |_, _, _| {
|
||||
tx.blocking_send(()).ok();
|
||||
}
|
||||
}),
|
||||
cx.subscribe(self, {
|
||||
let mut tx = tx.clone();
|
||||
move |_, _, _, _| {
|
||||
tx.blocking_send(()).ok();
|
||||
}
|
||||
}),
|
||||
)
|
||||
});
|
||||
let subscriptions = (
|
||||
cx.observe(self, {
|
||||
let mut tx = tx.clone();
|
||||
move |_, _| {
|
||||
tx.blocking_send(()).ok();
|
||||
}
|
||||
}),
|
||||
cx.subscribe(self, {
|
||||
let mut tx = tx.clone();
|
||||
move |_, _, _| {
|
||||
tx.blocking_send(()).ok();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
|
||||
let handle = self.downgrade();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
|
||||
use crate::{platform::InputHandler, AnyView, AppContext};
|
||||
use crate::{platform::InputHandler, window::WindowContext, AnyView, AppContext};
|
||||
|
||||
pub struct WindowInputHandler {
|
||||
pub app: Rc<RefCell<AppContext>>,
|
||||
@@ -12,7 +12,7 @@ pub struct WindowInputHandler {
|
||||
impl WindowInputHandler {
|
||||
fn read_focused_view<T, F>(&self, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&dyn AnyView, &AppContext) -> T,
|
||||
F: FnOnce(&dyn AnyView, &WindowContext) -> T,
|
||||
{
|
||||
// Input-related application hooks are sometimes called by the OS during
|
||||
// a call to a window-manipulation API, like prompting the user for file
|
||||
@@ -20,26 +20,26 @@ impl WindowInputHandler {
|
||||
// InputHandler methods need to fail gracefully.
|
||||
//
|
||||
// See https://github.com/zed-industries/community/issues/444
|
||||
let app = self.app.try_borrow().ok()?;
|
||||
|
||||
let view_id = app.focused_view_id(self.window_id)?;
|
||||
let view = app.views.get(&(self.window_id, view_id))?;
|
||||
let result = f(view.as_ref(), &app);
|
||||
Some(result)
|
||||
let mut app = self.app.try_borrow_mut().ok()?;
|
||||
app.update_window(self.window_id, |cx| {
|
||||
let view_id = cx.window.focused_view_id?;
|
||||
let view = cx.views.get(&(self.window_id, view_id))?;
|
||||
let result = f(view.as_ref(), &cx);
|
||||
Some(result)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(usize, usize, &mut dyn AnyView, &mut AppContext) -> T,
|
||||
F: FnOnce(&mut dyn AnyView, &mut WindowContext, usize) -> T,
|
||||
{
|
||||
let mut app = self.app.try_borrow_mut().ok()?;
|
||||
app.update(|app| {
|
||||
let view_id = app.focused_view_id(self.window_id)?;
|
||||
let mut view = app.views.remove(&(self.window_id, view_id))?;
|
||||
let result = f(self.window_id, view_id, view.as_mut(), &mut *app);
|
||||
app.views.insert((self.window_id, view_id), view);
|
||||
Some(result)
|
||||
app.update_window(self.window_id, |cx| {
|
||||
let view_id = cx.window.focused_view_id?;
|
||||
cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ impl InputHandler for WindowInputHandler {
|
||||
}
|
||||
|
||||
fn replace_text_in_range(&mut self, range: Option<Range<usize>>, text: &str) {
|
||||
self.update_focused_view(|window_id, view_id, view, cx| {
|
||||
view.replace_text_in_range(range, text, cx, window_id, view_id);
|
||||
self.update_focused_view(|view, cx, view_id| {
|
||||
view.replace_text_in_range(range, text, cx, view_id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -66,8 +66,8 @@ impl InputHandler for WindowInputHandler {
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self) {
|
||||
self.update_focused_view(|window_id, view_id, view, cx| {
|
||||
view.unmark_text(cx, window_id, view_id);
|
||||
self.update_focused_view(|view, cx, view_id| {
|
||||
view.unmark_text(cx, view_id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -77,22 +77,15 @@ impl InputHandler for WindowInputHandler {
|
||||
new_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
) {
|
||||
self.update_focused_view(|window_id, view_id, view, cx| {
|
||||
view.replace_and_mark_text_in_range(
|
||||
range,
|
||||
new_text,
|
||||
new_selected_range,
|
||||
cx,
|
||||
window_id,
|
||||
view_id,
|
||||
);
|
||||
self.update_focused_view(|view, cx, view_id| {
|
||||
view.replace_and_mark_text_in_range(range, new_text, new_selected_range, cx, view_id);
|
||||
});
|
||||
}
|
||||
|
||||
fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
|
||||
let app = self.app.borrow();
|
||||
let (presenter, _) = app.presenters_and_platform_windows.get(&self.window_id)?;
|
||||
let presenter = presenter.borrow();
|
||||
presenter.rect_for_text_range(range_utf16, &app)
|
||||
self.app
|
||||
.borrow_mut()
|
||||
.update_window(self.window_id, |cx| cx.rect_for_text_range(range_utf16))
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
+382
-127
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ElementBox {
|
||||
fn clone(&self) -> Self {
|
||||
ElementBox(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ElementBox> for ElementRc {
|
||||
fn from(val: ElementBox) -> Self {
|
||||
val.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ElementBox {
|
||||
type Target = ElementRc;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ElementBox {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ElementRc {
|
||||
pub fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F {
|
||||
self.element.borrow_mut().layout(constraint, cx)
|
||||
self.drawable
|
||||
.metadata()
|
||||
.and_then(|data| data.downcast_ref::<T>())
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext) {
|
||||
self.element.borrow_mut().paint(origin, visible_bounds, cx);
|
||||
pub fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Vector2F {
|
||||
self.drawable.layout(constraint, view, cx)
|
||||
}
|
||||
|
||||
pub fn paint(
|
||||
&mut self,
|
||||
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() {
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json,
|
||||
presenter::MeasurementContext,
|
||||
DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
|
||||
json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
use json::ToJson;
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
pub struct Align {
|
||||
child: ElementBox,
|
||||
pub struct Align<V: View> {
|
||||
child: Element<V>,
|
||||
alignment: Vector2F,
|
||||
}
|
||||
|
||||
impl Align {
|
||||
pub fn new(child: ElementBox) -> Self {
|
||||
impl<V: View> Align<V> {
|
||||
pub fn new(child: Element<V>) -> Self {
|
||||
Self {
|
||||
child,
|
||||
alignment: Vector2F::zero(),
|
||||
@@ -42,18 +40,19 @@ impl Align {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Align {
|
||||
impl<V: View> Drawable<V> for Align<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
mut constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let mut size = constraint.max;
|
||||
constraint.min = Vector2F::zero();
|
||||
let child_size = self.child.layout(constraint, cx);
|
||||
let child_size = self.child.layout(constraint, view, cx);
|
||||
if size.x().is_infinite() {
|
||||
size.set_x(child_size.x());
|
||||
}
|
||||
@@ -65,10 +64,12 @@ impl Element for Align {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let my_center = bounds.size() / 2.;
|
||||
let my_target = my_center + my_center * self.alignment;
|
||||
@@ -77,8 +78,10 @@ impl Element for Align {
|
||||
let child_target = child_center + child_center * self.alignment;
|
||||
|
||||
self.child.paint(
|
||||
scene,
|
||||
bounds.origin() - (child_target - my_target),
|
||||
visible_bounds,
|
||||
view,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
@@ -90,9 +93,10 @@ impl Element for Align {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.child.rect_for_text_range(range_utf16, cx)
|
||||
self.child.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
@@ -100,13 +104,14 @@ impl Element for Align {
|
||||
bounds: pathfinder_geometry::rect::RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> json::Value {
|
||||
json!({
|
||||
"type": "Align",
|
||||
"bounds": bounds.to_json(),
|
||||
"alignment": self.alignment.to_json(),
|
||||
"child": self.child.debug(cx),
|
||||
"child": self.child.debug(view, cx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use super::Element;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::Drawable;
|
||||
use crate::{
|
||||
json::{self, json},
|
||||
presenter::MeasurementContext,
|
||||
DebugContext, PaintContext,
|
||||
SceneBuilder, View, ViewContext,
|
||||
};
|
||||
use json::ToJson;
|
||||
use pathfinder_geometry::{
|
||||
@@ -10,20 +11,21 @@ use pathfinder_geometry::{
|
||||
vector::{vec2f, Vector2F},
|
||||
};
|
||||
|
||||
pub struct Canvas<F>(F);
|
||||
pub struct Canvas<V, F>(F, PhantomData<V>);
|
||||
|
||||
impl<F> Canvas<F>
|
||||
impl<V, F> Canvas<V, F>
|
||||
where
|
||||
F: FnMut(RectF, RectF, &mut PaintContext),
|
||||
V: View,
|
||||
F: FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
|
||||
{
|
||||
pub fn new(f: F) -> Self {
|
||||
Self(f)
|
||||
Self(f, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Element for Canvas<F>
|
||||
impl<V: View, F> Drawable<V> for Canvas<V, F>
|
||||
where
|
||||
F: FnMut(RectF, RectF, &mut PaintContext),
|
||||
F: FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
|
||||
{
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
@@ -31,7 +33,8 @@ where
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: crate::SizeConstraint,
|
||||
_: &mut crate::LayoutContext,
|
||||
_: &mut V,
|
||||
_: &mut crate::ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let x = if constraint.max.x().is_finite() {
|
||||
constraint.max.x()
|
||||
@@ -48,12 +51,14 @@ where
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
self.0(bounds, visible_bounds, cx)
|
||||
self.0(scene, bounds, visible_bounds, view, cx)
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
@@ -63,7 +68,8 @@ where
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &MeasurementContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
None
|
||||
}
|
||||
@@ -73,7 +79,8 @@ where
|
||||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &DebugContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> json::Value {
|
||||
json!({"type": "Canvas", "bounds": bounds.to_json()})
|
||||
}
|
||||
|
||||
@@ -3,43 +3,44 @@ use std::ops::Range;
|
||||
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{
|
||||
json, DebugContext, Element, ElementBox, LayoutContext, MeasurementContext, PaintContext,
|
||||
SizeConstraint,
|
||||
};
|
||||
use crate::{json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext};
|
||||
|
||||
pub struct Clipped {
|
||||
child: ElementBox,
|
||||
pub struct Clipped<V: View> {
|
||||
child: Element<V>,
|
||||
}
|
||||
|
||||
impl Clipped {
|
||||
pub fn new(child: ElementBox) -> Self {
|
||||
impl<V: View> Clipped<V> {
|
||||
pub fn new(child: Element<V>) -> Self {
|
||||
Self { child }
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Clipped {
|
||||
impl<V: View> Drawable<V> for Clipped<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
(self.child.layout(constraint, cx), ())
|
||||
(self.child.layout(constraint, view, cx), ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
cx.scene.push_layer(Some(bounds));
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
cx.scene.pop_layer();
|
||||
scene.paint_layer(Some(bounds), |scene| {
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
@@ -49,9 +50,10 @@ impl Element for Clipped {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.child.rect_for_text_range(range_utf16, cx)
|
||||
self.child.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
@@ -59,11 +61,12 @@ impl Element for Clipped {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> json::Value {
|
||||
json!({
|
||||
"type": "Clipped",
|
||||
"child": self.child.debug(cx)
|
||||
"child": self.child.debug(view, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,22 +5,20 @@ use serde_json::json;
|
||||
|
||||
use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json,
|
||||
presenter::MeasurementContext,
|
||||
DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
|
||||
json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
|
||||
pub struct ConstrainedBox {
|
||||
child: ElementBox,
|
||||
constraint: Constraint,
|
||||
pub struct ConstrainedBox<V: View> {
|
||||
child: Element<V>,
|
||||
constraint: Constraint<V>,
|
||||
}
|
||||
|
||||
pub enum Constraint {
|
||||
pub enum Constraint<V: View> {
|
||||
Static(SizeConstraint),
|
||||
Dynamic(Box<dyn FnMut(SizeConstraint, &mut LayoutContext) -> SizeConstraint>),
|
||||
Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint>),
|
||||
}
|
||||
|
||||
impl ToJson for Constraint {
|
||||
impl<V: View> ToJson for Constraint<V> {
|
||||
fn to_json(&self) -> serde_json::Value {
|
||||
match self {
|
||||
Constraint::Static(constraint) => constraint.to_json(),
|
||||
@@ -29,8 +27,8 @@ impl ToJson for Constraint {
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstrainedBox {
|
||||
pub fn new(child: ElementBox) -> Self {
|
||||
impl<V: View> ConstrainedBox<V> {
|
||||
pub fn new(child: Element<V>) -> Self {
|
||||
Self {
|
||||
child,
|
||||
constraint: Constraint::Static(Default::default()),
|
||||
@@ -39,7 +37,7 @@ impl ConstrainedBox {
|
||||
|
||||
pub fn dynamically(
|
||||
mut self,
|
||||
constraint: impl 'static + FnMut(SizeConstraint, &mut LayoutContext) -> SizeConstraint,
|
||||
constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint,
|
||||
) -> Self {
|
||||
self.constraint = Constraint::Dynamic(Box::new(constraint));
|
||||
self
|
||||
@@ -120,41 +118,48 @@ impl ConstrainedBox {
|
||||
fn constraint(
|
||||
&mut self,
|
||||
input_constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> SizeConstraint {
|
||||
match &mut self.constraint {
|
||||
Constraint::Static(constraint) => *constraint,
|
||||
Constraint::Dynamic(compute_constraint) => compute_constraint(input_constraint, cx),
|
||||
Constraint::Dynamic(compute_constraint) => {
|
||||
compute_constraint(input_constraint, view, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for ConstrainedBox {
|
||||
impl<V: View> Drawable<V> for ConstrainedBox<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
mut parent_constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let constraint = self.constraint(parent_constraint, cx);
|
||||
let constraint = self.constraint(parent_constraint, view, cx);
|
||||
parent_constraint.min = parent_constraint.min.max(constraint.min);
|
||||
parent_constraint.max = parent_constraint.max.min(constraint.max);
|
||||
parent_constraint.max = parent_constraint.max.max(parent_constraint.min);
|
||||
let size = self.child.layout(parent_constraint, cx);
|
||||
let size = self.child.layout(parent_constraint, view, cx);
|
||||
(size, ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
cx.paint_layer(Some(visible_bounds), |cx| {
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
scene.paint_layer(Some(visible_bounds), |scene| {
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -165,9 +170,10 @@ impl Element for ConstrainedBox {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.child.rect_for_text_range(range_utf16, cx)
|
||||
self.child.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
@@ -175,8 +181,9 @@ impl Element for ConstrainedBox {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> json::Value {
|
||||
json!({"type": "ConstrainedBox", "assigned_constraint": self.constraint.to_json(), "child": self.child.debug(cx)})
|
||||
json!({"type": "ConstrainedBox", "assigned_constraint": self.constraint.to_json(), "child": self.child.debug(view, cx)})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,8 @@ use crate::{
|
||||
},
|
||||
json::ToJson,
|
||||
platform::CursorStyle,
|
||||
presenter::MeasurementContext,
|
||||
scene::{self, Border, CursorRegion, Quad},
|
||||
Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
|
||||
Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
@@ -36,13 +35,13 @@ pub struct ContainerStyle {
|
||||
pub cursor: Option<CursorStyle>,
|
||||
}
|
||||
|
||||
pub struct Container {
|
||||
child: ElementBox,
|
||||
pub struct Container<V: View> {
|
||||
child: Element<V>,
|
||||
style: ContainerStyle,
|
||||
}
|
||||
|
||||
impl Container {
|
||||
pub fn new(child: ElementBox) -> Self {
|
||||
impl<V: View> Container<V> {
|
||||
pub fn new(child: Element<V>) -> Self {
|
||||
Self {
|
||||
child,
|
||||
style: Default::default(),
|
||||
@@ -185,14 +184,15 @@ impl Container {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Container {
|
||||
impl<V: View> Drawable<V> for Container<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let mut size_buffer = self.margin_size() + self.padding_size();
|
||||
if !self.style.border.overlay {
|
||||
@@ -202,16 +202,18 @@ impl Element for Container {
|
||||
min: (constraint.min - size_buffer).max(Vector2F::zero()),
|
||||
max: (constraint.max - size_buffer).max(Vector2F::zero()),
|
||||
};
|
||||
let child_size = self.child.layout(child_constraint, cx);
|
||||
let child_size = self.child.layout(child_constraint, view, cx);
|
||||
(child_size + size_buffer, ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let quad_bounds = RectF::from_points(
|
||||
bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
|
||||
@@ -219,7 +221,7 @@ impl Element for Container {
|
||||
);
|
||||
|
||||
if let Some(shadow) = self.style.shadow.as_ref() {
|
||||
cx.scene.push_shadow(scene::Shadow {
|
||||
scene.push_shadow(scene::Shadow {
|
||||
bounds: quad_bounds + shadow.offset,
|
||||
corner_radius: self.style.corner_radius,
|
||||
sigma: shadow.blur,
|
||||
@@ -229,7 +231,7 @@ impl Element for Container {
|
||||
|
||||
if let Some(hit_bounds) = quad_bounds.intersection(visible_bounds) {
|
||||
if let Some(style) = self.style.cursor {
|
||||
cx.scene.push_cursor_region(CursorRegion {
|
||||
scene.push_cursor_region(CursorRegion {
|
||||
bounds: hit_bounds,
|
||||
style,
|
||||
});
|
||||
@@ -240,25 +242,26 @@ impl Element for Container {
|
||||
quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);
|
||||
|
||||
if self.style.border.overlay {
|
||||
cx.scene.push_quad(Quad {
|
||||
scene.push_quad(Quad {
|
||||
bounds: quad_bounds,
|
||||
background: self.style.background_color,
|
||||
border: Default::default(),
|
||||
corner_radius: self.style.corner_radius,
|
||||
});
|
||||
|
||||
self.child.paint(child_origin, visible_bounds, cx);
|
||||
self.child
|
||||
.paint(scene, child_origin, visible_bounds, view, cx);
|
||||
|
||||
cx.scene.push_layer(None);
|
||||
cx.scene.push_quad(Quad {
|
||||
scene.push_layer(None);
|
||||
scene.push_quad(Quad {
|
||||
bounds: quad_bounds,
|
||||
background: self.style.overlay_color,
|
||||
border: self.style.border,
|
||||
corner_radius: self.style.corner_radius,
|
||||
});
|
||||
cx.scene.pop_layer();
|
||||
scene.pop_layer();
|
||||
} else {
|
||||
cx.scene.push_quad(Quad {
|
||||
scene.push_quad(Quad {
|
||||
bounds: quad_bounds,
|
||||
background: self.style.background_color,
|
||||
border: self.style.border,
|
||||
@@ -270,17 +273,18 @@ impl Element for Container {
|
||||
self.style.border.left_width(),
|
||||
self.style.border.top_width(),
|
||||
);
|
||||
self.child.paint(child_origin, visible_bounds, cx);
|
||||
self.child
|
||||
.paint(scene, child_origin, visible_bounds, view, cx);
|
||||
|
||||
if self.style.overlay_color.is_some() {
|
||||
cx.scene.push_layer(None);
|
||||
cx.scene.push_quad(Quad {
|
||||
scene.push_layer(None);
|
||||
scene.push_quad(Quad {
|
||||
bounds: quad_bounds,
|
||||
background: self.style.overlay_color,
|
||||
border: Default::default(),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
cx.scene.pop_layer();
|
||||
scene.pop_layer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,9 +296,10 @@ impl Element for Container {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.child.rect_for_text_range(range_utf16, cx)
|
||||
self.child.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
@@ -302,13 +307,14 @@ impl Element for Container {
|
||||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &crate::DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "Container",
|
||||
"bounds": bounds.to_json(),
|
||||
"details": self.style.to_json(),
|
||||
"child": self.child.debug(cx),
|
||||
"child": self.child.debug(view, cx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,9 @@ use crate::{
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json::{json, ToJson},
|
||||
presenter::MeasurementContext,
|
||||
DebugContext,
|
||||
SceneBuilder, View, ViewContext,
|
||||
};
|
||||
use crate::{Element, LayoutContext, PaintContext, SizeConstraint};
|
||||
use crate::{Drawable, SizeConstraint};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Empty {
|
||||
@@ -27,14 +26,15 @@ impl Empty {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Empty {
|
||||
impl<V: View> Drawable<V> for Empty {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
_: &mut LayoutContext,
|
||||
_: &mut V,
|
||||
_: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let x = if constraint.max.x().is_finite() && !self.collapsed {
|
||||
constraint.max.x()
|
||||
@@ -52,10 +52,12 @@ impl Element for Empty {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: &mut SceneBuilder,
|
||||
_: RectF,
|
||||
_: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
_: &mut PaintContext,
|
||||
_: &mut V,
|
||||
_: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
}
|
||||
|
||||
@@ -66,7 +68,8 @@ impl Element for Empty {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &MeasurementContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
None
|
||||
}
|
||||
@@ -76,7 +79,8 @@ impl Element for Empty {
|
||||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &DebugContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "Empty",
|
||||
|
||||
@@ -2,20 +2,18 @@ use std::ops::Range;
|
||||
|
||||
use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json,
|
||||
presenter::MeasurementContext,
|
||||
DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
|
||||
json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
pub struct Expanded {
|
||||
child: ElementBox,
|
||||
pub struct Expanded<V: View> {
|
||||
child: Element<V>,
|
||||
full_width: bool,
|
||||
full_height: bool,
|
||||
}
|
||||
|
||||
impl Expanded {
|
||||
pub fn new(child: ElementBox) -> Self {
|
||||
impl<V: View> Expanded<V> {
|
||||
pub fn new(child: Element<V>) -> Self {
|
||||
Self {
|
||||
child,
|
||||
full_width: true,
|
||||
@@ -36,14 +34,15 @@ impl Expanded {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Expanded {
|
||||
impl<V: View> Drawable<V> for Expanded<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
mut constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
if self.full_width {
|
||||
constraint.min.set_x(constraint.max.x());
|
||||
@@ -51,18 +50,21 @@ impl Element for Expanded {
|
||||
if self.full_height {
|
||||
constraint.min.set_y(constraint.max.y());
|
||||
}
|
||||
let size = self.child.layout(constraint, cx);
|
||||
let size = self.child.layout(constraint, view, cx);
|
||||
(size, ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
@@ -72,9 +74,10 @@ impl Element for Expanded {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.child.rect_for_text_range(range_utf16, cx)
|
||||
self.child.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
@@ -82,13 +85,14 @@ impl Element for Expanded {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> json::Value {
|
||||
json!({
|
||||
"type": "Expanded",
|
||||
"full_width": self.full_width,
|
||||
"full_height": self.full_height,
|
||||
"child": self.child.debug(cx)
|
||||
"child": self.child.debug(view, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
json::{self, ToJson, Value},
|
||||
presenter::MeasurementContext,
|
||||
Axis, DebugContext, Element, ElementBox, ElementStateHandle, LayoutContext, PaintContext,
|
||||
RenderContext, SizeConstraint, Vector2FExt, View,
|
||||
Axis, Drawable, Element, ElementStateHandle, SceneBuilder, SizeConstraint, Vector2FExt, View,
|
||||
ViewContext,
|
||||
};
|
||||
use pathfinder_geometry::{
|
||||
rect::RectF,
|
||||
@@ -18,14 +17,14 @@ struct ScrollState {
|
||||
scroll_position: Cell<f32>,
|
||||
}
|
||||
|
||||
pub struct Flex {
|
||||
pub struct Flex<V: View> {
|
||||
axis: Axis,
|
||||
children: Vec<ElementBox>,
|
||||
children: Vec<Element<V>>,
|
||||
scroll_state: Option<(ElementStateHandle<Rc<ScrollState>>, usize)>,
|
||||
child_alignment: f32,
|
||||
}
|
||||
|
||||
impl Flex {
|
||||
impl<V: View> Flex<V> {
|
||||
pub fn new(axis: Axis) -> Self {
|
||||
Self {
|
||||
axis,
|
||||
@@ -52,15 +51,14 @@ impl Flex {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn scrollable<Tag, V>(
|
||||
pub fn scrollable<Tag>(
|
||||
mut self,
|
||||
element_id: usize,
|
||||
scroll_to: Option<usize>,
|
||||
cx: &mut RenderContext<V>,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self
|
||||
where
|
||||
Tag: 'static,
|
||||
V: View,
|
||||
{
|
||||
let scroll_state = cx.default_element_state::<Tag, Rc<ScrollState>>(element_id);
|
||||
scroll_state.read(cx).scroll_to.set(scroll_to);
|
||||
@@ -75,7 +73,8 @@ impl Flex {
|
||||
remaining_space: &mut f32,
|
||||
remaining_flex: &mut f32,
|
||||
cross_axis_max: &mut f32,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
let cross_axis = self.axis.invert();
|
||||
for child in &mut self.children {
|
||||
@@ -102,7 +101,7 @@ impl Flex {
|
||||
vec2f(constraint.max.x(), child_max),
|
||||
),
|
||||
};
|
||||
let child_size = child.layout(child_constraint, cx);
|
||||
let child_size = child.layout(child_constraint, view, cx);
|
||||
*remaining_space -= child_size.along(self.axis);
|
||||
*remaining_flex -= flex;
|
||||
*cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
|
||||
@@ -112,20 +111,21 @@ impl Flex {
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<ElementBox> for Flex {
|
||||
fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
|
||||
impl<V: View> Extend<Element<V>> for Flex<V> {
|
||||
fn extend<T: IntoIterator<Item = Element<V>>>(&mut self, children: T) {
|
||||
self.children.extend(children);
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Flex {
|
||||
impl<V: View> Drawable<V> for Flex<V> {
|
||||
type LayoutState = f32;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let mut total_flex = None;
|
||||
let mut fixed_space = 0.0;
|
||||
@@ -150,7 +150,7 @@ impl Element for Flex {
|
||||
vec2f(constraint.max.x(), INFINITY),
|
||||
),
|
||||
};
|
||||
let size = child.layout(child_constraint, cx);
|
||||
let size = child.layout(child_constraint, view, cx);
|
||||
fixed_space += size.along(self.axis);
|
||||
cross_axis_max = cross_axis_max.max(size.along(cross_axis));
|
||||
}
|
||||
@@ -168,6 +168,7 @@ impl Element for Flex {
|
||||
&mut remaining_space,
|
||||
&mut remaining_flex,
|
||||
&mut cross_axis_max,
|
||||
view,
|
||||
cx,
|
||||
);
|
||||
self.layout_flex_children(
|
||||
@@ -176,6 +177,7 @@ impl Element for Flex {
|
||||
&mut remaining_space,
|
||||
&mut remaining_flex,
|
||||
&mut cross_axis_max,
|
||||
view,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -247,26 +249,28 @@ impl Element for Flex {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
remaining_space: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
|
||||
|
||||
let mut remaining_space = *remaining_space;
|
||||
let overflowing = remaining_space < 0.;
|
||||
if overflowing {
|
||||
cx.scene.push_layer(Some(visible_bounds));
|
||||
scene.push_layer(Some(visible_bounds));
|
||||
}
|
||||
|
||||
if let Some(scroll_state) = &self.scroll_state {
|
||||
cx.scene.push_mouse_region(
|
||||
scene.push_mouse_region(
|
||||
crate::MouseRegion::new::<Self>(scroll_state.1, 0, bounds)
|
||||
.on_scroll({
|
||||
let scroll_state = scroll_state.0.read(cx).clone();
|
||||
let axis = self.axis;
|
||||
move |e, cx| {
|
||||
move |e, _: &mut V, cx| {
|
||||
if remaining_space < 0. {
|
||||
let scroll_delta = e.delta.raw();
|
||||
|
||||
@@ -294,7 +298,7 @@ impl Element for Flex {
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_move(|_, _| { /* Capture move events */ }),
|
||||
.on_move(|_, _: &mut V, _| { /* Capture move events */ }),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -343,7 +347,7 @@ impl Element for Flex {
|
||||
aligned_child_origin
|
||||
};
|
||||
|
||||
child.paint(aligned_child_origin, visible_bounds, cx);
|
||||
child.paint(scene, aligned_child_origin, visible_bounds, view, cx);
|
||||
|
||||
match self.axis {
|
||||
Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
|
||||
@@ -352,7 +356,7 @@ impl Element for Flex {
|
||||
}
|
||||
|
||||
if overflowing {
|
||||
cx.scene.pop_layer();
|
||||
scene.pop_layer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,11 +367,12 @@ impl Element for Flex {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.children
|
||||
.iter()
|
||||
.find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx))
|
||||
.find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
|
||||
}
|
||||
|
||||
fn debug(
|
||||
@@ -375,13 +380,14 @@ impl Element for Flex {
|
||||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> json::Value {
|
||||
json!({
|
||||
"type": "Flex",
|
||||
"bounds": bounds.to_json(),
|
||||
"axis": self.axis.to_json(),
|
||||
"children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
|
||||
"children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -391,13 +397,13 @@ struct FlexParentData {
|
||||
float: bool,
|
||||
}
|
||||
|
||||
pub struct FlexItem {
|
||||
pub struct FlexItem<V: View> {
|
||||
metadata: FlexParentData,
|
||||
child: ElementBox,
|
||||
child: Element<V>,
|
||||
}
|
||||
|
||||
impl FlexItem {
|
||||
pub fn new(child: ElementBox) -> Self {
|
||||
impl<V: View> FlexItem<V> {
|
||||
pub fn new(child: Element<V>) -> Self {
|
||||
FlexItem {
|
||||
metadata: FlexParentData {
|
||||
flex: None,
|
||||
@@ -418,27 +424,31 @@ impl FlexItem {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for FlexItem {
|
||||
impl<V: View> Drawable<V> for FlexItem<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let size = self.child.layout(constraint, cx);
|
||||
let size = self.child.layout(constraint, view, cx);
|
||||
(size, ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx)
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx)
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
@@ -448,9 +458,10 @@ impl Element for FlexItem {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.child.rect_for_text_range(range_utf16, cx)
|
||||
self.child.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<&dyn Any> {
|
||||
@@ -462,12 +473,13 @@ impl Element for FlexItem {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Value {
|
||||
json!({
|
||||
"type": "Flexible",
|
||||
"flex": self.metadata.flex,
|
||||
"child": self.child.debug(cx)
|
||||
"child": self.child.debug(view, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,16 @@ use std::ops::Range;
|
||||
use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json::json,
|
||||
presenter::MeasurementContext,
|
||||
DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
|
||||
Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
|
||||
pub struct Hook {
|
||||
child: ElementBox,
|
||||
after_layout: Option<Box<dyn FnMut(Vector2F, &mut LayoutContext)>>,
|
||||
pub struct Hook<V: View> {
|
||||
child: Element<V>,
|
||||
after_layout: Option<Box<dyn FnMut(Vector2F, &mut ViewContext<V>)>>,
|
||||
}
|
||||
|
||||
impl Hook {
|
||||
pub fn new(child: ElementBox) -> Self {
|
||||
impl<V: View> Hook<V> {
|
||||
pub fn new(child: Element<V>) -> Self {
|
||||
Self {
|
||||
child,
|
||||
after_layout: None,
|
||||
@@ -22,23 +21,24 @@ impl Hook {
|
||||
|
||||
pub fn on_after_layout(
|
||||
mut self,
|
||||
f: impl 'static + FnMut(Vector2F, &mut LayoutContext),
|
||||
f: impl 'static + FnMut(Vector2F, &mut ViewContext<V>),
|
||||
) -> Self {
|
||||
self.after_layout = Some(Box::new(f));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Hook {
|
||||
impl<V: View> Drawable<V> for Hook<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let size = self.child.layout(constraint, cx);
|
||||
let size = self.child.layout(constraint, view, cx);
|
||||
if let Some(handler) = self.after_layout.as_mut() {
|
||||
handler(size, cx);
|
||||
}
|
||||
@@ -47,12 +47,15 @@ impl Element for Hook {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
@@ -62,9 +65,10 @@ impl Element for Hook {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.child.rect_for_text_range(range_utf16, cx)
|
||||
self.child.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
@@ -72,11 +76,12 @@ impl Element for Hook {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "Hooks",
|
||||
"child": self.child.debug(cx),
|
||||
"child": self.child.debug(view, cx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ use crate::{
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json::{json, ToJson},
|
||||
presenter::MeasurementContext,
|
||||
scene, Border, DebugContext, Element, ImageData, LayoutContext, PaintContext, SizeConstraint,
|
||||
scene, Border, Drawable, ImageData, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
@@ -56,14 +55,15 @@ impl Image {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Image {
|
||||
impl<V: View> Drawable<V> for Image {
|
||||
type LayoutState = Option<Arc<ImageData>>;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
_: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let data = match &self.source {
|
||||
ImageSource::Path(path) => match cx.asset_cache.png(path) {
|
||||
@@ -90,13 +90,15 @@ impl Element for Image {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
_: RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
_: &mut V,
|
||||
_: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
if let Some(data) = layout {
|
||||
cx.scene.push_image(scene::Image {
|
||||
scene.push_image(scene::Image {
|
||||
bounds,
|
||||
border: self.style.border,
|
||||
corner_radius: self.style.corner_radius,
|
||||
@@ -113,7 +115,8 @@ impl Element for Image {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &MeasurementContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
None
|
||||
}
|
||||
@@ -123,7 +126,8 @@ impl Element for Image {
|
||||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &DebugContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "Image",
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
elements::*,
|
||||
fonts::TextStyle,
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
Action, ElementBox, LayoutContext, PaintContext, SizeConstraint,
|
||||
Action, Element, SizeConstraint,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -12,20 +12,17 @@ pub struct KeystrokeLabel {
|
||||
action: Box<dyn Action>,
|
||||
container_style: ContainerStyle,
|
||||
text_style: TextStyle,
|
||||
window_id: usize,
|
||||
view_id: usize,
|
||||
}
|
||||
|
||||
impl KeystrokeLabel {
|
||||
pub fn new(
|
||||
window_id: usize,
|
||||
view_id: usize,
|
||||
action: Box<dyn Action>,
|
||||
container_style: ContainerStyle,
|
||||
text_style: TextStyle,
|
||||
) -> Self {
|
||||
Self {
|
||||
window_id,
|
||||
view_id,
|
||||
action,
|
||||
container_style,
|
||||
@@ -34,18 +31,18 @@ impl KeystrokeLabel {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for KeystrokeLabel {
|
||||
type LayoutState = ElementBox;
|
||||
impl<V: View> Drawable<V> for KeystrokeLabel {
|
||||
type LayoutState = Element<V>;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
) -> (Vector2F, ElementBox) {
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Element<V>) {
|
||||
let mut element = if let Some(keystrokes) =
|
||||
cx.app
|
||||
.keystrokes_for_action(self.window_id, self.view_id, self.action.as_ref())
|
||||
cx.keystrokes_for_action(self.view_id, self.action.as_ref())
|
||||
{
|
||||
Flex::row()
|
||||
.with_children(keystrokes.iter().map(|keystroke| {
|
||||
@@ -59,18 +56,20 @@ impl Element for KeystrokeLabel {
|
||||
Empty::new().collapsed().boxed()
|
||||
};
|
||||
|
||||
let size = element.layout(constraint, cx);
|
||||
let size = element.layout(constraint, view, cx);
|
||||
(size, element)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
element: &mut ElementBox,
|
||||
cx: &mut PaintContext,
|
||||
element: &mut Element<V>,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
element.paint(bounds.origin(), visible_bounds, cx);
|
||||
element.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
@@ -80,7 +79,8 @@ impl Element for KeystrokeLabel {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &MeasurementContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
None
|
||||
}
|
||||
@@ -88,14 +88,15 @@ impl Element for KeystrokeLabel {
|
||||
fn debug(
|
||||
&self,
|
||||
_: RectF,
|
||||
element: &ElementBox,
|
||||
element: &Element<V>,
|
||||
_: &(),
|
||||
cx: &crate::DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "KeystrokeLabel",
|
||||
"action": self.action.name(),
|
||||
"child": element.debug(cx)
|
||||
"child": element.debug(view, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,8 @@ use crate::{
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json::{ToJson, Value},
|
||||
presenter::MeasurementContext,
|
||||
text_layout::{Line, RunStyle},
|
||||
DebugContext, Element, LayoutContext, PaintContext, SizeConstraint,
|
||||
Drawable, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
@@ -128,19 +127,22 @@ impl Label {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Label {
|
||||
impl<V: View> Drawable<V> for Label {
|
||||
type LayoutState = Line;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
_: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let runs = self.compute_runs();
|
||||
let line =
|
||||
cx.text_layout_cache
|
||||
.layout_str(&self.text, self.style.text.font_size, runs.as_slice());
|
||||
let line = cx.text_layout_cache().layout_str(
|
||||
&self.text,
|
||||
self.style.text.font_size,
|
||||
runs.as_slice(),
|
||||
);
|
||||
|
||||
let size = vec2f(
|
||||
line.width()
|
||||
@@ -155,12 +157,20 @@ impl Element for Label {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
line: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
_: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
line.paint(bounds.origin(), visible_bounds, bounds.size().y(), cx)
|
||||
line.paint(
|
||||
scene,
|
||||
bounds.origin(),
|
||||
visible_bounds,
|
||||
bounds.size().y(),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
@@ -170,7 +180,8 @@ impl Element for Label {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &MeasurementContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
None
|
||||
}
|
||||
@@ -180,7 +191,8 @@ impl Element for Label {
|
||||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &DebugContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Value {
|
||||
json!({
|
||||
"type": "Label",
|
||||
|
||||
+331
-299
@@ -4,19 +4,16 @@ use crate::{
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json::json,
|
||||
presenter::MeasurementContext,
|
||||
DebugContext, Element, ElementBox, ElementRc, EventContext, LayoutContext, MouseRegion,
|
||||
PaintContext, RenderContext, SizeConstraint, View, ViewContext,
|
||||
Drawable, Element, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
use std::{cell::RefCell, collections::VecDeque, ops::Range, rc::Rc};
|
||||
use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
|
||||
pub struct List {
|
||||
state: ListState,
|
||||
pub struct List<V: View> {
|
||||
state: ListState<V>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ListState(Rc<RefCell<StateInner>>);
|
||||
pub struct ListState<V: View>(Rc<RefCell<StateInner<V>>>);
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Orientation {
|
||||
@@ -24,16 +21,16 @@ pub enum Orientation {
|
||||
Bottom,
|
||||
}
|
||||
|
||||
struct StateInner {
|
||||
struct StateInner<V: View> {
|
||||
last_layout_width: Option<f32>,
|
||||
render_item: Box<dyn FnMut(usize, &mut LayoutContext) -> Option<ElementBox>>,
|
||||
render_item: Box<dyn FnMut(&mut V, usize, &mut ViewContext<V>) -> Element<V>>,
|
||||
rendered_range: Range<usize>,
|
||||
items: SumTree<ListItem>,
|
||||
items: SumTree<ListItem<V>>,
|
||||
logical_scroll_top: Option<ListOffset>,
|
||||
orientation: Orientation,
|
||||
overdraw: f32,
|
||||
#[allow(clippy::type_complexity)]
|
||||
scroll_handler: Option<Box<dyn FnMut(Range<usize>, &mut EventContext)>>,
|
||||
scroll_handler: Option<Box<dyn FnMut(Range<usize>, &mut V, &mut ViewContext<V>)>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
@@ -42,14 +39,23 @@ pub struct ListOffset {
|
||||
pub offset_in_item: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ListItem {
|
||||
enum ListItem<V: View> {
|
||||
Unrendered,
|
||||
Rendered(ElementRc),
|
||||
Rendered(Rc<RefCell<Element<V>>>),
|
||||
Removed(f32),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ListItem {
|
||||
impl<V: View> Clone for ListItem<V> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Unrendered => Self::Unrendered,
|
||||
Self::Rendered(element) => Self::Rendered(element.clone()),
|
||||
Self::Removed(height) => Self::Removed(*height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View> Debug for ListItem<V> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Unrendered => write!(f, "Unrendered"),
|
||||
@@ -79,20 +85,21 @@ struct UnrenderedCount(usize);
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct Height(f32);
|
||||
|
||||
impl List {
|
||||
pub fn new(state: ListState) -> Self {
|
||||
impl<V: View> List<V> {
|
||||
pub fn new(state: ListState<V>) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for List {
|
||||
impl<V: View> Drawable<V> for List<V> {
|
||||
type LayoutState = ListOffset;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let state = &mut *self.state.0.borrow_mut();
|
||||
let size = constraint.max;
|
||||
@@ -134,9 +141,10 @@ impl Element for List {
|
||||
scroll_top.item_ix + ix,
|
||||
existing_element,
|
||||
item_constraint,
|
||||
view,
|
||||
cx,
|
||||
) {
|
||||
rendered_height += element.size().y();
|
||||
rendered_height += element.borrow().size().y();
|
||||
rendered_items.push_back(ListItem::Rendered(element));
|
||||
}
|
||||
}
|
||||
@@ -151,9 +159,9 @@ impl Element for List {
|
||||
cursor.prev(&());
|
||||
if cursor.item().is_some() {
|
||||
if let Some(element) =
|
||||
state.render_item(cursor.start().0, None, item_constraint, cx)
|
||||
state.render_item(cursor.start().0, None, item_constraint, view, cx)
|
||||
{
|
||||
rendered_height += element.size().y();
|
||||
rendered_height += element.borrow().size().y();
|
||||
rendered_items.push_front(ListItem::Rendered(element));
|
||||
}
|
||||
} else {
|
||||
@@ -187,9 +195,9 @@ impl Element for List {
|
||||
cursor.prev(&());
|
||||
if let Some(item) = cursor.item() {
|
||||
if let Some(element) =
|
||||
state.render_item(cursor.start().0, Some(item), item_constraint, cx)
|
||||
state.render_item(cursor.start().0, Some(item), item_constraint, view, cx)
|
||||
{
|
||||
leading_overdraw += element.size().y();
|
||||
leading_overdraw += element.borrow().size().y();
|
||||
rendered_items.push_front(ListItem::Rendered(element));
|
||||
}
|
||||
} else {
|
||||
@@ -241,25 +249,27 @@ impl Element for List {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
scroll_top: &mut ListOffset,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
|
||||
cx.scene.push_layer(Some(visible_bounds));
|
||||
|
||||
cx.scene.push_mouse_region(
|
||||
MouseRegion::new::<Self>(cx.current_view_id(), 0, bounds).on_scroll({
|
||||
scene.push_layer(Some(visible_bounds));
|
||||
scene.push_mouse_region(
|
||||
MouseRegion::new::<Self>(cx.view_id(), 0, bounds).on_scroll({
|
||||
let state = self.state.clone();
|
||||
let height = bounds.height();
|
||||
let scroll_top = scroll_top.clone();
|
||||
move |e, cx| {
|
||||
move |e, view, cx| {
|
||||
state.0.borrow_mut().scroll(
|
||||
&scroll_top,
|
||||
height,
|
||||
*e.platform_event.delta.raw(),
|
||||
e.platform_event.delta.precise(),
|
||||
view,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -267,11 +277,13 @@ impl Element for List {
|
||||
);
|
||||
|
||||
let state = &mut *self.state.0.borrow_mut();
|
||||
for (mut element, origin) in state.visible_elements(bounds, scroll_top) {
|
||||
element.paint(origin, visible_bounds, cx);
|
||||
for (element, origin) in state.visible_elements(bounds, scroll_top) {
|
||||
element
|
||||
.borrow_mut()
|
||||
.paint(scene, origin, visible_bounds, view, cx);
|
||||
}
|
||||
|
||||
cx.scene.pop_layer();
|
||||
scene.pop_layer();
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
@@ -281,7 +293,8 @@ impl Element for List {
|
||||
_: RectF,
|
||||
scroll_top: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
let state = self.state.0.borrow();
|
||||
let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
|
||||
@@ -293,11 +306,15 @@ impl Element for List {
|
||||
}
|
||||
|
||||
if let ListItem::Rendered(element) = item {
|
||||
if let Some(rect) = element.rect_for_text_range(range_utf16.clone(), cx) {
|
||||
if let Some(rect) =
|
||||
element
|
||||
.borrow()
|
||||
.rect_for_text_range(range_utf16.clone(), view, cx)
|
||||
{
|
||||
return Some(rect);
|
||||
}
|
||||
|
||||
item_origin.set_y(item_origin.y() + element.size().y());
|
||||
item_origin.set_y(item_origin.y() + element.borrow().size().y());
|
||||
cursor.next(&());
|
||||
} else {
|
||||
unreachable!();
|
||||
@@ -312,12 +329,13 @@ impl Element for List {
|
||||
bounds: RectF,
|
||||
scroll_top: &Self::LayoutState,
|
||||
_: &(),
|
||||
cx: &DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
let state = self.state.0.borrow_mut();
|
||||
let visible_elements = state
|
||||
.visible_elements(bounds, scroll_top)
|
||||
.map(|e| e.0.debug(cx))
|
||||
.map(|e| e.0.borrow().debug(view, cx))
|
||||
.collect::<Vec<_>>();
|
||||
let visible_range = scroll_top.item_ix..(scroll_top.item_ix + visible_elements.len());
|
||||
json!({
|
||||
@@ -328,27 +346,22 @@ impl Element for List {
|
||||
}
|
||||
}
|
||||
|
||||
impl ListState {
|
||||
pub fn new<F, V>(
|
||||
impl<V: View> ListState<V> {
|
||||
pub fn new<F>(
|
||||
element_count: usize,
|
||||
orientation: Orientation,
|
||||
overdraw: f32,
|
||||
cx: &mut ViewContext<V>,
|
||||
mut render_item: F,
|
||||
render_item: F,
|
||||
) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: 'static + FnMut(&mut V, usize, &mut RenderContext<V>) -> ElementBox,
|
||||
F: 'static + FnMut(&mut V, usize, &mut ViewContext<V>) -> Element<V>,
|
||||
{
|
||||
let mut items = SumTree::new();
|
||||
items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||
let handle = cx.weak_handle();
|
||||
Self(Rc::new(RefCell::new(StateInner {
|
||||
last_layout_width: None,
|
||||
render_item: Box::new(move |ix, cx| {
|
||||
let handle = handle.upgrade(cx)?;
|
||||
Some(cx.render(&handle, |view, cx| render_item(view, ix, cx)))
|
||||
}),
|
||||
render_item: Box::new(render_item),
|
||||
rendered_range: 0..0,
|
||||
items,
|
||||
logical_scroll_top: None,
|
||||
@@ -406,7 +419,7 @@ impl ListState {
|
||||
|
||||
pub fn set_scroll_handler(
|
||||
&mut self,
|
||||
handler: impl FnMut(Range<usize>, &mut EventContext) + 'static,
|
||||
handler: impl FnMut(Range<usize>, &mut V, &mut ViewContext<V>) + 'static,
|
||||
) {
|
||||
self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
|
||||
}
|
||||
@@ -426,20 +439,27 @@ impl ListState {
|
||||
}
|
||||
}
|
||||
|
||||
impl StateInner {
|
||||
impl<V: View> Clone for ListState<V> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View> StateInner<V> {
|
||||
fn render_item(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
existing_element: Option<&ListItem>,
|
||||
existing_element: Option<&ListItem<V>>,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
) -> Option<ElementRc> {
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Option<Rc<RefCell<Element<V>>>> {
|
||||
if let Some(ListItem::Rendered(element)) = existing_element {
|
||||
Some(element.clone())
|
||||
} else {
|
||||
let mut element = (self.render_item)(ix, cx)?;
|
||||
element.layout(constraint, cx);
|
||||
Some(element.into())
|
||||
let mut element = (self.render_item)(view, ix, cx);
|
||||
element.layout(constraint, view, cx);
|
||||
Some(Rc::new(RefCell::new(element)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,7 +475,7 @@ impl StateInner {
|
||||
&'a self,
|
||||
bounds: RectF,
|
||||
scroll_top: &ListOffset,
|
||||
) -> impl Iterator<Item = (ElementRc, Vector2F)> + 'a {
|
||||
) -> impl Iterator<Item = (Rc<RefCell<Element<V>>>, Vector2F)> + 'a {
|
||||
let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
|
||||
let mut cursor = self.items.cursor::<Count>();
|
||||
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||
@@ -467,7 +487,7 @@ impl StateInner {
|
||||
|
||||
if let ListItem::Rendered(element) = item {
|
||||
let result = (element.clone(), item_origin);
|
||||
item_origin.set_y(item_origin.y() + element.size().y());
|
||||
item_origin.set_y(item_origin.y() + element.borrow().size().y());
|
||||
cursor.next(&());
|
||||
return Some(result);
|
||||
}
|
||||
@@ -485,7 +505,8 @@ impl StateInner {
|
||||
height: f32,
|
||||
mut delta: Vector2F,
|
||||
precise: bool,
|
||||
cx: &mut EventContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
if !precise {
|
||||
delta *= 20.;
|
||||
@@ -511,7 +532,7 @@ impl StateInner {
|
||||
|
||||
if self.scroll_handler.is_some() {
|
||||
let visible_range = self.visible_range(height, scroll_top);
|
||||
self.scroll_handler.as_mut().unwrap()(visible_range, cx);
|
||||
self.scroll_handler.as_mut().unwrap()(visible_range, view, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
@@ -538,17 +559,17 @@ impl StateInner {
|
||||
}
|
||||
}
|
||||
|
||||
impl ListItem {
|
||||
impl<V: View> ListItem<V> {
|
||||
fn remove(&self) -> Self {
|
||||
match self {
|
||||
ListItem::Unrendered => ListItem::Unrendered,
|
||||
ListItem::Rendered(element) => ListItem::Removed(element.size().y()),
|
||||
ListItem::Rendered(element) => ListItem::Removed(element.borrow().size().y()),
|
||||
ListItem::Removed(height) => ListItem::Removed(*height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Item for ListItem {
|
||||
impl<V: View> sum_tree::Item for ListItem<V> {
|
||||
type Summary = ListItemSummary;
|
||||
|
||||
fn summary(&self) -> Self::Summary {
|
||||
@@ -563,7 +584,7 @@ impl sum_tree::Item for ListItem {
|
||||
count: 1,
|
||||
rendered_count: 1,
|
||||
unrendered_count: 0,
|
||||
height: element.size().y(),
|
||||
height: element.borrow().size().y(),
|
||||
},
|
||||
ListItem::Removed(height) => ListItemSummary {
|
||||
count: 1,
|
||||
@@ -631,264 +652,261 @@ mod tests {
|
||||
|
||||
#[crate::test(self)]
|
||||
fn test_layout(cx: &mut crate::AppContext) {
|
||||
let mut presenter = cx.build_presenter(0, 0., Default::default());
|
||||
let (_, view) = cx.add_window(Default::default(), |_| TestView);
|
||||
let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
|
||||
|
||||
let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
|
||||
|
||||
let state = view.update(cx, |_, cx| {
|
||||
ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, cx, {
|
||||
cx.add_window(Default::default(), |cx| {
|
||||
let mut view = TestView;
|
||||
let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
|
||||
let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
|
||||
let state = ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, {
|
||||
let elements = elements.clone();
|
||||
move |_, ix, _| {
|
||||
let (id, height) = elements.borrow()[ix];
|
||||
TestElement::new(id, height).boxed()
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let mut list = List::new(state.clone());
|
||||
let (size, _) = list.layout(constraint, &mut view, cx);
|
||||
assert_eq!(size, vec2f(100., 40.));
|
||||
assert_eq!(
|
||||
state.0.borrow().items.summary().clone(),
|
||||
ListItemSummary {
|
||||
count: 3,
|
||||
rendered_count: 3,
|
||||
unrendered_count: 0,
|
||||
height: 150.
|
||||
}
|
||||
);
|
||||
|
||||
state.0.borrow_mut().scroll(
|
||||
&ListOffset {
|
||||
item_ix: 0,
|
||||
offset_in_item: 0.,
|
||||
},
|
||||
40.,
|
||||
vec2f(0., -54.),
|
||||
true,
|
||||
&mut view,
|
||||
cx,
|
||||
);
|
||||
|
||||
let (_, logical_scroll_top) = list.layout(constraint, &mut view, cx);
|
||||
assert_eq!(
|
||||
logical_scroll_top,
|
||||
ListOffset {
|
||||
item_ix: 2,
|
||||
offset_in_item: 4.
|
||||
}
|
||||
);
|
||||
assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.);
|
||||
|
||||
elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
|
||||
elements.borrow_mut().push((5, 60.));
|
||||
state.splice(1..2, 2);
|
||||
state.splice(4..4, 1);
|
||||
assert_eq!(
|
||||
state.0.borrow().items.summary().clone(),
|
||||
ListItemSummary {
|
||||
count: 5,
|
||||
rendered_count: 2,
|
||||
unrendered_count: 3,
|
||||
height: 120.
|
||||
}
|
||||
);
|
||||
|
||||
let (size, logical_scroll_top) = list.layout(constraint, &mut view, cx);
|
||||
assert_eq!(size, vec2f(100., 40.));
|
||||
assert_eq!(
|
||||
state.0.borrow().items.summary().clone(),
|
||||
ListItemSummary {
|
||||
count: 5,
|
||||
rendered_count: 5,
|
||||
unrendered_count: 0,
|
||||
height: 270.
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
logical_scroll_top,
|
||||
ListOffset {
|
||||
item_ix: 3,
|
||||
offset_in_item: 4.
|
||||
}
|
||||
);
|
||||
assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.);
|
||||
|
||||
view
|
||||
});
|
||||
|
||||
let mut list = List::new(state.clone());
|
||||
let (size, _) = list.layout(
|
||||
constraint,
|
||||
&mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
|
||||
);
|
||||
assert_eq!(size, vec2f(100., 40.));
|
||||
assert_eq!(
|
||||
state.0.borrow().items.summary().clone(),
|
||||
ListItemSummary {
|
||||
count: 3,
|
||||
rendered_count: 3,
|
||||
unrendered_count: 0,
|
||||
height: 150.
|
||||
}
|
||||
);
|
||||
|
||||
state.0.borrow_mut().scroll(
|
||||
&ListOffset {
|
||||
item_ix: 0,
|
||||
offset_in_item: 0.,
|
||||
},
|
||||
40.,
|
||||
vec2f(0., -54.),
|
||||
true,
|
||||
&mut presenter.build_event_context(&mut Default::default(), cx),
|
||||
);
|
||||
let (_, logical_scroll_top) = list.layout(
|
||||
constraint,
|
||||
&mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
|
||||
);
|
||||
assert_eq!(
|
||||
logical_scroll_top,
|
||||
ListOffset {
|
||||
item_ix: 2,
|
||||
offset_in_item: 4.
|
||||
}
|
||||
);
|
||||
assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.);
|
||||
|
||||
elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
|
||||
elements.borrow_mut().push((5, 60.));
|
||||
state.splice(1..2, 2);
|
||||
state.splice(4..4, 1);
|
||||
assert_eq!(
|
||||
state.0.borrow().items.summary().clone(),
|
||||
ListItemSummary {
|
||||
count: 5,
|
||||
rendered_count: 2,
|
||||
unrendered_count: 3,
|
||||
height: 120.
|
||||
}
|
||||
);
|
||||
|
||||
let (size, logical_scroll_top) = list.layout(
|
||||
constraint,
|
||||
&mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
|
||||
);
|
||||
assert_eq!(size, vec2f(100., 40.));
|
||||
assert_eq!(
|
||||
state.0.borrow().items.summary().clone(),
|
||||
ListItemSummary {
|
||||
count: 5,
|
||||
rendered_count: 5,
|
||||
unrendered_count: 0,
|
||||
height: 270.
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
logical_scroll_top,
|
||||
ListOffset {
|
||||
item_ix: 3,
|
||||
offset_in_item: 4.
|
||||
}
|
||||
);
|
||||
assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.);
|
||||
}
|
||||
|
||||
#[crate::test(self, iterations = 10, seed = 0)]
|
||||
#[crate::test(self, iterations = 10)]
|
||||
fn test_random(cx: &mut crate::AppContext, mut rng: StdRng) {
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
|
||||
let (_, view) = cx.add_window(Default::default(), |_| TestView);
|
||||
let mut presenter = cx.build_presenter(0, 0., Default::default());
|
||||
let mut next_id = 0;
|
||||
let elements = Rc::new(RefCell::new(
|
||||
(0..rng.gen_range(0..=20))
|
||||
.map(|_| {
|
||||
let id = next_id;
|
||||
next_id += 1;
|
||||
(id, rng.gen_range(0..=200) as f32 / 2.0)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
));
|
||||
let orientation = *[Orientation::Top, Orientation::Bottom]
|
||||
.choose(&mut rng)
|
||||
.unwrap();
|
||||
let overdraw = rng.gen_range(1..=100) as f32;
|
||||
cx.add_window(Default::default(), |cx| {
|
||||
let mut view = TestView;
|
||||
|
||||
let state = view.update(cx, |_, cx| {
|
||||
ListState::new(elements.borrow().len(), orientation, overdraw, cx, {
|
||||
let mut next_id = 0;
|
||||
let elements = Rc::new(RefCell::new(
|
||||
(0..rng.gen_range(0..=20))
|
||||
.map(|_| {
|
||||
let id = next_id;
|
||||
next_id += 1;
|
||||
(id, rng.gen_range(0..=200) as f32 / 2.0)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
));
|
||||
let orientation = *[Orientation::Top, Orientation::Bottom]
|
||||
.choose(&mut rng)
|
||||
.unwrap();
|
||||
let overdraw = rng.gen_range(1..=100) as f32;
|
||||
|
||||
let state = ListState::new(elements.borrow().len(), orientation, overdraw, {
|
||||
let elements = elements.clone();
|
||||
move |_, ix, _| {
|
||||
let (id, height) = elements.borrow()[ix];
|
||||
TestElement::new(id, height).boxed()
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
let mut width = rng.gen_range(0..=2000) as f32 / 2.;
|
||||
let mut height = rng.gen_range(0..=2000) as f32 / 2.;
|
||||
log::info!("orientation: {:?}", orientation);
|
||||
log::info!("overdraw: {}", overdraw);
|
||||
log::info!("elements: {:?}", elements.borrow());
|
||||
log::info!("size: ({:?}, {:?})", width, height);
|
||||
log::info!("==================");
|
||||
let mut width = rng.gen_range(0..=2000) as f32 / 2.;
|
||||
let mut height = rng.gen_range(0..=2000) as f32 / 2.;
|
||||
log::info!("orientation: {:?}", orientation);
|
||||
log::info!("overdraw: {}", overdraw);
|
||||
log::info!("elements: {:?}", elements.borrow());
|
||||
log::info!("size: ({:?}, {:?})", width, height);
|
||||
log::info!("==================");
|
||||
|
||||
let mut last_logical_scroll_top = None;
|
||||
for _ in 0..operations {
|
||||
match rng.gen_range(0..=100) {
|
||||
0..=29 if last_logical_scroll_top.is_some() => {
|
||||
let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
|
||||
log::info!(
|
||||
"Scrolling by {:?}, previous scroll top: {:?}",
|
||||
delta,
|
||||
last_logical_scroll_top.unwrap()
|
||||
);
|
||||
state.0.borrow_mut().scroll(
|
||||
last_logical_scroll_top.as_ref().unwrap(),
|
||||
height,
|
||||
delta,
|
||||
true,
|
||||
&mut presenter.build_event_context(&mut Default::default(), cx),
|
||||
);
|
||||
let mut last_logical_scroll_top = None;
|
||||
for _ in 0..operations {
|
||||
match rng.gen_range(0..=100) {
|
||||
0..=29 if last_logical_scroll_top.is_some() => {
|
||||
let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
|
||||
log::info!(
|
||||
"Scrolling by {:?}, previous scroll top: {:?}",
|
||||
delta,
|
||||
last_logical_scroll_top.unwrap()
|
||||
);
|
||||
state.0.borrow_mut().scroll(
|
||||
last_logical_scroll_top.as_ref().unwrap(),
|
||||
height,
|
||||
delta,
|
||||
true,
|
||||
&mut view,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
30..=34 => {
|
||||
width = rng.gen_range(0..=2000) as f32 / 2.;
|
||||
log::info!("changing width: {:?}", width);
|
||||
}
|
||||
35..=54 => {
|
||||
height = rng.gen_range(0..=1000) as f32 / 2.;
|
||||
log::info!("changing height: {:?}", height);
|
||||
}
|
||||
_ => {
|
||||
let mut elements = elements.borrow_mut();
|
||||
let end_ix = rng.gen_range(0..=elements.len());
|
||||
let start_ix = rng.gen_range(0..=end_ix);
|
||||
let new_elements = (0..rng.gen_range(0..10))
|
||||
.map(|_| {
|
||||
let id = next_id;
|
||||
next_id += 1;
|
||||
(id, rng.gen_range(0..=200) as f32 / 2.)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
|
||||
state.splice(start_ix..end_ix, new_elements.len());
|
||||
elements.splice(start_ix..end_ix, new_elements);
|
||||
for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() {
|
||||
if let ListItem::Rendered(element) = item {
|
||||
let (expected_id, _) = elements[ix];
|
||||
element.borrow().with_metadata(|metadata: Option<&usize>| {
|
||||
assert_eq!(*metadata.unwrap(), expected_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30..=34 => {
|
||||
width = rng.gen_range(0..=2000) as f32 / 2.;
|
||||
log::info!("changing width: {:?}", width);
|
||||
}
|
||||
35..=54 => {
|
||||
height = rng.gen_range(0..=1000) as f32 / 2.;
|
||||
log::info!("changing height: {:?}", height);
|
||||
}
|
||||
_ => {
|
||||
let mut elements = elements.borrow_mut();
|
||||
let end_ix = rng.gen_range(0..=elements.len());
|
||||
let start_ix = rng.gen_range(0..=end_ix);
|
||||
let new_elements = (0..rng.gen_range(0..10))
|
||||
.map(|_| {
|
||||
let id = next_id;
|
||||
next_id += 1;
|
||||
(id, rng.gen_range(0..=200) as f32 / 2.)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
|
||||
state.splice(start_ix..end_ix, new_elements.len());
|
||||
elements.splice(start_ix..end_ix, new_elements);
|
||||
for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() {
|
||||
if let ListItem::Rendered(element) = item {
|
||||
let (expected_id, _) = elements[ix];
|
||||
|
||||
let mut list = List::new(state.clone());
|
||||
let window_size = vec2f(width, height);
|
||||
let (size, logical_scroll_top) = list.layout(
|
||||
SizeConstraint::new(vec2f(0., 0.), window_size),
|
||||
&mut view,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(size, window_size);
|
||||
last_logical_scroll_top = Some(logical_scroll_top);
|
||||
|
||||
let state = state.0.borrow();
|
||||
log::info!("items {:?}", state.items.items(&()));
|
||||
|
||||
let scroll_top = state.scroll_top(&logical_scroll_top);
|
||||
let rendered_top = (scroll_top - overdraw).max(0.);
|
||||
let rendered_bottom = scroll_top + height + overdraw;
|
||||
let mut item_top = 0.;
|
||||
|
||||
log::info!(
|
||||
"rendered top {:?}, rendered bottom {:?}, scroll top {:?}",
|
||||
rendered_top,
|
||||
rendered_bottom,
|
||||
scroll_top,
|
||||
);
|
||||
|
||||
let mut first_rendered_element_top = None;
|
||||
let mut last_rendered_element_bottom = None;
|
||||
assert_eq!(state.items.summary().count, elements.borrow().len());
|
||||
for (ix, item) in state.items.cursor::<()>().enumerate() {
|
||||
match item {
|
||||
ListItem::Unrendered => {
|
||||
let item_bottom = item_top;
|
||||
assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
|
||||
item_top = item_bottom;
|
||||
}
|
||||
ListItem::Removed(height) => {
|
||||
let (id, expected_height) = elements.borrow()[ix];
|
||||
assert_eq!(
|
||||
*height, expected_height,
|
||||
"element {} height didn't match",
|
||||
id
|
||||
);
|
||||
let item_bottom = item_top + height;
|
||||
assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
|
||||
item_top = item_bottom;
|
||||
}
|
||||
ListItem::Rendered(element) => {
|
||||
let (expected_id, expected_height) = elements.borrow()[ix];
|
||||
let element = element.borrow();
|
||||
element.with_metadata(|metadata: Option<&usize>| {
|
||||
assert_eq!(*metadata.unwrap(), expected_id);
|
||||
});
|
||||
assert_eq!(element.size().y(), expected_height);
|
||||
let item_bottom = item_top + element.size().y();
|
||||
first_rendered_element_top.get_or_insert(item_top);
|
||||
last_rendered_element_bottom = Some(item_bottom);
|
||||
assert!(item_bottom > rendered_top || item_top < rendered_bottom);
|
||||
item_top = item_bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match orientation {
|
||||
Orientation::Top => {
|
||||
if let Some(first_rendered_element_top) = first_rendered_element_top {
|
||||
assert!(first_rendered_element_top <= scroll_top);
|
||||
}
|
||||
}
|
||||
Orientation::Bottom => {
|
||||
if let Some(last_rendered_element_bottom) = last_rendered_element_bottom {
|
||||
assert!(last_rendered_element_bottom >= scroll_top + height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut list = List::new(state.clone());
|
||||
let window_size = vec2f(width, height);
|
||||
let (size, logical_scroll_top) = list.layout(
|
||||
SizeConstraint::new(vec2f(0., 0.), window_size),
|
||||
&mut presenter.build_layout_context(window_size, false, cx),
|
||||
);
|
||||
assert_eq!(size, window_size);
|
||||
last_logical_scroll_top = Some(logical_scroll_top);
|
||||
|
||||
let state = state.0.borrow();
|
||||
log::info!("items {:?}", state.items.items(&()));
|
||||
|
||||
let scroll_top = state.scroll_top(&logical_scroll_top);
|
||||
let rendered_top = (scroll_top - overdraw).max(0.);
|
||||
let rendered_bottom = scroll_top + height + overdraw;
|
||||
let mut item_top = 0.;
|
||||
|
||||
log::info!(
|
||||
"rendered top {:?}, rendered bottom {:?}, scroll top {:?}",
|
||||
rendered_top,
|
||||
rendered_bottom,
|
||||
scroll_top,
|
||||
);
|
||||
|
||||
let mut first_rendered_element_top = None;
|
||||
let mut last_rendered_element_bottom = None;
|
||||
assert_eq!(state.items.summary().count, elements.borrow().len());
|
||||
for (ix, item) in state.items.cursor::<()>().enumerate() {
|
||||
match item {
|
||||
ListItem::Unrendered => {
|
||||
let item_bottom = item_top;
|
||||
assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
|
||||
item_top = item_bottom;
|
||||
}
|
||||
ListItem::Removed(height) => {
|
||||
let (id, expected_height) = elements.borrow()[ix];
|
||||
assert_eq!(
|
||||
*height, expected_height,
|
||||
"element {} height didn't match",
|
||||
id
|
||||
);
|
||||
let item_bottom = item_top + height;
|
||||
assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
|
||||
item_top = item_bottom;
|
||||
}
|
||||
ListItem::Rendered(element) => {
|
||||
let (expected_id, expected_height) = elements.borrow()[ix];
|
||||
element.with_metadata(|metadata: Option<&usize>| {
|
||||
assert_eq!(*metadata.unwrap(), expected_id);
|
||||
});
|
||||
assert_eq!(element.size().y(), expected_height);
|
||||
let item_bottom = item_top + element.size().y();
|
||||
first_rendered_element_top.get_or_insert(item_top);
|
||||
last_rendered_element_bottom = Some(item_bottom);
|
||||
assert!(item_bottom > rendered_top || item_top < rendered_bottom);
|
||||
item_top = item_bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match orientation {
|
||||
Orientation::Top => {
|
||||
if let Some(first_rendered_element_top) = first_rendered_element_top {
|
||||
assert!(first_rendered_element_top <= scroll_top);
|
||||
}
|
||||
}
|
||||
Orientation::Bottom => {
|
||||
if let Some(last_rendered_element_bottom) = last_rendered_element_bottom {
|
||||
assert!(last_rendered_element_bottom >= scroll_top + height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
view
|
||||
});
|
||||
}
|
||||
|
||||
struct TestView;
|
||||
@@ -902,7 +920,7 @@ mod tests {
|
||||
"TestView"
|
||||
}
|
||||
|
||||
fn render(&mut self, _: &mut RenderContext<'_, Self>) -> ElementBox {
|
||||
fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
|
||||
Empty::new().boxed()
|
||||
}
|
||||
}
|
||||
@@ -921,15 +939,28 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for TestElement {
|
||||
impl<V: View> Drawable<V> for TestElement {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(&mut self, _: SizeConstraint, _: &mut LayoutContext) -> (Vector2F, ()) {
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: SizeConstraint,
|
||||
_: &mut V,
|
||||
_: &mut ViewContext<V>,
|
||||
) -> (Vector2F, ()) {
|
||||
(self.size, ())
|
||||
}
|
||||
|
||||
fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut PaintContext) {
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: &mut SceneBuilder,
|
||||
_: RectF,
|
||||
_: RectF,
|
||||
_: &mut (),
|
||||
_: &mut V,
|
||||
_: &mut ViewContext<V>,
|
||||
) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -940,12 +971,13 @@ mod tests {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &MeasurementContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn debug(&self, _: RectF, _: &(), _: &(), _: &DebugContext) -> serde_json::Value {
|
||||
fn debug(&self, _: RectF, _: &(), _: &(), _: &V, _: &ViewContext<V>) -> serde_json::Value {
|
||||
self.id.into()
|
||||
}
|
||||
|
||||
|
||||
@@ -10,14 +10,14 @@ use crate::{
|
||||
CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover,
|
||||
MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
|
||||
},
|
||||
DebugContext, Element, ElementBox, EventContext, LayoutContext, MeasurementContext,
|
||||
MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View,
|
||||
Drawable, Element, EventContext, MouseRegion, MouseState, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::{marker::PhantomData, ops::Range};
|
||||
|
||||
pub struct MouseEventHandler<Tag: 'static> {
|
||||
child: ElementBox,
|
||||
pub struct MouseEventHandler<Tag: 'static, V: View> {
|
||||
child: Element<V>,
|
||||
region_id: usize,
|
||||
cursor_style: Option<CursorStyle>,
|
||||
handlers: HandlerSet,
|
||||
@@ -31,11 +31,11 @@ pub struct MouseEventHandler<Tag: 'static> {
|
||||
|
||||
/// Element which provides a render_child callback with a MouseState and paints a mouse
|
||||
/// region under (or above) it for easy mouse event handling.
|
||||
impl<Tag> MouseEventHandler<Tag> {
|
||||
pub fn new<V, F>(region_id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
|
||||
impl<Tag, V: View> MouseEventHandler<Tag, V> {
|
||||
pub fn new<F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: FnOnce(&mut MouseState, &mut RenderContext<V>) -> ElementBox,
|
||||
F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> Element<V>,
|
||||
{
|
||||
let mut mouse_state = cx.mouse_state::<Tag>(region_id);
|
||||
let child = render_child(&mut mouse_state, cx);
|
||||
@@ -58,10 +58,10 @@ impl<Tag> MouseEventHandler<Tag> {
|
||||
/// Modifies the MouseEventHandler to render the MouseRegion above the child element. Useful
|
||||
/// for drag and drop handling and similar events which should be captured before the child
|
||||
/// gets the opportunity
|
||||
pub fn above<V, F>(region_id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
|
||||
pub fn above<F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: FnOnce(&mut MouseState, &mut RenderContext<V>) -> ElementBox,
|
||||
F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> Element<V>,
|
||||
{
|
||||
let mut handler = Self::new(region_id, cx, render_child);
|
||||
handler.above = true;
|
||||
@@ -78,14 +78,17 @@ impl<Tag> MouseEventHandler<Tag> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self {
|
||||
pub fn on_move(
|
||||
mut self,
|
||||
handler: impl Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_move(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_move_out(
|
||||
mut self,
|
||||
handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static,
|
||||
handler: impl Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_move_out(handler);
|
||||
self
|
||||
@@ -94,7 +97,7 @@ impl<Tag> MouseEventHandler<Tag> {
|
||||
pub fn on_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseDown, &mut EventContext) + 'static,
|
||||
handler: impl Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_down(button, handler);
|
||||
self
|
||||
@@ -103,7 +106,7 @@ impl<Tag> MouseEventHandler<Tag> {
|
||||
pub fn on_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseUp, &mut EventContext) + 'static,
|
||||
handler: impl Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_up(button, handler);
|
||||
self
|
||||
@@ -112,7 +115,7 @@ impl<Tag> MouseEventHandler<Tag> {
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseClick, &mut EventContext) + 'static,
|
||||
handler: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_click(button, handler);
|
||||
self
|
||||
@@ -121,7 +124,7 @@ impl<Tag> MouseEventHandler<Tag> {
|
||||
pub fn on_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseDownOut, &mut EventContext) + 'static,
|
||||
handler: impl Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_down_out(button, handler);
|
||||
self
|
||||
@@ -130,7 +133,7 @@ impl<Tag> MouseEventHandler<Tag> {
|
||||
pub fn on_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseUpOut, &mut EventContext) + 'static,
|
||||
handler: impl Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_up_out(button, handler);
|
||||
self
|
||||
@@ -139,20 +142,23 @@ impl<Tag> MouseEventHandler<Tag> {
|
||||
pub fn on_drag(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseDrag, &mut EventContext) + 'static,
|
||||
handler: impl Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_drag(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self {
|
||||
pub fn on_hover(
|
||||
mut self,
|
||||
handler: impl Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_hover(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_scroll(
|
||||
mut self,
|
||||
handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static,
|
||||
handler: impl Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_scroll(handler);
|
||||
self
|
||||
@@ -176,19 +182,25 @@ impl<Tag> MouseEventHandler<Tag> {
|
||||
.round_out()
|
||||
}
|
||||
|
||||
fn paint_regions(&self, bounds: RectF, visible_bounds: RectF, cx: &mut PaintContext) {
|
||||
fn paint_regions(
|
||||
&self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
|
||||
let hit_bounds = self.hit_bounds(visible_bounds);
|
||||
|
||||
if let Some(style) = self.cursor_style {
|
||||
cx.scene.push_cursor_region(CursorRegion {
|
||||
scene.push_cursor_region(CursorRegion {
|
||||
bounds: hit_bounds,
|
||||
style,
|
||||
});
|
||||
}
|
||||
cx.scene.push_mouse_region(
|
||||
scene.push_mouse_region(
|
||||
MouseRegion::from_handlers::<Tag>(
|
||||
cx.current_view_id(),
|
||||
cx.view_id(),
|
||||
self.region_id,
|
||||
hit_bounds,
|
||||
self.handlers.clone(),
|
||||
@@ -200,34 +212,39 @@ impl<Tag> MouseEventHandler<Tag> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> Element for MouseEventHandler<Tag> {
|
||||
impl<Tag, V: View> Drawable<V> for MouseEventHandler<Tag, V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
(self.child.layout(constraint, cx), ())
|
||||
(self.child.layout(constraint, view, cx), ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
if self.above {
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
|
||||
cx.paint_layer(None, |cx| {
|
||||
self.paint_regions(bounds, visible_bounds, cx);
|
||||
scene.paint_layer(None, |scene| {
|
||||
self.paint_regions(scene, bounds, visible_bounds, cx);
|
||||
});
|
||||
} else {
|
||||
self.paint_regions(bounds, visible_bounds, cx);
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
self.paint_regions(scene, bounds, visible_bounds, cx);
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,9 +255,10 @@ impl<Tag> Element for MouseEventHandler<Tag> {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.child.rect_for_text_range(range_utf16, cx)
|
||||
self.child.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
@@ -248,11 +266,12 @@ impl<Tag> Element for MouseEventHandler<Tag> {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "MouseEventHandler",
|
||||
"child": self.child.debug(cx),
|
||||
"child": self.child.debug(view, cx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,12 @@ use std::ops::Range;
|
||||
use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json::ToJson,
|
||||
presenter::MeasurementContext,
|
||||
Axis, DebugContext, Element, ElementBox, LayoutContext, MouseRegion, PaintContext,
|
||||
SizeConstraint,
|
||||
Axis, Drawable, Element, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
pub struct Overlay {
|
||||
child: ElementBox,
|
||||
pub struct Overlay<V: View> {
|
||||
child: Element<V>,
|
||||
anchor_position: Option<Vector2F>,
|
||||
anchor_corner: AnchorCorner,
|
||||
fit_mode: OverlayFitMode,
|
||||
@@ -74,8 +72,8 @@ impl AnchorCorner {
|
||||
}
|
||||
}
|
||||
|
||||
impl Overlay {
|
||||
pub fn new(child: ElementBox) -> Self {
|
||||
impl<V: View> Overlay<V> {
|
||||
pub fn new(child: Element<V>) -> Self {
|
||||
Self {
|
||||
child,
|
||||
anchor_position: None,
|
||||
@@ -118,30 +116,33 @@ impl Overlay {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Overlay {
|
||||
impl<V: View> Drawable<V> for Overlay<V> {
|
||||
type LayoutState = Vector2F;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let constraint = if self.anchor_position.is_some() {
|
||||
SizeConstraint::new(Vector2F::zero(), cx.window_size)
|
||||
SizeConstraint::new(Vector2F::zero(), cx.window_size())
|
||||
} else {
|
||||
constraint
|
||||
};
|
||||
let size = self.child.layout(constraint, cx);
|
||||
let size = self.child.layout(constraint, view, cx);
|
||||
(Vector2F::zero(), size)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
_: RectF,
|
||||
size: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
let (anchor_position, mut bounds) = match self.position_mode {
|
||||
OverlayPositionMode::Window => {
|
||||
@@ -162,9 +163,9 @@ impl Element for Overlay {
|
||||
OverlayFitMode::SnapToWindow => {
|
||||
// Snap the horizontal edges of the overlay to the horizontal edges of the window if
|
||||
// its horizontal bounds overflow
|
||||
if bounds.max_x() > cx.window_size.x() {
|
||||
if bounds.max_x() > cx.window_size().x() {
|
||||
let mut lower_right = bounds.lower_right();
|
||||
lower_right.set_x(cx.window_size.x());
|
||||
lower_right.set_x(cx.window_size().x());
|
||||
bounds = RectF::from_points(lower_right - *size, lower_right);
|
||||
} else if bounds.min_x() < 0. {
|
||||
let mut upper_left = bounds.origin();
|
||||
@@ -174,9 +175,9 @@ impl Element for Overlay {
|
||||
|
||||
// Snap the vertical edges of the overlay to the vertical edges of the window if
|
||||
// its vertical bounds overflow.
|
||||
if bounds.max_y() > cx.window_size.y() {
|
||||
if bounds.max_y() > cx.window_size().y() {
|
||||
let mut lower_right = bounds.lower_right();
|
||||
lower_right.set_y(cx.window_size.y());
|
||||
lower_right.set_y(cx.window_size().y());
|
||||
bounds = RectF::from_points(lower_right - *size, lower_right);
|
||||
} else if bounds.min_y() < 0. {
|
||||
let mut upper_left = bounds.origin();
|
||||
@@ -187,11 +188,11 @@ impl Element for Overlay {
|
||||
OverlayFitMode::SwitchAnchor => {
|
||||
let mut anchor_corner = self.anchor_corner;
|
||||
|
||||
if bounds.max_x() > cx.window_size.x() {
|
||||
if bounds.max_x() > cx.window_size().x() {
|
||||
anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
|
||||
}
|
||||
|
||||
if bounds.max_y() > cx.window_size.y() {
|
||||
if bounds.max_y() > cx.window_size().y() {
|
||||
anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
|
||||
}
|
||||
|
||||
@@ -211,21 +212,22 @@ impl Element for Overlay {
|
||||
OverlayFitMode::None => {}
|
||||
}
|
||||
|
||||
cx.paint_stacking_context(None, self.z_index, |cx| {
|
||||
scene.paint_stacking_context(None, self.z_index, |scene| {
|
||||
if self.hoverable {
|
||||
enum OverlayHoverCapture {}
|
||||
// Block hovers in lower stacking contexts
|
||||
cx.scene
|
||||
.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
|
||||
cx.current_view_id(),
|
||||
cx.current_view_id(),
|
||||
bounds,
|
||||
));
|
||||
scene.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
|
||||
cx.view_id(),
|
||||
cx.view_id(),
|
||||
bounds,
|
||||
));
|
||||
}
|
||||
|
||||
self.child.paint(
|
||||
scene,
|
||||
bounds.origin(),
|
||||
RectF::new(Vector2F::zero(), cx.window_size),
|
||||
RectF::new(Vector2F::zero(), cx.window_size()),
|
||||
view,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -238,9 +240,10 @@ impl Element for Overlay {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.child.rect_for_text_range(range_utf16, cx)
|
||||
self.child.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
@@ -248,12 +251,13 @@ impl Element for Overlay {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "Overlay",
|
||||
"abs_position": self.anchor_position.to_json(),
|
||||
"child": self.child.debug(cx),
|
||||
"child": self.child.debug(view, cx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
geometry::rect::RectF,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
scene::MouseDrag,
|
||||
Axis, Element, ElementBox, ElementStateHandle, MouseRegion, RenderContext, View,
|
||||
Axis, Drawable, Element, ElementStateHandle, MouseRegion, SceneBuilder, View, ViewContext,
|
||||
};
|
||||
|
||||
use super::{ConstrainedBox, Hook};
|
||||
@@ -75,22 +75,22 @@ struct ResizeHandleState {
|
||||
custom_dimension: Cell<f32>,
|
||||
}
|
||||
|
||||
pub struct Resizable {
|
||||
pub struct Resizable<V: View> {
|
||||
side: Side,
|
||||
handle_size: f32,
|
||||
child: ElementBox,
|
||||
child: Element<V>,
|
||||
state: Rc<ResizeHandleState>,
|
||||
_state_handle: ElementStateHandle<Rc<ResizeHandleState>>,
|
||||
}
|
||||
|
||||
impl Resizable {
|
||||
impl<V: View> Resizable<V> {
|
||||
pub fn new<Tag: 'static, T: View>(
|
||||
child: ElementBox,
|
||||
child: Element<V>,
|
||||
element_id: usize,
|
||||
side: Side,
|
||||
handle_size: f32,
|
||||
initial_size: f32,
|
||||
cx: &mut RenderContext<T>,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self {
|
||||
let state_handle = cx.element_state::<Tag, Rc<ResizeHandleState>>(
|
||||
element_id,
|
||||
@@ -132,51 +132,50 @@ impl Resizable {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Resizable {
|
||||
impl<V: View> Drawable<V> for Resizable<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: crate::SizeConstraint,
|
||||
cx: &mut crate::LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
(self.child.layout(constraint, cx), ())
|
||||
(self.child.layout(constraint, view, cx), ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: pathfinder_geometry::rect::RectF,
|
||||
visible_bounds: pathfinder_geometry::rect::RectF,
|
||||
_child_size: &mut Self::LayoutState,
|
||||
cx: &mut crate::PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
cx.scene.push_stacking_context(None, None);
|
||||
scene.push_stacking_context(None, None);
|
||||
|
||||
let handle_region = self.side.of_rect(bounds, self.handle_size);
|
||||
|
||||
enum ResizeHandle {}
|
||||
cx.scene.push_mouse_region(
|
||||
MouseRegion::new::<ResizeHandle>(
|
||||
cx.current_view_id(),
|
||||
self.side as usize,
|
||||
handle_region,
|
||||
)
|
||||
.on_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere
|
||||
.on_drag(MouseButton::Left, {
|
||||
let state = self.state.clone();
|
||||
let side = self.side;
|
||||
move |e, cx| {
|
||||
let prev_width = state.actual_dimension.get();
|
||||
state
|
||||
.custom_dimension
|
||||
.set(0f32.max(prev_width + side.compute_delta(e)).round());
|
||||
cx.notify();
|
||||
}
|
||||
}),
|
||||
scene.push_mouse_region(
|
||||
MouseRegion::new::<ResizeHandle>(cx.view_id(), self.side as usize, handle_region)
|
||||
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
|
||||
.on_drag(MouseButton::Left, {
|
||||
let state = self.state.clone();
|
||||
let side = self.side;
|
||||
move |e, _: &mut V, cx| {
|
||||
let prev_width = state.actual_dimension.get();
|
||||
state
|
||||
.custom_dimension
|
||||
.set(0f32.max(prev_width + side.compute_delta(e)).round());
|
||||
cx.notify();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
cx.scene.push_cursor_region(crate::CursorRegion {
|
||||
scene.push_cursor_region(crate::CursorRegion {
|
||||
bounds: handle_region,
|
||||
style: match self.side.axis() {
|
||||
Axis::Horizontal => CursorStyle::ResizeLeftRight,
|
||||
@@ -184,9 +183,10 @@ impl Element for Resizable {
|
||||
},
|
||||
});
|
||||
|
||||
cx.scene.pop_stacking_context();
|
||||
scene.pop_stacking_context();
|
||||
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
@@ -196,9 +196,10 @@ impl Element for Resizable {
|
||||
_visible_bounds: pathfinder_geometry::rect::RectF,
|
||||
_layout: &Self::LayoutState,
|
||||
_paint: &Self::PaintState,
|
||||
cx: &crate::MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<pathfinder_geometry::rect::RectF> {
|
||||
self.child.rect_for_text_range(range_utf16, cx)
|
||||
self.child.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
@@ -206,10 +207,11 @@ impl Element for Resizable {
|
||||
_bounds: pathfinder_geometry::rect::RectF,
|
||||
_layout: &Self::LayoutState,
|
||||
_paint: &Self::PaintState,
|
||||
cx: &crate::DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"child": self.child.debug(cx),
|
||||
"child": self.child.debug(view, cx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,41 +3,48 @@ use std::ops::Range;
|
||||
use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json::{self, json, ToJson},
|
||||
presenter::MeasurementContext,
|
||||
DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
|
||||
Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
|
||||
/// Element which renders it's children in a stack on top of each other.
|
||||
/// The first child determines the size of the others.
|
||||
#[derive(Default)]
|
||||
pub struct Stack {
|
||||
children: Vec<ElementBox>,
|
||||
pub struct Stack<V: View> {
|
||||
children: Vec<Element<V>>,
|
||||
}
|
||||
|
||||
impl Stack {
|
||||
impl<V: View> Default for Stack<V> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View> Stack<V> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Stack {
|
||||
impl<V: View> Drawable<V> for Stack<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
mut constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let mut size = constraint.min;
|
||||
let mut children = self.children.iter_mut();
|
||||
if let Some(bottom_child) = children.next() {
|
||||
size = bottom_child.layout(constraint, cx);
|
||||
size = bottom_child.layout(constraint, view, cx);
|
||||
constraint = SizeConstraint::strict(size);
|
||||
}
|
||||
|
||||
for child in children {
|
||||
child.layout(constraint, cx);
|
||||
child.layout(constraint, view, cx);
|
||||
}
|
||||
|
||||
(size, ())
|
||||
@@ -45,14 +52,16 @@ impl Element for Stack {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
for child in &mut self.children {
|
||||
cx.paint_layer(None, |cx| {
|
||||
child.paint(bounds.origin(), visible_bounds, cx);
|
||||
scene.paint_layer(None, |scene| {
|
||||
child.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -64,12 +73,13 @@ impl Element for Stack {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.children
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx))
|
||||
.find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
|
||||
}
|
||||
|
||||
fn debug(
|
||||
@@ -77,18 +87,19 @@ impl Element for Stack {
|
||||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> json::Value {
|
||||
json!({
|
||||
"type": "Stack",
|
||||
"bounds": bounds.to_json(),
|
||||
"children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
|
||||
"children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<ElementBox> for Stack {
|
||||
fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
|
||||
impl<V: View> Extend<Element<V>> for Stack<V> {
|
||||
fn extend<T: IntoIterator<Item = Element<V>>>(&mut self, children: T) {
|
||||
self.children.extend(children)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@ use crate::{
|
||||
rect::RectF,
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
presenter::MeasurementContext,
|
||||
scene, DebugContext, Element, LayoutContext, PaintContext, SizeConstraint,
|
||||
scene, Drawable, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
|
||||
pub struct Svg {
|
||||
@@ -31,14 +30,15 @@ impl Svg {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Svg {
|
||||
impl<V: View> Drawable<V> for Svg {
|
||||
type LayoutState = Option<usvg::Tree>;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
_: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
match cx.asset_cache.svg(&self.path) {
|
||||
Ok(tree) => {
|
||||
@@ -58,13 +58,15 @@ impl Element for Svg {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
_visible_bounds: RectF,
|
||||
svg: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
_: &mut V,
|
||||
_: &mut ViewContext<V>,
|
||||
) {
|
||||
if let Some(svg) = svg.clone() {
|
||||
cx.scene.push_icon(scene::Icon {
|
||||
scene.push_icon(scene::Icon {
|
||||
bounds,
|
||||
svg,
|
||||
path: self.path.clone(),
|
||||
@@ -80,7 +82,8 @@ impl Element for Svg {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &MeasurementContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
None
|
||||
}
|
||||
@@ -90,7 +93,8 @@ impl Element for Svg {
|
||||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &DebugContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "Svg",
|
||||
|
||||
@@ -6,9 +6,8 @@ use crate::{
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json::{ToJson, Value},
|
||||
presenter::MeasurementContext,
|
||||
text_layout::{Line, RunStyle, ShapedBoundary},
|
||||
DebugContext, Element, FontCache, LayoutContext, PaintContext, SizeConstraint, TextLayoutCache,
|
||||
Drawable, FontCache, SceneBuilder, SizeConstraint, TextLayoutCache, View, ViewContext,
|
||||
};
|
||||
use log::warn;
|
||||
use serde_json::json;
|
||||
@@ -53,14 +52,15 @@ impl Text {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Text {
|
||||
impl<V: View> Drawable<V> for Text {
|
||||
type LayoutState = LayoutState;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
_: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
// Convert the string and highlight ranges into an iterator of highlighted chunks.
|
||||
|
||||
@@ -98,8 +98,8 @@ impl Element for Text {
|
||||
let shaped_lines = layout_highlighted_chunks(
|
||||
chunks,
|
||||
&self.style,
|
||||
cx.text_layout_cache,
|
||||
cx.font_cache,
|
||||
cx.text_layout_cache(),
|
||||
&cx.font_cache,
|
||||
usize::MAX,
|
||||
self.text.matches('\n').count() + 1,
|
||||
);
|
||||
@@ -143,10 +143,12 @@ impl Element for Text {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
_: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let mut origin = bounds.origin();
|
||||
let empty = Vec::new();
|
||||
@@ -163,6 +165,7 @@ impl Element for Text {
|
||||
if boundaries.intersects(visible_bounds) {
|
||||
if self.soft_wrap {
|
||||
line.paint_wrapped(
|
||||
scene,
|
||||
origin,
|
||||
visible_bounds,
|
||||
layout.line_height,
|
||||
@@ -170,7 +173,7 @@ impl Element for Text {
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
line.paint(origin, visible_bounds, layout.line_height, cx);
|
||||
line.paint(scene, origin, visible_bounds, layout.line_height, cx);
|
||||
}
|
||||
}
|
||||
origin.set_y(boundaries.max_y());
|
||||
@@ -184,7 +187,8 @@ impl Element for Text {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &MeasurementContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
None
|
||||
}
|
||||
@@ -194,7 +198,8 @@ impl Element for Text {
|
||||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &DebugContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Value {
|
||||
json!({
|
||||
"type": "Text",
|
||||
@@ -208,9 +213,9 @@ impl Element for Text {
|
||||
/// Perform text layout on a series of highlighted chunks of text.
|
||||
pub fn layout_highlighted_chunks<'a>(
|
||||
chunks: impl Iterator<Item = (&'a str, Option<HighlightStyle>)>,
|
||||
text_style: &'a TextStyle,
|
||||
text_layout_cache: &'a TextLayoutCache,
|
||||
font_cache: &'a Arc<FontCache>,
|
||||
text_style: &TextStyle,
|
||||
text_layout_cache: &TextLayoutCache,
|
||||
font_cache: &Arc<FontCache>,
|
||||
max_line_len: usize,
|
||||
max_line_count: usize,
|
||||
) -> Vec<Line> {
|
||||
@@ -271,20 +276,23 @@ pub fn layout_highlighted_chunks<'a>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{elements::Empty, fonts, AppContext, ElementBox, Entity, RenderContext, View};
|
||||
use crate::{elements::Empty, fonts, AppContext, Element, Entity, View, ViewContext};
|
||||
|
||||
#[crate::test(self)]
|
||||
fn test_soft_wrapping_with_carriage_returns(cx: &mut AppContext) {
|
||||
let (window_id, _) = cx.add_window(Default::default(), |_| TestView);
|
||||
let mut presenter = cx.build_presenter(window_id, Default::default(), Default::default());
|
||||
fonts::with_font_cache(cx.font_cache().clone(), || {
|
||||
let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
|
||||
let (_, state) = text.layout(
|
||||
SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
|
||||
&mut presenter.build_layout_context(Default::default(), false, cx),
|
||||
);
|
||||
assert_eq!(state.shaped_lines.len(), 2);
|
||||
assert_eq!(state.wrap_boundaries.len(), 2);
|
||||
cx.add_window(Default::default(), |cx| {
|
||||
let mut view = TestView;
|
||||
fonts::with_font_cache(cx.font_cache().clone(), || {
|
||||
let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
|
||||
let (_, state) = text.layout(
|
||||
SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
|
||||
&mut view,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(state.shaped_lines.len(), 2);
|
||||
assert_eq!(state.wrap_boundaries.len(), 2);
|
||||
});
|
||||
view
|
||||
});
|
||||
}
|
||||
|
||||
@@ -299,7 +307,7 @@ mod tests {
|
||||
"TestView"
|
||||
}
|
||||
|
||||
fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
|
||||
Empty::new().boxed()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use super::{
|
||||
ContainerStyle, Element, ElementBox, Flex, KeystrokeLabel, MouseEventHandler, Overlay,
|
||||
ContainerStyle, Drawable, Element, Flex, KeystrokeLabel, MouseEventHandler, Overlay,
|
||||
OverlayFitMode, ParentElement, Text,
|
||||
};
|
||||
use crate::{
|
||||
fonts::TextStyle,
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json::json,
|
||||
presenter::MeasurementContext,
|
||||
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint,
|
||||
Task, View,
|
||||
Action, Axis, ElementStateHandle, SceneBuilder, SizeConstraint, Task, View, ViewContext,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
@@ -17,12 +15,13 @@ use std::{
|
||||
rc::Rc,
|
||||
time::Duration,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(500);
|
||||
|
||||
pub struct Tooltip {
|
||||
child: ElementBox,
|
||||
tooltip: Option<ElementBox>,
|
||||
pub struct Tooltip<V: View> {
|
||||
child: Element<V>,
|
||||
tooltip: Option<Element<V>>,
|
||||
_state: ElementStateHandle<Rc<TooltipState>>,
|
||||
}
|
||||
|
||||
@@ -50,24 +49,23 @@ pub struct KeystrokeStyle {
|
||||
text: TextStyle,
|
||||
}
|
||||
|
||||
impl Tooltip {
|
||||
impl<V: View> Tooltip<V> {
|
||||
pub fn new<Tag: 'static, T: View>(
|
||||
id: usize,
|
||||
text: String,
|
||||
action: Option<Box<dyn Action>>,
|
||||
style: TooltipStyle,
|
||||
child: ElementBox,
|
||||
cx: &mut RenderContext<T>,
|
||||
child: Element<V>,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self {
|
||||
struct ElementState<Tag>(Tag);
|
||||
struct MouseEventHandlerState<Tag>(Tag);
|
||||
let focused_view_id = cx.focused_view_id(cx.window_id);
|
||||
let focused_view_id = cx.focused_view_id();
|
||||
|
||||
let state_handle = cx.default_element_state::<ElementState<Tag>, Rc<TooltipState>>(id);
|
||||
let state = state_handle.read(cx).clone();
|
||||
let tooltip = if state.visible.get() {
|
||||
let mut collapsed_tooltip = Self::render_tooltip(
|
||||
cx.window_id,
|
||||
focused_view_id,
|
||||
text.clone(),
|
||||
style.clone(),
|
||||
@@ -77,12 +75,12 @@ impl Tooltip {
|
||||
.boxed();
|
||||
Some(
|
||||
Overlay::new(
|
||||
Self::render_tooltip(cx.window_id, focused_view_id, text, style, action, false)
|
||||
Self::render_tooltip(focused_view_id, text, style, action, false)
|
||||
.constrained()
|
||||
.dynamically(move |constraint, cx| {
|
||||
.dynamically(move |constraint, view, cx| {
|
||||
SizeConstraint::strict_along(
|
||||
Axis::Vertical,
|
||||
collapsed_tooltip.layout(constraint, cx).y(),
|
||||
collapsed_tooltip.layout(constraint, view, cx).y(),
|
||||
)
|
||||
})
|
||||
.boxed(),
|
||||
@@ -94,32 +92,31 @@ impl Tooltip {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let child = MouseEventHandler::<MouseEventHandlerState<Tag>>::new(id, cx, |_, _| child)
|
||||
.on_hover(move |e, cx| {
|
||||
let child = MouseEventHandler::<MouseEventHandlerState<Tag>, _>::new(id, cx, |_, _| child)
|
||||
.on_hover(move |e, _, cx| {
|
||||
let position = e.position;
|
||||
let window_id = cx.window_id();
|
||||
if let Some(view_id) = cx.view_id() {
|
||||
if e.started {
|
||||
if !state.visible.get() {
|
||||
state.position.set(position);
|
||||
if e.started {
|
||||
if !state.visible.get() {
|
||||
state.position.set(position);
|
||||
|
||||
let mut debounce = state.debounce.borrow_mut();
|
||||
if debounce.is_none() {
|
||||
*debounce = Some(cx.spawn({
|
||||
let state = state.clone();
|
||||
|mut cx| async move {
|
||||
cx.background().timer(DEBOUNCE_TIMEOUT).await;
|
||||
state.visible.set(true);
|
||||
cx.update(|cx| cx.notify_view(window_id, view_id));
|
||||
let mut debounce = state.debounce.borrow_mut();
|
||||
if debounce.is_none() {
|
||||
*debounce = Some(cx.spawn_weak({
|
||||
let state = state.clone();
|
||||
|view, mut cx| async move {
|
||||
cx.background().timer(DEBOUNCE_TIMEOUT).await;
|
||||
state.visible.set(true);
|
||||
if let Some(view) = view.upgrade(&cx) {
|
||||
view.update(&mut cx, |_, cx| cx.notify()).log_err();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
state.visible.set(false);
|
||||
state.debounce.take();
|
||||
cx.notify();
|
||||
}
|
||||
} else {
|
||||
state.visible.set(false);
|
||||
state.debounce.take();
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.boxed();
|
||||
@@ -131,13 +128,12 @@ impl Tooltip {
|
||||
}
|
||||
|
||||
pub fn render_tooltip(
|
||||
window_id: usize,
|
||||
focused_view_id: Option<usize>,
|
||||
text: String,
|
||||
style: TooltipStyle,
|
||||
action: Option<Box<dyn Action>>,
|
||||
measure: bool,
|
||||
) -> impl Element {
|
||||
) -> impl Drawable<V> {
|
||||
Flex::row()
|
||||
.with_child({
|
||||
let text = if let Some(max_text_width) = style.max_text_width {
|
||||
@@ -156,7 +152,6 @@ impl Tooltip {
|
||||
})
|
||||
.with_children(action.and_then(|action| {
|
||||
let keystroke_label = KeystrokeLabel::new(
|
||||
window_id,
|
||||
focused_view_id?,
|
||||
action,
|
||||
style.keystroke.container,
|
||||
@@ -173,32 +168,40 @@ impl Tooltip {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Tooltip {
|
||||
impl<V: View> Drawable<V> for Tooltip<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let size = self.child.layout(constraint, cx);
|
||||
let size = self.child.layout(constraint, view, cx);
|
||||
if let Some(tooltip) = self.tooltip.as_mut() {
|
||||
tooltip.layout(SizeConstraint::new(Vector2F::zero(), cx.window_size), cx);
|
||||
tooltip.layout(
|
||||
SizeConstraint::new(Vector2F::zero(), cx.window_size()),
|
||||
view,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
(size, ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
if let Some(tooltip) = self.tooltip.as_mut() {
|
||||
tooltip.paint(bounds.origin(), visible_bounds, cx);
|
||||
tooltip.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,9 +212,10 @@ impl Element for Tooltip {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.child.rect_for_text_range(range, cx)
|
||||
self.child.rect_for_text_range(range, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
@@ -219,11 +223,12 @@ impl Element for Tooltip {
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &crate::DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"child": self.child.debug(cx),
|
||||
"tooltip": self.tooltip.as_ref().map(|t| t.debug(cx)),
|
||||
"child": self.child.debug(view, cx),
|
||||
"tooltip": self.tooltip.as_ref().map(|t| t.debug(view, cx)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{Element, EventContext, LayoutContext, PaintContext, SizeConstraint};
|
||||
use super::{Drawable, SizeConstraint};
|
||||
use crate::{
|
||||
geometry::{
|
||||
rect::RectF,
|
||||
@@ -6,9 +6,7 @@ use crate::{
|
||||
},
|
||||
json::{self, json},
|
||||
platform::ScrollWheelEvent,
|
||||
presenter::MeasurementContext,
|
||||
scene::MouseScrollWheel,
|
||||
ElementBox, MouseRegion, RenderContext, View,
|
||||
Element, MouseRegion, SceneBuilder, View, ViewContext,
|
||||
};
|
||||
use json::ToJson;
|
||||
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
|
||||
@@ -38,45 +36,38 @@ struct StateInner {
|
||||
scroll_to: Option<ScrollTarget>,
|
||||
}
|
||||
|
||||
pub struct LayoutState {
|
||||
pub struct LayoutState<V: View> {
|
||||
scroll_max: f32,
|
||||
item_height: f32,
|
||||
items: Vec<ElementBox>,
|
||||
items: Vec<Element<V>>,
|
||||
}
|
||||
|
||||
pub struct UniformList {
|
||||
pub struct UniformList<V: View> {
|
||||
state: UniformListState,
|
||||
item_count: usize,
|
||||
#[allow(clippy::type_complexity)]
|
||||
append_items: Box<dyn Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext)>,
|
||||
append_items: Box<dyn Fn(&mut V, Range<usize>, &mut Vec<Element<V>>, &mut ViewContext<V>)>,
|
||||
padding_top: f32,
|
||||
padding_bottom: f32,
|
||||
get_width_from_item: Option<usize>,
|
||||
view_id: usize,
|
||||
}
|
||||
|
||||
impl UniformList {
|
||||
pub fn new<F, V>(
|
||||
impl<V: View> UniformList<V> {
|
||||
pub fn new<F>(
|
||||
state: UniformListState,
|
||||
item_count: usize,
|
||||
cx: &mut RenderContext<V>,
|
||||
cx: &mut ViewContext<V>,
|
||||
append_items: F,
|
||||
) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: 'static + Fn(&mut V, Range<usize>, &mut Vec<ElementBox>, &mut RenderContext<V>),
|
||||
F: 'static + Fn(&mut V, Range<usize>, &mut Vec<Element<V>>, &mut ViewContext<V>),
|
||||
{
|
||||
let handle = cx.handle();
|
||||
Self {
|
||||
state,
|
||||
item_count,
|
||||
append_items: Box::new(move |range, items, cx| {
|
||||
if let Some(handle) = handle.upgrade(cx) {
|
||||
cx.render(&handle, |view, cx| {
|
||||
append_items(view, range, items, cx);
|
||||
});
|
||||
}
|
||||
}),
|
||||
append_items: Box::new(append_items),
|
||||
padding_top: 0.,
|
||||
padding_bottom: 0.,
|
||||
get_width_from_item: None,
|
||||
@@ -105,7 +96,7 @@ impl UniformList {
|
||||
mut delta: Vector2F,
|
||||
precise: bool,
|
||||
scroll_max: f32,
|
||||
cx: &mut EventContext,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> bool {
|
||||
if !precise {
|
||||
delta *= 20.;
|
||||
@@ -160,14 +151,15 @@ impl UniformList {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for UniformList {
|
||||
type LayoutState = LayoutState;
|
||||
impl<V: View> Drawable<V> for UniformList<V> {
|
||||
type LayoutState = LayoutState<V>;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
if constraint.max.y().is_infinite() {
|
||||
unimplemented!(
|
||||
@@ -194,18 +186,18 @@ impl Element for UniformList {
|
||||
let sample_item_ix;
|
||||
let sample_item;
|
||||
if let Some(sample_ix) = self.get_width_from_item {
|
||||
(self.append_items)(sample_ix..sample_ix + 1, &mut items, cx);
|
||||
(self.append_items)(view, sample_ix..sample_ix + 1, &mut items, cx);
|
||||
sample_item_ix = sample_ix;
|
||||
|
||||
if let Some(mut item) = items.pop() {
|
||||
item_size = item.layout(constraint, cx);
|
||||
item_size = item.layout(constraint, view, cx);
|
||||
size.set_x(item_size.x());
|
||||
sample_item = item;
|
||||
} else {
|
||||
return no_items;
|
||||
}
|
||||
} else {
|
||||
(self.append_items)(0..1, &mut items, cx);
|
||||
(self.append_items)(view, 0..1, &mut items, cx);
|
||||
sample_item_ix = 0;
|
||||
if let Some(mut item) = items.pop() {
|
||||
item_size = item.layout(
|
||||
@@ -213,6 +205,7 @@ impl Element for UniformList {
|
||||
vec2f(constraint.max.x(), 0.0),
|
||||
vec2f(constraint.max.x(), f32::INFINITY),
|
||||
),
|
||||
view,
|
||||
cx,
|
||||
);
|
||||
item_size.set_x(size.x());
|
||||
@@ -249,20 +242,20 @@ impl Element for UniformList {
|
||||
|
||||
if (start..end).contains(&sample_item_ix) {
|
||||
if sample_item_ix > start {
|
||||
(self.append_items)(start..sample_item_ix, &mut items, cx);
|
||||
(self.append_items)(view, start..sample_item_ix, &mut items, cx);
|
||||
}
|
||||
|
||||
items.push(sample_item);
|
||||
|
||||
if sample_item_ix < end {
|
||||
(self.append_items)(sample_item_ix + 1..end, &mut items, cx);
|
||||
(self.append_items)(view, sample_item_ix + 1..end, &mut items, cx);
|
||||
}
|
||||
} else {
|
||||
(self.append_items)(start..end, &mut items, cx);
|
||||
(self.append_items)(view, start..end, &mut items, cx);
|
||||
}
|
||||
|
||||
for item in &mut items {
|
||||
let item_size = item.layout(item_constraint, cx);
|
||||
let item_size = item.layout(item_constraint, view, cx);
|
||||
if item_size.x() > size.x() {
|
||||
size.set_x(item_size.x());
|
||||
}
|
||||
@@ -280,27 +273,25 @@ impl Element for UniformList {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
|
||||
|
||||
cx.scene.push_layer(Some(visible_bounds));
|
||||
scene.push_layer(Some(visible_bounds));
|
||||
|
||||
cx.scene.push_mouse_region(
|
||||
scene.push_mouse_region(
|
||||
MouseRegion::new::<Self>(self.view_id, 0, visible_bounds).on_scroll({
|
||||
let scroll_max = layout.scroll_max;
|
||||
let state = self.state.clone();
|
||||
move |MouseScrollWheel {
|
||||
platform_event:
|
||||
ScrollWheelEvent {
|
||||
position, delta, ..
|
||||
},
|
||||
..
|
||||
},
|
||||
cx| {
|
||||
move |event, _, cx| {
|
||||
let ScrollWheelEvent {
|
||||
position, delta, ..
|
||||
} = event.platform_event;
|
||||
if !Self::scroll(
|
||||
state.clone(),
|
||||
position,
|
||||
@@ -322,11 +313,11 @@ impl Element for UniformList {
|
||||
);
|
||||
|
||||
for item in &mut layout.items {
|
||||
item.paint(item_origin, visible_bounds, cx);
|
||||
item.paint(scene, item_origin, visible_bounds, view, cx);
|
||||
item_origin += vec2f(0.0, layout.item_height);
|
||||
}
|
||||
|
||||
cx.scene.pop_layer();
|
||||
scene.pop_layer();
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
@@ -336,12 +327,13 @@ impl Element for UniformList {
|
||||
_: RectF,
|
||||
layout: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
layout
|
||||
.items
|
||||
.iter()
|
||||
.find_map(|child| child.rect_for_text_range(range.clone(), cx))
|
||||
.find_map(|child| child.rect_for_text_range(range.clone(), view, cx))
|
||||
}
|
||||
|
||||
fn debug(
|
||||
@@ -349,14 +341,15 @@ impl Element for UniformList {
|
||||
bounds: RectF,
|
||||
layout: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &crate::DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> json::Value {
|
||||
json!({
|
||||
"type": "UniformList",
|
||||
"bounds": bounds.to_json(),
|
||||
"scroll_max": layout.scroll_max,
|
||||
"item_height": layout.item_height,
|
||||
"items": layout.items.iter().map(|item| item.debug(cx)).collect::<Vec<json::Value>>()
|
||||
"items": layout.items.iter().map(|item| item.debug(view, cx)).collect::<Vec<json::Value>>()
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,13 +14,12 @@ mod clipboard;
|
||||
pub use clipboard::ClipboardItem;
|
||||
pub mod fonts;
|
||||
pub mod geometry;
|
||||
mod presenter;
|
||||
pub mod scene;
|
||||
pub use scene::{Border, CursorRegion, MouseRegion, MouseRegionId, Quad, Scene, SceneBuilder};
|
||||
pub mod text_layout;
|
||||
pub use text_layout::TextLayoutCache;
|
||||
mod util;
|
||||
pub use elements::{Element, ElementBox, ElementRc};
|
||||
pub use elements::{Drawable, Element};
|
||||
pub mod executor;
|
||||
pub use executor::Task;
|
||||
pub mod color;
|
||||
@@ -28,10 +27,7 @@ pub mod json;
|
||||
pub mod keymap_matcher;
|
||||
pub mod platform;
|
||||
pub use gpui_macros::test;
|
||||
pub use presenter::{
|
||||
Axis, DebugContext, EventContext, LayoutContext, MeasurementContext, PaintContext,
|
||||
SizeConstraint, Vector2FExt,
|
||||
};
|
||||
pub use window::{Axis, SizeConstraint, Vector2FExt, WindowContext};
|
||||
|
||||
pub use anyhow;
|
||||
pub use serde_json;
|
||||
|
||||
@@ -63,7 +63,7 @@ pub trait Platform: Send + Sync {
|
||||
) -> Box<dyn Window>;
|
||||
fn main_window_id(&self) -> Option<usize>;
|
||||
|
||||
fn add_status_item(&self) -> Box<dyn Window>;
|
||||
fn add_status_item(&self, id: usize) -> Box<dyn Window>;
|
||||
|
||||
fn write_to_clipboard(&self, item: ClipboardItem);
|
||||
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
|
||||
|
||||
@@ -601,7 +601,7 @@ impl platform::Platform for MacPlatform {
|
||||
Window::main_window_id()
|
||||
}
|
||||
|
||||
fn add_status_item(&self) -> Box<dyn platform::Window> {
|
||||
fn add_status_item(&self, _id: usize) -> Box<dyn platform::Window> {
|
||||
Box::new(StatusItem::add(self.fonts()))
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ pub struct Platform {
|
||||
fonts: Arc<dyn super::FontSystem>,
|
||||
current_clipboard_item: Mutex<Option<ClipboardItem>>,
|
||||
cursor: Mutex<CursorStyle>,
|
||||
active_window_id: Arc<Mutex<Option<usize>>>,
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
@@ -111,6 +112,7 @@ impl Platform {
|
||||
fonts: Arc::new(super::current::FontSystem::new()),
|
||||
current_clipboard_item: Default::default(),
|
||||
cursor: Mutex::new(CursorStyle::Arrow),
|
||||
active_window_id: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,22 +146,31 @@ impl super::Platform for Platform {
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
_: usize,
|
||||
id: usize,
|
||||
options: super::WindowOptions,
|
||||
_executor: Rc<super::executor::Foreground>,
|
||||
) -> Box<dyn super::Window> {
|
||||
Box::new(Window::new(match options.bounds {
|
||||
WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.),
|
||||
WindowBounds::Fixed(rect) => rect.size(),
|
||||
}))
|
||||
*self.active_window_id.lock() = Some(id);
|
||||
Box::new(Window::new(
|
||||
id,
|
||||
match options.bounds {
|
||||
WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.),
|
||||
WindowBounds::Fixed(rect) => rect.size(),
|
||||
},
|
||||
self.active_window_id.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
fn main_window_id(&self) -> Option<usize> {
|
||||
None
|
||||
self.active_window_id.lock().clone()
|
||||
}
|
||||
|
||||
fn add_status_item(&self) -> Box<dyn crate::platform::Window> {
|
||||
Box::new(Window::new(vec2f(24., 24.)))
|
||||
fn add_status_item(&self, id: usize) -> Box<dyn crate::platform::Window> {
|
||||
Box::new(Window::new(
|
||||
id,
|
||||
vec2f(24., 24.),
|
||||
self.active_window_id.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||
@@ -245,6 +256,7 @@ impl super::Screen for Screen {
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
id: usize,
|
||||
pub(crate) size: Vector2F,
|
||||
scale_factor: f32,
|
||||
current_scene: Option<crate::Scene>,
|
||||
@@ -258,11 +270,13 @@ pub struct Window {
|
||||
pub(crate) title: Option<String>,
|
||||
pub(crate) edited: bool,
|
||||
pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
|
||||
active_window_id: Arc<Mutex<Option<usize>>>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
fn new(size: Vector2F) -> Self {
|
||||
pub fn new(id: usize, size: Vector2F, active_window_id: Arc<Mutex<Option<usize>>>) -> Self {
|
||||
Self {
|
||||
id,
|
||||
size,
|
||||
event_handlers: Default::default(),
|
||||
resize_handlers: Default::default(),
|
||||
@@ -276,6 +290,7 @@ impl Window {
|
||||
title: None,
|
||||
edited: false,
|
||||
pending_prompts: Default::default(),
|
||||
active_window_id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +341,9 @@ impl super::Window for Window {
|
||||
done_rx
|
||||
}
|
||||
|
||||
fn activate(&self) {}
|
||||
fn activate(&self) {
|
||||
*self.active_window_id.lock() = Some(self.id);
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.title = Some(title.to_string())
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -236,6 +236,19 @@ impl SceneBuilder {
|
||||
self.scale_factor
|
||||
}
|
||||
|
||||
pub fn paint_stacking_context<F>(
|
||||
&mut self,
|
||||
clip_bounds: Option<RectF>,
|
||||
z_index: Option<usize>,
|
||||
f: F,
|
||||
) where
|
||||
F: FnOnce(&mut Self),
|
||||
{
|
||||
self.push_stacking_context(clip_bounds, z_index);
|
||||
f(self);
|
||||
self.pop_stacking_context();
|
||||
}
|
||||
|
||||
pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>, z_index: Option<usize>) {
|
||||
let z_index = z_index.unwrap_or_else(|| self.active_stacking_context().z_index + 1);
|
||||
self.active_stacking_context_stack
|
||||
@@ -249,6 +262,15 @@ impl SceneBuilder {
|
||||
assert!(!self.active_stacking_context_stack.is_empty());
|
||||
}
|
||||
|
||||
pub fn paint_layer<F>(&mut self, clip_bounds: Option<RectF>, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Self),
|
||||
{
|
||||
self.push_layer(clip_bounds);
|
||||
f(self);
|
||||
self.pop_layer();
|
||||
}
|
||||
|
||||
pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {
|
||||
self.active_stacking_context().push_layer(clip_bounds);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::{any::TypeId, fmt::Debug, mem::Discriminant, rc::Rc};
|
||||
|
||||
use crate::{platform::MouseButton, window::WindowContext, EventContext, View, ViewContext};
|
||||
use collections::HashMap;
|
||||
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{platform::MouseButton, EventContext};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
fmt::Debug,
|
||||
mem::Discriminant,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use super::{
|
||||
mouse_event::{
|
||||
@@ -60,82 +62,92 @@ impl MouseRegion {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseDown, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.handlers = self.handlers.on_down(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseUp, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.handlers = self.handlers.on_up(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseClick, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.handlers = self.handlers.on_click(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseDownOut, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.handlers = self.handlers.on_down_out(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseUpOut, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.handlers = self.handlers.on_up_out(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_drag(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseDrag, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.handlers = self.handlers.on_drag(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self {
|
||||
pub fn on_hover<V, F>(mut self, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.handlers = self.handlers.on_hover(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self {
|
||||
pub fn on_move<V, F>(mut self, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.handlers = self.handlers.on_move(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_move_out(
|
||||
mut self,
|
||||
handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_move_out<V, F>(mut self, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.handlers = self.handlers.on_move_out(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_scroll(
|
||||
mut self,
|
||||
handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_scroll<V, F>(mut self, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.handlers = self.handlers.on_scroll(handler);
|
||||
self
|
||||
}
|
||||
@@ -186,7 +198,7 @@ impl MouseRegionId {
|
||||
}
|
||||
}
|
||||
|
||||
pub type HandlerCallback = Rc<dyn Fn(MouseEvent, &mut EventContext)>;
|
||||
pub type HandlerCallback = Rc<dyn Fn(MouseEvent, &mut dyn Any, &mut WindowContext, usize) -> bool>;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct HandlerKey {
|
||||
@@ -211,41 +223,41 @@ impl HandlerSet {
|
||||
|
||||
set.insert(
|
||||
HandlerKey::new(MouseEvent::move_disc(), None),
|
||||
SmallVec::from_buf([Rc::new(|_, _| {})]),
|
||||
SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
|
||||
);
|
||||
set.insert(
|
||||
HandlerKey::new(MouseEvent::hover_disc(), None),
|
||||
SmallVec::from_buf([Rc::new(|_, _| {})]),
|
||||
SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
|
||||
);
|
||||
for button in MouseButton::all() {
|
||||
set.insert(
|
||||
HandlerKey::new(MouseEvent::drag_disc(), Some(button)),
|
||||
SmallVec::from_buf([Rc::new(|_, _| {})]),
|
||||
SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
|
||||
);
|
||||
set.insert(
|
||||
HandlerKey::new(MouseEvent::down_disc(), Some(button)),
|
||||
SmallVec::from_buf([Rc::new(|_, _| {})]),
|
||||
SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
|
||||
);
|
||||
set.insert(
|
||||
HandlerKey::new(MouseEvent::up_disc(), Some(button)),
|
||||
SmallVec::from_buf([Rc::new(|_, _| {})]),
|
||||
SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
|
||||
);
|
||||
set.insert(
|
||||
HandlerKey::new(MouseEvent::click_disc(), Some(button)),
|
||||
SmallVec::from_buf([Rc::new(|_, _| {})]),
|
||||
SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
|
||||
);
|
||||
set.insert(
|
||||
HandlerKey::new(MouseEvent::down_out_disc(), Some(button)),
|
||||
SmallVec::from_buf([Rc::new(|_, _| {})]),
|
||||
SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
|
||||
);
|
||||
set.insert(
|
||||
HandlerKey::new(MouseEvent::up_out_disc(), Some(button)),
|
||||
SmallVec::from_buf([Rc::new(|_, _| {})]),
|
||||
SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
|
||||
);
|
||||
}
|
||||
set.insert(
|
||||
HandlerKey::new(MouseEvent::scroll_wheel_disc(), None),
|
||||
SmallVec::from_buf([Rc::new(|_, _| {})]),
|
||||
SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
|
||||
);
|
||||
|
||||
HandlerSet { set }
|
||||
@@ -283,11 +295,19 @@ impl HandlerSet {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self {
|
||||
pub fn on_move<V, F>(mut self, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.insert(MouseEvent::move_disc(), None,
|
||||
Rc::new(move |region_event, cx| {
|
||||
Rc::new(move |region_event, view, cx, view_id| {
|
||||
if let MouseEvent::Move(e) = region_event {
|
||||
handler(e, cx);
|
||||
let view = view.downcast_mut().unwrap();
|
||||
let mut cx = ViewContext::mutable(cx, view_id);
|
||||
let mut cx = EventContext::new(&mut cx);
|
||||
handler(e, view, &mut cx);
|
||||
cx.handled
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}",
|
||||
@@ -297,14 +317,19 @@ impl HandlerSet {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_move_out(
|
||||
mut self,
|
||||
handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_move_out<V, F>(mut self, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.insert(MouseEvent::move_out_disc(), None,
|
||||
Rc::new(move |region_event, cx| {
|
||||
Rc::new(move |region_event, view, cx, view_id| {
|
||||
if let MouseEvent::MoveOut(e) = region_event {
|
||||
handler(e, cx);
|
||||
let view = view.downcast_mut().unwrap();
|
||||
let mut cx = ViewContext::<V>::mutable(cx, view_id);
|
||||
let mut cx = EventContext::new(&mut cx);
|
||||
handler(e, view, &mut cx);
|
||||
cx.handled
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::MoveOut, found {:?}",
|
||||
@@ -314,15 +339,19 @@ impl HandlerSet {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseDown, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.insert(MouseEvent::down_disc(), Some(button),
|
||||
Rc::new(move |region_event, cx| {
|
||||
Rc::new(move |region_event, view, cx, view_id| {
|
||||
if let MouseEvent::Down(e) = region_event {
|
||||
handler(e, cx);
|
||||
let view = view.downcast_mut().unwrap();
|
||||
let mut cx = ViewContext::mutable(cx, view_id);
|
||||
let mut cx = EventContext::new(&mut cx);
|
||||
handler(e, view, &mut cx);
|
||||
cx.handled
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}",
|
||||
@@ -332,15 +361,19 @@ impl HandlerSet {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseUp, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.insert(MouseEvent::up_disc(), Some(button),
|
||||
Rc::new(move |region_event, cx| {
|
||||
Rc::new(move |region_event, view, cx, view_id| {
|
||||
if let MouseEvent::Up(e) = region_event {
|
||||
handler(e, cx);
|
||||
let view = view.downcast_mut().unwrap();
|
||||
let mut cx = ViewContext::mutable(cx, view_id);
|
||||
let mut cx = EventContext::new(&mut cx);
|
||||
handler(e, view, &mut cx);
|
||||
cx.handled
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}",
|
||||
@@ -350,15 +383,19 @@ impl HandlerSet {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseClick, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.insert(MouseEvent::click_disc(), Some(button),
|
||||
Rc::new(move |region_event, cx| {
|
||||
Rc::new(move |region_event, view, cx, view_id| {
|
||||
if let MouseEvent::Click(e) = region_event {
|
||||
handler(e, cx);
|
||||
let view = view.downcast_mut().unwrap();
|
||||
let mut cx = ViewContext::mutable(cx, view_id);
|
||||
let mut cx = EventContext::new(&mut cx);
|
||||
handler(e, view, &mut cx);
|
||||
cx.handled
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}",
|
||||
@@ -368,15 +405,19 @@ impl HandlerSet {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseDownOut, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.insert(MouseEvent::down_out_disc(), Some(button),
|
||||
Rc::new(move |region_event, cx| {
|
||||
Rc::new(move |region_event, view, cx, view_id| {
|
||||
if let MouseEvent::DownOut(e) = region_event {
|
||||
handler(e, cx);
|
||||
let view = view.downcast_mut().unwrap();
|
||||
let mut cx = ViewContext::mutable(cx, view_id);
|
||||
let mut cx = EventContext::new(&mut cx);
|
||||
handler(e, view, &mut cx);
|
||||
cx.handled
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}",
|
||||
@@ -386,15 +427,19 @@ impl HandlerSet {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseUpOut, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.insert(MouseEvent::up_out_disc(), Some(button),
|
||||
Rc::new(move |region_event, cx| {
|
||||
Rc::new(move |region_event, view, cx, view_id| {
|
||||
if let MouseEvent::UpOut(e) = region_event {
|
||||
handler(e, cx);
|
||||
let view = view.downcast_mut().unwrap();
|
||||
let mut cx = ViewContext::mutable(cx, view_id);
|
||||
let mut cx = EventContext::new(&mut cx);
|
||||
handler(e, view, &mut cx);
|
||||
cx.handled
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}",
|
||||
@@ -404,15 +449,19 @@ impl HandlerSet {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_drag(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseDrag, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.insert(MouseEvent::drag_disc(), Some(button),
|
||||
Rc::new(move |region_event, cx| {
|
||||
Rc::new(move |region_event, view, cx, view_id| {
|
||||
if let MouseEvent::Drag(e) = region_event {
|
||||
handler(e, cx);
|
||||
let view = view.downcast_mut().unwrap();
|
||||
let mut cx = ViewContext::mutable(cx, view_id);
|
||||
let mut cx = EventContext::new(&mut cx);
|
||||
handler(e, view, &mut cx);
|
||||
cx.handled
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}",
|
||||
@@ -422,11 +471,19 @@ impl HandlerSet {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self {
|
||||
pub fn on_hover<V, F>(mut self, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.insert(MouseEvent::hover_disc(), None,
|
||||
Rc::new(move |region_event, cx| {
|
||||
Rc::new(move |region_event, view, cx, view_id| {
|
||||
if let MouseEvent::Hover(e) = region_event {
|
||||
handler(e, cx);
|
||||
let view = view.downcast_mut().unwrap();
|
||||
let mut cx = ViewContext::mutable(cx, view_id);
|
||||
let mut cx = EventContext::new(&mut cx);
|
||||
handler(e, view, &mut cx);
|
||||
cx.handled
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}",
|
||||
@@ -436,14 +493,19 @@ impl HandlerSet {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_scroll(
|
||||
mut self,
|
||||
handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
pub fn on_scroll<V, F>(mut self, handler: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
self.insert(MouseEvent::scroll_wheel_disc(), None,
|
||||
Rc::new(move |region_event, cx| {
|
||||
Rc::new(move |region_event, view, cx, view_id| {
|
||||
if let MouseEvent::ScrollWheel(e) = region_event {
|
||||
handler(e, cx);
|
||||
let view = view.downcast_mut().unwrap();
|
||||
let mut cx = ViewContext::mutable(cx, view_id);
|
||||
let mut cx = EventContext::new(&mut cx);
|
||||
handler(e, view, &mut cx);
|
||||
cx.handled
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ScrollWheel, found {:?}",
|
||||
|
||||
@@ -19,8 +19,8 @@ use crate::{
|
||||
platform,
|
||||
platform::Platform,
|
||||
util::CwdBacktrace,
|
||||
AppContext, Element, ElementBox, Entity, FontCache, Handle, RenderContext, Subscription,
|
||||
TestAppContext, View,
|
||||
AppContext, Drawable, Element, Entity, FontCache, Handle, Subscription, TestAppContext, View,
|
||||
ViewContext,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -242,7 +242,7 @@ impl View for EmptyView {
|
||||
"empty view"
|
||||
}
|
||||
|
||||
fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
|
||||
Element::boxed(Empty::new())
|
||||
fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
|
||||
Drawable::boxed(Empty::new())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ use crate::{
|
||||
},
|
||||
platform,
|
||||
platform::FontSystem,
|
||||
scene, PaintContext,
|
||||
scene,
|
||||
window::WindowContext,
|
||||
SceneBuilder,
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||
@@ -271,10 +273,11 @@ impl Line {
|
||||
|
||||
pub fn paint(
|
||||
&self,
|
||||
scene: &mut SceneBuilder,
|
||||
origin: Vector2F,
|
||||
visible_bounds: RectF,
|
||||
line_height: f32,
|
||||
cx: &mut PaintContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
|
||||
let baseline_offset = vec2f(0., padding_top + self.layout.ascent);
|
||||
@@ -331,7 +334,7 @@ impl Line {
|
||||
}
|
||||
|
||||
if let Some((underline_origin, underline_style)) = finished_underline {
|
||||
cx.scene.push_underline(scene::Underline {
|
||||
scene.push_underline(scene::Underline {
|
||||
origin: underline_origin,
|
||||
width: glyph_origin.x() - underline_origin.x(),
|
||||
thickness: underline_style.thickness.into(),
|
||||
@@ -341,14 +344,14 @@ impl Line {
|
||||
}
|
||||
|
||||
if glyph.is_emoji {
|
||||
cx.scene.push_image_glyph(scene::ImageGlyph {
|
||||
scene.push_image_glyph(scene::ImageGlyph {
|
||||
font_id: run.font_id,
|
||||
font_size: self.layout.font_size,
|
||||
id: glyph.id,
|
||||
origin: glyph_origin,
|
||||
});
|
||||
} else {
|
||||
cx.scene.push_glyph(scene::Glyph {
|
||||
scene.push_glyph(scene::Glyph {
|
||||
font_id: run.font_id,
|
||||
font_size: self.layout.font_size,
|
||||
id: glyph.id,
|
||||
@@ -361,7 +364,7 @@ impl Line {
|
||||
|
||||
if let Some((underline_start, underline_style)) = underline.take() {
|
||||
let line_end_x = origin.x() + self.layout.width;
|
||||
cx.scene.push_underline(scene::Underline {
|
||||
scene.push_underline(scene::Underline {
|
||||
origin: underline_start,
|
||||
width: line_end_x - underline_start.x(),
|
||||
color: underline_style.color.unwrap(),
|
||||
@@ -373,11 +376,12 @@ impl Line {
|
||||
|
||||
pub fn paint_wrapped(
|
||||
&self,
|
||||
scene: &mut SceneBuilder,
|
||||
origin: Vector2F,
|
||||
visible_bounds: RectF,
|
||||
line_height: f32,
|
||||
boundaries: impl IntoIterator<Item = ShapedBoundary>,
|
||||
cx: &mut PaintContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
|
||||
let baseline_origin = vec2f(0., padding_top + self.layout.ascent);
|
||||
@@ -416,14 +420,14 @@ impl Line {
|
||||
);
|
||||
if glyph_bounds.intersects(visible_bounds) {
|
||||
if glyph.is_emoji {
|
||||
cx.scene.push_image_glyph(scene::ImageGlyph {
|
||||
scene.push_image_glyph(scene::ImageGlyph {
|
||||
font_id: run.font_id,
|
||||
font_size: self.layout.font_size,
|
||||
id: glyph.id,
|
||||
origin: glyph_bounds.origin() + baseline_origin,
|
||||
});
|
||||
} else {
|
||||
cx.scene.push_glyph(scene::Glyph {
|
||||
scene.push_glyph(scene::Glyph {
|
||||
font_id: run.font_id,
|
||||
font_size: self.layout.font_size,
|
||||
id: glyph.id,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, RenderContext,
|
||||
actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, EventContext,
|
||||
View, ViewContext, WeakViewHandle,
|
||||
};
|
||||
|
||||
pub struct Select {
|
||||
handle: WeakViewHandle<Self>,
|
||||
render_item: Box<dyn Fn(usize, ItemType, bool, &AppContext) -> ElementBox>,
|
||||
render_item: Box<dyn Fn(usize, ItemType, bool, &AppContext) -> Element<Self>>,
|
||||
selected_item_ix: usize,
|
||||
item_count: usize,
|
||||
is_open: bool,
|
||||
@@ -41,7 +41,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
}
|
||||
|
||||
impl Select {
|
||||
pub fn new<F: 'static + Fn(usize, ItemType, bool, &AppContext) -> ElementBox>(
|
||||
pub fn new<F: 'static + Fn(usize, ItemType, bool, &AppContext) -> Element<Self>>(
|
||||
item_count: usize,
|
||||
cx: &mut ViewContext<Self>,
|
||||
render_item: F,
|
||||
@@ -92,7 +92,7 @@ impl View for Select {
|
||||
"Select"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
if self.item_count == 0 {
|
||||
return Empty::new().boxed();
|
||||
}
|
||||
@@ -106,7 +106,7 @@ impl View for Select {
|
||||
Default::default()
|
||||
};
|
||||
let mut result = Flex::column().with_child(
|
||||
MouseEventHandler::<Header>::new(self.handle.id(), cx, |mouse_state, cx| {
|
||||
MouseEventHandler::<Header, _>::new(self.handle.id(), cx, |mouse_state, cx| {
|
||||
Container::new((self.render_item)(
|
||||
self.selected_item_ix,
|
||||
ItemType::Header,
|
||||
@@ -116,9 +116,10 @@ impl View for Select {
|
||||
.with_style(style.header)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
cx.dispatch_action(ToggleSelect)
|
||||
})
|
||||
.on_click(
|
||||
MouseButton::Left,
|
||||
move |_, _, cx: &mut EventContext<Self>| cx.dispatch_action(ToggleSelect),
|
||||
)
|
||||
.boxed(),
|
||||
);
|
||||
if self.is_open {
|
||||
@@ -134,21 +135,28 @@ impl View for Select {
|
||||
let selected_item_ix = this.selected_item_ix;
|
||||
range.end = range.end.min(this.item_count);
|
||||
items.extend(range.map(|ix| {
|
||||
MouseEventHandler::<Item>::new(ix, cx, |mouse_state, cx| {
|
||||
(this.render_item)(
|
||||
ix,
|
||||
if ix == selected_item_ix {
|
||||
ItemType::Selected
|
||||
} else {
|
||||
ItemType::Unselected
|
||||
},
|
||||
mouse_state.hovered(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
cx.dispatch_action(SelectItem(ix))
|
||||
})
|
||||
MouseEventHandler::<Item, _>::new(
|
||||
ix,
|
||||
cx,
|
||||
|mouse_state, cx| {
|
||||
(this.render_item)(
|
||||
ix,
|
||||
if ix == selected_item_ix {
|
||||
ItemType::Selected
|
||||
} else {
|
||||
ItemType::Unselected
|
||||
},
|
||||
mouse_state.hovered(),
|
||||
cx,
|
||||
)
|
||||
},
|
||||
)
|
||||
.on_click(
|
||||
MouseButton::Left,
|
||||
move |_, _, cx: &mut EventContext<Self>| {
|
||||
cx.dispatch_action(SelectItem(ix))
|
||||
},
|
||||
)
|
||||
.boxed()
|
||||
}))
|
||||
},
|
||||
|
||||
@@ -174,23 +174,60 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Pass to the test function the number of app contexts that it needs,
|
||||
// based on its parameter list.
|
||||
let mut cx_vars = proc_macro2::TokenStream::new();
|
||||
let mut cx_teardowns = proc_macro2::TokenStream::new();
|
||||
let mut inner_fn_args = proc_macro2::TokenStream::new();
|
||||
for arg in inner_fn.sig.inputs.iter() {
|
||||
for (ix, arg) in inner_fn.sig.inputs.iter().enumerate() {
|
||||
if let FnArg::Typed(arg) = arg {
|
||||
if let Type::Path(ty) = &*arg.ty {
|
||||
let last_segment = ty.path.segments.last();
|
||||
|
||||
if let Some("StdRng") = last_segment.map(|s| s.ident.to_string()).as_deref() {
|
||||
inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(seed),));
|
||||
continue;
|
||||
}
|
||||
} else if let Type::Reference(ty) = &*arg.ty {
|
||||
if let Type::Path(ty) = &*ty.elem {
|
||||
let last_segment = ty.path.segments.last();
|
||||
match last_segment.map(|s| s.ident.to_string()).as_deref() {
|
||||
Some("AppContext") => {
|
||||
inner_fn_args.extend(quote!(cx,));
|
||||
continue;
|
||||
}
|
||||
Some("TestAppContext") => {
|
||||
let first_entity_id = ix * 100_000;
|
||||
let cx_varname = format_ident!("cx_{}", ix);
|
||||
cx_vars.extend(quote!(
|
||||
let mut #cx_varname = #namespace::TestAppContext::new(
|
||||
foreground_platform.clone(),
|
||||
cx.platform().clone(),
|
||||
deterministic.build_foreground(#ix),
|
||||
deterministic.build_background(),
|
||||
cx.font_cache().clone(),
|
||||
cx.leak_detector(),
|
||||
#first_entity_id,
|
||||
stringify!(#outer_fn_name).to_string(),
|
||||
);
|
||||
));
|
||||
cx_teardowns.extend(quote!(
|
||||
#cx_varname.update(|cx| cx.remove_all_windows());
|
||||
deterministic.run_until_parked();
|
||||
#cx_varname.update(|cx| cx.clear_globals());
|
||||
));
|
||||
inner_fn_args.extend(quote!(&mut #cx_varname,));
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inner_fn_args.extend(quote!(cx,));
|
||||
}
|
||||
} else {
|
||||
return TokenStream::from(
|
||||
syn::Error::new_spanned(arg, "invalid argument").into_compile_error(),
|
||||
);
|
||||
}
|
||||
|
||||
return TokenStream::from(
|
||||
syn::Error::new_spanned(arg, "invalid argument").into_compile_error(),
|
||||
);
|
||||
}
|
||||
|
||||
parse_quote! {
|
||||
@@ -203,7 +240,11 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||
#starting_seed as u64,
|
||||
#max_retries,
|
||||
#detect_nondeterminism,
|
||||
&mut |cx, _, _, seed| #inner_fn_name(#inner_fn_args),
|
||||
&mut |cx, foreground_platform, deterministic, seed| {
|
||||
#cx_vars
|
||||
#inner_fn_name(#inner_fn_args);
|
||||
#cx_teardowns
|
||||
},
|
||||
#on_failure_fn_name,
|
||||
stringify!(#outer_fn_name).to_string(),
|
||||
);
|
||||
|
||||
@@ -7,7 +7,6 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::TryFutureExt as _;
|
||||
use workspace::AppState;
|
||||
|
||||
actions!(journal, [NewJournalEntry]);
|
||||
@@ -44,40 +43,37 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
Ok::<_, std::io::Error>((journal_dir, entry_path))
|
||||
});
|
||||
|
||||
cx.spawn(|mut cx| {
|
||||
async move {
|
||||
let (journal_dir, entry_path) = create_entry.await?;
|
||||
let (workspace, _) = cx
|
||||
.update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))
|
||||
.await;
|
||||
cx.spawn(|mut cx| async move {
|
||||
let (journal_dir, entry_path) = create_entry.await?;
|
||||
let (workspace, _) = cx
|
||||
.update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))
|
||||
.await?;
|
||||
|
||||
let opened = workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.open_paths(vec![entry_path], true, cx)
|
||||
})
|
||||
.await;
|
||||
let opened = workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.open_paths(vec![entry_path], true, cx)
|
||||
})?
|
||||
.await;
|
||||
|
||||
if let Some(Some(Ok(item))) = opened.first() {
|
||||
if let Some(editor) = item.downcast::<Editor>() {
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
let len = editor.buffer().read(cx).len(cx);
|
||||
editor.change_selections(Some(Autoscroll::center()), cx, |s| {
|
||||
s.select_ranges([len..len])
|
||||
});
|
||||
if len > 0 {
|
||||
editor.insert("\n\n", cx);
|
||||
}
|
||||
editor.insert(&entry_heading, cx);
|
||||
editor.insert("\n\n", cx);
|
||||
if let Some(Some(Ok(item))) = opened.first() {
|
||||
if let Some(editor) = item.downcast::<Editor>() {
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
let len = editor.buffer().read(cx).len(cx);
|
||||
editor.change_selections(Some(Autoscroll::center()), cx, |s| {
|
||||
s.select_ranges([len..len])
|
||||
});
|
||||
}
|
||||
if len > 0 {
|
||||
editor.insert("\n\n", cx);
|
||||
}
|
||||
editor.insert(&entry_heading, cx);
|
||||
editor.insert("\n\n", cx);
|
||||
})?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err()
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn journal_dir(settings: &Settings) -> Option<PathBuf> {
|
||||
|
||||
@@ -17,5 +17,6 @@ picker = { path = "../picker" }
|
||||
project = { path = "../project" }
|
||||
theme = { path = "../theme" }
|
||||
settings = { path = "../settings" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
anyhow = "1.0"
|
||||
|
||||
@@ -2,7 +2,7 @@ use editor::Editor;
|
||||
use gpui::{
|
||||
elements::*,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Entity, RenderContext, Subscription, View, ViewContext, ViewHandle,
|
||||
Entity, Subscription, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
@@ -50,7 +50,7 @@ impl View for ActiveBufferLanguage {
|
||||
"ActiveBufferLanguage"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
if let Some(active_language) = self.active_language.as_ref() {
|
||||
let active_language_text = if let Some(active_language_text) = active_language {
|
||||
active_language_text.to_string()
|
||||
@@ -58,7 +58,7 @@ impl View for ActiveBufferLanguage {
|
||||
"Unknown".to_string()
|
||||
};
|
||||
|
||||
MouseEventHandler::<Self>::new(0, cx, |state, cx| {
|
||||
MouseEventHandler::<Self, Self>::new(0, cx, |state, cx| {
|
||||
let theme = &cx.global::<Settings>().theme.workspace.status_bar;
|
||||
let style = theme.active_language.style_for(state, false);
|
||||
Label::new(active_language_text, style.text.clone())
|
||||
@@ -67,7 +67,9 @@ impl View for ActiveBufferLanguage {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, |_, cx| cx.dispatch_action(crate::Toggle))
|
||||
.on_click(MouseButton::Left, |_, _, cx| {
|
||||
cx.dispatch_action(crate::Toggle)
|
||||
})
|
||||
.boxed()
|
||||
} else {
|
||||
Empty::new().boxed()
|
||||
|
||||
@@ -1,55 +1,64 @@
|
||||
mod active_buffer_language;
|
||||
|
||||
pub use active_buffer_language::ActiveBufferLanguage;
|
||||
use anyhow::anyhow;
|
||||
use editor::Editor;
|
||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
|
||||
RenderContext, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use gpui::{actions, elements::*, AppContext, ModelHandle, MouseState, ViewContext};
|
||||
use language::{Buffer, LanguageRegistry};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use picker::{Picker, PickerDelegate, PickerEvent};
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use util::ResultExt;
|
||||
use workspace::{AppState, Workspace};
|
||||
|
||||
actions!(language_selector, [Toggle]);
|
||||
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
Picker::<LanguageSelector>::init(cx);
|
||||
Picker::<LanguageSelectorDelegate>::init(cx);
|
||||
cx.add_action({
|
||||
let language_registry = app_state.languages.clone();
|
||||
move |workspace, _: &Toggle, cx| {
|
||||
LanguageSelector::toggle(workspace, language_registry.clone(), cx)
|
||||
}
|
||||
move |workspace, _: &Toggle, cx| toggle(workspace, language_registry.clone(), cx)
|
||||
});
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
Dismissed,
|
||||
fn toggle(
|
||||
workspace: &mut Workspace,
|
||||
registry: Arc<LanguageRegistry>,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<()> {
|
||||
let (_, buffer, _) = workspace
|
||||
.active_item(cx)?
|
||||
.act_as::<Editor>(cx)?
|
||||
.read(cx)
|
||||
.active_excerpt(cx)?;
|
||||
workspace.toggle_modal(cx, |workspace, cx| {
|
||||
cx.add_view(|cx| {
|
||||
Picker::new(
|
||||
LanguageSelectorDelegate::new(buffer, workspace.project().clone(), registry),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub struct LanguageSelector {
|
||||
pub struct LanguageSelectorDelegate {
|
||||
buffer: ModelHandle<Buffer>,
|
||||
project: ModelHandle<Project>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
candidates: Vec<StringMatchCandidate>,
|
||||
matches: Vec<StringMatch>,
|
||||
picker: ViewHandle<Picker<Self>>,
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
impl LanguageSelector {
|
||||
impl LanguageSelectorDelegate {
|
||||
fn new(
|
||||
buffer: ModelHandle<Buffer>,
|
||||
project: ModelHandle<Project>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let handle = cx.weak_handle();
|
||||
let picker = cx.add_view(|cx| Picker::new("Select Language...", handle, cx));
|
||||
|
||||
let candidates = language_registry
|
||||
.language_names()
|
||||
.into_iter()
|
||||
@@ -73,104 +82,63 @@ impl LanguageSelector {
|
||||
language_registry,
|
||||
candidates,
|
||||
matches,
|
||||
picker,
|
||||
selected_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle(
|
||||
workspace: &mut Workspace,
|
||||
registry: Arc<LanguageRegistry>,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
if let Some((_, buffer, _)) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|active_item| active_item.act_as::<Editor>(cx))
|
||||
.and_then(|editor| editor.read(cx).active_excerpt(cx))
|
||||
{
|
||||
workspace.toggle_modal(cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let this = cx.add_view(|cx| Self::new(buffer, project, registry, cx));
|
||||
cx.subscribe(&this, Self::on_event).detach();
|
||||
this
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
workspace: &mut Workspace,
|
||||
_: ViewHandle<LanguageSelector>,
|
||||
event: &Event,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
match event {
|
||||
Event::Dismissed => {
|
||||
workspace.dismiss_modal(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for LanguageSelector {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
impl View for LanguageSelector {
|
||||
fn ui_name() -> &'static str {
|
||||
"LanguageSelector"
|
||||
impl PickerDelegate for LanguageSelectorDelegate {
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
"Select a language...".into()
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
ChildView::new(&self.picker, cx).boxed()
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
if cx.is_self_focused() {
|
||||
cx.focus(&self.picker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for LanguageSelector {
|
||||
fn match_count(&self) -> usize {
|
||||
self.matches.len()
|
||||
}
|
||||
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if let Some(mat) = self.matches.get(self.selected_index) {
|
||||
let language_name = &self.candidates[mat.candidate_id].string;
|
||||
let language = self.language_registry.language_for_name(language_name);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let project = self.project.downgrade();
|
||||
let buffer = self.buffer.downgrade();
|
||||
cx.spawn_weak(|_, mut cx| async move {
|
||||
let language = language.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.project.update(cx, |project, cx| {
|
||||
project.set_language_for_buffer(&this.buffer, language, cx);
|
||||
});
|
||||
let project = project
|
||||
.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("project was dropped"))?;
|
||||
let buffer = buffer
|
||||
.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("buffer was dropped"))?;
|
||||
project.update(&mut cx, |project, cx| {
|
||||
project.set_language_for_buffer(&buffer, language, cx);
|
||||
});
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
cx.emit(Event::Dismissed);
|
||||
cx.emit(PickerEvent::Dismiss);
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(Event::Dismissed);
|
||||
}
|
||||
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
|
||||
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> gpui::Task<()> {
|
||||
let background = cx.background().clone();
|
||||
let candidates = self.candidates.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
let matches = if query.is_empty() {
|
||||
candidates
|
||||
.into_iter()
|
||||
@@ -194,13 +162,17 @@ impl PickerDelegate for LanguageSelector {
|
||||
.await
|
||||
};
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.matches = matches;
|
||||
this.selected_index = this
|
||||
.selected_index
|
||||
.min(this.matches.len().saturating_sub(1));
|
||||
cx.notify();
|
||||
});
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let delegate = this.delegate_mut();
|
||||
delegate.matches = matches;
|
||||
delegate.selected_index = delegate
|
||||
.selected_index
|
||||
.min(delegate.matches.len().saturating_sub(1));
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -210,7 +182,7 @@ impl PickerDelegate for LanguageSelector {
|
||||
mouse_state: &mut MouseState,
|
||||
selected: bool,
|
||||
cx: &AppContext,
|
||||
) -> ElementBox {
|
||||
) -> Element<Picker<Self>> {
|
||||
let settings = cx.global::<Settings>();
|
||||
let theme = &settings.theme;
|
||||
let mat = &self.matches[ix];
|
||||
|
||||
@@ -4,25 +4,51 @@ use editor::{
|
||||
};
|
||||
use fuzzy::StringMatch;
|
||||
use gpui::{
|
||||
actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Entity,
|
||||
MouseState, RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
actions, elements::*, geometry::vector::Vector2F, AppContext, MouseState, Task, ViewContext,
|
||||
ViewHandle, WindowContext,
|
||||
};
|
||||
use language::Outline;
|
||||
use ordered_float::OrderedFloat;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use picker::{Picker, PickerDelegate, PickerEvent};
|
||||
use settings::Settings;
|
||||
use std::cmp::{self, Reverse};
|
||||
use std::{
|
||||
cmp::{self, Reverse},
|
||||
sync::Arc,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
actions!(outline, [Toggle]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(OutlineView::toggle);
|
||||
Picker::<OutlineView>::init(cx);
|
||||
cx.add_action(toggle);
|
||||
OutlineView::init(cx);
|
||||
}
|
||||
|
||||
struct OutlineView {
|
||||
picker: ViewHandle<Picker<Self>>,
|
||||
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||
if let Some(editor) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.downcast::<Editor>())
|
||||
{
|
||||
let outline = editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.outline(Some(cx.global::<Settings>().theme.editor.syntax.as_ref()));
|
||||
if let Some(outline) = outline {
|
||||
workspace.toggle_modal(cx, |_, cx| {
|
||||
cx.add_view(|cx| {
|
||||
OutlineView::new(OutlineViewDelegate::new(outline, editor, cx), cx)
|
||||
.with_max_size(800., 1200.)
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type OutlineView = Picker<OutlineViewDelegate>;
|
||||
|
||||
struct OutlineViewDelegate {
|
||||
active_editor: ViewHandle<Editor>,
|
||||
outline: Outline<Anchor>,
|
||||
selected_match_index: usize,
|
||||
@@ -31,45 +57,13 @@ struct OutlineView {
|
||||
last_query: String,
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
Dismissed,
|
||||
}
|
||||
|
||||
impl Entity for OutlineView {
|
||||
type Event = Event;
|
||||
|
||||
fn release(&mut self, cx: &mut AppContext) {
|
||||
self.restore_active_editor(cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl View for OutlineView {
|
||||
fn ui_name() -> &'static str {
|
||||
"OutlineView"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
ChildView::new(&self.picker, cx).boxed()
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
if cx.is_self_focused() {
|
||||
cx.focus(&self.picker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OutlineView {
|
||||
impl OutlineViewDelegate {
|
||||
fn new(
|
||||
outline: Outline<Anchor>,
|
||||
editor: ViewHandle<Editor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut ViewContext<OutlineView>,
|
||||
) -> Self {
|
||||
let handle = cx.weak_handle();
|
||||
Self {
|
||||
picker: cx.add_view(|cx| {
|
||||
Picker::new("Search buffer symbols...", handle, cx).with_max_size(800., 1200.)
|
||||
}),
|
||||
last_query: Default::default(),
|
||||
matches: Default::default(),
|
||||
selected_match_index: 0,
|
||||
@@ -79,28 +73,7 @@ impl OutlineView {
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||
if let Some(editor) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.downcast::<Editor>())
|
||||
{
|
||||
let outline = editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.outline(Some(cx.global::<Settings>().theme.editor.syntax.as_ref()));
|
||||
if let Some(outline) = outline {
|
||||
workspace.toggle_modal(cx, |_, cx| {
|
||||
let view = cx.add_view(|cx| OutlineView::new(outline, editor, cx));
|
||||
cx.subscribe(&view, Self::on_event).detach();
|
||||
view
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_active_editor(&mut self, cx: &mut AppContext) {
|
||||
fn restore_active_editor(&mut self, cx: &mut WindowContext) {
|
||||
self.active_editor.update(cx, |editor, cx| {
|
||||
editor.highlight_rows(None);
|
||||
if let Some(scroll_position) = self.prev_scroll_position {
|
||||
@@ -109,7 +82,7 @@ impl OutlineView {
|
||||
})
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, navigate: bool, cx: &mut ViewContext<Self>) {
|
||||
fn set_selected_index(&mut self, ix: usize, navigate: bool, cx: &mut ViewContext<OutlineView>) {
|
||||
self.selected_match_index = ix;
|
||||
if navigate && !self.matches.is_empty() {
|
||||
let selected_match = &self.matches[self.selected_match_index];
|
||||
@@ -125,22 +98,14 @@ impl OutlineView {
|
||||
active_editor.request_autoscroll(Autoscroll::center(), cx);
|
||||
});
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
workspace: &mut Workspace,
|
||||
_: ViewHandle<Self>,
|
||||
event: &Event,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
match event {
|
||||
Event::Dismissed => workspace.dismiss_modal(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for 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);
|
||||
|
||||
+106
-122
@@ -4,43 +4,51 @@ use gpui::{
|
||||
geometry::vector::{vec2f, Vector2F},
|
||||
keymap_matcher::KeymapContext,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AnyViewHandle, AppContext, Axis, Entity, MouseState, RenderContext, Task, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
AnyViewHandle, AppContext, Axis, Element, Entity, MouseState, Task, View, ViewContext,
|
||||
ViewHandle,
|
||||
};
|
||||
use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev};
|
||||
use parking_lot::Mutex;
|
||||
use std::{cmp, sync::Arc};
|
||||
use util::ResultExt;
|
||||
use workspace::Modal;
|
||||
|
||||
pub enum PickerEvent {
|
||||
Dismiss,
|
||||
}
|
||||
|
||||
pub struct Picker<D: PickerDelegate> {
|
||||
delegate: WeakViewHandle<D>,
|
||||
delegate: D,
|
||||
query_editor: ViewHandle<Editor>,
|
||||
list_state: UniformListState,
|
||||
max_size: Vector2F,
|
||||
theme: Arc<Mutex<Box<dyn Fn(&theme::Theme) -> theme::Picker>>>,
|
||||
confirmed: bool,
|
||||
pending_update_matches: Task<Option<()>>,
|
||||
}
|
||||
|
||||
pub trait PickerDelegate: View {
|
||||
pub trait PickerDelegate: Sized + 'static {
|
||||
fn placeholder_text(&self) -> Arc<str>;
|
||||
fn match_count(&self) -> usize;
|
||||
fn selected_index(&self) -> usize;
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>);
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()>;
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Self>);
|
||||
fn dismiss(&mut self, cx: &mut ViewContext<Self>);
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>);
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
state: &mut MouseState,
|
||||
selected: bool,
|
||||
cx: &AppContext,
|
||||
) -> ElementBox;
|
||||
) -> Element<Picker<Self>>;
|
||||
fn center_selection_after_match_updates(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: PickerDelegate> Entity for Picker<D> {
|
||||
type Event = ();
|
||||
type Event = PickerEvent;
|
||||
}
|
||||
|
||||
impl<D: PickerDelegate> View for Picker<D> {
|
||||
@@ -48,15 +56,10 @@ impl<D: PickerDelegate> View for Picker<D> {
|
||||
"Picker"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let theme = (self.theme.lock())(&cx.global::<settings::Settings>().theme);
|
||||
let query = self.query(cx);
|
||||
let delegate = self.delegate.clone();
|
||||
let match_count = if let Some(delegate) = delegate.upgrade(cx.app) {
|
||||
delegate.read(cx).match_count()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let match_count = self.delegate.match_count();
|
||||
|
||||
let container_style;
|
||||
let editor_style;
|
||||
@@ -93,19 +96,16 @@ impl<D: PickerDelegate> View for Picker<D> {
|
||||
match_count,
|
||||
cx,
|
||||
move |this, mut range, items, cx| {
|
||||
let delegate = this.delegate.upgrade(cx).unwrap();
|
||||
let selected_ix = delegate.read(cx).selected_index();
|
||||
range.end = cmp::min(range.end, delegate.read(cx).match_count());
|
||||
let selected_ix = this.delegate.selected_index();
|
||||
range.end = cmp::min(range.end, this.delegate.match_count());
|
||||
items.extend(range.map(move |ix| {
|
||||
MouseEventHandler::<D>::new(ix, cx, |state, cx| {
|
||||
delegate
|
||||
.read(cx)
|
||||
.render_match(ix, state, ix == selected_ix, cx)
|
||||
MouseEventHandler::<D, _>::new(ix, cx, |state, cx| {
|
||||
this.delegate.render_match(ix, state, ix == selected_ix, cx)
|
||||
})
|
||||
// Capture mouse events
|
||||
.on_down(MouseButton::Left, |_, _| {})
|
||||
.on_up(MouseButton::Left, |_, _| {})
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_down(MouseButton::Left, |_, _, _| {})
|
||||
.on_up(MouseButton::Left, |_, _, _| {})
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(SelectIndex(ix))
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
@@ -140,6 +140,12 @@ impl<D: PickerDelegate> View for Picker<D> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: PickerDelegate> Modal for Picker<D> {
|
||||
fn dismiss_on_event(event: &Self::Event) -> bool {
|
||||
matches!(event, PickerEvent::Dismiss)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: PickerDelegate> Picker<D> {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(Self::select_first);
|
||||
@@ -151,14 +157,12 @@ impl<D: PickerDelegate> Picker<D> {
|
||||
cx.add_action(Self::cancel);
|
||||
}
|
||||
|
||||
pub fn new<P>(placeholder: P, delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self
|
||||
where
|
||||
P: Into<Arc<str>>,
|
||||
{
|
||||
pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
|
||||
let theme = Arc::new(Mutex::new(
|
||||
Box::new(|theme: &theme::Theme| theme.picker.clone())
|
||||
as Box<dyn Fn(&theme::Theme) -> theme::Picker>,
|
||||
));
|
||||
let placeholder_text = delegate.placeholder_text();
|
||||
let query_editor = cx.add_view({
|
||||
let picker_theme = theme.clone();
|
||||
|cx| {
|
||||
@@ -168,26 +172,22 @@ impl<D: PickerDelegate> Picker<D> {
|
||||
})),
|
||||
cx,
|
||||
);
|
||||
editor.set_placeholder_text(placeholder, cx);
|
||||
editor.set_placeholder_text(placeholder_text, cx);
|
||||
editor
|
||||
}
|
||||
});
|
||||
cx.subscribe(&query_editor, Self::on_query_editor_event)
|
||||
.detach();
|
||||
let this = Self {
|
||||
let mut this = Self {
|
||||
query_editor,
|
||||
list_state: Default::default(),
|
||||
delegate,
|
||||
max_size: vec2f(540., 420.),
|
||||
theme,
|
||||
confirmed: false,
|
||||
pending_update_matches: Task::ready(None),
|
||||
};
|
||||
cx.defer(|this, cx| {
|
||||
if let Some(delegate) = this.delegate.upgrade(cx) {
|
||||
cx.observe(&delegate, |_, _, cx| cx.notify()).detach();
|
||||
this.update_matches(String::new(), cx)
|
||||
}
|
||||
});
|
||||
this.update_matches(String::new(), cx);
|
||||
this
|
||||
}
|
||||
|
||||
@@ -204,6 +204,14 @@ impl<D: PickerDelegate> Picker<D> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn delegate(&self) -> &D {
|
||||
&self.delegate
|
||||
}
|
||||
|
||||
pub fn delegate_mut(&mut self) -> &mut D {
|
||||
&mut self.delegate
|
||||
}
|
||||
|
||||
pub fn query(&self, cx: &AppContext) -> String {
|
||||
self.query_editor.read(cx).text(cx)
|
||||
}
|
||||
@@ -222,119 +230,95 @@ impl<D: PickerDelegate> Picker<D> {
|
||||
match event {
|
||||
editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx),
|
||||
editor::Event::Blurred if !self.confirmed => {
|
||||
if let Some(delegate) = self.delegate.upgrade(cx) {
|
||||
delegate.update(cx, |delegate, cx| {
|
||||
delegate.dismiss(cx);
|
||||
})
|
||||
}
|
||||
self.dismiss(cx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
|
||||
if let Some(delegate) = self.delegate.upgrade(cx) {
|
||||
let update = delegate.update(cx, |d, cx| d.update_matches(query, cx));
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
update.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(delegate) = this.delegate.upgrade(cx) {
|
||||
let delegate = delegate.read(cx);
|
||||
let index = delegate.selected_index();
|
||||
let target = if delegate.center_selection_after_match_updates() {
|
||||
ScrollTarget::Center(index)
|
||||
} else {
|
||||
ScrollTarget::Show(index)
|
||||
};
|
||||
this.list_state.scroll_to(target);
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
let update = self.delegate.update_matches(query, cx);
|
||||
self.matches_updated(cx);
|
||||
self.pending_update_matches = cx.spawn_weak(|this, mut cx| async move {
|
||||
update.await;
|
||||
this.upgrade(&cx)?
|
||||
.update(&mut cx, |this, cx| this.matches_updated(cx))
|
||||
.log_err()
|
||||
});
|
||||
}
|
||||
|
||||
fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let index = self.delegate.selected_index();
|
||||
let target = if self.delegate.center_selection_after_match_updates() {
|
||||
ScrollTarget::Center(index)
|
||||
} else {
|
||||
ScrollTarget::Show(index)
|
||||
};
|
||||
self.list_state.scroll_to(target);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
|
||||
if let Some(delegate) = self.delegate.upgrade(cx) {
|
||||
delegate.update(cx, |delegate, cx| {
|
||||
if delegate.match_count() > 0 {
|
||||
delegate.set_selected_index(0, cx);
|
||||
self.list_state.scroll_to(ScrollTarget::Show(0));
|
||||
}
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
if self.delegate.match_count() > 0 {
|
||||
self.delegate.set_selected_index(0, cx);
|
||||
self.list_state.scroll_to(ScrollTarget::Show(0));
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn select_index(&mut self, action: &SelectIndex, cx: &mut ViewContext<Self>) {
|
||||
if let Some(delegate) = self.delegate.upgrade(cx) {
|
||||
let index = action.0;
|
||||
delegate.update(cx, |delegate, cx| {
|
||||
if delegate.match_count() > 0 {
|
||||
self.confirmed = true;
|
||||
delegate.set_selected_index(index, cx);
|
||||
delegate.confirm(cx);
|
||||
}
|
||||
});
|
||||
let index = action.0;
|
||||
if self.delegate.match_count() > 0 {
|
||||
self.confirmed = true;
|
||||
self.delegate.set_selected_index(index, cx);
|
||||
self.delegate.confirm(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
|
||||
if let Some(delegate) = self.delegate.upgrade(cx) {
|
||||
delegate.update(cx, |delegate, cx| {
|
||||
let match_count = delegate.match_count();
|
||||
if match_count > 0 {
|
||||
let index = match_count - 1;
|
||||
delegate.set_selected_index(index, cx);
|
||||
self.list_state.scroll_to(ScrollTarget::Show(index));
|
||||
}
|
||||
});
|
||||
cx.notify();
|
||||
let match_count = self.delegate.match_count();
|
||||
if match_count > 0 {
|
||||
let index = match_count - 1;
|
||||
self.delegate.set_selected_index(index, cx);
|
||||
self.list_state.scroll_to(ScrollTarget::Show(index));
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
||||
if let Some(delegate) = self.delegate.upgrade(cx) {
|
||||
delegate.update(cx, |delegate, cx| {
|
||||
let next_index = delegate.selected_index() + 1;
|
||||
if next_index < delegate.match_count() {
|
||||
delegate.set_selected_index(next_index, cx);
|
||||
self.list_state.scroll_to(ScrollTarget::Show(next_index));
|
||||
}
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
let next_index = self.delegate.selected_index() + 1;
|
||||
if next_index < self.delegate.match_count() {
|
||||
self.delegate.set_selected_index(next_index, cx);
|
||||
self.list_state.scroll_to(ScrollTarget::Show(next_index));
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
|
||||
if let Some(delegate) = self.delegate.upgrade(cx) {
|
||||
delegate.update(cx, |delegate, cx| {
|
||||
let mut selected_index = delegate.selected_index();
|
||||
if selected_index > 0 {
|
||||
selected_index -= 1;
|
||||
delegate.set_selected_index(selected_index, cx);
|
||||
self.list_state
|
||||
.scroll_to(ScrollTarget::Show(selected_index));
|
||||
}
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
let mut selected_index = self.delegate.selected_index();
|
||||
if selected_index > 0 {
|
||||
selected_index -= 1;
|
||||
self.delegate.set_selected_index(selected_index, cx);
|
||||
self.list_state
|
||||
.scroll_to(ScrollTarget::Show(selected_index));
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
if let Some(delegate) = self.delegate.upgrade(cx) {
|
||||
self.confirmed = true;
|
||||
delegate.update(cx, |delegate, cx| delegate.confirm(cx));
|
||||
}
|
||||
pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
self.confirmed = true;
|
||||
self.delegate.confirm(cx);
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||
if let Some(delegate) = self.delegate.upgrade(cx) {
|
||||
delegate.update(cx, |delegate, cx| delegate.dismiss(cx));
|
||||
}
|
||||
self.dismiss(cx);
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(PickerEvent::Dismiss);
|
||||
self.delegate.dismissed(cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ use gpui::{
|
||||
impl_internal_actions,
|
||||
keymap_matcher::KeymapContext,
|
||||
platform::{CursorStyle, MouseButton, PromptLevel},
|
||||
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, RenderContext, Task, View,
|
||||
ViewContext, ViewHandle,
|
||||
AppContext, ClipboardItem, Drawable, Element, Entity, ModelHandle, Task, View, ViewContext,
|
||||
ViewHandle,
|
||||
};
|
||||
use menu::{Confirm, SelectNext, SelectPrev};
|
||||
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
|
||||
@@ -498,7 +498,7 @@ impl ProjectPanel {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.edit_state.take();
|
||||
cx.notify();
|
||||
});
|
||||
})?;
|
||||
|
||||
let new_entry = new_entry?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
@@ -519,7 +519,7 @@ impl ProjectPanel {
|
||||
);
|
||||
}
|
||||
cx.notify();
|
||||
});
|
||||
})?;
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
@@ -655,7 +655,7 @@ impl ProjectPanel {
|
||||
this.project
|
||||
.update(cx, |project, cx| project.delete_entry(entry_id, cx))
|
||||
.ok_or_else(|| anyhow!("no such entry"))
|
||||
})?
|
||||
})??
|
||||
.await
|
||||
}))
|
||||
}
|
||||
@@ -1015,8 +1015,8 @@ impl ProjectPanel {
|
||||
fn for_each_visible_entry(
|
||||
&self,
|
||||
range: Range<usize>,
|
||||
cx: &mut RenderContext<ProjectPanel>,
|
||||
mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut RenderContext<ProjectPanel>),
|
||||
cx: &mut ViewContext<ProjectPanel>,
|
||||
mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut ViewContext<ProjectPanel>),
|
||||
) {
|
||||
let mut ix = 0;
|
||||
for (worktree_id, visible_worktree_entries) in &self.visible_entries {
|
||||
@@ -1097,8 +1097,8 @@ impl ProjectPanel {
|
||||
padding: f32,
|
||||
row_container_style: ContainerStyle,
|
||||
style: &ProjectPanelEntry,
|
||||
cx: &mut RenderContext<V>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Element<V> {
|
||||
let kind = details.kind;
|
||||
let show_editor = details.is_editing && !details.is_processing;
|
||||
|
||||
@@ -1154,9 +1154,8 @@ impl ProjectPanel {
|
||||
editor: &ViewHandle<Editor>,
|
||||
dragged_entry_destination: &mut Option<Arc<Path>>,
|
||||
theme: &theme::ProjectPanel,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
let this = cx.handle();
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let kind = details.kind;
|
||||
let path = details.path.clone();
|
||||
let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width;
|
||||
@@ -1171,7 +1170,7 @@ impl ProjectPanel {
|
||||
|
||||
let show_editor = details.is_editing && !details.is_processing;
|
||||
|
||||
MouseEventHandler::<Self>::new(entry_id.to_usize(), cx, |state, cx| {
|
||||
MouseEventHandler::<Self, _>::new(entry_id.to_usize(), cx, |state, cx| {
|
||||
let mut style = entry_style.style_for(state, details.is_selected).clone();
|
||||
|
||||
if cx
|
||||
@@ -1201,7 +1200,7 @@ impl ProjectPanel {
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(MouseButton::Left, move |e, cx| {
|
||||
.on_click(MouseButton::Left, move |e, _, cx| {
|
||||
if !show_editor {
|
||||
if kind == EntryKind::Dir {
|
||||
cx.dispatch_action(ToggleExpanded(entry_id))
|
||||
@@ -1213,13 +1212,13 @@ impl ProjectPanel {
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_down(MouseButton::Right, move |e, cx| {
|
||||
.on_down(MouseButton::Right, move |e, _, cx| {
|
||||
cx.dispatch_action(DeployContextMenu {
|
||||
entry_id,
|
||||
position: e.position,
|
||||
})
|
||||
})
|
||||
.on_up(MouseButton::Left, move |_, cx| {
|
||||
.on_up(MouseButton::Left, move |_, _, cx| {
|
||||
if let Some((_, dragged_entry)) = cx
|
||||
.global::<DragAndDrop<Workspace>>()
|
||||
.currently_dragged::<ProjectEntryId>(cx.window_id())
|
||||
@@ -1231,27 +1230,23 @@ impl ProjectPanel {
|
||||
});
|
||||
}
|
||||
})
|
||||
.on_move(move |_, cx| {
|
||||
.on_move(move |_, this, cx| {
|
||||
if cx
|
||||
.global::<DragAndDrop<Workspace>>()
|
||||
.currently_dragged::<ProjectEntryId>(cx.window_id())
|
||||
.is_some()
|
||||
{
|
||||
if let Some(this) = this.upgrade(cx.app) {
|
||||
this.update(cx.app, |this, _| {
|
||||
this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
|
||||
path.parent().map(|parent| Arc::from(parent))
|
||||
} else {
|
||||
Some(path.clone())
|
||||
};
|
||||
})
|
||||
}
|
||||
this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
|
||||
path.parent().map(|parent| Arc::from(parent))
|
||||
} else {
|
||||
Some(path.clone())
|
||||
};
|
||||
}
|
||||
})
|
||||
.as_draggable(entry_id, {
|
||||
let row_container_style = theme.dragged_entry.container;
|
||||
|
||||
move |_, cx: &mut RenderContext<Workspace>| {
|
||||
move |_, cx: &mut ViewContext<Workspace>| {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
Self::render_entry_visual_element(
|
||||
&details,
|
||||
@@ -1273,7 +1268,7 @@ impl View for ProjectPanel {
|
||||
"ProjectPanel"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::Element<Self> {
|
||||
enum ProjectPanel {}
|
||||
let theme = &cx.global::<Settings>().theme.project_panel;
|
||||
let mut container_style = theme.container;
|
||||
@@ -1285,7 +1280,7 @@ impl View for ProjectPanel {
|
||||
if has_worktree {
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ProjectPanel>::new(0, cx, |_, cx| {
|
||||
MouseEventHandler::<ProjectPanel, _>::new(0, cx, |_, cx| {
|
||||
UniformList::new(
|
||||
self.list.clone(),
|
||||
self.visible_entries
|
||||
@@ -1317,7 +1312,7 @@ impl View for ProjectPanel {
|
||||
.expanded()
|
||||
.boxed()
|
||||
})
|
||||
.on_down(MouseButton::Right, move |e, cx| {
|
||||
.on_down(MouseButton::Right, move |e, _, cx| {
|
||||
// When deploying the context menu anywhere below the last project entry,
|
||||
// act as if the user clicked the root of the last worktree.
|
||||
if let Some(entry_id) = last_worktree_root_id {
|
||||
@@ -1334,7 +1329,7 @@ impl View for ProjectPanel {
|
||||
} else {
|
||||
Flex::column()
|
||||
.with_child(
|
||||
MouseEventHandler::<Self>::new(2, cx, {
|
||||
MouseEventHandler::<Self, _>::new(2, cx, {
|
||||
let button_style = theme.open_project_button.clone();
|
||||
let context_menu_item_style =
|
||||
cx.global::<Settings>().theme.context_menu.item.clone();
|
||||
@@ -1353,7 +1348,7 @@ impl View for ProjectPanel {
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_action(workspace::Open)
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
@@ -1549,7 +1544,7 @@ mod tests {
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx));
|
||||
|
||||
select_path(&panel, "root1", cx);
|
||||
@@ -1571,7 +1566,10 @@ mod tests {
|
||||
// Add a file with the root folder selected. The filename editor is placed
|
||||
// before the first file in the root folder.
|
||||
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
|
||||
assert!(panel.read_with(cx, |panel, cx| panel.filename_editor.is_focused(cx)));
|
||||
cx.read_window(window_id, |cx| {
|
||||
let panel = panel.read(cx);
|
||||
assert!(panel.filename_editor.is_focused(cx));
|
||||
});
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
@@ -1943,7 +1941,8 @@ mod tests {
|
||||
let mut result = Vec::new();
|
||||
let mut project_entries = HashSet::new();
|
||||
let mut has_editor = false;
|
||||
cx.render(panel, |panel, cx| {
|
||||
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.for_each_visible_entry(range, cx, |project_entry, details, _| {
|
||||
if details.is_editing {
|
||||
assert!(!has_editor, "duplicate editor entry");
|
||||
|
||||
@@ -1,90 +1,63 @@
|
||||
use anyhow::anyhow;
|
||||
use editor::{
|
||||
combine_syntax_and_fuzzy_match_highlights, scroll::autoscroll::Autoscroll,
|
||||
styled_runs_for_code_label, Bias, Editor,
|
||||
};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
|
||||
RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle,
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use picker::{Picker, PickerDelegate, PickerEvent};
|
||||
use project::{Project, Symbol};
|
||||
use settings::Settings;
|
||||
use std::{borrow::Cow, cmp::Reverse};
|
||||
use std::{borrow::Cow, cmp::Reverse, sync::Arc};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
actions!(project_symbols, [Toggle]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(ProjectSymbolsView::toggle);
|
||||
Picker::<ProjectSymbolsView>::init(cx);
|
||||
cx.add_action(toggle);
|
||||
ProjectSymbols::init(cx);
|
||||
}
|
||||
|
||||
pub struct ProjectSymbolsView {
|
||||
picker: ViewHandle<Picker<Self>>,
|
||||
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||
workspace.toggle_modal(cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let workspace = cx.weak_handle();
|
||||
cx.add_view(|cx| ProjectSymbols::new(ProjectSymbolsDelegate::new(workspace, project), cx))
|
||||
});
|
||||
}
|
||||
|
||||
pub type ProjectSymbols = Picker<ProjectSymbolsDelegate>;
|
||||
|
||||
pub struct ProjectSymbolsDelegate {
|
||||
workspace: WeakViewHandle<Workspace>,
|
||||
project: ModelHandle<Project>,
|
||||
selected_match_index: usize,
|
||||
symbols: Vec<Symbol>,
|
||||
visible_match_candidates: Vec<StringMatchCandidate>,
|
||||
external_match_candidates: Vec<StringMatchCandidate>,
|
||||
show_worktree_root_name: bool,
|
||||
pending_update: Task<()>,
|
||||
matches: Vec<StringMatch>,
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
Dismissed,
|
||||
Selected(Symbol),
|
||||
}
|
||||
|
||||
impl Entity for ProjectSymbolsView {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
impl View for ProjectSymbolsView {
|
||||
fn ui_name() -> &'static str {
|
||||
"ProjectSymbolsView"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
ChildView::new(&self.picker, cx).boxed()
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
if cx.is_self_focused() {
|
||||
cx.focus(&self.picker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProjectSymbolsView {
|
||||
fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let handle = cx.weak_handle();
|
||||
impl ProjectSymbolsDelegate {
|
||||
fn new(workspace: WeakViewHandle<Workspace>, project: ModelHandle<Project>) -> Self {
|
||||
Self {
|
||||
workspace,
|
||||
project,
|
||||
picker: cx.add_view(|cx| Picker::new("Search project symbols...", handle, cx)),
|
||||
selected_match_index: 0,
|
||||
symbols: Default::default(),
|
||||
visible_match_candidates: Default::default(),
|
||||
external_match_candidates: Default::default(),
|
||||
matches: Default::default(),
|
||||
show_worktree_root_name: false,
|
||||
pending_update: Task::ready(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||
workspace.toggle_modal(cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let symbols = cx.add_view(|cx| Self::new(project, cx));
|
||||
cx.subscribe(&symbols, Self::on_event).detach();
|
||||
symbols
|
||||
});
|
||||
}
|
||||
|
||||
fn filter(&mut self, query: &str, cx: &mut ViewContext<Self>) {
|
||||
fn filter(&mut self, query: &str, cx: &mut ViewContext<ProjectSymbols>) {
|
||||
const MAX_MATCHES: usize = 100;
|
||||
let mut visible_matches = cx.background_executor().block(fuzzy::match_strings(
|
||||
&self.visible_match_candidates,
|
||||
@@ -125,60 +98,50 @@ impl ProjectSymbolsView {
|
||||
|
||||
self.matches = matches;
|
||||
self.set_selected_index(0, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
workspace: &mut Workspace,
|
||||
_: ViewHandle<Self>,
|
||||
event: &Event,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
match event {
|
||||
Event::Dismissed => workspace.dismiss_modal(cx),
|
||||
Event::Selected(symbol) => {
|
||||
let buffer = workspace
|
||||
.project()
|
||||
.update(cx, |project, cx| project.open_buffer_for_symbol(symbol, cx));
|
||||
|
||||
let symbol = symbol.clone();
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let buffer = buffer.await?;
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let position = buffer
|
||||
.read(cx)
|
||||
.clip_point_utf16(symbol.range.start, Bias::Left);
|
||||
|
||||
let editor = workspace.open_project_item::<Editor>(buffer, cx);
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::center()), cx, |s| {
|
||||
s.select_ranges([position..position])
|
||||
});
|
||||
});
|
||||
});
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
workspace.dismiss_modal(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for ProjectSymbolsView {
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
||||
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())
|
||||
{
|
||||
cx.emit(Event::Selected(symbol));
|
||||
let buffer = self.project.update(cx, |project, cx| {
|
||||
project.open_buffer_for_symbol(&symbol, cx)
|
||||
});
|
||||
let symbol = symbol.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
cx.spawn_weak(|_, mut cx| async move {
|
||||
let buffer = buffer.await?;
|
||||
let workspace = workspace
|
||||
.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("workspace was dropped"))?;
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let position = buffer
|
||||
.read(cx)
|
||||
.clip_point_utf16(symbol.range.start, Bias::Left);
|
||||
|
||||
let editor = workspace.open_project_item::<Editor>(buffer, cx);
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::center()), cx, |s| {
|
||||
s.select_ranges([position..position])
|
||||
});
|
||||
});
|
||||
})?;
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
cx.emit(PickerEvent::Dismiss);
|
||||
}
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(Event::Dismissed);
|
||||
}
|
||||
fn dismissed(&mut self, _cx: &mut ViewContext<ProjectSymbols>) {}
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
self.matches.len()
|
||||
@@ -188,23 +151,23 @@ impl PickerDelegate for ProjectSymbolsView {
|
||||
self.selected_match_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
|
||||
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<ProjectSymbols>) {
|
||||
self.selected_match_index = ix;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<ProjectSymbols>) -> Task<()> {
|
||||
self.filter(&query, cx);
|
||||
self.show_worktree_root_name = self.project.read(cx).visible_worktrees(cx).count() > 1;
|
||||
let symbols = self
|
||||
.project
|
||||
.update(cx, |project, cx| project.symbols(&query, cx));
|
||||
self.pending_update = cx.spawn_weak(|this, mut cx| async move {
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
let symbols = symbols.await.log_err();
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
if let Some(symbols) = symbols {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let project = this.project.read(cx);
|
||||
let delegate = this.delegate_mut();
|
||||
let project = delegate.project.read(cx);
|
||||
let (visible_match_candidates, external_match_candidates) = symbols
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -221,15 +184,15 @@ impl PickerDelegate for ProjectSymbolsView {
|
||||
.map_or(false, |e| !e.is_ignored)
|
||||
});
|
||||
|
||||
this.visible_match_candidates = visible_match_candidates;
|
||||
this.external_match_candidates = external_match_candidates;
|
||||
this.symbols = symbols;
|
||||
this.filter(&query, cx);
|
||||
});
|
||||
delegate.visible_match_candidates = visible_match_candidates;
|
||||
delegate.external_match_candidates = external_match_candidates;
|
||||
delegate.symbols = symbols;
|
||||
delegate.filter(&query, cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
});
|
||||
Task::ready(())
|
||||
})
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
@@ -238,7 +201,7 @@ impl PickerDelegate for ProjectSymbolsView {
|
||||
mouse_state: &mut MouseState,
|
||||
selected: bool,
|
||||
cx: &AppContext,
|
||||
) -> ElementBox {
|
||||
) -> Element<Picker<Self>> {
|
||||
let string_match = &self.matches[ix];
|
||||
let settings = cx.global::<Settings>();
|
||||
let style = &settings.theme.picker.item;
|
||||
@@ -363,46 +326,53 @@ mod tests {
|
||||
},
|
||||
);
|
||||
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
|
||||
// Create the project symbols view.
|
||||
let (_, symbols_view) = cx.add_window(|cx| ProjectSymbolsView::new(project.clone(), cx));
|
||||
let picker = symbols_view.read_with(cx, |symbols_view, _| symbols_view.picker.clone());
|
||||
let symbols = cx.add_view(&workspace, |cx| {
|
||||
ProjectSymbols::new(
|
||||
ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
// Spawn multiples updates before the first update completes,
|
||||
// such that in the end, there are no matches. Testing for regression:
|
||||
// https://github.com/zed-industries/zed/issues/861
|
||||
picker.update(cx, |p, cx| {
|
||||
symbols.update(cx, |p, cx| {
|
||||
p.update_matches("o".to_string(), cx);
|
||||
p.update_matches("on".to_string(), cx);
|
||||
p.update_matches("onex".to_string(), cx);
|
||||
});
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
symbols_view.read_with(cx, |symbols_view, _| {
|
||||
assert_eq!(symbols_view.matches.len(), 0);
|
||||
symbols.read_with(cx, |symbols, _| {
|
||||
assert_eq!(symbols.delegate().matches.len(), 0);
|
||||
});
|
||||
|
||||
// Spawn more updates such that in the end, there are matches.
|
||||
picker.update(cx, |p, cx| {
|
||||
symbols.update(cx, |p, cx| {
|
||||
p.update_matches("one".to_string(), cx);
|
||||
p.update_matches("on".to_string(), cx);
|
||||
});
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
symbols_view.read_with(cx, |symbols_view, _| {
|
||||
assert_eq!(symbols_view.matches.len(), 2);
|
||||
assert_eq!(symbols_view.matches[0].string, "ton");
|
||||
assert_eq!(symbols_view.matches[1].string, "one");
|
||||
symbols.read_with(cx, |symbols, _| {
|
||||
let delegate = symbols.delegate();
|
||||
assert_eq!(delegate.matches.len(), 2);
|
||||
assert_eq!(delegate.matches[0].string, "ton");
|
||||
assert_eq!(delegate.matches[1].string, "one");
|
||||
});
|
||||
|
||||
// Spawn more updates such that in the end, there are again no matches.
|
||||
picker.update(cx, |p, cx| {
|
||||
symbols.update(cx, |p, cx| {
|
||||
p.update_matches("o".to_string(), cx);
|
||||
p.update_matches("".to_string(), cx);
|
||||
});
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
symbols_view.read_with(cx, |symbols_view, _| {
|
||||
assert_eq!(symbols_view.matches.len(), 0);
|
||||
symbols.read_with(cx, |symbols, _| {
|
||||
assert_eq!(symbols.delegate().matches.len(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::path::Path;
|
||||
use fuzzy::StringMatch;
|
||||
use gpui::{
|
||||
elements::{Label, LabelStyle},
|
||||
Element, ElementBox,
|
||||
Drawable, Element, View,
|
||||
};
|
||||
use workspace::WorkspaceLocation;
|
||||
|
||||
@@ -42,7 +42,7 @@ impl HighlightedText {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(self, style: impl Into<LabelStyle>) -> ElementBox {
|
||||
pub fn render<V: View>(self, style: impl Into<LabelStyle>) -> Element<V> {
|
||||
Label::new(self.text, style)
|
||||
.with_highlights(self.highlight_positions)
|
||||
.boxed()
|
||||
|
||||
@@ -3,14 +3,15 @@ mod highlighted_workspace_location;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::{ChildView, Flex, ParentElement},
|
||||
AnyViewHandle, AppContext, Element, ElementBox, Entity, RenderContext, Task, View, ViewContext,
|
||||
ViewHandle,
|
||||
anyhow::Result,
|
||||
elements::{Flex, ParentElement},
|
||||
AppContext, Drawable, Element, Task, ViewContext,
|
||||
};
|
||||
use highlighted_workspace_location::HighlightedWorkspaceLocation;
|
||||
use ordered_float::OrderedFloat;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use picker::{Picker, PickerDelegate, PickerEvent};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use workspace::{
|
||||
notifications::simple_message_notification::MessageNotification, OpenPaths, Workspace,
|
||||
WorkspaceLocation, WORKSPACE_DB,
|
||||
@@ -19,101 +20,70 @@ use workspace::{
|
||||
actions!(projects, [OpenRecent]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(RecentProjectsView::toggle);
|
||||
Picker::<RecentProjectsView>::init(cx);
|
||||
cx.add_async_action(toggle);
|
||||
RecentProjects::init(cx);
|
||||
}
|
||||
|
||||
struct RecentProjectsView {
|
||||
picker: ViewHandle<Picker<Self>>,
|
||||
fn toggle(
|
||||
_: &mut Workspace,
|
||||
_: &OpenRecent,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
Some(cx.spawn(|workspace, mut cx| async move {
|
||||
let workspace_locations: Vec<_> = cx
|
||||
.background()
|
||||
.spawn(async {
|
||||
WORKSPACE_DB
|
||||
.recent_workspaces_on_disk()
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|(_, location)| location)
|
||||
.collect()
|
||||
})
|
||||
.await;
|
||||
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
if !workspace_locations.is_empty() {
|
||||
workspace.toggle_modal(cx, |_, cx| {
|
||||
cx.add_view(|cx| {
|
||||
RecentProjects::new(RecentProjectsDelegate::new(workspace_locations), cx)
|
||||
.with_max_size(800., 1200.)
|
||||
})
|
||||
});
|
||||
} else {
|
||||
workspace.show_notification(0, cx, |cx| {
|
||||
cx.add_view(|_| MessageNotification::new_message("No recent projects to open."))
|
||||
})
|
||||
}
|
||||
})?;
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
|
||||
type RecentProjects = Picker<RecentProjectsDelegate>;
|
||||
|
||||
struct RecentProjectsDelegate {
|
||||
workspace_locations: Vec<WorkspaceLocation>,
|
||||
selected_match_index: usize,
|
||||
matches: Vec<StringMatch>,
|
||||
}
|
||||
|
||||
impl RecentProjectsView {
|
||||
fn new(workspace_locations: Vec<WorkspaceLocation>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let handle = cx.weak_handle();
|
||||
impl RecentProjectsDelegate {
|
||||
fn new(workspace_locations: Vec<WorkspaceLocation>) -> Self {
|
||||
Self {
|
||||
picker: cx.add_view(|cx| {
|
||||
Picker::new("Recent Projects...", handle, cx).with_max_size(800., 1200.)
|
||||
}),
|
||||
workspace_locations,
|
||||
selected_match_index: 0,
|
||||
matches: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle(_: &mut Workspace, _: &OpenRecent, cx: &mut ViewContext<Workspace>) {
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let workspace_locations: Vec<_> = cx
|
||||
.background()
|
||||
.spawn(async {
|
||||
WORKSPACE_DB
|
||||
.recent_workspaces_on_disk()
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|(_, location)| location)
|
||||
.collect()
|
||||
})
|
||||
.await;
|
||||
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
if !workspace_locations.is_empty() {
|
||||
workspace.toggle_modal(cx, |_, cx| {
|
||||
let view = cx.add_view(|cx| Self::new(workspace_locations, cx));
|
||||
cx.subscribe(&view, Self::on_event).detach();
|
||||
view
|
||||
});
|
||||
} else {
|
||||
workspace.show_notification(0, cx, |cx| {
|
||||
cx.add_view(|_| {
|
||||
MessageNotification::new_message("No recent projects to open.")
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
workspace: &mut Workspace,
|
||||
_: ViewHandle<Self>,
|
||||
event: &Event,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
match event {
|
||||
Event::Dismissed => workspace.dismiss_modal(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
Dismissed,
|
||||
}
|
||||
|
||||
impl Entity for RecentProjectsView {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
impl View for RecentProjectsView {
|
||||
fn ui_name() -> &'static str {
|
||||
"RecentProjectsView"
|
||||
impl PickerDelegate for RecentProjectsDelegate {
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
"Recent Projects...".into()
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
ChildView::new(&self.picker, cx).boxed()
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
if cx.is_self_focused() {
|
||||
cx.focus(&self.picker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for RecentProjectsView {
|
||||
fn match_count(&self) -> usize {
|
||||
self.matches.len()
|
||||
}
|
||||
@@ -122,11 +92,15 @@ impl PickerDelegate for RecentProjectsView {
|
||||
self.selected_match_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Self>) {
|
||||
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<RecentProjects>) {
|
||||
self.selected_match_index = ix;
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
cx: &mut ViewContext<RecentProjects>,
|
||||
) -> gpui::Task<()> {
|
||||
let query = query.trim_start();
|
||||
let smart_case = query.chars().any(|c| c.is_uppercase());
|
||||
let candidates = self
|
||||
@@ -164,19 +138,17 @@ impl PickerDelegate for RecentProjectsView {
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn confirm(&mut self, cx: &mut ViewContext<RecentProjects>) {
|
||||
if let Some(selected_match) = &self.matches.get(self.selected_index()) {
|
||||
let workspace_location = &self.workspace_locations[selected_match.candidate_id];
|
||||
cx.dispatch_action(OpenPaths {
|
||||
paths: workspace_location.paths().as_ref().clone(),
|
||||
});
|
||||
cx.emit(Event::Dismissed);
|
||||
cx.emit(PickerEvent::Dismiss);
|
||||
}
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(Event::Dismissed);
|
||||
}
|
||||
fn dismissed(&mut self, _cx: &mut ViewContext<RecentProjects>) {}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
@@ -184,7 +156,7 @@ impl PickerDelegate for RecentProjectsView {
|
||||
mouse_state: &mut gpui::MouseState,
|
||||
selected: bool,
|
||||
cx: &gpui::AppContext,
|
||||
) -> ElementBox {
|
||||
) -> Element<Picker<Self>> {
|
||||
let settings = cx.global::<Settings>();
|
||||
let string_match = &self.matches[ix];
|
||||
let style = settings.theme.picker.item.style_for(mouse_state, selected);
|
||||
|
||||
@@ -9,13 +9,13 @@ use gpui::{
|
||||
elements::*,
|
||||
impl_actions,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Action, AnyViewHandle, AppContext, Entity, RenderContext, Subscription, Task, View,
|
||||
ViewContext, ViewHandle,
|
||||
Action, AnyViewHandle, AppContext, Entity, Subscription, Task, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use project::search::SearchQuery;
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use std::{any::Any, sync::Arc};
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
item::ItemHandle,
|
||||
searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle},
|
||||
@@ -92,7 +92,7 @@ impl View for BufferSearchBar {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let editor_container = if self.query_contains_error {
|
||||
theme.search.invalid_editor
|
||||
@@ -324,8 +324,8 @@ impl BufferSearchBar {
|
||||
option_supported: bool,
|
||||
icon: &'static str,
|
||||
option: SearchOption,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> Option<ElementBox> {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Element<Self>> {
|
||||
if !option_supported {
|
||||
return None;
|
||||
}
|
||||
@@ -333,7 +333,7 @@ impl BufferSearchBar {
|
||||
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
|
||||
let is_active = self.is_search_option_enabled(option);
|
||||
Some(
|
||||
MouseEventHandler::<Self>::new(option as usize, cx, |state, cx| {
|
||||
MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
|
||||
let style = cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
@@ -345,11 +345,11 @@ impl BufferSearchBar {
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_any_action(option.to_toggle_action())
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.with_tooltip::<Self, _>(
|
||||
.with_tooltip::<Self>(
|
||||
option as usize,
|
||||
format!("Toggle {}", option.label()),
|
||||
Some(option.to_toggle_action()),
|
||||
@@ -364,8 +364,8 @@ impl BufferSearchBar {
|
||||
&self,
|
||||
icon: &'static str,
|
||||
direction: Direction,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let action: Box<dyn Action>;
|
||||
let tooltip;
|
||||
match direction {
|
||||
@@ -381,7 +381,7 @@ impl BufferSearchBar {
|
||||
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
|
||||
|
||||
enum NavButton {}
|
||||
MouseEventHandler::<NavButton>::new(direction as usize, cx, |state, cx| {
|
||||
MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
|
||||
let style = cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
@@ -395,10 +395,10 @@ impl BufferSearchBar {
|
||||
})
|
||||
.on_click(MouseButton::Left, {
|
||||
let action = action.boxed_clone();
|
||||
move |_, cx| cx.dispatch_any_action(action.boxed_clone())
|
||||
move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.with_tooltip::<NavButton, _>(
|
||||
.with_tooltip::<NavButton>(
|
||||
direction as usize,
|
||||
tooltip.to_string(),
|
||||
Some(action),
|
||||
@@ -411,14 +411,14 @@ impl BufferSearchBar {
|
||||
fn render_close_button(
|
||||
&self,
|
||||
theme: &theme::Search,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let action = Box::new(Dismiss);
|
||||
let tooltip = "Dismiss Buffer Search";
|
||||
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
|
||||
|
||||
enum CloseButton {}
|
||||
MouseEventHandler::<CloseButton>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<CloseButton, _>::new(0, cx, |state, _| {
|
||||
let style = theme.dismiss_button.style_for(state, false);
|
||||
Svg::new("icons/x_mark_8.svg")
|
||||
.with_color(style.color)
|
||||
@@ -433,10 +433,10 @@ impl BufferSearchBar {
|
||||
})
|
||||
.on_click(MouseButton::Left, {
|
||||
let action = action.boxed_clone();
|
||||
move |_, cx| cx.dispatch_any_action(action.boxed_clone())
|
||||
move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.with_tooltip::<CloseButton, _>(0, tooltip.to_string(), Some(action), tooltip_style, cx)
|
||||
.with_tooltip::<CloseButton>(0, tooltip.to_string(), Some(action), tooltip_style, cx)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
@@ -618,7 +618,8 @@ impl BufferSearchBar {
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -12,9 +12,8 @@ use gpui::{
|
||||
actions,
|
||||
elements::*,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Action, AnyViewHandle, AppContext, ElementBox, Entity, ModelContext, ModelHandle,
|
||||
RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakModelHandle,
|
||||
WeakViewHandle,
|
||||
Action, AnyViewHandle, AppContext, Element, Entity, ModelContext, ModelHandle, Subscription,
|
||||
Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
|
||||
};
|
||||
use menu::Confirm;
|
||||
use project::{search::SearchQuery, Project};
|
||||
@@ -30,7 +29,7 @@ use std::{
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{
|
||||
item::{Item, ItemEvent, ItemHandle},
|
||||
item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
|
||||
searchable::{Direction, SearchableItem, SearchableItemHandle},
|
||||
ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
|
||||
};
|
||||
@@ -179,7 +178,7 @@ impl View for ProjectSearchView {
|
||||
"ProjectSearchView"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let model = &self.model.read(cx);
|
||||
if model.match_ranges.is_empty() {
|
||||
enum Status {}
|
||||
@@ -192,7 +191,7 @@ impl View for ProjectSearchView {
|
||||
} else {
|
||||
"No results"
|
||||
};
|
||||
MouseEventHandler::<Status>::new(0, cx, |_, _| {
|
||||
MouseEventHandler::<Status, _>::new(0, cx, |_, _| {
|
||||
Label::new(text, theme.search.results_status.clone())
|
||||
.aligned()
|
||||
.contained()
|
||||
@@ -200,7 +199,7 @@ impl View for ProjectSearchView {
|
||||
.flex(1., true)
|
||||
.boxed()
|
||||
})
|
||||
.on_down(MouseButton::Left, |_, cx| {
|
||||
.on_down(MouseButton::Left, |_, _, cx| {
|
||||
cx.focus_parent_view();
|
||||
})
|
||||
.boxed()
|
||||
@@ -250,12 +249,12 @@ impl Item for ProjectSearchView {
|
||||
.update(cx, |editor, cx| editor.deactivated(cx));
|
||||
}
|
||||
|
||||
fn tab_content(
|
||||
fn tab_content<T: View>(
|
||||
&self,
|
||||
_detail: Option<usize>,
|
||||
tab_theme: &theme::Tab,
|
||||
cx: &AppContext,
|
||||
) -> ElementBox {
|
||||
) -> Element<T> {
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Svg::new("icons/magnifying_glass_12.svg")
|
||||
@@ -370,7 +369,7 @@ impl Item for ProjectSearchView {
|
||||
}
|
||||
}
|
||||
|
||||
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
|
||||
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
|
||||
self.results_editor.breadcrumbs(theme, cx)
|
||||
}
|
||||
|
||||
@@ -752,8 +751,8 @@ impl ProjectSearchBar {
|
||||
&self,
|
||||
icon: &'static str,
|
||||
direction: Direction,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let action: Box<dyn Action>;
|
||||
let tooltip;
|
||||
match direction {
|
||||
@@ -769,7 +768,7 @@ impl ProjectSearchBar {
|
||||
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
|
||||
|
||||
enum NavButton {}
|
||||
MouseEventHandler::<NavButton>::new(direction as usize, cx, |state, cx| {
|
||||
MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
|
||||
let style = &cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
@@ -783,10 +782,10 @@ impl ProjectSearchBar {
|
||||
})
|
||||
.on_click(MouseButton::Left, {
|
||||
let action = action.boxed_clone();
|
||||
move |_, cx| cx.dispatch_any_action(action.boxed_clone())
|
||||
move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.with_tooltip::<NavButton, _>(
|
||||
.with_tooltip::<NavButton>(
|
||||
direction as usize,
|
||||
tooltip.to_string(),
|
||||
Some(action),
|
||||
@@ -800,11 +799,11 @@ impl ProjectSearchBar {
|
||||
&self,
|
||||
icon: &'static str,
|
||||
option: SearchOption,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Element<Self> {
|
||||
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
|
||||
let is_active = self.is_option_enabled(option, cx);
|
||||
MouseEventHandler::<Self>::new(option as usize, cx, |state, cx| {
|
||||
MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
|
||||
let style = &cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
@@ -816,11 +815,11 @@ impl ProjectSearchBar {
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
cx.dispatch_any_action(option.to_toggle_action())
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.with_tooltip::<Self, _>(
|
||||
.with_tooltip::<Self>(
|
||||
option as usize,
|
||||
format!("Toggle {}", option.label()),
|
||||
Some(option.to_toggle_action()),
|
||||
@@ -853,7 +852,7 @@ impl View for ProjectSearchBar {
|
||||
"ProjectSearchBar"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
if let Some(search) = self.active_project_search.as_ref() {
|
||||
let search = search.read(cx);
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
|
||||
@@ -80,9 +80,25 @@ mod tests {
|
||||
watch_files, watched_json::watch_settings_file, EditorSettings, Settings, SoftWrap,
|
||||
};
|
||||
use fs::FakeFs;
|
||||
use gpui::{actions, Action};
|
||||
use gpui::{actions, elements::*, Action, Entity, View, ViewContext, WindowContext};
|
||||
use theme::ThemeRegistry;
|
||||
|
||||
struct TestView;
|
||||
|
||||
impl Entity for TestView {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl View for TestView {
|
||||
fn ui_name() -> &'static str {
|
||||
"TestView"
|
||||
}
|
||||
|
||||
fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
|
||||
Empty::new().boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_base_keymap(cx: &mut gpui::TestAppContext) {
|
||||
let executor = cx.background();
|
||||
@@ -148,8 +164,10 @@ mod tests {
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
let (window_id, _view) = cx.add_window(|_| TestView);
|
||||
|
||||
// Test loading the keymap base at all
|
||||
cx.update(|cx| {
|
||||
cx.read_window(window_id, |cx| {
|
||||
assert_key_bindings_for(
|
||||
cx,
|
||||
vec![("backspace", &A), ("k", &ActivatePreviousPane)],
|
||||
@@ -177,7 +195,7 @@ mod tests {
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.read_window(window_id, |cx| {
|
||||
assert_key_bindings_for(
|
||||
cx,
|
||||
vec![("backspace", &B), ("k", &ActivatePreviousPane)],
|
||||
@@ -201,7 +219,7 @@ mod tests {
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.read_window(window_id, |cx| {
|
||||
assert_key_bindings_for(
|
||||
cx,
|
||||
vec![("backspace", &B), ("[", &ActivatePrevItem)],
|
||||
@@ -211,14 +229,14 @@ mod tests {
|
||||
}
|
||||
|
||||
fn assert_key_bindings_for<'a>(
|
||||
cx: &mut AppContext,
|
||||
cx: &WindowContext,
|
||||
actions: Vec<(&'static str, &'a dyn Action)>,
|
||||
line: u32,
|
||||
) {
|
||||
for (key, action) in actions {
|
||||
// assert that...
|
||||
assert!(
|
||||
cx.available_actions(0, 0).any(|(_, bound_action, b)| {
|
||||
cx.available_actions(0).any(|(_, bound_action, b)| {
|
||||
// action names match...
|
||||
bound_action.name() == action.name()
|
||||
&& bound_action.namespace() == action.namespace()
|
||||
|
||||
@@ -735,7 +735,7 @@ mod tests {
|
||||
.map_or(5, |o| o.parse().expect("invalid OPERATIONS variable"));
|
||||
|
||||
for seed in starting_seed..(starting_seed + num_iterations) {
|
||||
dbg!(seed);
|
||||
eprintln!("seed = {}", seed);
|
||||
let mut rng = StdRng::seed_from_u64(seed);
|
||||
|
||||
let rng = &mut rng;
|
||||
|
||||
@@ -3,8 +3,8 @@ use gpui::{
|
||||
elements::*,
|
||||
impl_internal_actions,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AppContext, Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle,
|
||||
WeakModelHandle, WeakViewHandle,
|
||||
AppContext, Drawable, Element, Entity, View, ViewContext, ViewHandle, WeakModelHandle,
|
||||
WeakViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::any::TypeId;
|
||||
@@ -42,14 +42,14 @@ impl View for TerminalButton {
|
||||
"TerminalButton"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
|
||||
let workspace = self.workspace.upgrade(cx);
|
||||
let project = match workspace {
|
||||
Some(workspace) => workspace.read(cx).project().read(cx),
|
||||
None => return Empty::new().boxed(),
|
||||
};
|
||||
|
||||
let focused_view = cx.focused_view_id(cx.window_id());
|
||||
let focused_view = cx.focused_view_id();
|
||||
let active = focused_view
|
||||
.map(|view_id| {
|
||||
cx.view_type_id(cx.window_id(), view_id) == Some(TypeId::of::<TerminalView>())
|
||||
@@ -62,7 +62,7 @@ impl View for TerminalButton {
|
||||
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<Self>::new(0, cx, {
|
||||
MouseEventHandler::<Self, _>::new(0, cx, {
|
||||
let theme = theme.clone();
|
||||
move |state, _cx| {
|
||||
let style = theme
|
||||
@@ -96,7 +96,7 @@ impl View for TerminalButton {
|
||||
}
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
if has_terminals {
|
||||
cx.dispatch_action(DeployTerminalMenu);
|
||||
} else {
|
||||
@@ -105,7 +105,7 @@ impl View for TerminalButton {
|
||||
}
|
||||
};
|
||||
})
|
||||
.with_tooltip::<Self, _>(
|
||||
.with_tooltip::<Self>(
|
||||
0,
|
||||
"Show Terminal".into(),
|
||||
Some(Box::new(FocusDock)),
|
||||
|
||||
@@ -10,8 +10,8 @@ use gpui::{
|
||||
platform::{CursorStyle, MouseButton},
|
||||
serde_json::json,
|
||||
text_layout::{Line, RunStyle},
|
||||
Element, ElementBox, EventContext, FontCache, ModelContext, MouseRegion, PaintContext, Quad,
|
||||
SizeConstraint, TextLayoutCache, WeakModelHandle, WeakViewHandle,
|
||||
Drawable, Element, EventContext, FontCache, ModelContext, MouseRegion, Quad, SceneBuilder,
|
||||
SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::CursorShape;
|
||||
@@ -45,7 +45,7 @@ pub struct LayoutState {
|
||||
size: TerminalSize,
|
||||
mode: TermMode,
|
||||
display_offset: usize,
|
||||
hyperlink_tooltip: Option<ElementBox>,
|
||||
hyperlink_tooltip: Option<Element<TerminalView>>,
|
||||
}
|
||||
|
||||
///Helper struct for converting data between alacritty's cursor points, and displayed cursor points
|
||||
@@ -84,10 +84,12 @@ impl LayoutCell {
|
||||
|
||||
fn paint(
|
||||
&self,
|
||||
scene: &mut SceneBuilder,
|
||||
origin: Vector2F,
|
||||
layout: &LayoutState,
|
||||
visible_bounds: RectF,
|
||||
cx: &mut PaintContext,
|
||||
_view: &mut TerminalView,
|
||||
cx: &mut ViewContext<TerminalView>,
|
||||
) {
|
||||
let pos = {
|
||||
let point = self.point;
|
||||
@@ -98,7 +100,7 @@ impl LayoutCell {
|
||||
};
|
||||
|
||||
self.text
|
||||
.paint(pos, visible_bounds, layout.size.line_height, cx);
|
||||
.paint(scene, pos, visible_bounds, layout.size.line_height, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +128,14 @@ impl LayoutRect {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&self, origin: Vector2F, layout: &LayoutState, cx: &mut PaintContext) {
|
||||
fn paint(
|
||||
&self,
|
||||
scene: &mut SceneBuilder,
|
||||
origin: Vector2F,
|
||||
layout: &LayoutState,
|
||||
_view: &mut TerminalView,
|
||||
_cx: &mut ViewContext<TerminalView>,
|
||||
) {
|
||||
let position = {
|
||||
let point = self.point;
|
||||
vec2f(
|
||||
@@ -139,7 +148,7 @@ impl LayoutRect {
|
||||
layout.size.line_height,
|
||||
);
|
||||
|
||||
cx.scene.push_quad(Quad {
|
||||
scene.push_quad(Quad {
|
||||
bounds: RectF::new(position, size),
|
||||
background: Some(self.color),
|
||||
border: Default::default(),
|
||||
@@ -152,20 +161,17 @@ impl LayoutRect {
|
||||
///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
|
||||
pub struct TerminalElement {
|
||||
terminal: WeakModelHandle<Terminal>,
|
||||
view: WeakViewHandle<TerminalView>,
|
||||
focused: bool,
|
||||
cursor_visible: bool,
|
||||
}
|
||||
|
||||
impl TerminalElement {
|
||||
pub fn new(
|
||||
view: WeakViewHandle<TerminalView>,
|
||||
terminal: WeakModelHandle<Terminal>,
|
||||
focused: bool,
|
||||
cursor_visible: bool,
|
||||
) -> TerminalElement {
|
||||
TerminalElement {
|
||||
view,
|
||||
terminal,
|
||||
focused,
|
||||
cursor_visible,
|
||||
@@ -361,11 +367,11 @@ impl TerminalElement {
|
||||
connection: WeakModelHandle<Terminal>,
|
||||
origin: Vector2F,
|
||||
f: impl Fn(&mut Terminal, Vector2F, E, &mut ModelContext<Terminal>),
|
||||
) -> impl Fn(E, &mut EventContext) {
|
||||
move |event, cx| {
|
||||
) -> impl Fn(E, &mut TerminalView, &mut EventContext<TerminalView>) {
|
||||
move |event, _: &mut TerminalView, cx| {
|
||||
cx.focus_parent_view();
|
||||
if let Some(conn_handle) = connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, cx| {
|
||||
if let Some(conn_handle) = connection.upgrade(cx) {
|
||||
conn_handle.update(cx, |terminal, cx| {
|
||||
f(terminal, origin, event, cx);
|
||||
|
||||
cx.notify();
|
||||
@@ -376,15 +382,15 @@ impl TerminalElement {
|
||||
|
||||
fn attach_mouse_handlers(
|
||||
&self,
|
||||
scene: &mut SceneBuilder,
|
||||
origin: Vector2F,
|
||||
view_id: usize,
|
||||
visible_bounds: RectF,
|
||||
mode: TermMode,
|
||||
cx: &mut PaintContext,
|
||||
cx: &mut ViewContext<TerminalView>,
|
||||
) {
|
||||
let connection = self.terminal;
|
||||
|
||||
let mut region = MouseRegion::new::<Self>(view_id, 0, visible_bounds);
|
||||
let mut region = MouseRegion::new::<Self>(cx.view_id(), 0, visible_bounds);
|
||||
|
||||
// Terminal Emulator controlled behavior:
|
||||
region = region
|
||||
@@ -400,10 +406,10 @@ impl TerminalElement {
|
||||
),
|
||||
)
|
||||
// Update drag selections
|
||||
.on_drag(MouseButton::Left, move |event, cx| {
|
||||
.on_drag(MouseButton::Left, move |event, _: &mut TerminalView, cx| {
|
||||
if cx.is_parent_view_focused() {
|
||||
if let Some(conn_handle) = connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, cx| {
|
||||
if let Some(conn_handle) = connection.upgrade(cx) {
|
||||
conn_handle.update(cx, |terminal, cx| {
|
||||
terminal.mouse_drag(event, origin);
|
||||
cx.notify();
|
||||
})
|
||||
@@ -422,9 +428,9 @@ impl TerminalElement {
|
||||
),
|
||||
)
|
||||
// Context menu
|
||||
.on_click(MouseButton::Right, move |e, cx| {
|
||||
let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift))
|
||||
.on_click(MouseButton::Right, move |e, _: &mut TerminalView, cx| {
|
||||
let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx) {
|
||||
conn_handle.update(cx, |terminal, _cx| terminal.mouse_mode(e.shift))
|
||||
} else {
|
||||
// If we can't get the model handle, probably can't deploy the context menu
|
||||
true
|
||||
@@ -435,20 +441,19 @@ impl TerminalElement {
|
||||
});
|
||||
}
|
||||
})
|
||||
.on_move(move |event, cx| {
|
||||
.on_move(move |event, _: &mut TerminalView, cx| {
|
||||
if cx.is_parent_view_focused() {
|
||||
if let Some(conn_handle) = connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, cx| {
|
||||
if let Some(conn_handle) = connection.upgrade(cx) {
|
||||
conn_handle.update(cx, |terminal, cx| {
|
||||
terminal.mouse_move(&event, origin);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_scroll(move |event, cx| {
|
||||
// cx.focus_parent_view();
|
||||
if let Some(conn_handle) = connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, cx| {
|
||||
.on_scroll(move |event, _: &mut TerminalView, cx| {
|
||||
if let Some(conn_handle) = connection.upgrade(cx) {
|
||||
conn_handle.update(cx, |terminal, cx| {
|
||||
terminal.scroll_wheel(event, origin);
|
||||
cx.notify();
|
||||
})
|
||||
@@ -501,7 +506,7 @@ impl TerminalElement {
|
||||
)
|
||||
}
|
||||
|
||||
cx.scene.push_mouse_region(region);
|
||||
scene.push_mouse_region(region);
|
||||
}
|
||||
|
||||
///Configures a text style from the current settings.
|
||||
@@ -546,14 +551,15 @@ impl TerminalElement {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for TerminalElement {
|
||||
impl Drawable<TerminalView> for TerminalElement {
|
||||
type LayoutState = LayoutState;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: gpui::SizeConstraint,
|
||||
cx: &mut gpui::LayoutContext,
|
||||
view: &mut TerminalView,
|
||||
cx: &mut ViewContext<TerminalView>,
|
||||
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
|
||||
let settings = cx.global::<Settings>();
|
||||
let font_cache = cx.font_cache();
|
||||
@@ -581,34 +587,31 @@ impl Element for TerminalElement {
|
||||
let background_color = terminal_theme.background;
|
||||
let terminal_handle = self.terminal.upgrade(cx).unwrap();
|
||||
|
||||
let last_hovered_hyperlink = terminal_handle.update(cx.app, |terminal, cx| {
|
||||
let last_hovered_hyperlink = terminal_handle.update(cx, |terminal, cx| {
|
||||
terminal.set_size(dimensions);
|
||||
terminal.try_sync(cx);
|
||||
terminal.last_content.last_hovered_hyperlink.clone()
|
||||
});
|
||||
|
||||
let view_handle = self.view.clone();
|
||||
let hyperlink_tooltip = last_hovered_hyperlink.and_then(|(uri, _, id)| {
|
||||
// last_mouse.and_then(|_last_mouse| {
|
||||
view_handle.upgrade(cx).map(|handle| {
|
||||
let mut tooltip = cx.render(&handle, |_, cx| {
|
||||
Overlay::new(
|
||||
Empty::new()
|
||||
.contained()
|
||||
.constrained()
|
||||
.with_width(dimensions.width())
|
||||
.with_height(dimensions.height())
|
||||
.with_tooltip::<TerminalElement, _>(id, uri, None, tooltip_style, cx)
|
||||
.boxed(),
|
||||
)
|
||||
.with_position_mode(gpui::elements::OverlayPositionMode::Local)
|
||||
.boxed()
|
||||
});
|
||||
let hyperlink_tooltip = last_hovered_hyperlink.map(|(uri, _, id)| {
|
||||
let mut tooltip = Overlay::new(
|
||||
Empty::new()
|
||||
.contained()
|
||||
.constrained()
|
||||
.with_width(dimensions.width())
|
||||
.with_height(dimensions.height())
|
||||
.with_tooltip::<TerminalElement>(id, uri, None, tooltip_style, cx)
|
||||
.boxed(),
|
||||
)
|
||||
.with_position_mode(gpui::elements::OverlayPositionMode::Local)
|
||||
.boxed();
|
||||
|
||||
tooltip.layout(SizeConstraint::new(Vector2F::zero(), cx.window_size), cx);
|
||||
tooltip
|
||||
})
|
||||
// })
|
||||
tooltip.layout(
|
||||
SizeConstraint::new(Vector2F::zero(), cx.window_size()),
|
||||
view,
|
||||
cx,
|
||||
);
|
||||
tooltip
|
||||
});
|
||||
|
||||
let TerminalContent {
|
||||
@@ -637,7 +640,7 @@ impl Element for TerminalElement {
|
||||
cells,
|
||||
&text_style,
|
||||
&terminal_theme,
|
||||
cx.text_layout_cache,
|
||||
cx.text_layout_cache(),
|
||||
cx.font_cache(),
|
||||
last_hovered_hyperlink
|
||||
.as_ref()
|
||||
@@ -659,7 +662,7 @@ impl Element for TerminalElement {
|
||||
terminal_theme.foreground
|
||||
};
|
||||
|
||||
cx.text_layout_cache.layout_str(
|
||||
cx.text_layout_cache().layout_str(
|
||||
&str_trxt,
|
||||
text_style.font_size,
|
||||
&[(
|
||||
@@ -717,23 +720,25 @@ impl Element for TerminalElement {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: gpui::geometry::rect::RectF,
|
||||
visible_bounds: gpui::geometry::rect::RectF,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
cx: &mut gpui::PaintContext,
|
||||
view: &mut TerminalView,
|
||||
cx: &mut ViewContext<TerminalView>,
|
||||
) -> Self::PaintState {
|
||||
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
|
||||
|
||||
//Setup element stuff
|
||||
let clip_bounds = Some(visible_bounds);
|
||||
|
||||
cx.paint_layer(clip_bounds, |cx| {
|
||||
scene.paint_layer(clip_bounds, |scene| {
|
||||
let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
|
||||
|
||||
// Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
|
||||
self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, layout.mode, cx);
|
||||
self.attach_mouse_handlers(scene, origin, visible_bounds, layout.mode, cx);
|
||||
|
||||
cx.scene.push_cursor_region(gpui::CursorRegion {
|
||||
scene.push_cursor_region(gpui::CursorRegion {
|
||||
bounds,
|
||||
style: if layout.hyperlink_tooltip.is_some() {
|
||||
CursorStyle::PointingHand
|
||||
@@ -742,9 +747,9 @@ impl Element for TerminalElement {
|
||||
},
|
||||
});
|
||||
|
||||
cx.paint_layer(clip_bounds, |cx| {
|
||||
scene.paint_layer(clip_bounds, |scene| {
|
||||
//Start with a background color
|
||||
cx.scene.push_quad(Quad {
|
||||
scene.push_quad(Quad {
|
||||
bounds: RectF::new(bounds.origin(), bounds.size()),
|
||||
background: Some(layout.background_color),
|
||||
border: Default::default(),
|
||||
@@ -752,12 +757,12 @@ impl Element for TerminalElement {
|
||||
});
|
||||
|
||||
for rect in &layout.rects {
|
||||
rect.paint(origin, layout, cx)
|
||||
rect.paint(scene, origin, layout, view, cx)
|
||||
}
|
||||
});
|
||||
|
||||
//Draw Highlighted Backgrounds
|
||||
cx.paint_layer(clip_bounds, |cx| {
|
||||
scene.paint_layer(clip_bounds, |scene| {
|
||||
for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter()
|
||||
{
|
||||
if let Some((start_y, highlighted_range_lines)) =
|
||||
@@ -771,29 +776,29 @@ impl Element for TerminalElement {
|
||||
//Copied from editor. TODO: move to theme or something
|
||||
corner_radius: 0.15 * layout.size.line_height,
|
||||
};
|
||||
hr.paint(bounds, cx.scene);
|
||||
hr.paint(bounds, scene);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Draw the text cells
|
||||
cx.paint_layer(clip_bounds, |cx| {
|
||||
scene.paint_layer(clip_bounds, |scene| {
|
||||
for cell in &layout.cells {
|
||||
cell.paint(origin, layout, visible_bounds, cx);
|
||||
cell.paint(scene, origin, layout, visible_bounds, view, cx);
|
||||
}
|
||||
});
|
||||
|
||||
//Draw cursor
|
||||
if self.cursor_visible {
|
||||
if let Some(cursor) = &layout.cursor {
|
||||
cx.paint_layer(clip_bounds, |cx| {
|
||||
cursor.paint(origin, cx);
|
||||
scene.paint_layer(clip_bounds, |scene| {
|
||||
cursor.paint(scene, origin, cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(element) = &mut layout.hyperlink_tooltip {
|
||||
element.paint(origin, visible_bounds, cx)
|
||||
element.paint(scene, origin, visible_bounds, view, cx)
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -804,10 +809,11 @@ impl Element for TerminalElement {
|
||||
|
||||
fn debug(
|
||||
&self,
|
||||
_bounds: gpui::geometry::rect::RectF,
|
||||
_layout: &Self::LayoutState,
|
||||
_paint: &Self::PaintState,
|
||||
_cx: &gpui::DebugContext,
|
||||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &TerminalView,
|
||||
_: &gpui::ViewContext<TerminalView>,
|
||||
) -> gpui::serde_json::Value {
|
||||
json!({
|
||||
"type": "TerminalElement",
|
||||
@@ -821,7 +827,8 @@ impl Element for TerminalElement {
|
||||
_: RectF,
|
||||
layout: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &gpui::MeasurementContext,
|
||||
_: &TerminalView,
|
||||
_: &gpui::ViewContext<TerminalView>,
|
||||
) -> Option<RectF> {
|
||||
// Use the same origin that's passed to `Cursor::paint` in the paint
|
||||
// method bove.
|
||||
|
||||
@@ -13,12 +13,12 @@ use context_menu::{ContextMenu, ContextMenuItem};
|
||||
use dirs::home_dir;
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::{AnchorCorner, ChildView, Flex, Label, ParentElement, Stack, Text},
|
||||
elements::{AnchorCorner, ChildView, Flex, Label, ParentElement, Stack},
|
||||
geometry::vector::Vector2F,
|
||||
impl_actions, impl_internal_actions,
|
||||
keymap_matcher::{KeymapContext, Keystroke},
|
||||
platform::KeyDownEvent,
|
||||
AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, Task, View, ViewContext,
|
||||
AnyViewHandle, AppContext, Drawable, Element, Entity, ModelHandle, Task, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use project::{LocalWorktree, Project};
|
||||
@@ -35,7 +35,7 @@ use terminal::{
|
||||
};
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
item::{Item, ItemEvent},
|
||||
item::{BreadcrumbText, Item, ItemEvent},
|
||||
notifications::NotifyResultExt,
|
||||
pane, register_deserializable_item,
|
||||
searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle},
|
||||
@@ -237,11 +237,7 @@ impl TerminalView {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn should_show_cursor(
|
||||
&self,
|
||||
focused: bool,
|
||||
cx: &mut gpui::RenderContext<'_, Self>,
|
||||
) -> bool {
|
||||
pub fn should_show_cursor(&self, focused: bool, cx: &mut gpui::ViewContext<Self>) -> bool {
|
||||
//Don't blink the cursor when not focused, blinking is disabled, or paused
|
||||
if !focused
|
||||
|| !self.blinking_on
|
||||
@@ -284,7 +280,8 @@ impl TerminalView {
|
||||
async move {
|
||||
Timer::after(CURSOR_BLINK_INTERVAL).await;
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx));
|
||||
this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx))
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -303,6 +300,7 @@ impl TerminalView {
|
||||
Timer::after(CURSOR_BLINK_INTERVAL).await;
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -389,19 +387,18 @@ impl View for TerminalView {
|
||||
"Terminal"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Element<Self> {
|
||||
let terminal_handle = self.terminal.clone().downgrade();
|
||||
|
||||
let self_id = cx.view_id();
|
||||
let focused = cx
|
||||
.focused_view_id(cx.window_id())
|
||||
.focused_view_id()
|
||||
.filter(|view_id| *view_id == self_id)
|
||||
.is_some();
|
||||
|
||||
Stack::new()
|
||||
.with_child(
|
||||
TerminalElement::new(
|
||||
cx.handle(),
|
||||
terminal_handle,
|
||||
focused,
|
||||
self.should_show_cursor(focused, cx),
|
||||
@@ -548,12 +545,12 @@ impl Item for TerminalView {
|
||||
Some(self.terminal().read(cx).title().into())
|
||||
}
|
||||
|
||||
fn tab_content(
|
||||
fn tab_content<T: View>(
|
||||
&self,
|
||||
_detail: Option<usize>,
|
||||
tab_theme: &theme::Tab,
|
||||
cx: &gpui::AppContext,
|
||||
) -> ElementBox {
|
||||
) -> Element<T> {
|
||||
let title = self.terminal().read(cx).title();
|
||||
|
||||
Flex::row()
|
||||
@@ -615,12 +612,11 @@ impl Item for TerminalView {
|
||||
ToolbarItemLocation::PrimaryLeft { flex: None }
|
||||
}
|
||||
|
||||
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
|
||||
Some(vec![Text::new(
|
||||
self.terminal().read(cx).breadcrumb_text.clone(),
|
||||
theme.workspace.breadcrumbs.default.text.clone(),
|
||||
)
|
||||
.boxed()])
|
||||
fn breadcrumbs(&self, _: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
|
||||
Some(vec![BreadcrumbText {
|
||||
text: self.terminal().read(cx).breadcrumb_text.clone(),
|
||||
highlights: None,
|
||||
}])
|
||||
}
|
||||
|
||||
fn serialized_item_kind() -> Option<&'static str> {
|
||||
|
||||
+35
-37
@@ -11,7 +11,7 @@ use gpui::{
|
||||
platform,
|
||||
platform::MouseButton,
|
||||
scene::MouseClick,
|
||||
Action, Element, ElementBox, EventContext, MouseState, RenderContext, View,
|
||||
Action, Drawable, Element, EventContext, MouseState, View, ViewContext,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -27,13 +27,13 @@ pub struct CheckboxStyle {
|
||||
pub hovered_and_checked: ContainerStyle,
|
||||
}
|
||||
|
||||
pub fn checkbox<T: 'static, V: View>(
|
||||
pub fn checkbox<Tag: 'static, V: View>(
|
||||
label: &'static str,
|
||||
style: &CheckboxStyle,
|
||||
checked: bool,
|
||||
cx: &mut RenderContext<V>,
|
||||
change: fn(checked: bool, cx: &mut EventContext) -> (),
|
||||
) -> MouseEventHandler<T> {
|
||||
cx: &mut ViewContext<V>,
|
||||
change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
|
||||
) -> MouseEventHandler<Tag, V> {
|
||||
let label = Label::new(label, style.label.text.clone())
|
||||
.contained()
|
||||
.with_style(style.label.container)
|
||||
@@ -42,14 +42,14 @@ pub fn checkbox<T: 'static, V: View>(
|
||||
checkbox_with_label(label, style, checked, cx, change)
|
||||
}
|
||||
|
||||
pub fn checkbox_with_label<T: 'static, V: View>(
|
||||
label: ElementBox,
|
||||
pub fn checkbox_with_label<Tag: 'static, V: View>(
|
||||
label: Element<V>,
|
||||
style: &CheckboxStyle,
|
||||
checked: bool,
|
||||
cx: &mut RenderContext<V>,
|
||||
change: fn(checked: bool, cx: &mut EventContext) -> (),
|
||||
) -> MouseEventHandler<T> {
|
||||
MouseEventHandler::<T>::new(0, cx, |state, _| {
|
||||
cx: &mut ViewContext<V>,
|
||||
change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
|
||||
) -> MouseEventHandler<Tag, V> {
|
||||
MouseEventHandler::new(0, cx, |state, _| {
|
||||
let indicator = if checked {
|
||||
svg(&style.icon)
|
||||
} else {
|
||||
@@ -82,7 +82,7 @@ pub fn checkbox_with_label<T: 'static, V: View>(
|
||||
.align_children_center()
|
||||
.boxed()
|
||||
})
|
||||
.on_click(platform::MouseButton::Left, move |_, cx| {
|
||||
.on_click(platform::MouseButton::Left, move |_, _, cx| {
|
||||
change(!checked, cx)
|
||||
})
|
||||
.with_cursor_style(platform::CursorStyle::PointingHand)
|
||||
@@ -107,7 +107,7 @@ impl Dimensions {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn svg(style: &SvgStyle) -> ConstrainedBox {
|
||||
pub fn svg<V: View>(style: &SvgStyle) -> ConstrainedBox<V> {
|
||||
Svg::new(style.asset.clone())
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
@@ -121,7 +121,7 @@ pub struct IconStyle {
|
||||
container: ContainerStyle,
|
||||
}
|
||||
|
||||
pub fn icon(style: &IconStyle) -> Container {
|
||||
pub fn icon<V: View>(style: &IconStyle) -> Container<V> {
|
||||
svg(&style.icon).contained().with_style(style.container)
|
||||
}
|
||||
|
||||
@@ -130,12 +130,11 @@ pub fn keystroke_label<V: View>(
|
||||
label_style: &ContainedText,
|
||||
keystroke_style: &ContainedText,
|
||||
action: Box<dyn Action>,
|
||||
cx: &mut RenderContext<V>,
|
||||
) -> Container {
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Container<V> {
|
||||
// FIXME: Put the theme in it's own global so we can
|
||||
// query the keystroke style on our own
|
||||
keystroke_label_for(
|
||||
cx.window_id(),
|
||||
cx.handle().id(),
|
||||
label_text,
|
||||
label_style,
|
||||
@@ -144,14 +143,13 @@ pub fn keystroke_label<V: View>(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn keystroke_label_for(
|
||||
window_id: usize,
|
||||
pub fn keystroke_label_for<V: View>(
|
||||
view_id: usize,
|
||||
label_text: &'static str,
|
||||
label_style: &ContainedText,
|
||||
keystroke_style: &ContainedText,
|
||||
action: Box<dyn Action>,
|
||||
) -> Container {
|
||||
) -> Container<V> {
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Label::new(label_text, label_style.text.clone())
|
||||
@@ -160,7 +158,6 @@ pub fn keystroke_label_for(
|
||||
)
|
||||
.with_child({
|
||||
KeystrokeLabel::new(
|
||||
window_id,
|
||||
view_id,
|
||||
action,
|
||||
keystroke_style.container,
|
||||
@@ -180,32 +177,33 @@ pub fn cta_button<L, A, V>(
|
||||
action: A,
|
||||
max_width: f32,
|
||||
style: &ButtonStyle,
|
||||
cx: &mut RenderContext<V>,
|
||||
) -> ElementBox
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Element<V>
|
||||
where
|
||||
L: Into<Cow<'static, str>>,
|
||||
A: 'static + Action + Clone,
|
||||
V: View,
|
||||
{
|
||||
cta_button_with_click(label, max_width, style, cx, move |_, cx| {
|
||||
cta_button_with_click::<A, _, _, _>(label, max_width, style, cx, move |_, _, cx| {
|
||||
cx.dispatch_action(action.clone())
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
pub fn cta_button_with_click<L, V, F>(
|
||||
pub fn cta_button_with_click<Tag, L, V, F>(
|
||||
label: L,
|
||||
max_width: f32,
|
||||
style: &ButtonStyle,
|
||||
cx: &mut RenderContext<V>,
|
||||
cx: &mut ViewContext<V>,
|
||||
f: F,
|
||||
) -> MouseEventHandler<F>
|
||||
) -> MouseEventHandler<Tag, V>
|
||||
where
|
||||
Tag: 'static,
|
||||
L: Into<Cow<'static, str>>,
|
||||
V: View,
|
||||
F: Fn(MouseClick, &mut EventContext) + 'static,
|
||||
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
MouseEventHandler::<F>::new(0, cx, |state, _| {
|
||||
MouseEventHandler::<Tag, V>::new(0, cx, |state, _| {
|
||||
let style = style.style_for(state, false);
|
||||
Label::new(label, style.text.to_owned())
|
||||
.aligned()
|
||||
@@ -234,16 +232,17 @@ impl ModalStyle {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modal<V, I, F>(
|
||||
pub fn modal<Tag, V, I, F>(
|
||||
title: I,
|
||||
style: &ModalStyle,
|
||||
cx: &mut RenderContext<V>,
|
||||
cx: &mut ViewContext<V>,
|
||||
build_modal: F,
|
||||
) -> ElementBox
|
||||
) -> Element<V>
|
||||
where
|
||||
Tag: 'static,
|
||||
V: View,
|
||||
I: Into<Cow<'static, str>>,
|
||||
F: FnOnce(&mut gpui::RenderContext<V>) -> ElementBox,
|
||||
F: FnOnce(&mut gpui::ViewContext<V>) -> Element<V>,
|
||||
{
|
||||
const TITLEBAR_HEIGHT: f32 = 28.;
|
||||
// let active = cx.window_is_active(cx.window_id());
|
||||
@@ -261,13 +260,12 @@ where
|
||||
)
|
||||
.boxed(),
|
||||
// FIXME: Get a better tag type
|
||||
MouseEventHandler::<V>::new(999999, cx, |state, _cx| {
|
||||
MouseEventHandler::<Tag, V>::new(999999, cx, |state, _cx| {
|
||||
let style = style.close_icon.style_for(state, false);
|
||||
icon(style).boxed()
|
||||
})
|
||||
.on_click(platform::MouseButton::Left, move |_, cx| {
|
||||
let window_id = cx.window_id();
|
||||
cx.remove_window(window_id);
|
||||
.on_click(platform::MouseButton::Left, move |_, _, cx| {
|
||||
cx.remove_window();
|
||||
})
|
||||
.with_cursor_style(platform::CursorStyle::PointingHand)
|
||||
.aligned()
|
||||
|
||||
@@ -1,45 +1,57 @@
|
||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, elements::*, AnyViewHandle, AppContext, Element, ElementBox, Entity, MouseState,
|
||||
RenderContext, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use gpui::{actions, elements::*, AppContext, Drawable, Element, MouseState, ViewContext};
|
||||
use picker::{Picker, PickerDelegate, PickerEvent};
|
||||
use settings::{settings_file::SettingsFile, Settings};
|
||||
use staff_mode::StaffMode;
|
||||
use std::sync::Arc;
|
||||
use theme::{Theme, ThemeMeta, ThemeRegistry};
|
||||
use util::ResultExt;
|
||||
use workspace::{AppState, Workspace};
|
||||
|
||||
pub struct ThemeSelector {
|
||||
registry: Arc<ThemeRegistry>,
|
||||
theme_data: Vec<ThemeMeta>,
|
||||
matches: Vec<StringMatch>,
|
||||
original_theme: Arc<Theme>,
|
||||
picker: ViewHandle<Picker<Self>>,
|
||||
selection_completed: bool,
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
actions!(theme_selector, [Toggle, Reload]);
|
||||
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
Picker::<ThemeSelector>::init(cx);
|
||||
cx.add_action({
|
||||
let theme_registry = app_state.themes.clone();
|
||||
move |workspace, _: &Toggle, cx| {
|
||||
ThemeSelector::toggle(workspace, theme_registry.clone(), cx)
|
||||
}
|
||||
move |workspace, _: &Toggle, cx| toggle(workspace, theme_registry.clone(), cx)
|
||||
});
|
||||
ThemeSelector::init(cx);
|
||||
}
|
||||
|
||||
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(¤t_theme_name) {
|
||||
Ok(theme) => {
|
||||
ThemeSelectorDelegate::set_theme(theme, cx);
|
||||
log::info!("reloaded theme {}", current_theme_name);
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!("failed to load theme {}: {:?}", current_theme_name, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(¤t_theme_name) {
|
||||
Ok(theme) => {
|
||||
Self::set_theme(theme, cx);
|
||||
log::info!("reloaded theme {}", current_theme_name);
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!("failed to load theme {}: {:?}", current_theme_name, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_selected_theme(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn show_selected_theme(&mut self, cx: &mut ViewContext<ThemeSelector>) {
|
||||
if let Some(mat) = self.matches.get(self.selected_index) {
|
||||
match self.registry.get(&mat.string) {
|
||||
Ok(theme) => {
|
||||
@@ -118,19 +102,6 @@ impl ThemeSelector {
|
||||
.unwrap_or(self.selected_index);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
workspace: &mut Workspace,
|
||||
_: ViewHandle<ThemeSelector>,
|
||||
event: &Event,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
match event {
|
||||
Event::Dismissed => {
|
||||
workspace.dismiss_modal(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_theme(theme: Arc<Theme>, cx: &mut AppContext) {
|
||||
cx.update_global::<Settings, _, _>(|settings, cx| {
|
||||
settings.theme = theme;
|
||||
@@ -139,12 +110,16 @@ impl ThemeSelector {
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for ThemeSelector {
|
||||
impl PickerDelegate for ThemeSelectorDelegate {
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
"Select Theme...".into()
|
||||
}
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
self.matches.len()
|
||||
}
|
||||
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn confirm(&mut self, cx: &mut ViewContext<ThemeSelector>) {
|
||||
self.selection_completed = true;
|
||||
|
||||
let theme_name = cx.global::<Settings>().theme.meta.name.clone();
|
||||
@@ -152,27 +127,30 @@ impl PickerDelegate for ThemeSelector {
|
||||
settings_content.theme = Some(theme_name);
|
||||
});
|
||||
|
||||
cx.emit(Event::Dismissed);
|
||||
cx.emit(PickerEvent::Dismiss);
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<ThemeSelector>) {
|
||||
if !self.selection_completed {
|
||||
Self::set_theme(self.original_theme.clone(), cx);
|
||||
self.selection_completed = true;
|
||||
}
|
||||
cx.emit(Event::Dismissed);
|
||||
}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<ThemeSelector>) {
|
||||
self.selected_index = ix;
|
||||
self.show_selected_theme(cx);
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
cx: &mut ViewContext<ThemeSelector>,
|
||||
) -> gpui::Task<()> {
|
||||
let background = cx.background().clone();
|
||||
let candidates = self
|
||||
.theme_data
|
||||
@@ -185,7 +163,7 @@ impl PickerDelegate for ThemeSelector {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
let matches = if query.is_empty() {
|
||||
candidates
|
||||
.into_iter()
|
||||
@@ -209,14 +187,17 @@ impl PickerDelegate for ThemeSelector {
|
||||
.await
|
||||
};
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.matches = matches;
|
||||
this.selected_index = this
|
||||
.selected_index
|
||||
.min(this.matches.len().saturating_sub(1));
|
||||
this.show_selected_theme(cx);
|
||||
cx.notify();
|
||||
});
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let delegate = this.delegate_mut();
|
||||
delegate.matches = matches;
|
||||
delegate.selected_index = delegate
|
||||
.selected_index
|
||||
.min(delegate.matches.len().saturating_sub(1));
|
||||
delegate.show_selected_theme(cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -226,7 +207,7 @@ impl PickerDelegate for ThemeSelector {
|
||||
mouse_state: &mut MouseState,
|
||||
selected: bool,
|
||||
cx: &AppContext,
|
||||
) -> ElementBox {
|
||||
) -> Element<Picker<Self>> {
|
||||
let settings = cx.global::<Settings>();
|
||||
let theme = &settings.theme;
|
||||
let theme_match = &self.matches[ix];
|
||||
@@ -239,29 +220,3 @@ impl PickerDelegate for ThemeSelector {
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for ThemeSelector {
|
||||
type Event = Event;
|
||||
|
||||
fn release(&mut self, cx: &mut AppContext) {
|
||||
if !self.selection_completed {
|
||||
Self::set_theme(self.original_theme.clone(), cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl View for ThemeSelector {
|
||||
fn ui_name() -> &'static str {
|
||||
"ThemeSelector"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
ChildView::new(&self.picker, cx).boxed()
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
if cx.is_self_focused() {
|
||||
cx.focus(&self.picker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user