From 030d4d2631004b5deb4a5937e17a476b83fe71d5 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Wed, 4 Jun 2025 07:16:56 +0530 Subject: [PATCH] project_panel: Holding `alt` or `shift` to copy the file should adds a green (+) icon to the mouse cursor (#32040) Part of https://github.com/zed-industries/zed/issues/14496 Depends on new API https://github.com/zed-industries/zed/pull/32028 Holding `alt` or `shift` to copy the file should add a green (+) icon to the mouse cursor to indicate this is a copy operation. 1. Press `option` first, then drag: https://github.com/user-attachments/assets/ae58c441-f1ab-423e-be59-a8ec5cba33b0 2. Drag first, then press `option`: https://github.com/user-attachments/assets/5136329f-9396-4ab9-a799-07d69cec89e2 Release Notes: - Added copy-drag cursor when pressing Alt or Shift to copy the file in Project Panel. --- crates/project_panel/src/project_panel.rs | 54 +++++++++++++++++++---- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 90a87a4480..397a3d1899 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -18,11 +18,12 @@ use file_icons::FileIcons; use git::status::GitSummary; use gpui::{ Action, AnyElement, App, ArcCow, AsyncWindowContext, Bounds, ClipboardItem, Context, - DismissEvent, Div, DragMoveEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, - Hsla, InteractiveElement, KeyContext, ListHorizontalSizingBehavior, ListSizingBehavior, - MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, ScrollStrategy, - Stateful, Styled, Subscription, Task, UniformListScrollHandle, WeakEntity, Window, actions, - anchored, deferred, div, impl_actions, point, px, size, transparent_white, uniform_list, + CursorStyle, DismissEvent, Div, DragMoveEvent, Entity, EventEmitter, ExternalPaths, + FocusHandle, Focusable, Hsla, InteractiveElement, KeyContext, ListHorizontalSizingBehavior, + ListSizingBehavior, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, + ParentElement, Pixels, Point, PromptLevel, Render, ScrollStrategy, Stateful, Styled, + Subscription, Task, UniformListScrollHandle, WeakEntity, Window, actions, anchored, deferred, + div, impl_actions, point, px, size, transparent_white, uniform_list, }; use indexmap::IndexMap; use language::DiagnosticSeverity; @@ -109,6 +110,7 @@ pub struct ProjectPanel { // in case a user clicks to open a file. mouse_down: bool, hover_expand_task: Option>, + previous_drag_position: Option>, } struct DragTargetEntry { @@ -503,6 +505,7 @@ impl ProjectPanel { scroll_handle, mouse_down: false, hover_expand_task: None, + previous_drag_position: None, }; this.update_visible_entries(None, cx); @@ -3106,6 +3109,29 @@ impl ProjectPanel { .detach(); } + fn refresh_drag_cursor_style( + &self, + modifiers: &Modifiers, + window: &mut Window, + cx: &mut Context, + ) { + if let Some(existing_cursor) = cx.active_drag_cursor_style() { + let new_cursor = if Self::is_copy_modifier_set(modifiers) { + CursorStyle::DragCopy + } else { + CursorStyle::PointingHand + }; + if existing_cursor != new_cursor { + cx.set_active_drag_cursor_style(new_cursor, window); + } + } + } + + fn is_copy_modifier_set(modifiers: &Modifiers) -> bool { + cfg!(target_os = "macos") && modifiers.alt + || cfg!(not(target_os = "macos")) && modifiers.control + } + fn drag_onto( &mut self, selections: &DraggedSelection, @@ -3114,9 +3140,7 @@ impl ProjectPanel { window: &mut Window, cx: &mut Context, ) { - let should_copy = cfg!(target_os = "macos") && window.modifiers().alt - || cfg!(not(target_os = "macos")) && window.modifiers().control; - if should_copy { + if Self::is_copy_modifier_set(&window.modifiers()) { let _ = maybe!({ let project = self.project.read(cx); let target_worktree = project.worktree_for_entry(target_entry_id, cx)?; @@ -4682,6 +4706,15 @@ impl Render for ProjectPanel { window: &mut Window, cx: &mut Context, ) { + if let Some(previous_position) = this.previous_drag_position { + // Refresh cursor only when an actual drag happens, + // because modifiers are not updated when the cursor is not moved. + if e.event.position != previous_position { + this.refresh_drag_cursor_style(&e.event.modifiers, window, cx); + } + } + this.previous_drag_position = Some(e.event.position); + if !e.bounds.contains(&e.event.position) { this.drag_target_entry = None; return; @@ -4741,6 +4774,11 @@ impl Render for ProjectPanel { .on_drag_move(cx.listener(handle_drag_move::)) .size_full() .relative() + .on_modifiers_changed(cx.listener( + |this, event: &ModifiersChangedEvent, window, cx| { + this.refresh_drag_cursor_style(&event.modifiers, window, cx); + }, + )) .on_hover(cx.listener(|this, hovered, window, cx| { if *hovered { this.show_scrollbar = true;