diff --git a/Avalonia.sln b/Avalonia.sln
index 38f8e5f720..e8d5034fb0 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -206,6 +206,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@@ -1921,6 +1923,30 @@ Global
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.Build.0 = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.AppStore|iPhone.Build.0 = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhone.Build.0 = Release|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Documentation/build.md b/Documentation/build.md
index 56b028206d..8c2ef57b54 100644
--- a/Documentation/build.md
+++ b/Documentation/build.md
@@ -36,7 +36,7 @@ Avalonia requires [CastXML](https://github.com/CastXML/CastXML) for XML processi
On macOS:
```
-brew install castxml
+brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/8a004a91a7fcd3f6620d5b01b6541ff0a640ffba/Formula/castxml.rb
```
On Debian based Linux (Debian, Ubuntu, Mint, etc):
diff --git a/build.sh b/build.sh
index 40b1c225a6..a40e00f815 100755
--- a/build.sh
+++ b/build.sh
@@ -67,6 +67,8 @@ else
fi
fi
+export PATH=$DOTNET_DIRECTORY:$PATH
+
echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]}
diff --git a/build/CoreLibraries.props b/build/CoreLibraries.props
index d61268173d..2b54ee3f56 100644
--- a/build/CoreLibraries.props
+++ b/build/CoreLibraries.props
@@ -11,6 +11,7 @@
+
diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h
index 38d99db5c9..f9bfaf0b47 100644
--- a/native/Avalonia.Native/inc/avalonia-native.h
+++ b/native/Avalonia.Native/inc/avalonia-native.h
@@ -22,6 +22,9 @@ struct IAvnGlSurfaceRenderTarget;
struct IAvnGlSurfaceRenderingSession;
struct IAvnMenu;
struct IAvnMenuItem;
+struct IAvnStringArray;
+struct IAvnDndResultCallback;
+struct IAvnGCHandleDeallocatorCallback;
struct IAvnMenuEvents;
enum SystemDecorations {
@@ -130,6 +133,22 @@ enum AvnInputModifiers
XButton2MouseButton = 256
};
+enum class AvnDragDropEffects
+{
+ None = 0,
+ Copy = 1,
+ Move = 2,
+ Link = 4,
+};
+
+enum class AvnDragEventType
+{
+ Enter,
+ Over,
+ Leave,
+ Drop
+};
+
enum AvnWindowState
{
Normal,
@@ -188,7 +207,7 @@ enum AvnMenuItemToggleType
AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown
{
public:
- virtual HRESULT Initialize() = 0;
+ virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator) = 0;
virtual IAvnMacOptions* GetMacOptions() = 0;
virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) = 0;
virtual HRESULT CreatePopup (IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) = 0;
@@ -196,6 +215,7 @@ public:
virtual HRESULT CreateSystemDialogs (IAvnSystemDialogs** ppv) = 0;
virtual HRESULT CreateScreens (IAvnScreens** ppv) = 0;
virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0;
+ virtual HRESULT CreateDndClipboard(IAvnClipboard** ppv) = 0;
virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0;
virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) = 0;
virtual HRESULT SetAppMenu(IAvnMenu* menu) = 0;
@@ -236,6 +256,9 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT ObtainNSWindowHandleRetained(void** retOut) = 0;
virtual HRESULT ObtainNSViewHandle(void** retOut) = 0;
virtual HRESULT ObtainNSViewHandleRetained(void** retOut) = 0;
+ virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
+ IAvnClipboard* clipboard, IAvnDndResultCallback* cb, void* sourceHandle) = 0;
+ virtual HRESULT SetBlurEnabled (bool enable) = 0;
};
AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase
@@ -245,7 +268,8 @@ AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase
AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
{
- virtual HRESULT ShowDialog (IAvnWindow* parent) = 0;
+ virtual HRESULT SetEnabled (bool enable) = 0;
+ virtual HRESULT SetParent (IAvnWindow* parent) = 0;
virtual HRESULT SetCanResize(bool value) = 0;
virtual HRESULT SetDecorations(SystemDecorations value) = 0;
virtual HRESULT SetTitle (void* utf8Title) = 0;
@@ -271,6 +295,9 @@ AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown
virtual bool RawTextInputEvent (unsigned int timeStamp, const char* text) = 0;
virtual void ScalingChanged(double scaling) = 0;
virtual void RunRenderPriorityJobs() = 0;
+ virtual AvnDragDropEffects DragEvent(AvnDragEventType type, AvnPoint position,
+ AvnInputModifiers modifiers, AvnDragDropEffects effects,
+ IAvnClipboard* clipboard, void* dataObjectHandle) = 0;
};
@@ -284,6 +311,8 @@ AVNCOM(IAvnWindowEvents, 06) : IAvnWindowBaseEvents
virtual bool Closing () = 0;
virtual void WindowStateChanged (AvnWindowState state) = 0;
+
+ virtual void GotInputWhenDisabled () = 0;
};
AVNCOM(IAvnMacOptions, 07) : IUnknown
@@ -354,8 +383,13 @@ AVNCOM(IAvnScreens, 0e) : IUnknown
AVNCOM(IAvnClipboard, 0f) : IUnknown
{
- virtual HRESULT GetText (IAvnString**ppv) = 0;
- virtual HRESULT SetText (void* utf8Text) = 0;
+ virtual HRESULT GetText (char* type, IAvnString**ppv) = 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 SetBytes(char* type, void* utf8Text, int len) = 0;
+ virtual HRESULT GetBytes(char* type, IAvnString**ppv) = 0;
+
virtual HRESULT Clear() = 0;
};
@@ -428,4 +462,20 @@ AVNCOM(IAvnMenuEvents, 1A) : IUnknown
virtual void NeedsUpdate () = 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();
diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
index 50a85bdf9f..ea28780c81 100644
--- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
+++ b/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 */; };
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */; };
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 */; };
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.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; };
1A3E5EAD23E9FB1300EDE661 /* cgl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cgl.mm; sourceTree = ""; };
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 = ""; };
37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = ""; };
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = ""; };
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = ""; };
@@ -92,6 +94,7 @@
5BF943652167AD1D009CAE35 /* cursor.h */,
5B21A981216530F500CEE36E /* cursor.mm */,
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */,
+ 1A465D0F246AB61600C5858B /* dnd.mm */,
AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */,
AB661C212148288600291242 /* common.h */,
379860FE214DA0C000CD0246 /* KeyTransform.h */,
@@ -196,6 +199,7 @@
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
+ 1A465D10246AB61600C5858B /* dnd.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
AB661C202148286E00291242 /* window.mm in Sources */,
diff --git a/native/Avalonia.Native/src/OSX/AvnString.h b/native/Avalonia.Native/src/OSX/AvnString.h
index 9a8f5a1318..5d299374e5 100644
--- a/native/Avalonia.Native/src/OSX/AvnString.h
+++ b/native/Avalonia.Native/src/OSX/AvnString.h
@@ -10,5 +10,7 @@
#define AvnString_h
extern IAvnString* CreateAvnString(NSString* string);
-
+extern IAvnStringArray* CreateAvnStringArray(NSArray* array);
+extern IAvnStringArray* CreateAvnStringArray(NSString* string);
+extern IAvnString* CreateByteArray(void* data, int len);
#endif /* AvnString_h */
diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm
index b62fe8a968..00b748ef63 100644
--- a/native/Avalonia.Native/src/OSX/AvnString.mm
+++ b/native/Avalonia.Native/src/OSX/AvnString.mm
@@ -7,6 +7,7 @@
//
#include "common.h"
+#include
class AvnStringImpl : public virtual ComSingleObject
{
@@ -28,6 +29,13 @@ public:
memcpy((void*)_cstring, (void*)cstring, _length);
}
+ AvnStringImpl(void*ptr, int len)
+ {
+ _length = len;
+ _cstring = (const char*)malloc(_length);
+ memcpy((void*)_cstring, ptr, len);
+ }
+
virtual ~AvnStringImpl()
{
free((void*)_cstring);
@@ -61,7 +69,60 @@ public:
}
};
+class AvnStringArrayImpl : public virtual ComSingleObject
+{
+private:
+ std::vector> _list;
+public:
+ FORWARD_IUNKNOWN()
+ AvnStringArrayImpl(NSArray* array)
+ {
+ for(int c = 0; c < [array count]; c++)
+ {
+ ComPtr s;
+ *s.getPPV() = new AvnStringImpl([array objectAtIndex:c]);
+ _list.push_back(s);
+ }
+ }
+
+ AvnStringArrayImpl(NSString* string)
+ {
+ ComPtr 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)
{
return new AvnStringImpl(string);
}
+
+
+IAvnStringArray* CreateAvnStringArray(NSArray * array)
+{
+ return new AvnStringArrayImpl(array);
+}
+
+IAvnStringArray* CreateAvnStringArray(NSString* string)
+{
+ return new AvnStringArrayImpl(string);
+}
+
+IAvnString* CreateByteArray(void* data, int len)
+{
+ return new AvnStringImpl(data, len);
+}
diff --git a/native/Avalonia.Native/src/OSX/clipboard.mm b/native/Avalonia.Native/src/OSX/clipboard.mm
index c2cf1f1f61..116a08670e 100644
--- a/native/Avalonia.Native/src/OSX/clipboard.mm
+++ b/native/Avalonia.Native/src/OSX/clipboard.mm
@@ -3,16 +3,27 @@
class Clipboard : public ComSingleObject
{
+private:
+ NSPasteboard* _pb;
+ NSPasteboardItem* _item;
public:
FORWARD_IUNKNOWN()
- Clipboard()
+ Clipboard(NSPasteboard* pasteboard, NSPasteboardItem* item)
{
- NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
- [pasteBoard stringForType:NSPasteboardTypeString];
+ if(pasteboard == nil && item == nil)
+ 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
{
@@ -20,39 +31,124 @@ public:
{
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;
}
}
- virtual HRESULT SetText (void* utf8String) override
+ virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) override
{
@autoreleasepool
{
- NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
- [pasteBoard clearContents];
- [pasteBoard setString:[NSString stringWithUTF8String:(const char*)utf8String] forType:NSPasteboardTypeString];
+ *ppv= nil;
+ NSString* typeString = [NSString stringWithUTF8String:(const char*)type];
+ 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;
}
+
+ virtual HRESULT SetBytes(char* type, void* bytes, int len) override
+ {
+ auto typeString = [NSString stringWithUTF8String:(const char*)type];
+ auto data = [NSData dataWithBytes:bytes length:len];
+ if(_item == nil)
+ [_pb setData:data forType:typeString];
+ else
+ [_item setData:data forType:typeString];
+ return S_OK;
+ }
+
+ virtual HRESULT GetBytes(char* type, IAvnString**ppv) override
+ {
+ *ppv = nil;
+ auto typeString = [NSString stringWithUTF8String:(const char*)type];
+ NSData*data;
+ @try
+ {
+ if(_item)
+ data = [_item dataForType:typeString];
+ else
+ data = [_pb dataForType:typeString];
+ if(data == nil)
+ return E_FAIL;
+ }
+ @catch(NSException* e)
+ {
+ return E_FAIL;
+ }
+ *ppv = CreateByteArray((void*)data.bytes, (int)data.length);
+ return S_OK;
+ }
+
virtual HRESULT Clear() override
{
@autoreleasepool
{
- NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
- [pasteBoard clearContents];
- [pasteBoard setString:@"" forType:NSPasteboardTypeString];
+ if(_item != nil)
+ _item = [NSPasteboardItem new];
+ else
+ {
+ [_pb clearContents];
+ [_pb setString:@"" forType:NSPasteboardTypeString];
+ }
}
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(cb);
+ if(clipboard == nil)
+ return nil;
+ return clipboard->TryGetItem();
}
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index 7a433bfd9f..df6a7be91c 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -8,11 +8,17 @@
#include
extern IAvnPlatformThreadingInterface* CreatePlatformThreading();
+extern void FreeAvnGCHandle(void* handle);
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl);
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl);
extern IAvnSystemDialogs* CreateSystemDialogs();
extern IAvnScreens* CreateScreens();
-extern IAvnClipboard* CreateClipboard();
+extern IAvnClipboard* CreateClipboard(NSPasteboard*, NSPasteboardItem*);
+extern NSPasteboardItem* TryGetPasteboardItem(IAvnClipboard*);
+extern NSObject* CreateDraggingSource(NSDragOperation op, IAvnDndResultCallback* cb, void* handle);
+extern void* GetAvnDataObjectHandleFromDraggingInfo(NSObject* info);
+extern NSString* GetAvnCustomDataType();
+extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop);
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlDisplay* GetGlDisplay();
extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);
diff --git a/native/Avalonia.Native/src/OSX/dnd.mm b/native/Avalonia.Native/src/OSX/dnd.mm
new file mode 100644
index 0000000000..294b8ee8ea
--- /dev/null
+++ b/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
+
+@end
+
+@implementation AvnDndSource
+{
+ NSDragOperation _operation;
+ ComPtr _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* CreateDraggingSource(NSDragOperation op, IAvnDndResultCallback* cb, void* handle)
+{
+ return [[AvnDndSource alloc] initWithOperation:op andCallback:cb andSourceHandle:handle];
+};
+
+extern void* GetAvnDataObjectHandleFromDraggingInfo(NSObject* info)
+{
+ id obj = [info draggingSource];
+ if(obj == nil)
+ return nil;
+ if([obj isKindOfClass: [AvnDndSource class]])
+ {
+ auto src = (AvnDndSource*)obj;
+ return [src gcHandle];
+ }
+ return nil;
+}
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index a63353bc0a..e6c4a861fd 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/native/Avalonia.Native/src/OSX/main.mm
@@ -150,14 +150,15 @@ public:
}
@end
-
+static ComPtr _deallocator;
class AvaloniaNative : public ComSingleObject
{
public:
FORWARD_IUNKNOWN()
- virtual HRESULT Initialize() override
+ virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator) override
{
+ _deallocator = deallocator;
@autoreleasepool{
[[ThreadingInitializer new] do];
}
@@ -207,7 +208,13 @@ public:
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;
}
@@ -257,6 +264,12 @@ extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()
return new AvaloniaNative();
};
+extern void FreeAvnGCHandle(void* handle)
+{
+ if(_deallocator != nil)
+ _deallocator->FreeGCHandle(handle);
+}
+
NSSize ToNSSize (AvnSize s)
{
NSSize result;
diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h
index ec8fe9e6ee..bdf3007a28 100644
--- a/native/Avalonia.Native/src/OSX/window.h
+++ b/native/Avalonia.Native/src/OSX/window.h
@@ -3,7 +3,10 @@
class WindowBaseImpl;
-@interface AvnView : NSView
+@interface AutoFitContentVisualEffectView : NSVisualEffectView
+@end
+
+@interface AvnView : NSView
-(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
-(NSEvent* _Nonnull) lastMouseDownEvent;
-(AvnPoint) translateLocalPoint:(AvnPoint)pt;
@@ -19,8 +22,7 @@ class WindowBaseImpl;
-(void) pollModalSession: (NSModalSession _Nonnull) session;
-(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents;
--(bool) isModal;
--(void) setModal: (bool) isModal;
+-(void) setEnabled: (bool) enable;
-(void) showAppMenuOnly;
-(void) showWindowMenuWithAppMenu;
-(void) applyMenu:(NSMenu* _Nullable)menu;
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 091219fc72..abfae3cf1e 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -20,6 +20,7 @@ public:
View = NULL;
Window = NULL;
}
+ NSVisualEffectView* VisualEffect;
AvnView* View;
AvnWindow* Window;
ComPtr BaseEvents;
@@ -47,6 +48,12 @@ public:
[Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
+
+ VisualEffect = [AutoFitContentVisualEffectView new];
+ [VisualEffect setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
+ [VisualEffect setMaterial:NSVisualEffectMaterialLight];
+ [VisualEffect setAutoresizesSubviews:true];
+
[Window setContentView: View];
}
@@ -382,6 +389,62 @@ public:
*ppv = [renderTarget createSurfaceRenderTarget];
return *ppv == nil ? E_FAIL : S_OK;
}
+
+ virtual HRESULT SetBlurEnabled (bool enable) override
+ {
+ [Window setContentView: enable ? VisualEffect : View];
+
+ if(enable)
+ {
+ [VisualEffect addSubview:View];
+ }
+
+ return 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:
virtual NSWindowStyleMask GetStyle()
@@ -453,12 +516,7 @@ private:
virtual HRESULT Show () override
{
@autoreleasepool
- {
- if([Window parentWindow] != nil)
- [[Window parentWindow] removeChildWindow:Window];
-
- [Window setModal:FALSE];
-
+ {
WindowBaseImpl::Show();
HideOrShowTrafficLights();
@@ -467,7 +525,16 @@ private:
}
}
- virtual HRESULT ShowDialog (IAvnWindow* parent) override
+ virtual HRESULT SetEnabled (bool enable) override
+ {
+ @autoreleasepool
+ {
+ [Window setEnabled:enable];
+ return S_OK;
+ }
+ }
+
+ virtual HRESULT SetParent (IAvnWindow* parent) override
{
@autoreleasepool
{
@@ -478,12 +545,9 @@ private:
if(cparent == nullptr)
return E_INVALIDARG;
- [Window setModal:TRUE];
-
[cparent->Window addChildWindow:Window ordered:NSWindowAbove];
- WindowBaseImpl::Show();
- HideOrShowTrafficLights();
+ UpdateStyle();
return S_OK;
}
@@ -839,15 +903,15 @@ protected:
switch (_decorations)
{
case SystemDecorationsNone:
- s = s | NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskMiniaturizable;
+ s = s | NSWindowStyleMaskFullSizeContentView;
break;
case SystemDecorationsBorderOnly:
- s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskMiniaturizable;
+ s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView;
break;
case SystemDecorationsFull:
- s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskBorderless;
+ s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskBorderless;
if(_canResize)
{
@@ -856,12 +920,26 @@ protected:
break;
}
+ if([Window parentWindow] == nullptr)
+ {
+ s |= NSWindowStyleMaskMiniaturizable;
+ }
return s;
}
};
NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEventTrackingRunLoopMode, NSModalPanelRunLoopMode, NSRunLoopCommonModes, NSConnectionReplyMode, nil];
+@implementation AutoFitContentVisualEffectView
+-(void)setFrameSize:(NSSize)newSize
+{
+ [super setFrameSize:newSize];
+ if([[self subviews] count] == 0)
+ return;
+ [[self subviews][0] setFrameSize: newSize];
+}
+@end
+
@implementation AvnView
{
ComPtr _parent;
@@ -911,7 +989,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_area = nullptr;
_lastPixelSize.Height = 100;
_lastPixelSize.Width = 100;
-
+ [self registerForDraggedTypes: @[@"public.data", GetAvnCustomDataType()]];
return self;
}
@@ -1037,15 +1115,28 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (bool) ignoreUserInput
{
auto parentWindow = objc_cast([self window]);
+
if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents])
+ {
+ auto window = dynamic_cast(_parent.getRaw());
+
+ if(window != nullptr)
+ {
+ window->WindowEvents->GotInputWhenDisabled();
+ }
+
return TRUE;
+ }
+
return FALSE;
}
- (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
{
if([self ignoreUserInput])
+ {
return;
+ }
[self becomeFirstResponder];
auto localPoint = [self convertPoint:[event locationInWindow] toView:self];
@@ -1190,7 +1281,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type
{
if([self ignoreUserInput])
+ {
return;
+ }
+
auto key = s_KeyMap[[event keyCode]];
auto timestamp = [event timestamp] * 1000;
@@ -1302,6 +1396,68 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return result;
}
+
+- (NSDragOperation)triggerAvnDragEvent: (AvnDragEventType) type info: (id )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 )sender
+{
+ return [self triggerAvnDragEvent: AvnDragEventType::Enter info:sender];
+}
+
+- (NSDragOperation)draggingUpdated:(id )sender
+{
+ return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender];
+}
+
+- (void)draggingExited:(id )sender
+{
+ [self triggerAvnDragEvent: AvnDragEventType::Leave info:sender];
+}
+
+- (BOOL)prepareForDragOperation:(id )sender
+{
+ return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender] != NSDragOperationNone;
+}
+
+- (BOOL)performDragOperation:(id )sender
+{
+ return [self triggerAvnDragEvent: AvnDragEventType::Drop info:sender] != NSDragOperationNone;
+}
+
+- (void)concludeDragOperation:(nullable id )sender
+{
+
+}
+
@end
@@ -1310,7 +1466,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
ComPtr _parent;
bool _canBecomeKeyAndMain;
bool _closed;
- bool _isModal;
+ bool _isEnabled;
AvnMenu* _menu;
double _lastScaling;
}
@@ -1432,6 +1588,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_parent = parent;
[self setDelegate:self];
_closed = false;
+ _isEnabled = true;
_lastScaling = [self backingScaleFactor];
[self setOpaque:NO];
@@ -1498,28 +1655,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
-(bool)shouldTryToHandleEvents
{
- for(NSWindow* uch in [self childWindows])
- {
- auto ch = objc_cast(uch);
- if(ch == nil)
- continue;
-
- if(![ch isModal])
- continue;
-
- return FALSE;
- }
- return TRUE;
-}
-
--(bool) isModal
-{
- return _isModal;
+ return _isEnabled;
}
--(void) setModal: (bool) isModal
+-(void) setEnabled:(bool)enable
{
- _isModal = isModal;
+ _isEnabled = enable;
}
-(void)makeKeyWindow
diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index 552713f94b..537495fcad 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -2,6 +2,7 @@
<_AvaloniaUseExternalMSBuild>$(AvaloniaUseExternalMSBuild)
<_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false
+ low
+ EmbeddedResources="@(EmbeddedResources)"
+ ReportImportance="$(AvaloniaXamlReportImportance)"/>
@@ -67,6 +69,7 @@
OriginalCopyPath="$(AvaloniaXamlOriginalCopyFilePath)"
ProjectDirectory="$(MSBuildProjectDirectory)"
VerifyIl="$(AvaloniaXamlIlVerifyIl)"
+ ReportImportance="$(AvaloniaXamlReportImportance)"
/>
-
-
-
+
-
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ButtonPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonPage.xaml.cs
index 1d0c228a0e..5e555c8c91 100644
--- a/samples/ControlCatalog/Pages/ButtonPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ButtonPage.xaml.cs
@@ -5,5 +5,25 @@ namespace ControlCatalog.Pages
{
public class ButtonPage : UserControl
{
+ private int repeatButtonClickCount = 0;
+
+ public ButtonPage()
+ {
+ InitializeComponent();
+
+ this.FindControl("RepeatButton").Click += OnRepeatButtonClick;
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public void OnRepeatButtonClick(object sender, object args)
+ {
+ repeatButtonClickCount++;
+ var textBlock = this.FindControl("RepeatButtonTextBlock");
+ textBlock.Text = $"Repeat Button: {repeatButtonClickCount}";
+ }
}
}
diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml b/samples/ControlCatalog/Pages/ComboBoxPage.xaml
index bbfbd4b4cd..486cc55d44 100644
--- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml
@@ -6,7 +6,7 @@
A drop-down list.
-
+
Inline Items
Inline Item 2
Inline Item 3
diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml b/samples/ControlCatalog/Pages/DragAndDropPage.xaml
index 9bfcd90149..65a798e53c 100644
--- a/samples/ControlCatalog/Pages/DragAndDropPage.xaml
+++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml
@@ -9,9 +9,15 @@
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
-
- Drag Me
-
+
+
+ Drag Me
+
+
+ Drag Me (custom)
+
+
+
Drop some text or files here
diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
index 0bf21c2820..5a52dbe12b 100644
--- a/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
@@ -3,69 +3,85 @@ using Avalonia.Input;
using Avalonia.Markup.Xaml;
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
using System.Text;
namespace ControlCatalog.Pages
{
public class DragAndDropPage : UserControl
{
- private TextBlock _DropState;
- private TextBlock _DragState;
- private Border _DragMe;
- private int DragCount = 0;
-
+ TextBlock _DropState;
+ private const string CustomFormat = "application/xxx-avalonia-controlcatalog-custom";
public DragAndDropPage()
{
this.InitializeComponent();
+ _DropState = this.Find("DropState");
- _DragMe.PointerPressed += DoDrag;
+ int textCount = 0;
+ SetupDnd("Text", d => d.Set(DataFormats.Text,
+ $"Text was dragged {++textCount} times"));
- AddHandler(DragDrop.DropEvent, Drop);
- AddHandler(DragDrop.DragOverEvent, DragOver);
+ SetupDnd("Custom", d => d.Set(CustomFormat, "Test123"));
}
- private async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
+ void SetupDnd(string suffix, Action factory, DragDropEffects effects = DragDropEffects.Copy)
{
- DataObject dragData = new DataObject();
- dragData.Set(DataFormats.Text, $"You have dragged text {++DragCount} times");
+ var dragMe = this.Find("DragMe" + suffix);
+ var dragState = this.Find("DragState"+suffix);
- var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
- switch(result)
+ async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
{
- case DragDropEffects.Copy:
- _DragState.Text = "The text was copied"; break;
- case DragDropEffects.Link:
- _DragState.Text = "The text was linked"; break;
- case DragDropEffects.None:
- _DragState.Text = "The drag operation was canceled"; break;
+ var dragData = new DataObject();
+ factory(dragData);
+
+ var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
+ switch (result)
+ {
+ 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)
- {
- // Only allow Copy or Link as Drop Operations.
- e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link);
+ void DragOver(object sender, DragEventArgs e)
+ {
+ // Only allow Copy or Link as Drop Operations.
+ e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link);
- // Only allow if the dragged data contains text or filenames.
- if (!e.Data.Contains(DataFormats.Text) && !e.Data.Contains(DataFormats.FileNames))
- e.DragEffects = DragDropEffects.None;
- }
+ // Only allow if the dragged data contains text or filenames.
+ if (!e.Data.Contains(DataFormats.Text)
+ && !e.Data.Contains(DataFormats.FileNames)
+ && !e.Data.Contains(CustomFormat))
+ e.DragEffects = DragDropEffects.None;
+ }
- private void Drop(object sender, DragEventArgs e)
- {
- if (e.Data.Contains(DataFormats.Text))
- _DropState.Text = e.Data.GetText();
- else if (e.Data.Contains(DataFormats.FileNames))
- _DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames());
+ void Drop(object sender, DragEventArgs e)
+ {
+ if (e.Data.Contains(DataFormats.Text))
+ _DropState.Text = e.Data.GetText();
+ else if (e.Data.Contains(DataFormats.FileNames))
+ _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()
{
AvaloniaXamlLoader.Load(this);
-
- _DropState = this.Find("DropState");
- _DragState = this.Find("DragState");
- _DragMe = this.Find("DragMe");
}
}
}
diff --git a/samples/ControlCatalog/Pages/SliderPage.xaml b/samples/ControlCatalog/Pages/SliderPage.xaml
index 58f7b881fe..c6f5521e60 100644
--- a/samples/ControlCatalog/Pages/SliderPage.xaml
+++ b/samples/ControlCatalog/Pages/SliderPage.xaml
@@ -9,12 +9,14 @@
diff --git a/samples/ControlCatalog/Pages/TreeViewPage.xaml b/samples/ControlCatalog/Pages/TreeViewPage.xaml
index c9e3fafb6d..789b45e62c 100644
--- a/samples/ControlCatalog/Pages/TreeViewPage.xaml
+++ b/samples/ControlCatalog/Pages/TreeViewPage.xaml
@@ -19,8 +19,8 @@
-
+
Single
diff --git a/samples/ControlCatalog/Pages/TreeViewPage.xaml.cs b/samples/ControlCatalog/Pages/TreeViewPage.xaml.cs
index 5893796b8b..3fb990459f 100644
--- a/samples/ControlCatalog/Pages/TreeViewPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/TreeViewPage.xaml.cs
@@ -1,9 +1,6 @@
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Reactive;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
-using ReactiveUI;
+using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
@@ -12,105 +9,12 @@ namespace ControlCatalog.Pages
public TreeViewPage()
{
InitializeComponent();
- DataContext = new PageViewModel();
+ DataContext = new TreeViewPageViewModel();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
-
- private class PageViewModel : ReactiveObject
- {
- private SelectionMode _selectionMode;
-
- public PageViewModel()
- {
- Node root = new Node();
- Items = root.Children;
- Selection = new SelectionModel();
-
- AddItemCommand = ReactiveCommand.Create(() =>
- {
- Node parentItem = Selection.SelectedItems.Count > 0 ?
- (Node)Selection.SelectedItems[0] : root;
- parentItem.AddNewItem();
- });
-
- RemoveItemCommand = ReactiveCommand.Create(() =>
- {
- while (Selection.SelectedItems.Count > 0)
- {
- Node lastItem = (Node)Selection.SelectedItems[0];
- RecursiveRemove(Items, lastItem);
- Selection.DeselectAt(Selection.SelectedIndices[0]);
- }
-
- bool RecursiveRemove(ObservableCollection items, Node selectedItem)
- {
- if (items.Remove(selectedItem))
- {
- return true;
- }
-
- foreach (Node item in items)
- {
- if (item.AreChildrenInitialized && RecursiveRemove(item.Children, selectedItem))
- {
- return true;
- }
- }
-
- return false;
- }
- });
- }
-
- public ObservableCollection Items { get; }
-
- public SelectionModel Selection { get; }
-
- public ReactiveCommand AddItemCommand { get; }
-
- public ReactiveCommand RemoveItemCommand { get; }
-
- public SelectionMode SelectionMode
- {
- get => _selectionMode;
- set
- {
- Selection.ClearSelection();
- this.RaiseAndSetIfChanged(ref _selectionMode, value);
- }
- }
- }
-
- private class Node
- {
- private int _counter;
- private ObservableCollection _children;
-
- public string Header { get; private set; }
-
- public bool AreChildrenInitialized => _children != null;
-
- public ObservableCollection Children
- {
- get
- {
- if (_children == null)
- {
- _children = new ObservableCollection(Enumerable.Range(1, 10).Select(i => CreateNewNode()));
- }
- return _children;
- }
- }
-
- public void AddNewItem() => Children.Add(CreateNewNode());
-
- public override string ToString() => Header;
-
- private Node CreateNewNode() => new Node { Header = $"Item {_counter++}" };
- }
}
}
diff --git a/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs b/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
new file mode 100644
index 0000000000..5bc23e2fe5
--- /dev/null
+++ b/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive;
+using Avalonia.Controls;
+using ReactiveUI;
+
+namespace ControlCatalog.ViewModels
+{
+ public class TreeViewPageViewModel : ReactiveObject
+ {
+ private readonly Node _root;
+ private SelectionMode _selectionMode;
+
+ public TreeViewPageViewModel()
+ {
+ _root = new Node();
+
+ Items = _root.Children;
+ Selection = new SelectionModel();
+ Selection.SelectionChanged += SelectionChanged;
+
+ AddItemCommand = ReactiveCommand.Create(AddItem);
+ RemoveItemCommand = ReactiveCommand.Create(RemoveItem);
+ SelectRandomItemCommand = ReactiveCommand.Create(SelectRandomItem);
+ }
+
+ public ObservableCollection Items { get; }
+ public SelectionModel Selection { get; }
+ public ReactiveCommand AddItemCommand { get; }
+ public ReactiveCommand RemoveItemCommand { get; }
+ public ReactiveCommand SelectRandomItemCommand { get; }
+
+ public SelectionMode SelectionMode
+ {
+ get => _selectionMode;
+ set
+ {
+ Selection.ClearSelection();
+ this.RaiseAndSetIfChanged(ref _selectionMode, value);
+ }
+ }
+
+ private void AddItem()
+ {
+ var parentItem = Selection.SelectedItems.Count > 0 ? (Node)Selection.SelectedItems[0] : _root;
+ parentItem.AddItem();
+ }
+
+ private void RemoveItem()
+ {
+ while (Selection.SelectedItems.Count > 0)
+ {
+ Node lastItem = (Node)Selection.SelectedItems[0];
+ RecursiveRemove(Items, lastItem);
+ Selection.DeselectAt(Selection.SelectedIndices[0]);
+ }
+
+ bool RecursiveRemove(ObservableCollection items, Node selectedItem)
+ {
+ if (items.Remove(selectedItem))
+ {
+ return true;
+ }
+
+ foreach (Node item in items)
+ {
+ if (item.AreChildrenInitialized && RecursiveRemove(item.Children, selectedItem))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ private void SelectRandomItem()
+ {
+ var random = new Random();
+ var depth = random.Next(4);
+ var indexes = Enumerable.Range(0, 4).Select(x => random.Next(10));
+ var path = new IndexPath(indexes);
+ Selection.SelectedIndex = path;
+ }
+
+ private void SelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e)
+ {
+ var selected = string.Join(",", e.SelectedIndices);
+ var deselected = string.Join(",", e.DeselectedIndices);
+ System.Diagnostics.Debug.WriteLine($"Selected '{selected}', Deselected '{deselected}'");
+ }
+
+ public class Node
+ {
+ private ObservableCollection _children;
+ private int _childIndex = 10;
+
+ public Node()
+ {
+ Header = "Item";
+ }
+
+ public Node(Node parent, int index)
+ {
+ Parent = parent;
+ Header = parent.Header + ' ' + index;
+ }
+
+ public Node Parent { get; }
+ public string Header { get; }
+ public bool AreChildrenInitialized => _children != null;
+ public ObservableCollection Children => _children ??= CreateChildren();
+ public void AddItem() => Children.Add(new Node(this, _childIndex++));
+ public void RemoveItem(Node child) => Children.Remove(child);
+ public override string ToString() => Header;
+
+ private ObservableCollection CreateChildren()
+ {
+ return new ObservableCollection(
+ Enumerable.Range(0, 10).Select(i => new Node(this, i)));
+ }
+ }
+ }
+}
diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props
index b9075b957b..8fc91aca14 100644
--- a/samples/Directory.Build.props
+++ b/samples/Directory.Build.props
@@ -1,6 +1,7 @@
false
+ $(MSBuildThisFileDirectory)..\src\tools\Avalonia.Designer.HostApp\bin\Debug\netcoreapp2.0\Avalonia.Designer.HostApp.dll
diff --git a/samples/RenderDemo/Controls/LineBoundsDemoControl.cs b/samples/RenderDemo/Controls/LineBoundsDemoControl.cs
new file mode 100644
index 0000000000..cc847a594d
--- /dev/null
+++ b/samples/RenderDemo/Controls/LineBoundsDemoControl.cs
@@ -0,0 +1,53 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.Threading;
+
+namespace RenderDemo.Controls
+{
+ public class LineBoundsDemoControl : Control
+ {
+ static LineBoundsDemoControl()
+ {
+ AffectsRender(AngleProperty);
+ }
+
+ public LineBoundsDemoControl()
+ {
+ var timer = new DispatcherTimer();
+ timer.Interval = TimeSpan.FromSeconds(1 / 60.0);
+ timer.Tick += (sender, e) => Angle += Math.PI / 360;
+ timer.Start();
+ }
+
+ public static readonly StyledProperty AngleProperty =
+ AvaloniaProperty.Register(nameof(Angle));
+
+ public double Angle
+ {
+ get => GetValue(AngleProperty);
+ set => SetValue(AngleProperty, value);
+ }
+
+ public override void Render(DrawingContext drawingContext)
+ {
+ var lineLength = Math.Sqrt((100 * 100) + (100 * 100));
+
+ var diffX = LineBoundsHelper.CalculateAdjSide(Angle, lineLength);
+ var diffY = LineBoundsHelper.CalculateOppSide(Angle, lineLength);
+
+
+ var p1 = new Point(200, 200);
+ var p2 = new Point(p1.X + diffX, p1.Y + diffY);
+
+ var pen = new Pen(Brushes.Green, 20, lineCap: PenLineCap.Square);
+ var boundPen = new Pen(Brushes.Black);
+
+ drawingContext.DrawLine(pen, p1, p2);
+
+ drawingContext.DrawRectangle(boundPen, LineBoundsHelper.CalculateBounds(p1, p2, pen));
+ }
+ }
+}
diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml
index b17520a466..c098ef411e 100644
--- a/samples/RenderDemo/MainWindow.xaml
+++ b/samples/RenderDemo/MainWindow.xaml
@@ -44,6 +44,9 @@
+
+
+
diff --git a/samples/RenderDemo/Pages/LineBoundsPage.xaml b/samples/RenderDemo/Pages/LineBoundsPage.xaml
new file mode 100644
index 0000000000..07d658630a
--- /dev/null
+++ b/samples/RenderDemo/Pages/LineBoundsPage.xaml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/samples/RenderDemo/Pages/LineBoundsPage.xaml.cs b/samples/RenderDemo/Pages/LineBoundsPage.xaml.cs
new file mode 100644
index 0000000000..28ddedd4bc
--- /dev/null
+++ b/samples/RenderDemo/Pages/LineBoundsPage.xaml.cs
@@ -0,0 +1,19 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace RenderDemo.Pages
+{
+ public class LineBoundsPage : UserControl
+ {
+ public LineBoundsPage()
+ {
+ this.InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/RenderDemo/RenderDemo.csproj b/samples/RenderDemo/RenderDemo.csproj
index ce33f42143..0d7d62e177 100644
--- a/samples/RenderDemo/RenderDemo.csproj
+++ b/samples/RenderDemo/RenderDemo.csproj
@@ -3,6 +3,9 @@
Exe
netcoreapp3.1
+
+
+
diff --git a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
index 51e0a1e799..7802f336fb 100644
--- a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
@@ -43,5 +43,11 @@ namespace Avalonia.Android.Platform
return Task.FromResult