Popover buttons titlebar (#3732)

Migrate project picker/vcs menu to use popover_menu.

Release Notes:
- N/A
This commit is contained in:
Piotr Osiewicz 2023-12-20 12:47:53 +01:00 committed by GitHub
parent ed5a9eb552
commit 2f57a3ccfb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 106 additions and 99 deletions

View file

@ -3,10 +3,10 @@ use auto_update::AutoUpdateStatus;
use call::{ActiveCall, ParticipantLocation, Room}; use call::{ActiveCall, ParticipantLocation, Room};
use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore}; use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore};
use gpui::{ use gpui::{
actions, canvas, div, overlay, point, px, rems, Action, AnyElement, AppContext, DismissEvent, actions, canvas, div, point, px, rems, Action, AnyElement, AppContext, Div, Element, Hsla,
Div, Element, FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, InteractiveElement, IntoElement, Model, ParentElement, Path, Render, Stateful,
Render, Stateful, StatefulInteractiveElement, Styled, Subscription, View, ViewContext, StatefulInteractiveElement, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
VisualContext, WeakView, WindowBounds, WindowBounds,
}; };
use project::{Project, RepositoryEntry}; use project::{Project, RepositoryEntry};
use recent_projects::RecentProjects; use recent_projects::RecentProjects;
@ -52,8 +52,8 @@ pub struct CollabTitlebarItem {
user_store: Model<UserStore>, user_store: Model<UserStore>,
client: Arc<Client>, client: Arc<Client>,
workspace: WeakView<Workspace>, workspace: WeakView<Workspace>,
branch_popover: Option<View<BranchList>>, branch_popover: Option<(View<BranchList>, Subscription)>,
project_popover: Option<recent_projects::RecentProjects>, project_popover: Option<(View<recent_projects::RecentProjects>, Subscription)>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@ -329,9 +329,8 @@ impl CollabTitlebarItem {
}; };
let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH); let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
popover_menu("project_name_trigger")
div() .trigger(
.child(
Button::new("project_name_trigger", name) Button::new("project_name_trigger", name)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text("Recent Projects", cx)) .tooltip(move |cx| Tooltip::text("Recent Projects", cx))
@ -339,16 +338,12 @@ impl CollabTitlebarItem {
this.toggle_project_menu(&ToggleProjectMenu, cx); this.toggle_project_menu(&ToggleProjectMenu, cx);
})), })),
) )
.children(self.project_popover.as_ref().map(|popover| { .when_some(
overlay().child( self.project_popover
div() .as_ref()
.min_w_56() .map(|(project, _)| project.clone()),
.on_mouse_down_out(cx.listener_for(&popover.picker, |picker, _, cx| { |this, project| this.menu(move |_| project.clone()),
picker.cancel(&Default::default(), cx) )
}))
.child(popover.picker.clone()),
)
}))
} }
pub fn render_project_branch(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> { pub fn render_project_branch(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
@ -366,10 +361,9 @@ impl CollabTitlebarItem {
.as_ref() .as_ref()
.and_then(RepositoryEntry::branch) .and_then(RepositoryEntry::branch)
.map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?; .map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?;
Some( Some(
div() popover_menu("project_branch_trigger")
.child( .trigger(
Button::new("project_branch_trigger", branch_name) Button::new("project_branch_trigger", branch_name)
.color(Color::Muted) .color(Color::Muted)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
@ -381,11 +375,16 @@ impl CollabTitlebarItem {
cx, cx,
) )
}) })
.on_click( .on_click(cx.listener(|this, _, cx| {
cx.listener(|this, _, cx| this.toggle_vcs_menu(&ToggleVcsMenu, cx)), this.toggle_vcs_menu(&ToggleVcsMenu, cx);
), })),
) )
.children(self.render_branches_popover_host()), .when_some(
self.branch_popover
.as_ref()
.map(|(branch, _)| branch.clone()),
|this, branch| this.menu(move |_| branch.clone()),
),
) )
} }
@ -461,29 +460,19 @@ impl CollabTitlebarItem {
.log_err(); .log_err();
} }
fn render_branches_popover_host<'a>(&'a self) -> Option<AnyElement> {
self.branch_popover.as_ref().map(|child| {
overlay()
.child(div().min_w_64().child(child.clone()))
.into_any()
})
}
pub fn toggle_vcs_menu(&mut self, _: &ToggleVcsMenu, cx: &mut ViewContext<Self>) { pub fn toggle_vcs_menu(&mut self, _: &ToggleVcsMenu, cx: &mut ViewContext<Self>) {
if self.branch_popover.take().is_none() { if self.branch_popover.take().is_none() {
if let Some(workspace) = self.workspace.upgrade() { if let Some(workspace) = self.workspace.upgrade() {
let Some(view) = build_branch_list(workspace, cx).log_err() else { let Some(view) = build_branch_list(workspace, cx).log_err() else {
return; return;
}; };
cx.subscribe(&view, |this, _, _, cx| {
this.branch_popover = None;
cx.notify();
})
.detach();
self.project_popover.take(); self.project_popover.take();
let focus_handle = view.focus_handle(cx); let focus_handle = view.focus_handle(cx);
cx.focus(&focus_handle); cx.focus(&focus_handle);
self.branch_popover = Some(view); let subscription = cx.subscribe(&view, |this, _, _, _| {
this.branch_popover.take();
});
self.branch_popover = Some((view, subscription));
} }
} }
@ -491,8 +480,8 @@ impl CollabTitlebarItem {
} }
pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext<Self>) { pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext<Self>) {
let workspace = self.workspace.clone();
if self.project_popover.take().is_none() { if self.project_popover.take().is_none() {
let workspace = self.workspace.clone();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let workspaces = WORKSPACE_DB let workspaces = WORKSPACE_DB
.recent_workspaces_on_disk() .recent_workspaces_on_disk()
@ -506,16 +495,13 @@ impl CollabTitlebarItem {
this.update(&mut cx, move |this, cx| { this.update(&mut cx, move |this, cx| {
let view = RecentProjects::open_popover(workspace, workspaces, cx); let view = RecentProjects::open_popover(workspace, workspaces, cx);
cx.subscribe(&view.picker, |this, _, _: &DismissEvent, cx| {
this.project_popover = None;
cx.notify();
})
.detach();
let focus_handle = view.focus_handle(cx); let focus_handle = view.focus_handle(cx);
cx.focus(&focus_handle); cx.focus(&focus_handle);
// todo!() this.branch_popover.take();
//this.branch_popover.take(); let subscription = cx.subscribe(&view, |this, _, _, _| {
this.project_popover = Some(view); this.project_popover.take();
});
this.project_popover = Some((view, subscription));
cx.notify(); cx.notify();
}) })
.log_err(); .log_err();

View file

@ -3,8 +3,8 @@ mod projects;
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Task, View, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Subscription,
ViewContext, WeakView, Task, View, ViewContext, WeakView,
}; };
use highlighted_workspace_location::HighlightedWorkspaceLocation; use highlighted_workspace_location::HighlightedWorkspaceLocation;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
@ -23,17 +23,22 @@ pub fn init(cx: &mut AppContext) {
cx.observe_new_views(RecentProjects::register).detach(); cx.observe_new_views(RecentProjects::register).detach();
} }
#[derive(Clone)]
pub struct RecentProjects { pub struct RecentProjects {
pub picker: View<Picker<RecentProjectsDelegate>>, pub picker: View<Picker<RecentProjectsDelegate>>,
rem_width: f32,
_subscription: Subscription,
} }
impl ModalView for RecentProjects {} impl ModalView for RecentProjects {}
impl RecentProjects { impl RecentProjects {
fn new(delegate: RecentProjectsDelegate, cx: &mut WindowContext<'_>) -> Self { fn new(delegate: RecentProjectsDelegate, rem_width: f32, cx: &mut ViewContext<Self>) -> Self {
let picker = cx.build_view(|cx| Picker::new(delegate, cx));
let _subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent));
Self { Self {
picker: cx.build_view(|cx| Picker::new(delegate, cx)), picker,
rem_width,
_subscription,
} }
} }
@ -76,9 +81,7 @@ impl RecentProjects {
let delegate = let delegate =
RecentProjectsDelegate::new(weak_workspace, workspace_locations, true); RecentProjectsDelegate::new(weak_workspace, workspace_locations, true);
let modal = RecentProjects::new(delegate, cx); let modal = RecentProjects::new(delegate, 34., cx);
cx.subscribe(&modal.picker, |_, _, _, cx| cx.emit(DismissEvent))
.detach();
modal modal
}); });
} else { } else {
@ -94,11 +97,14 @@ impl RecentProjects {
workspace: WeakView<Workspace>, workspace: WeakView<Workspace>,
workspaces: Vec<WorkspaceLocation>, workspaces: Vec<WorkspaceLocation>,
cx: &mut WindowContext<'_>, cx: &mut WindowContext<'_>,
) -> Self { ) -> View<Self> {
Self::new( cx.build_view(|cx| {
RecentProjectsDelegate::new(workspace, workspaces, false), Self::new(
cx, RecentProjectsDelegate::new(workspace, workspaces, false),
) 20.,
cx,
)
})
} }
} }
@ -113,8 +119,15 @@ impl FocusableView for RecentProjects {
impl Render for RecentProjects { impl Render for RecentProjects {
type Element = Div; type Element = Div;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
v_stack().w(rems(34.)).child(self.picker.clone()) v_stack()
.w(rems(self.rem_width))
.child(self.picker.clone())
.on_mouse_down_out(cx.listener(|this, _, cx| {
this.picker.update(cx, |this, cx| {
this.cancel(&Default::default(), cx);
})
}))
} }
} }

View file

@ -3,8 +3,8 @@ use fs::repository::Branch;
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, rems, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, actions, rems, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
ParentElement, Render, SharedString, Styled, Task, View, ViewContext, VisualContext, InteractiveElement, ParentElement, Render, SharedString, Styled, Subscription, Task, View,
WindowContext, ViewContext, VisualContext, WindowContext,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use std::sync::Arc; use std::sync::Arc;
@ -18,31 +18,61 @@ pub fn init(cx: &mut AppContext) {
// todo!() po // todo!() po
cx.observe_new_views(|workspace: &mut Workspace, _| { cx.observe_new_views(|workspace: &mut Workspace, _| {
workspace.register_action(|workspace, action, cx| { workspace.register_action(|workspace, action, cx| {
ModalBranchList::toggle_modal(workspace, action, cx).log_err(); BranchList::toggle_modal(workspace, action, cx).log_err();
}); });
}) })
.detach(); .detach();
} }
pub type BranchList = Picker<BranchListDelegate>;
pub struct ModalBranchList { pub struct BranchList {
pub picker: View<Picker<BranchListDelegate>>, pub picker: View<Picker<BranchListDelegate>>,
rem_width: f32,
_subscription: Subscription,
} }
impl ModalView for ModalBranchList {} impl BranchList {
impl EventEmitter<DismissEvent> for ModalBranchList {} fn new(delegate: BranchListDelegate, rem_width: f32, cx: &mut ViewContext<Self>) -> Self {
let picker = cx.build_view(|cx| Picker::new(delegate, cx));
let _subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent));
Self {
picker,
rem_width,
_subscription,
}
}
fn toggle_modal(
workspace: &mut Workspace,
_: &OpenRecent,
cx: &mut ViewContext<Workspace>,
) -> Result<()> {
// Modal branch picker has a longer trailoff than a popover one.
let delegate = BranchListDelegate::new(workspace, cx.view().clone(), 70, cx)?;
workspace.toggle_modal(cx, |cx| BranchList::new(delegate, 34., cx));
impl FocusableView for ModalBranchList { Ok(())
}
}
impl ModalView for BranchList {}
impl EventEmitter<DismissEvent> for BranchList {}
impl FocusableView for BranchList {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx) self.picker.focus_handle(cx)
} }
} }
impl Render for ModalBranchList { impl Render for BranchList {
type Element = Div; type Element = Div;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
v_stack().w(rems(34.)).child(self.picker.clone()) v_stack()
.w(rems(self.rem_width))
.child(self.picker.clone())
.on_mouse_down_out(cx.listener(|this, _, cx| {
this.picker.update(cx, |this, cx| {
this.cancel(&Default::default(), cx);
})
}))
} }
} }
@ -53,29 +83,7 @@ pub fn build_branch_list(
let delegate = workspace.update(cx, |workspace, cx| { let delegate = workspace.update(cx, |workspace, cx| {
BranchListDelegate::new(workspace, cx.view().clone(), 29, cx) BranchListDelegate::new(workspace, cx.view().clone(), 29, cx)
})?; })?;
Ok(cx.build_view(move |cx| BranchList::new(delegate, 20., cx)))
Ok(cx.build_view(|cx| Picker::new(delegate, cx)))
}
impl ModalBranchList {
fn toggle_modal(
workspace: &mut Workspace,
_: &OpenRecent,
cx: &mut ViewContext<Workspace>,
) -> Result<()> {
// Modal branch picker has a longer trailoff than a popover one.
let delegate = BranchListDelegate::new(workspace, cx.view().clone(), 70, cx)?;
workspace.toggle_modal(cx, |cx| {
let modal = ModalBranchList {
picker: cx.build_view(|cx| Picker::new(delegate, cx)),
};
cx.subscribe(&modal.picker, |_, _, _, cx| cx.emit(DismissEvent))
.detach();
modal
});
Ok(())
}
} }
pub struct BranchListDelegate { pub struct BranchListDelegate {
@ -116,7 +124,7 @@ impl BranchListDelegate {
}) })
} }
fn display_error_toast(&self, message: String, cx: &mut ViewContext<BranchList>) { fn display_error_toast(&self, message: String, cx: &mut WindowContext<'_>) {
const GIT_CHECKOUT_FAILURE_ID: usize = 2048; const GIT_CHECKOUT_FAILURE_ID: usize = 2048;
self.workspace.update(cx, |model, ctx| { self.workspace.update(cx, |model, ctx| {
model.show_toast(Toast::new(GIT_CHECKOUT_FAILURE_ID, message), ctx) model.show_toast(Toast::new(GIT_CHECKOUT_FAILURE_ID, message), ctx)