Add a draft of the mac platform file drag and drop events

Kirill Bulatov created

Change summary

crates/gpui2/src/interactive.rs         |  21 ++++
crates/gpui2/src/platform/mac/window.rs | 130 +++++++++++++++++++++++++-
crates/gpui2/src/window.rs              |  30 ++++-
3 files changed, 165 insertions(+), 16 deletions(-)

Detailed changes

crates/gpui2/src/interactive.rs 🔗

@@ -13,6 +13,7 @@ use std::{
     fmt::Debug,
     marker::PhantomData,
     ops::Deref,
+    path::PathBuf,
     sync::Arc,
 };
 
@@ -1048,6 +1049,19 @@ impl Deref for MouseExitEvent {
     }
 }
 
+#[derive(Debug, Clone, Default)]
+pub enum FileDropEvent {
+    #[default]
+    End,
+    Pending {
+        position: Point<Pixels>,
+    },
+    Submit {
+        position: Point<Pixels>,
+        paths: Vec<PathBuf>,
+    },
+}
+
 #[derive(Clone, Debug)]
 pub enum InputEvent {
     KeyDown(KeyDownEvent),
@@ -1058,6 +1072,7 @@ pub enum InputEvent {
     MouseMoved(MouseMoveEvent),
     MouseExited(MouseExitEvent),
     ScrollWheel(ScrollWheelEvent),
+    FileDrop(FileDropEvent),
 }
 
 impl InputEvent {
@@ -1071,6 +1086,10 @@ impl InputEvent {
             InputEvent::MouseMoved(event) => Some(event.position),
             InputEvent::MouseExited(event) => Some(event.position),
             InputEvent::ScrollWheel(event) => Some(event.position),
+            InputEvent::FileDrop(FileDropEvent::End) => None,
+            InputEvent::FileDrop(
+                FileDropEvent::Pending { position } | FileDropEvent::Submit { position, .. },
+            ) => Some(*position),
         }
     }
 
@@ -1084,6 +1103,7 @@ impl InputEvent {
             InputEvent::MouseMoved(event) => Some(event),
             InputEvent::MouseExited(event) => Some(event),
             InputEvent::ScrollWheel(event) => Some(event),
+            InputEvent::FileDrop(event) => Some(event),
         }
     }
 
@@ -1097,6 +1117,7 @@ impl InputEvent {
             InputEvent::MouseMoved(_) => None,
             InputEvent::MouseExited(_) => None,
             InputEvent::ScrollWheel(_) => None,
+            InputEvent::FileDrop(_) => None,
         }
     }
 }

crates/gpui2/src/platform/mac/window.rs 🔗

@@ -1,21 +1,22 @@
 use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
 use crate::{
-    display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Executor, GlobalPixels,
-    InputEvent, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
-    MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
-    PlatformInputHandler, PlatformWindow, Point, Scene, Size, Timer, WindowAppearance,
-    WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
+    display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Executor, FileDropEvent,
+    GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent,
+    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas,
+    PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, Size, Timer,
+    WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
 };
 use block::ConcreteBlock;
 use cocoa::{
     appkit::{
-        CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
-        NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior,
-        NSWindowStyleMask, NSWindowTitleVisibility,
+        CGPoint, NSApplication, NSBackingStoreBuffered, NSFilenamesPboardType, NSPasteboard,
+        NSScreen, NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton,
+        NSWindowCollectionBehavior, NSWindowStyleMask, NSWindowTitleVisibility,
     },
     base::{id, nil},
     foundation::{
-        NSAutoreleasePool, NSDictionary, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger,
+        NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect,
+        NSSize, NSString, NSUInteger,
     },
 };
 use core_graphics::display::CGRect;
@@ -37,6 +38,7 @@ use std::{
     mem,
     ops::Range,
     os::raw::c_char,
+    path::PathBuf,
     ptr,
     rc::Rc,
     sync::{Arc, Weak},
@@ -68,6 +70,13 @@ const NSTrackingInVisibleRect: NSUInteger = 0x200;
 const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4;
 #[allow(non_upper_case_globals)]
 const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
+// https://developer.apple.com/documentation/appkit/nsdragoperation
+#[allow(non_upper_case_globals)]
+type NSDragOperation = NSUInteger;
+#[allow(non_upper_case_globals)]
+const NSDragOperationNone: NSDragOperation = 0;
+#[allow(non_upper_case_globals)]
+const NSDragOperationCopy: NSDragOperation = 1;
 
 #[ctor]
 unsafe fn build_classes() {
@@ -259,6 +268,28 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C
         window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
     );
     decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
+
+    decl.add_method(
+        sel!(draggingEntered:),
+        dragging_entered as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
+    );
+    decl.add_method(
+        sel!(draggingUpdated:),
+        dragging_updated as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
+    );
+    decl.add_method(
+        sel!(draggingExited:),
+        dragging_exited as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(performDragOperation:),
+        perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
+    );
+    decl.add_method(
+        sel!(concludeDragOperation:),
+        conclude_drag_operation as extern "C" fn(&Object, Sel, id),
+    );
+
     decl.register()
 }
 
@@ -472,6 +503,11 @@ impl MacWindow {
                 target_screen,
             );
             assert!(!native_window.is_null());
+            let () = msg_send![
+                native_window,
+                registerForDraggedTypes:
+                    NSArray::arrayWithObject(nil, NSFilenamesPboardType)
+            ];
 
             let screen = native_window.screen();
             match options.bounds {
@@ -1595,6 +1631,66 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
     }
 }
 
+extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
+    let window_state = unsafe { get_window_state(this) };
+    let position = drag_event_position(&window_state, dragging_info);
+    if send_new_event(
+        &window_state,
+        InputEvent::FileDrop(FileDropEvent::Pending { position }),
+    ) {
+        NSDragOperationCopy
+    } else {
+        NSDragOperationNone
+    }
+}
+
+extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
+    let window_state = unsafe { get_window_state(this) };
+    let position = drag_event_position(&window_state, dragging_info);
+    if send_new_event(
+        &window_state,
+        InputEvent::FileDrop(FileDropEvent::Pending { position }),
+    ) {
+        NSDragOperationCopy
+    } else {
+        NSDragOperationNone
+    }
+}
+
+extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
+    let window_state = unsafe { get_window_state(this) };
+    send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::End));
+}
+
+extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL {
+    let mut paths = Vec::new();
+    let pb: id = unsafe { msg_send![dragging_info, draggingPasteboard] };
+    let filenames = unsafe { NSPasteboard::propertyListForType(pb, NSFilenamesPboardType) };
+    for file in unsafe { filenames.iter() } {
+        let path = unsafe {
+            let f = NSString::UTF8String(file);
+            CStr::from_ptr(f).to_string_lossy().into_owned()
+        };
+        paths.push(PathBuf::from(path))
+    }
+
+    let window_state = unsafe { get_window_state(this) };
+    let position = drag_event_position(&window_state, dragging_info);
+    if send_new_event(
+        &window_state,
+        InputEvent::FileDrop(FileDropEvent::Submit { position, paths }),
+    ) {
+        YES
+    } else {
+        NO
+    }
+}
+
+extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) {
+    let window_state = unsafe { get_window_state(this) };
+    send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::End));
+}
+
 async fn synthetic_drag(
     window_state: Weak<Mutex<MacWindowState>>,
     drag_id: usize,
@@ -1617,6 +1713,22 @@ async fn synthetic_drag(
     }
 }
 
+fn send_new_event(window_state_lock: &Mutex<MacWindowState>, e: InputEvent) -> bool {
+    let window_state = window_state_lock.lock().event_callback.take();
+    if let Some(mut callback) = window_state {
+        callback(e);
+        window_state_lock.lock().event_callback = Some(callback);
+        true
+    } else {
+        false
+    }
+}
+
+fn drag_event_position(window_state: &Mutex<MacWindowState>, dragging_info: id) -> Point<Pixels> {
+    let drag_location: NSPoint = unsafe { msg_send![dragging_info, draggingLocation] };
+    convert_mouse_position(drag_location, window_state.lock().content_size().height)
+}
+
 fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
 where
     F: FnOnce(&mut dyn PlatformInputHandler) -> R,

crates/gpui2/src/window.rs 🔗

@@ -1,13 +1,13 @@
 use crate::{
     px, size, Action, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace, Bounds,
     BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect, Element,
-    EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData,
-    InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, MainThread,
-    MainThreadOnly, MonochromeSprite, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
-    PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
-    RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription,
-    TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle, WindowOptions,
-    SUBPIXEL_VARIANTS,
+    EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Handle,
+    Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId,
+    MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, MouseUpEvent, Path, Pixels,
+    PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams,
+    RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
+    Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle,
+    WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use collections::HashMap;
@@ -894,6 +894,22 @@ impl<'a, 'w> WindowContext<'a, 'w> {
                 self.window.mouse_position = *position;
             }
 
+            match any_mouse_event.downcast_ref() {
+                Some(FileDropEvent::Pending { position }) => {
+                    dbg!("FileDropEvent::Pending", position);
+                    return true;
+                }
+                Some(FileDropEvent::Submit { position, paths }) => {
+                    dbg!("FileDropEvent::Submit", position, paths);
+                    return true;
+                }
+                Some(FileDropEvent::End) => {
+                    self.active_drag = None;
+                    return true;
+                }
+                _ => {}
+            }
+
             // Handlers may set this to false by calling `stop_propagation`
             self.app.propagate_event = true;
             self.window.default_prevented = false;