Rework Drag-And-Drop API (#4079)

* Add cursor position drag and drop events.
* Reword drag events to match pointer ones.
* appkit: Use `convertPoint_fromView` for coordinate conversion.
* appkit: use ProtocolObject<dyn NSDraggingInfo>.
* x11: store dnd.position as pair of i16

  It's what translate_coords takes anyway, so the extra precision is
  misleading if we're going to cast it to i16 everywhere it's used.

  We can also simplify the "unpacking" from the XdndPosition message--we
  can and should use the value of 16 as the shift instead of
  size_of::<c_short> * 2 or something like that, because the specification
  gives us the constant 16.
* x11: store translated DnD coords.
* x11: don't emit DragLeave without DragEnter.
* windows: only emit DragEnter if valid.
* windows: in DnD, always set pdwEffect.

  It appears other apps (like Chromium) set pdwEffect on Drop too:
  61a391b86b/ui/base/dragdrop/drop_target_win.cc
* docs: make it clearer that drag events are for dragged *files*.
* examples/dnd: handle RedrawRequested event.

Co-authored-by: amrbashir <amr.bashir2015@gmail.com>
This commit is contained in:
valadaptive 2025-01-28 15:10:40 -05:00 committed by GitHub
parent 77f1c73f06
commit f5dcd2aabe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 327 additions and 121 deletions

View file

@ -13,7 +13,7 @@ use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClas
use objc2_app_kit::{
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
NSColor, NSDraggingDestination, NSDraggingInfo, NSFilenamesPboardType,
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification,
NSWindow, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
@ -375,19 +375,45 @@ declare_class!(
unsafe impl NSDraggingDestination for WindowDelegate {
/// Invoked when the dragged image enters destination bounds or frame
#[method(draggingEntered:)]
fn dragging_entered(&self, sender: &NSObject) -> bool {
fn dragging_entered(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
trace_scope!("draggingEntered:");
use std::path::PathBuf;
let pb: Retained<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
let pb = unsafe { sender.draggingPasteboard() };
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
let paths = filenames
.into_iter()
.map(|file| PathBuf::from(file.to_string()))
.collect();
filenames.into_iter().for_each(|file| {
let path = PathBuf::from(file.to_string());
self.queue_event(WindowEvent::HoveredFile(path));
});
let dl = unsafe { sender.draggingLocation() };
let dl = self.view().convertPoint_fromView(dl, None);
let position = LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor());
self.queue_event(WindowEvent::DragEntered { paths, position });
true
}
#[method(wantsPeriodicDraggingUpdates)]
fn wants_periodic_dragging_updates(&self) -> bool {
trace_scope!("wantsPeriodicDraggingUpdates:");
true
}
/// Invoked periodically as the image is held within the destination area, allowing modification of the dragging operation or mouse-pointer position.
#[method(draggingUpdated:)]
fn dragging_updated(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
trace_scope!("draggingUpdated:");
let dl = unsafe { sender.draggingLocation() };
let dl = self.view().convertPoint_fromView(dl, None);
let position = LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor());
self.queue_event(WindowEvent::DragMoved { position });
true
}
@ -401,19 +427,24 @@ declare_class!(
/// Invoked after the released image has been removed from the screen
#[method(performDragOperation:)]
fn perform_drag_operation(&self, sender: &NSObject) -> bool {
fn perform_drag_operation(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
trace_scope!("performDragOperation:");
use std::path::PathBuf;
let pb: Retained<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
let pb = unsafe { sender.draggingPasteboard() };
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
let paths = filenames
.into_iter()
.map(|file| PathBuf::from(file.to_string()))
.collect();
filenames.into_iter().for_each(|file| {
let path = PathBuf::from(file.to_string());
self.queue_event(WindowEvent::DroppedFile(path));
});
let dl = unsafe { sender.draggingLocation() };
let dl = self.view().convertPoint_fromView(dl, None);
let position = LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor());
self.queue_event(WindowEvent::DragDropped { paths, position });
true
}
@ -426,9 +457,16 @@ declare_class!(
/// Invoked when the dragging operation is cancelled
#[method(draggingExited:)]
fn dragging_exited(&self, _sender: Option<&NSObject>) {
fn dragging_exited(&self, info: Option<&ProtocolObject<dyn NSDraggingInfo>>) {
trace_scope!("draggingExited:");
self.queue_event(WindowEvent::HoveredFileCancelled);
let position = info.map(|info| {
let dl = unsafe { info.draggingLocation() };
let dl = self.view().convertPoint_fromView(dl, None);
LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor())
});
self.queue_event(WindowEvent::DragLeft { position } );
}
}