From 0e48465adb179ba0164a08a7203db8e56c929e3c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 24 Oct 2023 11:31:07 +0200 Subject: [PATCH] Add a draft of the mac platform file drag and drop events --- 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(-) diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index aa8cbf4826..925763bd4e 100644 --- a/crates/gpui2/src/interactive.rs +++ b/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, + }, + Submit { + position: Point, + paths: Vec, + }, +} + #[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, } } } diff --git a/crates/gpui2/src/platform/mac/window.rs b/crates/gpui2/src/platform/mac/window.rs index f72c6ad416..9cd2bf20ee 100644 --- a/crates/gpui2/src/platform/mac/window.rs +++ b/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>, drag_id: usize, @@ -1617,6 +1713,22 @@ async fn synthetic_drag( } } +fn send_new_event(window_state_lock: &Mutex, 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, dragging_info: id) -> Point { + 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(window: &Object, f: F) -> Option where F: FnOnce(&mut dyn PlatformInputHandler) -> R, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 66106a0057..fcc49d2ef3 100644 --- a/crates/gpui2/src/window.rs +++ b/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;