Popover buttons titlebar (#3732)
Migrate project picker/vcs menu to use popover_menu. Release Notes: - N/A
This commit is contained in:
parent
ed5a9eb552
commit
2f57a3ccfb
3 changed files with 106 additions and 99 deletions
|
@ -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();
|
||||||
|
|
|
@ -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> {
|
||||||
|
cx.build_view(|cx| {
|
||||||
Self::new(
|
Self::new(
|
||||||
RecentProjectsDelegate::new(workspace, workspaces, false),
|
RecentProjectsDelegate::new(workspace, workspaces, false),
|
||||||
|
20.,
|
||||||
cx,
|
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);
|
||||||
|
})
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue