Fix project panel button and style it

Mikayla Maki and max created

Co-authored-by: max <max@zed.dev>

Change summary

crates/project/src/worktree.rs            |   2 
crates/project_panel/src/project_panel.rs | 130 +++++++++++-------------
crates/settings/src/settings_file.rs      |  51 ++++++---
crates/theme/src/theme.rs                 |   1 
crates/workspace/src/workspace.rs         |   1 
crates/zed/src/main.rs                    |   1 
styles/src/styleTree/projectPanel.ts      |  33 ++++++
7 files changed, 127 insertions(+), 92 deletions(-)

Detailed changes

crates/project/src/worktree.rs 🔗

@@ -867,7 +867,7 @@ impl LocalWorktree {
         let old_path = self.entry_for_id(entry_id)?.path.clone();
         let new_path = new_path.into();
         let abs_old_path = self.absolutize(&old_path);
-        let abs_new_path = self.absolutize(&new_path);
+        let abs_new_path = self.absolutize(new_path.as_ref());
         let rename = cx.background().spawn({
             let fs = self.fs.clone();
             let abs_new_path = abs_new_path.clone();

crates/project_panel/src/project_panel.rs 🔗

@@ -5,9 +5,8 @@ use futures::stream::StreamExt;
 use gpui::{
     actions,
     anyhow::{anyhow, Result},
-    color::Color,
     elements::{
-        AnchorCorner, Canvas, ChildView, ConstrainedBox, ContainerStyle, Empty, Flex,
+        AnchorCorner, ChildView, ConstrainedBox, Container, ContainerStyle, Empty, Flex,
         KeystrokeLabel, Label, MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg,
         UniformList, UniformListState,
     },
@@ -15,7 +14,7 @@ use gpui::{
     impl_internal_actions,
     keymap_matcher::KeymapContext,
     platform::CursorStyle,
-    AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton,
+    Action, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton,
     MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle,
 };
 use menu::{Confirm, SelectNext, SelectPrev};
@@ -29,7 +28,7 @@ use std::{
     path::{Path, PathBuf},
     sync::Arc,
 };
-use theme::ProjectPanelEntry;
+use theme::{ContainedText, ProjectPanelEntry};
 use unicase::UniCase;
 use workspace::Workspace;
 
@@ -1317,79 +1316,36 @@ impl View for ProjectPanel {
                 .boxed()
         } else {
             let parent_view_id = cx.handle().id();
-            Stack::new()
+            Flex::column()
                 .with_child(
-                    MouseEventHandler::<ProjectPanel>::new(1, cx, |_, cx| {
-                        Stack::new()
-                            .with_child(
-                                Canvas::new(|bounds, _visible_bounds, cx| {
-                                    cx.scene.push_quad(gpui::Quad {
-                                        bounds,
-                                        background: Some(Color::transparent_black()),
-                                        ..Default::default()
-                                    })
-                                })
-                                .boxed(),
-                            )
-                            .with_child(
-                                MouseEventHandler::<Self>::new(2, cx, |state, cx| {
-                                    let style = &cx
-                                        .global::<Settings>()
-                                        .theme
-                                        .search
-                                        .option_button
-                                        .style_for(state, false);
-
-                                    let context_menu_item = cx
-                                        .global::<Settings>()
-                                        .theme
-                                        .context_menu
-                                        .clone()
-                                        .item
-                                        .style_for(state, true)
-                                        .clone();
-
-                                    Flex::row()
-                                        .with_child(
-                                            Label::new(
-                                                "Open a new project!".to_string(),
-                                                context_menu_item.label.clone(),
-                                            )
-                                            .contained()
-                                            .boxed(),
-                                        )
-                                        .with_child({
-                                            KeystrokeLabel::new(
-                                                cx.window_id(),
-                                                parent_view_id,
-                                                Box::new(workspace::Open),
-                                                context_menu_item.keystroke.container,
-                                                context_menu_item.keystroke.text.clone(),
-                                            )
-                                            .flex_float()
-                                            .boxed()
-                                        })
-                                        .contained()
-                                        .with_style(style.container)
-                                        .aligned()
-                                        .top()
-                                        .constrained()
-                                        .with_width(100.)
-                                        .with_height(20.)
-                                        .boxed()
-                                })
-                                .on_click(MouseButton::Left, move |_, cx| {
-                                    cx.dispatch_action(workspace::Open)
-                                })
-                                .with_cursor_style(CursorStyle::PointingHand)
-                                .boxed(),
+                    MouseEventHandler::<Self>::new(2, cx, {
+                        let button_style = theme.open_project_button.clone();
+                        let context_menu_item_style =
+                            cx.global::<Settings>().theme.context_menu.item.clone();
+                        move |state, cx| {
+                            let button_style = button_style.style_for(state, false).clone();
+                            let context_menu_item =
+                                context_menu_item_style.style_for(state, true).clone();
+
+                            keystroke_label(
+                                parent_view_id,
+                                "Open a new project",
+                                &button_style,
+                                context_menu_item.keystroke,
+                                workspace::Open,
+                                cx,
                             )
                             .boxed()
+                        }
                     })
-                    // TODO is this nescessary?
-                    .on_click(MouseButton::Left, |_, cx| cx.focus_parent_view())
+                    .on_click(MouseButton::Left, move |_, cx| {
+                        cx.dispatch_action(workspace::Open)
+                    })
+                    .with_cursor_style(CursorStyle::PointingHand)
                     .boxed(),
                 )
+                .contained()
+                .with_style(container_style)
                 .boxed()
         }
     }
@@ -1401,6 +1357,38 @@ impl View for ProjectPanel {
     }
 }
 
+fn keystroke_label<A>(
+    view_id: usize,
+    label_text: &'static str,
+    label_style: &ContainedText,
+    keystroke_style: ContainedText,
+    action: A,
+    cx: &mut RenderContext<ProjectPanel>,
+) -> Container
+where
+    A: Action,
+{
+    Flex::row()
+        .with_child(
+            Label::new(label_text, label_style.text.clone())
+                .contained()
+                .boxed(),
+        )
+        .with_child({
+            KeystrokeLabel::new(
+                cx.window_id(),
+                view_id,
+                Box::new(action),
+                keystroke_style.container,
+                keystroke_style.text.clone(),
+            )
+            .flex_float()
+            .boxed()
+        })
+        .contained()
+        .with_style(label_style.container)
+}
+
 impl Entity for ProjectPanel {
     type Event = Event;
 }

crates/settings/src/settings_file.rs 🔗

@@ -111,14 +111,10 @@ mod tests {
         let default_settings = cx.read(Settings::test);
 
         cx.update(|cx| {
-            cx.add_global_action(|_: &A, _cx| {
-            });
-            cx.add_global_action(|_: &B, _cx| {
-            });
-            cx.add_global_action(|_: &ActivatePreviousPane, _cx| {
-            });
-            cx.add_global_action(|_: &ActivatePrevItem, _cx| {
-            });
+            cx.add_global_action(|_: &A, _cx| {});
+            cx.add_global_action(|_: &B, _cx| {});
+            cx.add_global_action(|_: &ActivatePreviousPane, _cx| {});
+            cx.add_global_action(|_: &ActivatePrevItem, _cx| {});
             watch_files(
                 default_settings,
                 settings_file,
@@ -132,7 +128,11 @@ mod tests {
 
         // Test loading the keymap base at all
         cx.update(|cx| {
-            assert_keybindings_for(cx, vec![("backspace", &A), ("k", &ActivatePreviousPane)], line!());
+            assert_keybindings_for(
+                cx,
+                vec![("backspace", &A), ("k", &ActivatePreviousPane)],
+                line!(),
+            );
         });
 
         // Test modifying the users keymap, while retaining the base keymap
@@ -152,13 +152,17 @@ mod tests {
         )
         .await
         .unwrap();
-        
+
         cx.foreground().run_until_parked();
 
         cx.update(|cx| {
-            assert_keybindings_for(cx, vec![("backspace", &B), ("k", &ActivatePreviousPane)], line!());
+            assert_keybindings_for(
+                cx,
+                vec![("backspace", &B), ("k", &ActivatePreviousPane)],
+                line!(),
+            );
         });
-        
+
         // Test modifying the base, while retaining the users keymap
         fs.save(
             "/settings.json".as_ref(),
@@ -172,11 +176,15 @@ mod tests {
         )
         .await
         .unwrap();
-        
+
         cx.foreground().run_until_parked();
 
         cx.update(|cx| {
-            assert_keybindings_for(cx, vec![("backspace", &B), ("[", &ActivatePrevItem)], line!());
+            assert_keybindings_for(
+                cx,
+                vec![("backspace", &B), ("[", &ActivatePrevItem)],
+                line!(),
+            );
         });
     }
 
@@ -185,17 +193,22 @@ mod tests {
         actions: Vec<(&'static str, &'a dyn Action)>,
         line: u32,
     ) {
-        
         for (key, action) in actions {
             // assert that...
-            assert!(cx.available_actions(0, 0).any(|(_, bound_action, b)| {
-                // action names match...
-                bound_action.name() == action.name()
+            assert!(
+                cx.available_actions(0, 0).any(|(_, bound_action, b)| {
+                    // action names match...
+                    bound_action.name() == action.name()
                     && bound_action.namespace() == action.namespace()
                     // and key strokes contain the given key
                     && b.iter()
                         .any(|binding| binding.keystrokes().iter().any(|k| k.key == key))
-            }), "On {} Failed to find {} with keybinding {}", line,  action.name(), key);
+                }),
+                "On {} Failed to find {} with keybinding {}",
+                line,
+                action.name(),
+                key
+            );
         }
     }
 

crates/theme/src/theme.rs 🔗

@@ -346,6 +346,7 @@ pub struct ProjectPanel {
     pub cut_entry: Interactive<ProjectPanelEntry>,
     pub filename_editor: FieldEditor,
     pub indent_width: f32,
+    pub open_project_button: Interactive<ContainedText>,
 }
 
 #[derive(Clone, Debug, Deserialize, Default)]

crates/workspace/src/workspace.rs 🔗

@@ -2815,6 +2815,7 @@ fn open(_: &Open, cx: &mut MutableAppContext) {
         directories: true,
         multiple: true,
     });
+
     cx.spawn(|mut cx| async move {
         if let Some(paths) = paths.recv().await.flatten() {
             cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));

crates/zed/src/main.rs 🔗

@@ -237,7 +237,6 @@ fn main() {
                 let app_state = app_state.clone();
                 async move {
                     while let Some(paths) = open_paths_rx.next().await {
-                        log::error!("OPEN PATHS FROM HANDLE");
                         cx.update(|cx| workspace::open_paths(&paths, &app_state, cx))
                             .detach();
                     }

styles/src/styleTree/projectPanel.ts 🔗

@@ -29,6 +29,39 @@ export default function projectPanel(colorScheme: ColorScheme) {
     }
 
     return {
+        openProjectButton: {
+            ...text(layer, "mono", "active", { size: "sm" }),
+            background: background(layer, "on"),
+            cornerRadius: 6,
+            border: border(layer, "on"),
+            margin: {
+                top: 20,
+                left: 10,
+                right: 10
+            },
+            padding: {
+                bottom: 2,
+                left: 10,
+                right: 10,
+                top: 2,
+            },
+            active: {
+                ...text(layer, "mono", "on", "inverted"),
+                background: background(layer, "on", "inverted"),
+                border: border(layer, "on", "inverted"),
+            },
+            clicked: {
+                ...text(layer, "mono", "on", "pressed"),
+                background: background(layer, "on", "pressed"),
+                border: border(layer, "on", "pressed"),
+            },
+            hover: {
+                ...text(layer, "mono", "on", "hovered"),
+                background: background(layer, "on", "hovered"),
+                border: border(layer, "on", "hovered"),
+            },
+
+        },
         background: background(layer),
         padding: { left: 12, right: 12, top: 6, bottom: 6 },
         indentWidth: 8,