@@ -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,
}
}
}
@@ -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,
@@ -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;