1
0
Эх сурвалжийг харах

Merge branch 'fixes/calculate-bounds-for-drawline' of https://github.com/AvaloniaUI/Avalonia into fixes/calculate-bounds-for-drawline

Steven Kirk 5 жил өмнө
parent
commit
6353ba51df
57 өөрчлөгдсөн 1218 нэмэгдсэн , 863 устгасан
  1. 46 3
      native/Avalonia.Native/inc/avalonia-native.h
  2. 4 0
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  3. 2 1
      native/Avalonia.Native/src/OSX/AvnString.h
  4. 49 0
      native/Avalonia.Native/src/OSX/AvnString.mm
  5. 76 14
      native/Avalonia.Native/src/OSX/clipboard.mm
  6. 7 1
      native/Avalonia.Native/src/OSX/common.h
  7. 89 0
      native/Avalonia.Native/src/OSX/dnd.mm
  8. 16 3
      native/Avalonia.Native/src/OSX/main.mm
  9. 1 1
      native/Avalonia.Native/src/OSX/window.h
  10. 107 1
      native/Avalonia.Native/src/OSX/window.mm
  11. 4 1
      packages/Avalonia/AvaloniaBuildTasks.targets
  12. 9 3
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  13. 54 38
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  14. 0 1
      samples/ControlCatalog/Pages/TreeViewPage.xaml
  15. 2 98
      samples/ControlCatalog/Pages/TreeViewPage.xaml.cs
  16. 115 0
      samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
  17. 9 2
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  18. 7 2
      src/Avalonia.Build.Tasks/Extensions.cs
  19. 21 10
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  20. 3 1
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  21. 1 1
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  22. 1 3
      src/Avalonia.Controls.DataGrid/DataGridCell.cs
  23. 1 3
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  24. 1 3
      src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
  25. 1 3
      src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
  26. 6 2
      src/Avalonia.Controls/Application.cs
  27. 2 2
      src/Avalonia.Controls/AutoCompleteBox.cs
  28. 1 1
      src/Avalonia.Controls/ButtonSpinner.cs
  29. 1 3
      src/Avalonia.Controls/Calendar/Calendar.cs
  30. 1 2
      src/Avalonia.Controls/Calendar/CalendarButton.cs
  31. 2 2
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  32. 1 3
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  33. 1 3
      src/Avalonia.Controls/Calendar/DatePicker.cs
  34. 1 3
      src/Avalonia.Controls/ComboBox.cs
  35. 1 2
      src/Avalonia.Controls/ListBox.cs
  36. 1 3
      src/Avalonia.Controls/MenuItem.cs
  37. 1 3
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  38. 1 1
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  39. 1 3
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  40. 9 2
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  41. 1 1
      src/Avalonia.Controls/ProgressBar.cs
  42. 9 2
      src/Avalonia.Controls/SelectionModel.cs
  43. 59 75
      src/Avalonia.Controls/SelectionNode.cs
  44. 1 1
      src/Avalonia.Controls/Slider.cs
  45. 1 3
      src/Avalonia.Controls/TabControl.cs
  46. 1 1
      src/Avalonia.Controls/TextBox.cs
  47. 9 1
      src/Avalonia.Controls/TreeView.cs
  48. 1 2
      src/Avalonia.Controls/TreeViewItem.cs
  49. 76 0
      src/Avalonia.Native/AvaloniaNativeDragSource.cs
  50. 13 3
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  51. 39 0
      src/Avalonia.Native/AvnString.cs
  52. 87 11
      src/Avalonia.Native/ClipboardImpl.cs
  53. 31 0
      src/Avalonia.Native/WindowImplBase.cs
  54. 1 1
      tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
  55. 219 532
      tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs
  56. 1 2
      tests/Avalonia.Controls.UnitTests/TestTemplatedControl.cs
  57. 13 0
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

+ 46 - 3
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,8 @@ 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;
 };
 
 AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase
@@ -271,6 +293,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;
 };
 
 
@@ -354,8 +379,10 @@ 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 Clear() = 0;
 };
 
@@ -428,4 +455,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();

+ 4 - 0
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 = "<group>"; };
 		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>"; };
 		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>"; };
@@ -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 */,

+ 2 - 1
native/Avalonia.Native/src/OSX/AvnString.h

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

+ 49 - 0
native/Avalonia.Native/src/OSX/AvnString.mm

@@ -7,6 +7,7 @@
 //
 
 #include "common.h"
+#include <vector>
 
 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)
 {
     return new AvnStringImpl(string);
 }
+
+
+IAvnStringArray* CreateAvnStringArray(NSArray<NSString*> * array)
+{
+    return new AvnStringArrayImpl(array);
+}
+
+IAvnStringArray* CreateAvnStringArray(NSString* string)
+{
+    return new AvnStringArrayImpl(string);
+}

+ 76 - 14
native/Avalonia.Native/src/OSX/clipboard.mm

@@ -3,16 +3,27 @@
 
 class Clipboard : public ComSingleObject<IAvnClipboard, &IID_IAvnClipboard>
 {
+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,20 +31,53 @@ 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;
@@ -43,16 +87,34 @@ public:
     {
         @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<Clipboard*>(cb);
+    if(clipboard == nil)
+        return nil;
+    return clipboard->TryGetItem();
 }

+ 7 - 1
native/Avalonia.Native/src/OSX/common.h

@@ -8,11 +8,17 @@
 #include <pthread.h>
 
 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<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 IAvnGlDisplay* GetGlDisplay();
 extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);

+ 89 - 0
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;
+}

+ 16 - 3
native/Avalonia.Native/src/OSX/main.mm

@@ -150,14 +150,15 @@ public:
 }
 @end
 
-
+static ComPtr<IAvnGCHandleDeallocatorCallback> _deallocator;
 class AvaloniaNative : public ComSingleObject<IAvaloniaNativeFactory, &IID_IAvaloniaNativeFactory>
 {
     
 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;

+ 1 - 1
native/Avalonia.Native/src/OSX/window.h

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

+ 107 - 1
native/Avalonia.Native/src/OSX/window.mm

@@ -382,6 +382,50 @@ public:
         *ppv = [renderTarget createSurfaceRenderTarget];
         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:
     virtual NSWindowStyleMask GetStyle()
@@ -911,7 +955,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     _area = nullptr;
     _lastPixelSize.Height = 100;
     _lastPixelSize.Width = 100;
-
+    [self registerForDraggedTypes: @[@"public.data", GetAvnCustomDataType()]];
     return self;
 }
 
@@ -1302,6 +1346,68 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     
     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
 
 

+ 4 - 1
packages/Avalonia/AvaloniaBuildTasks.targets

@@ -2,6 +2,7 @@
   <PropertyGroup>
     <_AvaloniaUseExternalMSBuild>$(AvaloniaUseExternalMSBuild)</_AvaloniaUseExternalMSBuild>
     <_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false</_AvaloniaUseExternalMSBuild>
+    <AvaloniaXamlReportImportance Condition="'$(AvaloniaXamlReportImportance)' == ''">low</AvaloniaXamlReportImportance>
   </PropertyGroup>
   
   <UsingTask TaskName="GenerateAvaloniaResourcesTask"
@@ -38,7 +39,8 @@
       Output="$(AvaloniaResourcesTemporaryFilePath)"
       Root="$(MSBuildProjectDirectory)"
       Resources="@(AvaloniaResource)"
-      EmbeddedResources="@(EmbeddedResources)"/>
+      EmbeddedResources="@(EmbeddedResources)"
+      ReportImportance="$(AvaloniaXamlReportImportance)"/>
     <Exec 
       Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
       Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
@@ -67,6 +69,7 @@
       OriginalCopyPath="$(AvaloniaXamlOriginalCopyFilePath)"
       ProjectDirectory="$(MSBuildProjectDirectory)"
       VerifyIl="$(AvaloniaXamlIlVerifyIl)"
+      ReportImportance="$(AvaloniaXamlReportImportance)"
     />
     <Exec
       Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"

+ 9 - 3
samples/ControlCatalog/Pages/DragAndDropPage.xaml

@@ -9,9 +9,15 @@
                 Margin="0,16,0,0"
                 HorizontalAlignment="Center"
                 Spacing="16">
-            <Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMe">
-                <TextBlock Name="DragState">Drag Me</TextBlock>
-            </Border>
+            <StackPanel>
+                <Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMeText">
+                  <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" 
                     DragDrop.AllowDrop="True">
                 <TextBlock Name="DropState">Drop some text or files here</TextBlock>

+ 54 - 38
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<TextBlock>("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<DataObject> factory, DragDropEffects effects = DragDropEffects.Copy)
         {
-            DataObject dragData = new DataObject();
-            dragData.Set(DataFormats.Text, $"You have dragged text {++DragCount} times");
+            var dragMe = this.Find<Border>("DragMe" + suffix);
+            var dragState = this.Find<TextBlock>("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<TextBlock>("DropState");
-            _DragState = this.Find<TextBlock>("DragState");
-            _DragMe = this.Find<Border>("DragMe");
         }
     }
 }

+ 0 - 1
samples/ControlCatalog/Pages/TreeViewPage.xaml

@@ -19,7 +19,6 @@
         </TreeView>
 
         <Button Command="{Binding AddItemCommand}">Add</Button>
-
         <Button Command="{Binding RemoveItemCommand}">Remove</Button>
 
         <ComboBox SelectedIndex="{Binding SelectionMode, Mode=TwoWay}">

+ 2 - 98
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<Node> 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<Node> Items { get; }
-
-            public SelectionModel Selection { get; }
-
-            public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
-
-            public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; }
-
-            public SelectionMode SelectionMode
-            {
-                get => _selectionMode;
-                set
-                {
-                    Selection.ClearSelection();
-                    this.RaiseAndSetIfChanged(ref _selectionMode, value);
-                }
-            }
-        }
-
-        private class Node
-        {
-            private int _counter;
-            private ObservableCollection<Node> _children;
-
-            public string Header { get; private set; }
-
-            public bool AreChildrenInitialized => _children != null;
-
-            public ObservableCollection<Node> Children
-            {
-                get
-                {
-                    if (_children == null)
-                    {
-                        _children = new ObservableCollection<Node>(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++}" };
-        }
     }
 }

+ 115 - 0
samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs

@@ -0,0 +1,115 @@
+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);
+        }
+
+        public ObservableCollection<Node> Items { get; }
+        public SelectionModel Selection { get; }
+        public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
+        public ReactiveCommand<Unit, Unit> RemoveItemCommand { 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<Node> 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 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<Node> _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<Node> 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<Node> CreateChildren()
+            {
+                return new ObservableCollection<Node>(
+                    Enumerable.Range(0, 10).Select(i => new Node(this, i)));
+            }
+        }
+    }
+}

+ 9 - 2
src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs

@@ -12,6 +12,8 @@ namespace Avalonia.Build.Tasks
     {
         public bool Execute()
         {
+            Enum.TryParse(ReportImportance, true, out MessageImportance outputImportance);
+
             OutputPath = OutputPath ?? AssemblyFile;
             var outputPdb = GetPdbPath(OutputPath);
             var input = AssemblyFile;
@@ -32,9 +34,12 @@ namespace Avalonia.Build.Tasks
                 }
             }
 
+            var msg = $"CompileAvaloniaXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{OutputPath}";
+            BuildEngine.LogMessage(msg, outputImportance < MessageImportance.Low ? MessageImportance.High : outputImportance);
+
             var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input,
                 File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
-                ProjectDirectory, OutputPath, VerifyIl);
+                ProjectDirectory, OutputPath, VerifyIl, outputImportance);
             if (!res.Success)
                 return false;
             if (!res.WrittenFile)
@@ -68,7 +73,9 @@ namespace Avalonia.Build.Tasks
         public string OutputPath { get; set; }
 
         public bool VerifyIl { get; set; }
-        
+
+        public string ReportImportance { get; set; }
+
         public IBuildEngine BuildEngine { get; set; }
         public ITaskHost HostObject { get; set; }
     }

+ 7 - 2
src/Avalonia.Build.Tasks/Extensions.cs

@@ -9,14 +9,19 @@ namespace Avalonia.Build.Tasks
 
         public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message)
         {
-            engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message, 
+            engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message,
                 "", "Avalonia"));
         }
-        
+
         public static void LogWarning(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message)
         {
             engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message,
                 "", "Avalonia"));
         }
+
+        public static void LogMessage(this IBuildEngine engine, string message, MessageImportance imp)
+        {
+            engine.LogMessageEvent(new BuildMessageEventArgs(message, "", "Avalonia", imp));
+        }
     }
 }

+ 21 - 10
src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs

@@ -22,6 +22,10 @@ namespace Avalonia.Build.Tasks
         [Required]
         public ITaskItem[] EmbeddedResources { get; set; }
 
+        public string ReportImportance { get; set; }
+
+        private MessageImportance _reportImportance;
+
         class Source
         {
             public string Path { get; set; }
@@ -29,15 +33,11 @@ namespace Avalonia.Build.Tasks
             private byte[] _data;
             private string _sourcePath;
 
-            public Source(string file, string root)
+            public Source(string relativePath, string root)
             {
-                file = SPath.GetFullPath(file);
                 root = SPath.GetFullPath(root);
-                var fileUri = new Uri(file, UriKind.Absolute);
-                var rootUri = new Uri(root, UriKind.Absolute);
-                rootUri = new Uri(rootUri.ToString().TrimEnd('/') + '/');
-                Path = '/' + rootUri.MakeRelativeUri(fileUri).ToString().TrimStart('/');
-                _sourcePath = file;
+                Path = "/" + relativePath.Replace('\\', '/');
+                _sourcePath = SPath.Combine(root, relativePath);
                 Size = (int)new FileInfo(_sourcePath).Length;
             }
 
@@ -65,7 +65,14 @@ namespace Avalonia.Build.Tasks
             }
         }
 
-        List<Source> BuildResourceSources() => Resources.Select(r => new Source(r.ItemSpec, Root)).ToList();
+        List<Source> BuildResourceSources()
+           => Resources.Select(r =>
+           {
+              
+               var src = new Source(r.ItemSpec, Root);
+               BuildEngine.LogMessage($"avares -> name:{src.Path}, path: {src.SystemPath}, size:{src.Size}, ItemSpec:{r.ItemSpec}", _reportImportance);
+               return src;
+           }).ToList();
 
         private void Pack(Stream output, List<Source> sources)
         {
@@ -136,10 +143,14 @@ namespace Avalonia.Build.Tasks
             sources.Add(new Source("/!AvaloniaResourceXamlInfo", ms.ToArray()));
             return true;
         }
-        
+
         public bool Execute()
         {
-            foreach(var r in EmbeddedResources.Where(r=>r.ItemSpec.EndsWith(".xaml")||r.ItemSpec.EndsWith(".paml")))
+            Enum.TryParse<MessageImportance>(ReportImportance, out _reportImportance);
+
+            BuildEngine.LogMessage($"GenerateAvaloniaResourcesTask -> Root: {Root}, {Resources?.Count()} resources, Output:{Output}", _reportImportance < MessageImportance.Low ? MessageImportance.High : _reportImportance);
+
+            foreach (var r in EmbeddedResources.Where(r => r.ItemSpec.EndsWith(".xaml") || r.ItemSpec.EndsWith(".paml")))
                 BuildEngine.LogWarning(BuildEngineErrorCode.LegacyResmScheme, r.ItemSpec,
                     "XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work");
             var resources = BuildResourceSources();

+ 3 - 1
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@@ -40,7 +40,7 @@ namespace Avalonia.Build.Tasks
         }
         
         public static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory,
-            string output, bool verifyIl)
+            string output, bool verifyIl, MessageImportance logImportance)
         {
             var typeSystem = new CecilTypeSystem(references.Concat(new[] {input}), input);
             var asm = typeSystem.TargetAssemblyDefinition;
@@ -121,6 +121,8 @@ namespace Avalonia.Build.Tasks
                 {
                     try
                     {
+                        engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", logImportance);
+
                         // StreamReader is needed here to handle BOM
                         var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
                         var parsed = XDocumentXamlIlParser.Parse(xaml);

+ 1 - 1
src/Avalonia.Controls.DataGrid/DataGrid.cs

@@ -2245,7 +2245,7 @@ namespace Avalonia.Controls
         /// Builds the visual tree for the column header when a new template is applied.
         /// </summary>
         //TODO Validation UI
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             // The template has changed, so we need to refresh the visuals
             _measured = false;

+ 1 - 3
src/Avalonia.Controls.DataGrid/DataGridCell.cs

@@ -121,10 +121,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// Builds the visual tree for the cell control when a new template is applied.
         /// </summary>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
-            base.OnTemplateApplied(e);
-
             UpdatePseudoClasses();
             _rightGridLine = e.NameScope.Find<Rectangle>(DATAGRIDCELL_elementRightGridLine);
             if (_rightGridLine != null && OwningColumn == null)

+ 1 - 3
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@@ -536,10 +536,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// Builds the visual tree for the column header when a new template is applied.
         /// </summary>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
-            base.OnTemplateApplied(e);
-
             RootElement = e.NameScope.Find<Panel>(DATAGRIDROW_elementRoot);
             if (RootElement != null)
             {

+ 1 - 3
src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs

@@ -168,7 +168,7 @@ namespace Avalonia.Controls
 
         private IDisposable _expanderButtonSubscription;
 
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             _rootElement = e.NameScope.Find<Panel>(DataGridRow.DATAGRIDROW_elementRoot);
 
@@ -199,8 +199,6 @@ namespace Avalonia.Controls
             _itemCountElement = e.NameScope.Find<TextBlock>(DATAGRIDROWGROUPHEADER_itemCountElement);
             _propertyNameElement = e.NameScope.Find<TextBlock>(DATAGRIDROWGROUPHEADER_propertyNameElement);
             UpdateTitleElements();
-
-            base.OnTemplateApplied(e);
         }
 
         internal void ApplyHeaderStatus()

+ 1 - 3
src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs

@@ -94,10 +94,8 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Builds the visual tree for the row header when a new template is applied. 
         /// </summary>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
-            base.OnTemplateApplied(e);
-
             _rootElement = e.NameScope.Find<Control>(DATAGRIDROWHEADER_elementRootName);
             if (_rootElement != null)
             {

+ 6 - 2
src/Avalonia.Controls/Application.cs

@@ -254,8 +254,12 @@ namespace Avalonia
                 .Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>()
                 .Bind<IStyler>().ToConstant(_styler)
                 .Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance)
-                .Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance)
-                .Bind<IPlatformDragSource>().ToTransient<InProcessDragSource>();
+                .Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance);
+            
+            // 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();
             AvaloniaLocator.CurrentMutable

+ 2 - 2
src/Avalonia.Controls/AutoCompleteBox.cs

@@ -1212,7 +1212,7 @@ namespace Avalonia.Controls
         /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control
         /// when a new template is applied.
         /// </summary>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
 
             if (DropDownPopup != null)
@@ -1240,7 +1240,7 @@ namespace Avalonia.Controls
                 OpeningDropDown(false);
             }
 
-            base.OnTemplateApplied(e);
+            base.OnApplyTemplate(e);
         }
 
         /// <summary>

+ 1 - 1
src/Avalonia.Controls/ButtonSpinner.cs

@@ -121,7 +121,7 @@ namespace Avalonia.Controls
         }
 
         /// <inheritdoc />
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             IncreaseButton = e.NameScope.Find<Button>("PART_IncreaseButton");
             DecreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton");

+ 1 - 3
src/Avalonia.Controls/Calendar/Calendar.cs

@@ -2079,10 +2079,8 @@ namespace Avalonia.Controls
         /// <see cref="T:System.Windows.Controls.Calendar" /> when a new
         /// template is applied.
         /// </summary>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
-            base.OnTemplateApplied(e);
-
             Root = e.NameScope.Find<Panel>(PART_ElementRoot);
 
             SelectedMonth = DisplayDate;

+ 1 - 2
src/Avalonia.Controls/Calendar/CalendarButton.cs

@@ -98,9 +98,8 @@ namespace Avalonia.Controls.Primitives
         /// <see cref="T:System.Windows.Controls.Primitives.CalendarButton" />
         /// when a new template is applied.
         /// </summary>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
-            base.OnTemplateApplied(e);
             SetPseudoClasses();
         }
         

+ 2 - 2
src/Avalonia.Controls/Calendar/CalendarDayButton.cs

@@ -150,11 +150,11 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
-            base.OnTemplateApplied(e);
             SetPseudoClasses();
         }
+
         private void SetPseudoClasses()
         {
             if (_ignoringMouseOverState)

+ 1 - 3
src/Avalonia.Controls/Calendar/CalendarItem.cs

@@ -268,10 +268,8 @@ namespace Avalonia.Controls.Primitives
         /// <see cref="T:System.Windows.Controls.Primitives.CalendarItem" />
         /// when a new template is applied.
         /// </summary>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
-            base.OnTemplateApplied(e);
-            
             HeaderButton = e.NameScope.Find<Button>(PART_ElementHeaderButton);
             PreviousButton = e.NameScope.Find<Button>(PART_ElementPreviousButton);
             NextButton = e.NameScope.Find<Button>(PART_ElementNextButton);

+ 1 - 3
src/Avalonia.Controls/Calendar/DatePicker.cs

@@ -413,7 +413,7 @@ namespace Avalonia.Controls
             DisplayDate = DateTime.Today;
         }
 
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             if (_calendar != null)
             {
@@ -508,8 +508,6 @@ namespace Avalonia.Controls
                     SetSelectedDate();
                 }
             }
-
-            base.OnTemplateApplied(e);
         }
 
         protected override void OnPropertyChanged<T>(

+ 1 - 3
src/Avalonia.Controls/ComboBox.cs

@@ -219,7 +219,7 @@ namespace Avalonia.Controls
         }
 
         /// <inheritdoc/>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             if (_popup != null)
             {
@@ -230,8 +230,6 @@ namespace Avalonia.Controls
             _popup = e.NameScope.Get<Popup>("PART_Popup");
             _popup.Opened += PopupOpened;
             _popup.Closed += PopupClosed;
-
-            base.OnTemplateApplied(e);
         }
 
         /// <summary>

+ 1 - 2
src/Avalonia.Controls/ListBox.cs

@@ -161,10 +161,9 @@ namespace Avalonia.Controls
             }
         }
 
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             Scroll = e.NameScope.Find<IScrollable>("PART_ScrollViewer");
-            base.OnTemplateApplied(e);
         }
     }
 }

+ 1 - 3
src/Avalonia.Controls/MenuItem.cs

@@ -417,10 +417,8 @@ namespace Avalonia.Controls
         }
 
         /// <inheritdoc/>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
-            base.OnTemplateApplied(e);
-
             if (_popup != null)
             {
                 _popup.Opened -= PopupOpened;

+ 1 - 3
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@@ -77,10 +77,8 @@ namespace Avalonia.Controls.Notifications
         }
 
         /// <inheritdoc/>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
-            base.OnTemplateApplied(e);
-
             var itemsControl = e.NameScope.Find<Panel>("PART_Items");
             _items = itemsControl?.Children;
         }

+ 1 - 1
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@@ -295,7 +295,7 @@ namespace Avalonia.Controls
         }
 
         /// <inheritdoc />
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             if (TextBox != null)
             {

+ 1 - 3
src/Avalonia.Controls/Primitives/ScrollBar.cs

@@ -147,10 +147,8 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
-            base.OnTemplateApplied(e);
-
             if (_lineUpButton != null)
             {
                 _lineUpButton.Click -= LineUpClick;

+ 9 - 2
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@@ -263,7 +263,10 @@ namespace Avalonia.Controls.Primitives
                     if (nameScope == null)
                         nameScope = new NameScope();
 
-                    OnTemplateApplied(new TemplateAppliedEventArgs(nameScope));
+                    var e = new TemplateAppliedEventArgs(nameScope);
+                    OnApplyTemplate(e);
+                    OnTemplateApplied(e);
+                    RaiseEvent(e);
                 }
 
                 _appliedTemplate = template;
@@ -306,13 +309,17 @@ namespace Avalonia.Controls.Primitives
             base.OnDetachedFromLogicalTree(e);
         }
 
+        protected virtual void OnApplyTemplate(TemplateAppliedEventArgs e)
+        {
+        }
+
         /// <summary>
         /// Called when the control's template is applied.
         /// </summary>
         /// <param name="e">The event args.</param>
+        [Obsolete("Use OnApplyTemplate")]
         protected virtual void OnTemplateApplied(TemplateAppliedEventArgs e)
         {
-            RaiseEvent(e);
         }
 
         /// <summary>

+ 1 - 1
src/Avalonia.Controls/ProgressBar.cs

@@ -102,7 +102,7 @@ namespace Avalonia.Controls
         }
 
         /// <inheritdoc/>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             _indicator = e.NameScope.Get<Border>("PART_Indicator");
 

+ 9 - 2
src/Avalonia.Controls/SelectionModel.cs

@@ -774,7 +774,10 @@ namespace Avalonia.Controls
                 winrtEnd,
                 info =>
                 {
-                    info.ParentNode!.Select(info.Path.GetAt(info.Path.GetSize() - 1), select);
+                    if (info.Path >= winrtStart && info.Path <= winrtEnd)
+                    {
+                        info.ParentNode!.Select(info.Path.GetAt(info.Path.GetSize() - 1), select);
+                    }
                 });
         }
 
@@ -808,7 +811,11 @@ namespace Avalonia.Controls
             }
 
             OnSelectionChanged(e);
-            _rootNode.Cleanup();
+
+            if (_operationCount == 0)
+            {
+                _rootNode.Cleanup();
+            }
         }
 
         private void ApplyAutoSelect()

+ 59 - 75
src/Avalonia.Controls/SelectionNode.cs

@@ -8,6 +8,7 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Linq;
+using Avalonia.Controls.Utils;
 
 #nullable enable
 
@@ -214,7 +215,21 @@ namespace Avalonia.Controls
 
         public void SetChildrenObservable(IObservable<object?> resolver)
         {
-            _childrenSubscription = resolver.Subscribe(x => Source = x);
+            _childrenSubscription = resolver.Subscribe(x =>
+            {
+                if (Source != null)
+                {
+                    using (_manager.Update())
+                    {
+                        SelectionTreeHelper.Traverse(
+                            this,
+                            realizeChildren: false,
+                            info => info.Node.Clear());
+                    }
+                }
+
+                Source = x;
+            });
         }
 
         public int SelectedCount { get; private set; }
@@ -544,11 +559,14 @@ namespace Avalonia.Controls
 
         private void ClearChildNodes()
         {
-            foreach (var child in _childrenNodes)
+            for (int i = 0; i < _childrenNodes.Count; i++)
             {
+                var child = _childrenNodes[i];
+
                 if (child != null && child != _manager.SharedLeafNode)
                 {
                     child.Dispose();
+                    _childrenNodes[i] = null;
                 }
             }
 
@@ -713,101 +731,67 @@ namespace Avalonia.Controls
             var selectionInvalidated = false;
             var removed = new List<object?>();
             var count = items.Count;
-            
-            // Remove the items from the selection for leaf
-            if (ItemsSourceView!.Count > 0)
-            {
-                bool isSelected = false;
+            var isSelected = false;
 
-                for (int i = 0; i <= count - 1; i++)
+            for (int i = 0; i <= count - 1; i++)
+            {
+                if (IsSelected(index + i))
                 {
-                    if (IsSelected(index + i))
-                    {
-                        isSelected = true;
-                        removed.Add(items[i]);
-                    }
+                    isSelected = true;
+                    removed.Add(items[i]);
                 }
+            }
 
-                if (isSelected)
-                {
-                    var removeRange = new IndexRange(index, index + count - 1);
-                    SelectedCount -= IndexRange.Remove(_selected, removeRange);
-                    selectionInvalidated = true;
-
-                    if (_selectedItems != null)
-                    {
-                        foreach (var i in items)
-                        {
-                            _selectedItems.Remove(i);
-                        }
-                    }
-                }
+            if (isSelected)
+            {
+                var removeRange = new IndexRange(index, index + count - 1);
+                SelectedCount -= IndexRange.Remove(_selected, removeRange);
+                selectionInvalidated = true;
 
-                for (int i = 0; i < _selected.Count; i++)
+                if (_selectedItems != null)
                 {
-                    var range = _selected[i];
-
-                    // The range is after the removed items, need to shift the range left
-                    if (range.End > index)
+                    foreach (var i in items)
                     {
-                        // Shift the range to the left
-                        _selected[i] = new IndexRange(range.Begin - count, range.End - count);
-                        selectionInvalidated = true;
+                        _selectedItems.Remove(i);
                     }
                 }
+            }
 
-                // Update for non-leaf if we are tracking non-leaf nodes
-                if (_childrenNodes.Count > 0)
-                {
-                    selectionInvalidated = true;
-                    for (int i = 0; i < count; i++)
-                    {
-                        if (_childrenNodes[index] != null)
-                        {
-                            removed.AddRange(_childrenNodes[index]!.SelectedItems);
-                            RealizedChildrenNodeCount--;
-                            _childrenNodes[index]!.Dispose();
-                        }
-                        _childrenNodes.RemoveAt(index);
-                    }
-                }
+            for (int i = 0; i < _selected.Count; i++)
+            {
+                var range = _selected[i];
 
-                //Adjust the anchor
-                if (AnchorIndex >= index)
+                // The range is after the removed items, need to shift the range left
+                if (range.End > index)
                 {
-                    AnchorIndex -= count;
+                    // Shift the range to the left
+                    _selected[i] = new IndexRange(range.Begin - count, range.End - count);
+                    selectionInvalidated = true;
                 }
             }
-            else
-            {
-                // No more items in the list, clear
-                ClearSelection();
-                RealizedChildrenNodeCount = 0;
-                selectionInvalidated = true;
-            }
 
-            // Check if removing a node invalidated an ancestors
-            // selection state. For example if parent was partially selected before
-            // removing an item, it could be selected now.
-            if (!selectionInvalidated)
+            // Update for non-leaf if we are tracking non-leaf nodes
+            if (_childrenNodes.Count > 0)
             {
-                var parent = _parent;
-                
-                while (parent != null)
+                selectionInvalidated = true;
+                for (int i = 0; i < count; i++)
                 {
-                    var isSelected = parent.IsSelectedWithPartial();
-                    // If a parent is partially selected, then it will become selected.
-                    // If it is selected or not selected - there is no change.
-                    if (!isSelected.HasValue)
+                    if (_childrenNodes[index] != null)
                     {
-                        selectionInvalidated = true;
-                        break;
+                        removed.AddRange(_childrenNodes[index]!.SelectedItems);
+                        RealizedChildrenNodeCount--;
+                        _childrenNodes[index]!.Dispose();
                     }
-
-                    parent = parent._parent;
+                    _childrenNodes.RemoveAt(index);
                 }
             }
 
+            //Adjust the anchor
+            if (AnchorIndex >= index)
+            {
+                AnchorIndex -= count;
+            }
+
             return (selectionInvalidated, removed);
         }
 

+ 1 - 1
src/Avalonia.Controls/Slider.cs

@@ -82,7 +82,7 @@ namespace Avalonia.Controls
         }
 
         /// <inheritdoc/>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             if (_decreaseButton != null)
             {

+ 1 - 3
src/Avalonia.Controls/TabControl.cs

@@ -217,10 +217,8 @@ namespace Avalonia.Controls
             return new TabItemContainerGenerator(this);
         }
 
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
-            base.OnTemplateApplied(e);
-
             ItemsPresenterPart = e.NameScope.Get<ItemsPresenter>("PART_ItemsPresenter");
         }
 

+ 1 - 1
src/Avalonia.Controls/TextBox.cs

@@ -341,7 +341,7 @@ namespace Avalonia.Controls
             set { SetAndRaise(NewLineProperty, ref _newLine, value); }
         }
 
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             _presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
 

+ 9 - 1
src/Avalonia.Controls/TreeView.cs

@@ -4,6 +4,7 @@ using System.Collections;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Linq;
+using System.Reactive.Linq;
 using Avalonia.Controls.Generators;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Utils;
@@ -395,7 +396,14 @@ namespace Avalonia.Controls
         private void OnSelectionModelChildrenRequested(object sender, SelectionModelChildrenRequestedEventArgs e)
         {
             var container = ItemContainerGenerator.Index.ContainerFromItem(e.Source) as ItemsControl;
-            e.Children = container?.GetObservable(ItemsProperty);
+
+            if (container is object)
+            {
+                e.Children = Observable.CombineLatest(
+                    container.GetObservable(TreeViewItem.IsExpandedProperty),
+                    container.GetObservable(ItemsProperty),
+                    (expanded, items) => expanded ? items : null);
+            }
         }
 
         private TreeViewItem GetContainerInDirection(

+ 1 - 2
src/Avalonia.Controls/TreeViewItem.cs

@@ -162,9 +162,8 @@ namespace Avalonia.Controls
             // Don't call base.OnKeyDown - let events bubble up to containing TreeView.
         }
 
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
-            base.OnTemplateApplied(e);
             _header = e.NameScope.Find<IControl>("PART_Header");
         }
 

+ 76 - 0
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;
+        }
+    }
+}

+ 13 - 3
src/Avalonia.Native/AvaloniaNativePlatform.cs

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

+ 39 - 0
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;
+        }
+    }
+}

+ 87 - 11
src/Avalonia.Native/ClipboardImpl.cs

@@ -1,15 +1,22 @@
-using System.Threading.Tasks;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
 using System.Runtime.InteropServices;
+using Avalonia.Input;
 using Avalonia.Input.Platform;
 using Avalonia.Native.Interop;
 using Avalonia.Platform.Interop;
 
 namespace Avalonia.Native
 {
-    class ClipboardImpl : IClipboard
+    class ClipboardImpl : IClipboard, IDisposable
     {
         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)
         {
             _native = native;
@@ -22,14 +29,10 @@ namespace Avalonia.Native
             return Task.CompletedTask;
         }
 
-        public unsafe Task<string> GetTextAsync()
+        public Task<string> GetTextAsync()
         {
-            using (var text = _native.GetText())
-            {
-                var result = System.Text.Encoding.UTF8.GetString((byte*)text.Pointer(), text.Length());
-
-                return Task.FromResult(result);
-            }
+            using (var text = _native.GetText(NSPasteboardTypeString))
+                return Task.FromResult(text.String);
         }
 
         public Task SetTextAsync(string text)
@@ -40,11 +43,84 @@ namespace Avalonia.Native
             {
                 using (var buffer = new Utf8Buffer(text))
                 {
-                    _native.SetText(buffer.DangerousGetHandle());
+                    _native.SetText(NSPasteboardTypeString, buffer.DangerousGetHandle());
                 }
             }
 
             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 - 0
src/Avalonia.Native/WindowImplBase.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Runtime.InteropServices;
 using Avalonia.Controls;
 using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Input;
@@ -200,6 +201,30 @@ namespace Avalonia.Native
             {
                 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()
@@ -358,6 +383,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; }
     }
 }

+ 1 - 1
tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs

@@ -352,7 +352,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                 set => SetValue(PopupContentProperty, value);
             }
 
-            protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+            protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
             {
                 Popup = (Popup)this.GetVisualChildren().Single();
             }

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 219 - 532
tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs


+ 1 - 2
tests/Avalonia.Controls.UnitTests/TestTemplatedControl.cs

@@ -12,9 +12,8 @@ namespace Avalonia.Controls.UnitTests
             VisualChildren.Add(visual);
         }
 
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
-            base.OnTemplateApplied(e);
             OnTemplateAppliedCalled = true;
         }
     }

+ 13 - 0
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@@ -130,6 +130,7 @@ namespace Avalonia.Controls.UnitTests
 
             CreateNodeDataTemplate(target);
             ApplyTemplates(target);
+            ExpandAll(target);
 
             var item = tree[0].Children[1].Children[0];
             var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
@@ -157,6 +158,7 @@ namespace Avalonia.Controls.UnitTests
 
             CreateNodeDataTemplate(target);
             ApplyTemplates(target);
+            ExpandAll(target);
 
             var item = tree[0].Children[1].Children[0];
             var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
@@ -188,6 +190,7 @@ namespace Avalonia.Controls.UnitTests
 
             CreateNodeDataTemplate(target);
             ApplyTemplates(target);
+            ExpandAll(target);
 
             var item1 = tree[0].Children[1].Children[0];
             var container1 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1);
@@ -225,6 +228,7 @@ namespace Avalonia.Controls.UnitTests
 
             CreateNodeDataTemplate(target);
             ApplyTemplates(target);
+            ExpandAll(target);
 
             var rootNode = tree[0];
 
@@ -264,6 +268,7 @@ namespace Avalonia.Controls.UnitTests
 
             CreateNodeDataTemplate(target);
             ApplyTemplates(target);
+            ExpandAll(target);
 
             var rootNode = tree[0];
 
@@ -297,6 +302,7 @@ namespace Avalonia.Controls.UnitTests
 
             CreateNodeDataTemplate(target);
             ApplyTemplates(target);
+            ExpandAll(target);
 
             var rootNode = tree[0];
 
@@ -330,6 +336,7 @@ namespace Avalonia.Controls.UnitTests
 
             CreateNodeDataTemplate(target);
             ApplyTemplates(target);
+            ExpandAll(target);
 
             var rootNode = tree[0];
 
@@ -376,6 +383,7 @@ namespace Avalonia.Controls.UnitTests
 
             CreateNodeDataTemplate(target);
             ApplyTemplates(target);
+            ExpandAll(target);
 
             var item = tree[0].Children[1].Children[0];
             var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
@@ -402,6 +410,7 @@ namespace Avalonia.Controls.UnitTests
 
             CreateNodeDataTemplate(target);
             ApplyTemplates(target);
+            ExpandAll(target);
 
             var item = tree[0].Children[1].Children[0];
 
@@ -579,6 +588,7 @@ namespace Avalonia.Controls.UnitTests
 
                 CreateNodeDataTemplate(target);
                 ApplyTemplates(target);
+                ExpandAll(target);
 
                 var item = data[0].Children[0];
                 var node = target.ItemContainerGenerator.Index.ContainerFromItem(item);
@@ -614,6 +624,7 @@ namespace Avalonia.Controls.UnitTests
 
                 CreateNodeDataTemplate(target);
                 ApplyTemplates(target);
+                ExpandAll(target);
 
                 var rootNode = tree[0];
 
@@ -651,6 +662,7 @@ namespace Avalonia.Controls.UnitTests
 
                 CreateNodeDataTemplate(target);
                 ApplyTemplates(target);
+                ExpandAll(target);
 
                 var rootNode = tree[0];
 
@@ -697,6 +709,7 @@ namespace Avalonia.Controls.UnitTests
 
                 CreateNodeDataTemplate(target);
                 ApplyTemplates(target);
+                ExpandAll(target);
 
                 var rootNode = tree[0];
 

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно