Add empty state for project panel (#3863)

Marshall Bowers created

This PR adds an empty state for the project panel.

It will now display an "Open a project" button.

Release Notes:

- Added an empty state for the project panel.

Change summary

crates/project_panel/src/project_panel.rs | 15 ++++++++++++
crates/ui/src/components/button/button.rs | 27 ++++++++++++++++++------
2 files changed, 34 insertions(+), 8 deletions(-)

Detailed changes

crates/project_panel/src/project_panel.rs 🔗

@@ -30,7 +30,7 @@ use std::{
     sync::Arc,
 };
 use theme::ThemeSettings;
-use ui::{prelude::*, v_stack, ContextMenu, IconElement, Label, ListItem};
+use ui::{prelude::*, v_stack, ContextMenu, IconElement, KeyBinding, Label, ListItem};
 use unicase::UniCase;
 use util::{maybe, ResultExt, TryFutureExt};
 use workspace::{
@@ -1540,7 +1540,20 @@ impl Render for ProjectPanel {
         } else {
             v_stack()
                 .id("empty-project_panel")
+                .size_full()
+                .p_4()
                 .track_focus(&self.focus_handle)
+                .child(
+                    Button::new("open_project", "Open a project")
+                        .style(ButtonStyle::Filled)
+                        .full_width()
+                        .key_binding(KeyBinding::for_action(&workspace::Open, cx))
+                        .on_click(cx.listener(|this, _, cx| {
+                            this.workspace
+                                .update(cx, |workspace, cx| workspace.open(&workspace::Open, cx))
+                                .log_err();
+                        })),
+                )
         }
     }
 }

crates/ui/src/components/button/button.rs 🔗

@@ -1,6 +1,6 @@
 use gpui::{AnyView, DefiniteLength};
 
-use crate::{prelude::*, IconPosition};
+use crate::{prelude::*, IconPosition, KeyBinding};
 use crate::{
     ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle,
 };
@@ -19,6 +19,7 @@ pub struct Button {
     icon_size: Option<IconSize>,
     icon_color: Option<Color>,
     selected_icon: Option<Icon>,
+    key_binding: Option<KeyBinding>,
 }
 
 impl Button {
@@ -34,6 +35,7 @@ impl Button {
             icon_size: None,
             icon_color: None,
             selected_icon: None,
+            key_binding: None,
         }
     }
 
@@ -76,6 +78,11 @@ impl Button {
         self.selected_icon = icon.into();
         self
     }
+
+    pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
+        self.key_binding = key_binding.into();
+        self
+    }
 }
 
 impl Selectable for Button {
@@ -157,7 +164,7 @@ impl RenderOnce for Button {
         self.base.child(
             h_stack()
                 .gap_1()
-                .when(self.icon_position.is_some(), |this| {
+                .when(self.icon_position == Some(IconPosition::Start), |this| {
                     this.children(self.icon.map(|icon| {
                         ButtonIcon::new(icon)
                             .disabled(is_disabled)
@@ -168,12 +175,18 @@ impl RenderOnce for Button {
                     }))
                 })
                 .child(
-                    Label::new(label)
-                        .color(label_color)
-                        .size(self.label_size.unwrap_or_default())
-                        .line_height_style(LineHeightStyle::UiLabel),
+                    h_stack()
+                        .gap_2()
+                        .justify_between()
+                        .child(
+                            Label::new(label)
+                                .color(label_color)
+                                .size(self.label_size.unwrap_or_default())
+                                .line_height_style(LineHeightStyle::UiLabel),
+                        )
+                        .children(self.key_binding),
                 )
-                .when(!self.icon_position.is_some(), |this| {
+                .when(self.icon_position != Some(IconPosition::Start), |this| {
                     this.children(self.icon.map(|icon| {
                         ButtonIcon::new(icon)
                             .disabled(is_disabled)