project panel: Reintroduce project panel knockout color (#21926)

Bennet Bo Fenner , Cole , and Danilo Leal created

Reintroduces #20760 after it was reverted in #21807

Closes #20572

/cc @danilo-leal 

Release Notes:

- N/A

---------

Co-authored-by: Cole <cole@zed.dev>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>

Change summary

crates/project_panel/src/project_panel.rs | 62 ++++++++++++++----------
crates/ui/src/components/icon.rs          | 26 ++++++++++
2 files changed, 60 insertions(+), 28 deletions(-)

Detailed changes

crates/project_panel/src/project_panel.rs 🔗

@@ -3147,6 +3147,8 @@ impl ProjectPanel {
         details: EntryDetails,
         cx: &mut ViewContext<Self>,
     ) -> Stateful<Div> {
+        const GROUP_NAME: &str = "project_entry";
+
         let kind = details.kind;
         let settings = ProjectPanelSettings::get_global(cx);
         let show_editor = details.is_editing && !details.is_processing;
@@ -3192,8 +3194,37 @@ impl ProjectPanel {
             marked_selections: selections,
         };
 
+        let default_color = if is_marked || is_active {
+            item_colors.marked_active
+        } else {
+            item_colors.default
+        };
+
+        let bg_hover_color = if self.mouse_down {
+            item_colors.marked_active
+        } else {
+            item_colors.hover
+        };
+
+        let border_color =
+            if !self.mouse_down && is_active && self.focus_handle.contains_focused(cx) {
+                item_colors.focused
+            } else if self.mouse_down && is_marked || is_active {
+                item_colors.marked_active
+            } else {
+                item_colors.default
+            };
+
         div()
             .id(entry_id.to_proto() as usize)
+            .group(GROUP_NAME)
+            .cursor_pointer()
+            .rounded_none()
+            .bg(default_color)
+            .border_1()
+            .border_r_2()
+            .border_color(border_color)
+            .hover(|style| style.bg(bg_hover_color))
             .when(is_local, |div| {
                 div.on_drag_move::<ExternalPaths>(cx.listener(
                     move |this, event: &DragMoveEvent<ExternalPaths>, cx| {
@@ -3329,12 +3360,11 @@ impl ProjectPanel {
                     this.open_entry(entry_id, focus_opened_item, allow_preview, cx);
                 }
             }))
-            .cursor_pointer()
             .child(
                 ListItem::new(entry_id.to_proto() as usize)
                     .indent_level(depth)
                     .indent_step_size(px(settings.indent_size))
-                    .selected(is_marked || is_active)
+                    .selectable(false)
                     .when_some(canonical_path, |this, path| {
                         this.end_slot::<AnyElement>(
                             div()
@@ -3374,13 +3404,11 @@ impl ProjectPanel {
                                             } else {
                                                 IconDecorationKind::Dot
                                             },
-                                            if is_marked || is_active {
-                                                item_colors.marked_active
-                                            } else {
-                                                item_colors.default
-                                            },
+                                            default_color,
                                             cx,
                                         )
+                                        .group_name(Some(GROUP_NAME.into()))
+                                        .knockout_hover_color(bg_hover_color)
                                         .color(decoration_color.color(cx))
                                         .position(Point {
                                             x: px(-2.),
@@ -3496,26 +3524,6 @@ impl ProjectPanel {
                     ))
                     .overflow_x(),
             )
-            .border_1()
-            .border_r_2()
-            .rounded_none()
-            .hover(|style| {
-                if is_active {
-                    style
-                } else {
-                    style.bg(item_colors.hover).border_color(item_colors.hover)
-                }
-            })
-            .when(is_marked || is_active, |this| {
-                this.when(is_marked, |this| {
-                    this.bg(item_colors.marked_active)
-                        .border_color(item_colors.marked_active)
-                })
-            })
-            .when(
-                !self.mouse_down && is_active && self.focus_handle.contains_focused(cx),
-                |this| this.border_color(item_colors.focused),
-            )
     }
 
     fn render_vertical_scrollbar(&self, cx: &mut ViewContext<Self>) -> Option<Stateful<Div>> {

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

@@ -427,7 +427,9 @@ pub struct IconDecoration {
     kind: IconDecorationKind,
     color: Hsla,
     knockout_color: Hsla,
+    knockout_hover_color: Hsla,
     position: Point<Pixels>,
+    group_name: Option<SharedString>,
 }
 
 impl IconDecoration {
@@ -440,7 +442,9 @@ impl IconDecoration {
             kind,
             color,
             knockout_color,
+            knockout_hover_color: knockout_color,
             position,
+            group_name: None,
         }
     }
 
@@ -465,11 +469,23 @@ impl IconDecoration {
         self
     }
 
+    /// Sets the color of the decoration that is used on hover
+    pub fn knockout_hover_color(mut self, color: Hsla) -> Self {
+        self.knockout_hover_color = color;
+        self
+    }
+
     /// Sets the position of the decoration
     pub fn position(mut self, position: Point<Pixels>) -> Self {
         self.position = position;
         self
     }
+
+    /// Sets the name of the group the decoration belongs to
+    pub fn group_name(mut self, name: Option<SharedString>) -> Self {
+        self.group_name = name;
+        self
+    }
 }
 
 impl RenderOnce for IconDecoration {
@@ -498,7 +514,15 @@ impl RenderOnce for IconDecoration {
                     .right_0()
                     .size(px(ICON_DECORATION_SIZE))
                     .path(self.kind.bg().path())
-                    .text_color(self.knockout_color),
+                    .text_color(self.knockout_color)
+                    .when(self.group_name.is_none(), |this| {
+                        this.hover(|style| style.text_color(self.knockout_hover_color))
+                    })
+                    .when_some(self.group_name.clone(), |this, group_name| {
+                        this.group_hover(group_name, |style| {
+                            style.text_color(self.knockout_hover_color)
+                        })
+                    }),
             )
     }
 }