Browse Source

Merge pull request #3925 from AvaloniaUI/osx-dnd-support

Osx dnd support (target: 0.9)
pull/4170/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
8e2a49d2e0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 49
      native/Avalonia.Native/inc/avalonia-native.h
  2. 4
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  3. 2
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme
  4. 3
      native/Avalonia.Native/src/OSX/AvnString.h
  5. 49
      native/Avalonia.Native/src/OSX/AvnString.mm
  6. 90
      native/Avalonia.Native/src/OSX/clipboard.mm
  7. 8
      native/Avalonia.Native/src/OSX/common.h
  8. 89
      native/Avalonia.Native/src/OSX/dnd.mm
  9. 19
      native/Avalonia.Native/src/OSX/main.mm
  10. 2
      native/Avalonia.Native/src/OSX/window.h
  11. 108
      native/Avalonia.Native/src/OSX/window.mm
  12. 12
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  13. 92
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  14. 8
      src/Avalonia.Controls/Application.cs
  15. 76
      src/Avalonia.Native/AvaloniaNativeDragSource.cs
  16. 16
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  17. 39
      src/Avalonia.Native/AvnString.cs
  18. 96
      src/Avalonia.Native/ClipboardImpl.cs
  19. 31
      src/Avalonia.Native/WindowImplBase.cs

49
native/Avalonia.Native/inc/avalonia-native.h

@ -24,6 +24,9 @@ struct IAvnGlSurfaceRenderTarget;
struct IAvnGlSurfaceRenderingSession; struct IAvnGlSurfaceRenderingSession;
struct IAvnAppMenu; struct IAvnAppMenu;
struct IAvnAppMenuItem; struct IAvnAppMenuItem;
struct IAvnStringArray;
struct IAvnDndResultCallback;
struct IAvnGCHandleDeallocatorCallback;
struct AvnSize struct AvnSize
{ {
@ -115,6 +118,22 @@ enum AvnInputModifiers
MiddleMouseButton = 64 MiddleMouseButton = 64
}; };
enum class AvnDragDropEffects
{
None = 0,
Copy = 1,
Move = 2,
Link = 4,
};
enum class AvnDragEventType
{
Enter,
Over,
Leave,
Drop
};
enum AvnWindowState enum AvnWindowState
{ {
Normal, Normal,
@ -165,7 +184,7 @@ enum AvnWindowEdge
AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown
{ {
public: public:
virtual HRESULT Initialize() = 0; virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator) = 0;
virtual IAvnMacOptions* GetMacOptions() = 0; virtual IAvnMacOptions* GetMacOptions() = 0;
virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) = 0; virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) = 0;
virtual HRESULT CreatePopup (IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) = 0; virtual HRESULT CreatePopup (IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) = 0;
@ -173,6 +192,7 @@ public:
virtual HRESULT CreateSystemDialogs (IAvnSystemDialogs** ppv) = 0; virtual HRESULT CreateSystemDialogs (IAvnSystemDialogs** ppv) = 0;
virtual HRESULT CreateScreens (IAvnScreens** ppv) = 0; virtual HRESULT CreateScreens (IAvnScreens** ppv) = 0;
virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0; virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0;
virtual HRESULT CreateDndClipboard(IAvnClipboard** ppv) = 0;
virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0; virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0;
virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) = 0; virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) = 0;
virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) = 0; virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) = 0;
@ -215,6 +235,8 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT ObtainNSWindowHandleRetained(void** retOut) = 0; virtual HRESULT ObtainNSWindowHandleRetained(void** retOut) = 0;
virtual HRESULT ObtainNSViewHandle(void** retOut) = 0; virtual HRESULT ObtainNSViewHandle(void** retOut) = 0;
virtual HRESULT ObtainNSViewHandleRetained(void** retOut) = 0; virtual HRESULT ObtainNSViewHandleRetained(void** retOut) = 0;
virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
IAvnClipboard* clipboard, IAvnDndResultCallback* cb, void* sourceHandle) = 0;
}; };
AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase
@ -250,6 +272,9 @@ AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown
virtual bool RawTextInputEvent (unsigned int timeStamp, const char* text) = 0; virtual bool RawTextInputEvent (unsigned int timeStamp, const char* text) = 0;
virtual void ScalingChanged(double scaling) = 0; virtual void ScalingChanged(double scaling) = 0;
virtual void RunRenderPriorityJobs() = 0; virtual void RunRenderPriorityJobs() = 0;
virtual AvnDragDropEffects DragEvent(AvnDragEventType type, AvnPoint position,
AvnInputModifiers modifiers, AvnDragDropEffects effects,
IAvnClipboard* clipboard, void* dataObjectHandle) = 0;
}; };
@ -333,8 +358,10 @@ AVNCOM(IAvnScreens, 0e) : IUnknown
AVNCOM(IAvnClipboard, 0f) : IUnknown AVNCOM(IAvnClipboard, 0f) : IUnknown
{ {
virtual HRESULT GetText (IAvnString**ppv) = 0; virtual HRESULT GetText (char* type, IAvnString**ppv) = 0;
virtual HRESULT SetText (void* utf8Text) = 0; virtual HRESULT SetText (char* type, void* utf8Text) = 0;
virtual HRESULT ObtainFormats(IAvnStringArray**ppv) = 0;
virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) = 0;
virtual HRESULT Clear() = 0; virtual HRESULT Clear() = 0;
}; };
@ -396,4 +423,20 @@ AVNCOM(IAvnAppMenuItem, 19) : IUnknown
virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) = 0; virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) = 0;
}; };
AVNCOM(IAvnStringArray, 20) : IUnknown
{
virtual unsigned int GetCount() = 0;
virtual HRESULT Get(unsigned int index, IAvnString**ppv) = 0;
};
AVNCOM(IAvnDndResultCallback, 21) : IUnknown
{
virtual void OnDragAndDropComplete(AvnDragDropEffects effecct) = 0;
};
AVNCOM(IAvnGCHandleDeallocatorCallback, 22) : IUnknown
{
virtual void FreeGCHandle(void* handle) = 0;
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative(); extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative();

4
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -12,6 +12,7 @@
1A3E5EAA23E9F26C00EDE661 /* IOSurface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */; }; 1A3E5EAA23E9F26C00EDE661 /* IOSurface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */; };
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */; }; 1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */; };
1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */; }; 1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */; };
1A465D10246AB61600C5858B /* dnd.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A465D0F246AB61600C5858B /* dnd.mm */; };
37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; }; 37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; };
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; }; 37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; }; 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
@ -33,6 +34,7 @@
1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOSurface.framework; path = System/Library/Frameworks/IOSurface.framework; sourceTree = SDKROOT; }; 1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOSurface.framework; path = System/Library/Frameworks/IOSurface.framework; sourceTree = SDKROOT; };
1A3E5EAD23E9FB1300EDE661 /* cgl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cgl.mm; sourceTree = "<group>"; }; 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cgl.mm; sourceTree = "<group>"; };
1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
1A465D0F246AB61600C5858B /* dnd.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = dnd.mm; sourceTree = "<group>"; };
37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = "<group>"; }; 37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = "<group>"; };
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; }; 379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; };
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; }; 37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; };
@ -92,6 +94,7 @@
5BF943652167AD1D009CAE35 /* cursor.h */, 5BF943652167AD1D009CAE35 /* cursor.h */,
5B21A981216530F500CEE36E /* cursor.mm */, 5B21A981216530F500CEE36E /* cursor.mm */,
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */, 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */,
1A465D0F246AB61600C5858B /* dnd.mm */,
AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */, AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */,
AB661C212148288600291242 /* common.h */, AB661C212148288600291242 /* common.h */,
379860FE214DA0C000CD0246 /* KeyTransform.h */, 379860FE214DA0C000CD0246 /* KeyTransform.h */,
@ -196,6 +199,7 @@
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */, 37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */, 520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */, 37A517B32159597E00FBA241 /* Screens.mm in Sources */,
1A465D10246AB61600C5858B /* dnd.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */, AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */, 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
AB661C202148286E00291242 /* window.mm in Sources */, AB661C202148286E00291242 /* window.mm in Sources */,

2
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme

@ -58,7 +58,7 @@
</MacroExpansion> </MacroExpansion>
<CommandLineArguments> <CommandLineArguments>
<CommandLineArgument <CommandLineArgument
argument = "bin/Debug/netcoreapp2.0/ControlCatalog.NetCore.dll" argument = "bin/Debug/netcoreapp3.1/ControlCatalog.NetCore.dll"
isEnabled = "YES"> isEnabled = "YES">
</CommandLineArgument> </CommandLineArgument>
</CommandLineArguments> </CommandLineArguments>

3
native/Avalonia.Native/src/OSX/AvnString.h

@ -10,5 +10,6 @@
#define AvnString_h #define AvnString_h
extern IAvnString* CreateAvnString(NSString* string); extern IAvnString* CreateAvnString(NSString* string);
extern IAvnStringArray* CreateAvnStringArray(NSArray<NSString*>* array);
extern IAvnStringArray* CreateAvnStringArray(NSString* string);
#endif /* AvnString_h */ #endif /* AvnString_h */

49
native/Avalonia.Native/src/OSX/AvnString.mm

@ -7,6 +7,7 @@
// //
#include "common.h" #include "common.h"
#include <vector>
class AvnStringImpl : public virtual ComSingleObject<IAvnString, &IID_IAvnString> class AvnStringImpl : public virtual ComSingleObject<IAvnString, &IID_IAvnString>
{ {
@ -61,7 +62,55 @@ public:
} }
}; };
class AvnStringArrayImpl : public virtual ComSingleObject<IAvnStringArray, &IID_IAvnStringArray>
{
private:
std::vector<ComPtr<IAvnString>> _list;
public:
FORWARD_IUNKNOWN()
AvnStringArrayImpl(NSArray<NSString*>* array)
{
for(int c = 0; c < [array count]; c++)
{
ComPtr<IAvnString> s;
*s.getPPV() = new AvnStringImpl([array objectAtIndex:c]);
_list.push_back(s);
}
}
AvnStringArrayImpl(NSString* string)
{
ComPtr<IAvnString> s;
*s.getPPV() = new AvnStringImpl(string);
_list.push_back(s);
}
virtual unsigned int GetCount() override
{
return (unsigned int)_list.size();
}
virtual HRESULT Get(unsigned int index, IAvnString**ppv) override
{
if(_list.size() <= index)
return E_INVALIDARG;
*ppv = _list[index].getRetainedReference();
return S_OK;
}
};
IAvnString* CreateAvnString(NSString* string) IAvnString* CreateAvnString(NSString* string)
{ {
return new AvnStringImpl(string); return new AvnStringImpl(string);
} }
IAvnStringArray* CreateAvnStringArray(NSArray<NSString*> * array)
{
return new AvnStringArrayImpl(array);
}
IAvnStringArray* CreateAvnStringArray(NSString* string)
{
return new AvnStringArrayImpl(string);
}

90
native/Avalonia.Native/src/OSX/clipboard.mm

@ -6,16 +6,27 @@
class Clipboard : public ComSingleObject<IAvnClipboard, &IID_IAvnClipboard> class Clipboard : public ComSingleObject<IAvnClipboard, &IID_IAvnClipboard>
{ {
private:
NSPasteboard* _pb;
NSPasteboardItem* _item;
public: public:
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
Clipboard() Clipboard(NSPasteboard* pasteboard, NSPasteboardItem* item)
{ {
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; if(pasteboard == nil && item == nil)
[pasteBoard stringForType:NSPasteboardTypeString]; pasteboard = [NSPasteboard generalPasteboard];
_pb = pasteboard;
_item = item;
} }
virtual HRESULT GetText (IAvnString**ppv) override NSPasteboardItem* TryGetItem()
{
return _item;
}
virtual HRESULT GetText (char* type, IAvnString**ppv) override
{ {
@autoreleasepool @autoreleasepool
{ {
@ -23,20 +34,53 @@ public:
{ {
return E_POINTER; return E_POINTER;
} }
NSString* typeString = [NSString stringWithUTF8String:(const char*)type];
NSString* string = _item == nil ? [_pb stringForType:typeString] : [_item stringForType:typeString];
*ppv = CreateAvnString([[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString]); *ppv = CreateAvnString(string);
return S_OK; return S_OK;
} }
} }
virtual HRESULT SetText (void* utf8String) override virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) override
{ {
@autoreleasepool @autoreleasepool
{ {
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; *ppv= nil;
[pasteBoard clearContents]; NSString* typeString = [NSString stringWithUTF8String:(const char*)type];
[pasteBoard setString:[NSString stringWithUTF8String:(const char*)utf8String] forType:NSPasteboardTypeString]; NSObject* data = _item == nil ? [_pb propertyListForType: typeString] : [_item propertyListForType: typeString];
if(data == nil)
return S_OK;
if([data isKindOfClass: [NSString class]])
{
*ppv = CreateAvnStringArray((NSString*) data);
return S_OK;
}
NSArray* arr = (NSArray*)data;
for(int c = 0; c < [arr count]; c++)
if(![[arr objectAtIndex:c] isKindOfClass:[NSString class]])
return E_INVALIDARG;
*ppv = CreateAvnStringArray(arr);
return S_OK;
}
}
virtual HRESULT SetText (char* type, void* utf8String) override
{
Clear();
@autoreleasepool
{
auto string = [NSString stringWithUTF8String:(const char*)utf8String];
auto typeString = [NSString stringWithUTF8String:(const char*)type];
if(_item == nil)
[_pb setString: string forType: typeString];
else
[_item setString: string forType:typeString];
} }
return S_OK; return S_OK;
@ -46,16 +90,34 @@ public:
{ {
@autoreleasepool @autoreleasepool
{ {
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; if(_item != nil)
[pasteBoard clearContents]; _item = [NSPasteboardItem new];
[pasteBoard setString:@"" forType:NSPasteboardTypeString]; else
{
[_pb clearContents];
[_pb setString:@"" forType:NSPasteboardTypeString];
}
} }
return S_OK; return S_OK;
} }
virtual HRESULT ObtainFormats(IAvnStringArray** ppv) override
{
*ppv = CreateAvnStringArray(_item == nil ? [_pb types] : [_item types]);
return S_OK;
}
}; };
extern IAvnClipboard* CreateClipboard() extern IAvnClipboard* CreateClipboard(NSPasteboard* pb, NSPasteboardItem* item)
{
return new Clipboard(pb, item);
}
extern NSPasteboardItem* TryGetPasteboardItem(IAvnClipboard*cb)
{ {
return new Clipboard(); auto clipboard = dynamic_cast<Clipboard*>(cb);
if(clipboard == nil)
return nil;
return clipboard->TryGetItem();
} }

8
native/Avalonia.Native/src/OSX/common.h

@ -11,11 +11,17 @@
#include <pthread.h> #include <pthread.h>
extern IAvnPlatformThreadingInterface* CreatePlatformThreading(); extern IAvnPlatformThreadingInterface* CreatePlatformThreading();
extern void FreeAvnGCHandle(void* handle);
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl); extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl);
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl); extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl);
extern IAvnSystemDialogs* CreateSystemDialogs(); extern IAvnSystemDialogs* CreateSystemDialogs();
extern IAvnScreens* CreateScreens(); extern IAvnScreens* CreateScreens();
extern IAvnClipboard* CreateClipboard(); extern IAvnClipboard* CreateClipboard(NSPasteboard*, NSPasteboardItem*);
extern NSPasteboardItem* TryGetPasteboardItem(IAvnClipboard*);
extern NSObject<NSDraggingSource>* CreateDraggingSource(NSDragOperation op, IAvnDndResultCallback* cb, void* handle);
extern void* GetAvnDataObjectHandleFromDraggingInfo(NSObject<NSDraggingInfo>* info);
extern NSString* GetAvnCustomDataType();
extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop);
extern IAvnCursorFactory* CreateCursorFactory(); extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlDisplay* GetGlDisplay(); extern IAvnGlDisplay* GetGlDisplay();
extern IAvnAppMenu* CreateAppMenu(); extern IAvnAppMenu* CreateAppMenu();

89
native/Avalonia.Native/src/OSX/dnd.mm

@ -0,0 +1,89 @@
#include "common.h"
extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop)
{
int effects = 0;
if((nsop & NSDragOperationCopy) != 0)
effects |= (int)AvnDragDropEffects::Copy;
if((nsop & NSDragOperationMove) != 0)
effects |= (int)AvnDragDropEffects::Move;
if((nsop & NSDragOperationLink) != 0)
effects |= (int)AvnDragDropEffects::Link;
return (AvnDragDropEffects)effects;
};
extern NSString* GetAvnCustomDataType()
{
char buffer[256];
sprintf(buffer, "net.avaloniaui.inproc.uti.n%in", getpid());
return [NSString stringWithUTF8String:buffer];
}
@interface AvnDndSource : NSObject<NSDraggingSource>
@end
@implementation AvnDndSource
{
NSDragOperation _operation;
ComPtr<IAvnDndResultCallback> _cb;
void* _sourceHandle;
};
- (NSDragOperation)draggingSession:(nonnull NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
return NSDragOperationCopy;
}
- (AvnDndSource*) initWithOperation: (NSDragOperation)operation
andCallback: (IAvnDndResultCallback*) cb
andSourceHandle: (void*) handle
{
self = [super init];
_operation = operation;
_cb = cb;
_sourceHandle = handle;
return self;
}
- (void)draggingSession:(NSDraggingSession *)session
endedAtPoint:(NSPoint)screenPoint
operation:(NSDragOperation)operation
{
if(_cb != nil)
{
auto cb = _cb;
_cb = nil;
cb->OnDragAndDropComplete(ConvertDragDropEffects(operation));
}
if(_sourceHandle != nil)
{
FreeAvnGCHandle(_sourceHandle);
_sourceHandle = nil;
}
}
- (void*) gcHandle
{
return _sourceHandle;
}
@end
extern NSObject<NSDraggingSource>* CreateDraggingSource(NSDragOperation op, IAvnDndResultCallback* cb, void* handle)
{
return [[AvnDndSource alloc] initWithOperation:op andCallback:cb andSourceHandle:handle];
};
extern void* GetAvnDataObjectHandleFromDraggingInfo(NSObject<NSDraggingInfo>* info)
{
id obj = [info draggingSource];
if(obj == nil)
return nil;
if([obj isKindOfClass: [AvnDndSource class]])
{
auto src = (AvnDndSource*)obj;
return [src gcHandle];
}
return nil;
}

19
native/Avalonia.Native/src/OSX/main.mm

@ -154,14 +154,15 @@ public:
} }
@end @end
static ComPtr<IAvnGCHandleDeallocatorCallback> _deallocator;
class AvaloniaNative : public ComSingleObject<IAvaloniaNativeFactory, &IID_IAvaloniaNativeFactory> class AvaloniaNative : public ComSingleObject<IAvaloniaNativeFactory, &IID_IAvaloniaNativeFactory>
{ {
public: public:
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
virtual HRESULT Initialize() override virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator) override
{ {
_deallocator = deallocator;
@autoreleasepool{ @autoreleasepool{
[[ThreadingInitializer new] do]; [[ThreadingInitializer new] do];
} }
@ -211,7 +212,13 @@ public:
virtual HRESULT CreateClipboard(IAvnClipboard** ppv) override virtual HRESULT CreateClipboard(IAvnClipboard** ppv) override
{ {
*ppv = ::CreateClipboard (); *ppv = ::CreateClipboard (nil, nil);
return S_OK;
}
virtual HRESULT CreateDndClipboard(IAvnClipboard** ppv) override
{
*ppv = ::CreateClipboard (nil, [NSPasteboardItem new]);
return S_OK; return S_OK;
} }
@ -273,6 +280,12 @@ extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()
return new AvaloniaNative(); return new AvaloniaNative();
}; };
extern void FreeAvnGCHandle(void* handle)
{
if(_deallocator != nil)
_deallocator->FreeGCHandle(handle);
}
NSSize ToNSSize (AvnSize s) NSSize ToNSSize (AvnSize s)
{ {
NSSize result; NSSize result;

2
native/Avalonia.Native/src/OSX/window.h

@ -6,7 +6,7 @@
class WindowBaseImpl; class WindowBaseImpl;
@interface AvnView : NSView<NSTextInputClient> @interface AvnView : NSView<NSTextInputClient, NSDraggingDestination>
-(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent; -(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
-(NSEvent* _Nonnull) lastMouseDownEvent; -(NSEvent* _Nonnull) lastMouseDownEvent;
-(AvnPoint) translateLocalPoint:(AvnPoint)pt; -(AvnPoint) translateLocalPoint:(AvnPoint)pt;

108
native/Avalonia.Native/src/OSX/window.mm

@ -393,6 +393,50 @@ public:
*ppv = [renderTarget createSurfaceRenderTarget]; *ppv = [renderTarget createSurfaceRenderTarget];
return *ppv == nil ? E_FAIL : S_OK; return *ppv == nil ? E_FAIL : S_OK;
} }
virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
IAvnClipboard* clipboard, IAvnDndResultCallback* cb,
void* sourceHandle) override
{
auto item = TryGetPasteboardItem(clipboard);
[item setString:@"" forType:GetAvnCustomDataType()];
if(item == nil)
return E_INVALIDARG;
if(View == NULL)
return E_FAIL;
auto nsevent = [NSApp currentEvent];
auto nseventType = [nsevent type];
// If current event isn't a mouse one (probably due to malfunctioning user app)
// attempt to forge a new one
if(!((nseventType >= NSEventTypeLeftMouseDown && nseventType <= NSEventTypeMouseExited)
|| (nseventType >= NSEventTypeOtherMouseDown && nseventType <= NSEventTypeOtherMouseDragged)))
{
auto nspoint = [Window convertBaseToScreen: ToNSPoint(point)];
CGPoint cgpoint = NSPointToCGPoint(nspoint);
auto cgevent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, cgpoint, kCGMouseButtonLeft);
nsevent = [NSEvent eventWithCGEvent: cgevent];
CFRelease(cgevent);
}
auto dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter: item];
auto dragItemImage = [NSImage imageNamed:NSImageNameMultipleDocuments];
NSRect dragItemRect = {(float)point.X, (float)point.Y, [dragItemImage size].width, [dragItemImage size].height};
[dragItem setDraggingFrame: dragItemRect contents: dragItemImage];
int op = 0; int ieffects = (int)effects;
if((ieffects & (int)AvnDragDropEffects::Copy) != 0)
op |= NSDragOperationCopy;
if((ieffects & (int)AvnDragDropEffects::Link) != 0)
op |= NSDragOperationLink;
if((ieffects & (int)AvnDragDropEffects::Move) != 0)
op |= NSDragOperationMove;
[View beginDraggingSessionWithItems: @[dragItem] event: nsevent
source: CreateDraggingSource((NSDragOperation) op, cb, sourceHandle)];
return S_OK;
}
protected: protected:
virtual NSWindowStyleMask GetStyle() virtual NSWindowStyleMask GetStyle()
@ -716,7 +760,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_area = nullptr; _area = nullptr;
_lastPixelSize.Height = 100; _lastPixelSize.Height = 100;
_lastPixelSize.Width = 100; _lastPixelSize.Width = 100;
[self registerForDraggedTypes: @[@"public.data", GetAvnCustomDataType()]];
return self; return self;
} }
@ -1076,6 +1120,68 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return result; return result;
} }
- (NSDragOperation)triggerAvnDragEvent: (AvnDragEventType) type info: (id <NSDraggingInfo>)info
{
auto localPoint = [self convertPoint:[info draggingLocation] toView:self];
auto avnPoint = [self toAvnPoint:localPoint];
auto point = [self translateLocalPoint:avnPoint];
auto modifiers = [self getModifiers:[[NSApp currentEvent] modifierFlags]];
NSDragOperation nsop = [info draggingSourceOperationMask];
auto effects = ConvertDragDropEffects(nsop);
int reffects = (int)_parent->BaseEvents
->DragEvent(type, point, modifiers, effects,
CreateClipboard([info draggingPasteboard], nil),
GetAvnDataObjectHandleFromDraggingInfo(info));
NSDragOperation ret = 0;
// Ensure that the managed part didn't add any new effects
reffects = (int)effects & (int)reffects;
// OSX requires exactly one operation
if((reffects & (int)AvnDragDropEffects::Copy) != 0)
ret = NSDragOperationCopy;
else if((reffects & (int)AvnDragDropEffects::Move) != 0)
ret = NSDragOperationMove;
else if((reffects & (int)AvnDragDropEffects::Link) != 0)
ret = NSDragOperationLink;
if(ret == 0)
ret = NSDragOperationNone;
return ret;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
return [self triggerAvnDragEvent: AvnDragEventType::Enter info:sender];
}
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender];
}
- (void)draggingExited:(id <NSDraggingInfo>)sender
{
[self triggerAvnDragEvent: AvnDragEventType::Leave info:sender];
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender] != NSDragOperationNone;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
return [self triggerAvnDragEvent: AvnDragEventType::Drop info:sender] != NSDragOperationNone;
}
- (void)concludeDragOperation:(nullable id <NSDraggingInfo>)sender
{
}
@end @end

12
samples/ControlCatalog/Pages/DragAndDropPage.xaml

@ -9,9 +9,15 @@
Margin="0,16,0,0" Margin="0,16,0,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Spacing="16"> Spacing="16">
<Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMe"> <StackPanel>
<TextBlock Name="DragState">Drag Me</TextBlock> <Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMeText">
</Border> <TextBlock Name="DragStateText">Drag Me</TextBlock>
</Border>
<Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMeCustom">
<TextBlock Name="DragStateCustom">Drag Me (custom)</TextBlock>
</Border>
</StackPanel>
<Border Background="{DynamicResource ThemeAccentBrush2}" Padding="16" <Border Background="{DynamicResource ThemeAccentBrush2}" Padding="16"
DragDrop.AllowDrop="True"> DragDrop.AllowDrop="True">
<TextBlock Name="DropState">Drop some text or files here</TextBlock> <TextBlock Name="DropState">Drop some text or files here</TextBlock>

92
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@ -3,69 +3,85 @@ using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
namespace ControlCatalog.Pages namespace ControlCatalog.Pages
{ {
public class DragAndDropPage : UserControl public class DragAndDropPage : UserControl
{ {
private TextBlock _DropState; TextBlock _DropState;
private TextBlock _DragState; private const string CustomFormat = "application/xxx-avalonia-controlcatalog-custom";
private Border _DragMe;
private int DragCount = 0;
public DragAndDropPage() public DragAndDropPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
_DropState = this.Find<TextBlock>("DropState");
_DragMe.PointerPressed += DoDrag; int textCount = 0;
SetupDnd("Text", d => d.Set(DataFormats.Text,
$"Text was dragged {++textCount} times"));
AddHandler(DragDrop.DropEvent, Drop); SetupDnd("Custom", d => d.Set(CustomFormat, "Test123"));
AddHandler(DragDrop.DragOverEvent, DragOver);
} }
private async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e) void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects = DragDropEffects.Copy)
{ {
DataObject dragData = new DataObject(); var dragMe = this.Find<Border>("DragMe" + suffix);
dragData.Set(DataFormats.Text, $"You have dragged text {++DragCount} times"); var dragState = this.Find<TextBlock>("DragState"+suffix);
var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy); async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
switch(result)
{ {
case DragDropEffects.Copy: var dragData = new DataObject();
_DragState.Text = "The text was copied"; break; factory(dragData);
case DragDropEffects.Link:
_DragState.Text = "The text was linked"; break; var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
case DragDropEffects.None: switch (result)
_DragState.Text = "The drag operation was canceled"; break; {
case DragDropEffects.Copy:
dragState.Text = "Data was copied";
break;
case DragDropEffects.Link:
dragState.Text = "Data was linked";
break;
case DragDropEffects.None:
dragState.Text = "The drag operation was canceled";
break;
}
} }
}
private void DragOver(object sender, DragEventArgs e) void DragOver(object sender, DragEventArgs e)
{ {
// Only allow Copy or Link as Drop Operations. // Only allow Copy or Link as Drop Operations.
e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link); e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link);
// Only allow if the dragged data contains text or filenames. // Only allow if the dragged data contains text or filenames.
if (!e.Data.Contains(DataFormats.Text) && !e.Data.Contains(DataFormats.FileNames)) if (!e.Data.Contains(DataFormats.Text)
e.DragEffects = DragDropEffects.None; && !e.Data.Contains(DataFormats.FileNames)
} && !e.Data.Contains(CustomFormat))
e.DragEffects = DragDropEffects.None;
}
private void Drop(object sender, DragEventArgs e) void Drop(object sender, DragEventArgs e)
{ {
if (e.Data.Contains(DataFormats.Text)) if (e.Data.Contains(DataFormats.Text))
_DropState.Text = e.Data.GetText(); _DropState.Text = e.Data.GetText();
else if (e.Data.Contains(DataFormats.FileNames)) else if (e.Data.Contains(DataFormats.FileNames))
_DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames()); _DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames());
else if (e.Data.Contains(CustomFormat))
_DropState.Text = "Custom: " + e.Data.Get(CustomFormat);
}
dragMe.PointerPressed += DoDrag;
AddHandler(DragDrop.DropEvent, Drop);
AddHandler(DragDrop.DragOverEvent, DragOver);
} }
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
_DropState = this.Find<TextBlock>("DropState");
_DragState = this.Find<TextBlock>("DragState");
_DragMe = this.Find<Border>("DragMe");
} }
} }
} }

8
src/Avalonia.Controls/Application.cs

@ -219,8 +219,12 @@ namespace Avalonia
.Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>() .Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>()
.Bind<IStyler>().ToConstant(_styler) .Bind<IStyler>().ToConstant(_styler)
.Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance) .Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance)
.Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance) .Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance);
.Bind<IPlatformDragSource>().ToTransient<InProcessDragSource>();
// TODO: Fix this, for now we keep this behavior since someone might be relying on it in 0.9.x
if (AvaloniaLocator.Current.GetService<IPlatformDragSource>() == null)
AvaloniaLocator.CurrentMutable
.Bind<IPlatformDragSource>().ToTransient<InProcessDragSource>();
var clock = new RenderLoopClock(); var clock = new RenderLoopClock();
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable

76
src/Avalonia.Native/AvaloniaNativeDragSource.cs

@ -0,0 +1,76 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Native.Interop;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Native
{
class AvaloniaNativeDragSource : IPlatformDragSource
{
private readonly IAvaloniaNativeFactory _factory;
public AvaloniaNativeDragSource(IAvaloniaNativeFactory factory)
{
_factory = factory;
}
TopLevel FindRoot(IInteractive interactive)
{
while (interactive != null && !(interactive is IVisual))
interactive = interactive.InteractiveParent;
if (interactive == null)
return null;
var visual = (IVisual)interactive;
return visual.VisualRoot as TopLevel;
}
class DndCallback : CallbackBase, IAvnDndResultCallback
{
private TaskCompletionSource<DragDropEffects> _tcs;
public DndCallback(TaskCompletionSource<DragDropEffects> tcs)
{
_tcs = tcs;
}
public void OnDragAndDropComplete(AvnDragDropEffects effect)
{
_tcs?.TrySetResult((DragDropEffects)effect);
_tcs = null;
}
}
public Task<DragDropEffects> DoDragDrop(PointerEventArgs triggerEvent, IDataObject data, DragDropEffects allowedEffects)
{
// Sanity check
var tl = FindRoot(triggerEvent.Source);
var view = tl?.PlatformImpl as WindowBaseImpl;
if (view == null)
throw new ArgumentException();
triggerEvent.Pointer.Capture(null);
var tcs = new TaskCompletionSource<DragDropEffects>();
var clipboardImpl = _factory.CreateDndClipboard();
using (var clipboard = new ClipboardImpl(clipboardImpl))
using (var cb = new DndCallback(tcs))
{
if (data.Contains(DataFormats.Text))
// API is synchronous, so it's OK
clipboard.SetTextAsync(data.GetText()).Wait();
view.BeginDraggingSession((AvnDragDropEffects)allowedEffects,
triggerEvent.GetPosition(tl).ToAvnPoint(), clipboardImpl, cb,
GCHandle.ToIntPtr(GCHandle.Alloc(data)));
}
return tcs.Task;
}
}
}

16
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -79,17 +79,25 @@ namespace Avalonia.Native
_factory = factory; _factory = factory;
} }
class GCHandleDeallocator : CallbackBase, IAvnGCHandleDeallocatorCallback
{
public void FreeGCHandle(IntPtr handle)
{
GCHandle.FromIntPtr(handle).Free();
}
}
void DoInitialize(AvaloniaNativePlatformOptions options) void DoInitialize(AvaloniaNativePlatformOptions options)
{ {
_options = options; _options = options;
_factory.Initialize(); _factory.Initialize(new GCHandleDeallocator());
if (_factory.MacOptions != null) if (_factory.MacOptions != null)
{ {
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>(); var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>();
_factory.MacOptions.ShowInDock = macOpts?.ShowInDock != false ? 1 : 0; _factory.MacOptions.ShowInDock = macOpts?.ShowInDock != false ? 1 : 0;
} }
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>() .Bind<IPlatformThreadingInterface>()
.ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface())) .ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface()))
@ -103,7 +111,9 @@ namespace Avalonia.Native
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60)) .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs())) .Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows)) .Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider()); .Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
;
if (_options.UseGpu) if (_options.UseGpu)
AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>() AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>()
.ToConstant(_glFeature = new GlPlatformFeature(_factory.ObtainGlDisplay())); .ToConstant(_glFeature = new GlPlatformFeature(_factory.ObtainGlDisplay()));

39
src/Avalonia.Native/AvnString.cs

@ -0,0 +1,39 @@
using System.Runtime.InteropServices;
namespace Avalonia.Native.Interop
{
unsafe partial class IAvnString
{
private string _managed;
public string String
{
get
{
if (_managed == null)
{
var ptr = Pointer();
if (ptr == null)
return null;
_managed = System.Text.Encoding.UTF8.GetString((byte*)ptr.ToPointer(), Length());
}
return _managed;
}
}
public override string ToString() => String;
}
partial class IAvnStringArray
{
public string[] ToStringArray()
{
var arr = new string[Count];
for(uint c = 0; c<arr.Length;c++)
using (var s = Get(c))
arr[c] = s.String;
return arr;
}
}
}

96
src/Avalonia.Native/ClipboardImpl.cs

@ -1,18 +1,25 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
using Avalonia.Platform.Interop; using Avalonia.Platform.Interop;
namespace Avalonia.Native namespace Avalonia.Native
{ {
class ClipboardImpl : IClipboard class ClipboardImpl : IClipboard, IDisposable
{ {
private IAvnClipboard _native; private IAvnClipboard _native;
private const string NSPasteboardTypeString = "public.utf8-plain-text";
private const string NSFilenamesPboardType = "NSFilenamesPboardType";
private const string NSPasteboardTypeFileUrl = "public.file-url";
public ClipboardImpl(IAvnClipboard native) public ClipboardImpl(IAvnClipboard native)
{ {
_native = native; _native = native;
@ -25,14 +32,10 @@ namespace Avalonia.Native
return Task.CompletedTask; return Task.CompletedTask;
} }
public unsafe Task<string> GetTextAsync() public Task<string> GetTextAsync()
{ {
using (var text = _native.GetText()) using (var text = _native.GetText(NSPasteboardTypeString))
{ return Task.FromResult(text.String);
var result = System.Text.Encoding.UTF8.GetString((byte*)text.Pointer(), text.Length());
return Task.FromResult(result);
}
} }
public Task SetTextAsync(string text) public Task SetTextAsync(string text)
@ -43,11 +46,84 @@ namespace Avalonia.Native
{ {
using (var buffer = new Utf8Buffer(text)) using (var buffer = new Utf8Buffer(text))
{ {
_native.SetText(buffer.DangerousGetHandle()); _native.SetText(NSPasteboardTypeString, buffer.DangerousGetHandle());
} }
} }
return Task.CompletedTask; return Task.CompletedTask;
} }
public IEnumerable<string> GetFormats()
{
var rv = new List<string>();
using (var formats = _native.ObtainFormats())
{
var cnt = formats.Count;
for (uint c = 0; c < cnt; c++)
{
using (var fmt = formats.Get(c))
{
if(fmt.String == NSPasteboardTypeString)
rv.Add(DataFormats.Text);
if(fmt.String == NSFilenamesPboardType)
rv.Add(DataFormats.FileNames);
}
}
}
return rv;
}
public void Dispose()
{
_native?.Dispose();
_native = null;
}
public IEnumerable<string> GetFileNames()
{
using (var strings = _native.GetStrings(NSFilenamesPboardType))
return strings.ToStringArray();
}
}
class ClipboardDataObject : IDataObject, IDisposable
{
private ClipboardImpl _clipboard;
private List<string> _formats;
public ClipboardDataObject(IAvnClipboard clipboard)
{
_clipboard = new ClipboardImpl(clipboard);
}
public void Dispose()
{
_clipboard?.Dispose();
_clipboard = null;
}
List<string> Formats => _formats ??= _clipboard.GetFormats().ToList();
public IEnumerable<string> GetDataFormats() => Formats;
public bool Contains(string dataFormat) => Formats.Contains(dataFormat);
public string GetText()
{
// bad idea in general, but API is synchronous anyway
return _clipboard.GetTextAsync().Result;
}
public IEnumerable<string> GetFileNames() => _clipboard.GetFileNames();
public object Get(string dataFormat)
{
if (dataFormat == DataFormats.Text)
return GetText();
if (dataFormat == DataFormats.FileNames)
return GetFileNames();
return null;
}
} }
} }

31
src/Avalonia.Native/WindowImplBase.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input; using Avalonia.Input;
@ -201,6 +202,30 @@ namespace Avalonia.Native
{ {
Dispatcher.UIThread.RunJobs(DispatcherPriority.Render); Dispatcher.UIThread.RunJobs(DispatcherPriority.Render);
} }
public AvnDragDropEffects DragEvent(AvnDragEventType type, AvnPoint position,
AvnInputModifiers modifiers,
AvnDragDropEffects effects,
IAvnClipboard clipboard, IntPtr dataObjectHandle)
{
var device = AvaloniaLocator.Current.GetService<IDragDropDevice>();
IDataObject dataObject = null;
if (dataObjectHandle != IntPtr.Zero)
dataObject = GCHandle.FromIntPtr(dataObjectHandle).Target as IDataObject;
using(var clipboardDataObject = new ClipboardDataObject(clipboard))
{
if (dataObject == null)
dataObject = clipboardDataObject;
var args = new RawDragEvent(device, (RawDragEventType)type,
_parent._inputRoot, position.ToAvaloniaPoint(), dataObject, (DragDropEffects)effects,
(RawInputModifiers)modifiers);
_parent.Input(args);
return (AvnDragDropEffects)args.Effects;
}
}
} }
public void Activate() public void Activate()
@ -359,6 +384,12 @@ namespace Avalonia.Native
} }
internal void BeginDraggingSession(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard clipboard,
IAvnDndResultCallback callback, IntPtr sourceHandle)
{
_native.BeginDragAndDropOperation(effects, point, clipboard, callback, sourceHandle);
}
public IPlatformHandle Handle { get; private set; } public IPlatformHandle Handle { get; private set; }
} }
} }

Loading…
Cancel
Save