Project dropdown menu

Piotr Osiewicz created

Change summary

Cargo.lock                                    |  2 
crates/collab_ui/Cargo.toml                   |  2 
crates/collab_ui/src/collab_titlebar_item.rs  | 79 ++++++++++++++++++--
crates/recent_projects/Cargo.toml             |  1 
crates/recent_projects/src/recent_projects.rs | 21 +++++
5 files changed, 95 insertions(+), 10 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1415,6 +1415,7 @@ dependencies = [
  "picker",
  "postage",
  "project",
+ "recent_projects",
  "serde",
  "serde_derive",
  "settings",
@@ -5376,6 +5377,7 @@ version = "0.1.0"
 dependencies = [
  "db",
  "editor",
+ "futures 0.3.28",
  "fuzzy",
  "gpui",
  "language",

crates/collab_ui/Cargo.toml 🔗

@@ -35,6 +35,7 @@ gpui = { path = "../gpui" }
 menu = { path = "../menu" }
 picker = { path = "../picker" }
 project = { path = "../project" }
+recent_projects = {path = "../recent_projects"}
 settings = { path = "../settings" }
 theme = { path = "../theme" }
 theme_selector = { path = "../theme_selector" }
@@ -42,6 +43,7 @@ util = { path = "../util" }
 workspace = { path = "../workspace" }
 zed-actions = {path = "../zed-actions"}
 
+
 anyhow.workspace = true
 futures.workspace = true
 log.workspace = true

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -23,6 +23,7 @@ use gpui::{
 };
 use picker::PickerEvent;
 use project::{Project, RepositoryEntry};
+use recent_projects::{build_recent_projects, RecentProjects};
 use std::{ops::Range, sync::Arc};
 use theme::{AvatarStyle, Theme};
 use util::ResultExt;
@@ -37,6 +38,7 @@ actions!(
         ToggleContactsMenu,
         ToggleUserMenu,
         ToggleVcsMenu,
+        ToggleProjectMenu,
         SwitchBranch,
         ShareProject,
         UnshareProject,
@@ -49,6 +51,7 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(CollabTitlebarItem::unshare_project);
     cx.add_action(CollabTitlebarItem::toggle_user_menu);
     cx.add_action(CollabTitlebarItem::toggle_vcs_menu);
+    cx.add_action(CollabTitlebarItem::toggle_project_menu);
 }
 
 pub struct CollabTitlebarItem {
@@ -58,6 +61,7 @@ pub struct CollabTitlebarItem {
     workspace: WeakViewHandle<Workspace>,
     contacts_popover: Option<ViewHandle<ContactsPopover>>,
     branch_popover: Option<ViewHandle<BranchList>>,
+    project_popover: Option<ViewHandle<recent_projects::RecentProjects>>,
     user_menu: ViewHandle<ContextMenu>,
     _subscriptions: Vec<Subscription>,
 }
@@ -191,6 +195,7 @@ impl CollabTitlebarItem {
                 menu
             }),
             branch_popover: None,
+            project_popover: None,
             _subscriptions: subscriptions,
         }
     }
@@ -233,14 +238,22 @@ impl CollabTitlebarItem {
         };
         let highlights = (0..name.len()).into_iter().collect();
         let mut ret = Flex::row().with_child(
-            Stack::new().with_child(
-                Label::new(name, style.clone())
-                    .with_highlights(highlights)
-                    .contained()
-                    .aligned()
-                    .left()
-                    .into_any_named("title-project-name"),
-            ),
+            Stack::new()
+                .with_child(
+                    MouseEventHandler::<ToggleProjectMenu, Self>::new(0, cx, |_, _| {
+                        Label::new(name, style.clone())
+                            .with_highlights(highlights)
+                            .contained()
+                            .aligned()
+                            .left()
+                            .into_any_named("title-project-name")
+                    })
+                    .with_cursor_style(CursorStyle::PointingHand)
+                    .on_click(MouseButton::Left, move |_, this, cx| {
+                        this.toggle_project_menu(&Default::default(), cx)
+                    }),
+                )
+                .with_children(self.render_project_popover_host(&theme.titlebar, cx)),
         );
         if let Some(git_branch) = branch_prepended {
             ret = ret.with_child(
@@ -382,6 +395,36 @@ impl CollabTitlebarItem {
                 .into_any()
         })
     }
+    fn render_project_popover_host<'a>(
+        &'a self,
+        _theme: &'a theme::Titlebar,
+        cx: &'a mut ViewContext<Self>,
+    ) -> Option<AnyElement<Self>> {
+        self.project_popover.as_ref().map(|child| {
+            let theme = theme::current(cx).clone();
+            let child = ChildView::new(child, cx);
+            let child = MouseEventHandler::<RecentProjects, Self>::new(0, cx, |_, _| {
+                Flex::column()
+                    .with_child(child.flex(1., true))
+                    .contained()
+                    .constrained()
+                    .with_width(theme.contacts_popover.width)
+                    .with_height(theme.contacts_popover.height)
+            })
+            .on_click(MouseButton::Left, |_, _, _| {})
+            .on_down_out(MouseButton::Left, move |_, _, cx| cx.emit(()))
+            .into_any();
+
+            Overlay::new(child)
+                .with_fit_mode(OverlayFitMode::SwitchAnchor)
+                .with_anchor_corner(AnchorCorner::TopLeft)
+                .with_z_index(999)
+                .aligned()
+                .bottom()
+                .left()
+                .into_any()
+        })
+    }
     pub fn toggle_vcs_menu(&mut self, _: &ToggleVcsMenu, cx: &mut ViewContext<Self>) {
         if self.branch_popover.take().is_none() {
             if let Some(workspace) = self.workspace.upgrade(cx) {
@@ -402,6 +445,26 @@ impl CollabTitlebarItem {
 
         cx.notify();
     }
+
+    pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext<Self>) {
+        log::error!("Toggling project menu");
+        if self.project_popover.take().is_none() {
+            let view = cx.add_view(|cx| build_recent_projects(self.workspace.clone(), cx));
+            cx.subscribe(&view, |this, _, event, cx| {
+                match event {
+                    PickerEvent::Dismiss => {
+                        this.project_popover = None;
+                    }
+                }
+
+                cx.notify();
+            })
+            .detach();
+            self.project_popover = Some(view);
+        }
+
+        cx.notify();
+    }
     fn render_toggle_contacts_button(
         &self,
         theme: &Theme,

crates/recent_projects/Cargo.toml 🔗

@@ -21,6 +21,7 @@ util = { path = "../util"}
 theme = { path = "../theme" }
 workspace = { path = "../workspace" }
 
+futures.workspace = true
 ordered-float.workspace = true
 postage.workspace = true
 smol.workspace = true

crates/recent_projects/src/recent_projects.rs 🔗

@@ -64,9 +64,26 @@ fn toggle(
     }))
 }
 
-type RecentProjects = Picker<RecentProjectsDelegate>;
+pub fn build_recent_projects(
+    workspace: WeakViewHandle<Workspace>,
+    cx: &mut ViewContext<RecentProjects>,
+) -> RecentProjects {
+    let workspaces = futures::executor::block_on(async {
+        WORKSPACE_DB
+            .recent_workspaces_on_disk()
+            .await
+            .unwrap_or_default()
+            .into_iter()
+            .map(|(_, location)| location)
+            .collect()
+    });
+    Picker::new(RecentProjectsDelegate::new(workspace, workspaces), cx)
+        .with_theme(|theme| theme.picker.clone())
+}
+
+pub type RecentProjects = Picker<RecentProjectsDelegate>;
 
-struct RecentProjectsDelegate {
+pub struct RecentProjectsDelegate {
     workspace: WeakViewHandle<Workspace>,
     workspace_locations: Vec<WorkspaceLocation>,
     selected_match_index: usize,