Added icons to context menu

Mikayla Maki created

Change summary

crates/context_menu/src/context_menu.rs   | 84 ++++++++++++++++++------
crates/editor/src/mouse_context_menu.rs   |  9 +-
crates/project_panel/src/project_panel.rs | 18 ++--
crates/theme/src/theme.rs                 |  2 
crates/workspace/src/pane.rs              | 22 +++---
styles/src/styleTree/contextMenu.ts       |  2 
6 files changed, 93 insertions(+), 44 deletions(-)

Detailed changes

crates/context_menu/src/context_menu.rs 🔗

@@ -26,15 +26,17 @@ pub enum ContextMenuItem {
     Item {
         label: String,
         action: Box<dyn Action>,
+        icon: Option<String>,
     },
     Separator,
 }
 
 impl ContextMenuItem {
-    pub fn item(label: impl ToString, action: impl 'static + Action) -> Self {
+    pub fn item(label: impl ToString, icon: Option<&str>, action: impl 'static + Action) -> Self {
         Self::Item {
             label: label.to_string(),
             action: Box::new(action),
+            icon: icon.map(|item| item.to_string()),
         }
     }
 
@@ -254,14 +256,31 @@ impl ContextMenu {
                 Flex::column()
                     .with_children(self.items.iter().enumerate().map(|(ix, item)| {
                         match item {
-                            ContextMenuItem::Item { label, .. } => {
+                            ContextMenuItem::Item { label, icon, .. } => {
                                 let style = style
                                     .item
                                     .style_for(Default::default(), Some(ix) == self.selected_index);
-                                Label::new(label.to_string(), style.label.clone())
-                                    .contained()
-                                    .with_style(style.container)
-                                    .boxed()
+                                let mut line = Flex::row();
+                                if let Some(_) = icon {
+                                    line.add_child(
+                                        Empty::new()
+                                            .constrained()
+                                            .with_width(style.icon_width)
+                                            .boxed(),
+                                    );
+                                }
+                                line.add_child(
+                                    Label::new(label.to_string(), style.label.clone())
+                                        .contained()
+                                        .with_style(style.container)
+                                        .with_margin_left(if icon.is_some() {
+                                            style.icon_spacing
+                                        } else {
+                                            0.
+                                        })
+                                        .boxed(),
+                                );
+                                line.boxed()
                             }
                             ContextMenuItem::Separator => Empty::new()
                                 .collapsed()
@@ -314,27 +333,50 @@ impl ContextMenu {
             Flex::column()
                 .with_children(self.items.iter().enumerate().map(|(ix, item)| {
                     match item {
-                        ContextMenuItem::Item { label, action } => {
+                        ContextMenuItem::Item {
+                            label,
+                            action,
+                            icon,
+                        } => {
                             let action = action.boxed_clone();
                             MouseEventHandler::new::<MenuItem, _, _>(ix, cx, |state, _| {
                                 let style =
                                     style.item.style_for(state, Some(ix) == self.selected_index);
-                                Flex::row()
-                                    .with_child(
-                                        Label::new(label.to_string(), style.label.clone()).boxed(),
+
+                                let mut line = Flex::row();
+                                if let Some(icon_file) = icon {
+                                    line.add_child(
+                                        Svg::new(format!("icons/{}", icon_file))
+                                            .with_color(style.label.color)
+                                            .constrained()
+                                            .with_width(style.icon_width)
+                                            .aligned()
+                                            .boxed(),
                                     )
-                                    .with_child({
-                                        KeystrokeLabel::new(
-                                            action.boxed_clone(),
-                                            style.keystroke.container,
-                                            style.keystroke.text.clone(),
-                                        )
-                                        .flex_float()
-                                        .boxed()
-                                    })
-                                    .contained()
-                                    .with_style(style.container)
+                                }
+
+                                line.with_child(
+                                    Label::new(label.to_string(), style.label.clone())
+                                        .contained()
+                                        .with_margin_left(if icon.is_some() {
+                                            style.icon_spacing
+                                        } else {
+                                            0.
+                                        })
+                                        .boxed(),
+                                )
+                                .with_child({
+                                    KeystrokeLabel::new(
+                                        action.boxed_clone(),
+                                        style.keystroke.container,
+                                        style.keystroke.text.clone(),
+                                    )
+                                    .flex_float()
                                     .boxed()
+                                })
+                                .contained()
+                                .with_style(style.container)
+                                .boxed()
                             })
                             .with_cursor_style(CursorStyle::PointingHand)
                             .on_click(MouseButton::Left, move |_, cx| {

crates/editor/src/mouse_context_menu.rs 🔗

@@ -48,12 +48,13 @@ pub fn deploy_context_menu(
         menu.show(
             position,
             vec![
-                ContextMenuItem::item("Rename Symbol", Rename),
-                ContextMenuItem::item("Go To Definition", GoToDefinition),
-                ContextMenuItem::item("Go To Type Definition", GoToTypeDefinition),
-                ContextMenuItem::item("Find All References", FindAllReferences),
+                ContextMenuItem::item("Rename Symbol", None, Rename),
+                ContextMenuItem::item("Go To Definition", None, GoToDefinition),
+                ContextMenuItem::item("Go To Type Definition", None, GoToTypeDefinition),
+                ContextMenuItem::item("Find All References", None, FindAllReferences),
                 ContextMenuItem::item(
                     "Code Actions",
+                    None,
                     ToggleCodeActions {
                         deployed_from_indicator: false,
                     },

crates/project_panel/src/project_panel.rs 🔗

@@ -269,30 +269,32 @@ impl ProjectPanel {
             if !project.is_remote() {
                 menu_entries.push(ContextMenuItem::item(
                     "Add Folder to Project",
+                    None,
                     workspace::AddFolderToProject,
                 ));
                 if is_root {
                     menu_entries.push(ContextMenuItem::item(
                         "Remove from Project",
+                        None,
                         workspace::RemoveWorktreeFromProject(worktree_id),
                     ));
                 }
             }
-            menu_entries.push(ContextMenuItem::item("New File", AddFile));
-            menu_entries.push(ContextMenuItem::item("New Folder", AddDirectory));
+            menu_entries.push(ContextMenuItem::item("New File", None, AddFile));
+            menu_entries.push(ContextMenuItem::item("New Folder", None, AddDirectory));
             menu_entries.push(ContextMenuItem::Separator);
-            menu_entries.push(ContextMenuItem::item("Copy", Copy));
-            menu_entries.push(ContextMenuItem::item("Copy Path", CopyPath));
-            menu_entries.push(ContextMenuItem::item("Cut", Cut));
+            menu_entries.push(ContextMenuItem::item("Copy", None, Copy));
+            menu_entries.push(ContextMenuItem::item("Copy Path", None, CopyPath));
+            menu_entries.push(ContextMenuItem::item("Cut", None, Cut));
             if let Some(clipboard_entry) = self.clipboard_entry {
                 if clipboard_entry.worktree_id() == worktree.id() {
-                    menu_entries.push(ContextMenuItem::item("Paste", Paste));
+                    menu_entries.push(ContextMenuItem::item("Paste", None, Paste));
                 }
             }
             menu_entries.push(ContextMenuItem::Separator);
-            menu_entries.push(ContextMenuItem::item("Rename", Rename));
+            menu_entries.push(ContextMenuItem::item("Rename", None, Rename));
             if !is_root {
-                menu_entries.push(ContextMenuItem::item("Delete", Delete));
+                menu_entries.push(ContextMenuItem::item("Delete", None, Delete));
             }
         }
 

crates/theme/src/theme.rs 🔗

@@ -267,6 +267,8 @@ pub struct ContextMenuItem {
     pub container: ContainerStyle,
     pub label: TextStyle,
     pub keystroke: ContainedText,
+    pub icon_width: f32,
+    pub icon_spacing: f32,
 }
 
 #[derive(Debug, Deserialize, Default)]

crates/workspace/src/pane.rs 🔗

@@ -147,7 +147,7 @@ pub struct Pane {
     autoscroll: bool,
     nav_history: Rc<RefCell<NavHistory>>,
     toolbar: ViewHandle<Toolbar>,
-    split_menu: ViewHandle<ContextMenu>,
+    context_menu: ViewHandle<ContextMenu>,
 }
 
 pub struct ItemNavHistory {
@@ -203,7 +203,7 @@ impl Pane {
                 pane: handle.clone(),
             })),
             toolbar: cx.add_view(|_| Toolbar::new(handle)),
-            split_menu,
+            context_menu: split_menu,
         }
     }
 
@@ -837,14 +837,14 @@ impl Pane {
     }
 
     fn deploy_split_menu(&mut self, action: &DeploySplitMenu, cx: &mut ViewContext<Self>) {
-        self.split_menu.update(cx, |menu, cx| {
+        self.context_menu.update(cx, |menu, cx| {
             menu.show(
                 action.position,
                 vec![
-                    ContextMenuItem::item("Split Right", SplitRight),
-                    ContextMenuItem::item("Split Left", SplitLeft),
-                    ContextMenuItem::item("Split Up", SplitUp),
-                    ContextMenuItem::item("Split Down", SplitDown),
+                    ContextMenuItem::item("Split Right", None, SplitRight),
+                    ContextMenuItem::item("Split Left", None, SplitLeft),
+                    ContextMenuItem::item("Split Up", None, SplitUp),
+                    ContextMenuItem::item("Split Down", None, SplitDown),
                 ],
                 cx,
             );
@@ -852,12 +852,12 @@ impl Pane {
     }
 
     fn deploy_new_menu(&mut self, action: &DeployNewMenu, cx: &mut ViewContext<Self>) {
-        self.split_menu.update(cx, |menu, cx| {
+        self.context_menu.update(cx, |menu, cx| {
             menu.show(
                 action.position,
                 vec![
-                    ContextMenuItem::item("New File", NewFile),
-                    ContextMenuItem::item("New Terminal", NewTerminal),
+                    ContextMenuItem::item("New File", Some("circle_info_12.svg"), NewFile),
+                    ContextMenuItem::item("New Terminal", Some("terminal_12.svg"), NewTerminal),
                 ],
                 cx,
             );
@@ -1204,7 +1204,7 @@ impl View for Pane {
                 })
                 .boxed(),
             )
-            .with_child(ChildView::new(&self.split_menu).boxed())
+            .with_child(ChildView::new(&self.context_menu).boxed())
             .named("pane")
     }
 

styles/src/styleTree/contextMenu.ts 🔗

@@ -16,6 +16,8 @@ export default function contextMenu(theme: Theme) {
     border: border(theme, "primary"),
     keystrokeMargin: 30,
     item: {
+      iconSpacing: 8,
+      iconWidth: 14,
       padding: { left: 4, right: 4, top: 2, bottom: 2 },
       cornerRadius: 6,
       label: text(theme, "sans", "primary", { size: "sm" }),