From 2f57a3ccfb74d663588d0792d88144d03fe22977 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 20 Dec 2023 12:47:53 +0100 Subject: [PATCH] Popover buttons titlebar (#3732) Migrate project picker/vcs menu to use popover_menu. Release Notes: - N/A --- crates/collab_ui2/src/collab_titlebar_item.rs | 84 ++++++++----------- .../recent_projects2/src/recent_projects.rs | 43 ++++++---- crates/vcs_menu2/src/lib.rs | 78 +++++++++-------- 3 files changed, 106 insertions(+), 99 deletions(-) diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 17ee5087bb..9359ff41d8 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -3,10 +3,10 @@ use auto_update::AutoUpdateStatus; use call::{ActiveCall, ParticipantLocation, Room}; use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore}; use gpui::{ - actions, canvas, div, overlay, point, px, rems, Action, AnyElement, AppContext, DismissEvent, - Div, Element, FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, - Render, Stateful, StatefulInteractiveElement, Styled, Subscription, View, ViewContext, - VisualContext, WeakView, WindowBounds, + actions, canvas, div, point, px, rems, Action, AnyElement, AppContext, Div, Element, Hsla, + InteractiveElement, IntoElement, Model, ParentElement, Path, Render, Stateful, + StatefulInteractiveElement, Styled, Subscription, View, ViewContext, VisualContext, WeakView, + WindowBounds, }; use project::{Project, RepositoryEntry}; use recent_projects::RecentProjects; @@ -52,8 +52,8 @@ pub struct CollabTitlebarItem { user_store: Model, client: Arc, workspace: WeakView, - branch_popover: Option>, - project_popover: Option, + branch_popover: Option<(View, Subscription)>, + project_popover: Option<(View, Subscription)>, _subscriptions: Vec, } @@ -329,9 +329,8 @@ impl CollabTitlebarItem { }; let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH); - - div() - .child( + popover_menu("project_name_trigger") + .trigger( Button::new("project_name_trigger", name) .style(ButtonStyle::Subtle) .tooltip(move |cx| Tooltip::text("Recent Projects", cx)) @@ -339,16 +338,12 @@ impl CollabTitlebarItem { this.toggle_project_menu(&ToggleProjectMenu, cx); })), ) - .children(self.project_popover.as_ref().map(|popover| { - overlay().child( - div() - .min_w_56() - .on_mouse_down_out(cx.listener_for(&popover.picker, |picker, _, cx| { - picker.cancel(&Default::default(), cx) - })) - .child(popover.picker.clone()), - ) - })) + .when_some( + self.project_popover + .as_ref() + .map(|(project, _)| project.clone()), + |this, project| this.menu(move |_| project.clone()), + ) } pub fn render_project_branch(&self, cx: &mut ViewContext) -> Option { @@ -366,10 +361,9 @@ impl CollabTitlebarItem { .as_ref() .and_then(RepositoryEntry::branch) .map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?; - Some( - div() - .child( + popover_menu("project_branch_trigger") + .trigger( Button::new("project_branch_trigger", branch_name) .color(Color::Muted) .style(ButtonStyle::Subtle) @@ -381,11 +375,16 @@ impl CollabTitlebarItem { cx, ) }) - .on_click( - cx.listener(|this, _, cx| this.toggle_vcs_menu(&ToggleVcsMenu, cx)), - ), + .on_click(cx.listener(|this, _, 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(); } - fn render_branches_popover_host<'a>(&'a self) -> Option { - 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) { if self.branch_popover.take().is_none() { if let Some(workspace) = self.workspace.upgrade() { let Some(view) = build_branch_list(workspace, cx).log_err() else { return; }; - cx.subscribe(&view, |this, _, _, cx| { - this.branch_popover = None; - cx.notify(); - }) - .detach(); self.project_popover.take(); let focus_handle = view.focus_handle(cx); 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) { - let workspace = self.workspace.clone(); if self.project_popover.take().is_none() { + let workspace = self.workspace.clone(); cx.spawn(|this, mut cx| async move { let workspaces = WORKSPACE_DB .recent_workspaces_on_disk() @@ -506,16 +495,13 @@ impl CollabTitlebarItem { this.update(&mut cx, move |this, 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); cx.focus(&focus_handle); - // todo!() - //this.branch_popover.take(); - this.project_popover = Some(view); + this.branch_popover.take(); + let subscription = cx.subscribe(&view, |this, _, _, _| { + this.project_popover.take(); + }); + this.project_popover = Some((view, subscription)); cx.notify(); }) .log_err(); diff --git a/crates/recent_projects2/src/recent_projects.rs b/crates/recent_projects2/src/recent_projects.rs index 3ecf1180af..13790a5b23 100644 --- a/crates/recent_projects2/src/recent_projects.rs +++ b/crates/recent_projects2/src/recent_projects.rs @@ -3,8 +3,8 @@ mod projects; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Task, View, - ViewContext, WeakView, + AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Subscription, + Task, View, ViewContext, WeakView, }; use highlighted_workspace_location::HighlightedWorkspaceLocation; use ordered_float::OrderedFloat; @@ -23,17 +23,22 @@ pub fn init(cx: &mut AppContext) { cx.observe_new_views(RecentProjects::register).detach(); } -#[derive(Clone)] pub struct RecentProjects { pub picker: View>, + rem_width: f32, + _subscription: Subscription, } impl ModalView for RecentProjects {} impl RecentProjects { - fn new(delegate: RecentProjectsDelegate, cx: &mut WindowContext<'_>) -> Self { + fn new(delegate: RecentProjectsDelegate, rem_width: f32, cx: &mut ViewContext) -> Self { + let picker = cx.build_view(|cx| Picker::new(delegate, cx)); + let _subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent)); Self { - picker: cx.build_view(|cx| Picker::new(delegate, cx)), + picker, + rem_width, + _subscription, } } @@ -76,9 +81,7 @@ impl RecentProjects { let delegate = RecentProjectsDelegate::new(weak_workspace, workspace_locations, true); - let modal = RecentProjects::new(delegate, cx); - cx.subscribe(&modal.picker, |_, _, _, cx| cx.emit(DismissEvent)) - .detach(); + let modal = RecentProjects::new(delegate, 34., cx); modal }); } else { @@ -94,11 +97,14 @@ impl RecentProjects { workspace: WeakView, workspaces: Vec, cx: &mut WindowContext<'_>, - ) -> Self { - Self::new( - RecentProjectsDelegate::new(workspace, workspaces, false), - cx, - ) + ) -> View { + cx.build_view(|cx| { + Self::new( + RecentProjectsDelegate::new(workspace, workspaces, false), + 20., + cx, + ) + }) } } @@ -113,8 +119,15 @@ impl FocusableView for RecentProjects { impl Render for RecentProjects { type Element = Div; - fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { - v_stack().w(rems(34.)).child(self.picker.clone()) + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + 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); + }) + })) } } diff --git a/crates/vcs_menu2/src/lib.rs b/crates/vcs_menu2/src/lib.rs index ca3b685aa6..1483a2e81e 100644 --- a/crates/vcs_menu2/src/lib.rs +++ b/crates/vcs_menu2/src/lib.rs @@ -3,8 +3,8 @@ use fs::repository::Branch; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ actions, rems, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, - ParentElement, Render, SharedString, Styled, Task, View, ViewContext, VisualContext, - WindowContext, + InteractiveElement, ParentElement, Render, SharedString, Styled, Subscription, Task, View, + ViewContext, VisualContext, WindowContext, }; use picker::{Picker, PickerDelegate}; use std::sync::Arc; @@ -18,31 +18,61 @@ pub fn init(cx: &mut AppContext) { // todo!() po cx.observe_new_views(|workspace: &mut Workspace, _| { workspace.register_action(|workspace, action, cx| { - ModalBranchList::toggle_modal(workspace, action, cx).log_err(); + BranchList::toggle_modal(workspace, action, cx).log_err(); }); }) .detach(); } -pub type BranchList = Picker; -pub struct ModalBranchList { +pub struct BranchList { pub picker: View>, + rem_width: f32, + _subscription: Subscription, } -impl ModalView for ModalBranchList {} -impl EventEmitter for ModalBranchList {} +impl BranchList { + fn new(delegate: BranchListDelegate, rem_width: f32, cx: &mut ViewContext) -> 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, + ) -> 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 for BranchList {} + +impl FocusableView for BranchList { fn focus_handle(&self, cx: &AppContext) -> FocusHandle { self.picker.focus_handle(cx) } } -impl Render for ModalBranchList { +impl Render for BranchList { type Element = Div; - fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { - v_stack().w(rems(34.)).child(self.picker.clone()) + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + 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| { BranchListDelegate::new(workspace, cx.view().clone(), 29, cx) })?; - - Ok(cx.build_view(|cx| Picker::new(delegate, cx))) -} - -impl ModalBranchList { - fn toggle_modal( - workspace: &mut Workspace, - _: &OpenRecent, - cx: &mut ViewContext, - ) -> 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(()) - } + Ok(cx.build_view(move |cx| BranchList::new(delegate, 20., cx))) } pub struct BranchListDelegate { @@ -116,7 +124,7 @@ impl BranchListDelegate { }) } - fn display_error_toast(&self, message: String, cx: &mut ViewContext) { + fn display_error_toast(&self, message: String, cx: &mut WindowContext<'_>) { const GIT_CHECKOUT_FAILURE_ID: usize = 2048; self.workspace.update(cx, |model, ctx| { model.show_toast(Toast::new(GIT_CHECKOUT_FAILURE_ID, message), ctx)