-
Notifications
You must be signed in to change notification settings - Fork 938
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework DnD redux #4079
Rework DnD redux #4079
Changes from all commits
2594b83
1069dd8
b3510c2
d100b86
9bf7a4e
7c293f5
35a972f
36438b1
c5e3575
a2b150b
5bbbedd
3d3f6fb
bafb090
2873e88
991b4c8
dcfb62d
6b4b84b
a4be71e
df8e5ce
f55a534
2f589b5
59dd7ed
2153865
731a0ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
use std::error::Error; | ||
|
||
use winit::application::ApplicationHandler; | ||
use winit::event::WindowEvent; | ||
use winit::event_loop::{ActiveEventLoop, EventLoop}; | ||
use winit::window::{Window, WindowAttributes, WindowId}; | ||
|
||
#[path = "util/fill.rs"] | ||
mod fill; | ||
#[path = "util/tracing.rs"] | ||
mod tracing; | ||
|
||
fn main() -> Result<(), Box<dyn Error>> { | ||
tracing::init(); | ||
|
||
let event_loop = EventLoop::new()?; | ||
|
||
let app = Application::new(); | ||
Ok(event_loop.run_app(app)?) | ||
} | ||
|
||
/// Application state and event handling. | ||
struct Application { | ||
window: Option<Box<dyn Window>>, | ||
} | ||
|
||
impl Application { | ||
fn new() -> Self { | ||
Self { window: None } | ||
} | ||
} | ||
|
||
impl ApplicationHandler for Application { | ||
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { | ||
let window_attributes = | ||
WindowAttributes::default().with_title("Drag and drop files on me!"); | ||
self.window = Some(event_loop.create_window(window_attributes).unwrap()); | ||
} | ||
|
||
fn window_event( | ||
&mut self, | ||
event_loop: &dyn ActiveEventLoop, | ||
_window_id: WindowId, | ||
event: WindowEvent, | ||
) { | ||
match event { | ||
WindowEvent::DragLeft { .. } | ||
| WindowEvent::DragEntered { .. } | ||
| WindowEvent::DragMoved { .. } | ||
| WindowEvent::DragDropped { .. } => { | ||
println!("{:?}", event); | ||
}, | ||
WindowEvent::RedrawRequested => { | ||
let window = self.window.as_ref().unwrap(); | ||
window.pre_present_notify(); | ||
fill::fill_window(window.as_ref()); | ||
}, | ||
WindowEvent::CloseRequested => { | ||
event_loop.exit(); | ||
}, | ||
_ => {}, | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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>>) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For @madsmtm: is this the right way to represent a sender param that may be null? The Swift docs note that for this specific callback, this parameter is nullable. (As a side node, kinda alarming how the Objective-C docs just don't mention its nullability at all, as far as I can tell. Any idea what's going on there?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is correct, see also the docs in The docs on Apple's website don't mention it, probably to be less verbose, but the header does contain a nullability attribute: // AppKit.framework/Headers/NSDragging.h
- (void)draggingExited:(nullable id <NSDraggingInfo>)sender; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Please keep the name as |
||
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 } ); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This extra text with "may be negative" sounds confusing, and probably only relevant on Wayland given that the coordinates are actually in window coordinates, and not surface coordinates. But that's to be resolved in #3891.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By "surface coordinates", do you mean relative to the contents of the window? Because the drag position is currently in surface coordinates, to match the pointer position. Unless I'm misunderstanding.
If you run the
dnd
example and drag a file over the window's title bar, the y-position will be negative in macOS and Windows. On X11, no drag events are emitted unless you're dragging over the window's contents/surface.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is that "window coordinates" are relative to the top-left corner of the window including decorations, and that "surface coordinates" are relative to the top-left corner of the window's contents. Pointer events are in surface coordinates.
DnD events in Windows and X11 were also in surface coordinates in the original PR, so I changed macOS to match those platforms (EDIT: and also the pointer events).