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

Merge branch 'master' of https://github.com/AvaloniaUI/Avalonia into compiled-bindings

Jeremy Koritzinsky 6 жил өмнө
parent
commit
fd3d70430a
100 өөрчлөгдсөн 2169 нэмэгдсэн , 739 устгасан
  1. 55 3
      Avalonia.sln
  2. 1 0
      azure-pipelines.yml
  3. 8 0
      build/AndroidWorkarounds.props
  4. 1 0
      build/CoreLibraries.props
  5. 1 1
      native/Avalonia.Native/inc/avalonia-native.h
  6. 4 0
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  7. 16 4
      native/Avalonia.Native/src/OSX/AvnString.mm
  8. 24 0
      native/Avalonia.Native/src/OSX/app.mm
  9. 10 1
      native/Avalonia.Native/src/OSX/clipboard.mm
  10. 2 2
      native/Avalonia.Native/src/OSX/common.h
  11. 4 10
      native/Avalonia.Native/src/OSX/main.mm
  12. 33 26
      native/Avalonia.Native/src/OSX/platformthreading.mm
  13. 1 0
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  14. 20 11
      samples/ControlCatalog.NetCore/Program.cs
  15. 1 1
      samples/ControlCatalog/DecoratedWindow.xaml.cs
  16. 19 5
      samples/ControlCatalog/MainView.xaml
  17. 1 0
      samples/ControlCatalog/MainWindow.xaml
  18. 1 0
      samples/ControlCatalog/MainWindow.xaml.cs
  19. 1 1
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  20. 3 2
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  21. 11 1
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  22. 14 1
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  23. 46 6
      samples/ControlCatalog/Pages/ListBoxPage.xaml.cs
  24. 9 0
      samples/ControlCatalog/Pages/PointersPage.cs
  25. 4 1
      samples/ControlCatalog/Pages/ScreenPage.cs
  26. 23 10
      samples/ControlCatalog/Pages/TreeViewPage.xaml
  27. 84 9
      samples/ControlCatalog/Pages/TreeViewPage.xaml.cs
  28. 17 9
      samples/ControlCatalog/SideBar.xaml
  29. 27 0
      samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
  30. 0 5
      src/Android/Avalonia.Android/AndroidPlatform.cs
  31. 0 112
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  32. 2 0
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  33. 4 4
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  34. 3 3
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  35. 5 0
      src/Avalonia.Base/AvaloniaProperty.cs
  36. 56 18
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  37. 170 59
      src/Avalonia.Base/Collections/AvaloniaList.cs
  38. 3 3
      src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
  39. 14 12
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  40. 4 4
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  41. 9 2
      src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs
  42. 3 2
      src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
  43. 2 2
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  44. 11 12
      src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
  45. 4 4
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  46. 2 3
      src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
  47. 1 2
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs
  48. 2 2
      src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs
  49. 26 13
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  50. 15 8
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  51. 15 6
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  52. 7 5
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
  53. 9 4
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
  54. 4 2
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  55. 16 2
      src/Avalonia.Base/Data/Core/SettableNode.cs
  56. 2 2
      src/Avalonia.Base/Data/Core/StreamNode.cs
  57. 21 0
      src/Avalonia.Base/Data/DataValidationException.cs
  58. 11 0
      src/Avalonia.Controls/AppBuilderBase.cs
  59. 1 1
      src/Avalonia.Controls/ComboBox.cs
  60. 2 0
      src/Avalonia.Controls/ContextMenu.cs
  61. 9 6
      src/Avalonia.Controls/ControlExtensions.cs
  62. 16 8
      src/Avalonia.Controls/DataValidationErrors.cs
  63. 1 0
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  64. 6 0
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  65. 3 3
      src/Avalonia.Controls/MenuItem.cs
  66. 1 0
      src/Avalonia.Controls/Mixins/ContentControlMixin.cs
  67. 1 1
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  68. 17 2
      src/Avalonia.Controls/PlacementMode.cs
  69. 1 1
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  70. 23 0
      src/Avalonia.Controls/Platform/IMountedVolumeInfoProvider.cs
  71. 3 1
      src/Avalonia.Controls/Platform/IPopupImpl.cs
  72. 2 0
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  73. 1 23
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  74. 27 0
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  75. 0 1
      src/Avalonia.Controls/Platform/IWindowingPlatform.cs
  76. 13 13
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  77. 24 0
      src/Avalonia.Controls/Platform/MountedDriveInfo.cs
  78. 0 5
      src/Avalonia.Controls/Platform/PlatformManager.cs
  79. 6 1
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  80. 1 1
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  81. 0 42
      src/Avalonia.Controls/Primitives/AdornerDecorator.cs
  82. 1 1
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  83. 26 0
      src/Avalonia.Controls/Primitives/IPopupHost.cs
  84. 38 0
      src/Avalonia.Controls/Primitives/OverlayLayer.cs
  85. 149 0
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  86. 144 140
      src/Avalonia.Controls/Primitives/Popup.cs
  87. 358 0
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  88. 175 0
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  89. 50 0
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs
  90. 55 64
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  91. 3 2
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  92. 93 0
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  93. 14 10
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  94. 2 2
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  95. 5 3
      src/Avalonia.Controls/Repeater/ItemsSourceView.cs
  96. 13 3
      src/Avalonia.Controls/SystemDialog.cs
  97. 11 2
      src/Avalonia.Controls/TextBox.cs
  98. 8 6
      src/Avalonia.Controls/ToolTip.cs
  99. 1 1
      src/Avalonia.Controls/TopLevel.cs
  100. 3 1
      src/Avalonia.Controls/TreeView.cs

+ 55 - 3
Avalonia.sln

@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2027
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29102.190
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
 EndProject
@@ -197,7 +197,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformSanityChecks", "sam
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid", "src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj", "{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid", "src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj", "{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
 EndProject
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
@@ -1842,6 +1846,54 @@ Global
 		{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhone.Build.0 = Release|Any CPU
 		{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|Any CPU.Build.0 = Release|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|iPhone.Build.0 = Release|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|Any CPU.Build.0 = Release|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhone.Build.0 = Release|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 1 - 0
azure-pipelines.yml

@@ -134,3 +134,4 @@ jobs:
       pathToPublish: '$(Build.SourcesDirectory)/artifacts/zip'
       artifactName: 'Samples'
     condition: succeeded()
+

+ 8 - 0
build/AndroidWorkarounds.props

@@ -5,4 +5,12 @@
     <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.1" />
     <PackageReference Include="System.Buffers" Version="4.5.0" />
   </ItemGroup>
+  <Target Name="_RemoveNonExistingResgenFile" BeforeTargets="CoreCompile" Condition="'$(_SdkSetAndroidResgenFile)' == 'true' And '$(AndroidResgenFile)' != '' And !Exists('$(AndroidResgenFile)')">
+    <ItemGroup>
+      <Compile Remove="$(AndroidResgenFile)"/>
+    </ItemGroup>
+  </Target>
+  <PropertyGroup>
+    <DesignTimeBuild>false</DesignTimeBuild>
+  </PropertyGroup>
 </Project>

+ 1 - 0
build/CoreLibraries.props

@@ -13,6 +13,7 @@
       <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Styling/Avalonia.Styling.csproj" />
       <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj" />
       <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.OpenGL/Avalonia.OpenGL.csproj" />
+      <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Dialogs/Avalonia.Dialogs.csproj" />
       <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup/Avalonia.Markup.csproj" />
       <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj" />
       <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj" Condition="'$(TargetFramework)' != 'netstandard2.0'" />

+ 1 - 1
native/Avalonia.Native/inc/avalonia-native.h

@@ -280,7 +280,7 @@ AVNCOM(IAvnPlatformThreadingInterface, 0b) : IUnknown
     virtual bool GetCurrentThreadIsLoopThread() = 0;
     virtual void SetSignaledCallback(IAvnSignaledCallback* cb) = 0;
     virtual IAvnLoopCancellation* CreateLoopCancellation() = 0;
-    virtual void RunLoop(IAvnLoopCancellation* cancel) = 0;
+    virtual HRESULT RunLoop(IAvnLoopCancellation* cancel) = 0;
     // Can't pass int* to sharpgentools for some reason
     virtual void Signal(int priority) = 0;
     virtual IUnknown* StartTimer(int priority, int ms, IAvnActionCallback* callback) = 0;

+ 4 - 0
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@@ -7,6 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
 		37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
 		37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
 		37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; };
@@ -22,6 +23,7 @@
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
+		1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; 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>"; };
 		37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; };
@@ -68,6 +70,7 @@
 		AB7A61E62147C814003C5833 = {
 			isa = PBXGroup;
 			children = (
+				1A002B9D232135EE00021753 /* app.mm */,
 				37DDA9B121933371002E132B /* AvnString.h */,
 				37DDA9AF219330F8002E132B /* AvnString.mm */,
 				37A4E71A2178846A00EACBCD /* headers */,
@@ -164,6 +167,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				1A002B9E232135EE00021753 /* app.mm in Sources */,
 				5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */,
 				5B21A982216530F500CEE36E /* cursor.mm in Sources */,
 				37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,

+ 16 - 4
native/Avalonia.Native/src/OSX/AvnString.mm

@@ -11,14 +11,26 @@
 class AvnStringImpl : public virtual ComSingleObject<IAvnString, &IID_IAvnString>
 {
 private:
-    NSString* _string;
+    int _length;
+    const char* _cstring;
     
 public:
     FORWARD_IUNKNOWN()
     
     AvnStringImpl(NSString* string)
+    { 
+        auto cstring = [string cStringUsingEncoding:NSUTF8StringEncoding];
+        _length = (int)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+        
+        _cstring = (const char*)malloc(_length + 5);
+        
+        memset((void*)_cstring, 0, _length + 5);
+        memcpy((void*)_cstring, (void*)cstring, _length);
+    }
+    
+    virtual ~AvnStringImpl()
     {
-        _string = string;
+        free((void*)_cstring);
     }
     
     virtual HRESULT Pointer(void**retOut) override
@@ -30,7 +42,7 @@ public:
                 return E_POINTER;
             }
             
-            *retOut = (void*)_string.UTF8String;
+            *retOut = (void*)_cstring;
             
             return S_OK;
         }
@@ -43,7 +55,7 @@ public:
             return E_POINTER;
         }
         
-        *retOut = (int)_string.length;
+        *retOut = _length;
         
         return S_OK;
     }

+ 24 - 0
native/Avalonia.Native/src/OSX/app.mm

@@ -0,0 +1,24 @@
+#include "common.h"
+@interface AvnAppDelegate : NSObject<NSApplicationDelegate>
+@end
+extern NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
+@implementation AvnAppDelegate
+- (void)applicationWillFinishLaunching:(NSNotification *)notification
+{
+    [[NSApplication sharedApplication] setActivationPolicy: AvnDesiredActivationPolicy];
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification *)notification
+{
+    [NSApp activateIgnoringOtherApps:true];
+}
+
+@end
+
+extern void InitializeAvnApp()
+{
+    NSApplication* app = [NSApplication sharedApplication];
+    id delegate = [AvnAppDelegate new];
+    [app setDelegate:delegate];
+    
+}

+ 10 - 1
native/Avalonia.Native/src/OSX/clipboard.mm

@@ -8,6 +8,13 @@ class Clipboard : public ComSingleObject<IAvnClipboard, &IID_IAvnClipboard>
 {
 public:
     FORWARD_IUNKNOWN()
+    
+    Clipboard()
+    {
+        NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
+        [pasteBoard stringForType:NSPasteboardTypeString];
+    }
+    
     virtual HRESULT GetText (IAvnString**ppv) override
     {
         @autoreleasepool
@@ -39,7 +46,9 @@ public:
     {
         @autoreleasepool
         {
-            [[NSPasteboard generalPasteboard] clearContents];
+            NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
+            [pasteBoard clearContents];
+            [pasteBoard setString:@"" forType:NSPasteboardTypeString];
         }
         
         return S_OK;

+ 2 - 2
native/Avalonia.Native/src/OSX/common.h

@@ -19,12 +19,12 @@ extern IAvnClipboard* CreateClipboard();
 extern IAvnCursorFactory* CreateCursorFactory();
 extern IAvnGlFeature* GetGlFeature();
 extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view);
-
+extern void InitializeAvnApp();
+extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
 extern NSPoint ToNSPoint (AvnPoint p);
 extern AvnPoint ToAvnPoint (NSPoint p);
 extern AvnPoint ConvertPointY (AvnPoint p);
 extern NSSize ToNSSize (AvnSize s);
-
 #ifdef DEBUG
 #define NSDebugLog(...) NSLog(__VA_ARGS__)
 #else

+ 4 - 10
native/Avalonia.Native/src/OSX/main.mm

@@ -5,21 +5,14 @@
 #define COM_GUIDS_MATERIALIZE
 #include "common.h"
 
-static BOOL ShowInDock = 1;
-
-static void SetActivationPolicy()
-{
-    [[NSApplication sharedApplication] setActivationPolicy: (ShowInDock ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory)];
-}
-
 class MacOptions : public ComSingleObject<IAvnMacOptions, &IID_IAvnMacOptions>
 {
 public:
     FORWARD_IUNKNOWN()
     virtual HRESULT SetShowInDock(int show)  override
     {
-        ShowInDock = show;
-        SetActivationPolicy();
+        AvnDesiredActivationPolicy = show
+            ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory;
         return S_OK;
     }
 };
@@ -64,8 +57,9 @@ public:
     {
         @autoreleasepool{
             [[ThreadingInitializer new] do];
-            return S_OK;
         }
+        InitializeAvnApp();
+        return S_OK;
     };
     
     virtual IAvnMacOptions* GetMacOptions()  override

+ 33 - 26
native/Avalonia.Native/src/OSX/platformthreading.mm

@@ -57,16 +57,36 @@ class PlatformThreadingInterface : public ComSingleObject<IAvnPlatformThreadingI
 {
 private:
     Signaler* _signaler;
+    bool _wasRunningAtLeastOnce = false;
     
     class LoopCancellation : public ComSingleObject<IAvnLoopCancellation, &IID_IAvnLoopCancellation>
     {
     public:
         FORWARD_IUNKNOWN()
-        bool Cancelled = 0;
-        virtual void Cancel() override
+        bool Running = false;
+        bool Cancelled = false;
+        virtual void Cancel()
         {
-            Cancelled = 1;
+            Cancelled = true;
+            if(Running)
+            {
+                Running = false;
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [[NSApplication sharedApplication] stop:nil];
+                    NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
+                                                        location:NSMakePoint(0, 0)
+                                                   modifierFlags:0
+                                                       timestamp:0
+                                                    windowNumber:0
+                                                         context:nil
+                                                         subtype:0
+                                                           data1:0
+                                                           data2:0];
+                    [NSApp postEvent:event atStart:YES];
+                });
+            }
         }
+
     };
     
 public:
@@ -99,30 +119,17 @@ public:
         return new LoopCancellation();
     }
     
-    virtual void RunLoop(IAvnLoopCancellation* cancel) override
+    virtual HRESULT RunLoop(IAvnLoopCancellation* cancel) override
     {
-        @autoreleasepool {
-            auto can = dynamic_cast<LoopCancellation*>(cancel);
-            [[NSApplication sharedApplication] activateIgnoringOtherApps:true];
-            while(true)
-            {
-                @autoreleasepool
-                {
-                    if(can != NULL && can->Cancelled)
-                        return;
-                    NSEvent* ev = [[NSApplication sharedApplication]
-                                   nextEventMatchingMask:NSEventMaskAny
-                                   untilDate: [NSDate dateWithTimeIntervalSinceNow:1]
-                                   inMode:NSDefaultRunLoopMode
-                                   dequeue:true];
-                    if(can != NULL && can->Cancelled)
-                        return;
-                    if(ev != NULL)
-                        [[NSApplication sharedApplication] sendEvent:ev];
-                }
-            }
-            NSDebugLog(@"RunLoop exited");
-        }
+        auto can = dynamic_cast<LoopCancellation*>(cancel);
+        if(can->Cancelled)
+            return S_OK;
+        if(_wasRunningAtLeastOnce)
+            return E_FAIL;
+        can->Running = true;
+        _wasRunningAtLeastOnce = true;
+        [NSApp run];
+        return S_OK;
     }
     
     virtual void Signal(int priority) override

+ 1 - 0
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@@ -7,6 +7,7 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\..\src\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
     <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
     <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />

+ 20 - 11
samples/ControlCatalog.NetCore/Program.cs

@@ -1,21 +1,19 @@
 using System;
 using System.Diagnostics;
+using System.Globalization;
 using System.Linq;
 using System.Threading;
 using Avalonia;
-using Avalonia.Controls;
-using Avalonia.LinuxFramebuffer.Output;
-using Avalonia.Skia;
 using Avalonia.ReactiveUI;
+using Avalonia.Dialogs;
 
 namespace ControlCatalog.NetCore
 {
     static class Program
     {
-
+        [STAThread]
         static int Main(string[] args)
         {
-            Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA);
             if (args.Contains("--wait-for-attach"))
             {
                 Console.WriteLine("Attach debugger and use 'Set next statement'");
@@ -28,34 +26,44 @@ namespace ControlCatalog.NetCore
             }
 
             var builder = BuildAvaloniaApp();
+
+            double GetScaling()
+            {
+                var idx = Array.IndexOf(args, "--scaling");
+                if (idx != 0 && args.Length > idx + 1 &&
+                    double.TryParse(args[idx + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out var scaling))
+                    return scaling;
+                return 1;
+            }
             if (args.Contains("--fbdev"))
             {
                 SilenceConsole();
-                return builder.StartLinuxFbDev(args);
+                return builder.StartLinuxFbDev(args, scaling: GetScaling());
             }
             else if (args.Contains("--drm"))
             {
                 SilenceConsole();
-                return builder.StartLinuxDrm(args);
+                return builder.StartLinuxDrm(args, scaling: GetScaling());
             }
             else
                 return builder.StartWithClassicDesktopLifetime(args);
         }
-        
+
         /// <summary>
         /// This method is needed for IDE previewer infrastructure
         /// </summary>
         public static AppBuilder BuildAvaloniaApp()
             => AppBuilder.Configure<App>()
                 .UsePlatformDetect()
-                .With(new X11PlatformOptions {EnableMultiTouch = true})
+                .With(new X11PlatformOptions { EnableMultiTouch = true })
                 .With(new Win32PlatformOptions
                 {
                     EnableMultitouch = true,
                     AllowEglInitialization = true
                 })
                 .UseSkia()
-                .UseReactiveUI();
+                .UseReactiveUI()
+                .UseManagedSystemDialogs();
 
         static void SilenceConsole()
         {
@@ -64,7 +72,8 @@ namespace ControlCatalog.NetCore
                 Console.CursorVisible = false;
                 while (true)
                     Console.ReadKey(true);
-            }) {IsBackground = true}.Start();
+            })
+            { IsBackground = true }.Start();
         }
     }
 }

+ 1 - 1
samples/ControlCatalog/DecoratedWindow.xaml.cs

@@ -34,7 +34,7 @@ namespace ControlCatalog
             SetupSide("Left", StandardCursorType.LeftSide, WindowEdge.West);
             SetupSide("Right", StandardCursorType.RightSide, WindowEdge.East);
             SetupSide("Top", StandardCursorType.TopSide, WindowEdge.North);
-            SetupSide("Bottom", StandardCursorType.BottomSize, WindowEdge.South);
+            SetupSide("Bottom", StandardCursorType.BottomSide, WindowEdge.South);
             SetupSide("TopLeft", StandardCursorType.TopLeftCorner, WindowEdge.NorthWest);
             SetupSide("TopRight", StandardCursorType.TopRightCorner, WindowEdge.NorthEast);
             SetupSide("BottomLeft", StandardCursorType.BottomLeftCorner, WindowEdge.SouthWest);

+ 19 - 5
samples/ControlCatalog/MainView.xaml

@@ -6,10 +6,13 @@
         Foreground="{DynamicResource ThemeForegroundBrush}"
         FontSize="{DynamicResource FontSizeNormal}">
   <Grid>
-    <ComboBox x:Name="Themes" SelectedIndex="0" Width="100" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Bottom">
-      <ComboBoxItem>Light</ComboBoxItem>
-      <ComboBoxItem>Dark</ComboBoxItem>
-    </ComboBox>
+    <Grid.Styles>
+        <Style Selector="TextBlock.h2">
+            <Setter Property="TextWrapping" Value="Wrap"/>
+            <Setter Property="MaxWidth" Value="400"/>
+            <Setter Property="HorizontalAlignment" Value="Left"/>
+        </Style>
+    </Grid.Styles>  
     <TabControl Classes="sidebar" Name="Sidebar">
       <TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
       <TabItem Header="Border"><pages:BorderPage/></TabItem>
@@ -21,7 +24,12 @@
       <TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
       <TabItem Header="ComboBox"><pages:ComboBoxPage/></TabItem>
       <TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
-      <TabItem Header="DataGrid"><pages:DataGridPage/></TabItem>
+      <!-- DataGrid is our special snowflake -->  
+      <TabItem Header="DataGrid" 
+               ScrollViewer.VerticalScrollBarVisibility="Disabled"
+               ScrollViewer.HorizontalScrollBarVisibility="Disabled">
+          <pages:DataGridPage/>
+      </TabItem>
       <TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem>
       <TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
       <TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
@@ -42,6 +50,12 @@
       <TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
       <TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
       <TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
+    <TabControl.Tag>
+        <ComboBox x:Name="Themes" SelectedIndex="0" Width="100" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Bottom">
+            <ComboBoxItem>Light</ComboBoxItem>
+            <ComboBoxItem>Dark</ComboBoxItem>
+        </ComboBox>
+    </TabControl.Tag>
     </TabControl>
   </Grid>
 </UserControl>

+ 1 - 0
samples/ControlCatalog/MainWindow.xaml

@@ -1,4 +1,5 @@
 <Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
+        Width="1024" Height="800"
         xmlns:pages="clr-namespace:ControlCatalog.Pages"
         Title="Avalonia Control Gallery"
         Icon="/Assets/test_icon.ico"

+ 1 - 0
samples/ControlCatalog/MainWindow.xaml.cs

@@ -6,6 +6,7 @@ using Avalonia.Markup.Xaml;
 using Avalonia.Threading;
 using ControlCatalog.ViewModels;
 using System;
+using System.Collections.Generic;
 using System.Threading.Tasks;
 
 namespace ControlCatalog

+ 1 - 1
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@@ -29,7 +29,7 @@ namespace ControlCatalog.Pages
             DataObject dragData = new DataObject();
             dragData.Set(DataFormats.Text, $"You have dragged text {++DragCount} times");
 
-            var result = await DragDrop.DoDragDrop(dragData, DragDropEffects.Copy);
+            var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
             switch(result)
             {
                 case DragDropEffects.Copy:

+ 3 - 2
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@@ -6,19 +6,20 @@
       <TextBlock Classes="h1">ItemsRepeater</TextBlock>
       <TextBlock Classes="h2">A data-driven collection control that incorporates a flexible layout system, custom views, and virtualization.</TextBlock>
     </StackPanel>
-    <StackPanel DockPanel.Dock="Right" Margin="8 0">
+    <StackPanel DockPanel.Dock="Right" Margin="8 0" Spacing="4">
       <ComboBox SelectedIndex="0" SelectionChanged="LayoutChanged">
         <ComboBoxItem>Stack - Vertical</ComboBoxItem>
         <ComboBoxItem>Stack - Horizontal</ComboBoxItem>
         <ComboBoxItem>UniformGrid - Vertical</ComboBoxItem>
         <ComboBoxItem>UniformGrid - Horizontal</ComboBoxItem>
       </ComboBox>
+      <Button Command="{Binding AddItem}">Add Item</Button>
     </StackPanel>
     <Border BorderThickness="1" BorderBrush="{DynamicResource ThemeBorderMidBrush}" Margin="0 0 0 16">
       <ScrollViewer Name="scroller"
                     HorizontalScrollBarVisibility="Auto"
                     VerticalScrollBarVisibility="Auto">
-        <ItemsRepeater Name="repeater" Items="{Binding}"/>
+        <ItemsRepeater Name="repeater" Background="Transparent" Items="{Binding Items}"/>
       </ScrollViewer>
     </Border>
   </DockPanel>

+ 11 - 1
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@@ -1,8 +1,11 @@
+using System;
 using System.Linq;
 using Avalonia.Controls;
 using Avalonia.Controls.Primitives;
+using Avalonia.Input;
 using Avalonia.Layout;
 using Avalonia.Markup.Xaml;
+using ControlCatalog.ViewModels;
 
 namespace ControlCatalog.Pages
 {
@@ -16,7 +19,8 @@ namespace ControlCatalog.Pages
             this.InitializeComponent();
             _repeater = this.FindControl<ItemsRepeater>("repeater");
             _scroller = this.FindControl<ScrollViewer>("scroller");
-            DataContext = Enumerable.Range(1, 100000).Select(i => $"Item {i}" ).ToArray();
+            _repeater.PointerPressed += RepeaterClick;
+            DataContext = new ItemsRepeaterPageViewModel();
         }
 
         private void InitializeComponent()
@@ -67,5 +71,11 @@ namespace ControlCatalog.Pages
                     break;
             }
         }
+
+        private void RepeaterClick(object sender, PointerPressedEventArgs e)
+        {
+            var item = (e.Source as TextBlock)?.DataContext as string;
+            ((ItemsRepeaterPageViewModel)DataContext).SelectedItem = item;
+        }
     }
 }

+ 14 - 1
samples/ControlCatalog/Pages/ListBoxPage.xaml

@@ -9,7 +9,20 @@
               Margin="0,16,0,0"
               HorizontalAlignment="Center"
               Spacing="16">
-      <ListBox Items="{Binding}" Width="250" Height="350"></ListBox>
+      <StackPanel Orientation="Vertical" Spacing="8">
+        <ListBox Items="{Binding Items}" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350"></ListBox>
+
+        <Button Command="{Binding AddItemCommand}">Add</Button>
+
+        <Button Command="{Binding RemoveItemCommand}">Remove</Button>
+
+        <ComboBox SelectedIndex="{Binding SelectionMode, Mode=TwoWay}">
+          <ComboBoxItem>Single</ComboBoxItem>
+          <ComboBoxItem>Multiple</ComboBoxItem>
+          <ComboBoxItem>Toggle</ComboBoxItem>
+          <ComboBoxItem>AlwaysSelected</ComboBoxItem>
+        </ComboBox>
+      </StackPanel>
     </StackPanel>
   </StackPanel>
 </UserControl>

+ 46 - 6
samples/ControlCatalog/Pages/ListBoxPage.xaml.cs

@@ -1,9 +1,9 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
+using System.Reactive;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
+using ReactiveUI;
 
 namespace ControlCatalog.Pages
 {
@@ -11,9 +11,8 @@ namespace ControlCatalog.Pages
     {
         public ListBoxPage()
         {
-            this.InitializeComponent();
-            DataContext = Enumerable.Range(1, 10).Select(i => $"Item {i}" )
-                .ToArray();
+            InitializeComponent();
+            DataContext = new PageViewModel();
         }
 
         private void InitializeComponent()
@@ -21,5 +20,46 @@ namespace ControlCatalog.Pages
             AvaloniaXamlLoader.Load(this);
         }
 
+        private class PageViewModel : ReactiveObject
+        {
+            private int _counter;
+            private SelectionMode _selectionMode;
+
+            public PageViewModel()
+            {
+                Items = new ObservableCollection<string>(Enumerable.Range(1, 10).Select(i => GenerateItem()));
+                SelectedItems = new ObservableCollection<string>();
+
+                AddItemCommand = ReactiveCommand.Create(() => Items.Add(GenerateItem()));
+
+                RemoveItemCommand = ReactiveCommand.Create(() =>
+                {
+                    while (SelectedItems.Count > 0)
+                    {
+                        Items.Remove(SelectedItems[0]);
+                    }
+                });
+            }
+
+            public ObservableCollection<string> Items { get; }
+
+            public ObservableCollection<string> SelectedItems { get; }
+
+            public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
+
+            public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; }
+
+            public SelectionMode SelectionMode
+            {
+                get => _selectionMode;
+                set
+                {
+                    SelectedItems.Clear();
+                    this.RaiseAndSetIfChanged(ref _selectionMode, value);
+                }
+            }
+
+            private string GenerateItem() => $"Item {_counter++}";
+        }
     }
 }

+ 9 - 0
samples/ControlCatalog/Pages/PointersPage.cs

@@ -69,16 +69,25 @@ namespace ControlCatalog.Pages
         {
             UpdatePointer(e);
             e.Pointer.Capture(this);
+            e.Handled = true;
             base.OnPointerPressed(e);
         }
 
         protected override void OnPointerMoved(PointerEventArgs e)
         {
             UpdatePointer(e);
+            e.Handled = true;
             base.OnPointerMoved(e);
         }
 
         protected override void OnPointerReleased(PointerReleasedEventArgs e)
+        {
+            _pointers.Remove(e.Pointer);
+            e.Handled = true;
+            InvalidateVisual();
+        }
+
+        protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
         {
             _pointers.Remove(e.Pointer);
             InvalidateVisual();

+ 4 - 1
samples/ControlCatalog/Pages/ScreenPage.cs

@@ -22,7 +22,10 @@ namespace ControlCatalog.Pages
         public override void Render(DrawingContext context)
         {
             base.Render(context);
-            Window w = (Window)VisualRoot;
+            if (!(VisualRoot is Window w))
+            {
+                return;                
+            }
             var screens = w.Screens.All;
             var scaling = ((IRenderRoot)w).RenderScaling;
 

+ 23 - 10
samples/ControlCatalog/Pages/TreeViewPage.xaml

@@ -6,16 +6,29 @@
     <TextBlock Classes="h2">Displays a hierachical tree of data.</TextBlock>
 
     <StackPanel Orientation="Horizontal"
-              Margin="0,16,0,0"
-              HorizontalAlignment="Center"
-              Spacing="16">
-      <TreeView SelectionMode="Multiple" Items="{Binding}" Width="250" Height="350">
-        <TreeView.ItemTemplate>
-          <TreeDataTemplate ItemsSource="{Binding Children}">
-            <TextBlock Text="{Binding Header}"/>
-          </TreeDataTemplate>
-        </TreeView.ItemTemplate>
-      </TreeView>
+                Margin="0,16,0,0"
+                HorizontalAlignment="Center"
+                Spacing="16">
+      <StackPanel Orientation="Vertical" Spacing="8">
+        <TreeView Items="{Binding Items}" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350">
+          <TreeView.ItemTemplate>
+            <TreeDataTemplate ItemsSource="{Binding Children}">
+              <TextBlock Text="{Binding Header}"/>
+            </TreeDataTemplate>
+          </TreeView.ItemTemplate>
+        </TreeView>
+
+        <Button Command="{Binding AddItemCommand}">Add</Button>
+
+        <Button Command="{Binding RemoveItemCommand}">Remove</Button>
+
+        <ComboBox SelectedIndex="{Binding SelectionMode, Mode=TwoWay}">
+          <ComboBoxItem>Single</ComboBoxItem>
+          <ComboBoxItem>Multiple</ComboBoxItem>
+          <ComboBoxItem>Toggle</ComboBoxItem>
+          <ComboBoxItem>AlwaysSelected</ComboBoxItem>
+        </ComboBox>
+      </StackPanel>
     </StackPanel>
   </StackPanel>
 </UserControl>

+ 84 - 9
samples/ControlCatalog/Pages/TreeViewPage.xaml.cs

@@ -1,8 +1,9 @@
-using System.Collections;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
+using System.Reactive;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
+using ReactiveUI;
 
 namespace ControlCatalog.Pages
 {
@@ -10,8 +11,8 @@ namespace ControlCatalog.Pages
     {
         public TreeViewPage()
         {
-            this.InitializeComponent();
-            DataContext = new Node().Children;
+            InitializeComponent();
+            DataContext = new PageViewModel();
         }
 
         private void InitializeComponent()
@@ -19,22 +20,96 @@ namespace ControlCatalog.Pages
             AvaloniaXamlLoader.Load(this);
         }
 
-        public class Node
+        private class PageViewModel : ReactiveObject
         {
-            private IList<Node> _children;
+            private SelectionMode _selectionMode;
+
+            public PageViewModel()
+            {
+                Node root = new Node();
+                Items = root.Children;
+                SelectedItems = new ObservableCollection<Node>();
+
+                AddItemCommand = ReactiveCommand.Create(() =>
+                {
+                    Node parentItem = SelectedItems.Count > 0 ? SelectedItems[0] : root;
+                    parentItem.AddNewItem();
+                });
+
+                RemoveItemCommand = ReactiveCommand.Create(() =>
+                {
+                    while (SelectedItems.Count > 0)
+                    {
+                        Node lastItem = SelectedItems[0];
+                        RecursiveRemove(Items, lastItem);
+                        SelectedItems.Remove(lastItem);
+                    }
+
+                    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 ObservableCollection<Node> SelectedItems { get; }
+
+            public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
+
+            public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; }
+
+            public SelectionMode SelectionMode
+            {
+                get => _selectionMode;
+                set
+                {
+                    SelectedItems.Clear();
+                    this.RaiseAndSetIfChanged(ref _selectionMode, value);
+                }
+            }
+        }
+
+        private class Node
+        {
+            private int _counter;
+            private ObservableCollection<Node> _children;
+
             public string Header { get; private set; }
-            public IList<Node> Children
+
+            public bool AreChildrenInitialized => _children != null;
+
+            public ObservableCollection<Node> Children
             {
                 get
                 {
                     if (_children == null)
                     {
-                        _children = Enumerable.Range(1, 10).Select(i => new Node() {Header = $"Item {i}"})
-                            .ToArray();
+                        _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++}"};
         }
     }
 }

+ 17 - 9
samples/ControlCatalog/SideBar.xaml

@@ -24,7 +24,8 @@
                             Name="PART_ScrollViewer"
                             HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
                             VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
-                            Background="{TemplateBinding Background}">
+                            Background="{TemplateBinding Background}"
+                            DockPanel.Dock="Left">
                             <ItemsPresenter
                                 Name="PART_ItemsPresenter"                          
                                 Items="{TemplateBinding Items}"
@@ -32,14 +33,19 @@
                                 ItemTemplate="{TemplateBinding ItemTemplate}">
                             </ItemsPresenter>
                         </ScrollViewer>
-                        <ContentPresenter
-                            Name="PART_SelectedContentHost"
-                            Margin="{TemplateBinding Padding}"                           
-                            HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
-                            VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
-                            Content="{TemplateBinding SelectedContent}"
-                            ContentTemplate="{TemplateBinding SelectedContentTemplate}">
-                        </ContentPresenter>
+                        <ContentControl Content="{TemplateBinding Tag}" HorizontalContentAlignment="Right" DockPanel.Dock="Bottom"/>
+                        <ScrollViewer
+                            HorizontalScrollBarVisibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem.(ScrollViewer.HorizontalScrollBarVisibility)}"
+                            VerticalScrollBarVisibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem.(ScrollViewer.VerticalScrollBarVisibility)}">
+                            <ContentPresenter
+                                    Name="PART_SelectedContentHost"
+                                    Margin="{TemplateBinding Padding}"                           
+                                    HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                                    VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                                    Content="{TemplateBinding SelectedContent}"
+                                    ContentTemplate="{TemplateBinding SelectedContentTemplate}">
+                            </ContentPresenter>
+                        </ScrollViewer>
                     </DockPanel>
                 </Border>
             </ControlTemplate>
@@ -58,6 +64,8 @@
                 <DoubleTransition Property="Opacity" Duration="0:0:0.150"/>
             </Transitions>
         </Setter>
+        <Setter Property="(ScrollViewer.HorizontalScrollBarVisibility)" Value="Auto"/>
+        <Setter Property="(ScrollViewer.VerticalScrollBarVisibility)" Value="Auto"/>
     </Style>
     <Style Selector="TabControl.sidebar > TabItem:pointerover">
         <Setter Property="Opacity" Value="1"/>

+ 27 - 0
samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs

@@ -0,0 +1,27 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+using ReactiveUI;
+
+namespace ControlCatalog.ViewModels
+{
+    public class ItemsRepeaterPageViewModel : ReactiveObject
+    {
+        private int newItemIndex = 1;
+
+        public ItemsRepeaterPageViewModel()
+        {
+            Items = new ObservableCollection<string>(
+                Enumerable.Range(1, 100000).Select(i => $"Item {i}"));
+        }
+
+        public ObservableCollection<string> Items { get; }
+
+        public string SelectedItem { get; set; }
+
+        public void AddItem()
+        {
+            var index = SelectedItem != null ? Items.IndexOf(SelectedItem) : -1;
+            Items.Insert(index + 1, $"New Item {newItemIndex++}");
+        }
+    }
+}

+ 0 - 5
src/Android/Avalonia.Android/AndroidPlatform.cs

@@ -71,10 +71,5 @@ namespace Avalonia.Android
         {
             throw new NotSupportedException();
         }
-
-        public IPopupImpl CreatePopup()
-        {
-            return new PopupImpl();
-        }
     }
 }

+ 0 - 112
src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs

@@ -1,112 +0,0 @@
-using System;
-using Android.Content;
-using Android.Graphics;
-using Android.Runtime;
-using Android.Views;
-using Avalonia.Controls;
-using Avalonia.Platform;
-
-namespace Avalonia.Android.Platform.SkiaPlatform
-{
-    class PopupImpl : TopLevelImpl, IPopupImpl
-    {
-        private PixelPoint _position;
-        private bool _isAdded;
-        Action IWindowBaseImpl.Activated { get; set; }
-        public Action<PixelPoint> PositionChanged { get; set; }
-        public Action Deactivated { get; set; }
-
-        public PopupImpl() : base(ActivityTracker.Current, true)
-        {
-        }
-
-        private Size _clientSize = new Size(1, 1);
-
-        public void Resize(Size value)
-        {
-            if (View == null)
-                return;
-            _clientSize = value;
-            UpdateParams();
-        }
-
-        public void SetMinMaxSize(Size minSize, Size maxSize)
-        {
-        }
-
-        public IScreenImpl Screen { get; }
-
-        public PixelPoint Position
-        {
-            get { return _position; }
-            set
-            {
-                _position = value;
-                PositionChanged?.Invoke(_position);
-                UpdateParams();
-            }
-        }
-
-        WindowManagerLayoutParams CreateParams() => new WindowManagerLayoutParams(0,
-            WindowManagerFlags.NotTouchModal, Format.Translucent)
-        {
-            Gravity = GravityFlags.Left | GravityFlags.Top,
-            WindowAnimations = 0,
-            X = (int) _position.X,
-            Y = (int) _position.Y,
-            Width = Math.Max(1, (int) _clientSize.Width),
-            Height = Math.Max(1, (int) _clientSize.Height)
-        };
-
-        void UpdateParams()
-        {
-            if (_isAdded)
-                ActivityTracker.Current?.WindowManager?.UpdateViewLayout(View, CreateParams());
-        }
-
-        public override void Show()
-        {
-            if (_isAdded)
-                return;
-            ActivityTracker.Current.WindowManager.AddView(View, CreateParams());
-            _isAdded = true;
-        }
-
-        public override void Hide()
-        {
-            if (_isAdded)
-            {
-                var wm = View.Context.ApplicationContext.GetSystemService(Context.WindowService)
-                    .JavaCast<IWindowManager>();
-                wm.RemoveView(View);
-                _isAdded = false;
-            }
-        }
-
-        public override void Dispose()
-        {
-            Hide();
-            base.Dispose();
-        }
-
-
-        public void Activate()
-        {
-        }
-
-        public void BeginMoveDrag()
-        {
-            //Not supported
-        }
-
-        public void BeginResizeDrag(WindowEdge edge)
-        {
-            //Not supported
-        }
-
-        public void SetTopmost(bool value)
-        {
-            //Not supported
-        }
-    }
-}

+ 2 - 0
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -191,6 +191,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             }
         }
 
+        public IPopupImpl CreatePopup() => null;
+
         ILockedFramebuffer IFramebufferPlatformSurface.Lock()=>new AndroidFramebuffer(_view.Holder.Surface);
     }
 }

+ 4 - 4
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@@ -72,12 +72,12 @@ namespace Avalonia.Android.Platform.Specific.Helpers
             return false;
         }
 
-        private static InputModifiers GetModifierKeys(KeyEvent e)
+        private static RawInputModifiers GetModifierKeys(KeyEvent e)
         {
-            var rv = InputModifiers.None;
+            var rv = RawInputModifiers.None;
 
-            if (e.IsCtrlPressed) rv |= InputModifiers.Control;
-            if (e.IsShiftPressed) rv |= InputModifiers.Shift;
+            if (e.IsCtrlPressed) rv |= RawInputModifiers.Control;
+            if (e.IsShiftPressed) rv |= RawInputModifiers.Shift;
 
             return rv;
         }

+ 3 - 3
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs

@@ -78,12 +78,12 @@ namespace Avalonia.Android.Platform.Specific.Helpers
                     if (mouseEventType == RawPointerEventType.LeftButtonDown)
                     {
                         var me = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
-                                    RawPointerEventType.Move, _point, InputModifiers.None);
+                                    RawPointerEventType.Move, _point, RawInputModifiers.None);
                         _view.Input(me);
                     }
 
                     var mouseEvent = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
-                        mouseEventType.Value, _point, InputModifiers.LeftMouseButton);
+                        mouseEventType.Value, _point, RawInputModifiers.LeftMouseButton);
                     _view.Input(mouseEvent);
 
                     if (e.Action == MotionEventActions.Move && mouseDevice.Captured == null)
@@ -102,7 +102,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
                                         (uint)eventTime.Ticks,
                                         inputRoot,
                                         _point,
-                                        new Vector(vectorX * correction / ps, vectorY * correction / ps), InputModifiers.LeftMouseButton);
+                                        new Vector(vectorX * correction / ps, vectorY * correction / ps), RawInputModifiers.LeftMouseButton);
                             _view.Input(mouseWheelEvent);
                         }
                         _lastTouchMovePoint = _point;

+ 5 - 0
src/Avalonia.Base/AvaloniaProperty.cs

@@ -493,6 +493,11 @@ namespace Avalonia
             return Name;
         }
 
+        /// <summary>
+        /// True if <see cref="Initialized"/> has any observers.
+        /// </summary>
+        internal bool HasNotifyInitializedObservers => _initialized.HasObservers;
+
         /// <summary>
         /// Notifies the <see cref="Initialized"/> observable.
         /// </summary>

+ 56 - 18
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@@ -24,8 +24,8 @@ namespace Avalonia
             new Dictionary<Type, List<AvaloniaProperty>>();
         private readonly Dictionary<Type, List<AvaloniaProperty>> _attachedCache =
             new Dictionary<Type, List<AvaloniaProperty>>();
-        private readonly Dictionary<Type, List<KeyValuePair<AvaloniaProperty, object>>> _initializedCache =
-            new Dictionary<Type, List<KeyValuePair<AvaloniaProperty, object>>>();
+        private readonly Dictionary<Type, List<PropertyInitializationData>> _initializedCache =
+            new Dictionary<Type, List<PropertyInitializationData>>();
 
         /// <summary>
         /// Gets the <see cref="AvaloniaPropertyRegistry"/> instance
@@ -286,35 +286,73 @@ namespace Avalonia
                 property.NotifyInitialized(e);
             }
 
-            if (!_initializedCache.TryGetValue(type, out var items))
+            if (!_initializedCache.TryGetValue(type, out var initializationData))
             {
-                var build = new Dictionary<AvaloniaProperty, object>();
+                var visited = new HashSet<AvaloniaProperty>();
 
-                foreach (var property in GetRegistered(type))
+                initializationData = new List<PropertyInitializationData>();
+
+                foreach (AvaloniaProperty property in GetRegistered(type))
                 {
-                    var value = !property.IsDirect ?
-                        ((IStyledPropertyAccessor)property).GetDefaultValue(type) :
-                        null;
-                    build.Add(property, value);
+                    if (property.IsDirect)
+                    {
+                        initializationData.Add(new PropertyInitializationData(property, (IDirectPropertyAccessor)property));
+                    }
+                    else
+                    {
+                        initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
+                    }
+
+                    visited.Add(property);
                 }
 
-                foreach (var property in GetRegisteredAttached(type))
+                foreach (AvaloniaProperty property in GetRegisteredAttached(type))
                 {
-                    if (!build.ContainsKey(property))
+                    if (!visited.Contains(property))
                     {
-                        var value = ((IStyledPropertyAccessor)property).GetDefaultValue(type);
-                        build.Add(property, value);
+                        initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
+
+                        visited.Add(property);
                     }
                 }
 
-                items = build.ToList();
-                _initializedCache.Add(type, items);
+                _initializedCache.Add(type, initializationData);
+            }
+
+            foreach (PropertyInitializationData data in initializationData)
+            {
+                if (!data.Property.HasNotifyInitializedObservers)
+                {
+                    continue;
+                }
+
+                object value = data.IsDirect ? data.DirectAccessor.GetValue(o) : data.Value;
+
+                Notify(data.Property, value);
+            }
+        }
+
+        private readonly struct PropertyInitializationData
+        {
+            public AvaloniaProperty Property { get; }
+            public object Value { get; }
+            public bool IsDirect { get; }
+            public IDirectPropertyAccessor DirectAccessor { get; }
+
+            public PropertyInitializationData(AvaloniaProperty property, IDirectPropertyAccessor directAccessor)
+            {
+                Property = property;
+                Value = null;
+                IsDirect = true;
+                DirectAccessor = directAccessor;
             }
 
-            foreach (var i in items)
+            public PropertyInitializationData(AvaloniaProperty property, IStyledPropertyAccessor styledAccessor, Type type)
             {
-                var value = i.Key.IsDirect ? o.GetValue(i.Key) : i.Value;
-                Notify(i.Key, value);
+                Property = property;
+                Value = styledAccessor.GetDefaultValue(type);
+                IsDirect = false;
+                DirectAccessor = null;
             }
         }
     }

+ 170 - 59
src/Avalonia.Base/Collections/AvaloniaList.cs

@@ -55,15 +55,15 @@ namespace Avalonia.Collections
     /// </remarks>
     public class AvaloniaList<T> : IAvaloniaList<T>, IList, INotifyCollectionChangedDebug
     {
-        private List<T> _inner;
+        private readonly List<T> _inner;
         private NotifyCollectionChangedEventHandler _collectionChanged;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="AvaloniaList{T}"/> class.
         /// </summary>
         public AvaloniaList()
-            : this(Enumerable.Empty<T>())
         {
+            _inner = new List<T>();
         }
 
         /// <summary>
@@ -89,8 +89,8 @@ namespace Avalonia.Collections
         /// </summary>
         public event NotifyCollectionChangedEventHandler CollectionChanged
         {
-            add { _collectionChanged += value; }
-            remove { _collectionChanged -= value; }
+            add => _collectionChanged += value;
+            remove => _collectionChanged -= value;
         }
 
         /// <summary>
@@ -150,7 +150,7 @@ namespace Avalonia.Collections
 
                 T old = _inner[index];
 
-                if (!object.Equals(old, value))
+                if (!EqualityComparer<T>.Default.Equals(old, value))
                 {
                     _inner[index] = value;
 
@@ -187,45 +187,38 @@ namespace Avalonia.Collections
             Validate?.Invoke(item);
             int index = _inner.Count;
             _inner.Add(item);
-            NotifyAdd(new[] { item }, index);
+            NotifyAdd(item, index);
         }
 
         /// <summary>
         /// Adds multiple items to the collection.
         /// </summary>
         /// <param name="items">The items.</param>
-        public virtual void AddRange(IEnumerable<T> items)
-        {
-            Contract.Requires<ArgumentNullException>(items != null);
-
-            var list = (items as IList) ?? items.ToList();
-
-            if (list.Count > 0)
-            {
-                if (Validate != null)
-                {
-                    foreach (var item in list)
-                    {
-                        Validate((T)item);
-                    }
-                }
-
-                int index = _inner.Count;
-                _inner.AddRange(items);
-                NotifyAdd(list, index);
-            }
-        }
+        public virtual void AddRange(IEnumerable<T> items) => InsertRange(_inner.Count, items);
 
         /// <summary>
         /// Removes all items from the collection.
         /// </summary>
         public virtual void Clear()
         {
-            if (this.Count > 0)
+            if (Count > 0)
             {
-                var old = _inner;
-                _inner = new List<T>();
-                NotifyReset(old);
+                if (_collectionChanged != null)
+                {
+                    var e = ResetBehavior == ResetBehavior.Reset ?
+                        EventArgsCache.ResetCollectionChanged :
+                        new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, _inner.ToList(), 0);
+
+                    _inner.Clear();
+
+                    _collectionChanged(this, e);
+                }
+                else
+                {
+                    _inner.Clear();
+                }
+
+                NotifyCountChanged();
             }
         }
 
@@ -253,9 +246,20 @@ namespace Avalonia.Collections
         /// Returns an enumerator that enumerates the items in the collection.
         /// </summary>
         /// <returns>An <see cref="IEnumerator{T}"/>.</returns>
-        public IEnumerator<T> GetEnumerator()
+        IEnumerator<T> IEnumerable<T>.GetEnumerator()
         {
-            return _inner.GetEnumerator();
+            return new Enumerator(_inner);
+        }
+
+        /// <inheritdoc/>
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return new Enumerator(_inner);
+        }
+
+        public Enumerator GetEnumerator()
+        {
+            return new Enumerator(_inner);
         }
 
         /// <summary>
@@ -289,7 +293,7 @@ namespace Avalonia.Collections
         {
             Validate?.Invoke(item);
             _inner.Insert(index, item);
-            NotifyAdd(new[] { item }, index);
+            NotifyAdd(item, index);
         }
 
         /// <summary>
@@ -301,20 +305,83 @@ namespace Avalonia.Collections
         {
             Contract.Requires<ArgumentNullException>(items != null);
 
-            var list = (items as IList) ?? items.ToList();
+            bool willRaiseCollectionChanged = _collectionChanged != null;
+            bool hasValidation = Validate != null;
 
-            if (list.Count > 0)
+            if (items is IList list)
             {
-                if (Validate != null)
+                if (list.Count > 0)
                 {
-                    foreach (var item in list)
+                    if (list is ICollection<T> collection)
                     {
-                        Validate((T)item);
+                        if (hasValidation)
+                        {
+                            foreach (T item in collection)
+                            {
+                                Validate(item);
+                            }
+                        }
+
+                        _inner.InsertRange(index, collection);
+                        NotifyAdd(list, index);
+                    }
+                    else
+                    {
+                        using (IEnumerator<T> en = items.GetEnumerator())
+                        {
+                            int insertIndex = index;
+
+                            while (en.MoveNext())
+                            {
+                                T item = en.Current;
+
+                                if (hasValidation)
+                                {
+                                    Validate(item);
+                                }
+
+                                _inner.Insert(insertIndex++, item);
+                            }
+                        }
+
+                        NotifyAdd(list, index);
                     }
                 }
+            }
+            else
+            {
+                using (IEnumerator<T> en = items.GetEnumerator())
+                {
+                    if (en.MoveNext())
+                    {
+                        // Avoid allocating list for collection notification if there is no event subscriptions.
+                        List<T> notificationItems = willRaiseCollectionChanged ?
+                            new List<T>() :
+                            null;
+
+                        int insertIndex = index;
+
+                        do
+                        {
+                            T item = en.Current;
+
+                            if (hasValidation)
+                            {
+                                Validate(item);
+                            }
 
-                _inner.InsertRange(index, items);
-                NotifyAdd((items as IList) ?? items.ToList(), index);
+                            _inner.Insert(insertIndex++, item);
+
+                            if (willRaiseCollectionChanged)
+                            {
+                                notificationItems.Add(item);
+                            }
+
+                        } while (en.MoveNext());
+
+                        NotifyAdd(notificationItems, index);
+                    }
+                }
             }
         }
 
@@ -382,7 +449,7 @@ namespace Avalonia.Collections
             if (index != -1)
             {
                 _inner.RemoveAt(index);
-                NotifyRemove(new[] { item }, index);
+                NotifyRemove(item , index);
                 return true;
             }
 
@@ -412,7 +479,7 @@ namespace Avalonia.Collections
         {
             T item = _inner[index];
             _inner.RemoveAt(index);
-            NotifyRemove(new[] { item }, index);
+            NotifyRemove(item , index);
         }
 
         /// <summary>
@@ -480,12 +547,6 @@ namespace Avalonia.Collections
             _inner.CopyTo((T[])array, index);
         }
 
-        /// <inheritdoc/>
-        IEnumerator IEnumerable.GetEnumerator()
-        {
-            return _inner.GetEnumerator();
-        }
-
         /// <inheritdoc/>
         Delegate[] INotifyCollectionChangedDebug.GetCollectionChangedSubscribers() => _collectionChanged?.GetInvocationList();
 
@@ -505,13 +566,29 @@ namespace Avalonia.Collections
             NotifyCountChanged();
         }
 
+        /// <summary>
+        /// Raises the <see cref="CollectionChanged"/> event with a add action.
+        /// </summary>
+        /// <param name="item">The item that was added.</param>
+        /// <param name="index">The starting index.</param>
+        private void NotifyAdd(T item, int index)
+        {
+            if (_collectionChanged != null)
+            {
+                var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new[] { item }, index);
+                _collectionChanged(this, e);
+            }
+
+            NotifyCountChanged();
+        }
+
         /// <summary>
         /// Raises the <see cref="PropertyChanged"/> event when the <see cref="Count"/> property
         /// changes.
         /// </summary>
         private void NotifyCountChanged()
         {
-            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
+            PropertyChanged?.Invoke(this, EventArgsCache.CountPropertyChanged);
         }
 
         /// <summary>
@@ -531,23 +608,57 @@ namespace Avalonia.Collections
         }
 
         /// <summary>
-        /// Raises the <see cref="CollectionChanged"/> event with a reset action.
+        /// Raises the <see cref="CollectionChanged"/> event with a remove action.
         /// </summary>
-        /// <param name="t">The items that were removed.</param>
-        private void NotifyReset(IList t)
+        /// <param name="item">The item that was removed.</param>
+        /// <param name="index">The starting index.</param>
+        private void NotifyRemove(T item, int index)
         {
             if (_collectionChanged != null)
             {
-                NotifyCollectionChangedEventArgs e;
-
-                e = ResetBehavior == ResetBehavior.Reset ? 
-                    new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset) : 
-                    new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, t, 0);
-
+                var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { item }, index);
                 _collectionChanged(this, e);
             }
 
             NotifyCountChanged();
         }
+
+        /// <summary>
+        /// Enumerates the elements of a <see cref="AvaloniaList{T}"/>.
+        /// </summary>
+        public struct Enumerator : IEnumerator<T>
+        {
+            private List<T>.Enumerator _innerEnumerator;
+
+            public Enumerator(List<T> inner)
+            {
+                _innerEnumerator = inner.GetEnumerator();
+            }
+
+            public bool MoveNext()
+            {
+                return _innerEnumerator.MoveNext();
+            }
+
+            void IEnumerator.Reset()
+            {
+                ((IEnumerator)_innerEnumerator).Reset();
+            }
+
+            public T Current => _innerEnumerator.Current;
+
+            object IEnumerator.Current => Current;
+
+            public void Dispose()
+            {
+                _innerEnumerator.Dispose();
+            }
+        }
+    }
+
+    internal static class EventArgsCache
+    {
+        internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs(nameof(AvaloniaList<object>.Count));
+        internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
     }
 }

+ 3 - 3
src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs

@@ -24,7 +24,7 @@ namespace Avalonia.Data.Core
         {
             try
             {
-                if (Target.IsAlive && Target.Target is IAvaloniaObject obj)
+                if (Target.TryGetTarget(out object target) && target is IAvaloniaObject obj)
                 {
                     obj.SetValue(_property, value, priority);
                     return true;
@@ -37,9 +37,9 @@ namespace Avalonia.Data.Core
             }
         }
 
-        protected override void StartListeningCore(WeakReference reference)
+        protected override void StartListeningCore(WeakReference<object> reference)
         {
-            if (reference.Target is IAvaloniaObject obj)
+            if (reference.TryGetTarget(out object target) && target is IAvaloniaObject obj)
             {
                 _subscription = new AvaloniaPropertyObservable<object>(obj, _property).Subscribe(ValueChanged);
             }

+ 14 - 12
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@@ -8,27 +8,27 @@ namespace Avalonia.Data.Core
     public abstract class ExpressionNode
     {
         private static readonly object CacheInvalid = new object();
-        protected static readonly WeakReference UnsetReference = 
-            new WeakReference(AvaloniaProperty.UnsetValue);
+        protected static readonly WeakReference<object> UnsetReference = 
+            new WeakReference<object>(AvaloniaProperty.UnsetValue);
 
-        private WeakReference _target = UnsetReference;
+        private WeakReference<object> _target = UnsetReference;
         private Action<object> _subscriber;
         private bool _listening;
 
-        protected WeakReference LastValue { get; private set; }
+        protected WeakReference<object> LastValue { get; private set; }
 
         public abstract string Description { get; }
         public ExpressionNode Next { get; set; }
 
-        public WeakReference Target
+        public WeakReference<object> Target
         {
             get { return _target; }
             set
             {
                 Contract.Requires<ArgumentNullException>(value != null);
 
-                var oldTarget = _target?.Target;
-                var newTarget = value.Target;
+                _target.TryGetTarget(out var oldTarget);
+                value.TryGetTarget(out object newTarget);
 
                 if (!ReferenceEquals(oldTarget, newTarget))
                 {
@@ -72,9 +72,11 @@ namespace Avalonia.Data.Core
             _subscriber = null;
         }
 
-        protected virtual void StartListeningCore(WeakReference reference)
+        protected virtual void StartListeningCore(WeakReference<object> reference)
         {
-            ValueChanged(reference.Target);
+            reference.TryGetTarget(out object target);
+
+            ValueChanged(target);
         }
 
         protected virtual void StopListeningCore()
@@ -96,7 +98,7 @@ namespace Avalonia.Data.Core
 
             if (notification == null)
             {
-                LastValue = new WeakReference(value);
+                LastValue = new WeakReference<object>(value);
 
                 if (Next != null)
                 {
@@ -109,7 +111,7 @@ namespace Avalonia.Data.Core
             }
             else
             {
-                LastValue = new WeakReference(notification.Value);
+                LastValue = new WeakReference<object>(notification.Value);
 
                 if (Next != null)
                 {
@@ -125,7 +127,7 @@ namespace Avalonia.Data.Core
 
         private void StartListening()
         {
-            var target = _target.Target;
+            _target.TryGetTarget(out object target);
 
             if (target == null)
             {

+ 4 - 4
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@@ -79,7 +79,7 @@ namespace Avalonia.Data.Core
 
             _node = node;
             Description = description;
-            _root = new WeakReference(root);
+            _root = new WeakReference<object>(root);
         }
 
         /// <summary>
@@ -121,7 +121,7 @@ namespace Avalonia.Data.Core
             Contract.Requires<ArgumentNullException>(update != null);
             Description = description;
             _node = node;
-            _node.Target = new WeakReference(rootGetter());
+            _node.Target = new WeakReference<object>(rootGetter());
             _root = update.Select(x => rootGetter());
         }
 
@@ -299,13 +299,13 @@ namespace Avalonia.Data.Core
             if (_root is IObservable<object> observable)
             {
                 _rootSubscription = observable.Subscribe(
-                    x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null),
+                    x => _node.Target = new WeakReference<object>(x != AvaloniaProperty.UnsetValue ? x : null),
                     x => PublishCompleted(),
                     () => PublishCompleted());
             }
             else
             {
-                _node.Target = (WeakReference)_root;
+                _node.Target = (WeakReference<object>)_root;
             }
         }
 

+ 9 - 2
src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs

@@ -36,7 +36,9 @@ namespace Avalonia.Data.Core
         {
             try
             {
-                _setDelegate.DynamicInvoke(Target.Target, value);
+                Target.TryGetTarget(out object target);
+
+                _setDelegate.DynamicInvoke(target, value);
                 return true;
             }
             catch (Exception)
@@ -64,6 +66,11 @@ namespace Avalonia.Data.Core
             return _expression.Indexer == null || _expression.Indexer.Name == e.PropertyName;
         }
 
-        protected override int? TryGetFirstArgumentAsInt() => _firstArgumentDelegate.DynamicInvoke(Target.Target) as int?;
+        protected override int? TryGetFirstArgumentAsInt()
+        {
+            Target.TryGetTarget(out object target);
+
+            return _firstArgumentDelegate.DynamicInvoke(target) as int?;
+        } 
     }
 }

+ 3 - 2
src/Avalonia.Base/Data/Core/IndexerNodeBase.cs

@@ -13,9 +13,10 @@ namespace Avalonia.Data.Core
     {
         private IDisposable _subscription;
         
-        protected override void StartListeningCore(WeakReference reference)
+        protected override void StartListeningCore(WeakReference<object> reference)
         {
-            var target = reference.Target;
+            reference.TryGetTarget(out object target);
+
             var incc = target as INotifyCollectionChanged;
             var inpc = target as INotifyPropertyChanged;
             var inputs = new List<IObservable<object>>();

+ 2 - 2
src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs

@@ -31,12 +31,12 @@ namespace Avalonia.Data.Core.Plugins
         /// An <see cref="IPropertyAccessor"/> interface through which future interactions with the 
         /// property will be made.
         /// </returns>
-        public IPropertyAccessor Start(WeakReference reference, string propertyName)
+        public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
         {
             Contract.Requires<ArgumentNullException>(reference != null);
             Contract.Requires<ArgumentNullException>(propertyName != null);
 
-            var instance = reference.Target;
+            reference.TryGetTarget(out object instance);
             var o = (AvaloniaObject)instance;
             var p = LookupProperty(o, propertyName);
 

+ 11 - 12
src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs

@@ -15,9 +15,11 @@ namespace Avalonia.Data.Core.Plugins
     public class DataAnnotationsValidationPlugin : IDataValidationPlugin
     {
         /// <inheritdoc/>
-        public bool Match(WeakReference reference, string memberName)
+        public bool Match(WeakReference<object> reference, string memberName)
         {
-            return reference.Target?
+            reference.TryGetTarget(out object target);
+
+            return target?
                 .GetType()
                 .GetRuntimeProperty(memberName)?
                 .GetCustomAttributes<ValidationAttribute>()
@@ -25,25 +27,22 @@ namespace Avalonia.Data.Core.Plugins
         }
 
         /// <inheritdoc/>
-        public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor inner)
+        public IPropertyAccessor Start(WeakReference<object> reference, string name, IPropertyAccessor inner)
         {
             return new Accessor(reference, name, inner);
         }
 
-        private class Accessor : DataValidationBase
+        private sealed class Accessor : DataValidationBase
         {
-            private ValidationContext _context;
+            private readonly ValidationContext _context;
 
-            public Accessor(WeakReference reference, string name, IPropertyAccessor inner)
+            public Accessor(WeakReference<object> reference, string name, IPropertyAccessor inner)
                 : base(inner)
             {
-                _context = new ValidationContext(reference.Target);
-                _context.MemberName = name;
-            }
+                reference.TryGetTarget(out object target);
 
-            public override bool SetValue(object value, BindingPriority priority)
-            {
-                return base.SetValue(value, priority);
+                _context = new ValidationContext(target);
+                _context.MemberName = name;
             }
 
             protected override void InnerValueChanged(object value)

+ 4 - 4
src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs

@@ -12,17 +12,17 @@ namespace Avalonia.Data.Core.Plugins
     public class ExceptionValidationPlugin : IDataValidationPlugin
     {
         /// <inheritdoc/>
-        public bool Match(WeakReference reference, string memberName) => true;
+        public bool Match(WeakReference<object> reference, string memberName) => true;
 
         /// <inheritdoc/>
-        public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor inner)
+        public IPropertyAccessor Start(WeakReference<object> reference, string name, IPropertyAccessor inner)
         {
             return new Validator(reference, name, inner);
         }
 
-        private class Validator : DataValidationBase
+        private sealed class Validator : DataValidationBase
         {
-            public Validator(WeakReference reference, string name, IPropertyAccessor inner)
+            public Validator(WeakReference<object> reference, string name, IPropertyAccessor inner)
                 : base(inner)
             {
             }

+ 2 - 3
src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Data.Core.Plugins
         /// <param name="reference">A weak reference to the object.</param>
         /// <param name="memberName">The name of the member to validate.</param>
         /// <returns>True if the plugin can handle the object; otherwise false.</returns>
-        bool Match(WeakReference reference, string memberName);
+        bool Match(WeakReference<object> reference, string memberName);
 
         /// <summary>
         /// Starts monitoring the data validation state of a property on an object.
@@ -28,8 +28,7 @@ namespace Avalonia.Data.Core.Plugins
         /// An <see cref="IPropertyAccessor"/> interface through which future interactions with the 
         /// property will be made.
         /// </returns>
-        IPropertyAccessor Start(
-            WeakReference reference,
+        IPropertyAccessor Start(WeakReference<object> reference,
             string propertyName,
             IPropertyAccessor inner);
     }

+ 1 - 2
src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs

@@ -28,8 +28,7 @@ namespace Avalonia.Data.Core.Plugins
         /// An <see cref="IPropertyAccessor"/> interface through which future interactions with the 
         /// property will be made.
         /// </returns>
-        IPropertyAccessor Start(
-            WeakReference reference, 
+        IPropertyAccessor Start(WeakReference<object> reference,
             string propertyName);
     }
 }

+ 2 - 2
src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Data.Core.Plugins
         /// </summary>
         /// <param name="reference">A weak reference to the value.</param>
         /// <returns>True if the plugin can handle the value; otherwise false.</returns>
-        bool Match(WeakReference reference);
+        bool Match(WeakReference<object> reference);
 
         /// <summary>
         /// Starts producing output based on the specified value.
@@ -24,6 +24,6 @@ namespace Avalonia.Data.Core.Plugins
         /// <returns>
         /// An observable that produces the output for the value.
         /// </returns>
-        IObservable<object> Start(WeakReference reference);
+        IObservable<object> Start(WeakReference<object> reference);
     }
 }

+ 26 - 13
src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@@ -15,20 +15,25 @@ namespace Avalonia.Data.Core.Plugins
     public class IndeiValidationPlugin : IDataValidationPlugin
     {
         /// <inheritdoc/>
-        public bool Match(WeakReference reference, string memberName) => reference.Target is INotifyDataErrorInfo;
+        public bool Match(WeakReference<object> reference, string memberName)
+        {
+            reference.TryGetTarget(out object target);
+
+            return target is INotifyDataErrorInfo;
+        }
 
         /// <inheritdoc/>
-        public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor accessor)
+        public IPropertyAccessor Start(WeakReference<object> reference, string name, IPropertyAccessor accessor)
         {
             return new Validator(reference, name, accessor);
         }
 
         private class Validator : DataValidationBase, IWeakSubscriber<DataErrorsChangedEventArgs>
         {
-            WeakReference _reference;
-            string _name;
+            private readonly WeakReference<object> _reference;
+            private readonly string _name;
 
-            public Validator(WeakReference reference, string name, IPropertyAccessor inner)
+            public Validator(WeakReference<object> reference, string name, IPropertyAccessor inner)
                 : base(inner)
             {
                 _reference = reference;
@@ -45,7 +50,7 @@ namespace Avalonia.Data.Core.Plugins
 
             protected override void SubscribeCore()
             {
-                var target = _reference.Target as INotifyDataErrorInfo;
+                var target = GetReferenceTarget() as INotifyDataErrorInfo;
 
                 if (target != null)
                 {
@@ -60,7 +65,7 @@ namespace Avalonia.Data.Core.Plugins
 
             protected override void UnsubscribeCore()
             {
-                var target = _reference.Target as INotifyDataErrorInfo;
+                var target = GetReferenceTarget() as INotifyDataErrorInfo;
 
                 if (target != null)
                 {
@@ -80,13 +85,14 @@ namespace Avalonia.Data.Core.Plugins
 
             private BindingNotification CreateBindingNotification(object value)
             {
-                var target = (INotifyDataErrorInfo)_reference.Target;
+                var target = (INotifyDataErrorInfo)GetReferenceTarget();
 
                 if (target != null)
                 {
                     var errors = target.GetErrors(_name)?
-                        .Cast<String>()
-                        .Where(x => x != null).ToList();
+                        .Cast<object>()
+                        .Where(x => x != null)
+                        .ToList();
 
                     if (errors?.Count > 0)
                     {
@@ -100,16 +106,23 @@ namespace Avalonia.Data.Core.Plugins
                 return new BindingNotification(value);
             }
 
-            private Exception GenerateException(IList<string> errors)
+            private object GetReferenceTarget()
+            {
+                _reference.TryGetTarget(out object target);
+
+                return target;
+            }
+
+            private Exception GenerateException(IList<object> errors)
             {
                 if (errors.Count == 1)
                 {
-                    return new Exception(errors[0]);
+                    return new DataValidationException(errors[0]);
                 }
                 else
                 {
                     return new AggregateException(
-                        errors.Select(x => new Exception(x)));
+                        errors.Select(x => new DataValidationException(x)));
                 }
             }
         }

+ 15 - 8
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@@ -28,12 +28,12 @@ namespace Avalonia.Data.Core.Plugins
         /// An <see cref="IPropertyAccessor"/> interface through which future interactions with the 
         /// property will be made.
         /// </returns>
-        public IPropertyAccessor Start(WeakReference reference, string propertyName)
+        public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
         {
             Contract.Requires<ArgumentNullException>(reference != null);
             Contract.Requires<ArgumentNullException>(propertyName != null);
 
-            var instance = reference.Target;
+            reference.TryGetTarget(out object instance);
             var p = instance.GetType().GetRuntimeProperties().FirstOrDefault(x => x.Name == propertyName);
 
             if (p != null)
@@ -50,11 +50,11 @@ namespace Avalonia.Data.Core.Plugins
 
         private class Accessor : PropertyAccessorBase, IWeakSubscriber<PropertyChangedEventArgs>
         {
-            private readonly WeakReference _reference;
+            private readonly WeakReference<object> _reference;
             private readonly PropertyInfo _property;
             private bool _eventRaised;
 
-            public Accessor(WeakReference reference,  PropertyInfo property)
+            public Accessor(WeakReference<object> reference,  PropertyInfo property)
             {
                 Contract.Requires<ArgumentNullException>(reference != null);
                 Contract.Requires<ArgumentNullException>(property != null);
@@ -69,7 +69,7 @@ namespace Avalonia.Data.Core.Plugins
             {
                 get
                 {
-                    var o = _reference.Target;
+                    var o = GetReferenceTarget();
                     return (o != null) ? _property.GetValue(o) : null;
                 }
             }
@@ -79,7 +79,7 @@ namespace Avalonia.Data.Core.Plugins
                 if (_property.CanWrite)
                 {
                     _eventRaised = false;
-                    _property.SetValue(_reference.Target, value);
+                    _property.SetValue(GetReferenceTarget(), value);
 
                     if (!_eventRaised)
                     {
@@ -109,7 +109,7 @@ namespace Avalonia.Data.Core.Plugins
 
             protected override void UnsubscribeCore()
             {
-                var inpc = _reference.Target as INotifyPropertyChanged;
+                var inpc = GetReferenceTarget() as INotifyPropertyChanged;
 
                 if (inpc != null)
                 {
@@ -120,6 +120,13 @@ namespace Avalonia.Data.Core.Plugins
                 }
             }
 
+            private object GetReferenceTarget()
+            {
+                _reference.TryGetTarget(out object target);
+
+                return target;
+            }
+
             private void SendCurrentValue()
             {
                 try
@@ -132,7 +139,7 @@ namespace Avalonia.Data.Core.Plugins
 
             private void SubscribeToChanges()
             {
-                var inpc = _reference.Target as INotifyPropertyChanged;
+                var inpc = GetReferenceTarget() as INotifyPropertyChanged;
 
                 if (inpc != null)
                 {

+ 15 - 6
src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@@ -9,12 +9,12 @@ namespace Avalonia.Data.Core.Plugins
         public bool Match(object obj, string methodName)
             => obj.GetType().GetRuntimeMethods().Any(x => x.Name == methodName);
 
-        public IPropertyAccessor Start(WeakReference reference, string methodName)
+        public IPropertyAccessor Start(WeakReference<object> reference, string methodName)
         {
             Contract.Requires<ArgumentNullException>(reference != null);
             Contract.Requires<ArgumentNullException>(methodName != null);
 
-            var instance = reference.Target;
+            reference.TryGetTarget(out object instance);
             var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == methodName);
 
             if (method != null)
@@ -35,9 +35,9 @@ namespace Avalonia.Data.Core.Plugins
             }
         }
 
-        private class Accessor : PropertyAccessorBase
+        private sealed class Accessor : PropertyAccessorBase
         {
-            public Accessor(WeakReference reference, MethodInfo method)
+            public Accessor(WeakReference<object> reference, MethodInfo method)
             {
                 Contract.Requires<ArgumentNullException>(reference != null);
                 Contract.Requires<ArgumentNullException>(method != null);
@@ -61,8 +61,17 @@ namespace Avalonia.Data.Core.Plugins
                     var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray();
                     PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters);
                 }
-                
-                Value = method.IsStatic ? method.CreateDelegate(PropertyType) : method.CreateDelegate(PropertyType, reference.Target);
+
+                if (method.IsStatic)
+                {
+                    Value = method.CreateDelegate(PropertyType);
+                }
+                else
+                {
+                    reference.TryGetTarget(out object target);
+
+                    Value = method.CreateDelegate(PropertyType, target);
+                }
             }
 
             public override Type PropertyType { get; }

+ 7 - 5
src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs

@@ -20,9 +20,11 @@ namespace Avalonia.Data.Core.Plugins
         /// </summary>
         /// <param name="reference">A weak reference to the value.</param>
         /// <returns>True if the plugin can handle the value; otherwise false.</returns>
-        public virtual bool Match(WeakReference reference)
+        public virtual bool Match(WeakReference<object> reference)
         {
-            return reference.Target.GetType().GetInterfaces().Any(x =>
+            reference.TryGetTarget(out object target);
+
+            return target != null && target.GetType().GetInterfaces().Any(x =>
               x.IsGenericType &&
               x.GetGenericTypeDefinition() == typeof(IObservable<>));
         }
@@ -34,9 +36,9 @@ namespace Avalonia.Data.Core.Plugins
         /// <returns>
         /// An observable that produces the output for the value.
         /// </returns>
-        public virtual IObservable<object> Start(WeakReference reference)
+        public virtual IObservable<object> Start(WeakReference<object> reference)
         {
-            var target = reference.Target;
+            reference.TryGetTarget(out object target);
 
             // If the observable returns a reference type then we can cast it.
             if (target is IObservable<object> result)
@@ -46,7 +48,7 @@ namespace Avalonia.Data.Core.Plugins
 
             // If the observable returns a value type then we need to call Observable.Select on it.
             // First get the type of T in `IObservable<T>`.
-            var sourceType = reference.Target.GetType().GetInterfaces().First(x =>
+            var sourceType = target.GetType().GetInterfaces().First(x =>
                   x.IsGenericType &&
                   x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0];
 

+ 9 - 4
src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs

@@ -19,7 +19,12 @@ namespace Avalonia.Data.Core.Plugins
         /// </summary>
         /// <param name="reference">A weak reference to the value.</param>
         /// <returns>True if the plugin can handle the value; otherwise false.</returns>
-        public virtual bool Match(WeakReference reference) => reference.Target is Task;
+        public virtual bool Match(WeakReference<object> reference)
+        {
+            reference.TryGetTarget(out object target);
+
+            return target is Task;
+        } 
 
         /// <summary>
         /// Starts producing output based on the specified value.
@@ -28,11 +33,11 @@ namespace Avalonia.Data.Core.Plugins
         /// <returns>
         /// An observable that produces the output for the value.
         /// </returns>
-        public virtual IObservable<object> Start(WeakReference reference)
+        public virtual IObservable<object> Start(WeakReference<object> reference)
         {
-            var task = reference.Target as Task;
+            reference.TryGetTarget(out object target);
 
-            if (task != null)
+            if (target is Task task)
             {
                 var resultProperty = task.GetType().GetRuntimeProperty("Result");
 

+ 4 - 2
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@@ -45,9 +45,11 @@ namespace Avalonia.Data.Core
             return false;
         }
 
-        protected override void StartListeningCore(WeakReference reference)
+        protected override void StartListeningCore(WeakReference<object> reference)
         {
-            var plugin = _customPlugin ?? GetPropertyAccessorPluginForObject(reference.Target);
+            reference.TryGetTarget(out object target);
+
+            var plugin = _customPlugin ?? GetPropertyAccessorPluginForObject(target);
             var accessor = plugin?.Start(reference, PropertyName);
 
             if (_enableValidation && Next == null)

+ 16 - 2
src/Avalonia.Base/Data/Core/SettableNode.cs

@@ -19,11 +19,25 @@ namespace Avalonia.Data.Core
             {
                 return false;
             }
+
+            if (LastValue == null)
+            {
+                return false;
+            }
+
+            bool isLastValueAlive = LastValue.TryGetTarget(out object lastValue);
+
+            if (!isLastValueAlive)
+            {
+                return false;
+            }
+
             if (PropertyType.IsValueType)
             {
-                return LastValue?.Target != null && LastValue.Target.Equals(value);
+                return lastValue.Equals(value);
             }
-            return LastValue != null && Object.ReferenceEquals(LastValue?.Target, value);
+
+            return ReferenceEquals(lastValue, value);
         }
 
         protected abstract bool SetTargetValueCore(object value, BindingPriority priority);

+ 2 - 2
src/Avalonia.Base/Data/Core/StreamNode.cs

@@ -21,7 +21,7 @@ namespace Avalonia.Data.Core
             _customPlugin = customPlugin;
         }
 
-        protected override void StartListeningCore(WeakReference reference)
+        protected override void StartListeningCore(WeakReference<object> reference)
         {
             GetPlugin(reference)?.Start(reference).Subscribe(ValueChanged);
         }
@@ -32,7 +32,7 @@ namespace Avalonia.Data.Core
             _subscription = null;
         }
 
-        private IStreamPlugin GetPlugin(WeakReference reference)
+        private IStreamPlugin GetPlugin(WeakReference<object> reference)
         {
             if (_customPlugin != null)
             {

+ 21 - 0
src/Avalonia.Base/Data/DataValidationException.cs

@@ -0,0 +1,21 @@
+using System;
+
+namespace Avalonia.Data
+{
+    /// <summary>
+    /// Exception, which wrap validation errors.
+    /// </summary>
+    public class DataValidationException : Exception
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DataValidationException"/> class.
+        /// </summary>
+        /// <param name="errorData">Data of validation error.</param>
+        public DataValidationException(object errorData) : base(errorData?.ToString())
+        {
+            ErrorData = errorData;
+        }
+
+        public object ErrorData { get; }
+    }
+}

+ 11 - 0
src/Avalonia.Controls/AppBuilderBase.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Collections.Generic;
 using System.Reflection;
 using System.Linq;
 using Avalonia.Controls.ApplicationLifetimes;
@@ -59,6 +60,8 @@ namespace Avalonia.Controls
         public Action<TAppBuilder> AfterSetupCallback { get; private set; } = builder => { };
 
 
+        public Action<TAppBuilder> AfterPlatformServicesSetupCallback { get; private set; } = builder => { };
+        
         protected AppBuilderBase(IRuntimePlatform platform, Action<TAppBuilder> platformServices)
         {
             RuntimePlatform = platform;
@@ -97,6 +100,13 @@ namespace Avalonia.Controls
             AfterSetupCallback = (Action<TAppBuilder>)Delegate.Combine(AfterSetupCallback, callback);
             return Self;
         }
+        
+        
+        public TAppBuilder AfterPlatformServicesSetup(Action<TAppBuilder> callback)
+        {
+            AfterPlatformServicesSetupCallback = (Action<TAppBuilder>)Delegate.Combine(AfterPlatformServicesSetupCallback, callback);
+            return Self;
+        }
 
         /// <summary>
         /// Starts the application with an instance of <typeparamref name="TMainWindow"/>.
@@ -274,6 +284,7 @@ namespace Avalonia.Controls
             RuntimePlatformServicesInitializer();
             WindowingSubsystemInitializer();
             RenderingSubsystemInitializer();
+            AfterPlatformServicesSetupCallback(Self);
             Instance.RegisterServices();
             Instance.Initialize();
             AfterSetupCallback(Self);

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

@@ -202,7 +202,7 @@ namespace Avalonia.Controls
         {
             if (!e.Handled)
             {
-                if (_popup?.PopupRoot != null && ((IVisual)e.Source).GetVisualRoot() == _popup?.PopupRoot)
+                if (_popup?.IsInsidePopup((IVisual)e.Source) == true)
                 {
                     if (UpdateSelectionFromEventSource(e.Source))
                     {

+ 2 - 0
src/Avalonia.Controls/ContextMenu.cs

@@ -91,6 +91,8 @@ namespace Avalonia.Controls
         /// <param name="control">The control.</param>
         public void Open(Control control)
         {
+            if (control == null)
+                throw new ArgumentNullException(nameof(control));
             if (IsOpen)
             {
                 return;

+ 9 - 6
src/Avalonia.Controls/ControlExtensions.cs

@@ -34,14 +34,17 @@ namespace Avalonia.Controls
         {
             Contract.Requires<ArgumentNullException>(control != null);
 
-            var ev = new RequestBringIntoViewEventArgs
+            if (control.IsEffectivelyVisible)
             {
-                RoutedEvent = Control.RequestBringIntoViewEvent,
-                TargetObject = control,
-                TargetRect = rect,
-            };
+                var ev = new RequestBringIntoViewEventArgs
+                {
+                    RoutedEvent = Control.RequestBringIntoViewEvent,
+                    TargetObject = control,
+                    TargetRect = rect,
+                };
 
-            control.RaiseEvent(ev);
+                control.RaiseEvent(ev);
+            }
         }
 
         /// <summary>

+ 16 - 8
src/Avalonia.Controls/DataValidationErrors.cs

@@ -22,8 +22,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the DataValidationErrors.Errors attached property.
         /// </summary>
-        public static readonly AttachedProperty<IEnumerable<Exception>> ErrorsProperty =
-            AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, IEnumerable<Exception>>("Errors");
+        public static readonly AttachedProperty<IEnumerable<object>> ErrorsProperty =
+            AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, IEnumerable<object>>("Errors");
 
         /// <summary>
         /// Defines the DataValidationErrors.HasErrors attached property.
@@ -76,7 +76,7 @@ namespace Avalonia.Controls
         private static void ErrorsChanged(AvaloniaPropertyChangedEventArgs e)
         {
             var control = (Control)e.Sender;
-            var errors = (IEnumerable<Exception>)e.NewValue;
+            var errors = (IEnumerable<object>)e.NewValue;
 
             var hasErrors = false;
             if (errors != null && errors.Any())
@@ -91,11 +91,11 @@ namespace Avalonia.Controls
             classes.Set(":error", (bool)e.NewValue);
         }
 
-        public static IEnumerable<Exception> GetErrors(Control control)
+        public static IEnumerable<object> GetErrors(Control control)
         {
             return control.GetValue(ErrorsProperty);
         }
-        public static void SetErrors(Control control, IEnumerable<Exception> errors)
+        public static void SetErrors(Control control, IEnumerable<object> errors)
         {
             control.SetValue(ErrorsProperty, errors);
         }
@@ -112,14 +112,14 @@ namespace Avalonia.Controls
             return control.GetValue(HasErrorsProperty);
         }
 
-        private static IEnumerable<Exception> UnpackException(Exception exception)
+        private static IEnumerable<object> UnpackException(Exception exception)
         {
             if (exception != null)
             {
                 var aggregate = exception as AggregateException;
                 var exceptions = aggregate == null ?
-                    (IEnumerable<Exception>)new[] { exception } :
-                    aggregate.InnerExceptions;
+                    new[] { GetExceptionData(exception) } :
+                    aggregate.InnerExceptions.Select(GetExceptionData).ToArray();
                 var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList();
 
                 if (filtered.Count > 0)
@@ -130,5 +130,13 @@ namespace Avalonia.Controls
 
             return null;
         }
+
+        private static object GetExceptionData(Exception exception)
+        {
+            if (exception is DataValidationException dataValidationException)
+                return dataValidationException.ErrorData;
+
+            return exception;
+        }
     }
 }

+ 1 - 0
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@@ -61,5 +61,6 @@ namespace Avalonia.Controls.Embedding.Offscreen
 
         public Action Closed { get; set; }
         public abstract IMouseDevice MouseDevice { get; }
+        public IPopupImpl CreatePopup() => null;
     }
 }

+ 6 - 0
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@@ -128,6 +128,12 @@ namespace Avalonia.Controls.Generators
                 }
 
                 Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
+
+                if (toMove.Count > 0)
+                {
+                    var containers = toMove.Select(x => x.Value).ToList();
+                    Recycled?.Invoke(this, new ItemContainerEventArgs(containers[0].Index, containers));
+                }
             }
 
             return result;

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

@@ -224,7 +224,7 @@ namespace Avalonia.Controls
         public bool IsTopLevel => Parent is Menu;
 
         /// <inheritdoc/>
-        bool IMenuItem.IsPointerOverSubMenu => _popup.PopupRoot?.IsPointerOver ?? false;
+        bool IMenuItem.IsPointerOverSubMenu => _popup?.IsPointerOverPopup ?? false; 
 
         /// <inheritdoc/>
         IMenuElement IMenuItem.Parent => Parent as IMenuElement;
@@ -339,7 +339,7 @@ namespace Avalonia.Controls
 
             var point = e.GetPointerPoint(null);
             RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
-                e.Timestamp, point.Properties, e.InputModifiers));
+                e.Timestamp, point.Properties, e.KeyModifiers));
         }
 
         /// <inheritdoc/>
@@ -349,7 +349,7 @@ namespace Avalonia.Controls
 
             var point = e.GetPointerPoint(null);
             RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
-                e.Timestamp, point.Properties, e.InputModifiers));
+                e.Timestamp, point.Properties, e.KeyModifiers));
         }
 
         /// <summary>

+ 1 - 0
src/Avalonia.Controls/Mixins/ContentControlMixin.cs

@@ -150,6 +150,7 @@ namespace Avalonia.Controls.Mixins
                 if (oldValue is IControl child)
                 {
                     logicalChildren.Remove(child);
+                    ((ISetInheritanceParent)child).SetParent(child.Parent);
                 }
 
                 child = newValue as IControl;

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

@@ -150,7 +150,7 @@ namespace Avalonia.Controls.Notifications
         private void Install(Window host)
         {
             var adornerLayer = host.GetVisualDescendants()
-                .OfType<AdornerDecorator>()
+                .OfType<VisualLayerManager>()
                 .FirstOrDefault()
                 ?.AdornerLayer;
 

+ 17 - 2
src/Avalonia.Controls/PlacementMode.cs

@@ -23,6 +23,21 @@ namespace Avalonia.Controls
         /// <summary>
         /// The popup is placed at the top right of its target.
         /// </summary>
-        Right
+        Right,
+        
+        /// <summary>
+        /// The popup is placed at the top left of its target.
+        /// </summary>
+        Left,
+        
+        /// <summary>
+        /// The popup is placed at the top left of its target.
+        /// </summary>
+        Top,
+        
+        /// <summary>
+        /// The popup is placed according to anchor and gravity rules
+        /// </summary>
+        AnchorAndGravity
     }
-}
+}

+ 1 - 1
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@@ -396,7 +396,7 @@ namespace Avalonia.Controls.Platform
 
         protected internal virtual void WindowDeactivated(object sender, EventArgs e)
         {
-            Menu.Close();
+            Menu?.Close();
         }
 
         protected void Click(IMenuItem item)

+ 23 - 0
src/Avalonia.Controls/Platform/IMountedVolumeInfoProvider.cs

@@ -0,0 +1,23 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.ObjectModel;
+using System.Threading.Tasks;
+using Avalonia.Platform;
+
+namespace Avalonia.Controls.Platform
+{
+    /// <summary>
+    /// Defines a platform-specific mount volumes info provider implementation.
+    /// </summary>
+    public interface IMountedVolumeInfoProvider 
+    {
+        /// <summary>
+        /// Listens to any changes in volume mounts and
+        /// forwards updates to the referenced
+        /// <see cref="ObservableCollection{MountedDriveInfo}"/>.
+        /// </summary> 
+        IDisposable Listen(ObservableCollection<MountedVolumeInfo> mountedDrives);
+    }
+}

+ 3 - 1
src/Avalonia.Controls/Platform/IPopupImpl.cs

@@ -1,6 +1,8 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using Avalonia.Controls.Primitives.PopupPositioning;
+
 namespace Avalonia.Platform
 {
     /// <summary>
@@ -8,6 +10,6 @@ namespace Avalonia.Platform
     /// </summary>
     public interface IPopupImpl : IWindowBaseImpl
     {
-
+        IPopupPositioner PopupPositioner { get; }
     }
 }

+ 2 - 0
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@@ -107,5 +107,7 @@ namespace Avalonia.Platform
         /// </summary>
         [CanBeNull]
         IMouseDevice MouseDevice { get; }
+
+        IPopupImpl CreatePopup();
     }
 }

+ 1 - 23
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@@ -15,21 +15,10 @@ namespace Avalonia.Platform
         /// </summary>
         void Hide();
 
-        /// <summary>
-        /// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
-        /// </summary>
-        void BeginMoveDrag();
-
-        /// <summary>
-        /// Starts resizing a window. This function is used if an application has window resizing controls. 
-        /// Should be called from left mouse button press event handler
-        /// </summary>
-        void BeginResizeDrag(WindowEdge edge);
-
         /// <summary>
         /// Gets the position of the window in device pixels.
         /// </summary>
-        PixelPoint Position { get; set; }
+        PixelPoint Position { get; }
         
         /// <summary>
         /// Gets or sets a method called when the window's position changes.
@@ -61,17 +50,6 @@ namespace Avalonia.Platform
         /// </summary>
         Size MaxClientSize { get; }
 
-        /// <summary>
-        /// Sets the client size of the top level.
-        /// </summary>
-        void Resize(Size clientSize);
-
-        /// <summary>
-        /// Minimum width of the window.
-        /// </summary>
-        /// 
-        void SetMinMaxSize(Size minSize, Size maxSize);
-
         /// <summary>
         /// Sets whether this window appears on top of all other windows
         /// </summary>

+ 27 - 0
src/Avalonia.Controls/Platform/IWindowImpl.cs

@@ -57,5 +57,32 @@ namespace Avalonia.Platform
         /// Return true to prevent the underlying implementation from closing.
         /// </summary>
         Func<bool> Closing { get; set; }
+        
+        /// <summary>
+        /// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
+        /// </summary>
+        void BeginMoveDrag();
+
+        /// <summary>
+        /// Starts resizing a window. This function is used if an application has window resizing controls. 
+        /// Should be called from left mouse button press event handler
+        /// </summary>
+        void BeginResizeDrag(WindowEdge edge);
+        
+        /// <summary>
+        /// Sets the client size of the top level.
+        /// </summary>
+        void Resize(Size clientSize);
+        
+        /// <summary>
+        /// Sets the client size of the top level.
+        /// </summary>
+        void Move(PixelPoint point);
+        
+        /// <summary>
+        /// Minimum width of the window.
+        /// </summary>
+        /// 
+        void SetMinMaxSize(Size minSize, Size maxSize);
     }
 }

+ 0 - 1
src/Avalonia.Controls/Platform/IWindowingPlatform.cs

@@ -4,6 +4,5 @@ namespace Avalonia.Platform
     {
         IWindowImpl CreateWindow();
         IEmbeddableWindowImpl CreateEmbeddableWindow();
-        IPopupImpl CreatePopup();
     }
 }

+ 13 - 13
src/Avalonia.Controls/Platform/InProcessDragSource.cs

@@ -14,7 +14,7 @@ namespace Avalonia.Platform
 {
     class InProcessDragSource : IPlatformDragSource
     {
-        private const InputModifiers MOUSE_INPUTMODIFIERS = InputModifiers.LeftMouseButton|InputModifiers.MiddleMouseButton|InputModifiers.RightMouseButton;
+        private const RawInputModifiers MOUSE_INPUTMODIFIERS = RawInputModifiers.LeftMouseButton|RawInputModifiers.MiddleMouseButton|RawInputModifiers.RightMouseButton;
         private readonly IDragDropDevice _dragDrop;
         private readonly IInputManager _inputManager;
         private readonly Subject<DragDropEffects> _result = new Subject<DragDropEffects>();
@@ -25,7 +25,7 @@ namespace Avalonia.Platform
         private Point _lastPosition;
         private StandardCursorType _lastCursorType;
         private object _originalCursor;
-        private InputModifiers? _initialInputModifiers;
+        private RawInputModifiers? _initialInputModifiers;
 
         public InProcessDragSource()
         {
@@ -33,9 +33,10 @@ namespace Avalonia.Platform
             _dragDrop = AvaloniaLocator.Current.GetService<IDragDropDevice>();
         }
 
-        public async Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects)
+        public async Task<DragDropEffects> DoDragDrop(PointerEventArgs triggerEvent, IDataObject data, DragDropEffects allowedEffects)
         {
             Dispatcher.UIThread.VerifyAccess();
+            triggerEvent.Pointer.Capture(null);
             if (_draggedData == null)
             {
                 _draggedData = data;
@@ -55,8 +56,7 @@ namespace Avalonia.Platform
             return DragDropEffects.None;
         }
 
-
-        private DragDropEffects RaiseEventAndUpdateCursor(RawDragEventType type, IInputElement root, Point pt, InputModifiers modifiers)
+        private DragDropEffects RaiseEventAndUpdateCursor(RawDragEventType type, IInputElement root, Point pt, RawInputModifiers modifiers)
         {
             _lastPosition = pt;
 
@@ -69,13 +69,13 @@ namespace Avalonia.Platform
             return effect;
         }
 
-        private DragDropEffects GetPreferredEffect(DragDropEffects effect, InputModifiers modifiers)
+        private DragDropEffects GetPreferredEffect(DragDropEffects effect, RawInputModifiers modifiers)
         {
             if (effect == DragDropEffects.Copy || effect == DragDropEffects.Move || effect == DragDropEffects.Link || effect == DragDropEffects.None)
                 return effect; // No need to check for the modifiers.
-            if (effect.HasFlag(DragDropEffects.Link) && modifiers.HasFlag(InputModifiers.Alt))
+            if (effect.HasFlag(DragDropEffects.Link) && modifiers.HasFlag(RawInputModifiers.Alt))
                 return DragDropEffects.Link;
-            if (effect.HasFlag(DragDropEffects.Copy) && modifiers.HasFlag(InputModifiers.Control))
+            if (effect.HasFlag(DragDropEffects.Copy) && modifiers.HasFlag(RawInputModifiers.Control))
                 return DragDropEffects.Copy;
             return DragDropEffects.Move;
         }
@@ -131,7 +131,7 @@ namespace Avalonia.Platform
         private void CancelDragging()
         {
             if (_lastRoot != null)
-                RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, InputModifiers.None);
+                RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, RawInputModifiers.None);
             UpdateCursor(null, DragDropEffects.None);
             _result.OnNext(DragDropEffects.None);
         }
@@ -159,7 +159,7 @@ namespace Avalonia.Platform
                 _initialInputModifiers = e.InputModifiers & MOUSE_INPUTMODIFIERS;
 
             
-            void CheckDraggingAccepted(InputModifiers changedMouseButton)
+            void CheckDraggingAccepted(RawInputModifiers changedMouseButton)
             {
                 if (_initialInputModifiers.Value.HasFlag(changedMouseButton))
                 {
@@ -184,11 +184,11 @@ namespace Avalonia.Platform
                 case RawPointerEventType.LeaveWindow:
                     RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, e.Root, e.Position,  e.InputModifiers); break;
                 case RawPointerEventType.LeftButtonUp:
-                    CheckDraggingAccepted(InputModifiers.LeftMouseButton); break;
+                    CheckDraggingAccepted(RawInputModifiers.LeftMouseButton); break;
                 case RawPointerEventType.MiddleButtonUp:
-                    CheckDraggingAccepted(InputModifiers.MiddleMouseButton); break;
+                    CheckDraggingAccepted(RawInputModifiers.MiddleMouseButton); break;
                 case RawPointerEventType.RightButtonUp:
-                    CheckDraggingAccepted(InputModifiers.RightMouseButton); break;
+                    CheckDraggingAccepted(RawInputModifiers.RightMouseButton); break;
                 case RawPointerEventType.Move:
                     var mods = e.InputModifiers & MOUSE_INPUTMODIFIERS;
                     if (_initialInputModifiers.Value != mods)

+ 24 - 0
src/Avalonia.Controls/Platform/MountedDriveInfo.cs

@@ -0,0 +1,24 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Controls.Platform
+{
+    /// <summary>
+    /// Describes a Drive's properties.
+    /// </summary>
+    public class MountedVolumeInfo : IEquatable<MountedVolumeInfo>
+    {
+        public string VolumeLabel { get; set; }
+        public string VolumePath { get; set; }
+        public ulong VolumeSizeBytes { get; set; }
+
+        public bool Equals(MountedVolumeInfo other)
+        {
+            return this.VolumeSizeBytes.Equals(other.VolumeSizeBytes) &&
+                   this.VolumePath.Equals(other.VolumePath) &&
+                   (this.VolumeLabel ?? string.Empty).Equals(other.VolumeLabel ?? string.Empty);
+        }
+    }
+}

+ 0 - 5
src/Avalonia.Controls/Platform/PlatformManager.cs

@@ -41,10 +41,5 @@ namespace Avalonia.Controls.Platform
                 throw new Exception("Could not CreateEmbeddableWindow(): IWindowingPlatform is not registered.");
             return platform.CreateEmbeddableWindow();
         }
-
-        public static IPopupImpl CreatePopup()
-        {
-            return AvaloniaLocator.Current.GetService<IWindowingPlatform>().CreatePopup();
-        }
     }
 }

+ 6 - 1
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -237,7 +237,7 @@ namespace Avalonia.Controls.Presenters
                     // template.
                     LogicalChildren.Remove(oldChild);
                 }
-                else
+                else if (TemplatedParent != null)
                 {
                     // If we're in a ContentControl's template then invoke ChildChanging to let
                     // ContentControlMixin handle removing the logical child.
@@ -248,6 +248,10 @@ namespace Avalonia.Controls.Presenters
                         newChild,
                         BindingPriority.LocalValue));
                 }
+                else if (oldChild != null)
+                {
+                    ((ISetInheritanceParent)oldChild).SetParent(oldChild.Parent);
+                }
             }
 
             // Set the DataContext if the data isn't a control.
@@ -433,6 +437,7 @@ namespace Avalonia.Controls.Presenters
             {
                 VisualChildren.Remove(Child);
                 LogicalChildren.Remove(Child);
+                ((ISetInheritanceParent)Child).SetParent(Child.Parent);
                 Child = null;
                 _dataTemplate = null;
             }

+ 1 - 1
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@@ -141,7 +141,7 @@ namespace Avalonia.Controls.Presenters
         /// <returns>True if the scroll offset was changed; otherwise false.</returns>
         public bool BringDescendantIntoView(IVisual target, Rect targetRect)
         {
-            if (Child == null)
+            if (Child?.IsEffectivelyVisible != true)
             {
                 return false;
             }

+ 0 - 42
src/Avalonia.Controls/Primitives/AdornerDecorator.cs

@@ -1,42 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using Avalonia.LogicalTree;
-
-namespace Avalonia.Controls.Primitives
-{
-    public class AdornerDecorator : Decorator
-    {
-        public AdornerDecorator()
-        {
-            AdornerLayer = new AdornerLayer();
-            ((ISetLogicalParent)AdornerLayer).SetParent(this);
-            AdornerLayer.ZIndex = int.MaxValue;
-            VisualChildren.Add(AdornerLayer);
-        }
-
-        protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
-        {
-            base.OnAttachedToLogicalTree(e);
-
-            ((ILogical)AdornerLayer).NotifyAttachedToLogicalTree(e);
-        }
-
-        public AdornerLayer AdornerLayer
-        {
-            get;
-        }
-
-        protected override Size MeasureOverride(Size availableSize)
-        {
-            AdornerLayer.Measure(availableSize);
-            return base.MeasureOverride(availableSize);
-        }
-
-        protected override Size ArrangeOverride(Size finalSize)
-        {
-            AdornerLayer.Arrange(new Rect(finalSize));
-            return base.ArrangeOverride(finalSize);
-        }
-    }
-}

+ 1 - 1
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@@ -42,7 +42,7 @@ namespace Avalonia.Controls.Primitives
         public static AdornerLayer GetAdornerLayer(IVisual visual)
         {
             return visual.GetVisualAncestors()
-                .OfType<AdornerDecorator>()
+                .OfType<VisualLayerManager>()
                 .FirstOrDefault()
                 ?.AdornerLayer;
         }

+ 26 - 0
src/Avalonia.Controls/Primitives/IPopupHost.cs

@@ -0,0 +1,26 @@
+using System;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Primitives.PopupPositioning;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.Primitives
+{
+    public interface IPopupHost : IDisposable
+    {
+        void SetChild(IControl control);
+        IContentPresenter Presenter { get; }
+        IVisual HostedVisualTreeRoot { get; }
+
+        event EventHandler<TemplateAppliedEventArgs> TemplateApplied;
+
+        void ConfigurePosition(IVisual target, PlacementMode placement, Point offset,
+            PopupPositioningEdge anchor = PopupPositioningEdge.None,
+            PopupPositioningEdge gravity = PopupPositioningEdge.None);
+        void Show();
+        void Hide();
+        IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty,
+            StyledProperty<double> minWidthProperty, StyledProperty<double> maxWidthProperty,
+            StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
+            StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty);
+    }
+}

+ 38 - 0
src/Avalonia.Controls/Primitives/OverlayLayer.cs

@@ -0,0 +1,38 @@
+using System.Linq;
+using Avalonia.Rendering;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.Primitives
+{
+    public class OverlayLayer : Canvas, ICustomSimpleHitTest
+    {
+        public Size AvailableSize { get; private set; }
+        public static OverlayLayer GetOverlayLayer(IVisual visual)
+        {
+            foreach(var v in visual.GetVisualAncestors())
+                if(v is VisualLayerManager vlm)
+                    if (vlm.OverlayLayer != null)
+                        return vlm.OverlayLayer;
+            if (visual is TopLevel tl)
+            {
+                var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault();
+                return layers?.OverlayLayer;
+            }
+
+            return null;
+        }
+        
+        public bool HitTest(Point point)
+        {
+            return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
+        }
+        
+        protected override Size ArrangeOverride(Size finalSize)
+        {
+            // We are saving it here since child controls might need to know the entire size of the overlay
+            // and Bounds won't be updated in time
+            AvailableSize = finalSize;
+            return base.ArrangeOverride(finalSize);
+        }
+    }
+}

+ 149 - 0
src/Avalonia.Controls/Primitives/OverlayPopupHost.cs

@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Reactive.Disposables;
+using Avalonia.Controls.Primitives.PopupPositioning;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.Primitives
+{
+    public class OverlayPopupHost : ContentControl, IPopupHost, IInteractive, IManagedPopupPositionerPopup
+    {
+        private readonly OverlayLayer _overlayLayer;
+        private PopupPositionerParameters _positionerParameters = new PopupPositionerParameters();
+        private ManagedPopupPositioner _positioner;
+        private Point _lastRequestedPosition;
+        private bool _shown;
+
+        public OverlayPopupHost(OverlayLayer overlayLayer)
+        {
+            _overlayLayer = overlayLayer;
+            _positioner = new ManagedPopupPositioner(this);
+        }
+
+        public void SetChild(IControl control)
+        {
+            Content = control;
+        }
+
+        public IVisual HostedVisualTreeRoot => null;
+        
+        /// <inheritdoc/>
+        IInteractive IInteractive.InteractiveParent => Parent;
+
+        public void Dispose() => Hide();
+
+
+        public void Show()
+        {
+            _overlayLayer.Children.Add(this);
+            _shown = true;
+        }
+
+        public void Hide()
+        {
+            _overlayLayer.Children.Remove(this);
+            _shown = false;
+        }
+
+        public IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty, StyledProperty<double> minWidthProperty,
+            StyledProperty<double> maxWidthProperty, StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
+            StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty)
+        {
+            // Topmost property is not supported
+            var bindings = new List<IDisposable>();
+
+            void Bind(AvaloniaProperty what, AvaloniaProperty to) => bindings.Add(this.Bind(what, popup[~to]));
+            Bind(WidthProperty, widthProperty);
+            Bind(MinWidthProperty, minWidthProperty);
+            Bind(MaxWidthProperty, maxWidthProperty);
+            Bind(HeightProperty, heightProperty);
+            Bind(MinHeightProperty, minHeightProperty);
+            Bind(MaxHeightProperty, maxHeightProperty);
+            
+            return Disposable.Create(() =>
+            {
+                foreach (var x in bindings)
+                    x.Dispose();
+            });
+        }
+
+        public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset,
+            PopupPositioningEdge anchor = PopupPositioningEdge.None, PopupPositioningEdge gravity = PopupPositioningEdge.None)
+        {
+            _positionerParameters.ConfigurePosition((TopLevel)_overlayLayer.GetVisualRoot(), target, placement, offset, anchor,
+                gravity);
+            UpdatePosition();
+        }
+
+        protected override Size ArrangeOverride(Size finalSize)
+        {
+            if (_positionerParameters.Size != finalSize)
+            {
+                _positionerParameters.Size = finalSize;
+                UpdatePosition();
+            }
+            return base.ArrangeOverride(finalSize);
+        }
+
+
+        private void UpdatePosition()
+        {
+            // Don't bother the positioner with layout system artifacts
+            if (_positionerParameters.Size.Width == 0 || _positionerParameters.Size.Height == 0)
+                return;
+            if (_shown)
+            {
+                _positioner.Update(_positionerParameters);
+            }
+        }
+
+        IReadOnlyList<ManagedPopupPositionerScreenInfo> IManagedPopupPositionerPopup.Screens
+        {
+            get
+            {
+                var rc = new Rect(default, _overlayLayer.AvailableSize);
+                return new[] {new ManagedPopupPositionerScreenInfo(rc, rc)};
+            }
+        }
+
+        Rect IManagedPopupPositionerPopup.ParentClientAreaScreenGeometry =>
+            new Rect(default, _overlayLayer.Bounds.Size);
+
+        void IManagedPopupPositionerPopup.MoveAndResize(Point devicePoint, Size virtualSize)
+        {
+            _lastRequestedPosition = devicePoint;
+            Dispatcher.UIThread.Post(() =>
+            {
+                OverlayLayer.SetLeft(this, _lastRequestedPosition.X);
+                OverlayLayer.SetTop(this, _lastRequestedPosition.Y);
+            }, DispatcherPriority.Layout);
+        }
+
+        Point IManagedPopupPositionerPopup.TranslatePoint(Point pt) => pt;
+
+        Size IManagedPopupPositionerPopup.TranslateSize(Size size) => size;
+        
+        public static IPopupHost CreatePopupHost(IVisual target, IAvaloniaDependencyResolver dependencyResolver)
+        {
+            var platform = (target.GetVisualRoot() as TopLevel)?.PlatformImpl?.CreatePopup();
+            if (platform != null)
+                return new PopupRoot((TopLevel)target.GetVisualRoot(), platform, dependencyResolver);
+            
+            var overlayLayer = OverlayLayer.GetOverlayLayer(target);
+            if (overlayLayer == null)
+                throw new InvalidOperationException(
+                    "Unable to create IPopupImpl and no overlay layer is found for the target control");
+
+
+            return new OverlayPopupHost(overlayLayer);
+        }
+
+        public override void Render(DrawingContext context)
+        {
+            context.FillRectangle(Brushes.White, new Rect(default, Bounds.Size));
+        }
+    }
+}

+ 144 - 140
src/Avalonia.Controls/Primitives/Popup.cs

@@ -2,7 +2,12 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
+using System.Reactive.Disposables;
+using Avalonia.Controls.Presenters;
+using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
 using Avalonia.Interactivity;
@@ -42,7 +47,7 @@ namespace Avalonia.Controls.Primitives
         /// Defines the <see cref="ObeyScreenEdges"/> property.
         /// </summary>
         public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
-            AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges));
+            AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges), true);
 
         /// <summary>
         /// Defines the <see cref="HorizontalOffset"/> property.
@@ -75,10 +80,12 @@ namespace Avalonia.Controls.Primitives
             AvaloniaProperty.Register<Popup, bool>(nameof(Topmost));
 
         private bool _isOpen;
-        private PopupRoot _popupRoot;
+        private IPopupHost _popupHost;
         private TopLevel _topLevel;
         private IDisposable _nonClientListener;
+        private IDisposable _presenterSubscription;
         bool _ignoreIsOpenChanged = false;
+        private List<IDisposable> _bindings = new List<IDisposable>();
 
         /// <summary>
         /// Initializes static members of the <see cref="Popup"/> class.
@@ -88,7 +95,11 @@ namespace Avalonia.Controls.Primitives
             IsHitTestVisibleProperty.OverrideDefaultValue<Popup>(false);
             ChildProperty.Changed.AddClassHandler<Popup>(x => x.ChildChanged);
             IsOpenProperty.Changed.AddClassHandler<Popup>(x => x.IsOpenChanged);
-            TopmostProperty.Changed.AddClassHandler<Popup>((p, e) => p.PopupRoot.Topmost = (bool)e.NewValue);
+        }
+
+        public Popup()
+        {
+            
         }
 
         /// <summary>
@@ -101,10 +112,7 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         public event EventHandler Opened;
 
-        /// <summary>
-        /// Raised when the popup root has been created, but before it has been shown.
-        /// </summary>
-        public event EventHandler PopupRootCreated;
+        public IPopupHost Host => _popupHost;
 
         /// <summary>
         /// Gets or sets the control to display in the popup.
@@ -147,10 +155,7 @@ namespace Avalonia.Controls.Primitives
             set { SetValue(PlacementModeProperty, value); }
         }
 
-        /// <summary>
-        /// Gets or sets a value indicating whether the popup positions itself within the nearest screen boundary
-        /// when its opened at a position where it would otherwise overlap the screen edge.
-        /// </summary>
+        [Obsolete("This property has no effect")]
         public bool ObeyScreenEdges
         {
             get => GetValue(ObeyScreenEdgesProperty);
@@ -184,11 +189,6 @@ namespace Avalonia.Controls.Primitives
             set { SetValue(PlacementTargetProperty, value); }
         }
 
-        /// <summary>
-        /// Gets the root of the popup window.
-        /// </summary>
-        public PopupRoot PopupRoot => _popupRoot;
-
         /// <summary>
         /// Gets or sets a value indicating whether the popup should stay open when the popup is
         /// pressed or loses focus.
@@ -211,63 +211,58 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Gets the root of the popup window.
         /// </summary>
-        IVisual IVisualTreeHost.Root => _popupRoot;
+        IVisual IVisualTreeHost.Root => _popupHost?.HostedVisualTreeRoot;
 
         /// <summary>
         /// Opens the popup.
         /// </summary>
         public void Open()
         {
-            if (_popupRoot == null)
+            // Popup is currently open
+            if (_topLevel != null)
+                return;
+            CloseCurrent();
+            var placementTarget = PlacementTarget ?? this.GetLogicalAncestors().OfType<IVisual>().FirstOrDefault();
+            if (placementTarget == null)
+                throw new InvalidOperationException("Popup has no logical parent and PlacementTarget is null");
+            
+            _topLevel = placementTarget.GetVisualRoot() as TopLevel;
+
+            if (_topLevel == null)
             {
-                _popupRoot = new PopupRoot(DependencyResolver)
-                {
-                    [~ContentControl.ContentProperty] = this[~ChildProperty],
-                    [~WidthProperty] = this[~WidthProperty],
-                    [~HeightProperty] = this[~HeightProperty],
-                    [~MinWidthProperty] = this[~MinWidthProperty],
-                    [~MaxWidthProperty] = this[~MaxWidthProperty],
-                    [~MinHeightProperty] = this[~MinHeightProperty],
-                    [~MaxHeightProperty] = this[~MaxHeightProperty],
-                };
-
-                ((ISetLogicalParent)_popupRoot).SetParent(this);
+                throw new InvalidOperationException(
+                    "Attempted to open a popup not attached to a TopLevel");
             }
 
-            _popupRoot.Position = GetPosition();
+            _popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver);
+
+            _bindings.Add(_popupHost.BindConstraints(this, WidthProperty, MinWidthProperty, MaxWidthProperty,
+                HeightProperty, MinHeightProperty, MaxHeightProperty, TopmostProperty));
 
-            if (_topLevel == null && PlacementTarget != null)
+            _popupHost.SetChild(Child);
+            ((ISetLogicalParent)_popupHost).SetParent(this);
+            _popupHost.ConfigurePosition(placementTarget,
+                PlacementMode, new Point(HorizontalOffset, VerticalOffset));
+            _popupHost.TemplateApplied += RootTemplateApplied;
+            
+            var window = _topLevel as Window;
+            if (window != null)
             {
-                _topLevel = PlacementTarget.GetSelfAndLogicalAncestors().First(x => x is TopLevel) as TopLevel;
+                window.Deactivated += WindowDeactivated;
             }
-
-            if (_topLevel != null)
+            else
             {
-                var window = _topLevel as Window;
-                if (window != null)
+                var parentPopuproot = _topLevel as PopupRoot;
+                if (parentPopuproot?.Parent is Popup popup)
                 {
-                    window.Deactivated += WindowDeactivated;
+                    popup.Closed += ParentClosed;
                 }
-                else
-                {
-                    var parentPopuproot = _topLevel as PopupRoot;
-                    if (parentPopuproot?.Parent is Popup popup)
-                    {
-                        popup.Closed += ParentClosed;
-                    }
-                }
-                _topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
-                _nonClientListener = InputManager.Instance.Process.Subscribe(ListenForNonClientClick);
             }
+            _topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
+            _nonClientListener = InputManager.Instance?.Process.Subscribe(ListenForNonClientClick);
+        
 
-            PopupRootCreated?.Invoke(this, EventArgs.Empty);
-
-            _popupRoot.Show();
-
-            if (ObeyScreenEdges)
-            {
-                _popupRoot.SnapInsideScreenEdges();
-            }
+            _popupHost.Show();
 
             using (BeginIgnoringIsOpen())
             {
@@ -282,29 +277,14 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         public void Close()
         {
-            if (_popupRoot != null)
+            if (_popupHost != null)
             {
-                if (_topLevel != null)
-                {
-                    _topLevel.RemoveHandler(PointerPressedEvent, PointerPressedOutside);
-                    var window = _topLevel as Window;
-                    if (window != null)
-                        window.Deactivated -= WindowDeactivated;
-                    else
-                    {
-                        var parentPopuproot = _topLevel as PopupRoot;
-                        if (parentPopuproot?.Parent is Popup popup)
-                        {
-                            popup.Closed -= ParentClosed;
-                        }
-                    }
-                    _nonClientListener?.Dispose();
-                    _nonClientListener = null;
-                }
-
-                _popupRoot.Hide();
+                _popupHost.TemplateApplied -= RootTemplateApplied;
             }
 
+            _presenterSubscription?.Dispose();
+
+            CloseCurrent();
             using (BeginIgnoringIsOpen())
             {
                 IsOpen = false;
@@ -313,6 +293,41 @@ namespace Avalonia.Controls.Primitives
             Closed?.Invoke(this, EventArgs.Empty);
         }
 
+        void CloseCurrent()
+        {
+            if (_topLevel != null)
+            {
+                _topLevel.RemoveHandler(PointerPressedEvent, PointerPressedOutside);
+                var window = _topLevel as Window;
+                if (window != null)
+                    window.Deactivated -= WindowDeactivated;
+                else
+                {
+                    var parentPopuproot = _topLevel as PopupRoot;
+                    if (parentPopuproot?.Parent is Popup popup)
+                    {
+                        popup.Closed -= ParentClosed;
+                    }
+                }
+                _nonClientListener?.Dispose();
+                _nonClientListener = null;
+                
+                _topLevel = null;
+            }
+            if (_popupHost != null)
+            {
+                foreach(var b in _bindings)
+                    b.Dispose();
+                _bindings.Clear();
+                _popupHost.SetChild(null);
+                _popupHost.Hide();
+                ((ISetLogicalParent)_popupHost).SetParent(null);
+                _popupHost.Dispose();
+                _popupHost = null;
+            }
+
+        }
+
         /// <summary>
         /// Measures the control.
         /// </summary>
@@ -323,27 +338,14 @@ namespace Avalonia.Controls.Primitives
             return new Size();
         }
 
-        /// <inheritdoc/>
-        protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
-        {
-            base.OnAttachedToLogicalTree(e);
-            _topLevel = e.Root as TopLevel;
-        }
-
         /// <inheritdoc/>
         protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
         {
             base.OnDetachedFromLogicalTree(e);
-            _topLevel = null;
-
-            if (_popupRoot != null)
-            {
-                ((ISetLogicalParent)_popupRoot).SetParent(null);
-                _popupRoot.Dispose();
-                _popupRoot = null;
-            }
+            Close();
         }
 
+
         /// <summary>
         /// Called when the <see cref="IsOpen"/> property changes.
         /// </summary>
@@ -380,49 +382,6 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        /// <summary>
-        /// Gets the position for the popup based on the placement properties.
-        /// </summary>
-        /// <returns>The popup's position in screen coordinates.</returns>
-        protected virtual PixelPoint GetPosition()
-        {
-            var result = GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
-                HorizontalOffset, VerticalOffset);
-
-            return result;
-        }
-
-        internal static PixelPoint GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
-        {
-            var root = target?.GetVisualRoot();
-            var mode = root != null ? placement : PlacementMode.Pointer;
-            var scaling = root?.RenderScaling ?? 1;
-
-            switch (mode)
-            {
-                case PlacementMode.Pointer:
-                    if (popupRoot != null)
-                    {
-                        var screenOffset = PixelPoint.FromPoint(new Point(horizontalOffset, verticalOffset), scaling);
-                        var mouseOffset = ((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default;
-                        return new PixelPoint(
-                            screenOffset.X + mouseOffset.X,
-                            screenOffset.Y + mouseOffset.Y);
-                    }
-
-                    return default;
-
-                case PlacementMode.Bottom:
-                    return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ?? default;
-
-                case PlacementMode.Right:
-                    return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? default;
-
-                default:
-                    throw new InvalidOperationException("Invalid value for Popup.PlacementMode");
-            }
-        }
-
         private void ListenForNonClientClick(RawInputEventArgs e)
         {
             var mouse = e as RawPointerEventArgs;
@@ -445,17 +404,62 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        private bool IsChildOrThis(IVisual child)
+        private void RootTemplateApplied(object sender, TemplateAppliedEventArgs e)
         {
-            IVisual root = child.GetVisualRoot();
-            while (root is PopupRoot)
+            _popupHost.TemplateApplied -= RootTemplateApplied;
+
+            if (_presenterSubscription != null)
             {
-                if (root == PopupRoot) return true;
-                root = ((PopupRoot)root).Parent.GetVisualRoot();
+                _presenterSubscription.Dispose();
+                _presenterSubscription = null;
+            }
+
+            // If the Popup appears in a control template, then the child controls
+            // that appear in the popup host need to have their TemplatedParent
+            // properties set.
+            if (TemplatedParent != null)
+            {
+                _popupHost.Presenter?.ApplyTemplate();
+                _popupHost.Presenter?.GetObservable(ContentPresenter.ChildProperty)
+                    .Subscribe(SetTemplatedParentAndApplyChildTemplates);
+            }
+        }
+
+        private void SetTemplatedParentAndApplyChildTemplates(IControl control)
+        {
+            if (control != null)
+            {
+                var templatedParent = TemplatedParent;
+
+                if (control.TemplatedParent == null)
+                {
+                    control.SetValue(TemplatedParentProperty, templatedParent);
+                }
+
+                control.ApplyTemplate();
+
+                if (!(control is IPresenter) && control.TemplatedParent == templatedParent)
+                {
+                    foreach (IControl child in control.GetVisualChildren())
+                    {
+                        SetTemplatedParentAndApplyChildTemplates(child);
+                    }
+                }
             }
-            return false;
         }
 
+        private bool IsChildOrThis(IVisual child)
+        {
+            return _popupHost != null && ((IVisual)_popupHost).FindCommonVisualAncestor(child) == _popupHost;
+        }
+        
+        public bool IsInsidePopup(IVisual visual)
+        {
+            return _popupHost != null && ((IVisual)_popupHost)?.IsVisualAncestorOf(visual) == true;
+        }
+
+        public bool IsPointerOverPopup => ((IInputElement)_popupHost).IsPointerOver;
+
         private void WindowDeactivated(object sender, EventArgs e)
         {
             if (!StaysOpen)

+ 358 - 0
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@@ -0,0 +1,358 @@
+// The documentation and flag names in this file are initially taken from
+// xdg_shell wayland protocol this API is designed after
+// therefore, I'm including the license from wayland-protocols repo
+
+/* 
+Copyright © 2008-2013 Kristian Høgsberg
+Copyright © 2010-2013 Intel Corporation
+Copyright © 2013      Rafael Antognolli
+Copyright © 2013      Jasper St. Pierre
+Copyright © 2014      Jonas Ådahl
+Copyright © 2014      Jason Ekstrand
+Copyright © 2014-2015 Collabora, Ltd.
+Copyright © 2015      Red Hat Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next
+paragraph) shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+---
+
+The above is the version of the MIT "Expat" License used by X.org:
+
+    http://cgit.freedesktop.org/xorg/xserver/tree/COPYING
+    
+    
+Adjustments for Avalonia needs:
+Copyright © 2019 Nikita Tsukanov
+    
+    
+*/
+
+using System;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.Primitives.PopupPositioning
+{
+    /// <summary>
+    /// 
+    /// The IPopupPositioner provides a collection of rules for the placement of a
+    /// a popup relative to its parent. Rules can be defined to ensure
+    /// the popup remains within the visible area's borders, and to
+    /// specify how the popup changes its position, such as sliding along
+    /// an axis, or flipping around a rectangle. These positioner-created rules are
+    /// constrained by the requirement that a popup must intersect with or
+    /// be at least partially adjacent to its parent surface.
+    /// </summary>
+    public struct PopupPositionerParameters
+    {
+        private PopupPositioningEdge _gravity;
+        private PopupPositioningEdge _anchor;
+
+        /// <summary>
+        /// Set the size of the popup that is to be positioned with the positioner
+        /// object. The size is in scaled coordinates.
+        /// </summary>
+        public Size Size { get; set; }
+
+        /// <summary>
+        /// Specify the anchor rectangle within the parent that the popup
+        /// will be placed relative to. The rectangle is relative to the
+        /// parent geometry
+        /// 
+        /// The anchor rectangle may not extend outside the window geometry of the
+        /// popup's parent. The anchor rectangle is in scaled coordinates
+        /// </summary>
+        public Rect AnchorRectangle { get; set; }
+
+
+        /// <summary>
+        /// Defines the anchor point for the anchor rectangle. The specified anchor
+        /// is used derive an anchor point that the popup will be
+        /// positioned relative to. If a corner anchor is set (e.g. 'TopLeft' or
+        /// 'BottomRight'), the anchor point will be at the specified corner;
+        /// otherwise, the derived anchor point will be centered on the specified
+        /// edge, or in the center of the anchor rectangle if no edge is specified.
+        /// </summary>
+        public PopupPositioningEdge Anchor
+        {
+            get => _anchor;
+            set
+            {
+                PopupPositioningEdgeHelper.ValidateEdge(value);
+                _anchor = value;
+            }
+        }
+
+        /// <summary>
+        /// Defines in what direction a popup should be positioned, relative to
+        /// the anchor point of the parent. If a corner gravity is
+        /// specified (e.g. 'BottomRight' or 'TopLeft'), then the popup
+        /// will be placed towards the specified gravity; otherwise, the popup
+        /// will be centered over the anchor point on any axis that had no
+        /// gravity specified.
+        /// </summary>
+        public PopupPositioningEdge Gravity
+        {
+            get => _gravity;
+            set
+            {
+                PopupPositioningEdgeHelper.ValidateEdge(value);
+                _gravity = value;
+            }
+        }
+
+        /// <summary>
+        /// Specify how the popup should be positioned if the originally intended
+        /// position caused the popup to be constrained, meaning at least
+        /// partially outside positioning boundaries set by the positioner. The
+        /// adjustment is set by constructing a bitmask describing the adjustment to
+        /// be made when the popup is constrained on that axis.
+        /// 
+        /// If no bit for one axis is set, the positioner will assume that the child
+        /// surface should not change its position on that axis when constrained.
+        /// 
+        /// If more than one bit for one axis is set, the order of how adjustments
+        /// are applied is specified in the corresponding adjustment descriptions.
+        /// 
+        /// The default adjustment is none.
+        /// </summary>
+        public PopupPositionerConstraintAdjustment ConstraintAdjustment { get; set; }
+        
+        /// <summary>
+        /// Specify the popup position offset relative to the position of the
+        /// anchor on the anchor rectangle and the anchor on the popup. For
+        /// example if the anchor of the anchor rectangle is at (x, y), the popup
+        /// has the gravity bottom|right, and the offset is (ox, oy), the calculated
+        /// surface position will be (x + ox, y + oy). The offset position of the
+        /// surface is the one used for constraint testing. See
+        /// set_constraint_adjustment.
+        /// 
+        /// An example use case is placing a popup menu on top of a user interface
+        /// element, while aligning the user interface element of the parent surface
+        /// with some user interface element placed somewhere in the popup.
+        /// </summary>
+        public Point Offset { get; set; }
+    }
+    
+    /// <summary>
+    /// The constraint adjustment value define ways how popup position will
+    /// be adjusted if the unadjusted position would result in the popup
+    /// being partly constrained.
+    /// 
+    /// Whether a popup is considered 'constrained' is left to the positioner
+    /// to determine. For example, the popup may be partly outside the
+    /// target platform defined 'work area', thus necessitating the popup's
+    /// position be adjusted until it is entirely inside the work area.
+    /// </summary>
+    [Flags]
+    public enum PopupPositionerConstraintAdjustment
+    {
+        /// <summary>
+        /// Don't alter the surface position even if it is constrained on some
+        /// axis, for example partially outside the edge of an output.
+        /// </summary>
+        None = 0,
+
+        /// <summary>
+        /// Slide the surface along the x axis until it is no longer constrained.
+        ///        First try to slide towards the direction of the gravity on the x axis
+        ///        until either the edge in the opposite direction of the gravity is
+        ///        unconstrained or the edge in the direction of the gravity is
+        ///        constrained.
+        ///
+        ///        Then try to slide towards the opposite direction of the gravity on the
+        ///        x axis until either the edge in the direction of the gravity is
+        ///        unconstrained or the edge in the opposite direction of the gravity is
+        ///        constrained.
+        /// </summary>
+        SlideX = 1,
+
+
+        /// <summary>
+        ///            Slide the surface along the y axis until it is no longer constrained.
+        /// 
+        /// First try to slide towards the direction of the gravity on the y axis
+        /// until either the edge in the opposite direction of the gravity is
+        /// unconstrained or the edge in the direction of the gravity is
+        /// constrained.
+        /// 
+        /// Then try to slide towards the opposite direction of the gravity on the
+        /// y axis until either the edge in the direction of the gravity is
+        /// unconstrained or the edge in the opposite direction of the gravity is
+        /// constrained.
+        /// */
+        /// </summary>
+        SlideY = 2,
+
+        /// <summary>
+        /// Invert the anchor and gravity on the x axis if the surface is
+        /// constrained on the x axis. For example, if the left edge of the
+        /// surface is constrained, the gravity is 'left' and the anchor is
+        /// 'left', change the gravity to 'right' and the anchor to 'right'.
+        /// 
+        /// If the adjusted position also ends up being constrained, the resulting
+        /// position of the flip_x adjustment will be the one before the
+        /// adjustment.
+        /// </summary>
+        FlipX = 4,
+
+        /// <summary>
+        /// Invert the anchor and gravity on the y axis if the surface is
+        /// constrained on the y axis. For example, if the bottom edge of the
+        /// surface is constrained, the gravity is 'bottom' and the anchor is
+        /// 'bottom', change the gravity to 'top' and the anchor to 'top'.
+        /// 
+        /// The adjusted position is calculated given the original anchor
+        /// rectangle and offset, but with the new flipped anchor and gravity
+        /// values.
+        /// 
+        /// If the adjusted position also ends up being constrained, the resulting
+        /// position of the flip_y adjustment will be the one before the
+        /// adjustment.
+        /// </summary>
+        FlipY = 8,
+        All = SlideX|SlideY|FlipX|FlipY
+    }
+
+    static class PopupPositioningEdgeHelper
+    {
+        public static void ValidateEdge(this PopupPositioningEdge edge)
+        {
+            if (((edge & PopupPositioningEdge.Left) != 0 && (edge & PopupPositioningEdge.Right) != 0)
+                ||
+                ((edge & PopupPositioningEdge.Top) != 0 && (edge & PopupPositioningEdge.Bottom) != 0))
+                throw new ArgumentException("Opposite edges specified");
+        }
+
+        public static PopupPositioningEdge Flip(this PopupPositioningEdge edge)
+        {
+            var hmask = PopupPositioningEdge.Left | PopupPositioningEdge.Right;
+            var vmask = PopupPositioningEdge.Top | PopupPositioningEdge.Bottom;
+            if ((edge & hmask) != 0)
+                edge ^= hmask;
+            if ((edge & vmask) != 0)
+                edge ^= vmask;
+            return edge;
+        }
+
+        public static PopupPositioningEdge FlipX(this PopupPositioningEdge edge)
+        {
+            if ((edge & PopupPositioningEdge.HorizontalMask) != 0)
+                edge ^= PopupPositioningEdge.HorizontalMask;
+            return edge;
+        }
+        
+        public static PopupPositioningEdge FlipY(this PopupPositioningEdge edge)
+        {
+            if ((edge & PopupPositioningEdge.VerticalMask) != 0)
+                edge ^= PopupPositioningEdge.VerticalMask;
+            return edge;
+        }
+        
+    }
+
+    [Flags]
+    public enum PopupPositioningEdge
+    {
+        None,
+        Top = 1,
+        Bottom = 2,
+        Left = 4,
+        Right = 8,
+        TopLeft = Top | Left,
+        TopRight = Top | Right,
+        BottomLeft = Bottom | Left,
+        BottomRight = Bottom | Right,
+
+        
+        VerticalMask = Top | Bottom,
+        HorizontalMask = Left | Right,
+        AllMask = VerticalMask|HorizontalMask
+    }
+
+    public interface IPopupPositioner
+    {
+        void Update(PopupPositionerParameters parameters);
+    }
+
+    static class PopupPositionerExtensions
+    {
+        public static void ConfigurePosition(ref this PopupPositionerParameters positionerParameters,
+            TopLevel topLevel,
+            IVisual target, PlacementMode placement, Point offset,
+            PopupPositioningEdge anchor, PopupPositioningEdge gravity)
+        {
+            // We need a better way for tracking the last pointer position
+            var pointer = topLevel.PointToClient(topLevel.PlatformImpl.MouseDevice.Position);
+            
+            positionerParameters.Offset = offset;
+            positionerParameters.ConstraintAdjustment = PopupPositionerConstraintAdjustment.All;
+            if (placement == PlacementMode.Pointer)
+            {
+                positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1));
+                positionerParameters.Anchor = PopupPositioningEdge.BottomRight;
+                positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
+            }
+            else
+            {
+                if (target == null)
+                    throw new InvalidOperationException("Placement mode is not Pointer and PlacementTarget is null");
+                var matrix = target.TransformToVisual(topLevel);
+                if (matrix == null)
+                {
+                    if (target.GetVisualRoot() == null)
+                        throw new InvalidCastException("Target control is not attached to the visual tree");
+                    throw new InvalidCastException("Target control is not in the same tree as the popup parent");
+                }
+
+                positionerParameters.AnchorRectangle = new Rect(default, target.Bounds.Size)
+                    .TransformToAABB(matrix.Value);
+
+                if (placement == PlacementMode.Right)
+                {
+                    positionerParameters.Anchor = PopupPositioningEdge.TopRight;
+                    positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
+                }
+                else if (placement == PlacementMode.Bottom)
+                {
+                    positionerParameters.Anchor = PopupPositioningEdge.BottomLeft;
+                    positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
+                }
+                else if (placement == PlacementMode.Left)
+                {
+                    positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
+                    positionerParameters.Gravity = PopupPositioningEdge.BottomLeft;
+                }
+                else if (placement == PlacementMode.Top)
+                {
+                    positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
+                    positionerParameters.Gravity = PopupPositioningEdge.TopRight;
+                }
+                else if (placement == PlacementMode.AnchorAndGravity)
+                {
+                    positionerParameters.Anchor = anchor;
+                    positionerParameters.Gravity = gravity;
+                }
+                else
+                    throw new InvalidOperationException("Invalid value for Popup.PlacementMode");
+            }
+        }
+    }
+
+}

+ 175 - 0
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs

@@ -0,0 +1,175 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Avalonia.Controls.Primitives.PopupPositioning
+{
+    public interface IManagedPopupPositionerPopup
+    {
+        IReadOnlyList<ManagedPopupPositionerScreenInfo> Screens { get; }
+        Rect ParentClientAreaScreenGeometry { get; }
+        void MoveAndResize(Point devicePoint, Size virtualSize);
+        Point TranslatePoint(Point pt);
+        Size TranslateSize(Size size);
+    }
+
+    public class ManagedPopupPositionerScreenInfo
+    {
+        public Rect Bounds { get; }
+        public Rect WorkingArea { get; }
+
+        public ManagedPopupPositionerScreenInfo(Rect bounds, Rect workingArea)
+        {
+            Bounds = bounds;
+            WorkingArea = workingArea;
+        }
+    }
+
+    public class ManagedPopupPositioner : IPopupPositioner
+    {
+        private readonly IManagedPopupPositionerPopup _popup;
+
+        public ManagedPopupPositioner(IManagedPopupPositionerPopup popup)
+        {
+            _popup = popup;
+        }
+
+
+        private static Point GetAnchorPoint(Rect anchorRect, PopupPositioningEdge edge)
+        {
+            double x, y;
+            if ((edge & PopupPositioningEdge.Left) != 0)
+                x = anchorRect.X;
+            else if ((edge & PopupPositioningEdge.Right) != 0)
+                x = anchorRect.Right;
+            else
+                x = anchorRect.X + anchorRect.Width / 2;
+            
+            if ((edge & PopupPositioningEdge.Top) != 0)
+                y = anchorRect.Y;
+            else if ((edge & PopupPositioningEdge.Bottom) != 0)
+                y = anchorRect.Bottom;
+            else
+                y = anchorRect.Y + anchorRect.Height / 2;
+            return new Point(x, y);
+        }
+
+        private static Point Gravitate(Point anchorPoint, Size size, PopupPositioningEdge gravity)
+        {
+            double x, y;
+            if ((gravity & PopupPositioningEdge.Left) != 0)
+                x = -size.Width;
+            else if ((gravity & PopupPositioningEdge.Right) != 0)
+                x = 0;
+            else
+                x = -size.Width / 2;
+            
+            if ((gravity & PopupPositioningEdge.Top) != 0)
+                y = -size.Height;
+            else if ((gravity & PopupPositioningEdge.Bottom) != 0)
+                y = 0;
+            else
+                y = -size.Height / 2;
+            return anchorPoint + new Point(x, y);
+        }
+
+        public void Update(PopupPositionerParameters parameters)
+        {
+
+            Update(_popup.TranslateSize(parameters.Size), parameters.Size,
+                new Rect(_popup.TranslatePoint(parameters.AnchorRectangle.TopLeft),
+                    _popup.TranslateSize(parameters.AnchorRectangle.Size)),
+                parameters.Anchor, parameters.Gravity, parameters.ConstraintAdjustment,
+                _popup.TranslatePoint(parameters.Offset));
+        }
+
+        
+        private void Update(Size translatedSize, Size originalSize,
+            Rect anchorRect, PopupPositioningEdge anchor, PopupPositioningEdge gravity,
+            PopupPositionerConstraintAdjustment constraintAdjustment, Point offset)
+        {
+            var parentGeometry = _popup.ParentClientAreaScreenGeometry;
+            anchorRect = anchorRect.Translate(parentGeometry.TopLeft);
+            
+            Rect GetBounds()
+            {
+                var screens = _popup.Screens;
+                
+                var targetScreen = screens.FirstOrDefault(s => s.Bounds.Contains(anchorRect.TopLeft))
+                                   ?? screens.FirstOrDefault(s => s.Bounds.Intersects(anchorRect))
+                                   ?? screens.FirstOrDefault(s => s.Bounds.Contains(parentGeometry.TopLeft))
+                                   ?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry))
+                                   ?? screens.FirstOrDefault();
+                return targetScreen?.WorkingArea
+                       ?? new Rect(0, 0, double.MaxValue, double.MaxValue);
+            }
+
+            var bounds = GetBounds();
+
+            bool FitsInBounds(Rect rc, PopupPositioningEdge edge = PopupPositioningEdge.AllMask)
+            {
+                if ((edge & PopupPositioningEdge.Left) != 0
+                    && rc.X < bounds.X)
+                    return false;
+
+                if ((edge & PopupPositioningEdge.Top) != 0
+                    && rc.Y < bounds.Y)
+                    return false;
+
+                if ((edge & PopupPositioningEdge.Right) != 0
+                    && rc.Right > bounds.Right)
+                    return false;
+
+                if ((edge & PopupPositioningEdge.Bottom) != 0
+                    && rc.Bottom > bounds.Bottom)
+                    return false;
+
+                return true;
+            }
+
+            Rect GetUnconstrained(PopupPositioningEdge a, PopupPositioningEdge g) =>
+                new Rect(Gravitate(GetAnchorPoint(anchorRect, a), translatedSize, g) + offset, translatedSize);
+
+
+            var geo = GetUnconstrained(anchor, gravity);
+
+            // If flipping geometry and anchor is allowed and helps, use the flipped one,
+            // otherwise leave it as is
+            if (!FitsInBounds(geo, PopupPositioningEdge.HorizontalMask)
+                && (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipX) != 0)
+            {
+                var flipped = GetUnconstrained(anchor.FlipX(), gravity.FlipX());
+                if (FitsInBounds(flipped, PopupPositioningEdge.HorizontalMask))
+                    geo = geo.WithX(flipped.X);
+            }
+
+            // If sliding is allowed, try moving the rect into the bounds
+            if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideX) != 0)
+            {
+                geo = geo.WithX(Math.Max(geo.X, bounds.X));
+                if (geo.Right > bounds.Right)
+                    geo = geo.WithX(bounds.Right - geo.Width);
+            }
+            
+            // If flipping geometry and anchor is allowed and helps, use the flipped one,
+            // otherwise leave it as is
+            if (!FitsInBounds(geo, PopupPositioningEdge.VerticalMask)
+                && (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipY) != 0)
+            {
+                var flipped = GetUnconstrained(anchor.FlipY(), gravity.FlipY());
+                if (FitsInBounds(flipped, PopupPositioningEdge.VerticalMask))
+                    geo = geo.WithY(flipped.Y);
+            }
+
+            // If sliding is allowed, try moving the rect into the bounds
+            if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideY) != 0)
+            {
+                geo = geo.WithY(Math.Max(geo.Y, bounds.Y));
+                if (geo.Bottom > bounds.Bottom)
+                    geo = geo.WithY(bounds.Bottom - geo.Height);
+            }
+
+            _popup.MoveAndResize(geo.TopLeft, originalSize);
+        }
+    }
+}

+ 50 - 0
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Platform;
+
+namespace Avalonia.Controls.Primitives.PopupPositioning
+{
+    /// <summary>
+    /// This class is used to simplify integration of IPopupImpl implementations with popup positioner
+    /// </summary>
+    public class ManagedPopupPositionerPopupImplHelper : IManagedPopupPositionerPopup 
+    {
+        private readonly IWindowBaseImpl _parent;
+
+        public delegate void MoveResizeDelegate(PixelPoint position, Size size, double scaling);
+        private readonly MoveResizeDelegate _moveResize;
+
+        public ManagedPopupPositionerPopupImplHelper(IWindowBaseImpl parent, MoveResizeDelegate moveResize)
+        {
+            _parent = parent;
+            _moveResize = moveResize;
+        }
+
+        public IReadOnlyList<ManagedPopupPositionerScreenInfo> Screens =>
+
+            _parent.Screen.AllScreens.Select(s => new ManagedPopupPositionerScreenInfo(
+                s.Bounds.ToRect(1), s.WorkingArea.ToRect(1))).ToList();
+
+        public Rect ParentClientAreaScreenGeometry
+        {
+            get
+            {
+                // Popup positioner operates with abstract coordinates, but in our case they are pixel ones
+                var point = _parent.PointToScreen(default);
+                var size = PixelSize.FromSize(_parent.ClientSize, _parent.Scaling);
+                return new Rect(point.X, point.Y, size.Width, size.Height);
+
+            }
+        }
+
+        public void MoveAndResize(Point devicePoint, Size virtualSize)
+        {
+            _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.Scaling);
+        }
+
+        public Point TranslatePoint(Point pt) => pt * _parent.Scaling;
+
+        public Size TranslateSize(Size size) => size * _parent.Scaling;
+    }
+}

+ 55 - 64
src/Avalonia.Controls/Primitives/PopupRoot.cs

@@ -2,8 +2,9 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using Avalonia.Controls.Platform;
-using Avalonia.Controls.Presenters;
+using System.Collections.Generic;
+using System.Reactive.Disposables;
+using Avalonia.Controls.Primitives.PopupPositioning;
 using Avalonia.Interactivity;
 using Avalonia.Media;
 using Avalonia.Platform;
@@ -16,9 +17,10 @@ namespace Avalonia.Controls.Primitives
     /// <summary>
     /// The root window of a <see cref="Popup"/>.
     /// </summary>
-    public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost
+    public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost
     {
-        private IDisposable _presenterSubscription;
+        private readonly TopLevel _parent;
+        private PopupPositionerParameters _positionerParameters;
 
         /// <summary>
         /// Initializes static members of the <see cref="PopupRoot"/> class.
@@ -31,8 +33,8 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Initializes a new instance of the <see cref="PopupRoot"/> class.
         /// </summary>
-        public PopupRoot()
-            : this(null)
+        public PopupRoot(TopLevel parent, IPopupImpl impl)
+            : this(parent, impl,null)
         {
         }
 
@@ -42,9 +44,10 @@ namespace Avalonia.Controls.Primitives
         /// <param name="dependencyResolver">
         /// The dependency resolver to use. If null the default dependency resolver will be used.
         /// </param>
-        public PopupRoot(IAvaloniaDependencyResolver dependencyResolver)
-            : base(PlatformManager.CreatePopup(), dependencyResolver)
+        public PopupRoot(TopLevel parent, IPopupImpl impl, IAvaloniaDependencyResolver dependencyResolver)
+            : base(impl, dependencyResolver)
         {
+            _parent = parent;
         }
 
         /// <summary>
@@ -74,73 +77,61 @@ namespace Avalonia.Controls.Primitives
         /// <inheritdoc/>
         public void Dispose() => PlatformImpl?.Dispose();
 
-        /// <summary>
-        /// Moves the Popups position so that it doesnt overlap screen edges.
-        /// This method can be called immediately after Show has been called.
-        /// </summary>
-        public void SnapInsideScreenEdges()
+        private void UpdatePosition()
         {
-            var screen = (VisualRoot as WindowBase)?.Screens?.ScreenFromPoint(Position);
-
-            if (screen != null)
-            {
-                var scaling = VisualRoot.RenderScaling;
-                var bounds = PixelRect.FromRect(Bounds, scaling);
-                var screenX = Position.X + bounds.Width - screen.Bounds.X;
-                var screenY = Position.Y + bounds.Height - screen.Bounds.Y;
-
-                if (screenX > screen.Bounds.Width)
-                {
-                    Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width));
-                }
-
-                if (screenY > screen.Bounds.Height)
-                {
-                    Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height));
-                }
-            }
+            PlatformImpl?.PopupPositioner.Update(_positionerParameters);
         }
 
-        /// <inheritdoc/>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset,
+            PopupPositioningEdge anchor = PopupPositioningEdge.None,
+            PopupPositioningEdge gravity = PopupPositioningEdge.None)
         {
-            base.OnTemplateApplied(e);
+            _positionerParameters.ConfigurePosition(_parent, target,
+                placement, offset, anchor, gravity);
+
+            if (_positionerParameters.Size != default)
+                UpdatePosition();
+        }
+
+        public void SetChild(IControl control) => Content = control;
 
-            if (Parent?.TemplatedParent != null)
+        IVisual IPopupHost.HostedVisualTreeRoot => this;
+        
+        public IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty, StyledProperty<double> minWidthProperty,
+            StyledProperty<double> maxWidthProperty, StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
+            StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty)
+        {
+            var bindings = new List<IDisposable>();
+
+            void Bind(AvaloniaProperty what, AvaloniaProperty to) => bindings.Add(this.Bind(what, popup[~to]));
+            Bind(WidthProperty, widthProperty);
+            Bind(MinWidthProperty, minWidthProperty);
+            Bind(MaxWidthProperty, maxWidthProperty);
+            Bind(HeightProperty, heightProperty);
+            Bind(MinHeightProperty, minHeightProperty);
+            Bind(MaxHeightProperty, maxHeightProperty);
+            Bind(TopmostProperty, topmostProperty);
+            return Disposable.Create(() =>
             {
-                if (_presenterSubscription != null)
-                {
-                    _presenterSubscription.Dispose();
-                    _presenterSubscription = null;
-                }
-
-                Presenter?.ApplyTemplate();
-                Presenter?.GetObservable(ContentPresenter.ChildProperty)
-                    .Subscribe(SetTemplatedParentAndApplyChildTemplates);
-            }
+                foreach (var x in bindings)
+                    x.Dispose();
+            });
         }
 
-        private void SetTemplatedParentAndApplyChildTemplates(IControl control)
+        /// <summary>
+        /// Carries out the arrange pass of the window.
+        /// </summary>
+        /// <param name="finalSize">The final window size.</param>
+        /// <returns>The <paramref name="finalSize"/> parameter unchanged.</returns>
+        protected override Size ArrangeOverride(Size finalSize)
         {
-            if (control != null)
+            using (BeginAutoSizing())
             {
-                var templatedParent = Parent.TemplatedParent;
-
-                if (control.TemplatedParent == null)
-                {
-                    control.SetValue(TemplatedParentProperty, templatedParent);
-                }
-
-                control.ApplyTemplate();
-
-                if (!(control is IPresenter) && control.TemplatedParent == templatedParent)
-                {
-                    foreach (IControl child in control.GetVisualChildren())
-                    {
-                        SetTemplatedParentAndApplyChildTemplates(child);
-                    }
-                }
+                _positionerParameters.Size = finalSize;
+                UpdatePosition();
             }
+
+            return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
         }
     }
 }

+ 3 - 2
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@@ -979,13 +979,14 @@ namespace Avalonia.Controls.Primitives
             }
 
             var item = ElementAt(Items, index);
+            var itemChanged = !Equals(item, oldItem);
             var added = -1;
             HashSet<int> removed = null;
 
             _selectedIndex = index;
             _selectedItem = item;
 
-            if (oldIndex != index || _selection.HasMultiple)
+            if (oldIndex != index || itemChanged || _selection.HasMultiple)
             {
                 if (clear)
                 {
@@ -1022,7 +1023,7 @@ namespace Avalonia.Controls.Primitives
                     index);
             }
 
-            if (!Equals(item, oldItem))
+            if (itemChanged)
             {
                 RaisePropertyChanged(
                     SelectedItemProperty,

+ 93 - 0
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@@ -0,0 +1,93 @@
+using System.Collections.Generic;
+using Avalonia.LogicalTree;
+using Avalonia.Styling;
+
+namespace Avalonia.Controls.Primitives
+{
+    public class VisualLayerManager : Decorator
+    {
+        private const int AdornerZIndex = int.MaxValue - 100;
+        private const int OverlayZIndex = int.MaxValue - 99;
+        private IStyleHost _styleRoot;
+        private readonly List<Control> _layers = new List<Control>();
+        
+
+        public bool IsPopup { get; set; }
+        
+        public AdornerLayer AdornerLayer
+        {
+            get
+            {
+                var rv = FindLayer<AdornerLayer>();
+                if (rv == null)
+                    AddLayer(rv = new AdornerLayer(), AdornerZIndex);
+                return rv;
+            }
+        }
+
+        public OverlayLayer OverlayLayer
+        {
+            get
+            {
+                if (IsPopup)
+                    return null;
+                var rv = FindLayer<OverlayLayer>();
+                if(rv == null)
+                    AddLayer(rv = new OverlayLayer(), OverlayZIndex);
+                return rv;
+            }
+        }
+
+        T FindLayer<T>() where T : class
+        {
+            foreach (var layer in _layers)
+                if (layer is T match)
+                    return match;
+            return null;
+        }
+
+        void AddLayer(Control layer, int zindex)
+        {
+            _layers.Add(layer);
+            ((ISetLogicalParent)layer).SetParent(this);
+            layer.ZIndex = zindex;
+            VisualChildren.Add(layer);
+            if (((ILogical)this).IsAttachedToLogicalTree)
+                ((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_styleRoot));
+            InvalidateArrange();
+        }
+        
+        
+        protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            base.OnAttachedToLogicalTree(e);
+            _styleRoot = e.Root;
+
+            foreach (var l in _layers)
+                ((ILogical)l).NotifyAttachedToLogicalTree(e);
+        }
+
+        protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            _styleRoot = null;
+            base.OnDetachedFromLogicalTree(e);
+            foreach (var l in _layers)
+                ((ILogical)l).NotifyDetachedFromLogicalTree(e);
+        }
+
+
+        protected override Size MeasureOverride(Size availableSize)
+        {
+            foreach (var l in _layers)
+                l.Measure(availableSize);
+            return base.MeasureOverride(availableSize);
+        }
+
+        protected override Size ArrangeOverride(Size finalSize)
+        {
+            foreach (var l in _layers)
+                l.Arrange(new Rect(finalSize));
+            return base.ArrangeOverride(finalSize);
+        }
+    }
+}

+ 14 - 10
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@@ -57,9 +57,13 @@ namespace Avalonia.Controls.Remote.Server
             }
         }
 
-        private static InputModifiers GetAvaloniaInputModifiers (Avalonia.Remote.Protocol.Input.InputModifiers[] modifiers)
+        private static RawInputModifiers GetAvaloniaRawInputModifiers(
+            Avalonia.Remote.Protocol.Input.InputModifiers[] modifiers)
+            => (RawInputModifiers)GetAvaloniaInputModifiers(modifiers);
+        
+        private static RawInputModifiers GetAvaloniaInputModifiers (Avalonia.Remote.Protocol.Input.InputModifiers[] modifiers)
         {
-            var result = InputModifiers.None;
+            var result = RawInputModifiers.None;
 
             if (modifiers == null)
             {
@@ -71,31 +75,31 @@ namespace Avalonia.Controls.Remote.Server
                 switch (modifier)
                 {
                     case Avalonia.Remote.Protocol.Input.InputModifiers.Control:
-                        result |= InputModifiers.Control;
+                        result |= RawInputModifiers.Control;
                         break;
 
                     case Avalonia.Remote.Protocol.Input.InputModifiers.Alt:
-                        result |= InputModifiers.Alt;
+                        result |= RawInputModifiers.Alt;
                         break;
 
                     case Avalonia.Remote.Protocol.Input.InputModifiers.Shift:
-                        result |= InputModifiers.Shift;
+                        result |= RawInputModifiers.Shift;
                         break;
 
                     case Avalonia.Remote.Protocol.Input.InputModifiers.Windows:
-                        result |= InputModifiers.Windows;
+                        result |= RawInputModifiers.Meta;
                         break;
 
                     case Avalonia.Remote.Protocol.Input.InputModifiers.LeftMouseButton:
-                        result |= InputModifiers.LeftMouseButton;
+                        result |= RawInputModifiers.LeftMouseButton;
                         break;
 
                     case Avalonia.Remote.Protocol.Input.InputModifiers.MiddleMouseButton:
-                        result |= InputModifiers.MiddleMouseButton;
+                        result |= RawInputModifiers.MiddleMouseButton;
                         break;
 
                     case Avalonia.Remote.Protocol.Input.InputModifiers.RightMouseButton:
-                        result |= InputModifiers.RightMouseButton;
+                        result |= RawInputModifiers.RightMouseButton;
                         break;
                 }
             }
@@ -225,7 +229,7 @@ namespace Avalonia.Controls.Remote.Server
                             0,
                             key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
                             (Key)key.Key,
-                            GetAvaloniaInputModifiers(key.Modifiers)));
+                            GetAvaloniaRawInputModifiers(key.Modifiers)));
                     }, DispatcherPriority.Input);
                 }
                 if(obj is TextInputEventMessage text)

+ 2 - 2
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@@ -707,9 +707,9 @@ namespace Avalonia.Controls
             }
         }
 
-        private void InvalidateArrangeForLayout(object sender, EventArgs e) => InvalidateMeasure();
+        private void InvalidateMeasureForLayout(object sender, EventArgs e) => InvalidateMeasure();
 
-        private void InvalidateMeasureForLayout(object sender, EventArgs e) => InvalidateArrange();
+        private void InvalidateArrangeForLayout(object sender, EventArgs e) => InvalidateArrange();
 
         private VirtualizingLayoutContext GetLayoutContext()
         {

+ 5 - 3
src/Avalonia.Controls/Repeater/ItemsSourceView.cs

@@ -35,9 +35,11 @@ namespace Avalonia.Controls
         {
             Contract.Requires<ArgumentNullException>(source != null);
 
-            _inner = source as IList;
-
-            if (_inner == null && source is IEnumerable<object> objectEnumerable)
+            if (source is IList list)
+            {
+                _inner = list;
+            }
+            else if (source is IEnumerable<object> objectEnumerable)
             {
                 _inner = new List<object>(objectEnumerable);
             }

+ 13 - 3
src/Avalonia.Controls/SystemDialog.cs

@@ -14,7 +14,13 @@ namespace Avalonia.Controls
 
     public abstract class FileSystemDialog : SystemDialog
     {
-        public string InitialDirectory { get; set; }
+        [Obsolete("Use Directory")]
+        public string InitialDirectory
+        {
+            get => Directory;
+            set => Directory = value;
+        }
+        public string Directory { get; set; }
     }
 
     public class SaveFileDialog : FileDialog
@@ -45,8 +51,12 @@ namespace Avalonia.Controls
 
     public class OpenFolderDialog : FileSystemDialog
     {
-        public string DefaultDirectory { get; set; }
-
+        [Obsolete("Use Directory")]
+        public string DefaultDirectory
+        {
+            get => Directory;
+            set => Directory = value;
+        }
         public Task<string> ShowAsync(Window parent)
         {
             if(parent == null)

+ 11 - 2
src/Avalonia.Controls/TextBox.cs

@@ -59,6 +59,9 @@ namespace Avalonia.Controls
                 o => o.SelectionEnd,
                 (o, v) => o.SelectionEnd = v);
 
+        public static readonly StyledProperty<int> MaxLengthProperty =
+            AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0);
+
         public static readonly DirectProperty<TextBox, string> TextProperty =
             TextBlock.TextProperty.AddOwner<TextBox>(
                 o => o.Text,
@@ -232,6 +235,12 @@ namespace Avalonia.Controls
             }
         }
 
+        public int MaxLength
+        {
+            get { return GetValue(MaxLengthProperty); }
+            set { SetValue(MaxLengthProperty, value); }
+        }
+
         [Content]
         public string Text
         {
@@ -318,7 +327,7 @@ namespace Avalonia.Controls
 
         private void DecideCaretVisibility()
         {
-            _presenter.ShowCaret();
+            _presenter?.ShowCaret();
         }
 
         protected override void OnLostFocus(RoutedEventArgs e)
@@ -345,7 +354,7 @@ namespace Avalonia.Controls
                 input = RemoveInvalidCharacters(input);
                 string text = Text ?? string.Empty;
                 int caretIndex = CaretIndex;
-                if (!string.IsNullOrEmpty(input))
+                if (!string.IsNullOrEmpty(input) && (MaxLength == 0 || input.Length + text.Length - (Math.Abs(SelectionStart - SelectionEnd)) <= MaxLength))
                 {
                     DeleteSelection();
                     caretIndex = CaretIndex;

+ 8 - 6
src/Avalonia.Controls/ToolTip.cs

@@ -4,6 +4,7 @@
 using System;
 using System.Reactive.Linq;
 using Avalonia.Controls.Primitives;
+using Avalonia.VisualTree;
 
 namespace Avalonia.Controls
 {
@@ -60,7 +61,7 @@ namespace Avalonia.Controls
         private static readonly AttachedProperty<ToolTip> ToolTipProperty =
             AvaloniaProperty.RegisterAttached<ToolTip, Control, ToolTip>("ToolTip");
 
-        private PopupRoot _popup;
+        private IPopupHost _popup;
 
         /// <summary>
         /// Initializes static members of the <see cref="ToolTip"/> class.
@@ -234,19 +235,20 @@ namespace Avalonia.Controls
         {
             Close();
 
-            _popup = new PopupRoot { Content = this,  };
+            _popup = OverlayPopupHost.CreatePopupHost(control, null);
+            _popup.SetChild(this);
             ((ISetLogicalParent)_popup).SetParent(control);
-            _popup.Position = Popup.GetPosition(control, GetPlacement(control), _popup,
-                GetHorizontalOffset(control), GetVerticalOffset(control));
+            
+            _popup.ConfigurePosition(control, GetPlacement(control), 
+                new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
             _popup.Show();
-            _popup.SnapInsideScreenEdges();
         }
 
         private void Close()
         {
             if (_popup != null)
             {
-                _popup.Content = null;
+                _popup.SetChild(null);
                 _popup.Hide();
                 _popup = null;
             }

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

@@ -269,8 +269,8 @@ namespace Avalonia.Controls
         /// </summary>
         protected virtual void HandleClosed()
         {
+            (this as IInputRoot).MouseDevice?.TopLevelClosed(this);
             PlatformImpl = null;
-
             Closed?.Invoke(this, EventArgs.Empty);
             Renderer?.Dispose();
             Renderer = null;

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

@@ -105,11 +105,13 @@ namespace Avalonia.Controls
             get => _selectedItem;
             set
             {
+                var selectedItems = SelectedItems;
+
                 SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
 
                 if (value != null)
                 {
-                    if (SelectedItems.Count != 1 || SelectedItems[0] != value)
+                    if (selectedItems.Count != 1 || selectedItems[0] != value)
                     {
                         _syncingSelectedItems = true;
                         SelectSingleItem(value);

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