From 1921d477ff6520182498e73b3bc960cf9f84478d Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Mon, 19 Jun 2023 19:11:39 +0300 Subject: [PATCH 1/2] Start implementing drag and drop for Mac --- src/macos/keyboard.rs | 4 ++ src/macos/mod.rs | 8 +++ src/macos/view.rs | 116 ++++++++++++++++++++++++++++++++++++++++-- src/macos/window.rs | 4 ++ 4 files changed, 129 insertions(+), 3 deletions(-) diff --git a/src/macos/keyboard.rs b/src/macos/keyboard.rs index ba2bdc42..7e41c95f 100644 --- a/src/macos/keyboard.rs +++ b/src/macos/keyboard.rs @@ -273,6 +273,10 @@ impl KeyboardState { KeyboardState { last_mods } } + pub(crate) fn last_mods(&self) -> NSEventModifierFlags { + self.last_mods + } + pub(crate) fn process_native_event(&mut self, event: id) -> Option { unsafe { let event_type = event.eventType(); diff --git a/src/macos/mod.rs b/src/macos/mod.rs index e1fa7b87..02a53c80 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -2,4 +2,12 @@ mod keyboard; mod view; mod window; +use cocoa::foundation::NSUInteger; pub use window::*; + +const NSDragOperationNone: NSUInteger = 0; +const NSDragOperationCopy: NSUInteger = 1; +const NSDragOperationLink: NSUInteger = 2; +const NSDragOperationGeneric: NSUInteger = 4; +const NSDragOperationMove: NSUInteger = 16; + diff --git a/src/macos/view.rs b/src/macos/view.rs index 52c93110..ded1a9bd 100644 --- a/src/macos/view.rs +++ b/src/macos/view.rs @@ -1,8 +1,8 @@ use std::ffi::c_void; -use cocoa::appkit::{NSEvent, NSView, NSWindow}; +use cocoa::appkit::{NSEvent, NSView, NSWindow, NSFilenamesPboardType}; use cocoa::base::{id, nil, BOOL, NO, YES}; -use cocoa::foundation::{NSArray, NSPoint, NSRect, NSSize}; +use cocoa::foundation::{NSArray, NSPoint, NSRect, NSSize, NSUInteger}; use objc::{ class, @@ -16,9 +16,10 @@ use uuid::Uuid; use crate::MouseEvent::{ButtonPressed, ButtonReleased}; use crate::{ Event, EventStatus, MouseButton, MouseEvent, Point, ScrollDelta, Size, WindowEvent, WindowInfo, - WindowOpenOptions, + WindowOpenOptions, DropData, DropEffect, }; +use super::{NSDragOperationGeneric, NSDragOperationCopy, NSDragOperationMove, NSDragOperationLink, NSDragOperationNone}; use super::keyboard::make_modifiers; use super::window::WindowState; @@ -105,6 +106,8 @@ pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id { view.initWithFrame_(NSRect::new(NSPoint::new(0., 0.), NSSize::new(size.width, size.height))); + let _: id = msg_send![view, registerForDraggedTypes: NSArray::arrayWithObjects(nil, &[NSFilenamesPboardType])]; + view } @@ -154,6 +157,27 @@ unsafe fn create_view_class() -> &'static Class { view_did_change_backing_properties as extern "C" fn(&Object, Sel, id), ); + class.add_method( + sel!(draggingEntered:), + dragging_entered as extern "C" fn(&Object, Sel, id) -> NSUInteger, + ); + class.add_method( + sel!(prepareForDragOperation:), + prepare_for_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL, + ); + class.add_method( + sel!(performDragOperation:), + perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL, + ); + class.add_method( + sel!(draggingUpdated:), + dragging_updated as extern "C" fn(&Object, Sel, id) -> NSUInteger, + ); + class.add_method( + sel!(draggingExited:), + dragging_exited as extern "C" fn(&Object, Sel, id), + ); + add_mouse_button_class_method!(class, mouseDown, ButtonPressed, MouseButton::Left); add_mouse_button_class_method!(class, mouseUp, ButtonReleased, MouseButton::Left); add_mouse_button_class_method!(class, rightMouseDown, ButtonPressed, MouseButton::Right); @@ -372,3 +396,89 @@ extern "C" fn scroll_wheel(this: &Object, _: Sel, event: id) { modifiers: make_modifiers(modifiers), })); } + +fn get_drag_position(sender: id) -> Point { + let point: NSPoint = unsafe { msg_send![sender, draggingLocation] }; + Point::new(point.x, point.y) +} + +fn get_drop_data(sender: id) -> DropData { + DropData::None +} + +fn on_event(window_state: &mut WindowState, event: MouseEvent) -> NSUInteger { + let event_status = window_state.trigger_event(Event::Mouse(event)); + match event_status { + EventStatus::AcceptDrop(DropEffect::Copy) => NSDragOperationCopy, + EventStatus::AcceptDrop(DropEffect::Move) => NSDragOperationMove, + EventStatus::AcceptDrop(DropEffect::Link) => NSDragOperationLink, + EventStatus::AcceptDrop(DropEffect::Scroll) => NSDragOperationGeneric, + _ => NSDragOperationNone, + } +} + +extern "C" fn dragging_entered(this: &Object, _sel: Sel, sender: id) -> NSUInteger { + let state: &mut WindowState = unsafe { WindowState::from_field(this) }; + let modifiers = state.keyboard_state().last_mods(); + let drop_data = get_drop_data(sender); + + let event = MouseEvent::DragEntered { + position: get_drag_position(sender), + modifiers: make_modifiers(modifiers), + data: drop_data, + }; + + on_event(state, event) +} + +extern "C" fn dragging_updated(this: &Object, _sel: Sel, dragging_info: id) -> NSUInteger { + if let Some(drag_info) = unsafe { get_drag_info(dragging_info) } { + let state: &mut WindowState = unsafe { WindowState::from_field(this) }; + let event = WindowEvent::DragUpdated(drag_info); + state.trigger_event(Event::Window(event)); + } + 4 // NSDragOperationGeneric +} + +extern "C" fn prepare_for_drag_operation(_this: &Object, _sel: Sel, _dragging_info: id) -> BOOL { + YES +} + +extern "C" fn perform_drag_operation(this: &Object, _sel: Sel, dragging_info: id) -> BOOL { + if let Some(drag_info) = unsafe { get_drag_info(dragging_info) } { + let state: &mut WindowState = unsafe { WindowState::from_field(this) }; + let event = WindowEvent::DragPerformed(drag_info); + state.trigger_event(Event::Window(event)); + } + YES +} + +extern "C" fn dragging_exited(this: &Object, _sel: Sel, dragging_info: id) { + let drag_info = unsafe { get_drag_info(dragging_info) }; + let state: &mut WindowState = unsafe { WindowState::from_field(this) }; + let event = WindowEvent::DragExited(drag_info); + state.trigger_event(Event::Window(event)); +} + +unsafe fn get_drag_info(dragging_info: id) -> Option { + if dragging_info == nil { + return None; + } + + let pasteboard: id = msg_send![dragging_info, draggingPasteboard]; + let file_list: id = msg_send![pasteboard, propertyListForType: NSFilenamesPboardType]; + + if file_list == nil { + return None; + } + + let mut file_vec: Vec = vec![]; + let count = NSArray::count(file_list); + if count > 0 { + let data = NSArray::objectAtIndex(file_list, 0); + let str = from_nsstring(data); + file_vec.push(str); + } + + Some(DragInfo::FilesDragged { files: file_vec }) +} \ No newline at end of file diff --git a/src/macos/window.rs b/src/macos/window.rs index 54046ddd..67e8d201 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -415,6 +415,10 @@ impl WindowState { } } + pub(super) fn keyboard_state(&self) -> &KeyboardState { + &self.keyboard_state + } + pub(super) fn process_native_key_event(&mut self, event: *mut Object) -> Option { self.keyboard_state.process_native_event(event) } From 8270260382cb0a212a14f17ba1ef229c5b56de02 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Tue, 20 Jun 2023 17:30:30 +0300 Subject: [PATCH 2/2] Working drag and drop for Mac OS --- src/macos/view.rs | 96 +++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 41 deletions(-) diff --git a/src/macos/view.rs b/src/macos/view.rs index ded1a9bd..b0714dac 100644 --- a/src/macos/view.rs +++ b/src/macos/view.rs @@ -20,7 +20,7 @@ use crate::{ }; use super::{NSDragOperationGeneric, NSDragOperationCopy, NSDragOperationMove, NSDragOperationLink, NSDragOperationNone}; -use super::keyboard::make_modifiers; +use super::keyboard::{make_modifiers, from_nsstring}; use super::window::WindowState; /// Name of the field used to store the `WindowState` pointer. @@ -403,7 +403,26 @@ fn get_drag_position(sender: id) -> Point { } fn get_drop_data(sender: id) -> DropData { - DropData::None + if sender == nil { + return DropData::None; + } + + unsafe { + let pasteboard: id = msg_send![sender, draggingPasteboard]; + let file_list: id = msg_send![pasteboard, propertyListForType: NSFilenamesPboardType]; + + if file_list == nil { + return DropData::None; + } + + let mut files = vec![]; + for i in 0..NSArray::count(file_list) { + let data = NSArray::objectAtIndex(file_list, i); + files.push(from_nsstring(data).into()); + } + + DropData::Files(files) + } } fn on_event(window_state: &mut WindowState, event: MouseEvent) -> NSUInteger { @@ -431,54 +450,49 @@ extern "C" fn dragging_entered(this: &Object, _sel: Sel, sender: id) -> NSUInteg on_event(state, event) } -extern "C" fn dragging_updated(this: &Object, _sel: Sel, dragging_info: id) -> NSUInteger { - if let Some(drag_info) = unsafe { get_drag_info(dragging_info) } { - let state: &mut WindowState = unsafe { WindowState::from_field(this) }; - let event = WindowEvent::DragUpdated(drag_info); - state.trigger_event(Event::Window(event)); - } - 4 // NSDragOperationGeneric -} +extern "C" fn dragging_updated(this: &Object, _sel: Sel, sender: id) -> NSUInteger { + let state: &mut WindowState = unsafe { WindowState::from_field(this) }; + let modifiers = state.keyboard_state().last_mods(); + let drop_data = get_drop_data(sender); -extern "C" fn prepare_for_drag_operation(_this: &Object, _sel: Sel, _dragging_info: id) -> BOOL { - YES + let event = MouseEvent::DragMoved { + position: get_drag_position(sender), + modifiers: make_modifiers(modifiers), + data: drop_data, + }; + + on_event(state, event) } -extern "C" fn perform_drag_operation(this: &Object, _sel: Sel, dragging_info: id) -> BOOL { - if let Some(drag_info) = unsafe { get_drag_info(dragging_info) } { - let state: &mut WindowState = unsafe { WindowState::from_field(this) }; - let event = WindowEvent::DragPerformed(drag_info); - state.trigger_event(Event::Window(event)); - } +extern "C" fn prepare_for_drag_operation(_this: &Object, _sel: Sel, sender: id) -> BOOL { + // Always accept drag operation if we get this far + // This function won't be called unless dragging_entered/updated + // has returned an acceptable operation YES } -extern "C" fn dragging_exited(this: &Object, _sel: Sel, dragging_info: id) { - let drag_info = unsafe { get_drag_info(dragging_info) }; +extern "C" fn perform_drag_operation(this: &Object, _sel: Sel, sender: id) -> BOOL { let state: &mut WindowState = unsafe { WindowState::from_field(this) }; - let event = WindowEvent::DragExited(drag_info); - state.trigger_event(Event::Window(event)); -} - -unsafe fn get_drag_info(dragging_info: id) -> Option { - if dragging_info == nil { - return None; - } + let modifiers = state.keyboard_state().last_mods(); + let drop_data = get_drop_data(sender); - let pasteboard: id = msg_send![dragging_info, draggingPasteboard]; - let file_list: id = msg_send![pasteboard, propertyListForType: NSFilenamesPboardType]; + let event = MouseEvent::DragDropped { + position: get_drag_position(sender), + modifiers: make_modifiers(modifiers), + data: drop_data, + }; - if file_list == nil { - return None; + let event_status = state.trigger_event(Event::Mouse(event)); + match event_status { + EventStatus::AcceptDrop(_) => YES, + _ => NO, } +} - let mut file_vec: Vec = vec![]; - let count = NSArray::count(file_list); - if count > 0 { - let data = NSArray::objectAtIndex(file_list, 0); - let str = from_nsstring(data); - file_vec.push(str); - } +extern "C" fn dragging_exited(this: &Object, _sel: Sel, sender: id) { + let state: &mut WindowState = unsafe { WindowState::from_field(this) }; + let modifiers = state.keyboard_state().last_mods(); + let drop_data = get_drop_data(sender); - Some(DragInfo::FilesDragged { files: file_vec }) -} \ No newline at end of file + on_event(state, MouseEvent::DragLeft); +}