collab_ui: Wire up project picker

Piotr Osiewicz and Conrad created

Co-authored-by: Conrad <conrad@zed.dev>

Change summary

crates/collab_ui2/src/collab_titlebar_item.rs  | 111 +++++++++++--------
crates/picker2/src/picker2.rs                  |  23 ++-
crates/recent_projects2/src/recent_projects.rs |  21 ++
3 files changed, 95 insertions(+), 60 deletions(-)

Detailed changes

crates/collab_ui2/src/collab_titlebar_item.rs 🔗

@@ -2,11 +2,13 @@ use crate::face_pile::FacePile;
 use call::{ActiveCall, ParticipantLocation, Room};
 use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore};
 use gpui::{
-    actions, canvas, div, point, px, rems, AppContext, Div, Element, Hsla, InteractiveElement,
-    IntoElement, Model, ParentElement, Path, Render, Stateful, StatefulInteractiveElement, Styled,
-    Subscription, ViewContext, VisualContext, WeakView, WindowBounds,
+    actions, canvas, div, overlay, point, px, rems, AppContext, DismissEvent, Div, Element,
+    FocusableView, Hsla, InteractiveElement, IntoElement, Model, Overlay, ParentElement, Path,
+    Render, Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext,
+    WeakView, WindowBounds,
 };
 use project::{Project, RepositoryEntry};
+use recent_projects::RecentProjects;
 use std::sync::Arc;
 use theme::{ActiveTheme, PlayerColors};
 use ui::{
@@ -14,7 +16,7 @@ use ui::{
     IconButton, IconElement, KeyBinding, Tooltip,
 };
 use util::ResultExt;
-use workspace::{notifications::NotifyResultExt, Workspace};
+use workspace::{notifications::NotifyResultExt, Workspace, WORKSPACE_DB};
 
 const MAX_PROJECT_NAME_LENGTH: usize = 40;
 const MAX_BRANCH_NAME_LENGTH: usize = 40;
@@ -49,7 +51,7 @@ pub struct CollabTitlebarItem {
     client: Arc<Client>,
     workspace: WeakView<Workspace>,
     //branch_popover: Option<ViewHandle<BranchList>>,
-    //project_popover: Option<ViewHandle<recent_projects::RecentProjects>>,
+    project_popover: Option<recent_projects::RecentProjects>,
     //user_menu: ViewHandle<ContextMenu>,
     _subscriptions: Vec<Subscription>,
 }
@@ -328,7 +330,7 @@ impl CollabTitlebarItem {
             //             menu
             //         }),
             //         branch_popover: None,
-            //         project_popover: None,
+            project_popover: None,
             _subscriptions: subscriptions,
         }
     }
@@ -366,11 +368,27 @@ impl CollabTitlebarItem {
 
         let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
 
-        div().border().border_color(gpui::red()).child(
-            Button::new("project_name_trigger", name)
-                .style(ButtonStyle::Subtle)
-                .tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
-        )
+        div()
+            .border()
+            .border_color(gpui::red())
+            .child(
+                Button::new("project_name_trigger", name)
+                    .style(ButtonStyle::Subtle)
+                    .tooltip(move |cx| Tooltip::text("Recent Projects", cx))
+                    .on_click(cx.listener(|this, _, cx| {
+                        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()),
+                )
+            }))
     }
 
     pub fn render_project_branch(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
@@ -611,43 +629,40 @@ impl CollabTitlebarItem {
     //     cx.notify();
     // }
 
-    // pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext<Self>) {
-    //     let workspace = self.workspace.clone();
-    //     if self.project_popover.take().is_none() {
-    //         cx.spawn(|this, mut cx| async move {
-    //             let workspaces = WORKSPACE_DB
-    //                 .recent_workspaces_on_disk()
-    //                 .await
-    //                 .unwrap_or_default()
-    //                 .into_iter()
-    //                 .map(|(_, location)| location)
-    //                 .collect();
-
-    //             let workspace = workspace.clone();
-    //             this.update(&mut cx, move |this, cx| {
-    //                 let view = cx.add_view(|cx| build_recent_projects(workspace, workspaces, cx));
-
-    //                 cx.subscribe(&view, |this, _, event, cx| {
-    //                     match event {
-    //                         PickerEvent::Dismiss => {
-    //                             this.project_popover = None;
-    //                         }
-    //                     }
-
-    //                     cx.notify();
-    //                 })
-    //                 .detach();
-    //                 cx.focus(&view);
-    //                 this.branch_popover.take();
-    //                 this.project_popover = Some(view);
-    //                 cx.notify();
-    //             })
-    //             .log_err();
-    //         })
-    //         .detach();
-    //     }
-    //     cx.notify();
-    // }
+    pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext<Self>) {
+        let workspace = self.workspace.clone();
+        if self.project_popover.take().is_none() {
+            cx.spawn(|this, mut cx| async move {
+                let workspaces = WORKSPACE_DB
+                    .recent_workspaces_on_disk()
+                    .await
+                    .unwrap_or_default()
+                    .into_iter()
+                    .map(|(_, location)| location)
+                    .collect();
+
+                let workspace = workspace.clone();
+                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);
+                    cx.notify();
+                })
+                .log_err();
+            })
+            .detach();
+        }
+        cx.notify();
+    }
 
     // fn render_user_menu_button(
     //     &self,

crates/picker2/src/picker2.rs 🔗

@@ -1,8 +1,8 @@
 use editor::Editor;
 use gpui::{
-    div, prelude::*, rems, uniform_list, AnyElement, AppContext, Div, FocusHandle, FocusableView,
-    MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle, View, ViewContext,
-    WindowContext,
+    div, prelude::*, rems, uniform_list, AnyElement, AppContext, DismissEvent, Div, EventEmitter,
+    FocusHandle, FocusableView, MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle,
+    View, ViewContext, WindowContext,
 };
 use std::{cmp, sync::Arc};
 use ui::{prelude::*, v_stack, Color, Divider, Label};
@@ -113,8 +113,9 @@ impl<D: PickerDelegate> Picker<D> {
         cx.notify();
     }
 
-    fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
+    pub fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
         self.delegate.dismissed(cx);
+        cx.emit(DismissEvent);
     }
 
     fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
@@ -146,9 +147,15 @@ impl<D: PickerDelegate> Picker<D> {
         event: &editor::EditorEvent,
         cx: &mut ViewContext<Self>,
     ) {
-        if let editor::EditorEvent::BufferEdited = event {
-            let query = self.editor.read(cx).text(cx);
-            self.update_matches(query, cx);
+        match event {
+            editor::EditorEvent::BufferEdited => {
+                let query = self.editor.read(cx).text(cx);
+                self.update_matches(query, cx);
+            }
+            editor::EditorEvent::Blurred => {
+                self.cancel(&menu::Cancel, cx);
+            }
+            _ => {}
         }
     }
 
@@ -189,6 +196,8 @@ impl<D: PickerDelegate> Picker<D> {
     }
 }
 
+impl<D: PickerDelegate> EventEmitter<DismissEvent> for Picker<D> {}
+
 impl<D: PickerDelegate> Render for Picker<D> {
     type Element = Div;
 

crates/recent_projects2/src/recent_projects.rs 🔗

@@ -23,14 +23,15 @@ pub fn init(cx: &mut AppContext) {
     cx.observe_new_views(RecentProjects::register).detach();
 }
 
+#[derive(Clone)]
 pub struct RecentProjects {
-    picker: View<Picker<RecentProjectsDelegate>>,
+    pub picker: View<Picker<RecentProjectsDelegate>>,
 }
 
 impl ModalView for RecentProjects {}
 
 impl RecentProjects {
-    fn new(delegate: RecentProjectsDelegate, cx: &mut ViewContext<Self>) -> Self {
+    fn new(delegate: RecentProjectsDelegate, cx: &mut WindowContext<'_>) -> Self {
         Self {
             picker: cx.build_view(|cx| Picker::new(delegate, cx)),
         }
@@ -86,6 +87,16 @@ impl RecentProjects {
             Ok(())
         }))
     }
+    pub fn open_popover(
+        workspace: WeakView<Workspace>,
+        workspaces: Vec<WorkspaceLocation>,
+        cx: &mut WindowContext<'_>,
+    ) -> Self {
+        Self::new(
+            RecentProjectsDelegate::new(workspace, workspaces, false),
+            cx,
+        )
+    }
 }
 
 impl EventEmitter<DismissEvent> for RecentProjects {}
@@ -127,7 +138,7 @@ impl RecentProjectsDelegate {
         }
     }
 }
-
+impl EventEmitter<DismissEvent> for RecentProjectsDelegate {}
 impl PickerDelegate for RecentProjectsDelegate {
     type ListItem = ListItem;
 
@@ -202,11 +213,11 @@ impl PickerDelegate for RecentProjectsDelegate {
                         .open_workspace_for_paths(workspace_location.paths().as_ref().clone(), cx)
                 })
                 .detach_and_log_err(cx);
-            self.dismissed(cx);
+            cx.emit(DismissEvent);
         }
     }
 
-    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
+    fn dismissed(&mut self, _: &mut ViewContext<Picker<Self>>) {}
 
     fn render_match(
         &self,