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

Merge branch 'master' into refactor/remove-property-initialized

Steven Kirk 5 жил өмнө
parent
commit
5d48222418
100 өөрчлөгдсөн 4348 нэмэгдсэн , 561 устгасан
  1. 27 0
      Avalonia.sln
  2. 10 2
      azure-pipelines.yml
  3. 2 2
      build/HarfBuzzSharp.props
  4. 1 1
      build/SharedVersion.props
  5. 2 2
      build/SkiaSharp.props
  6. 16 2
      native/Avalonia.Native/inc/avalonia-native.h
  7. 11 3
      native/Avalonia.Native/src/OSX/app.mm
  8. 16 8
      native/Avalonia.Native/src/OSX/gl.mm
  9. 2 0
      native/Avalonia.Native/src/OSX/window.h
  10. 153 29
      native/Avalonia.Native/src/OSX/window.mm
  11. 1 0
      nukebuild/Build.cs
  12. 1 1
      readme.md
  13. 1 3
      samples/ControlCatalog/App.xaml.cs
  14. 5 1
      samples/ControlCatalog/MainView.xaml
  15. 3 1
      samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
  16. 46 39
      samples/ControlCatalog/Pages/ImagePage.xaml
  17. 20 19
      samples/ControlCatalog/Pages/ImagePage.xaml.cs
  18. 3 0
      samples/RenderDemo/MainWindow.xaml
  19. 14 0
      samples/RenderDemo/Pages/GlyphRunPage.xaml
  20. 80 0
      samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
  21. 1 1
      samples/RenderDemo/Pages/RenderTargetBitmapPage.cs
  22. 0 64
      scripts/avalonia-rename.ps1
  23. 2 2
      src/Avalonia.Animation/Animatable.cs
  24. 1 1
      src/Avalonia.Animation/IterationCount.cs
  25. 5 3
      src/Avalonia.Base/AttachedProperty.cs
  26. 2 93
      src/Avalonia.Base/AvaloniaObject.cs
  27. 15 9
      src/Avalonia.Base/AvaloniaProperty.cs
  28. 2 2
      src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs
  29. 10 2
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  30. 20 0
      src/Avalonia.Base/Collections/AvaloniaList.cs
  31. 40 0
      src/Avalonia.Base/Collections/Pooled/ClearMode.cs
  32. 31 0
      src/Avalonia.Base/Collections/Pooled/ICollectionDebugView.cs
  33. 21 0
      src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs
  34. 1531 0
      src/Avalonia.Base/Collections/Pooled/PooledList.cs
  35. 699 0
      src/Avalonia.Base/Collections/Pooled/PooledStack.cs
  36. 28 0
      src/Avalonia.Base/Collections/Pooled/StackDebugView.cs
  37. 691 0
      src/Avalonia.Base/Collections/Pooled/ThrowHelper.cs
  38. 6 0
      src/Avalonia.Base/Data/BindingNotification.cs
  39. 17 3
      src/Avalonia.Base/Data/BindingOperations.cs
  40. 28 7
      src/Avalonia.Base/Data/BindingValue.cs
  41. 12 1
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  42. 3 3
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  43. 17 3
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  44. 11 3
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  45. 26 6
      src/Avalonia.Base/Data/Optional.cs
  46. 1 1
      src/Avalonia.Base/IAvaloniaObject.cs
  47. 15 0
      src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs
  48. 7 0
      src/Avalonia.Base/PropertyStore/BindingEntry.cs
  49. 6 1
      src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs
  50. 7 0
      src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs
  51. 7 0
      src/Avalonia.Base/PropertyStore/IValue.cs
  52. 4 2
      src/Avalonia.Base/PropertyStore/IValueSink.cs
  53. 5 0
      src/Avalonia.Base/PropertyStore/LocalValueEntry.cs
  54. 11 0
      src/Avalonia.Base/PropertyStore/PriorityValue.cs
  55. 2 0
      src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs
  56. 10 1
      src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
  57. 2 2
      src/Avalonia.Base/Reactive/BindingValueExtensions.cs
  58. 19 6
      src/Avalonia.Base/Reactive/LightweightObservableBase.cs
  59. 1 14
      src/Avalonia.Base/StyledPropertyMetadata`1.cs
  60. 1 1
      src/Avalonia.Base/Utilities/IdentifierParser.cs
  61. 48 0
      src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs
  62. 16 16
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  63. 94 0
      src/Avalonia.Base/Utilities/ValueSingleOrList.cs
  64. 28 25
      src/Avalonia.Base/ValueStore.cs
  65. 1 1
      src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs
  66. 1 1
      src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs
  67. 1 1
      src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs
  68. 20 1
      src/Avalonia.Controls/Application.cs
  69. 1 1
      src/Avalonia.Controls/Button.cs
  70. 1 1
      src/Avalonia.Controls/ButtonSpinner.cs
  71. 5 1
      src/Avalonia.Controls/DefinitionBase.cs
  72. 3 1
      src/Avalonia.Controls/DrawingPresenter.cs
  73. 1 1
      src/Avalonia.Controls/Expander.cs
  74. 5 0
      src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs
  75. 32 11
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  76. 6 6
      src/Avalonia.Controls/GridSplitter.cs
  77. 27 18
      src/Avalonia.Controls/Image.cs
  78. 14 11
      src/Avalonia.Controls/ItemsControl.cs
  79. 2 2
      src/Avalonia.Controls/LayoutTransformControl.cs
  80. 1 1
      src/Avalonia.Controls/ListBox.cs
  81. 7 10
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  82. 17 13
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  83. 1 1
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  84. 2 2
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  85. 1 1
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  86. 1 4
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  87. 2 5
      src/Avalonia.Controls/Primitives/OverlayLayer.cs
  88. 1 1
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  89. 15 10
      src/Avalonia.Controls/Primitives/RangeBase.cs
  90. 1 1
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  91. 40 16
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  92. 124 1
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  93. 1 1
      src/Avalonia.Controls/Primitives/Track.cs
  94. 4 5
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  95. 2 2
      src/Avalonia.Controls/ProgressBar.cs
  96. 1 1
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  97. 27 25
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  98. 61 16
      src/Avalonia.Controls/Repeater/ViewManager.cs
  99. 1 1
      src/Avalonia.Controls/Slider.cs
  100. 1 2
      src/Avalonia.Controls/Templates/FuncDataTemplate.cs

+ 27 - 0
Avalonia.sln

@@ -204,6 +204,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
+EndProject
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@@ -1895,6 +1897,30 @@ Global
 		{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
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.Build.0 = Release|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -1951,6 +1977,7 @@ Global
 		{41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
 		{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+		{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

+ 10 - 2
azure-pipelines.yml

@@ -34,9 +34,17 @@ jobs:
   pool:
     vmImage: 'macOS-10.14'
   steps:
-  - task: DotNetCoreInstaller@0
+  - task: UseDotNet@2
+    displayName: 'Use .NET Core SDK 3.0.x'
     inputs:
-      version: '2.1.403'
+      packageType: sdk
+      version: 3.0.x
+
+  - task: UseDotNet@2
+    displayName: 'Use .NET Core Runtime 2.1.x'
+    inputs:
+      packageType: runtime
+      version: 2.1.x
 
   - task: CmdLine@2
     displayName: 'Install Mono 5.18'

+ 2 - 2
build/HarfBuzzSharp.props

@@ -1,6 +1,6 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="HarfBuzzSharp" Version="2.6.1-rc.153" />
-    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1-rc.153" />
+    <PackageReference Include="HarfBuzzSharp" Version="2.6.1" />
+    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1" />
   </ItemGroup>
 </Project>

+ 1 - 1
build/SharedVersion.props

@@ -2,7 +2,7 @@
   xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
     <Product>Avalonia</Product>
-    <Version>0.8.999</Version>
+    <Version>0.9.999</Version>
     <Copyright>Copyright 2019 &#169; The AvaloniaUI Project</Copyright>
     <PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
     <RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>

+ 2 - 2
build/SkiaSharp.props

@@ -1,6 +1,6 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="SkiaSharp" Version="1.68.1-rc.153" />
-    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1-rc.153" />
+    <PackageReference Include="SkiaSharp" Version="1.68.1" />
+    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1" />
   </ItemGroup>
 </Project>

+ 16 - 2
native/Avalonia.Native/inc/avalonia-native.h

@@ -92,9 +92,17 @@ enum AvnRawMouseEventType
     RightButtonUp,
     MiddleButtonDown,
     MiddleButtonUp,
+    XButton1Down,
+    XButton1Up,
+    XButton2Down,
+    XButton2Up,
     Move,
     Wheel,
-    NonClientLeftButtonDown
+    NonClientLeftButtonDown,
+    TouchBegin,
+    TouchUpdate,
+    TouchEnd,
+    TouchCancel
 };
 
 enum AvnRawKeyEventType
@@ -112,7 +120,9 @@ enum AvnInputModifiers
     Windows = 8,
     LeftMouseButton = 16,
     RightMouseButton = 32,
-    MiddleMouseButton = 64
+    MiddleMouseButton = 64,
+    XButton1MouseButton = 128,
+    XButton2MouseButton = 256
 };
 
 enum AvnWindowState
@@ -212,6 +222,10 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
     virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) = 0;
     virtual HRESULT SetMainMenu(IAvnAppMenu* menu) = 0;
     virtual HRESULT ObtainMainMenu(IAvnAppMenu** retOut) = 0;
+    virtual HRESULT ObtainNSWindowHandle(void** retOut) = 0;
+    virtual HRESULT ObtainNSWindowHandleRetained(void** retOut) = 0;
+    virtual HRESULT ObtainNSViewHandle(void** retOut) = 0;
+    virtual HRESULT ObtainNSViewHandleRetained(void** retOut) = 0;
     virtual bool TryLock() = 0;
     virtual void Unlock() = 0;
 };

+ 11 - 3
native/Avalonia.Native/src/OSX/app.mm

@@ -1,16 +1,25 @@
 #include "common.h"
 @interface AvnAppDelegate : NSObject<NSApplicationDelegate>
 @end
+
 extern NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
 @implementation AvnAppDelegate
 - (void)applicationWillFinishLaunching:(NSNotification *)notification
 {
-    [[NSApplication sharedApplication] setActivationPolicy: AvnDesiredActivationPolicy];
+    if([[NSApplication sharedApplication] activationPolicy] != AvnDesiredActivationPolicy)
+    {
+        for (NSRunningApplication * app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) {
+            [app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
+            break;
+        }
+        
+        [[NSApplication sharedApplication] setActivationPolicy: AvnDesiredActivationPolicy];
+    }
 }
 
 - (void)applicationDidFinishLaunching:(NSNotification *)notification
 {
-    [NSApp activateIgnoringOtherApps:true];
+    [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
 }
 
 @end
@@ -20,5 +29,4 @@ extern void InitializeAvnApp()
     NSApplication* app = [NSApplication sharedApplication];
     id delegate = [AvnAppDelegate new];
     [app setDelegate:delegate];
-    
 }

+ 16 - 8
native/Avalonia.Native/src/OSX/gl.mm

@@ -1,6 +1,7 @@
 #include "common.h"
 #include <OpenGL/gl.h>
 #include <dlfcn.h>
+#include "window.h"
 
 template <typename T, size_t N> char (&ArrayCounter(T (&a)[N]))[N];
 #define ARRAY_COUNT(a) (sizeof(ArrayCounter(a)))
@@ -181,12 +182,12 @@ extern IAvnGlFeature* GetGlFeature()
 
 class AvnGlRenderingSession : public ComSingleObject<IAvnGlSurfaceRenderingSession, &IID_IAvnGlSurfaceRenderingSession>
 {
-    NSView* _view;
-    NSWindow* _window;
+    AvnView* _view;
+    AvnWindow* _window;
     NSOpenGLContext* _context;
 public:
     FORWARD_IUNKNOWN()
-    AvnGlRenderingSession(NSWindow*window, NSView* view, NSOpenGLContext* context)
+    AvnGlRenderingSession(AvnWindow*window, AvnView* view, NSOpenGLContext* context)
     {
         _context = context;
         _window = window;
@@ -195,14 +196,12 @@ public:
     
     virtual HRESULT GetPixelSize(AvnPixelSize* ret)  override
     {
-        auto fsize = [_view convertSizeToBacking: [_view frame].size];
-        ret->Width = (int)fsize.width;
-        ret->Height = (int)fsize.height;
+        *ret = [_view getPixelSize];
         return S_OK;
     }
     virtual HRESULT GetScaling(double* ret)  override
     {
-        *ret = [_window backingScaleFactor];
+        *ret = [_window getScaling];
         return S_OK;
     }
     
@@ -234,8 +233,17 @@ public:
         auto f = GetFeature();
         if(f == NULL)
             return E_FAIL;
-        if(![_view lockFocusIfCanDraw])
+        
+        @try
+        {
+            if(![_view lockFocusIfCanDraw])
+                return E_ABORT;
+        }
+        @catch(NSException* exception)
+        {
             return E_ABORT;
+        }
+        
         
         auto gl = _context;
         CGLLockContext([_context CGLContextObj]);

+ 2 - 0
native/Avalonia.Native/src/OSX/window.h

@@ -12,6 +12,7 @@ class WindowBaseImpl;
 -(AvnPoint) translateLocalPoint:(AvnPoint)pt;
 -(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose;
 -(void) onClosed;
+-(AvnPixelSize) getPixelSize;
 @end
 
 @interface AvnWindow : NSWindow <NSWindowDelegate>
@@ -22,6 +23,7 @@ class WindowBaseImpl;
 -(void) restoreParentWindow;
 -(bool) shouldTryToHandleEvents;
 -(void) applyMenu:(NSMenu *)menu;
+-(double) getScaling;
 @end
 
 struct INSWindowHolder

+ 153 - 29
native/Avalonia.Native/src/OSX/window.mm

@@ -83,6 +83,54 @@ public:
         [Window setContentView: View];
     }
     
+    virtual HRESULT ObtainNSWindowHandle(void** ret) override
+    {
+        if (ret == nullptr)
+        {
+            return E_POINTER;
+        }
+        
+        *ret =  (__bridge void*)Window;
+        
+        return S_OK;
+    }
+    
+    virtual HRESULT ObtainNSWindowHandleRetained(void** ret) override
+    {
+        if (ret == nullptr)
+        {
+            return E_POINTER;
+        }
+        
+        *ret =  (__bridge_retained void*)Window;
+        
+        return S_OK;
+    }
+    
+    virtual HRESULT ObtainNSViewHandle(void** ret) override
+    {
+        if (ret == nullptr)
+        {
+            return E_POINTER;
+        }
+        
+        *ret =  (__bridge void*)View;
+        
+        return S_OK;
+    }
+    
+    virtual HRESULT ObtainNSViewHandleRetained(void** ret) override
+    {
+        if (ret == nullptr)
+        {
+            return E_POINTER;
+        }
+        
+        *ret =  (__bridge_retained void*)View;
+        
+        return S_OK;
+    }
+    
     virtual AvnWindow* GetNSWindow() override
     {
         return Window;
@@ -147,7 +195,11 @@ public:
     {
         @autoreleasepool
         {
-            [Window close];
+            if (Window != nullptr)
+            {
+                [Window close];
+            }
+            
             return S_OK;
         }
     }
@@ -243,7 +295,14 @@ public:
     {
         @autoreleasepool
         {
-            return [View lockFocusIfCanDraw] == YES;
+            @try
+            {
+                return [View lockFocusIfCanDraw] == YES;
+            }
+            @catch (NSException*)
+            {
+                return NO;
+            }
         }
     }
     
@@ -668,18 +727,36 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     AvnFramebuffer _swRenderedFrameBuffer;
     bool _queuedDisplayFromThread;
     NSTrackingArea* _area;
-    bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isMouseOver;
+    bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed, _isMouseOver;
     NSEvent* _lastMouseDownEvent;
     bool _lastKeyHandled;
+    AvnPixelSize _lastPixelSize;
 }
 
-- (void)dealloc
+- (void)onClosed
 {
+    @synchronized (self)
+    {
+        _parent = nullptr;
+    }
 }
 
-- (void)onClosed
+- (BOOL)lockFocusIfCanDraw
 {
-    _parent = NULL;
+    @synchronized (self)
+    {
+        if(_parent == nullptr)
+        {
+            return NO;
+        }
+    }
+    
+    return [super lockFocusIfCanDraw];
+}
+
+-(AvnPixelSize) getPixelSize
+{
+    return _lastPixelSize;
 }
 
 - (NSEvent*) lastMouseDownEvent
@@ -691,8 +768,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
 {
     self = [super init];
     [self setWantsBestResolutionOpenGLSurface:true];
+    [self setWantsLayer:YES];
     _parent = parent;
     _area = nullptr;
+    _lastPixelSize.Height = 100;
+    _lastPixelSize.Width = 100;
     return self;
 }
 
@@ -734,6 +814,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     [self addTrackingArea:_area];
     
     _parent->UpdateCursor();
+    
+    auto fsize = [self convertSizeToBacking: [self frame].size];
+    _lastPixelSize.Width = (int)fsize.width;
+    _lastPixelSize.Height = (int)fsize.height;
 
     _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
 }
@@ -763,7 +847,13 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
 
 - (void)drawRect:(NSRect)dirtyRect
 {
+    if (_parent == nullptr)
+    {
+        return;
+    }
+        
     _parent->BaseEvents->RunRenderPriorityJobs();
+    
     @synchronized (self) {
         if(_swRenderedFrame != NULL)
         {
@@ -830,7 +920,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
 
 - (void) viewDidChangeBackingProperties
 {
+    auto fsize = [self convertSizeToBacking: [self frame].size];
+    _lastPixelSize.Width = (int)fsize.width;
+    _lastPixelSize.Height = (int)fsize.height;
+    
     _parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]);
+    
     [super viewDidChangeBackingProperties];
 }
 
@@ -893,9 +988,23 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
 
 - (void)otherMouseDown:(NSEvent *)event
 {
-    _isMiddlePressed = true;
     _lastMouseDownEvent = event;
-    [self mouseEvent:event withType:MiddleButtonDown];
+
+    switch(event.buttonNumber)
+    {
+        case 3:
+            _isMiddlePressed = true;
+            [self mouseEvent:event withType:MiddleButtonDown];
+            break;
+        case 4:
+            _isXButton1Pressed = true;
+            [self mouseEvent:event withType:XButton1Down];
+            break;
+        case 5:
+            _isXButton2Pressed = true;
+            [self mouseEvent:event withType:XButton2Down];
+            break;
+    }
 }
 
 - (void)rightMouseDown:(NSEvent *)event
@@ -913,8 +1022,21 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
 
 - (void)otherMouseUp:(NSEvent *)event
 {
-    _isMiddlePressed = false;
-    [self mouseEvent:event withType:MiddleButtonUp];
+    switch(event.buttonNumber)
+    {
+        case 3:
+            _isMiddlePressed = false;
+            [self mouseEvent:event withType:MiddleButtonUp];
+            break;
+        case 4:
+            _isXButton1Pressed = false;
+            [self mouseEvent:event withType:XButton1Up];
+            break;
+        case 5:
+            _isXButton2Pressed = false;
+            [self mouseEvent:event withType:XButton2Up];
+            break;
+    }
 }
 
 - (void)rightMouseUp:(NSEvent *)event
@@ -1013,6 +1135,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
         rv |= MiddleMouseButton;
     if (_isRightPressed)
         rv |= RightMouseButton;
+    if (_isXButton1Pressed)
+        rv |= XButton1MouseButton;
+    if (_isXButton2Pressed)
+        rv |= XButton2MouseButton;
     
     return (AvnInputModifiers)rv;
 }
@@ -1081,6 +1207,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     bool _closed;
     NSMenu* _menu;
     bool _isAppMenuApplied;
+    double _lastScaling;
+}
+
+-(double) getScaling
+{
+    return _lastScaling;
 }
 
 +(void)closeAll
@@ -1094,10 +1226,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     }
 }
 
-- (void)dealloc
-{
-}
-
 - (void)pollModalSession:(nonnull NSModalSession)session
 {
     auto response = [NSApp runModalSession:session];
@@ -1152,6 +1280,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     [self setReleasedWhenClosed:false];
     _parent = parent;
     [self setDelegate:self];
+    _closed = false;
+    
+    _lastScaling = [self backingScaleFactor];
+    [self setOpaque:NO];
+    [self setBackgroundColor: [NSColor clearColor]];
+    [self invalidateShadow];
     return self;
 }
 
@@ -1167,6 +1301,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     return true;
 }
 
+- (void)windowDidChangeBackingProperties:(NSNotification *)notification
+{
+    _lastScaling = [self backingScaleFactor];
+}
+
 - (void)windowWillClose:(NSNotification *)notification
 {
     _closed = true;
@@ -1177,9 +1316,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
         [self restoreParentWindow];
         parent->BaseEvents->Closed();
         [parent->View onClosed];
-        dispatch_async(dispatch_get_main_queue(), ^{
-            [self setContentView: nil];
-        });
     }
 }
 
@@ -1326,18 +1462,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     _parent->GetPosition(&position);
     _parent->BaseEvents->PositionChanged(position);
 }
-
-// TODO this breaks resizing.
-/*- (void)windowDidResize:(NSNotification *)notification
-{
-    
-    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
-    
-    if(parent != nullptr)
-    {
-        parent->WindowStateChanged();
-    }
-}*/
 @end
 
 class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup

+ 1 - 0
nukebuild/Build.cs

@@ -191,6 +191,7 @@ partial class Build : NukeBuild
             RunCoreTest("./tests/Avalonia.Animation.UnitTests");
             RunCoreTest("./tests/Avalonia.Base.UnitTests");
             RunCoreTest("./tests/Avalonia.Controls.UnitTests");
+            RunCoreTest("./tests/Avalonia.Controls.DataGrid.UnitTests");
             RunCoreTest("./tests/Avalonia.Input.UnitTests");
             RunCoreTest("./tests/Avalonia.Interactivity.UnitTests");
             RunCoreTest("./tests/Avalonia.Layout.UnitTests");

+ 1 - 1
readme.md

@@ -22,7 +22,7 @@ Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?it
 
 For those without Visual Studio, a starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core).
 
-Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: ([stable(ish)](https://www.nuget.org/packages/Avalonia/), [nightly](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed))
+Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: [stable(ish)](https://www.nuget.org/packages/Avalonia/)
 
 Use these commands in the Package Manager console to install Avalonia manually:
 ```

+ 1 - 3
samples/ControlCatalog/App.xaml.cs

@@ -1,6 +1,4 @@
-using System;
 using Avalonia;
-using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Markup.Xaml;
 
@@ -19,7 +17,7 @@ namespace ControlCatalog
                 desktopLifetime.MainWindow = new MainWindow();
             else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
                 singleViewLifetime.MainView = new MainView();
-            
+
             base.OnFrameworkInitializationCompleted();
         }
     }

+ 5 - 1
samples/ControlCatalog/MainView.xaml

@@ -32,7 +32,11 @@
       <TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem>
       <TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
       <TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
-      <TabItem Header="Image"><pages:ImagePage/></TabItem>
+      <TabItem Header="Image"
+               ScrollViewer.VerticalScrollBarVisibility="Disabled"
+               ScrollViewer.HorizontalScrollBarVisibility="Disabled">
+        <pages:ImagePage/>
+      </TabItem>
       <TabItem Header="ItemsRepeater"
                ScrollViewer.VerticalScrollBarVisibility="Disabled"
                ScrollViewer.HorizontalScrollBarVisibility="Disabled">

+ 3 - 1
samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs

@@ -1,5 +1,7 @@
+using System.Linq;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
+using Avalonia.Media;
 
 namespace ControlCatalog.Pages
 {
@@ -14,7 +16,7 @@ namespace ControlCatalog.Pages
         {
             AvaloniaXamlLoader.Load(this);
             var fontComboBox = this.Find<ComboBox>("fontComboBox");
-            fontComboBox.Items = Avalonia.Media.FontFamily.SystemFontFamilies;
+            fontComboBox.Items = FontManager.Current.GetInstalledFontFamilyNames().Select(x => new FontFamily(x));
             fontComboBox.SelectedIndex = 0;
         }
     }

+ 46 - 39
samples/ControlCatalog/Pages/ImagePage.xaml

@@ -1,45 +1,52 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              x:Class="ControlCatalog.Pages.ImagePage">
-  <StackPanel Orientation="Vertical" Spacing="4">
-    <TextBlock Classes="h1">Image</TextBlock>
-    <TextBlock Classes="h2">Displays an image</TextBlock>
-
-    <StackPanel Orientation="Horizontal"
-                Margin="0,16,0,0"
-                HorizontalAlignment="Center"
-                Spacing="16">
-      <StackPanel Orientation="Vertical">
-        <TextBlock>No Stretch</TextBlock>
-        <Image Source="/Assets/delicate-arch-896885_640.jpg"
-               Width="100" Height="200"
-               Stretch="None"/>
-      </StackPanel>
-
-      <StackPanel Orientation="Vertical">
-        <TextBlock>Fill</TextBlock>
-        <Image Source="/Assets/delicate-arch-896885_640.jpg"
-               Width="100" Height="200"
-               Stretch="Fill"/>
-      </StackPanel>
+  <DockPanel>
+    <StackPanel DockPanel.Dock="Top" Orientation="Vertical" Spacing="4">
+      <TextBlock Classes="h1">Image</TextBlock>
+      <TextBlock Classes="h2">Displays an image</TextBlock>
+    </StackPanel>
 
-      <StackPanel Orientation="Vertical">
-        <TextBlock>Uniform</TextBlock>
-        <Image Source="/Assets/delicate-arch-896885_640.jpg"
-                Width="100" Height="200"
-                Stretch="Uniform"/>
-      </StackPanel>
+    <Grid ColumnDefinitions="*,*" RowDefinitions="Auto,*" Margin="64">
+      
+      <DockPanel Grid.Column="0" Grid.Row="1" Margin="16">
+        <TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Bitmap</TextBlock>
+        <ComboBox Name="bitmapStretch" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="BitmapStretchChanged">
+          <ComboBoxItem>None</ComboBoxItem>
+          <ComboBoxItem>Fill</ComboBoxItem>
+          <ComboBoxItem>Uniform</ComboBoxItem>
+          <ComboBoxItem>UniformToFill</ComboBoxItem>
+        </ComboBox>
+        <Image Name="bitmapImage"
+               Source="/Assets/delicate-arch-896885_640.jpg"/>
+      </DockPanel>
 
-      <StackPanel Orientation="Vertical">
-        <TextBlock>UniformToFill</TextBlock>
-        <Image Source="/Assets/delicate-arch-896885_640.jpg"
-               Width="100" Height="200"
-               Stretch="UniformToFill"/>
-      </StackPanel>
-    </StackPanel>
-    <StackPanel Orientation="Vertical">
-      <TextBlock>Window Icon as an Image</TextBlock>
-      <Image Name="Icon" Width="100" Height="200" Stretch="None" />
-    </StackPanel>
-  </StackPanel>
+      <DockPanel Grid.Column="1" Grid.Row="1" Margin="16">
+        <TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Drawing</TextBlock>
+        <ComboBox Name="drawingStretch" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="DrawingStretchChanged">
+          <ComboBoxItem>None</ComboBoxItem>
+          <ComboBoxItem>Fill</ComboBoxItem>
+          <ComboBoxItem>Uniform</ComboBoxItem>
+          <ComboBoxItem>UniformToFill</ComboBoxItem>
+        </ComboBox>
+        <Image Name="drawingImage">
+          <Image.Source>
+            <DrawingImage>
+              <GeometryDrawing Brush="Red">
+                <PathGeometry>
+                  <PathFigure StartPoint="0,0" IsClosed="True">
+                    <QuadraticBezierSegment Point1="50,0" Point2="50,-50" />
+                    <QuadraticBezierSegment Point1="100,-50" Point2="100,0" />
+                    <LineSegment Point="50,0" />
+                    <LineSegment Point="50,50" />
+                  </PathFigure>
+                </PathGeometry>
+              </GeometryDrawing>
+            </DrawingImage>
+          </Image.Source>
+        </Image>
+      </DockPanel>
+    </Grid>
+    
+  </DockPanel>
 </UserControl>

+ 20 - 19
samples/ControlCatalog/Pages/ImagePage.xaml.cs

@@ -1,40 +1,41 @@
-using System.IO;
-using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
-using Avalonia.Media.Imaging;
+using Avalonia.Media;
 
 namespace ControlCatalog.Pages
 {
     public class ImagePage : UserControl
     {
-        private Image iconImage;
+        private readonly Image _bitmapImage;
+        private readonly Image _drawingImage;
+
         public ImagePage()
         {
-            this.InitializeComponent();
+            InitializeComponent();
+            _bitmapImage = this.FindControl<Image>("bitmapImage");
+            _drawingImage = this.FindControl<Image>("drawingImage");
         }
 
         private void InitializeComponent()
         {
             AvaloniaXamlLoader.Load(this);
-            iconImage = this.Get<Image>("Icon");
         }
 
-        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+        public void BitmapStretchChanged(object sender, SelectionChangedEventArgs e)
+        {
+            if (_bitmapImage != null)
+            {
+                var comboxBox = (ComboBox)sender;
+                _bitmapImage.Stretch = (Stretch)comboxBox.SelectedIndex;
+            }
+        }
+
+        public void DrawingStretchChanged(object sender, SelectionChangedEventArgs e)
         {
-            base.OnAttachedToVisualTree(e);
-            if (iconImage.Source == null)
+            if (_drawingImage != null)
             {
-                var windowRoot = e.Root as Window;
-                if (windowRoot != null)
-                {
-                    using (var stream = new MemoryStream())
-                    {
-                        windowRoot.Icon.Save(stream);
-                        stream.Seek(0, SeekOrigin.Begin);
-                        iconImage.Source = new Bitmap(stream);
-                    }
-                }
+                var comboxBox = (ComboBox)sender;
+                _drawingImage.Stretch = (Stretch)comboxBox.SelectedIndex;
             }
         }
     }

+ 3 - 0
samples/RenderDemo/MainWindow.xaml

@@ -41,6 +41,9 @@
       <TabItem Header="RenderTargetBitmap">
         <pages:RenderTargetBitmapPage/>
       </TabItem>
+      <TabItem Header="GlyphRun">
+        <pages:GlyphRunPage/>
+      </TabItem>
     </TabControl>
   </DockPanel>
 </Window>

+ 14 - 0
samples/RenderDemo/Pages/GlyphRunPage.xaml

@@ -0,0 +1,14 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="RenderDemo.Pages.GlyphRunPage">
+  <Border
+    Background="White">
+    <DrawingPresenter
+      x:Name="drawingPresenter"
+      Stretch="None">
+    </DrawingPresenter>
+  </Border>
+</UserControl>

+ 80 - 0
samples/RenderDemo/Pages/GlyphRunPage.xaml.cs

@@ -0,0 +1,80 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using Avalonia.Threading;
+
+namespace RenderDemo.Pages
+{
+    public class GlyphRunPage : UserControl
+    {
+        private DrawingPresenter _drawingPresenter;
+        private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
+        private readonly Random _rand = new Random();
+        private ushort[] _glyphIndices = new ushort[1];
+        private float _fontSize = 20;
+        private int _direction = 10;
+
+        public GlyphRunPage()
+        {
+            this.InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+
+            _drawingPresenter = this.FindControl<DrawingPresenter>("drawingPresenter");
+
+            DispatcherTimer.Run(() =>
+            {
+                UpdateGlyphRun();
+
+                return true;
+            }, TimeSpan.FromSeconds(1));
+        }
+
+        private void UpdateGlyphRun()
+        {
+            var c = (uint)_rand.Next(65, 90);
+
+            if (_fontSize + _direction > 200)
+            {
+                _direction = -10;
+            }
+
+            if (_fontSize + _direction < 20)
+            {
+                _direction = 10;
+            }
+
+            _fontSize += _direction;
+
+            _glyphIndices[0] = _glyphTypeface.GetGlyph(c);
+
+            var scale = (double)_fontSize / _glyphTypeface.DesignEmHeight;
+
+            var drawingGroup = new DrawingGroup();
+
+            var glyphRunDrawing = new GlyphRunDrawing
+            {
+                Foreground = Brushes.Black,
+                GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _glyphIndices),
+                BaselineOrigin = new Point(0, -_glyphTypeface.Ascent * scale)
+            };
+
+            drawingGroup.Children.Add(glyphRunDrawing);
+
+            var geometryDrawing = new GeometryDrawing
+            {
+                Pen = new Pen(Brushes.Black),
+                Geometry = new RectangleGeometry { Rect = glyphRunDrawing.GlyphRun.Bounds }
+            };
+
+            drawingGroup.Children.Add(geometryDrawing);
+
+            _drawingPresenter.Drawing = drawingGroup;
+        }
+    }
+}

+ 1 - 1
samples/RenderDemo/Pages/RenderTargetBitmapPage.cs

@@ -39,7 +39,7 @@ namespace RenderDemo.Pages
                 ctx.FillRectangle(Brushes.Fuchsia, new Rect(50, 50, 100, 100));
             }
 
-            context.DrawImage(_bitmap, 1, 
+            context.DrawImage(_bitmap,
                 new Rect(0, 0, 200, 200), 
                 new Rect(0, 0, 200, 200));
             Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);

+ 0 - 64
scripts/avalonia-rename.ps1

@@ -1,64 +0,0 @@
-function Get-NewDirectoryName {
-    param ([System.IO.DirectoryInfo]$item)
-
-    $name = $item.Name.Replace("perspex", "avalonia")
-    $name = $name.Replace("Perspex", "Avalonia")
-    Join-Path $item.Parent.FullName $name
-}
-
-function Get-NewFileName {
-    param ([System.IO.FileInfo]$item)
-
-    $name = $item.Name.Replace("perspex", "avalonia")
-    $name = $name.Replace("Perspex", "Avalonia")
-    Join-Path $item.DirectoryName $name
-}
-
-function Rename-Contents {
-    param ([System.IO.FileInfo] $file)
-
-    $extensions = @(".cs",".xaml",".csproj",".sln",".md",".json",".yml",".partial",".ps1",".nuspec",".htm",".html",".gitmodules".".xml",".plist",".targets",".projitems",".shproj",".xib")
-
-    if ($extensions.Contains($file.Extension)) {
-        $text = [IO.File]::ReadAllText($file.FullName)
-        $text = $text.Replace("github.com/perspex", "github.com/avaloniaui")
-        $text = $text.Replace("github.com/Perspex", "github.com/AvaloniaUI")
-        $text = $text.Replace("perspex", "avalonia")
-        $text = $text.Replace("Perspex", "Avalonia")
-        $text = $text.Replace("PERSPEX", "AVALONIA")
-        [IO.File]::WriteAllText($file.FullName, $text)
-    }
-}
-
-function Process-Files {
-    param ([System.IO.DirectoryInfo] $item)
-
-    $dirs = Get-ChildItem -Path $item.FullName -Directory
-    $files = Get-ChildItem -Path $item.FullName -File
-
-    foreach ($dir in $dirs) {
-        Process-Files $dir.FullName
-    }
-
-    foreach ($file in $files) {
-        Rename-Contents $file
-
-        $renamed = Get-NewFileName $file
-
-        if ($file.FullName -ne $renamed) {
-            Write-Host git mv $file.FullName $renamed
-            & git mv $file.FullName $renamed
-        }
-    }
-
-    $renamed = Get-NewDirectoryName $item
-
-    if ($item.FullName -ne $renamed) {
-        Write-Host git mv $item.FullName $renamed
-        & git mv $item.FullName $renamed
-    }
-}
-
-& git submodule deinit .
-& git clean -xdf
-Process-Files .

+ 2 - 2
src/Avalonia.Animation/Animatable.cs

@@ -85,8 +85,8 @@ namespace Avalonia.Animation
                     var instance = transition.Apply(
                         this,
                         Clock ?? Avalonia.Animation.Clock.GlobalClock,
-                        oldValue.ValueOrDefault(),
-                        newValue.ValueOrDefault());
+                        oldValue.GetValueOrDefault(),
+                        newValue.GetValueOrDefault());
 
                     _previousTransitions[property] = instance;
                     return;

+ 1 - 1
src/Avalonia.Animation/IterationCount.cs

@@ -63,7 +63,7 @@ namespace Avalonia.Animation
         public IterationType RepeatType => _type;
 
         /// <summary>
-        /// Gets a value that indicates whether the <see cref="IterationCount"/> is set to loop.
+        /// Gets a value that indicates whether the <see cref="IterationCount"/> is set to Infinite.
         /// </summary>
         public bool IsInfinite => _type == IterationType.Infinite;
 

+ 5 - 3
src/Avalonia.Base/AttachedProperty.cs

@@ -18,12 +18,14 @@ namespace Avalonia
         /// <param name="ownerType">The class that is registering the property.</param>
         /// <param name="metadata">The property metadata.</param>
         /// <param name="inherits">Whether the property inherits its value.</param>
+        /// <param name="validate">A value validation callback.</param>
         public AttachedProperty(
             string name,
-            Type ownerType,            
+            Type ownerType,
             StyledPropertyMetadata<TValue> metadata,
-            bool inherits = false)
-            : base(name, ownerType, metadata, inherits)
+            bool inherits = false,
+            Func<TValue, bool> validate = null)
+            : base(name, ownerType, metadata, inherits, validate)
         {
         }
 

+ 2 - 93
src/Avalonia.Base/AvaloniaObject.cs

@@ -229,24 +229,6 @@ namespace Avalonia
             return property.RouteGetValue(this);
         }
 
-        /// <summary>
-        /// Gets a <see cref="AvaloniaProperty"/> value.
-        /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <returns>The value.</returns>
-        public T GetValue<T>(AvaloniaProperty<T> property)
-        {
-            property = property ?? throw new ArgumentNullException(nameof(property));
-
-            return property switch
-            {
-                StyledPropertyBase<T> styled => GetValue(styled),
-                DirectPropertyBase<T> direct => GetValue(direct),
-                _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
-            };
-        }
-
         /// <summary>
         /// Gets a <see cref="AvaloniaProperty"/> value.
         /// </summary>
@@ -322,33 +304,6 @@ namespace Avalonia
             property.RouteSetValue(this, value, priority);
         }
 
-        /// <summary>
-        /// Sets a <see cref="AvaloniaProperty"/> value.
-        /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="value">The value.</param>
-        /// <param name="priority">The priority of the value.</param>
-        public void SetValue<T>(
-            AvaloniaProperty<T> property,
-            T value,
-            BindingPriority priority = BindingPriority.LocalValue)
-        {
-            property = property ?? throw new ArgumentNullException(nameof(property));
-
-            switch (property)
-            {
-                case StyledPropertyBase<T> styled:
-                    SetValue(styled, value, priority);
-                    break;
-                case DirectPropertyBase<T> direct:
-                    SetValue(direct, value);
-                    break;
-                default:
-                    throw new NotSupportedException("Unsupported AvaloniaProperty type.");
-            }
-        }
-
         /// <summary>
         /// Sets a <see cref="AvaloniaProperty"/> value.
         /// </summary>
@@ -375,7 +330,7 @@ namespace Avalonia
                 else
                 {
                     throw new NotSupportedException(
-                        "Canot set property to Unset at non-local value priority.");
+                        "Cannot set property to Unset at non-local value priority.");
                 }
             }
             else if (!(value is DoNothingType))
@@ -399,52 +354,6 @@ namespace Avalonia
             SetDirectValueUnchecked(property, value);
         }
 
-        /// <summary>
-        /// Binds a <see cref="AvaloniaProperty"/> to an observable.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <param name="source">The observable.</param>
-        /// <param name="priority">The priority of the binding.</param>
-        /// <returns>
-        /// A disposable which can be used to terminate the binding.
-        /// </returns>
-        public IDisposable Bind(
-            AvaloniaProperty property,
-            IObservable<BindingValue<object>> source,
-            BindingPriority priority = BindingPriority.LocalValue)
-        {
-            property = property ?? throw new ArgumentNullException(nameof(property));
-            source = source ?? throw new ArgumentNullException(nameof(source));
-
-            return property.RouteBind(this, source, priority);
-        }
-
-        /// <summary>
-        /// Binds a <see cref="AvaloniaProperty"/> to an observable.
-        /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="source">The observable.</param>
-        /// <param name="priority">The priority of the binding.</param>
-        /// <returns>
-        /// A disposable which can be used to terminate the binding.
-        /// </returns>
-        public IDisposable Bind<T>(
-            AvaloniaProperty<T> property,
-            IObservable<BindingValue<T>> source,
-            BindingPriority priority = BindingPriority.LocalValue)
-        {
-            property = property ?? throw new ArgumentNullException(nameof(property));
-            source = source ?? throw new ArgumentNullException(nameof(source));
-
-            return property switch
-            {
-                StyledPropertyBase<T> styled => Bind(styled, source, priority),
-                DirectPropertyBase<T> direct => Bind(direct, source),
-                _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
-            };
-        }
-
         /// <summary>
         /// Binds a <see cref="AvaloniaProperty"/> to an observable.
         /// </summary>
@@ -531,7 +440,7 @@ namespace Avalonia
             Optional<T> oldValue,
             Optional<T> newValue)
         {
-            if (property.Inherits && !IsSet(property))
+            if (property.Inherits && (_values == null || !_values.IsSet(property)))
             {
                 RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue);
             }

+ 15 - 9
src/Avalonia.Base/AvaloniaProperty.cs

@@ -3,9 +3,7 @@
 
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Reactive.Subjects;
-using System.Reflection;
 using Avalonia.Data;
 using Avalonia.Utilities;
 
@@ -285,22 +283,25 @@ namespace Avalonia
         /// <param name="inherits">Whether the property inherits its value.</param>
         /// <param name="defaultBindingMode">The default binding mode for the property.</param>
         /// <param name="validate">A value validation callback.</param>
+        /// <param name="coerce">A value coercion callback.</param>
         /// <returns>A <see cref="AvaloniaProperty{TValue}"/></returns>
         public static AttachedProperty<TValue> RegisterAttached<TOwner, THost, TValue>(
             string name,
             TValue defaultValue = default(TValue),
             bool inherits = false,
             BindingMode defaultBindingMode = BindingMode.OneWay,
-            Func<TValue, bool> validate = null)
+            Func<TValue, bool> validate = null,
+            Func<IAvaloniaObject, TValue, TValue> coerce = null)
                 where THost : IAvaloniaObject
         {
             Contract.Requires<ArgumentNullException>(name != null);
 
             var metadata = new StyledPropertyMetadata<TValue>(
                 defaultValue,
-                defaultBindingMode: defaultBindingMode);
+                defaultBindingMode: defaultBindingMode,
+                coerce: coerce);
 
-            var result = new AttachedProperty<TValue>(name, typeof(TOwner), metadata, inherits);
+            var result = new AttachedProperty<TValue>(name, typeof(TOwner), metadata, inherits, validate);
             var registry = AvaloniaPropertyRegistry.Instance;
             registry.Register(typeof(TOwner), result);
             registry.RegisterAttached(typeof(THost), result);
@@ -317,22 +318,27 @@ namespace Avalonia
         /// <param name="defaultValue">The default value of the property.</param>
         /// <param name="inherits">Whether the property inherits its value.</param>
         /// <param name="defaultBindingMode">The default binding mode for the property.</param>
+        /// <param name="validate">A value validation callback.</param>
+        /// <param name="coerce">A value coercion callback.</param>
         /// <returns>A <see cref="AvaloniaProperty{TValue}"/></returns>
         public static AttachedProperty<TValue> RegisterAttached<THost, TValue>(
             string name,
             Type ownerType,
             TValue defaultValue = default(TValue),
             bool inherits = false,
-            BindingMode defaultBindingMode = BindingMode.OneWay)
+            BindingMode defaultBindingMode = BindingMode.OneWay,
+            Func<TValue, bool> validate = null,
+            Func<IAvaloniaObject, TValue, TValue> coerce = null)
                 where THost : IAvaloniaObject
         {
             Contract.Requires<ArgumentNullException>(name != null);
 
             var metadata = new StyledPropertyMetadata<TValue>(
                 defaultValue,
-                defaultBindingMode: defaultBindingMode);
+                defaultBindingMode: defaultBindingMode,
+                coerce: coerce);
 
-            var result = new AttachedProperty<TValue>(name, ownerType, metadata, inherits);
+            var result = new AttachedProperty<TValue>(name, ownerType, metadata, inherits, validate);
             var registry = AvaloniaPropertyRegistry.Instance;
             registry.Register(ownerType, result);
             registry.RegisterAttached(typeof(THost), result);
@@ -555,7 +561,7 @@ namespace Avalonia
                     return result;
                 }
 
-                currentType = currentType.GetTypeInfo().BaseType;
+                currentType = currentType.BaseType;
             }
 
             _metadataCache[type] = _defaultMetadata;

+ 2 - 2
src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs

@@ -60,8 +60,8 @@ namespace Avalonia
 
         protected override AvaloniaProperty GetProperty() => Property;
 
-        protected override object? GetOldValue() => OldValue.ValueOrDefault(AvaloniaProperty.UnsetValue);
+        protected override object? GetOldValue() => OldValue.GetValueOrDefault(AvaloniaProperty.UnsetValue);
 
-        protected override object? GetNewValue() => NewValue.ValueOrDefault(AvaloniaProperty.UnsetValue);
+        protected override object? GetNewValue() => NewValue.GetValueOrDefault(AvaloniaProperty.UnsetValue);
     }
 }

+ 10 - 2
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@@ -224,12 +224,20 @@ namespace Avalonia
             Contract.Requires<ArgumentNullException>(type != null);
             Contract.Requires<ArgumentNullException>(name != null);
 
-            if (name.Contains('.'))
+            if (name.Contains("."))
             {
                 throw new InvalidOperationException("Attached properties not supported.");
             }
 
-            return GetRegistered(type).FirstOrDefault(x => x.Name == name);
+            foreach (AvaloniaProperty x in GetRegistered(type))
+            {
+                if (x.Name == name)
+                {
+                    return x;
+                }
+            }
+
+            return null;
         }
 
         /// <summary>

+ 20 - 0
src/Avalonia.Base/Collections/AvaloniaList.cs

@@ -327,6 +327,8 @@ namespace Avalonia.Collections
                     }
                     else
                     {
+                        EnsureCapacity(_inner.Count + list.Count);
+
                         using (IEnumerator<T> en = items.GetEnumerator())
                         {
                             int insertIndex = index;
@@ -550,6 +552,24 @@ namespace Avalonia.Collections
         /// <inheritdoc/>
         Delegate[] INotifyCollectionChangedDebug.GetCollectionChangedSubscribers() => _collectionChanged?.GetInvocationList();
 
+        private void EnsureCapacity(int capacity)
+        {
+            // Adapted from List<T> implementation.
+            var currentCapacity = _inner.Capacity;
+
+            if (currentCapacity < capacity)
+            {
+                var newCapacity = currentCapacity == 0 ? 4 : currentCapacity * 2;
+
+                if (newCapacity < capacity)
+                {
+                    newCapacity = capacity;
+                }
+
+                _inner.Capacity = newCapacity;
+            }
+        }
+
         /// <summary>
         /// Raises the <see cref="CollectionChanged"/> event with an add action.
         /// </summary>

+ 40 - 0
src/Avalonia.Base/Collections/Pooled/ClearMode.cs

@@ -0,0 +1,40 @@
+// This source file is adapted from the Collections.Pooled.
+// (https://github.com/jtmueller/Collections.Pooled/tree/master/Collections.Pooled/)
+
+namespace Avalonia.Collections.Pooled
+{
+    /// <summary>
+    /// This enum allows control over how data is treated when internal
+    /// arrays are returned to the ArrayPool. Be careful to understand 
+    /// what each option does before using anything other than the default
+    /// of Auto.
+    /// </summary>
+    public enum ClearMode
+    {
+        /// <summary>
+        /// <para><code>Auto</code> has different behavior depending on the host project's target framework.</para>
+        /// <para>.NET Core 2.1: Reference types and value types that contain reference types are cleared
+        /// when the internal arrays are returned to the pool. Value types that do not contain reference
+        /// types are not cleared when returned to the pool.</para>
+        /// <para>.NET Standard 2.0: All user types are cleared before returning to the pool, in case they
+        /// contain reference types.
+        /// For .NET Standard, Auto and Always have the same behavior.</para>
+        /// </summary>
+        Auto = 0,
+        /// <summary>
+        /// The <para><code>Always</code> setting has the effect of always clearing user types before returning to the pool.
+        /// This is the default behavior on .NET Standard.</para><para>You might want to turn this on in a .NET Core project
+        /// if you were concerned about sensitive data stored in value types leaking to other pars of your application.</para> 
+        /// </summary>
+        Always = 1,
+        /// <summary>
+        /// <para><code>Never</code> will cause pooled collections to never clear user types before returning them to the pool.</para>
+        /// <para>You might want to use this setting in a .NET Standard project when you know that a particular collection stores
+        /// only value types and you want the performance benefit of not taking time to reset array items to their default value.</para>
+        /// <para>Be careful with this setting: if used for a collection that contains reference types, or value types that contain
+        /// reference types, this setting could cause memory issues by making the garbage collector unable to clean up instances
+        /// that are still being referenced by arrays sitting in the ArrayPool.</para>
+        /// </summary>
+        Never = 2
+    }
+}

+ 31 - 0
src/Avalonia.Base/Collections/Pooled/ICollectionDebugView.cs

@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Avalonia.Collections.Pooled
+{
+    internal sealed class ICollectionDebugView<T>
+    {
+        private readonly ICollection<T> _collection;
+
+        public ICollectionDebugView(ICollection<T> collection)
+        {
+            _collection = collection ?? throw new ArgumentNullException(nameof(collection));
+        }
+
+        [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+        public T[] Items
+        {
+            get
+            {
+                T[] items = new T[_collection.Count];
+                _collection.CopyTo(items, 0);
+                return items;
+            }
+        }
+    }
+}

+ 21 - 0
src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs

@@ -0,0 +1,21 @@
+// This source file is adapted from the Collections.Pooled.
+// (https://github.com/jtmueller/Collections.Pooled/tree/master/Collections.Pooled/)
+
+using System;
+using System.Collections.Generic;
+
+namespace Avalonia.Collections.Pooled
+{
+    /// <summary>
+    /// Represents a read-only collection of pooled elements that can be accessed by index
+    /// </summary>
+    /// <typeparam name="T">The type of elements in the read-only pooled list.</typeparam>
+
+    public interface IReadOnlyPooledList<T> : IReadOnlyList<T>
+    {
+        /// <summary>
+        /// Gets a <see cref="System.ReadOnlySpan{T}"/> for the items currently in the collection.
+        /// </summary>
+        ReadOnlySpan<T> Span { get; }
+    }
+}

+ 1531 - 0
src/Avalonia.Base/Collections/Pooled/PooledList.cs

@@ -0,0 +1,1531 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Buffers;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Threading;
+
+namespace Avalonia.Collections.Pooled
+{
+    /// <summary>
+    /// Implements a variable-size list that uses a pooled array to store the
+    /// elements. A PooledList has a capacity, which is the allocated length
+    /// of the internal array. As elements are added to a PooledList, the capacity
+    /// of the PooledList is automatically increased as required by reallocating the
+    /// internal array.
+    /// </summary>
+    /// <remarks>
+    /// This class is based on the code for <see cref="List{T}"/> but it supports <see cref="Span{T}"/>
+    /// and uses <see cref="ArrayPool{T}"/> when allocating internal arrays.
+    /// </remarks>
+    [DebuggerDisplay("Count = {Count}")]
+    [DebuggerTypeProxy(typeof(ICollectionDebugView<>))]
+    [Serializable]
+    public class PooledList<T> : IList<T>, IReadOnlyPooledList<T>, IList, IDisposable, IDeserializationCallback
+    {
+        // internal constant copied from Array.MaxArrayLength
+        private const int MaxArrayLength = 0x7FEFFFFF;
+        private const int DefaultCapacity = 4;
+        private static readonly T[] s_emptyArray = Array.Empty<T>();
+
+        [NonSerialized]
+        private ArrayPool<T> _pool;
+        [NonSerialized]
+        private object _syncRoot;
+
+        private T[] _items; // Do not rename (binary serialization)
+        private int _size; // Do not rename (binary serialization)
+        private int _version; // Do not rename (binary serialization)
+        private readonly bool _clearOnFree;
+
+        #region Constructors
+
+        /// <summary>
+        /// Constructs a PooledList. The list is initially empty and has a capacity
+        /// of zero. Upon adding the first element to the list the capacity is
+        /// increased to DefaultCapacity, and then increased in multiples of two
+        /// as required.
+        /// </summary>
+        public PooledList() : this(ClearMode.Auto, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Constructs a PooledList. The list is initially empty and has a capacity
+        /// of zero. Upon adding the first element to the list the capacity is
+        /// increased to DefaultCapacity, and then increased in multiples of two
+        /// as required.
+        /// </summary>
+        public PooledList(ClearMode clearMode) : this(clearMode, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Constructs a PooledList. The list is initially empty and has a capacity
+        /// of zero. Upon adding the first element to the list the capacity is
+        /// increased to DefaultCapacity, and then increased in multiples of two
+        /// as required.
+        /// </summary>
+        public PooledList(ArrayPool<T> customPool) : this(ClearMode.Auto, customPool) { }
+
+        /// <summary>
+        /// Constructs a PooledList. The list is initially empty and has a capacity
+        /// of zero. Upon adding the first element to the list the capacity is
+        /// increased to DefaultCapacity, and then increased in multiples of two
+        /// as required.
+        /// </summary>
+        public PooledList(ClearMode clearMode, ArrayPool<T> customPool)
+        {
+            _items = s_emptyArray;
+            _pool = customPool ?? ArrayPool<T>.Shared;
+            _clearOnFree = ShouldClear(clearMode);
+        }
+
+        /// <summary>
+        /// Constructs a List with a given initial capacity. The list is
+        /// initially empty, but will have room for the given number of elements
+        /// before any reallocations are required.
+        /// </summary>
+        public PooledList(int capacity) : this(capacity, ClearMode.Auto, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Constructs a List with a given initial capacity. The list is
+        /// initially empty, but will have room for the given number of elements
+        /// before any reallocations are required.
+        /// </summary>
+        public PooledList(int capacity, bool sizeToCapacity) : this(capacity, ClearMode.Auto, ArrayPool<T>.Shared, sizeToCapacity) { }
+
+        /// <summary>
+        /// Constructs a List with a given initial capacity. The list is
+        /// initially empty, but will have room for the given number of elements
+        /// before any reallocations are required.
+        /// </summary>
+        public PooledList(int capacity, ClearMode clearMode) : this(capacity, clearMode, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Constructs a List with a given initial capacity. The list is
+        /// initially empty, but will have room for the given number of elements
+        /// before any reallocations are required.
+        /// </summary>
+        public PooledList(int capacity, ClearMode clearMode, bool sizeToCapacity) : this(capacity, clearMode, ArrayPool<T>.Shared, sizeToCapacity) { }
+
+        /// <summary>
+        /// Constructs a List with a given initial capacity. The list is
+        /// initially empty, but will have room for the given number of elements
+        /// before any reallocations are required.
+        /// </summary>
+        public PooledList(int capacity, ArrayPool<T> customPool) : this(capacity, ClearMode.Auto, customPool) { }
+
+        /// <summary>
+        /// Constructs a List with a given initial capacity. The list is
+        /// initially empty, but will have room for the given number of elements
+        /// before any reallocations are required.
+        /// </summary>
+        public PooledList(int capacity, ArrayPool<T> customPool, bool sizeToCapacity) : this(capacity, ClearMode.Auto, customPool, sizeToCapacity) { }
+
+        /// <summary>
+        /// Constructs a List with a given initial capacity. The list is
+        /// initially empty, but will have room for the given number of elements
+        /// before any reallocations are required.
+        /// </summary>
+        public PooledList(int capacity, ClearMode clearMode, ArrayPool<T> customPool) : this(capacity, clearMode, customPool, false) { }
+
+        /// <summary>
+        /// Constructs a List with a given initial capacity. The list is
+        /// initially empty, but will have room for the given number of elements
+        /// before any reallocations are required.
+        /// </summary>
+        /// <param name="sizeToCapacity">If true, Count of list equals capacity. Depending on ClearMode, rented items may or may not hold dirty values.</param>
+        public PooledList(int capacity, ClearMode clearMode, ArrayPool<T> customPool, bool sizeToCapacity)
+        {
+            if (capacity < 0)
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+
+            _pool = customPool ?? ArrayPool<T>.Shared;
+            _clearOnFree = ShouldClear(clearMode);
+
+            if (capacity == 0)
+            {
+                _items = s_emptyArray;
+            }
+            else
+            {
+                _items = _pool.Rent(capacity);
+            }
+
+            if (sizeToCapacity)
+            {
+                _size = capacity;
+                if (clearMode != ClearMode.Never)
+                {
+                    Array.Clear(_items, 0, _size);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Constructs a PooledList, copying the contents of the given collection. The
+        /// size and capacity of the new list will both be equal to the size of the
+        /// given collection.
+        /// </summary>
+        public PooledList(T[] array) : this(array.AsSpan(), ClearMode.Auto, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Constructs a PooledList, copying the contents of the given collection. The
+        /// size and capacity of the new list will both be equal to the size of the
+        /// given collection.
+        /// </summary>
+        public PooledList(T[] array, ClearMode clearMode) : this(array.AsSpan(), clearMode, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Constructs a PooledList, copying the contents of the given collection. The
+        /// size and capacity of the new list will both be equal to the size of the
+        /// given collection.
+        /// </summary>
+        public PooledList(T[] array, ArrayPool<T> customPool) : this(array.AsSpan(), ClearMode.Auto, customPool) { }
+
+        /// <summary>
+        /// Constructs a PooledList, copying the contents of the given collection. The
+        /// size and capacity of the new list will both be equal to the size of the
+        /// given collection.
+        /// </summary>
+        public PooledList(T[] array, ClearMode clearMode, ArrayPool<T> customPool) : this(array.AsSpan(), clearMode, customPool) { }
+
+        /// <summary>
+        /// Constructs a PooledList, copying the contents of the given collection. The
+        /// size and capacity of the new list will both be equal to the size of the
+        /// given collection.
+        /// </summary>
+        public PooledList(ReadOnlySpan<T> span) : this(span, ClearMode.Auto, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Constructs a PooledList, copying the contents of the given collection. The
+        /// size and capacity of the new list will both be equal to the size of the
+        /// given collection.
+        /// </summary>
+        public PooledList(ReadOnlySpan<T> span, ClearMode clearMode) : this(span, clearMode, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Constructs a PooledList, copying the contents of the given collection. The
+        /// size and capacity of the new list will both be equal to the size of the
+        /// given collection.
+        /// </summary>
+        public PooledList(ReadOnlySpan<T> span, ArrayPool<T> customPool) : this(span, ClearMode.Auto, customPool) { }
+
+        /// <summary>
+        /// Constructs a PooledList, copying the contents of the given collection. The
+        /// size and capacity of the new list will both be equal to the size of the
+        /// given collection.
+        /// </summary>
+        public PooledList(ReadOnlySpan<T> span, ClearMode clearMode, ArrayPool<T> customPool)
+        {
+            _pool = customPool ?? ArrayPool<T>.Shared;
+            _clearOnFree = ShouldClear(clearMode);
+
+            int count = span.Length;
+            if (count == 0)
+            {
+                _items = s_emptyArray;
+            }
+            else
+            {
+                _items = _pool.Rent(count);
+                span.CopyTo(_items);
+                _size = count;
+            }
+        }
+
+        /// <summary>
+        /// Constructs a PooledList, copying the contents of the given collection. The
+        /// size and capacity of the new list will both be equal to the size of the
+        /// given collection.
+        /// </summary>
+        public PooledList(IEnumerable<T> collection) : this(collection, ClearMode.Auto, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Constructs a PooledList, copying the contents of the given collection. The
+        /// size and capacity of the new list will both be equal to the size of the
+        /// given collection.
+        /// </summary>
+        public PooledList(IEnumerable<T> collection, ClearMode clearMode) : this(collection, clearMode, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Constructs a PooledList, copying the contents of the given collection. The
+        /// size and capacity of the new list will both be equal to the size of the
+        /// given collection.
+        /// </summary>
+        public PooledList(IEnumerable<T> collection, ArrayPool<T> customPool) : this(collection, ClearMode.Auto, customPool) { }
+
+        /// <summary>
+        /// Constructs a PooledList, copying the contents of the given collection. The
+        /// size and capacity of the new list will both be equal to the size of the
+        /// given collection.
+        /// </summary>
+        public PooledList(IEnumerable<T> collection, ClearMode clearMode, ArrayPool<T> customPool)
+        {
+            _pool = customPool ?? ArrayPool<T>.Shared;
+            _clearOnFree = ShouldClear(clearMode);
+
+            switch (collection)
+            {
+                case null:
+                    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
+                    break;
+
+                case ICollection<T> c:
+                    int count = c.Count;
+                    if (count == 0)
+                    {
+                        _items = s_emptyArray;
+                    }
+                    else
+                    {
+                        _items = _pool.Rent(count);
+                        c.CopyTo(_items, 0);
+                        _size = count;
+                    }
+                    break;
+
+                default:
+                    _size = 0;
+                    _items = s_emptyArray;
+                    using (var en = collection.GetEnumerator())
+                    {
+                        while (en.MoveNext())
+                            Add(en.Current);
+                    }
+                    break;
+            }
+        }
+
+        #endregion
+
+        /// <summary>
+        /// Gets a <see cref="System.Span{T}"/> for the items currently in the collection.
+        /// </summary>
+        public Span<T> Span => _items.AsSpan(0, _size);
+
+        /// <inheritdoc/>
+        ReadOnlySpan<T> IReadOnlyPooledList<T>.Span => Span;
+
+        /// <summary>
+        /// Gets and sets the capacity of this list.  The capacity is the size of
+        /// the internal array used to hold items.  When set, the internal 
+        /// Memory of the list is reallocated to the given capacity.
+        /// Note that the return value for this property may be larger than the property was set to.
+        /// </summary>
+        public int Capacity
+        {
+            get => _items.Length;
+            set
+            {
+                if (value < _size)
+                {
+                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
+                }
+
+                if (value != _items.Length)
+                {
+                    if (value > 0)
+                    {
+                        var newItems = _pool.Rent(value);
+                        if (_size > 0)
+                        {
+                            Array.Copy(_items, newItems, _size);
+                        }
+                        ReturnArray();
+                        _items = newItems;
+                    }
+                    else
+                    {
+                        ReturnArray();
+                        _size = 0;
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Read-only property describing how many elements are in the List.
+        /// </summary>
+        public int Count => _size;
+
+        /// <summary>
+        /// Returns the ClearMode behavior for the collection, denoting whether values are
+        /// cleared from internal arrays before returning them to the pool.
+        /// </summary>
+        public ClearMode ClearMode => _clearOnFree ? ClearMode.Always : ClearMode.Never;
+
+        bool IList.IsFixedSize => false;
+
+        bool ICollection<T>.IsReadOnly => false;
+
+        bool IList.IsReadOnly => false;
+
+        int ICollection.Count => _size;
+
+        bool ICollection.IsSynchronized => false;
+
+        // Synchronization root for this object.
+        object ICollection.SyncRoot
+        {
+            get
+            {
+                if (_syncRoot == null)
+                {
+                    Interlocked.CompareExchange<object>(ref _syncRoot, new object(), null);
+                }
+                return _syncRoot;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the element at the given index.
+        /// </summary>
+        public T this[int index]
+        {
+            get
+            {
+                // Following trick can reduce the range check by one
+                if ((uint)index >= (uint)_size)
+                {
+                    ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+                }
+                return _items[index];
+            }
+
+            set
+            {
+                if ((uint)index >= (uint)_size)
+                {
+                    ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+                }
+                _items[index] = value;
+                _version++;
+            }
+        }
+
+        private static bool IsCompatibleObject(object value)
+        {
+            // Non-null values are fine.  Only accept nulls if T is a class or Nullable<U>.
+            // Note that default(T) is not equal to null for value types except when T is Nullable<U>. 
+            return ((value is T) || (value == null && default(T) == null));
+        }
+
+        object IList.this[int index]
+        {
+            get
+            {
+                return this[index];
+            }
+            set
+            {
+                ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(value, ExceptionArgument.value);
+
+                try
+                {
+                    this[index] = (T)value;
+                }
+                catch (InvalidCastException)
+                {
+                    ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T));
+                }
+            }
+        }
+
+        /// <summary>
+        /// Adds the given object to the end of this list. The size of the list is
+        /// increased by one. If required, the capacity of the list is doubled
+        /// before adding the new element.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Add(T item)
+        {
+            _version++;
+            int size = _size;
+            if ((uint)size < (uint)_items.Length)
+            {
+                _size = size + 1;
+                _items[size] = item;
+            }
+            else
+            {
+                AddWithResize(item);
+            }
+        }
+
+        // Non-inline from List.Add to improve its code quality as uncommon path
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private void AddWithResize(T item)
+        {
+            int size = _size;
+            EnsureCapacity(size + 1);
+            _size = size + 1;
+            _items[size] = item;
+        }
+
+        int IList.Add(object item)
+        {
+            ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(item, ExceptionArgument.item);
+
+            try
+            {
+                Add((T)item);
+            }
+            catch (InvalidCastException)
+            {
+                ThrowHelper.ThrowWrongValueTypeArgumentException(item, typeof(T));
+            }
+
+            return Count - 1;
+        }
+
+        /// <summary>
+        /// Adds the elements of the given collection to the end of this list. If
+        /// required, the capacity of the list is increased to twice the previous
+        /// capacity or the new size, whichever is larger.
+        /// </summary>
+        public void AddRange(IEnumerable<T> collection)
+            => InsertRange(_size, collection);
+
+        /// <summary>
+        /// Adds the elements of the given array to the end of this list. If
+        /// required, the capacity of the list is increased to twice the previous
+        /// capacity or the new size, whichever is larger.
+        /// </summary>
+        public void AddRange(T[] array)
+            => AddRange(array.AsSpan());
+
+        /// <summary>
+        /// Adds the elements of the given <see cref="ReadOnlySpan{T}"/> to the end of this list. If
+        /// required, the capacity of the list is increased to twice the previous
+        /// capacity or the new size, whichever is larger.
+        /// </summary>
+        public void AddRange(ReadOnlySpan<T> span)
+        {
+            var newSpan = InsertSpan(_size, span.Length, false);
+            span.CopyTo(newSpan);
+        }
+
+        /// <summary>
+        /// Advances the <see cref="Count"/> by the number of items specified,
+        /// increasing the capacity if required, then returns a Span representing
+        /// the set of items to be added, allowing direct writes to that section
+        /// of the collection.
+        /// </summary>
+        /// <param name="count">The number of items to add.</param>
+        public Span<T> AddSpan(int count)
+            => InsertSpan(_size, count);
+
+        public ReadOnlyCollection<T> AsReadOnly()
+            => new ReadOnlyCollection<T>(this);
+
+        /// <summary>
+        /// Searches a section of the list for a given element using a binary search
+        /// algorithm. 
+        /// </summary>
+        /// 
+        /// <remarks><para>Elements of the list are compared to the search value using
+        /// the given IComparer interface. If comparer is null, elements of
+        /// the list are compared to the search value using the IComparable
+        /// interface, which in that case must be implemented by all elements of the
+        /// list and the given search value. This method assumes that the given
+        /// section of the list is already sorted; if this is not the case, the
+        /// result will be incorrect.</para>
+        ///
+        /// <para>The method returns the index of the given value in the list. If the
+        /// list does not contain the given value, the method returns a negative
+        /// integer. The bitwise complement operator (~) can be applied to a
+        /// negative result to produce the index of the first element (if any) that
+        /// is larger than the given search value. This is also the index at which
+        /// the search value should be inserted into the list in order for the list
+        /// to remain sorted.
+        /// </para></remarks>
+        public int BinarySearch(int index, int count, T item, IComparer<T> comparer)
+        {
+            if (index < 0)
+                ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+            if (count < 0)
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+            if (_size - index < count)
+                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
+
+            return Array.BinarySearch(_items, index, count, item, comparer);
+        }
+
+        /// <summary>
+        /// Searches the list for a given element using a binary search
+        /// algorithm. If the item implements <see cref="IComparable{T}"/>
+        /// then that is used for comparison, otherwise <see cref="Comparer{T}.Default"/> is used.
+        /// </summary>
+        public int BinarySearch(T item)
+            => BinarySearch(0, Count, item, null);
+
+        /// <summary>
+        /// Searches the list for a given element using a binary search
+        /// algorithm. If the item implements <see cref="IComparable{T}"/>
+        /// then that is used for comparison, otherwise <see cref="Comparer{T}.Default"/> is used.
+        /// </summary>
+        public int BinarySearch(T item, IComparer<T> comparer)
+            => BinarySearch(0, Count, item, comparer);
+
+        /// <summary>
+        /// Clears the contents of the PooledList.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Clear()
+        {
+            _version++;
+            int size = _size;
+            _size = 0;
+
+            if (size > 0 && _clearOnFree)
+            {
+                // Clear the elements so that the gc can reclaim the references.
+                Array.Clear(_items, 0, _size);
+            }
+        }
+
+        /// <summary>
+        /// Contains returns true if the specified element is in the List.
+        /// It does a linear, O(n) search.  Equality is determined by calling
+        /// EqualityComparer{T}.Default.Equals.
+        /// </summary>
+        public bool Contains(T item)
+        {
+            // PERF: IndexOf calls Array.IndexOf, which internally
+            // calls EqualityComparer<T>.Default.IndexOf, which
+            // is specialized for different types. This
+            // boosts performance since instead of making a
+            // virtual method call each iteration of the loop,
+            // via EqualityComparer<T>.Default.Equals, we
+            // only make one virtual call to EqualityComparer.IndexOf.
+
+            return _size != 0 && IndexOf(item) != -1;
+        }
+
+        bool IList.Contains(object item)
+        {
+            if (IsCompatibleObject(item))
+            {
+                return Contains((T)item);
+            }
+            return false;
+        }
+
+        public PooledList<TOutput> ConvertAll<TOutput>(Func<T, TOutput> converter)
+        {
+            if (converter == null)
+            {
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.converter);
+            }
+
+            var list = new PooledList<TOutput>(_size);
+            for (int i = 0; i < _size; i++)
+            {
+                list._items[i] = converter(_items[i]);
+            }
+            list._size = _size;
+            return list;
+        }
+
+        /// <summary>
+        /// Copies this list to the given span.
+        /// </summary>
+        public void CopyTo(Span<T> span)
+        {
+            if (span.Length < Count)
+                throw new ArgumentException("Destination span is shorter than the list to be copied.");
+
+            Span.CopyTo(span);
+        }
+
+        void ICollection<T>.CopyTo(T[] array, int arrayIndex)
+        {
+            Array.Copy(_items, 0, array, arrayIndex, _size);
+        }
+
+        // Copies this List into array, which must be of a 
+        // compatible array type.  
+        void ICollection.CopyTo(Array array, int arrayIndex)
+        {
+            if ((array != null) && (array.Rank != 1))
+            {
+                ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
+            }
+
+            try
+            {
+                // Array.Copy will check for NULL.
+                Array.Copy(_items, 0, array, arrayIndex, _size);
+            }
+            catch (ArrayTypeMismatchException)
+            {
+                ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+            }
+        }
+
+        /// <summary>
+        /// Ensures that the capacity of this list is at least the given minimum
+        /// value. If the current capacity of the list is less than min, the
+        /// capacity is increased to twice the current capacity or to min,
+        /// whichever is larger.
+        /// </summary>
+        private void EnsureCapacity(int min)
+        {
+            if (_items.Length < min)
+            {
+                int newCapacity = _items.Length == 0 ? DefaultCapacity : _items.Length * 2;
+                // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
+                // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
+                if ((uint)newCapacity > MaxArrayLength)
+                    newCapacity = MaxArrayLength;
+                if (newCapacity < min)
+                    newCapacity = min;
+                Capacity = newCapacity;
+            }
+        }
+
+        public bool Exists(Func<T, bool> match)
+            => FindIndex(match) != -1;
+
+        public bool TryFind(Func<T, bool> match, out T result)
+        {
+            if (match == null)
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+
+            for (int i = 0; i < _size; i++)
+            {
+                if (match(_items[i]))
+                {
+                    result = _items[i];
+                    return true;
+                }
+            }
+
+            result = default;
+            return false;
+        }
+
+        public PooledList<T> FindAll(Func<T, bool> match)
+        {
+            if (match == null)
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+
+            var list = new PooledList<T>();
+            for (int i = 0; i < _size; i++)
+            {
+                if (match(_items[i]))
+                {
+                    list.Add(_items[i]);
+                }
+            }
+            return list;
+        }
+
+        public int FindIndex(Func<T, bool> match)
+            => FindIndex(0, _size, match);
+
+        public int FindIndex(int startIndex, Func<T, bool> match)
+            => FindIndex(startIndex, _size - startIndex, match);
+
+        public int FindIndex(int startIndex, int count, Func<T, bool> match)
+        {
+            if ((uint)startIndex > (uint)_size)
+                ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index();
+
+            if (count < 0 || startIndex > _size - count)
+                ThrowHelper.ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count();
+
+            if (match is null)
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+
+            int endIndex = startIndex + count;
+            for (int i = startIndex; i < endIndex; i++)
+            {
+                if (match(_items[i]))
+                    return i;
+            }
+            return -1;
+        }
+
+        public bool TryFindLast(Func<T, bool> match, out T result)
+        {
+            if (match is null)
+            {
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+            }
+
+            for (int i = _size - 1; i >= 0; i--)
+            {
+                if (match(_items[i]))
+                {
+                    result = _items[i];
+                    return true;
+                }
+            }
+
+            result = default;
+            return false;
+        }
+
+        public int FindLastIndex(Func<T, bool> match)
+            => FindLastIndex(_size - 1, _size, match);
+
+        public int FindLastIndex(int startIndex, Func<T, bool> match)
+            => FindLastIndex(startIndex, startIndex + 1, match);
+
+        public int FindLastIndex(int startIndex, int count, Func<T, bool> match)
+        {
+            if (match == null)
+            {
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+            }
+
+            if (_size == 0)
+            {
+                // Special case for 0 length List
+                if (startIndex != -1)
+                {
+                    ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index();
+                }
+            }
+            else
+            {
+                // Make sure we're not out of range
+                if ((uint)startIndex >= (uint)_size)
+                {
+                    ThrowHelper.ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index();
+                }
+            }
+
+            // 2nd half of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
+            if (count < 0 || startIndex - count + 1 < 0)
+            {
+                ThrowHelper.ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count();
+            }
+
+            int endIndex = startIndex - count;
+            for (int i = startIndex; i > endIndex; i--)
+            {
+                if (match(_items[i]))
+                {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        public void ForEach(Action<T> action)
+        {
+            if (action == null)
+            {
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.action);
+            }
+
+            int version = _version;
+            for (int i = 0; i < _size; i++)
+            {
+                if (version != _version)
+                {
+                    break;
+                }
+                action(_items[i]);
+            }
+
+            if (version != _version)
+                ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+        }
+
+        /// <summary>
+        /// Returns an enumerator for this list with the given
+        /// permission for removal of elements. If modifications made to the list 
+        /// while an enumeration is in progress, the MoveNext and 
+        /// GetObject methods of the enumerator will throw an exception.
+        /// </summary>
+        public Enumerator GetEnumerator()
+            => new Enumerator(this);
+
+        IEnumerator<T> IEnumerable<T>.GetEnumerator()
+            => new Enumerator(this);
+
+        IEnumerator IEnumerable.GetEnumerator()
+            => new Enumerator(this);
+
+        /// <summary>
+        /// Equivalent to PooledList.Span.Slice(index, count).
+        /// </summary>
+        public Span<T> GetRange(int index, int count)
+        {
+            if (index < 0)
+            {
+                ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+            }
+
+            if (count < 0)
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+            }
+
+            if (_size - index < count)
+            {
+                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
+            }
+
+            return Span.Slice(index, count);
+        }
+
+        /// <summary>
+        /// Returns the index of the first occurrence of a given value in
+        /// this list. The list is searched forwards from beginning to end.
+        /// </summary>
+        public int IndexOf(T item)
+            => Array.IndexOf(_items, item, 0, _size);
+
+        int IList.IndexOf(object item)
+        {
+            if (IsCompatibleObject(item))
+            {
+                return IndexOf((T)item);
+            }
+            return -1;
+        }
+
+        /// <summary>
+        /// Returns the index of the first occurrence of a given value in a range of
+        /// this list. The list is searched forwards, starting at index
+        /// index and ending at count number of elements. 
+        /// </summary>
+        public int IndexOf(T item, int index)
+        {
+            if (index > _size)
+                ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+            return Array.IndexOf(_items, item, index, _size - index);
+        }
+
+        /// <summary>
+        /// Returns the index of the first occurrence of a given value in a range of
+        /// this list. The list is searched forwards, starting at index
+        /// index and upto count number of elements. 
+        /// </summary>
+        public int IndexOf(T item, int index, int count)
+        {
+            if (index > _size)
+                ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+
+            if (count < 0 || index > _size - count)
+                ThrowHelper.ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count();
+
+            return Array.IndexOf(_items, item, index, count);
+        }
+
+        /// <summary>
+        /// Inserts an element into this list at a given index. The size of the list
+        /// is increased by one. If required, the capacity of the list is doubled
+        /// before inserting the new element.
+        /// </summary>
+        public void Insert(int index, T item)
+        {
+            // Note that insertions at the end are legal.
+            if ((uint)index > (uint)_size)
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_ListInsert);
+            }
+
+            if (_size == _items.Length)
+                EnsureCapacity(_size + 1);
+            if (index < _size)
+            {
+                Array.Copy(_items, index, _items, index + 1, _size - index);
+            }
+            _items[index] = item;
+            _size++;
+            _version++;
+        }
+
+        void IList.Insert(int index, object item)
+        {
+            ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(item, ExceptionArgument.item);
+
+            try
+            {
+                Insert(index, (T)item);
+            }
+            catch (InvalidCastException)
+            {
+                ThrowHelper.ThrowWrongValueTypeArgumentException(item, typeof(T));
+            }
+        }
+
+        /// <summary>
+        /// Inserts the elements of the given collection at a given index. If
+        /// required, the capacity of the list is increased to twice the previous
+        /// capacity or the new size, whichever is larger.  Ranges may be added
+        /// to the end of the list by setting index to the List's size.
+        /// </summary>
+        public void InsertRange(int index, IEnumerable<T> collection)
+        {
+            if ((uint)index > (uint)_size)
+            {
+                ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+            }
+
+            switch (collection)
+            {
+                case null:
+                    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
+                    break;
+
+                case ICollection<T> c:
+                    int count = c.Count;
+                    if (count > 0)
+                    {
+                        EnsureCapacity(_size + count);
+                        if (index < _size)
+                        {
+                            Array.Copy(_items, index, _items, index + count, _size - index);
+                        }
+
+                        // If we're inserting a List into itself, we want to be able to deal with that.
+                        if (this == c)
+                        {
+                            // Copy first part of _items to insert location
+                            Array.Copy(_items, 0, _items, index, index);
+                            // Copy last part of _items back to inserted location
+                            Array.Copy(_items, index + count, _items, index * 2, _size - index);
+                        }
+                        else
+                        {
+                            c.CopyTo(_items, index);
+                        }
+                        _size += count;
+                    }
+                    break;
+
+                default:
+                    using (var en = collection.GetEnumerator())
+                    {
+                        while (en.MoveNext())
+                        {
+                            Insert(index++, en.Current);
+                        }
+                    }
+                    break;
+            }
+
+            _version++;
+        }
+
+        /// <summary>
+        /// Inserts the elements of the given collection at a given index. If
+        /// required, the capacity of the list is increased to twice the previous
+        /// capacity or the new size, whichever is larger.  Ranges may be added
+        /// to the end of the list by setting index to the List's size.
+        /// </summary>
+        public void InsertRange(int index, ReadOnlySpan<T> span)
+        {
+            var newSpan = InsertSpan(index, span.Length, false);
+            span.CopyTo(newSpan);
+        }
+
+        /// <summary>
+        /// Inserts the elements of the given collection at a given index. If
+        /// required, the capacity of the list is increased to twice the previous
+        /// capacity or the new size, whichever is larger.  Ranges may be added
+        /// to the end of the list by setting index to the List's size.
+        /// </summary>
+        public void InsertRange(int index, T[] array)
+        {
+            if (array is null)
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+            InsertRange(index, array.AsSpan());
+        }
+
+        /// <summary>
+        /// Advances the <see cref="Count"/> by the number of items specified,
+        /// increasing the capacity if required, then returns a Span representing
+        /// the set of items to be added, allowing direct writes to that section
+        /// of the collection.
+        /// </summary>
+        public Span<T> InsertSpan(int index, int count)
+            => InsertSpan(index, count, true);
+
+        private Span<T> InsertSpan(int index, int count, bool clearOutput)
+        {
+            EnsureCapacity(_size + count);
+
+            if (index < _size)
+            {
+                Array.Copy(_items, index, _items, index + count, _size - index);
+            }
+
+            _size += count;
+            _version++;
+
+            var output = _items.AsSpan(index, count);
+
+            if (clearOutput && _clearOnFree)
+            {
+                output.Clear();
+            }
+
+            return output;
+        }
+
+        /// <summary>
+        /// Returns the index of the last occurrence of a given value in a range of
+        /// this list. The list is searched backwards, starting at the end 
+        /// and ending at the first element in the list.
+        /// </summary>
+        public int LastIndexOf(T item)
+        {
+            if (_size == 0)
+            {  // Special case for empty list
+                return -1;
+            }
+            else
+            {
+                return LastIndexOf(item, _size - 1, _size);
+            }
+        }
+
+        /// <summary>
+        /// Returns the index of the last occurrence of a given value in a range of
+        /// this list. The list is searched backwards, starting at index
+        /// index and ending at the first element in the list.
+        /// </summary>
+        public int LastIndexOf(T item, int index)
+        {
+            if (index >= _size)
+                ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+            return LastIndexOf(item, index, index + 1);
+        }
+
+        /// <summary>
+        /// Returns the index of the last occurrence of a given value in a range of
+        /// this list. The list is searched backwards, starting at index
+        /// index and upto count elements
+        /// </summary>
+        public int LastIndexOf(T item, int index, int count)
+        {
+            if (Count != 0 && index < 0)
+            {
+                ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+            }
+
+            if (Count != 0 && count < 0)
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+            }
+
+            if (_size == 0)
+            {
+                // Special case for empty list
+                return -1;
+            }
+
+            if (index >= _size)
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_BiggerThanCollection);
+            }
+
+            if (count > index + 1)
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_BiggerThanCollection);
+            }
+
+            return Array.LastIndexOf(_items, item, index, count);
+        }
+
+        // Removes the element at the given index. The size of the list is
+        // decreased by one.
+        public bool Remove(T item)
+        {
+            int index = IndexOf(item);
+            if (index >= 0)
+            {
+                RemoveAt(index);
+                return true;
+            }
+
+            return false;
+        }
+
+        void IList.Remove(object item)
+        {
+            if (IsCompatibleObject(item))
+            {
+                Remove((T)item);
+            }
+        }
+
+        /// <summary>
+        /// This method removes all items which match the predicate.
+        /// The complexity is O(n).
+        /// </summary>
+        public int RemoveAll(Func<T, bool> match)
+        {
+            if (match == null)
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+
+            int freeIndex = 0;   // the first free slot in items array
+
+            // Find the first item which needs to be removed.
+            while (freeIndex < _size && !match(_items[freeIndex]))
+                freeIndex++;
+            if (freeIndex >= _size)
+                return 0;
+
+            int current = freeIndex + 1;
+            while (current < _size)
+            {
+                // Find the first item which needs to be kept.
+                while (current < _size && match(_items[current]))
+                    current++;
+
+                if (current < _size)
+                {
+                    // copy item to the free slot.
+                    _items[freeIndex++] = _items[current++];
+                }
+            }
+
+            if (_clearOnFree)
+            {
+                // Clear the removed elements so that the gc can reclaim the references.
+                Array.Clear(_items, freeIndex, _size - freeIndex);
+            }
+
+            int result = _size - freeIndex;
+            _size = freeIndex;
+            _version++;
+            return result;
+        }
+
+        /// <summary>
+        /// Removes the element at the given index. The size of the list is
+        /// decreased by one.
+        /// </summary>
+        public void RemoveAt(int index)
+        {
+            if ((uint)index >= (uint)_size)
+                ThrowHelper.ThrowArgumentOutOfRange_IndexException();
+
+            _size--;
+            if (index < _size)
+            {
+                Array.Copy(_items, index + 1, _items, index, _size - index);
+            }
+            _version++;
+
+            if (_clearOnFree)
+            {
+                // Clear the removed element so that the gc can reclaim the reference.
+                _items[_size] = default;
+            }
+        }
+
+        /// <summary>
+        /// Removes a range of elements from this list.
+        /// </summary>
+        public void RemoveRange(int index, int count)
+        {
+            if (index < 0)
+                ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+
+            if (count < 0)
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+
+            if (_size - index < count)
+                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
+
+            if (count > 0)
+            {
+                _size -= count;
+                if (index < _size)
+                {
+                    Array.Copy(_items, index + count, _items, index, _size - index);
+                }
+
+                _version++;
+
+                if (_clearOnFree)
+                {
+                    // Clear the removed elements so that the gc can reclaim the references.
+                    Array.Clear(_items, _size, count);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Reverses the elements in this list.
+        /// </summary>
+        public void Reverse()
+            => Reverse(0, _size);
+
+        /// <summary>
+        /// Reverses the elements in a range of this list. Following a call to this
+        /// method, an element in the range given by index and count
+        /// which was previously located at index i will now be located at
+        /// index index + (index + count - i - 1).
+        /// </summary>
+        public void Reverse(int index, int count)
+        {
+            if (index < 0)
+                ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+
+            if (count < 0)
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+
+            if (_size - index < count)
+                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
+
+            if (count > 1)
+            {
+                Array.Reverse(_items, index, count);
+            }
+            _version++;
+        }
+
+        /// <summary>
+        /// Sorts the elements in this list.  Uses the default comparer and 
+        /// Array.Sort.
+        /// </summary>
+        public void Sort()
+            => Sort(0, Count, null);
+
+        /// <summary>
+        /// Sorts the elements in this list.  Uses Array.Sort with the
+        /// provided comparer.
+        /// </summary>
+        /// <param name="comparer"></param>
+        public void Sort(IComparer<T> comparer)
+            => Sort(0, Count, comparer);
+
+        /// <summary>
+        /// Sorts the elements in a section of this list. The sort compares the
+        /// elements to each other using the given IComparer interface. If
+        /// comparer is null, the elements are compared to each other using
+        /// the IComparable interface, which in that case must be implemented by all
+        /// elements of the list.
+        /// 
+        /// This method uses the Array.Sort method to sort the elements.
+        /// </summary>
+        public void Sort(int index, int count, IComparer<T> comparer)
+        {
+            if (index < 0)
+                ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+
+            if (count < 0)
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+
+            if (_size - index < count)
+                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
+
+            if (count > 1)
+            {
+                Array.Sort(_items, index, count, comparer);
+            }
+            _version++;
+        }
+
+        public void Sort(Func<T, T, int> comparison)
+        {
+            if (comparison == null)
+            {
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparison);
+            }
+
+            if (_size > 1)
+            {
+                // List<T> uses ArraySortHelper here but since it's an internal class,
+                // we're creating an IComparer<T> using the comparison function to avoid
+                // duplicating all that code.
+                Array.Sort(_items, 0, _size, new Comparer(comparison));
+            }
+            _version++;
+        }
+
+        /// <summary>
+        /// ToArray returns an array containing the contents of the List.
+        /// This requires copying the List, which is an O(n) operation.
+        /// </summary>
+        public T[] ToArray()
+        {
+            if (_size == 0)
+            {
+                return s_emptyArray;
+            }
+
+            return Span.ToArray();
+        }
+
+        /// <summary>
+        /// Sets the capacity of this list to the size of the list. This method can
+        /// be used to minimize a list's memory overhead once it is known that no
+        /// new elements will be added to the list. To completely clear a list and
+        /// release all memory referenced by the list, execute the following
+        /// statements:
+        /// <code>
+        /// list.Clear();
+        /// list.TrimExcess();
+        /// </code>
+        /// </summary>
+        public void TrimExcess()
+        {
+            int threshold = (int)(_items.Length * 0.9);
+            if (_size < threshold)
+            {
+                Capacity = _size;
+            }
+        }
+
+        public bool TrueForAll(Func<T, bool> match)
+        {
+            if (match == null)
+            {
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+            }
+
+            for (int i = 0; i < _size; i++)
+            {
+                if (!match(_items[i]))
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private void ReturnArray()
+        {
+            if (_items.Length == 0)
+                return;
+
+            try
+            {
+                // Clear the elements so that the gc can reclaim the references.
+                _pool.Return(_items, clearArray: _clearOnFree);
+            }
+            catch (ArgumentException)
+            {
+                // oh well, the array pool didn't like our array
+            }
+
+            _items = s_emptyArray;
+        }
+
+        private static bool ShouldClear(ClearMode mode)
+        {
+#if NETCOREAPP2_1
+            return mode == ClearMode.Always
+                || (mode == ClearMode.Auto && RuntimeHelpers.IsReferenceOrContainsReferences<T>());
+#else
+            return mode != ClearMode.Never;
+#endif
+        }
+
+        /// <summary>
+        /// Returns the internal buffers to the ArrayPool.
+        /// </summary>
+        public void Dispose()
+        {
+            ReturnArray();
+            _size = 0;
+            _version++;
+        }
+
+        void IDeserializationCallback.OnDeserialization(object sender)
+        {
+            // We can't serialize array pools, so deserialized PooledLists will
+            // have to use the shared pool, even if they were using a custom pool
+            // before serialization.
+            _pool = ArrayPool<T>.Shared;
+        }
+
+        public struct Enumerator : IEnumerator<T>, IEnumerator
+        {
+            private readonly PooledList<T> _list;
+            private int _index;
+            private readonly int _version;
+            private T _current;
+
+            internal Enumerator(PooledList<T> list)
+            {
+                _list = list;
+                _index = 0;
+                _version = list._version;
+                _current = default;
+            }
+
+            public void Dispose()
+            {
+            }
+
+            public bool MoveNext()
+            {
+                var localList = _list;
+
+                if (_version == localList._version && ((uint)_index < (uint)localList._size))
+                {
+                    _current = localList._items[_index];
+                    _index++;
+                    return true;
+                }
+                return MoveNextRare();
+            }
+
+            private bool MoveNextRare()
+            {
+                if (_version != _list._version)
+                {
+                    ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+                }
+
+                _index = _list._size + 1;
+                _current = default;
+                return false;
+            }
+
+            public T Current => _current;
+
+            object IEnumerator.Current
+            {
+                get
+                {
+                    if (_index == 0 || _index == _list._size + 1)
+                    {
+                        ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+                    }
+                    return Current;
+                }
+            }
+
+            void IEnumerator.Reset()
+            {
+                if (_version != _list._version)
+                {
+                    ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+                }
+
+                _index = 0;
+                _current = default;
+            }
+        }
+
+        private readonly struct Comparer : IComparer<T>
+        {
+            private readonly Func<T, T, int> _comparison;
+
+            public Comparer(Func<T, T, int> comparison)
+            {
+                _comparison = comparison;
+            }
+
+            public int Compare(T x, T y) => _comparison(x, y);
+        }
+    }
+}

+ 699 - 0
src/Avalonia.Base/Collections/Pooled/PooledStack.cs

@@ -0,0 +1,699 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*=============================================================================
+**
+**
+** Purpose: An array implementation of a generic stack.
+**
+**
+=============================================================================*/
+
+using System;
+using System.Buffers;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Threading;
+
+namespace Avalonia.Collections.Pooled
+{
+    /// <summary>
+    /// A simple stack of objects.  Internally it is implemented as an array,
+    /// so Push can be O(n).  Pop is O(1).
+    /// </summary>
+    [DebuggerTypeProxy(typeof(StackDebugView<>))]
+    [DebuggerDisplay("Count = {Count}")]
+    [Serializable]
+    public class PooledStack<T> : IEnumerable<T>, ICollection, IReadOnlyCollection<T>, IDisposable, IDeserializationCallback
+    {
+        [NonSerialized]
+        private ArrayPool<T> _pool;
+        [NonSerialized]
+        private object _syncRoot;
+
+        private T[] _array; // Storage for stack elements. Do not rename (binary serialization)
+        private int _size; // Number of items in the stack. Do not rename (binary serialization)
+        private int _version; // Used to keep enumerator in sync w/ collection. Do not rename (binary serialization)
+        private readonly bool _clearOnFree;
+
+        private const int DefaultCapacity = 4;
+
+        #region Constructors
+
+        /// <summary>
+        /// Create a stack with the default initial capacity. 
+        /// </summary>
+        public PooledStack() : this(ClearMode.Auto, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Create a stack with the default initial capacity. 
+        /// </summary>
+        public PooledStack(ClearMode clearMode) : this(clearMode, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Create a stack with the default initial capacity. 
+        /// </summary>
+        public PooledStack(ArrayPool<T> customPool) : this(ClearMode.Auto, customPool) { }
+
+        /// <summary>
+        /// Create a stack with the default initial capacity and a custom ArrayPool.
+        /// </summary>
+        public PooledStack(ClearMode clearMode, ArrayPool<T> customPool)
+        {
+            _pool = customPool ?? ArrayPool<T>.Shared;
+            _array = Array.Empty<T>();
+            _clearOnFree = ShouldClear(clearMode);
+        }
+
+        /// <summary>
+        /// Create a stack with a specific initial capacity.  The initial capacity
+        /// must be a non-negative number.
+        /// </summary>
+        public PooledStack(int capacity) : this(capacity, ClearMode.Auto, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Create a stack with a specific initial capacity.  The initial capacity
+        /// must be a non-negative number.
+        /// </summary>
+        public PooledStack(int capacity, ClearMode clearMode) : this(capacity, clearMode, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Create a stack with a specific initial capacity.  The initial capacity
+        /// must be a non-negative number.
+        /// </summary>
+        public PooledStack(int capacity, ArrayPool<T> customPool) : this(capacity, ClearMode.Auto, customPool) { }
+
+        /// <summary>
+        /// Create a stack with a specific initial capacity.  The initial capacity
+        /// must be a non-negative number.
+        /// </summary>
+        public PooledStack(int capacity, ClearMode clearMode, ArrayPool<T> customPool)
+        {
+            if (capacity < 0)
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity,
+                    ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+            }
+            _pool = customPool ?? ArrayPool<T>.Shared;
+            _array = _pool.Rent(capacity);
+            _clearOnFree = ShouldClear(clearMode);
+        }
+
+        /// <summary>
+        /// Fills a Stack with the contents of a particular collection.  The items are
+        /// pushed onto the stack in the same order they are read by the enumerator.
+        /// </summary>
+        public PooledStack(IEnumerable<T> enumerable) : this(enumerable, ClearMode.Auto, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Fills a Stack with the contents of a particular collection.  The items are
+        /// pushed onto the stack in the same order they are read by the enumerator.
+        /// </summary>
+        public PooledStack(IEnumerable<T> enumerable, ClearMode clearMode) : this(enumerable, clearMode, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Fills a Stack with the contents of a particular collection.  The items are
+        /// pushed onto the stack in the same order they are read by the enumerator.
+        /// </summary>
+        public PooledStack(IEnumerable<T> enumerable, ArrayPool<T> customPool) : this(enumerable, ClearMode.Auto, customPool) { }
+
+        /// <summary>
+        /// Fills a Stack with the contents of a particular collection.  The items are
+        /// pushed onto the stack in the same order they are read by the enumerator.
+        /// </summary>
+        public PooledStack(IEnumerable<T> enumerable, ClearMode clearMode, ArrayPool<T> customPool)
+        {
+            _pool = customPool ?? ArrayPool<T>.Shared;
+            _clearOnFree = ShouldClear(clearMode);
+
+            switch (enumerable)
+            {
+                case null:
+                    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.enumerable);
+                    break;
+
+                case ICollection<T> collection:
+                    if (collection.Count == 0)
+                    {
+                        _array = Array.Empty<T>();
+                    }
+                    else
+                    {
+                        _array = _pool.Rent(collection.Count);
+                        collection.CopyTo(_array, 0);
+                        _size = collection.Count;
+                    }
+                    break;
+
+                default:
+                    using (var list = new PooledList<T>(enumerable))
+                    {
+                        _array = _pool.Rent(list.Count);
+                        list.Span.CopyTo(_array);
+                        _size = list.Count;
+                    }
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Fills a Stack with the contents of a particular collection.  The items are
+        /// pushed onto the stack in the same order they are read by the enumerator.
+        /// </summary>
+        public PooledStack(T[] array) : this(array.AsSpan(), ClearMode.Auto, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Fills a Stack with the contents of a particular collection.  The items are
+        /// pushed onto the stack in the same order they are read by the enumerator.
+        /// </summary>
+        public PooledStack(T[] array, ClearMode clearMode) : this(array.AsSpan(), clearMode, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Fills a Stack with the contents of a particular collection.  The items are
+        /// pushed onto the stack in the same order they are read by the enumerator.
+        /// </summary>
+        public PooledStack(T[] array, ArrayPool<T> customPool) : this(array.AsSpan(), ClearMode.Auto, customPool) { }
+
+        /// <summary>
+        /// Fills a Stack with the contents of a particular collection.  The items are
+        /// pushed onto the stack in the same order they are read by the enumerator.
+        /// </summary>
+        public PooledStack(T[] array, ClearMode clearMode, ArrayPool<T> customPool) : this(array.AsSpan(), clearMode, customPool) { }
+
+        /// <summary>
+        /// Fills a Stack with the contents of a particular collection.  The items are
+        /// pushed onto the stack in the same order they are read by the enumerator.
+        /// </summary>
+        public PooledStack(ReadOnlySpan<T> span) : this(span, ClearMode.Auto, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Fills a Stack with the contents of a particular collection.  The items are
+        /// pushed onto the stack in the same order they are read by the enumerator.
+        /// </summary>
+        public PooledStack(ReadOnlySpan<T> span, ClearMode clearMode) : this(span, clearMode, ArrayPool<T>.Shared) { }
+
+        /// <summary>
+        /// Fills a Stack with the contents of a particular collection.  The items are
+        /// pushed onto the stack in the same order they are read by the enumerator.
+        /// </summary>
+        public PooledStack(ReadOnlySpan<T> span, ArrayPool<T> customPool) : this(span, ClearMode.Auto, customPool) { }
+
+        /// <summary>
+        /// Fills a Stack with the contents of a particular collection.  The items are
+        /// pushed onto the stack in the same order they are read by the enumerator.
+        /// </summary>
+        public PooledStack(ReadOnlySpan<T> span, ClearMode clearMode, ArrayPool<T> customPool)
+        {
+            _pool = customPool ?? ArrayPool<T>.Shared;
+            _clearOnFree = ShouldClear(clearMode);
+            _array = _pool.Rent(span.Length);
+            span.CopyTo(_array);
+            _size = span.Length;
+        }
+
+        #endregion
+
+        /// <summary>
+        /// The number of items in the stack.
+        /// </summary>
+        public int Count => _size;
+
+        /// <summary>
+        /// Returns the ClearMode behavior for the collection, denoting whether values are
+        /// cleared from internal arrays before returning them to the pool.
+        /// </summary>
+        public ClearMode ClearMode => _clearOnFree ? ClearMode.Always : ClearMode.Never;
+
+        bool ICollection.IsSynchronized => false;
+
+        object ICollection.SyncRoot
+        {
+            get
+            {
+                if (_syncRoot == null)
+                {
+                    Interlocked.CompareExchange<object>(ref _syncRoot, new object(), null);
+                }
+                return _syncRoot;
+            }
+        }
+
+        /// <summary>
+        /// Removes all Objects from the Stack.
+        /// </summary>
+        public void Clear()
+        {
+            if (_clearOnFree)
+            {
+                Array.Clear(_array, 0, _size); // clear the elements so that the gc can reclaim the references.
+            }
+            _size = 0;
+            _version++;
+        }
+
+        /// <summary>
+        /// Compares items using the default equality comparer
+        /// </summary>
+        public bool Contains(T item)
+        {
+            // PERF: Internally Array.LastIndexOf calls
+            // EqualityComparer<T>.Default.LastIndexOf, which
+            // is specialized for different types. This
+            // boosts performance since instead of making a
+            // virtual method call each iteration of the loop,
+            // via EqualityComparer<T>.Default.Equals, we
+            // only make one virtual call to EqualityComparer.LastIndexOf.
+
+            return _size != 0 && Array.LastIndexOf(_array, item, _size - 1) != -1;
+        }
+
+        /// <summary>
+        /// This method removes all items which match the predicate.
+        /// The complexity is O(n).
+        /// </summary>
+        public int RemoveWhere(Func<T, bool> match)
+        {
+            if (match == null)
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
+
+            int freeIndex = 0;   // the first free slot in items array
+
+            // Find the first item which needs to be removed.
+            while (freeIndex < _size && !match(_array[freeIndex]))
+                freeIndex++;
+            if (freeIndex >= _size)
+                return 0;
+
+            int current = freeIndex + 1;
+            while (current < _size)
+            {
+                // Find the first item which needs to be kept.
+                while (current < _size && match(_array[current]))
+                    current++;
+
+                if (current < _size)
+                {
+                    // copy item to the free slot.
+                    _array[freeIndex++] = _array[current++];
+                }
+            }
+
+            if (_clearOnFree)
+            {
+                // Clear the removed elements so that the gc can reclaim the references.
+                Array.Clear(_array, freeIndex, _size - freeIndex);
+            }
+
+            int result = _size - freeIndex;
+            _size = freeIndex;
+            _version++;
+            return result;
+        }
+
+        // Copies the stack into an array.
+        public void CopyTo(T[] array, int arrayIndex)
+        {
+            if (array == null)
+            {
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+            }
+
+            if (arrayIndex < 0 || arrayIndex > array.Length)
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex);
+            }
+
+            if (array.Length - arrayIndex < _size)
+            {
+                ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+            }
+
+            Debug.Assert(array != _array);
+            int srcIndex = 0;
+            int dstIndex = arrayIndex + _size;
+            while (srcIndex < _size)
+            {
+                array[--dstIndex] = _array[srcIndex++];
+            }
+        }
+
+        public void CopyTo(Span<T> span)
+        {
+            if (span.Length < _size)
+            {
+                ThrowHelper.ThrowArgumentException_DestinationTooShort();
+            }
+
+            int srcIndex = 0;
+            int dstIndex = _size;
+            while (srcIndex < _size)
+            {
+                span[--dstIndex] = _array[srcIndex++];
+            }
+        }
+
+        void ICollection.CopyTo(Array array, int arrayIndex)
+        {
+            if (array == null)
+            {
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+            }
+
+            if (array.Rank != 1)
+            {
+                ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
+            }
+
+            if (array.GetLowerBound(0) != 0)
+            {
+                ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound, ExceptionArgument.array);
+            }
+
+            if (arrayIndex < 0 || arrayIndex > array.Length)
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex);
+            }
+
+            if (array.Length - arrayIndex < _size)
+            {
+                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
+            }
+
+            try
+            {
+                Array.Copy(_array, 0, array, arrayIndex, _size);
+                Array.Reverse(array, arrayIndex, _size);
+            }
+            catch (ArrayTypeMismatchException)
+            {
+                ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+            }
+        }
+
+        /// <summary>
+        /// Returns an IEnumerator for this PooledStack.
+        /// </summary>
+        /// <returns></returns>
+        public Enumerator GetEnumerator()
+            => new Enumerator(this);
+
+        /// <internalonly/>
+        IEnumerator<T> IEnumerable<T>.GetEnumerator()
+            => new Enumerator(this);
+
+        IEnumerator IEnumerable.GetEnumerator()
+            => new Enumerator(this);
+
+        public void TrimExcess()
+        {
+            if (_size == 0)
+            {
+                ReturnArray(replaceWith: Array.Empty<T>());
+                _version++;
+                return;
+            }
+
+            int threshold = (int)(_array.Length * 0.9);
+            if (_size < threshold)
+            {
+                var newArray = _pool.Rent(_size);
+                if (newArray.Length < _array.Length)
+                {
+                    Array.Copy(_array, newArray, _size);
+                    ReturnArray(replaceWith: newArray);
+                    _version++;
+                }
+                else
+                {
+                    // The array from the pool wasn't any smaller than the one we already had,
+                    // (we can only control minimum size) so return it and do nothing.
+                    // If we create an exact-sized array not from the pool, we'll
+                    // get an exception when returning it to the pool.
+                    _pool.Return(newArray);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Returns the top object on the stack without removing it.  If the stack
+        /// is empty, Peek throws an InvalidOperationException.
+        /// </summary>
+        public T Peek()
+        {
+            int size = _size - 1;
+            T[] array = _array;
+
+            if ((uint)size >= (uint)array.Length)
+            {
+                ThrowForEmptyStack();
+            }
+
+            return array[size];
+        }
+
+        public bool TryPeek(out T result)
+        {
+            int size = _size - 1;
+            T[] array = _array;
+
+            if ((uint)size >= (uint)array.Length)
+            {
+                result = default;
+                return false;
+            }
+            result = array[size];
+            return true;
+        }
+
+        /// <summary>
+        /// Pops an item from the top of the stack.  If the stack is empty, Pop
+        /// throws an InvalidOperationException.
+        /// </summary>
+        public T Pop()
+        {
+            int size = _size - 1;
+            T[] array = _array;
+
+            // if (_size == 0) is equivalent to if (size == -1), and this case
+            // is covered with (uint)size, thus allowing bounds check elimination 
+            // https://github.com/dotnet/coreclr/pull/9773
+            if ((uint)size >= (uint)array.Length)
+            {
+                ThrowForEmptyStack();
+            }
+
+            _version++;
+            _size = size;
+            T item = array[size];
+            if (_clearOnFree)
+            {
+                array[size] = default;     // Free memory quicker.
+            }
+            return item;
+        }
+
+        public bool TryPop(out T result)
+        {
+            int size = _size - 1;
+            T[] array = _array;
+
+            if ((uint)size >= (uint)array.Length)
+            {
+                result = default;
+                return false;
+            }
+
+            _version++;
+            _size = size;
+            result = array[size];
+            if (_clearOnFree)
+            {
+                array[size] = default;     // Free memory quicker.
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// Pushes an item to the top of the stack.
+        /// </summary>
+        public void Push(T item)
+        {
+            int size = _size;
+            T[] array = _array;
+
+            if ((uint)size < (uint)array.Length)
+            {
+                array[size] = item;
+                _version++;
+                _size = size + 1;
+            }
+            else
+            {
+                PushWithResize(item);
+            }
+        }
+
+        // Non-inline from Stack.Push to improve its code quality as uncommon path
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private void PushWithResize(T item)
+        {
+            var newArray = _pool.Rent((_array.Length == 0) ? DefaultCapacity : 2 * _array.Length);
+            Array.Copy(_array, newArray, _size);
+            ReturnArray(replaceWith: newArray);
+            _array[_size] = item;
+            _version++;
+            _size++;
+        }
+
+        /// <summary>
+        /// Copies the Stack to an array, in the same order Pop would return the items.
+        /// </summary>
+        public T[] ToArray()
+        {
+            if (_size == 0)
+                return Array.Empty<T>();
+
+            T[] objArray = new T[_size];
+            int i = 0;
+            while (i < _size)
+            {
+                objArray[i] = _array[_size - i - 1];
+                i++;
+            }
+            return objArray;
+        }
+
+        private void ThrowForEmptyStack()
+        {
+            Debug.Assert(_size == 0);
+            throw new InvalidOperationException("Stack was empty.");
+        }
+
+        private void ReturnArray(T[] replaceWith = null)
+        {
+            if (_array?.Length > 0)
+            {
+                try
+                {
+                    _pool.Return(_array, clearArray: _clearOnFree);
+                }
+                catch (ArgumentException)
+                {
+                    // oh well, the array pool didn't like our array
+                }
+            }
+
+            if (!(replaceWith is null))
+            {
+                _array = replaceWith;
+            }
+        }
+
+        private static bool ShouldClear(ClearMode mode)
+        {
+#if NETCOREAPP2_1
+            return mode == ClearMode.Always
+                || (mode == ClearMode.Auto && RuntimeHelpers.IsReferenceOrContainsReferences<T>());
+#else
+            return mode != ClearMode.Never;
+#endif
+        }
+
+        public void Dispose()
+        {
+            ReturnArray(replaceWith: Array.Empty<T>());
+            _size = 0;
+            _version++;
+        }
+
+        void IDeserializationCallback.OnDeserialization(object sender)
+        {
+            // We can't serialize array pools, so deserialized PooledStacks will
+            // have to use the shared pool, even if they were using a custom pool
+            // before serialization.
+            _pool = ArrayPool<T>.Shared;
+        }
+
+        [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "not an expected scenario")]
+        public struct Enumerator : IEnumerator<T>, IEnumerator
+        {
+            private readonly PooledStack<T> _stack;
+            private readonly int _version;
+            private int _index;
+            private T _currentElement;
+
+            internal Enumerator(PooledStack<T> stack)
+            {
+                _stack = stack;
+                _version = stack._version;
+                _index = -2;
+                _currentElement = default;
+            }
+
+            public void Dispose()
+            {
+                _index = -1;
+            }
+
+            public bool MoveNext()
+            {
+                bool retval;
+                if (_version != _stack._version)
+                    throw new InvalidOperationException("Collection was modified during enumeration.");
+                if (_index == -2)
+                {  // First call to enumerator.
+                    _index = _stack._size - 1;
+                    retval = (_index >= 0);
+                    if (retval)
+                        _currentElement = _stack._array[_index];
+                    return retval;
+                }
+                if (_index == -1)
+                {  // End of enumeration.
+                    return false;
+                }
+
+                retval = (--_index >= 0);
+                if (retval)
+                    _currentElement = _stack._array[_index];
+                else
+                    _currentElement = default;
+                return retval;
+            }
+
+            public T Current
+            {
+                get
+                {
+                    if (_index < 0)
+                        ThrowEnumerationNotStartedOrEnded();
+                    return _currentElement;
+                }
+            }
+
+            private void ThrowEnumerationNotStartedOrEnded()
+            {
+                Debug.Assert(_index == -1 || _index == -2);
+                throw new InvalidOperationException(_index == -2 ? "Enumeration was not started." : "Enumeration has ended.");
+            }
+
+            object IEnumerator.Current
+            {
+                get { return Current; }
+            }
+
+            void IEnumerator.Reset()
+            {
+                if (_version != _stack._version)
+                    throw new InvalidOperationException("Collection was modified during enumeration.");
+                _index = -2;
+                _currentElement = default;
+            }
+        }
+    }
+}

+ 28 - 0
src/Avalonia.Base/Collections/Pooled/StackDebugView.cs

@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+
+namespace Avalonia.Collections.Pooled
+{
+    internal sealed class StackDebugView<T>
+    {
+        private readonly PooledStack<T> _stack;
+
+        public StackDebugView(PooledStack<T> stack)
+        {
+            _stack = stack ?? throw new ArgumentNullException(nameof(stack));
+        }
+
+        [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+        public T[] Items
+        {
+            get
+            {
+                return _stack.ToArray();
+            }
+        }
+    }
+}

+ 691 - 0
src/Avalonia.Base/Collections/Pooled/ThrowHelper.cs

@@ -0,0 +1,691 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+// This file defines an internal class used to throw exceptions in BCL code.
+// The main purpose is to reduce code size.
+//
+// The old way to throw an exception generates quite a lot IL code and assembly code.
+// Following is an example:
+//     C# source
+//          throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
+//     IL code:
+//          IL_0003:  ldstr      "key"
+//          IL_0008:  ldstr      "ArgumentNull_Key"
+//          IL_000d:  call       string System.Environment::GetResourceString(string)
+//          IL_0012:  newobj     instance void System.ArgumentNullException::.ctor(string,string)
+//          IL_0017:  throw
+//    which is 21bytes in IL.
+//
+// So we want to get rid of the ldstr and call to Environment.GetResource in IL.
+// In order to do that, I created two enums: ExceptionResource, ExceptionArgument to represent the
+// argument name and resource name in a small integer. The source code will be changed to
+//    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key, ExceptionResource.ArgumentNull_Key);
+//
+// The IL code will be 7 bytes.
+//    IL_0008:  ldc.i4.4
+//    IL_0009:  ldc.i4.4
+//    IL_000a:  call       void System.ThrowHelper::ThrowArgumentNullException(valuetype System.ExceptionArgument)
+//    IL_000f:  ldarg.0
+//
+// This will also reduce the Jitted code size a lot.
+//
+// It is very important we do this for generic classes because we can easily generate the same code
+// multiple times for different instantiation.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+
+namespace Avalonia.Collections.Pooled
+{
+    internal static class ThrowHelper
+    {
+        internal static void ThrowArrayTypeMismatchException()
+        {
+            throw new ArrayTypeMismatchException();
+        }
+
+        internal static void ThrowIndexOutOfRangeException()
+        {
+            throw new IndexOutOfRangeException();
+        }
+
+        internal static void ThrowArgumentOutOfRangeException()
+        {
+            throw new ArgumentOutOfRangeException();
+        }
+
+        internal static void ThrowArgumentException_DestinationTooShort()
+        {
+            throw new ArgumentException("Destination too short.");
+        }
+
+        internal static void ThrowArgumentException_OverlapAlignmentMismatch()
+        {
+            throw new ArgumentException("Overlap alignment mismatch.");
+        }
+
+        internal static void ThrowArgumentOutOfRange_IndexException()
+        {
+            throw GetArgumentOutOfRangeException(ExceptionArgument.index,
+                                                    ExceptionResource.ArgumentOutOfRange_Index);
+        }
+
+        internal static void ThrowIndexArgumentOutOfRange_NeedNonNegNumException()
+        {
+            throw GetArgumentOutOfRangeException(ExceptionArgument.index,
+                                                    ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+        }
+
+        internal static void ThrowValueArgumentOutOfRange_NeedNonNegNumException()
+        {
+            throw GetArgumentOutOfRangeException(ExceptionArgument.value,
+                                                    ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+        }
+
+        internal static void ThrowLengthArgumentOutOfRange_ArgumentOutOfRange_NeedNonNegNum()
+        {
+            throw GetArgumentOutOfRangeException(ExceptionArgument.length,
+                                                    ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+        }
+
+        internal static void ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index()
+        {
+            throw GetArgumentOutOfRangeException(ExceptionArgument.startIndex,
+                                                    ExceptionResource.ArgumentOutOfRange_Index);
+        }
+
+        internal static void ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count()
+        {
+            throw GetArgumentOutOfRangeException(ExceptionArgument.count,
+                                                    ExceptionResource.ArgumentOutOfRange_Count);
+        }
+
+        internal static void ThrowWrongKeyTypeArgumentException<T>(T key, Type targetType)
+        {
+            // Generic key to move the boxing to the right hand side of throw
+            throw GetWrongKeyTypeArgumentException((object)key, targetType);
+        }
+
+        internal static void ThrowWrongValueTypeArgumentException<T>(T value, Type targetType)
+        {
+            // Generic key to move the boxing to the right hand side of throw
+            throw GetWrongValueTypeArgumentException((object)value, targetType);
+        }
+
+        private static ArgumentException GetAddingDuplicateWithKeyArgumentException(object key)
+        {
+            return new ArgumentException($"Error adding duplicate with key: {key}.");
+        }
+
+        internal static void ThrowAddingDuplicateWithKeyArgumentException<T>(T key)
+        {
+            // Generic key to move the boxing to the right hand side of throw
+            throw GetAddingDuplicateWithKeyArgumentException((object)key);
+        }
+
+        internal static void ThrowKeyNotFoundException<T>(T key)
+        {
+            // Generic key to move the boxing to the right hand side of throw
+            throw GetKeyNotFoundException((object)key);
+        }
+
+        internal static void ThrowArgumentException(ExceptionResource resource)
+        {
+            throw GetArgumentException(resource);
+        }
+
+        internal static void ThrowArgumentException(ExceptionResource resource, ExceptionArgument argument)
+        {
+            throw GetArgumentException(resource, argument);
+        }
+
+        private static ArgumentNullException GetArgumentNullException(ExceptionArgument argument)
+        {
+            return new ArgumentNullException(GetArgumentName(argument));
+        }
+
+        internal static void ThrowArgumentNullException(ExceptionArgument argument)
+        {
+            throw GetArgumentNullException(argument);
+        }
+
+        internal static void ThrowArgumentNullException(ExceptionResource resource)
+        {
+            throw new ArgumentNullException(GetResourceString(resource));
+        }
+
+        internal static void ThrowArgumentNullException(ExceptionArgument argument, ExceptionResource resource)
+        {
+            throw new ArgumentNullException(GetArgumentName(argument), GetResourceString(resource));
+        }
+
+        internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument)
+        {
+            throw new ArgumentOutOfRangeException(GetArgumentName(argument));
+        }
+
+        internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
+        {
+            throw GetArgumentOutOfRangeException(argument, resource);
+        }
+
+        internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, int paramNumber, ExceptionResource resource)
+        {
+            throw GetArgumentOutOfRangeException(argument, paramNumber, resource);
+        }
+
+        internal static void ThrowInvalidOperationException(ExceptionResource resource)
+        {
+            throw GetInvalidOperationException(resource);
+        }
+
+        internal static void ThrowInvalidOperationException(ExceptionResource resource, Exception e)
+        {
+            throw new InvalidOperationException(GetResourceString(resource), e);
+        }
+
+        internal static void ThrowSerializationException(ExceptionResource resource)
+        {
+            throw new SerializationException(GetResourceString(resource));
+        }
+
+        internal static void ThrowSecurityException(ExceptionResource resource)
+        {
+            throw new System.Security.SecurityException(GetResourceString(resource));
+        }
+
+        internal static void ThrowRankException(ExceptionResource resource)
+        {
+            throw new RankException(GetResourceString(resource));
+        }
+
+        internal static void ThrowNotSupportedException(ExceptionResource resource)
+        {
+            throw new NotSupportedException(GetResourceString(resource));
+        }
+
+        internal static void ThrowUnauthorizedAccessException(ExceptionResource resource)
+        {
+            throw new UnauthorizedAccessException(GetResourceString(resource));
+        }
+
+        internal static void ThrowObjectDisposedException(string objectName, ExceptionResource resource)
+        {
+            throw new ObjectDisposedException(objectName, GetResourceString(resource));
+        }
+
+        internal static void ThrowObjectDisposedException(ExceptionResource resource)
+        {
+            throw new ObjectDisposedException(null, GetResourceString(resource));
+        }
+
+        internal static void ThrowNotSupportedException()
+        {
+            throw new NotSupportedException();
+        }
+
+        internal static void ThrowAggregateException(List<Exception> exceptions)
+        {
+            throw new AggregateException(exceptions);
+        }
+
+        internal static void ThrowOutOfMemoryException()
+        {
+            throw new OutOfMemoryException();
+        }
+
+        internal static void ThrowArgumentException_Argument_InvalidArrayType()
+        {
+            throw new ArgumentException("Invalid array type.");
+        }
+
+        internal static void ThrowInvalidOperationException_InvalidOperation_EnumNotStarted()
+        {
+            throw new InvalidOperationException("Enumeration has not started.");
+        }
+
+        internal static void ThrowInvalidOperationException_InvalidOperation_EnumEnded()
+        {
+            throw new InvalidOperationException("Enumeration has ended.");
+        }
+
+        internal static void ThrowInvalidOperationException_EnumCurrent(int index)
+        {
+            throw GetInvalidOperationException_EnumCurrent(index);
+        }
+
+        internal static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion()
+        {
+            throw new InvalidOperationException("Collection was modified during enumeration.");
+        }
+
+        internal static void ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen()
+        {
+            throw new InvalidOperationException("Invalid enumerator state: enumeration cannot proceed.");
+        }
+
+        internal static void ThrowInvalidOperationException_InvalidOperation_NoValue()
+        {
+            throw new InvalidOperationException("No value provided.");
+        }
+
+        internal static void ThrowInvalidOperationException_ConcurrentOperationsNotSupported()
+        {
+            throw new InvalidOperationException("Concurrent operations are not supported.");
+        }
+
+        internal static void ThrowInvalidOperationException_HandleIsNotInitialized()
+        {
+            throw new InvalidOperationException("Handle is not initialized.");
+        }
+
+        internal static void ThrowFormatException_BadFormatSpecifier()
+        {
+            throw new FormatException("Bad format specifier.");
+        }
+
+        private static ArgumentException GetArgumentException(ExceptionResource resource)
+        {
+            return new ArgumentException(GetResourceString(resource));
+        }
+
+        private static InvalidOperationException GetInvalidOperationException(ExceptionResource resource)
+        {
+            return new InvalidOperationException(GetResourceString(resource));
+        }
+
+        private static ArgumentException GetWrongKeyTypeArgumentException(object key, Type targetType)
+        {
+            return new ArgumentException($"Wrong key type. Expected {targetType}, got: '{key}'.", nameof(key));
+        }
+
+        private static ArgumentException GetWrongValueTypeArgumentException(object value, Type targetType)
+        {
+            return new ArgumentException($"Wrong value type. Expected {targetType}, got: '{value}'.", nameof(value));
+        }
+
+        private static KeyNotFoundException GetKeyNotFoundException(object key)
+        {
+            return new KeyNotFoundException($"Key not found: {key}");
+        }
+
+        private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
+        {
+            return new ArgumentOutOfRangeException(GetArgumentName(argument), GetResourceString(resource));
+        }
+
+        private static ArgumentException GetArgumentException(ExceptionResource resource, ExceptionArgument argument)
+        {
+            return new ArgumentException(GetResourceString(resource), GetArgumentName(argument));
+        }
+
+        private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument, int paramNumber, ExceptionResource resource)
+        {
+            return new ArgumentOutOfRangeException(GetArgumentName(argument) + "[" + paramNumber.ToString() + "]", GetResourceString(resource));
+        }
+
+        private static InvalidOperationException GetInvalidOperationException_EnumCurrent(int index)
+        {
+            return new InvalidOperationException(
+                index < 0 ?
+                "Enumeration has not started" :
+                "Enumeration has ended");
+        }
+
+        // Allow nulls for reference types and Nullable<U>, but not for value types.
+        // Aggressively inline so the jit evaluates the if in place and either drops the call altogether
+        // Or just leaves null test and call to the Non-returning ThrowHelper.ThrowArgumentNullException
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static void IfNullAndNullsAreIllegalThenThrow<T>(object value, ExceptionArgument argName)
+        {
+            // Note that default(T) is not equal to null for value types except when T is Nullable<U>.
+            if (!(default(T) == null) && value == null)
+                ThrowHelper.ThrowArgumentNullException(argName);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static void ThrowForUnsupportedVectorBaseType<T>() where T : struct
+        {
+            if (typeof(T) != typeof(byte) && typeof(T) != typeof(sbyte) &&
+                typeof(T) != typeof(short) && typeof(T) != typeof(ushort) &&
+                typeof(T) != typeof(int) && typeof(T) != typeof(uint) &&
+                typeof(T) != typeof(long) && typeof(T) != typeof(ulong) &&
+                typeof(T) != typeof(float) && typeof(T) != typeof(double))
+            {
+                ThrowNotSupportedException(ExceptionResource.Arg_TypeNotSupported);
+            }
+        }
+
+#if false // Reflection-based implementation does not work for CoreRT/ProjectN
+        // This function will convert an ExceptionArgument enum value to the argument name string.
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static string GetArgumentName(ExceptionArgument argument)
+        {
+            Debug.Assert(Enum.IsDefined(typeof(ExceptionArgument), argument),
+                "The enum value is not defined, please check the ExceptionArgument Enum.");
+
+            return argument.ToString();
+        }
+#endif
+
+        private static string GetArgumentName(ExceptionArgument argument)
+        {
+            switch (argument)
+            {
+                case ExceptionArgument.obj:
+                    return "obj";
+                case ExceptionArgument.dictionary:
+                    return "dictionary";
+                case ExceptionArgument.array:
+                    return "array";
+                case ExceptionArgument.info:
+                    return "info";
+                case ExceptionArgument.key:
+                    return "key";
+                case ExceptionArgument.text:
+                    return "text";
+                case ExceptionArgument.values:
+                    return "values";
+                case ExceptionArgument.value:
+                    return "value";
+                case ExceptionArgument.startIndex:
+                    return "startIndex";
+                case ExceptionArgument.task:
+                    return "task";
+                case ExceptionArgument.ch:
+                    return "ch";
+                case ExceptionArgument.s:
+                    return "s";
+                case ExceptionArgument.input:
+                    return "input";
+                case ExceptionArgument.list:
+                    return "list";
+                case ExceptionArgument.index:
+                    return "index";
+                case ExceptionArgument.capacity:
+                    return "capacity";
+                case ExceptionArgument.collection:
+                    return "collection";
+                case ExceptionArgument.item:
+                    return "item";
+                case ExceptionArgument.converter:
+                    return "converter";
+                case ExceptionArgument.match:
+                    return "match";
+                case ExceptionArgument.count:
+                    return "count";
+                case ExceptionArgument.action:
+                    return "action";
+                case ExceptionArgument.comparison:
+                    return "comparison";
+                case ExceptionArgument.exceptions:
+                    return "exceptions";
+                case ExceptionArgument.exception:
+                    return "exception";
+                case ExceptionArgument.enumerable:
+                    return "enumerable";
+                case ExceptionArgument.start:
+                    return "start";
+                case ExceptionArgument.format:
+                    return "format";
+                case ExceptionArgument.culture:
+                    return "culture";
+                case ExceptionArgument.comparer:
+                    return "comparer";
+                case ExceptionArgument.comparable:
+                    return "comparable";
+                case ExceptionArgument.source:
+                    return "source";
+                case ExceptionArgument.state:
+                    return "state";
+                case ExceptionArgument.length:
+                    return "length";
+                case ExceptionArgument.comparisonType:
+                    return "comparisonType";
+                case ExceptionArgument.manager:
+                    return "manager";
+                case ExceptionArgument.sourceBytesToCopy:
+                    return "sourceBytesToCopy";
+                case ExceptionArgument.callBack:
+                    return "callBack";
+                case ExceptionArgument.creationOptions:
+                    return "creationOptions";
+                case ExceptionArgument.function:
+                    return "function";
+                case ExceptionArgument.delay:
+                    return "delay";
+                case ExceptionArgument.millisecondsDelay:
+                    return "millisecondsDelay";
+                case ExceptionArgument.millisecondsTimeout:
+                    return "millisecondsTimeout";
+                case ExceptionArgument.timeout:
+                    return "timeout";
+                case ExceptionArgument.type:
+                    return "type";
+                case ExceptionArgument.sourceIndex:
+                    return "sourceIndex";
+                case ExceptionArgument.sourceArray:
+                    return "sourceArray";
+                case ExceptionArgument.destinationIndex:
+                    return "destinationIndex";
+                case ExceptionArgument.destinationArray:
+                    return "destinationArray";
+                case ExceptionArgument.other:
+                    return "other";
+                case ExceptionArgument.newSize:
+                    return "newSize";
+                case ExceptionArgument.lowerBounds:
+                    return "lowerBounds";
+                case ExceptionArgument.lengths:
+                    return "lengths";
+                case ExceptionArgument.len:
+                    return "len";
+                case ExceptionArgument.keys:
+                    return "keys";
+                case ExceptionArgument.indices:
+                    return "indices";
+                case ExceptionArgument.endIndex:
+                    return "endIndex";
+                case ExceptionArgument.elementType:
+                    return "elementType";
+                case ExceptionArgument.arrayIndex:
+                    return "arrayIndex";
+                default:
+                    Debug.Fail("The enum value is not defined, please check the ExceptionArgument Enum.");
+                    return argument.ToString();
+            }
+        }
+
+#if false // Reflection-based implementation does not work for CoreRT/ProjectN
+        // This function will convert an ExceptionResource enum value to the resource string.
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static string GetResourceString(ExceptionResource resource)
+        {
+            Debug.Assert(Enum.IsDefined(typeof(ExceptionResource), resource),
+                "The enum value is not defined, please check the ExceptionResource Enum.");
+
+            return SR.GetResourceString(resource.ToString());
+        }
+#endif
+
+        private static string GetResourceString(ExceptionResource resource)
+        {
+            switch (resource)
+            {
+                case ExceptionResource.ArgumentOutOfRange_Index:
+                    return "Argument 'index' was out of the range of valid values.";
+                case ExceptionResource.ArgumentOutOfRange_Count:
+                    return "Argument 'count' was out of the range of valid values.";
+                case ExceptionResource.Arg_ArrayPlusOffTooSmall:
+                    return "Array plus offset too small.";
+                case ExceptionResource.NotSupported_ReadOnlyCollection:
+                    return "This operation is not supported on a read-only collection.";
+                case ExceptionResource.Arg_RankMultiDimNotSupported:
+                    return "Multi-dimensional arrays are not supported.";
+                case ExceptionResource.Arg_NonZeroLowerBound:
+                    return "Arrays with a non-zero lower bound are not supported.";
+                case ExceptionResource.ArgumentOutOfRange_ListInsert:
+                    return "Insertion index was out of the range of valid values.";
+                case ExceptionResource.ArgumentOutOfRange_NeedNonNegNum:
+                    return "The number must be non-negative.";
+                case ExceptionResource.ArgumentOutOfRange_SmallCapacity:
+                    return "The capacity cannot be set below the current Count.";
+                case ExceptionResource.Argument_InvalidOffLen:
+                    return "Invalid offset length.";
+                case ExceptionResource.ArgumentOutOfRange_BiggerThanCollection:
+                    return "The given value was larger than the size of the collection.";
+                case ExceptionResource.Serialization_MissingKeys:
+                    return "Serialization error: missing keys.";
+                case ExceptionResource.Serialization_NullKey:
+                    return "Serialization error: null key.";
+                case ExceptionResource.NotSupported_KeyCollectionSet:
+                    return "The KeyCollection does not support modification.";
+                case ExceptionResource.NotSupported_ValueCollectionSet:
+                    return "The ValueCollection does not support modification.";
+                case ExceptionResource.InvalidOperation_NullArray:
+                    return "Null arrays are not supported.";
+                case ExceptionResource.InvalidOperation_HSCapacityOverflow:
+                    return "Set hash capacity overflow. Cannot increase size.";
+                case ExceptionResource.NotSupported_StringComparison:
+                    return "String comparison not supported.";
+                case ExceptionResource.ConcurrentCollection_SyncRoot_NotSupported:
+                    return "SyncRoot not supported.";
+                case ExceptionResource.ArgumentException_OtherNotArrayOfCorrectLength:
+                    return "The other array is not of the correct length.";
+                case ExceptionResource.ArgumentOutOfRange_EndIndexStartIndex:
+                    return "The end index does not come after the start index.";
+                case ExceptionResource.ArgumentOutOfRange_HugeArrayNotSupported:
+                    return "Huge arrays are not supported.";
+                case ExceptionResource.Argument_AddingDuplicate:
+                    return "Duplicate item added.";
+                case ExceptionResource.Argument_InvalidArgumentForComparison:
+                    return "Invalid argument for comparison.";
+                case ExceptionResource.Arg_LowerBoundsMustMatch:
+                    return "Array lower bounds must match.";
+                case ExceptionResource.Arg_MustBeType:
+                    return "Argument must be of type: ";
+                case ExceptionResource.InvalidOperation_IComparerFailed:
+                    return "IComparer failed.";
+                case ExceptionResource.NotSupported_FixedSizeCollection:
+                    return "This operation is not suppored on a fixed-size collection.";
+                case ExceptionResource.Rank_MultiDimNotSupported:
+                    return "Multi-dimensional arrays are not supported.";
+                case ExceptionResource.Arg_TypeNotSupported:
+                    return "Type not supported.";
+                default:
+                    Debug.Assert(false,
+                        "The enum value is not defined, please check the ExceptionResource Enum.");
+                    return resource.ToString();
+            }
+        }
+    }
+
+    //
+    // The convention for this enum is using the argument name as the enum name
+    //
+    internal enum ExceptionArgument
+    {
+        obj,
+        dictionary,
+        array,
+        info,
+        key,
+        text,
+        values,
+        value,
+        startIndex,
+        task,
+        ch,
+        s,
+        input,
+        list,
+        index,
+        capacity,
+        collection,
+        item,
+        converter,
+        match,
+        count,
+        action,
+        comparison,
+        exceptions,
+        exception,
+        enumerable,
+        start,
+        format,
+        culture,
+        comparer,
+        comparable,
+        source,
+        state,
+        length,
+        comparisonType,
+        manager,
+        sourceBytesToCopy,
+        callBack,
+        creationOptions,
+        function,
+        delay,
+        millisecondsDelay,
+        millisecondsTimeout,
+        timeout,
+        type,
+        sourceIndex,
+        sourceArray,
+        destinationIndex,
+        destinationArray,
+        other,
+        newSize,
+        lowerBounds,
+        lengths,
+        len,
+        keys,
+        indices,
+        endIndex,
+        elementType,
+        arrayIndex
+    }
+
+    //
+    // The convention for this enum is using the resource name as the enum name
+    //
+    internal enum ExceptionResource
+    {
+        ArgumentOutOfRange_Index,
+        ArgumentOutOfRange_Count,
+        Arg_ArrayPlusOffTooSmall,
+        NotSupported_ReadOnlyCollection,
+        Arg_RankMultiDimNotSupported,
+        Arg_NonZeroLowerBound,
+        ArgumentOutOfRange_ListInsert,
+        ArgumentOutOfRange_NeedNonNegNum,
+        ArgumentOutOfRange_SmallCapacity,
+        Argument_InvalidOffLen,
+        ArgumentOutOfRange_BiggerThanCollection,
+        Serialization_MissingKeys,
+        Serialization_NullKey,
+        NotSupported_KeyCollectionSet,
+        NotSupported_ValueCollectionSet,
+        InvalidOperation_NullArray,
+        InvalidOperation_HSCapacityOverflow,
+        NotSupported_StringComparison,
+        ConcurrentCollection_SyncRoot_NotSupported,
+        ArgumentException_OtherNotArrayOfCorrectLength,
+        ArgumentOutOfRange_EndIndexStartIndex,
+        ArgumentOutOfRange_HugeArrayNotSupported,
+        Argument_AddingDuplicate,
+        Argument_InvalidArgumentForComparison,
+        Arg_LowerBoundsMustMatch,
+        Arg_MustBeType,
+        InvalidOperation_IComparerFailed,
+        NotSupported_FixedSizeCollection,
+        Rank_MultiDimNotSupported,
+        Arg_TypeNotSupported,
+    }
+}

+ 6 - 0
src/Avalonia.Base/Data/BindingNotification.cs

@@ -30,6 +30,12 @@ namespace Avalonia.Data
     /// Represents a binding notification that can be a valid binding value, or a binding or
     /// data validation error.
     /// </summary>
+    /// <remarks>
+    /// This class is very similar to <see cref="BindingValue{T}"/>, but where <see cref="BindingValue{T}"/>
+    /// is used by typed bindings, this class is used to hold binding and data validation errors in
+    /// untyped bindings. As Avalonia moves towards using typed bindings by default we may want to remove
+    /// this class.
+    /// </remarks>
     public class BindingNotification
     {
         /// <summary>

+ 17 - 3
src/Avalonia.Base/Data/BindingOperations.cs

@@ -2,7 +2,6 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Linq;
 using System.Reactive.Disposables;
 using System.Reactive.Linq;
 using Avalonia.Reactive;
@@ -57,22 +56,37 @@ namespace Avalonia.Data
 
                     if (source != null)
                     {
+                        // Perf: Avoid allocating closure in the outer scope.
+                        var targetCopy = target;
+                        var propertyCopy = property;
+                        var bindingCopy = binding;
+
                         return source
                             .Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue)
                             .Take(1)
-                            .Subscribe(x => target.SetValue(property, x, binding.Priority));
+                            .Subscribe(x => targetCopy.SetValue(
+                                propertyCopy,
+                                BindingNotification.ExtractValue(x),
+                                bindingCopy.Priority));
                     }
                     else
                     {
                         target.SetValue(property, binding.Value, binding.Priority);
                         return Disposable.Empty;
                     }
+
                 case BindingMode.OneWayToSource:
+                {
+                    // Perf: Avoid allocating closure in the outer scope.
+                    var bindingCopy = binding;
+
                     return Observable.CombineLatest(
                         binding.Observable,
                         target.GetObservable(property),
                         (_, v) => v)
-                    .Subscribe(x => binding.Subject.OnNext(x));
+                    .Subscribe(x => bindingCopy.Subject.OnNext(x));
+                }
+
                 default:
                     throw new ArgumentException("Invalid binding mode.");
             }

+ 28 - 7
src/Avalonia.Base/Data/BindingValue.cs

@@ -8,6 +8,7 @@ namespace Avalonia.Data
     /// <summary>
     /// Describes the type of a <see cref="BindingValue{T}"/>.
     /// </summary>
+    [Flags]
     public enum BindingValueType
     {
         /// <summary>
@@ -135,10 +136,10 @@ namespace Avalonia.Data
         /// Converts the binding value to an <see cref="Optional{T}"/>.
         /// </summary>
         /// <returns></returns>
-        public Optional<T> ToOptional() => HasValue ? new Optional<T>(Value) : default;
+        public Optional<T> ToOptional() => HasValue ? new Optional<T>(_value) : default;
 
         /// <inheritdoc/>
-        public override string ToString() => HasError ? $"Error: {Error!.Message}" : Value?.ToString() ?? "(null)";
+        public override string ToString() => HasError ? $"Error: {Error!.Message}" : _value?.ToString() ?? "(null)";
 
         /// <summary>
         /// Converts the value to untyped representation, using <see cref="AvaloniaProperty.UnsetValue"/>,
@@ -152,7 +153,7 @@ namespace Avalonia.Data
             {
                 BindingValueType.UnsetValue => AvaloniaProperty.UnsetValue,
                 BindingValueType.DoNothing => BindingOperations.DoNothing,
-                BindingValueType.Value => Value,
+                BindingValueType.Value => _value,
                 BindingValueType.BindingError => 
                     new BindingNotification(Error, BindingErrorType.Error),
                 BindingValueType.BindingErrorWithFallback =>
@@ -161,7 +162,7 @@ namespace Avalonia.Data
                     new BindingNotification(Error, BindingErrorType.DataValidationError),
                 BindingValueType.DataValidationErrorWithFallback =>
                     new BindingNotification(Error, BindingErrorType.DataValidationError, Value),
-                _ => throw new NotSupportedException("Invalida BindingValueType."),
+                _ => throw new NotSupportedException("Invalid BindingValueType."),
             };
         }
 
@@ -185,12 +186,32 @@ namespace Avalonia.Data
             return new BindingValue<T>(type | BindingValueType.HasValue, value, Error);
         }
 
+        /// <summary>
+        /// Gets the value of the binding value if present, otherwise the default value.
+        /// </summary>
+        /// <returns>The value.</returns>
+        public T GetValueOrDefault() => HasValue ? _value : default;
+
         /// <summary>
         /// Gets the value of the binding value if present, otherwise a default value.
         /// </summary>
         /// <param name="defaultValue">The default value.</param>
         /// <returns>The value.</returns>
-        public T ValueOrDefault(T defaultValue = default) => HasValue ? Value : defaultValue;
+        public T GetValueOrDefault(T defaultValue) => HasValue ? _value : defaultValue;
+
+        /// <summary>
+        /// Gets the value if present, otherwise the default value.
+        /// </summary>
+        /// <returns>
+        /// The value if present and of the correct type, `default(TResult)` if the value is
+        /// not present or of an incorrect type.
+        /// </returns>
+        public TResult GetValueOrDefault<TResult>()
+        {
+            return HasValue ?
+                _value is TResult result ? result : default
+                : default;
+        }
 
         /// <summary>
         /// Gets the value of the binding value if present, otherwise a default value.
@@ -201,10 +222,10 @@ namespace Avalonia.Data
         /// present but not of the correct type or null, or <paramref name="defaultValue"/> if the
         /// value is not present.
         /// </returns>
-        public TResult ValueOrDefault<TResult>(TResult defaultValue = default)
+        public TResult GetValueOrDefault<TResult>(TResult defaultValue)
         {
             return HasValue ?
-                Value is TResult result ? result : default
+                _value is TResult result ? result : default
                 : defaultValue;
         }
 

+ 12 - 1
src/Avalonia.Base/Data/Core/BindingExpression.cs

@@ -21,6 +21,7 @@ namespace Avalonia.Data.Core
         private readonly ExpressionObserver _inner;
         private readonly Type _targetType;
         private readonly object _fallbackValue;
+        private readonly object _targetNullValue;
         private readonly BindingPriority _priority;
         InnerListener _innerListener;
         WeakReference<object> _value;
@@ -51,7 +52,7 @@ namespace Avalonia.Data.Core
             IValueConverter converter,
             object converterParameter = null,
             BindingPriority priority = BindingPriority.LocalValue)
-            : this(inner, targetType, AvaloniaProperty.UnsetValue, converter, converterParameter, priority)
+            : this(inner, targetType, AvaloniaProperty.UnsetValue, AvaloniaProperty.UnsetValue, converter, converterParameter, priority)
         {
         }
 
@@ -63,6 +64,9 @@ namespace Avalonia.Data.Core
         /// <param name="fallbackValue">
         /// The value to use when the binding is unable to produce a value.
         /// </param>
+        /// <param name="targetNullValue">
+        /// The value to use when the binding result is null.
+        /// </param>
         /// <param name="converter">The value converter to use.</param>
         /// <param name="converterParameter">
         /// A parameter to pass to <paramref name="converter"/>.
@@ -72,6 +76,7 @@ namespace Avalonia.Data.Core
             ExpressionObserver inner, 
             Type targetType,
             object fallbackValue,
+            object targetNullValue,
             IValueConverter converter,
             object converterParameter = null,
             BindingPriority priority = BindingPriority.LocalValue)
@@ -85,6 +90,7 @@ namespace Avalonia.Data.Core
             Converter = converter;
             ConverterParameter = converterParameter;
             _fallbackValue = fallbackValue;
+            _targetNullValue = targetNullValue;
             _priority = priority;
         }
 
@@ -196,6 +202,11 @@ namespace Avalonia.Data.Core
         /// <inheritdoc/>
         private object ConvertValue(object value)
         {
+            if (value == null && _targetNullValue != AvaloniaProperty.UnsetValue)
+            {
+                return _targetNullValue;
+            }
+
             if (value == BindingOperations.DoNothing)
             {
                 return value;

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

@@ -21,7 +21,7 @@ namespace Avalonia.Data.Core
         /// An ordered collection of property accessor plugins that can be used to customize
         /// the reading and subscription of property values on a type.
         /// </summary>
-        public static readonly IList<IPropertyAccessorPlugin> PropertyAccessors =
+        public static readonly List<IPropertyAccessorPlugin> PropertyAccessors =
             new List<IPropertyAccessorPlugin>
             {
                 new AvaloniaPropertyAccessorPlugin(),
@@ -33,7 +33,7 @@ namespace Avalonia.Data.Core
         /// An ordered collection of validation checker plugins that can be used to customize
         /// the validation of view model and model data.
         /// </summary>
-        public static readonly IList<IDataValidationPlugin> DataValidators =
+        public static readonly List<IDataValidationPlugin> DataValidators =
             new List<IDataValidationPlugin>
             {
                 new DataAnnotationsValidationPlugin(),
@@ -45,7 +45,7 @@ namespace Avalonia.Data.Core
         /// An ordered collection of stream plugins that can be used to customize the behavior
         /// of the '^' stream binding operator.
         /// </summary>
-        public static readonly IList<IStreamPlugin> StreamHandlers =
+        public static readonly List<IStreamPlugin> StreamHandlers =
             new List<IStreamPlugin>
             {
                 new TaskStreamPlugin(),

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

@@ -2,7 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Reactive.Linq;
+using System.Runtime.ExceptionServices;
 
 namespace Avalonia.Data.Core.Plugins
 {
@@ -76,7 +76,7 @@ namespace Avalonia.Data.Core.Plugins
             return false;
         }
 
-        private class Accessor : PropertyAccessorBase
+        private class Accessor : PropertyAccessorBase, IObserver<object>
         {
             private readonly WeakReference<AvaloniaObject> _reference;
             private readonly AvaloniaProperty _property;
@@ -117,7 +117,7 @@ namespace Avalonia.Data.Core.Plugins
 
             protected override void SubscribeCore()
             {
-                _subscription = Instance?.GetObservable(_property).Subscribe(PublishValue);
+                _subscription = Instance?.GetObservable(_property).Subscribe(this);
             }
 
             protected override void UnsubscribeCore()
@@ -125,6 +125,20 @@ namespace Avalonia.Data.Core.Plugins
                 _subscription?.Dispose();
                 _subscription = null;
             }
+
+            void IObserver<object>.OnCompleted()
+            {
+            }
+
+            void IObserver<object>.OnError(Exception error)
+            {
+                ExceptionDispatchInfo.Capture(error).Throw();
+            }
+
+            void IObserver<object>.OnNext(object value)
+            {
+                PublishValue(value);
+            }
         }
     }
 }

+ 11 - 3
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@@ -2,8 +2,6 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Linq;
-using System.Reactive.Linq;
 using Avalonia.Data.Core.Plugins;
 
 namespace Avalonia.Data.Core
@@ -41,7 +39,17 @@ namespace Avalonia.Data.Core
         {
             reference.TryGetTarget(out object target);
 
-            var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(target, PropertyName));
+            IPropertyAccessorPlugin plugin = null;
+
+            foreach (IPropertyAccessorPlugin x in ExpressionObserver.PropertyAccessors)
+            {
+                if (x.Match(target, PropertyName))
+                {
+                    plugin = x;
+                    break;
+                }
+            }
+
             var accessor = plugin?.Start(reference, PropertyName);
 
             if (_enableValidation && Next == null)

+ 26 - 6
src/Avalonia.Base/Data/Optional.cs

@@ -54,23 +54,43 @@ namespace Avalonia.Data
         public bool Equals(Optional<T> other) => this == other;
 
         /// <inheritdoc/>
-        public override int GetHashCode() => HasValue ? Value!.GetHashCode() : 0;
+        public override int GetHashCode() => HasValue ? _value?.GetHashCode() ?? 0 : 0;
 
         /// <summary>
         /// Casts the value (if any) to an <see cref="object"/>.
         /// </summary>
         /// <returns>The cast optional value.</returns>
-        public Optional<object> ToObject() => HasValue ? new Optional<object>(Value) : default;
+        public Optional<object> ToObject() => HasValue ? new Optional<object>(_value) : default;
 
         /// <inheritdoc/>
-        public override string ToString() => HasValue ? Value?.ToString() ?? "(null)" : "(empty)";
+        public override string ToString() => HasValue ? _value?.ToString() ?? "(null)" : "(empty)";
+
+        /// <summary>
+        /// Gets the value if present, otherwise the default value.
+        /// </summary>
+        /// <returns>The value.</returns>
+        public T GetValueOrDefault() => HasValue ? _value : default;
 
         /// <summary>
         /// Gets the value if present, otherwise a default value.
         /// </summary>
         /// <param name="defaultValue">The default value.</param>
         /// <returns>The value.</returns>
-        public T ValueOrDefault(T defaultValue = default) => HasValue ? Value : defaultValue;
+        public T GetValueOrDefault(T defaultValue) => HasValue ? _value : defaultValue;
+
+        /// <summary>
+        /// Gets the value if present, otherwise the default value.
+        /// </summary>
+        /// <returns>
+        /// The value if present and of the correct type, `default(TResult)` if the value is
+        /// not present or of an incorrect type.
+        /// </returns>
+        public TResult GetValueOrDefault<TResult>()
+        {
+            return HasValue ?
+                _value is TResult result ? result : default
+                : default;
+        }
 
         /// <summary>
         /// Gets the value if present, otherwise a default value.
@@ -81,10 +101,10 @@ namespace Avalonia.Data
         /// present but not of the correct type or null, or <paramref name="defaultValue"/> if the
         /// value is not present.
         /// </returns>
-        public TResult ValueOrDefault<TResult>(TResult defaultValue = default)
+        public TResult GetValueOrDefault<TResult>(TResult defaultValue)
         {
             return HasValue ?
-                Value is TResult result ? result : default
+                _value is TResult result ? result : default
                 : defaultValue;
         }
 

+ 1 - 1
src/Avalonia.Base/IAvaloniaObject.cs

@@ -118,7 +118,7 @@ namespace Avalonia
         /// </summary>
         /// <param name="child">The inheritance child.</param>
         /// <remarks>
-        /// Inheritance children will recieve a call to
+        /// Inheritance children will receive a call to
         /// <see cref="InheritedPropertyChanged{T}(AvaloniaProperty{T}, Optional{T}, Optional{T})"/>
         /// when an inheritable property value changes on the parent.
         /// </remarks>

+ 15 - 0
src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs

@@ -0,0 +1,15 @@
+// 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.Platform
+{
+    public interface IMacOSTopLevelPlatformHandle
+    {
+        IntPtr NSView { get; }
+        IntPtr GetNSViewRetained();
+        IntPtr NSWindow { get; }
+        IntPtr GetNSWindowRetained();
+    }
+}

+ 7 - 0
src/Avalonia.Base/PropertyStore/BindingEntry.cs

@@ -6,10 +6,17 @@ using Avalonia.Threading;
 
 namespace Avalonia.PropertyStore
 {
+    /// <summary>
+    /// Represents an untyped interface to <see cref="BindingEntry{T}"/>.
+    /// </summary>
     internal interface IBindingEntry : IPriorityValueEntry, IDisposable
     {
     }
 
+    /// <summary>
+    /// Stores a binding in a <see cref="ValueStore"/> or <see cref="PriorityValue{T}"/>.
+    /// </summary>
+    /// <typeparam name="T">The property type.</typeparam>
     internal class BindingEntry<T> : IBindingEntry, IPriorityValueEntry<T>, IObserver<BindingValue<T>>
     {
         private readonly IAvaloniaObject _owner;

+ 6 - 1
src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs

@@ -5,6 +5,11 @@ using Avalonia.Data;
 
 namespace Avalonia.PropertyStore
 {
+    /// <summary>
+    /// Stores a value with a priority in a <see cref="ValueStore"/> or
+    /// <see cref="PriorityValue{T}"/>.
+    /// </summary>
+    /// <typeparam name="T">The property type.</typeparam>
     internal class ConstantValueEntry<T> : IPriorityValueEntry<T>
     {
         public ConstantValueEntry(
@@ -19,7 +24,7 @@ namespace Avalonia.PropertyStore
 
         public StyledPropertyBase<T> Property { get; }
         public BindingPriority Priority { get; }
-        public Optional<T> Value { get; private set; }
+        public Optional<T> Value { get; }
         Optional<object> IValue.Value => Value.ToObject();
         BindingPriority IValue.ValuePriority => Priority;
 

+ 7 - 0
src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs

@@ -5,6 +5,9 @@ using Avalonia.Data;
 
 namespace Avalonia.PropertyStore
 {
+    /// <summary>
+    /// Represents an untyped interface to <see cref="IPriorityValueEntry{T}"/>.
+    /// </summary>
     internal interface IPriorityValueEntry : IValue
     {
         BindingPriority Priority { get; }
@@ -12,6 +15,10 @@ namespace Avalonia.PropertyStore
         void Reparent(IValueSink sink);
     }
 
+    /// <summary>
+    /// Represents an object that can act as an entry in a <see cref="PriorityValue{T}"/>.
+    /// </summary>
+    /// <typeparam name="T">The property type.</typeparam>
     internal interface IPriorityValueEntry<T> : IPriorityValueEntry, IValue<T>
     {
     }

+ 7 - 0
src/Avalonia.Base/PropertyStore/IValue.cs

@@ -4,12 +4,19 @@
 
 namespace Avalonia.PropertyStore
 {
+    /// <summary>
+    /// Represents an untyped interface to <see cref="IValue{T}"/>.
+    /// </summary>
     internal interface IValue
     {
         Optional<object> Value { get; }
         BindingPriority ValuePriority { get; }
     }
 
+    /// <summary>
+    /// Represents an object that can act as an entry in a <see cref="ValueStore"/>.
+    /// </summary>
+    /// <typeparam name="T">The property type.</typeparam>
     internal interface IValue<T> : IValue
     {
         new Optional<T> Value { get; }

+ 4 - 2
src/Avalonia.Base/PropertyStore/IValueSink.cs

@@ -1,10 +1,12 @@
-using System;
-using Avalonia.Data;
+using Avalonia.Data;
 
 #nullable enable
 
 namespace Avalonia.PropertyStore
 {
+    /// <summary>
+    /// Represents an entity that can receive change notifications in a <see cref="ValueStore"/>.
+    /// </summary>
     internal interface IValueSink
     {
         void ValueChanged<T>(

+ 5 - 0
src/Avalonia.Base/PropertyStore/LocalValueEntry.cs

@@ -4,6 +4,11 @@
 
 namespace Avalonia.PropertyStore
 {
+    /// <summary>
+    /// Stores a value with local value priority in a <see cref="ValueStore"/> or
+    /// <see cref="PriorityValue{T}"/>.
+    /// </summary>
+    /// <typeparam name="T">The property type.</typeparam>
     internal class LocalValueEntry<T> : IValue<T>
     {
         private T _value;

+ 11 - 0
src/Avalonia.Base/PropertyStore/PriorityValue.cs

@@ -6,6 +6,17 @@ using Avalonia.Data;
 
 namespace Avalonia.PropertyStore
 {
+    /// <summary>
+    /// Stores a set of prioritized values and bindings in a <see cref="ValueStore"/>.
+    /// </summary>
+    /// <typeparam name="T">The property type.</typeparam>
+    /// <remarks>
+    /// When more than a single value or binding is applied to a property in an
+    /// <see cref="AvaloniaObject"/>, the entry in the <see cref="ValueStore"/> is converted into
+    /// a <see cref="PriorityValue{T}"/>. This class holds any number of
+    /// <see cref="IPriorityValueEntry{T}"/> entries (sorted first by priority and then in the order
+    /// they were added) plus a local value.
+    /// </remarks>
     internal class PriorityValue<T> : IValue<T>, IValueSink
     {
         private readonly IAvaloniaObject _owner;

+ 2 - 0
src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs

@@ -12,6 +12,7 @@ namespace Avalonia.Reactive
         private readonly AvaloniaProperty _property;
         private T _value;
 
+#nullable disable
         public AvaloniaPropertyBindingObservable(
             IAvaloniaObject target,
             AvaloniaProperty property)
@@ -19,6 +20,7 @@ namespace Avalonia.Reactive
             _target = new WeakReference<IAvaloniaObject>(target);
             _property = property;
         }
+#nullable enable
 
         public string Description => $"{_target.GetType().Name}.{_property.Name}";
 

+ 10 - 1
src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs

@@ -44,7 +44,16 @@ namespace Avalonia.Reactive
         {
             if (e.Property == _property)
             {
-                var newValue = e.Sender.GetValue(e.Property);
+                T newValue;
+
+                if (e is AvaloniaPropertyChangedEventArgs<T> typed)
+                {
+                    newValue = typed.Sender.GetValue(typed.Property);
+                }
+                else
+                {
+                    newValue = (T)e.Sender.GetValue(e.Property);
+                }
 
                 if (!Equals(newValue, _value))
                 {

+ 2 - 2
src/Avalonia.Base/Reactive/BindingValueExtensions.cs

@@ -20,13 +20,13 @@ namespace Avalonia.Reactive
             return new BindingValueSubjectAdapter<T>(source);
         }
 
-        public static IObservable<object> ToUntyped<T>(this IObservable<BindingValue<T>> source)
+        public static IObservable<object?> ToUntyped<T>(this IObservable<BindingValue<T>> source)
         {
             source = source ?? throw new ArgumentNullException(nameof(source));
             return new UntypedBindingAdapter<T>(source);
         }
 
-        public static ISubject<object> ToUntyped<T>(this ISubject<BindingValue<T>> source)
+        public static ISubject<object?> ToUntyped<T>(this ISubject<BindingValue<T>> source)
         {
             source = source ?? throw new ArgumentNullException(nameof(source));
             return new UntypedBindingSubjectAdapter<T>(source);

+ 19 - 6
src/Avalonia.Base/Reactive/LightweightObservableBase.cs

@@ -116,20 +116,33 @@ namespace Avalonia.Reactive
         {
             if (Volatile.Read(ref _observers) != null)
             {
-                IObserver<T>[] observers;
-
+                IObserver<T>[] observers = null;
+                IObserver<T> singleObserver = null;
                 lock (this)
                 {
                     if (_observers == null)
                     {
                         return;
                     }
-                    observers = _observers.ToArray();
+                    if (_observers.Count == 1)
+                    {
+                        singleObserver = _observers[0];
+                    }
+                    else
+                    {
+                        observers = _observers.ToArray();
+                    }
                 }
-
-                foreach (var observer in observers)
+                if (singleObserver != null)
                 {
-                    observer.OnNext(value);
+                    singleObserver.OnNext(value);
+                }
+                else
+                {
+                    foreach (var observer in observers)
+                    {
+                        observer.OnNext(value);
+                    }
                 }
             }
         }

+ 1 - 14
src/Avalonia.Base/StyledPropertyMetadata`1.cs

@@ -33,7 +33,7 @@ namespace Avalonia
         /// <summary>
         /// Gets the default value for the property.
         /// </summary>
-        public TValue DefaultValue => _defaultValue.ValueOrDefault();
+        public TValue DefaultValue => _defaultValue.GetValueOrDefault();
 
         /// <summary>
         /// Gets the value coercion callback, if any.
@@ -60,18 +60,5 @@ namespace Avalonia
                 }
             }
         }
-
-        [DebuggerHidden]
-        private static Func<IAvaloniaObject, object, object> Cast(Func<IAvaloniaObject, TValue, TValue> f)
-        {
-            if (f == null)
-            {
-                return null;
-            }
-            else
-            {
-                return (o, v) => f(o, (TValue)v);
-            }
-        }
     }
 }

+ 1 - 1
src/Avalonia.Base/Utilities/IdentifierParser.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Utilities
         {
             if (IsValidIdentifierStart(r.Peek))
             {
-                return r.TakeWhile(IsValidIdentifierChar);
+                return r.TakeWhile(c => IsValidIdentifierChar(c));
             }
             else
             {

+ 48 - 0
src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs

@@ -0,0 +1,48 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Avalonia.Utilities
+{
+    public struct ImmutableReadOnlyListStructEnumerator<T> : IEnumerator, IEnumerator<T>
+    {
+        private readonly IReadOnlyList<T> _readOnlyList;
+        private int _pos;
+
+        public ImmutableReadOnlyListStructEnumerator(IReadOnlyList<T> readOnlyList)
+        {
+            _readOnlyList = readOnlyList;
+            _pos = -1;
+            Current = default;
+        }
+
+        public T Current
+        {
+            get;
+            private set;
+        }
+
+        object IEnumerator.Current => Current;
+
+        public void Dispose() { }
+
+        public bool MoveNext()
+        {
+            if (_pos >= _readOnlyList.Count - 1)
+            {
+                return false;
+            }
+
+            Current = _readOnlyList[++_pos];
+
+            return true;
+
+        }
+
+        public void Reset()
+        {
+            _pos = -1;
+
+            Current = default;
+        }
+    }
+}

+ 16 - 16
src/Avalonia.Base/Utilities/TypeUtilities.cs

@@ -92,8 +92,7 @@ namespace Avalonia.Utilities
         /// <returns>True if the type accepts null values; otherwise false.</returns>
         public static bool AcceptsNull(Type type)
         {
-            var t = type.GetTypeInfo();
-            return !t.IsValueType || (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(Nullable<>)));
+            return !type.IsValueType || IsNullableType(type);
         }
 
         /// <summary>
@@ -119,10 +118,8 @@ namespace Avalonia.Utilities
             }
 
             var from = value.GetType();
-            var fromTypeInfo = from.GetTypeInfo();
-            var toTypeInfo = to.GetTypeInfo();
 
-            if (toTypeInfo.IsAssignableFrom(fromTypeInfo))
+            if (to.IsAssignableFrom(from))
             {
                 result = value;
                 return true;
@@ -134,7 +131,7 @@ namespace Avalonia.Utilities
                 return true;
             }
 
-            if (toTypeInfo.IsEnum && from == typeof(string))
+            if (to.IsEnum && from == typeof(string))
             {
                 if (Enum.IsDefined(to, (string)value))
                 {
@@ -143,7 +140,7 @@ namespace Avalonia.Utilities
                 }
             }
 
-            if (!fromTypeInfo.IsEnum && toTypeInfo.IsEnum)
+            if (!from.IsEnum && to.IsEnum)
             {
                 result = null;
 
@@ -154,7 +151,7 @@ namespace Avalonia.Utilities
                 }
             }
 
-            if (fromTypeInfo.IsEnum && IsNumeric(to))
+            if (from.IsEnum && IsNumeric(to))
             {
                 try
                 {
@@ -223,10 +220,8 @@ namespace Avalonia.Utilities
             }
 
             var from = value.GetType();
-            var fromTypeInfo = from.GetTypeInfo();
-            var toTypeInfo = to.GetTypeInfo();
 
-            if (toTypeInfo.IsAssignableFrom(fromTypeInfo))
+            if (to.IsAssignableFrom(from))
             {
                 result = value;
                 return true;
@@ -307,9 +302,7 @@ namespace Avalonia.Utilities
         /// <returns>The default value.</returns>
         public static object Default(Type type)
         {
-            var typeInfo = type.GetTypeInfo();
-
-            if (typeInfo.IsValueType)
+            if (type.IsValueType)
             {
                 return Activator.CreateInstance(type);
             }
@@ -335,9 +328,11 @@ namespace Avalonia.Utilities
                 return false;
             }
 
-            if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
+            Type underlyingType = Nullable.GetUnderlyingType(type);
+
+            if (underlyingType != null)
             {
-                return IsNumeric(Nullable.GetUnderlyingType(type));
+                return IsNumeric(underlyingType);
             }
             else
             {
@@ -352,6 +347,11 @@ namespace Avalonia.Utilities
             Explicit = 2
         }
 
+        private static bool IsNullableType(Type type)
+        {
+            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
+        }
+
         private static MethodInfo FindTypeConversionOperatorMethod(Type fromType, Type toType, OperatorType operatorType)
         {
             const string implicitName = "op_Implicit";

+ 94 - 0
src/Avalonia.Base/Utilities/ValueSingleOrList.cs

@@ -0,0 +1,94 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Utilities
+{
+    /// <summary>
+    /// A list like struct optimized for holding zero or one items.
+    /// </summary>
+    /// <typeparam name="T">The type of items held in the list.</typeparam>
+    /// <remarks>
+    /// Once more than value has been added to this storage it will switch to using <see cref="List"/> internally.
+    /// </remarks>
+    public ref struct ValueSingleOrList<T>
+    {
+        private bool _isSingleSet;
+
+        /// <summary>
+        /// Single contained value. Only valid if <see cref="IsSingle"/> is set.
+        /// </summary>
+        public T Single { get; private set; }
+
+        /// <summary>
+        /// List of values.
+        /// </summary>
+        public List<T> List { get; private set; }
+
+        /// <summary>
+        /// If this struct is backed by a list.
+        /// </summary>
+        public bool HasList => List != null;
+
+        /// <summary>
+        /// If this struct contains only single value and storage was not promoted to a list.
+        /// </summary>
+        public bool IsSingle => List == null && _isSingleSet;
+
+        /// <summary>
+        /// Adds a value.
+        /// </summary>
+        /// <param name="value">Value to add.</param>
+        public void Add(T value)
+        {
+            if (List != null)
+            {
+                List.Add(value);
+            }
+            else
+            {
+                if (!_isSingleSet)
+                {
+                    Single = value;
+
+                    _isSingleSet = true;
+                }
+                else
+                {
+                    List = new List<T>();
+
+                    List.Add(Single);
+                    List.Add(value);
+
+                    Single = default;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Removes a value.
+        /// </summary>
+        /// <param name="value">Value to remove.</param>
+        public bool Remove(T value)
+        {
+            if (List != null)
+            {
+                return List.Remove(value);
+            }
+
+            if (!_isSingleSet)
+            {
+                return false;
+            }
+
+            if (EqualityComparer<T>.Default.Equals(Single, value))
+            {
+                Single = default;
+
+                _isSingleSet = false;
+
+                return true;
+            }
+
+            return false;
+        }
+    }
+}

+ 28 - 25
src/Avalonia.Base/ValueStore.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Collections.Generic;
 using Avalonia.Data;
 using Avalonia.PropertyStore;
 using Avalonia.Utilities;
@@ -8,26 +7,37 @@ using Avalonia.Utilities;
 
 namespace Avalonia
 {
+    /// <summary>
+    /// Stores styled property values for an <see cref="AvaloniaObject"/>.
+    /// </summary>
+    /// <remarks>
+    /// At its core this class consists of an <see cref="AvaloniaProperty"/> to 
+    /// <see cref="IValue"/> mapping which holds the current values for each set property. This
+    /// <see cref="IValue"/> can be in one of 4 states:
+    /// 
+    /// - For a single local value it will be an instance of <see cref="LocalValueEntry{T}"/>.
+    /// - For a single value of a priority other than LocalValue it will be an instance of
+    ///   <see cref="ConstantValueEntry{T}"/>`
+    /// - For a single binding it will be an instance of <see cref="BindingEntry{T}"/>
+    /// - For all other cases it will be an instance of <see cref="PriorityValue{T}"/>
+    /// </remarks>
     internal class ValueStore : IValueSink
     {
         private readonly AvaloniaObject _owner;
         private readonly IValueSink _sink;
-        private readonly AvaloniaPropertyValueStore<object> _values;
+        private readonly AvaloniaPropertyValueStore<IValue> _values;
 
         public ValueStore(AvaloniaObject owner)
         {
             _sink = _owner = owner;
-            _values = new AvaloniaPropertyValueStore<object>();
+            _values = new AvaloniaPropertyValueStore<IValue>();
         }
 
         public bool IsAnimating(AvaloniaProperty property)
         {
             if (_values.TryGetValue(property, out var slot))
             {
-                if (slot is IValue v)
-                {
-                    return v.ValuePriority < BindingPriority.LocalValue;
-                }
+                return slot.ValuePriority < BindingPriority.LocalValue;
             }
 
             return false;
@@ -37,10 +47,7 @@ namespace Avalonia
         {
             if (_values.TryGetValue(property, out var slot))
             {
-                if (slot is IValue v)
-                {
-                    return v.Value.HasValue;
-                }
+                return slot.Value.HasValue;
             }
 
             return false;
@@ -50,13 +57,12 @@ namespace Avalonia
         {
             if (_values.TryGetValue(property, out var slot))
             {
-                if (slot is IValue<T> v)
+                var v = (IValue<T>)slot;
+
+                if (v.Value.HasValue)
                 {
-                    if (v.Value.HasValue)
-                    {
-                        value = v.Value.Value;
-                        return true;
-                    }
+                    value = v.Value.Value;
+                    return true;
                 }
             }
 
@@ -165,14 +171,11 @@ namespace Avalonia
         {
             if (_values.TryGetValue(property, out var slot))
             {
-                if (slot is IValue value)
-                {
-                    return new Diagnostics.AvaloniaPropertyValue(
-                        property,
-                        value.Value.HasValue ? (object)value.Value : AvaloniaProperty.UnsetValue,
-                        value.ValuePriority,
-                        null);
-                }
+                return new Diagnostics.AvaloniaPropertyValue(
+                    property,
+                    slot.Value.HasValue ? (object)slot.Value : AvaloniaProperty.UnsetValue,
+                    slot.ValuePriority,
+                    null);
             }
 
             return null;

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

@@ -238,7 +238,7 @@ namespace Avalonia.Collections
                 }
                 else
                 {
-                    return seq.ThenByDescending(o => GetValue(o), InternalComparer);
+                    return seq.ThenBy(o => GetValue(o), InternalComparer);
                 }
             }
 

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

@@ -14,7 +14,7 @@ namespace Avalonia.Controls.Primitives
     /// </summary>
     public class DataGridFrozenGrid : Grid
     {
-        public static readonly AvaloniaProperty<bool> IsFrozenProperty =
+        public static readonly StyledProperty<bool> IsFrozenProperty =
             AvaloniaProperty.RegisterAttached<DataGridFrozenGrid, Control, bool>("IsFrozen");
 
         /// <summary>

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

@@ -5,7 +5,7 @@ using System.Reflection;
 using System.Runtime.CompilerServices;
 using Avalonia.Metadata;
 
-[assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests")]
+[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid.UnitTests")]
 [assembly: InternalsVisibleTo("Avalonia.DesignerSupport")]
 
 [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")]

+ 20 - 1
src/Avalonia.Controls/Application.cs

@@ -32,7 +32,7 @@ namespace Avalonia
     /// method.
     /// - Tracks the lifetime of the application.
     /// </remarks>
-    public class Application : AvaloniaObject, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
+    public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IResourceNode
     {
         /// <summary>
         /// The application-global data templates.
@@ -45,6 +45,12 @@ namespace Avalonia
         private Styles _styles;
         private IResourceDictionary _resources;
 
+        /// <summary>
+        /// Defines the <see cref="DataContext"/> property.
+        /// </summary>
+        public static readonly StyledProperty<object> DataContextProperty =
+            StyledElement.DataContextProperty.AddOwner<Application>();
+
         /// <inheritdoc/>
         public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
 
@@ -56,6 +62,19 @@ namespace Avalonia
             Name = "Avalonia Application";
         }
 
+        /// <summary>
+        /// Gets or sets the Applications's data context.
+        /// </summary>
+        /// <remarks>
+        /// The data context property specifies the default object that will
+        /// be used for data binding.
+        /// </remarks>
+        public object DataContext
+        {
+            get { return GetValue(DataContextProperty); }
+            set { SetValue(DataContextProperty, value); }
+        }
+
         /// <summary>
         /// Gets the current instance of the <see cref="Application"/> class.
         /// </summary>

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

@@ -326,7 +326,7 @@ namespace Avalonia.Controls
 
             if (property == IsPressedProperty)
             {
-                UpdatePseudoClasses(newValue.ValueOrDefault<bool>());
+                UpdatePseudoClasses(newValue.GetValueOrDefault<bool>());
             }
         }
 

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

@@ -215,7 +215,7 @@ namespace Avalonia.Controls
 
             if (property == ButtonSpinnerLocationProperty)
             {
-                UpdatePseudoClasses(newValue.ValueOrDefault<Location>());
+                UpdatePseudoClasses(newValue.GetValueOrDefault<Location>());
             }
         }
 

+ 5 - 1
src/Avalonia.Controls/DefinitionBase.cs

@@ -358,7 +358,11 @@ namespace Avalonia.Controls
         /// </remarks>
         private static bool SharedSizeGroupPropertyValueValid(string value)
         {
-            Contract.Requires<ArgumentNullException>(value != null);
+            //  null is default value
+            if (value == null)
+            {
+                return true;
+            }
 
             string id = (string)value;
 

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

@@ -1,9 +1,11 @@
-using Avalonia.Controls.Shapes;
+using System;
+using Avalonia.Controls.Shapes;
 using Avalonia.Media;
 using Avalonia.Metadata;
 
 namespace Avalonia.Controls
 {
+    [Obsolete("Use Image control with DrawingImage source")]
     public class DrawingPresenter : Control
     {
         static DrawingPresenter()

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

@@ -88,7 +88,7 @@ namespace Avalonia.Controls
 
             if (property == ExpandDirectionProperty)
             {
-                UpdatePseudoClasses(newValue.ValueOrDefault<ExpandDirection>());
+                UpdatePseudoClasses(newValue.GetValueOrDefault<ExpandDirection>());
             }
         }
 

+ 5 - 0
src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs

@@ -12,5 +12,10 @@ namespace Avalonia.Controls.Generators
         /// Gets the container index for the tree.
         /// </summary>
         TreeContainerIndex Index { get; }
+
+        /// <summary>
+        /// Updates the index based on the parent <see cref="TreeView"/>.
+        /// </summary>
+        void UpdateIndex();
     }
 }

+ 32 - 11
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@@ -3,8 +3,10 @@
 
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using Avalonia.Controls.Templates;
 using Avalonia.Data;
+using Avalonia.LogicalTree;
 
 namespace Avalonia.Controls.Generators
 {
@@ -15,6 +17,8 @@ namespace Avalonia.Controls.Generators
     public class TreeItemContainerGenerator<T> : ItemContainerGenerator<T>, ITreeItemContainerGenerator
         where T : class, IControl, new()
     {
+        private TreeView _treeView;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="TreeItemContainerGenerator{T}"/> class.
         /// </summary>
@@ -23,31 +27,28 @@ namespace Avalonia.Controls.Generators
         /// <param name="contentTemplateProperty">The container's ContentTemplate property.</param>
         /// <param name="itemsProperty">The container's Items property.</param>
         /// <param name="isExpandedProperty">The container's IsExpanded property.</param>
-        /// <param name="index">The container index for the tree</param>
         public TreeItemContainerGenerator(
             IControl owner,
             AvaloniaProperty contentProperty,
             AvaloniaProperty contentTemplateProperty,
             AvaloniaProperty itemsProperty,
-            AvaloniaProperty isExpandedProperty,
-            TreeContainerIndex index)
+            AvaloniaProperty isExpandedProperty)
             : base(owner, contentProperty, contentTemplateProperty)
         {
             Contract.Requires<ArgumentNullException>(owner != null);
             Contract.Requires<ArgumentNullException>(contentProperty != null);
             Contract.Requires<ArgumentNullException>(itemsProperty != null);
             Contract.Requires<ArgumentNullException>(isExpandedProperty != null);
-            Contract.Requires<ArgumentNullException>(index != null);
 
             ItemsProperty = itemsProperty;
             IsExpandedProperty = isExpandedProperty;
-            Index = index;
+            UpdateIndex();
         }
 
         /// <summary>
         /// Gets the container index for the tree.
         /// </summary>
-        public TreeContainerIndex Index { get; }
+        public TreeContainerIndex Index { get; private set; }
 
         /// <summary>
         /// Gets the item container's Items property.
@@ -70,7 +71,7 @@ namespace Avalonia.Controls.Generators
             }
             else if (container != null)
             {
-                Index.Add(item, container);
+                Index?.Add(item, container);
                 return container;
             }
             else
@@ -92,7 +93,7 @@ namespace Avalonia.Controls.Generators
                     result.DataContext = item;
                 }
 
-                Index.Add(item, result);
+                Index?.Add(item, result);
 
                 return result;
             }
@@ -101,24 +102,44 @@ namespace Avalonia.Controls.Generators
         public override IEnumerable<ItemContainerInfo> Clear()
         {
             var items = base.Clear();
-            Index.Remove(0, items);
+            Index?.Remove(0, items);
             return items;
         }
 
         public override IEnumerable<ItemContainerInfo> Dematerialize(int startingIndex, int count)
         {
-            Index.Remove(startingIndex, GetContainerRange(startingIndex, count));
+            Index?.Remove(startingIndex, GetContainerRange(startingIndex, count));
             return base.Dematerialize(startingIndex, count);
         }
 
         public override IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count)
         {
-            Index.Remove(startingIndex, GetContainerRange(startingIndex, count));
+            Index?.Remove(startingIndex, GetContainerRange(startingIndex, count));
             return base.RemoveRange(startingIndex, count);
         }
 
         public override bool TryRecycle(int oldIndex, int newIndex, object item) => false;
 
+        public void UpdateIndex()
+        {
+            if (Owner is TreeView treeViewOwner && Index == null)
+            {
+                Index = new TreeContainerIndex();
+                _treeView = treeViewOwner;
+            }
+            else
+            {
+                var treeView = Owner.GetSelfAndLogicalAncestors().OfType<TreeView>().FirstOrDefault();
+                
+                if (treeView != _treeView)
+                {
+                    Clear();
+                    Index = treeView?.ItemContainerGenerator?.Index;
+                    _treeView = treeView;
+                }
+            }
+        }
+
         class WrapperTreeDataTemplate : ITreeDataTemplate
         {
             private readonly IDataTemplate _inner;

+ 6 - 6
src/Avalonia.Controls/GridSplitter.cs

@@ -23,37 +23,37 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="ResizeDirection"/> property.
         /// </summary>
-        public static readonly AvaloniaProperty<GridResizeDirection> ResizeDirectionProperty =
+        public static readonly StyledProperty<GridResizeDirection> ResizeDirectionProperty =
             AvaloniaProperty.Register<GridSplitter, GridResizeDirection>(nameof(ResizeDirection));
 
         /// <summary>
         /// Defines the <see cref="ResizeBehavior"/> property.
         /// </summary>
-        public static readonly AvaloniaProperty<GridResizeBehavior> ResizeBehaviorProperty =
+        public static readonly StyledProperty<GridResizeBehavior> ResizeBehaviorProperty =
             AvaloniaProperty.Register<GridSplitter, GridResizeBehavior>(nameof(ResizeBehavior));
 
         /// <summary>
         /// Defines the <see cref="ShowsPreview"/> property.
         /// </summary>
-        public static readonly AvaloniaProperty<bool> ShowsPreviewProperty =
+        public static readonly StyledProperty<bool> ShowsPreviewProperty =
             AvaloniaProperty.Register<GridSplitter, bool>(nameof(ShowsPreview));
 
         /// <summary>
         /// Defines the <see cref="KeyboardIncrement"/> property.
         /// </summary>
-        public static readonly AvaloniaProperty<double> KeyboardIncrementProperty =
+        public static readonly StyledProperty<double> KeyboardIncrementProperty =
             AvaloniaProperty.Register<GridSplitter, double>(nameof(KeyboardIncrement), 10d);
 
         /// <summary>
         /// Defines the <see cref="DragIncrement"/> property.
         /// </summary>
-        public static readonly AvaloniaProperty<double> DragIncrementProperty =
+        public static readonly StyledProperty<double> DragIncrementProperty =
             AvaloniaProperty.Register<GridSplitter, double>(nameof(DragIncrement), 1d);
 
         /// <summary>
         /// Defines the <see cref="PreviewContent"/> property.
         /// </summary>
-        public static readonly AvaloniaProperty<ITemplate<IControl>> PreviewContentProperty =
+        public static readonly StyledProperty<ITemplate<IControl>> PreviewContentProperty =
             AvaloniaProperty.Register<GridSplitter, ITemplate<IControl>>(nameof(PreviewContent));
 
         private static readonly Cursor s_columnSplitterCursor = new Cursor(StandardCursorType.SizeWestEast);

+ 27 - 18
src/Avalonia.Controls/Image.cs

@@ -14,8 +14,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="Source"/> property.
         /// </summary>
-        public static readonly StyledProperty<IBitmap> SourceProperty =
-            AvaloniaProperty.Register<Image, IBitmap>(nameof(Source));
+        public static readonly StyledProperty<IImage> SourceProperty =
+            AvaloniaProperty.Register<Image, IImage>(nameof(Source));
 
         /// <summary>
         /// Defines the <see cref="Stretch"/> property.
@@ -23,6 +23,14 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<Stretch> StretchProperty =
             AvaloniaProperty.Register<Image, Stretch>(nameof(Stretch), Stretch.Uniform);
 
+        /// <summary>
+        /// Defines the <see cref="StretchDirection"/> property.
+        /// </summary>
+        public static readonly StyledProperty<StretchDirection> StretchDirectionProperty =
+            AvaloniaProperty.Register<Image, StretchDirection>(
+                nameof(StretchDirection),
+                StretchDirection.Both);
+
         static Image()
         {
             AffectsRender<Image>(SourceProperty, StretchProperty);
@@ -30,9 +38,9 @@ namespace Avalonia.Controls
         }
 
         /// <summary>
-        /// Gets or sets the bitmap image that will be displayed.
+        /// Gets or sets the image that will be displayed.
         /// </summary>
-        public IBitmap Source
+        public IImage Source
         {
             get { return GetValue(SourceProperty); }
             set { SetValue(SourceProperty, value); }
@@ -43,10 +51,19 @@ namespace Avalonia.Controls
         /// </summary>
         public Stretch Stretch
         {
-            get { return (Stretch)GetValue(StretchProperty); }
+            get { return GetValue(StretchProperty); }
             set { SetValue(StretchProperty, value); }
         }
 
+        /// <summary>
+        /// Gets or sets a value controlling in what direction the image will be stretched.
+        /// </summary>
+        public StretchDirection StretchDirection
+        {
+            get { return GetValue(StretchDirectionProperty); }
+            set { SetValue(StretchDirectionProperty, value); }
+        }
+
         /// <summary>
         /// Renders the control.
         /// </summary>
@@ -58,8 +75,8 @@ namespace Avalonia.Controls
             if (source != null)
             {
                 Rect viewPort = new Rect(Bounds.Size);
-                Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
-                Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize);
+                Size sourceSize = source.Size;
+                Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);
                 Size scaledSize = sourceSize * scale;
                 Rect destRect = viewPort
                     .CenterRect(new Rect(scaledSize))
@@ -69,7 +86,7 @@ namespace Avalonia.Controls
 
                 var interpolationMode = RenderOptions.GetBitmapInterpolationMode(this);
 
-                context.DrawImage(source, 1, sourceRect, destRect, interpolationMode);
+                context.DrawImage(source, sourceRect, destRect, interpolationMode);
             }
         }
 
@@ -85,15 +102,7 @@ namespace Avalonia.Controls
 
             if (source != null)
             {
-                Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
-                if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height))
-                {
-                    result = sourceSize;
-                }
-                else
-                {
-                    result = Stretch.CalculateSize(availableSize, sourceSize);
-                }
+                result = Stretch.CalculateSize(availableSize, source.Size, StretchDirection);
             }
 
             return result;
@@ -106,7 +115,7 @@ namespace Avalonia.Controls
 
             if (source != null)
             {
-                var sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
+                var sourceSize = source.Size;
                 var result = Stretch.CalculateSize(finalSize, sourceSize);
                 return result;
             }

+ 14 - 11
src/Avalonia.Controls/ItemsControl.cs

@@ -5,7 +5,6 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Specialized;
-using System.Linq;
 using Avalonia.Collections;
 using Avalonia.Controls.Generators;
 using Avalonia.Controls.Presenters;
@@ -324,20 +323,24 @@ namespace Avalonia.Controls
                     return;
                 }
 
-                var current = focus.Current
-                    .GetSelfAndVisualAncestors()
-                    .OfType<IInputElement>()
-                    .FirstOrDefault(x => x.VisualParent == container);
+                IVisual current = focus.Current;
 
-                if (current != null)
+                while (current != null)
                 {
-                    var next = GetNextControl(container, direction.Value, current, false);
-
-                    if (next != null)
+                    if (current.VisualParent == container && current is IInputElement inputElement)
                     {
-                        focus.Focus(next, NavigationMethod.Directional);
-                        e.Handled = true;
+                        IInputElement next = GetNextControl(container, direction.Value, inputElement, false);
+
+                        if (next != null)
+                        {
+                            focus.Focus(next, NavigationMethod.Directional);
+                            e.Handled = true;
+                        }
+
+                        break;
                     }
+
+                    current = current.VisualParent;
                 }
             }
 

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

@@ -17,10 +17,10 @@ namespace Avalonia.Controls
     /// </summary>
     public class LayoutTransformControl : Decorator
     {
-        public static readonly AvaloniaProperty<Transform> LayoutTransformProperty =
+        public static readonly StyledProperty<Transform> LayoutTransformProperty =
             AvaloniaProperty.Register<LayoutTransformControl, Transform>(nameof(LayoutTransform));
 
-        public static readonly AvaloniaProperty<bool> UseRenderTransformProperty =
+        public static readonly StyledProperty<bool> UseRenderTransformProperty =
             AvaloniaProperty.Register<LayoutTransformControl, bool>(nameof(LayoutTransform));
 
         static LayoutTransformControl()

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

@@ -124,7 +124,7 @@ namespace Avalonia.Controls
                 e.Handled = UpdateSelectionFromEventSource(
                     e.Source,
                     true,
-                    (e.InputModifiers & InputModifiers.Shift) != 0);
+                    (e.KeyModifiers & KeyModifiers.Shift) != 0);
             }
         }
 

+ 7 - 10
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@@ -7,6 +7,7 @@ using System.Linq;
 using System.Reactive.Linq;
 using System.Threading.Tasks;
 using Avalonia.Controls.Primitives;
+using Avalonia.Rendering;
 using Avalonia.Data;
 using Avalonia.VisualTree;
 
@@ -15,7 +16,7 @@ namespace Avalonia.Controls.Notifications
     /// <summary>
     /// An <see cref="INotificationManager"/> that displays notifications in a <see cref="Window"/>.
     /// </summary>
-    public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager
+    public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager, ICustomSimpleHitTest
     {
         private IList _items;
 
@@ -150,7 +151,7 @@ namespace Avalonia.Controls.Notifications
 
             if (property == PositionProperty)
             {
-                UpdatePseudoClasses(newValue.ValueOrDefault<NotificationPosition>());
+                UpdatePseudoClasses(newValue.GetValueOrDefault<NotificationPosition>());
             }
         }
 
@@ -161,15 +162,9 @@ namespace Avalonia.Controls.Notifications
         /// <param name="host">The <see cref="Window"/> that will be the host.</param>
         private void Install(Window host)
         {
-            var adornerLayer = host.GetVisualDescendants()
-                .OfType<VisualLayerManager>()
-                .FirstOrDefault()
-                ?.AdornerLayer;
+            var adornerLayer = host.FindDescendantOfType<VisualLayerManager>()?.AdornerLayer;
 
-            if (adornerLayer != null)
-            {
-                adornerLayer.Children.Add(this);
-            }
+            adornerLayer?.Children.Add(this);
         }
 
         private void UpdatePseudoClasses(NotificationPosition position)
@@ -179,5 +174,7 @@ namespace Avalonia.Controls.Notifications
             PseudoClasses.Set(":bottomleft", position == NotificationPosition.BottomLeft);
             PseudoClasses.Set(":bottomright", position == NotificationPosition.BottomRight);
         }
+
+        public bool HitTest(Point point) => VisualChildren.HitTestCustom(point);
     }
 }

+ 17 - 13
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@@ -6,6 +6,8 @@ using Avalonia.LogicalTree;
 using Avalonia.Rendering;
 using Avalonia.Threading;
 
+#nullable enable
+
 namespace Avalonia.Controls.Platform
 {
     /// <summary>
@@ -14,8 +16,8 @@ namespace Avalonia.Controls.Platform
     public class DefaultMenuInteractionHandler : IMenuInteractionHandler
     {
         private readonly bool _isContextMenu;
-        private IDisposable _inputManagerSubscription;
-        private IRenderRoot _root;
+        private IDisposable? _inputManagerSubscription;
+        private IRenderRoot? _root;
 
         public DefaultMenuInteractionHandler(bool isContextMenu)
             : this(isContextMenu, Input.InputManager.Instance, DefaultDelayRun)
@@ -24,9 +26,11 @@ namespace Avalonia.Controls.Platform
 
         public DefaultMenuInteractionHandler(
             bool isContextMenu,
-            IInputManager inputManager,
+            IInputManager? inputManager,
             Action<Action, TimeSpan> delayRun)
         {
+            delayRun = delayRun ?? throw new ArgumentNullException(nameof(delayRun));
+
             _isContextMenu = isContextMenu;
             InputManager = inputManager;
             DelayRun = delayRun;
@@ -92,7 +96,7 @@ namespace Avalonia.Controls.Platform
                 root.Deactivated -= WindowDeactivated;
             }
 
-            _inputManagerSubscription.Dispose();
+            _inputManagerSubscription!.Dispose();
 
             Menu = null;
             _root = null;
@@ -100,9 +104,9 @@ namespace Avalonia.Controls.Platform
 
         protected Action<Action, TimeSpan> DelayRun { get; }
 
-        protected IInputManager InputManager { get; }
+        protected IInputManager? InputManager { get; }
 
-        protected IMenu Menu { get; private set; }
+        protected IMenu? Menu { get; private set; }
 
         protected static TimeSpan MenuShowDelay { get; } = TimeSpan.FromMilliseconds(400);
 
@@ -131,7 +135,7 @@ namespace Avalonia.Controls.Platform
             KeyDown(GetMenuItem(e.Source as IControl), e);
         }
 
-        protected internal virtual void KeyDown(IMenuItem item, KeyEventArgs e)
+        protected internal virtual void KeyDown(IMenuItem? item, KeyEventArgs e)
         {
             switch (e.Key)
             {
@@ -200,7 +204,7 @@ namespace Avalonia.Controls.Platform
                     }
                     else
                     {
-                        Menu.Close();
+                        Menu!.Close();
                     }
 
                     e.Handled = true;
@@ -213,12 +217,12 @@ namespace Avalonia.Controls.Platform
                     {
                         if (item == null && _isContextMenu)
                         {
-                            if (Menu.MoveSelection(direction.Value, true) == true)
+                            if (Menu!.MoveSelection(direction.Value, true) == true)
                             {
                                 e.Handled = true;
                             }
                         }
-                        else if (item.Parent?.MoveSelection(direction.Value, true) == true)
+                        else if (item?.Parent?.MoveSelection(direction.Value, true) == true)
                         {
                             // If the the parent is an IMenu which successfully moved its selection,
                             // and the current menu is open then close the current menu and open the
@@ -408,7 +412,7 @@ namespace Avalonia.Controls.Platform
 
         protected void CloseMenu(IMenuItem item)
         {
-            var current = (IMenuElement)item;
+            var current = (IMenuElement?)item;
 
             while (current != null && !(current is IMenu))
             {
@@ -456,7 +460,7 @@ namespace Avalonia.Controls.Platform
 
         protected void SelectItemAndAncestors(IMenuItem item)
         {
-            var current = item;
+            var current = (IMenuItem?)item;
 
             while (current?.Parent != null)
             {
@@ -465,7 +469,7 @@ namespace Avalonia.Controls.Platform
             }
         }
 
-        protected static IMenuItem GetMenuItem(IControl item)
+        protected static IMenuItem? GetMenuItem(IControl? item)
         {
             while (true)
             {

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

@@ -28,7 +28,7 @@ namespace Avalonia.Controls.Presenters
         /// <summary>
         /// Defines the <see cref="BorderBrush"/> property.
         /// </summary>
-        public static readonly AvaloniaProperty<IBrush> BorderBrushProperty =
+        public static readonly StyledProperty<IBrush> BorderBrushProperty =
             Border.BorderBrushProperty.AddOwner<ContentPresenter>();
 
         /// <summary>

+ 2 - 2
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@@ -188,8 +188,8 @@ namespace Avalonia.Controls.Presenters
                         break;
 
                     case NotifyCollectionChangedAction.Remove:
-                        if (e.OldStartingIndex >= FirstIndex &&
-                            e.OldStartingIndex < NextIndex)
+                        if ((e.OldStartingIndex >= FirstIndex && e.OldStartingIndex < NextIndex) ||
+                            panel.Children.Count > ItemCount)
                         {
                             RecycleContainersOnRemove();
                         }

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

@@ -297,7 +297,7 @@ namespace Avalonia.Controls.Presenters
                 return new FormattedText
                 {
                     Text = "X",
-                    Typeface = new Typeface(FontFamily, FontWeight, FontStyle),
+                    Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontWeight, FontStyle),
                     FontSize = FontSize,
                     TextAlignment = TextAlignment,
                     Constraint = availableSize,

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

@@ -138,10 +138,7 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        public bool HitTest(Point point)
-        {
-            return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
-        }
+        public bool HitTest(Point point) => Children.HitTestCustom(point);
 
         private class AdornedElementInfo
         {

+ 2 - 5
src/Avalonia.Controls/Primitives/OverlayLayer.cs

@@ -21,11 +21,8 @@ namespace Avalonia.Controls.Primitives
 
             return null;
         }
-        
-        public bool HitTest(Point point)
-        {
-            return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
-        }
+
+        public bool HitTest(Point point) => Children.HitTestCustom(point);
         
         protected override Size ArrangeOverride(Size finalSize)
         {

+ 1 - 1
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@@ -306,7 +306,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
             if (placement == PlacementMode.Pointer)
             {
                 positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1));
-                positionerParameters.Anchor = PopupPositioningEdge.BottomRight;
+                positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
                 positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
             }
             else

+ 15 - 10
src/Avalonia.Controls/Primitives/RangeBase.cs

@@ -75,7 +75,10 @@ namespace Avalonia.Controls.Primitives
 
             set
             {
-                ValidateDouble(value, "Minimum");
+                if (!ValidateDouble(value))
+                {
+                    return;
+                }
 
                 if (IsInitialized)
                 {
@@ -102,7 +105,10 @@ namespace Avalonia.Controls.Primitives
 
             set
             {
-                ValidateDouble(value, "Maximum");
+                if (!ValidateDouble(value))
+                {
+                    return;
+                }
 
                 if (IsInitialized)
                 {
@@ -129,7 +135,10 @@ namespace Avalonia.Controls.Primitives
 
             set
             {
-                ValidateDouble(value, "Value");
+                if (!ValidateDouble(value))
+                {
+                    return;
+                }
 
                 if (IsInitialized)
                 {
@@ -164,16 +173,12 @@ namespace Avalonia.Controls.Primitives
         }
 
         /// <summary>
-        /// Throws an exception if the double value is NaN or Inf.
+        /// Checks if the double value is not inifinity nor NaN.
         /// </summary>
         /// <param name="value">The value.</param>
-        /// <param name="property">The name of the property being set.</param>
-        private static void ValidateDouble(double value, string property)
+        private static bool ValidateDouble(double value)
         {
-            if (double.IsInfinity(value) || double.IsNaN(value))
-            {
-                throw new ArgumentException($"{value} is not a valid value for {property}.");
-            }
+            return !double.IsInfinity(value) || !double.IsNaN(value);
         }
 
         /// <summary>

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

@@ -151,7 +151,7 @@ namespace Avalonia.Controls.Primitives
 
             if (property == OrientationProperty)
             {
-                UpdatePseudoClasses(newValue.ValueOrDefault<Orientation>());
+                UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
             }
         }
 

+ 40 - 16
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Specialized;
+using System.Diagnostics;
 using System.Linq;
 using Avalonia.Collections;
 using Avalonia.Controls.Generators;
@@ -13,7 +14,6 @@ using Avalonia.Input;
 using Avalonia.Input.Platform;
 using Avalonia.Interactivity;
 using Avalonia.Logging;
-using Avalonia.Styling;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Controls.Primitives
@@ -241,17 +241,14 @@ namespace Avalonia.Controls.Primitives
         public override void BeginInit()
         {
             base.BeginInit();
-            ++_updateCount;
-            _updateSelectedIndex = int.MinValue;
+
+            InternalBeginInit();
         }
 
         /// <inheritdoc/>
         public override void EndInit()
         {
-            if (--_updateCount == 0)
-            {
-                UpdateFinished();
-            }
+            InternalEndInit();
 
             base.EndInit();
         }
@@ -269,11 +266,20 @@ namespace Avalonia.Controls.Primitives
         /// <returns>The container or null if the event did not originate in a container.</returns>
         protected IControl GetContainerFromEventSource(IInteractive eventSource)
         {
-            var item = ((IVisual)eventSource).GetSelfAndVisualAncestors()
-                .OfType<IControl>()
-                .FirstOrDefault(x => x.LogicalParent == this && ItemContainerGenerator?.IndexFromContainer(x) != -1);
+            var parent = (IVisual)eventSource;
 
-            return item;
+            while (parent != null)
+            {
+                if (parent is IControl control && control.LogicalParent == this
+                                               && ItemContainerGenerator?.IndexFromContainer(control) != -1)
+                {
+                    return control;
+                }
+
+                parent = parent.VisualParent;
+            }
+
+            return null;
         }
 
         /// <inheritdoc/>
@@ -429,7 +435,8 @@ namespace Avalonia.Controls.Primitives
         protected override void OnDataContextBeginUpdate()
         {
             base.OnDataContextBeginUpdate();
-            ++_updateCount;
+
+            InternalBeginInit();
         }
 
         /// <inheritdoc/>
@@ -437,10 +444,7 @@ namespace Avalonia.Controls.Primitives
         {
             base.OnDataContextEndUpdate();
 
-            if (--_updateCount == 0)
-            {
-                UpdateFinished();
-            }
+            InternalEndInit();
         }
 
         protected override void OnKeyDown(KeyEventArgs e)
@@ -1110,6 +1114,26 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
+        private void InternalBeginInit()
+        {
+            if (_updateCount == 0)
+            {
+                _updateSelectedIndex = int.MinValue;
+            }
+
+            ++_updateCount;
+        }
+
+        private void InternalEndInit()
+        {
+            Debug.Assert(_updateCount > 0);
+
+            if (--_updateCount == 0)
+            {
+                UpdateFinished();
+            }
+        }
+
         private class Selection : IEnumerable<int>
         {
             private readonly List<int> _list = new List<int>();

+ 124 - 1
src/Avalonia.Controls/Primitives/ToggleButton.cs

@@ -1,12 +1,20 @@
 // 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 Avalonia.Data;
+using Avalonia.Interactivity;
 
 namespace Avalonia.Controls.Primitives
 {
+    /// <summary>
+    /// Represents a control that a user can select (check) or clear (uncheck). Base class for controls that can switch states.
+    /// </summary>
     public class ToggleButton : Button
     {
+        /// <summary>
+        /// Defines the <see cref="IsChecked"/> property.
+        /// </summary>
         public static readonly DirectProperty<ToggleButton, bool?> IsCheckedProperty =
             AvaloniaProperty.RegisterDirect<ToggleButton, bool?>(
                 nameof(IsChecked),
@@ -15,19 +23,71 @@ namespace Avalonia.Controls.Primitives
                 unsetValue: null,
                 defaultBindingMode: BindingMode.TwoWay);
 
+        /// <summary>
+        /// Defines the <see cref="IsThreeState"/> property.
+        /// </summary>
         public static readonly StyledProperty<bool> IsThreeStateProperty =
             AvaloniaProperty.Register<ToggleButton, bool>(nameof(IsThreeState));
 
+        /// <summary>
+        /// Defines the <see cref="Checked"/> event.
+        /// </summary>
+        public static readonly RoutedEvent<RoutedEventArgs> CheckedEvent =
+            RoutedEvent.Register<ToggleButton, RoutedEventArgs>(nameof(Checked), RoutingStrategies.Bubble);
+
+        /// <summary>
+        /// Defines the <see cref="Unchecked"/> event.
+        /// </summary>
+        public static readonly RoutedEvent<RoutedEventArgs> UncheckedEvent =
+            RoutedEvent.Register<ToggleButton, RoutedEventArgs>(nameof(Unchecked), RoutingStrategies.Bubble);
+
+        /// <summary>
+        /// Defines the <see cref="Unchecked"/> event.
+        /// </summary>
+        public static readonly RoutedEvent<RoutedEventArgs> IndeterminateEvent =
+            RoutedEvent.Register<ToggleButton, RoutedEventArgs>(nameof(Indeterminate), RoutingStrategies.Bubble);
+
         private bool? _isChecked = false;
 
         public ToggleButton()
         {
             UpdatePseudoClasses(IsChecked);
+            IsCheckedProperty.Changed.AddClassHandler<ToggleButton>((x, e) => x.OnIsCheckedChanged(e));
+        }
+
+        /// <summary>
+        /// Raised when a <see cref="ToggleButton"/> is checked.
+        /// </summary>
+        public event EventHandler<RoutedEventArgs> Checked
+        {
+            add => AddHandler(CheckedEvent, value);
+            remove => RemoveHandler(CheckedEvent, value);
+        }
+
+        /// <summary>
+        /// Raised when a <see cref="ToggleButton"/> is unchecked.
+        /// </summary>
+        public event EventHandler<RoutedEventArgs> Unchecked
+        {
+            add => AddHandler(UncheckedEvent, value);
+            remove => RemoveHandler(UncheckedEvent, value);
         }
 
+        /// <summary>
+        /// Raised when a <see cref="ToggleButton"/> is neither checked nor unchecked.
+        /// </summary>
+        public event EventHandler<RoutedEventArgs> Indeterminate
+        {
+            add => AddHandler(IndeterminateEvent, value);
+            remove => RemoveHandler(IndeterminateEvent, value);
+        }
+
+        /// <summary>
+        /// Gets or sets whether the <see cref="ToggleButton"/> is checked.
+        /// </summary>
         public bool? IsChecked
         {
-            get { return _isChecked; }
+            get => _isChecked;
             set 
             { 
                 SetAndRaise(IsCheckedProperty, ref _isChecked, value);
@@ -35,6 +95,9 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
+        /// <summary>
+        /// Gets or sets a value that indicates whether the control supports three states.
+        /// </summary>
         public bool IsThreeState
         {
             get => GetValue(IsThreeStateProperty);
@@ -47,18 +110,78 @@ namespace Avalonia.Controls.Primitives
             base.OnClick();
         }
 
+        /// <summary>
+        /// Toggles the <see cref="IsChecked"/> property.
+        /// </summary>
         protected virtual void Toggle()
         {
             if (IsChecked.HasValue)
+            {
                 if (IsChecked.Value)
+                {
                     if (IsThreeState)
+                    {
                         IsChecked = null;
+                    }
                     else
+                    {
                         IsChecked = false;
+                    }
+                }
                 else
+                {
                     IsChecked = true;
+                }
+            }
             else
+            {
                 IsChecked = false;
+            }
+        }
+
+        /// <summary>
+        /// Called when <see cref="IsChecked"/> becomes true.
+        /// </summary>
+        /// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
+        protected virtual void OnChecked(RoutedEventArgs e)
+        {
+            RaiseEvent(e);
+        }
+
+        /// <summary>
+        /// Called when <see cref="IsChecked"/> becomes false.
+        /// </summary>
+        /// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
+        protected virtual void OnUnchecked(RoutedEventArgs e)
+        {
+            RaiseEvent(e);
+        }
+
+        /// <summary>
+        /// Called when <see cref="IsChecked"/> becomes null.
+        /// </summary>
+        /// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
+        protected virtual void OnIndeterminate(RoutedEventArgs e)
+        {
+            RaiseEvent(e);
+        }
+
+        private void OnIsCheckedChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var newValue = (bool?)e.NewValue;
+
+            switch (newValue)
+            {
+                case true:
+                    OnChecked(new RoutedEventArgs(CheckedEvent));
+                    break;
+                case false:
+                    OnUnchecked(new RoutedEventArgs(UncheckedEvent));
+                    break;
+                default:
+                    OnIndeterminate(new RoutedEventArgs(IndeterminateEvent));
+                    break;
+            }
         }
 
         private void UpdatePseudoClasses(bool? isChecked)

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

@@ -290,7 +290,7 @@ namespace Avalonia.Controls.Primitives
 
             if (property == OrientationProperty)
             {
-                UpdatePseudoClasses(newValue.ValueOrDefault<Orientation>());
+                UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
             }
         }
 

+ 4 - 5
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@@ -1,6 +1,5 @@
 using System.Collections.Generic;
 using Avalonia.LogicalTree;
-using Avalonia.Styling;
 
 namespace Avalonia.Controls.Primitives
 {
@@ -8,7 +7,7 @@ namespace Avalonia.Controls.Primitives
     {
         private const int AdornerZIndex = int.MaxValue - 100;
         private const int OverlayZIndex = int.MaxValue - 99;
-        private IStyleHost _styleRoot;
+        private ILogicalRoot _logicalRoot;
         private readonly List<Control> _layers = new List<Control>();
         
 
@@ -53,7 +52,7 @@ namespace Avalonia.Controls.Primitives
             layer.ZIndex = zindex;
             VisualChildren.Add(layer);
             if (((ILogical)this).IsAttachedToLogicalTree)
-                ((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_styleRoot));
+                ((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_logicalRoot, layer, this));
             InvalidateArrange();
         }
         
@@ -61,7 +60,7 @@ namespace Avalonia.Controls.Primitives
         protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
         {
             base.OnAttachedToLogicalTree(e);
-            _styleRoot = e.Root;
+            _logicalRoot = e.Root;
 
             foreach (var l in _layers)
                 ((ILogical)l).NotifyAttachedToLogicalTree(e);
@@ -69,7 +68,7 @@ namespace Avalonia.Controls.Primitives
 
         protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
         {
-            _styleRoot = null;
+            _logicalRoot = null;
             base.OnDetachedFromLogicalTree(e);
             foreach (var l in _layers)
                 ((ILogical)l).NotifyDetachedFromLogicalTree(e);

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

@@ -87,11 +87,11 @@ namespace Avalonia.Controls
 
             if (property == IsIndeterminateProperty)
             {
-                UpdatePseudoClasses(newValue.ValueOrDefault<bool>(), null);
+                UpdatePseudoClasses(newValue.GetValueOrDefault<bool>(), null);
             }
             else if (property == OrientationProperty)
             {
-                UpdatePseudoClasses(null, newValue.ValueOrDefault<Orientation>());
+                UpdatePseudoClasses(null, newValue.GetValueOrDefault<Orientation>());
             }
         }
 

+ 1 - 1
src/Avalonia.Controls/Remote/RemoteWidget.cs

@@ -83,7 +83,7 @@ namespace Avalonia.Controls.Remote
                         Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride,
                             new IntPtr(l.Address.ToInt64() + l.RowBytes * y), lineLen);
                 }
-                context.DrawImage(_bitmap, 1, new Rect(0, 0, _bitmap.PixelSize.Width, _bitmap.PixelSize.Height),
+                context.DrawImage(_bitmap, new Rect(0, 0, _bitmap.PixelSize.Width, _bitmap.PixelSize.Height),
                     new Rect(Bounds.Size));
             }
             base.Render(context);

+ 27 - 25
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@@ -22,7 +22,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="HorizontalCacheLength"/> property.
         /// </summary>
-        public static readonly AvaloniaProperty<double> HorizontalCacheLengthProperty =
+        public static readonly StyledProperty<double> HorizontalCacheLengthProperty =
             AvaloniaProperty.Register<ItemsRepeater, double>(nameof(HorizontalCacheLength), 2.0);
 
         /// <summary>
@@ -40,16 +40,16 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="Layout"/> property.
         /// </summary>
-        public static readonly AvaloniaProperty<AttachedLayout> LayoutProperty =
+        public static readonly StyledProperty<AttachedLayout> LayoutProperty =
             AvaloniaProperty.Register<ItemsRepeater, AttachedLayout>(nameof(Layout), new StackLayout());
 
         /// <summary>
         /// Defines the <see cref="VerticalCacheLength"/> property.
         /// </summary>
-        public static readonly AvaloniaProperty<double> VerticalCacheLengthProperty =
+        public static readonly StyledProperty<double> VerticalCacheLengthProperty =
             AvaloniaProperty.Register<ItemsRepeater, double>(nameof(VerticalCacheLength), 2.0);
 
-        private static readonly AttachedProperty<VirtualizationInfo> VirtualizationInfoProperty =
+        private static readonly StyledProperty<VirtualizationInfo> VirtualizationInfoProperty =
             AvaloniaProperty.RegisterAttached<ItemsRepeater, IControl, VirtualizationInfo>("VirtualizationInfo");
 
         internal static readonly Rect InvalidRect = new Rect(-1, -1, -1, -1);
@@ -379,7 +379,7 @@ namespace Avalonia.Controls
         {
             if (property == ItemsProperty)
             {
-                var newEnumerable = newValue.ValueOrDefault<IEnumerable>();
+                var newEnumerable = newValue.GetValueOrDefault<IEnumerable>();
                 var newDataSource = newEnumerable as ItemsSourceView;
                 if (newEnumerable != null && newDataSource == null)
                 {
@@ -390,19 +390,19 @@ namespace Avalonia.Controls
             }
             else if (property == ItemTemplateProperty)
             {
-                OnItemTemplateChanged(oldValue.ValueOrDefault<IDataTemplate>(), newValue.ValueOrDefault<IDataTemplate>());
+                OnItemTemplateChanged(oldValue.GetValueOrDefault<IDataTemplate>(), newValue.GetValueOrDefault<IDataTemplate>());
             }
             else if (property == LayoutProperty)
             {
-                OnLayoutChanged(oldValue.ValueOrDefault<AttachedLayout>(), newValue.ValueOrDefault<AttachedLayout>());
+                OnLayoutChanged(oldValue.GetValueOrDefault<AttachedLayout>(), newValue.GetValueOrDefault<AttachedLayout>());
             }
             else if (property == HorizontalCacheLengthProperty)
             {
-                _viewportManager.HorizontalCacheLength = newValue.ValueOrDefault<double>();
+                _viewportManager.HorizontalCacheLength = newValue.GetValueOrDefault<double>();
             }
             else if (property == VerticalCacheLengthProperty)
             {
-                _viewportManager.VerticalCacheLength = newValue.ValueOrDefault<double>();
+                _viewportManager.VerticalCacheLength = newValue.GetValueOrDefault<double>();
             }
 
             base.OnPropertyChanged(property, oldValue, newValue, priority);
@@ -559,33 +559,35 @@ namespace Avalonia.Controls
 
             if (Layout != null)
             {
-                if (Layout is VirtualizingLayout virtualLayout)
-                {
-                    var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
+                var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
 
+                try
+                {
                     _processingItemsSourceChange = args;
 
-                    try
+                    if (Layout is VirtualizingLayout virtualLayout)
                     {
                         virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args);
                     }
-                    finally
-                    {
-                        _processingItemsSourceChange = null;
-                    }
-                }
-                else if (Layout is NonVirtualizingLayout nonVirtualLayout)
-                {
-                    // Walk through all the elements and make sure they are cleared for
-                    // non-virtualizing layouts.
-                    foreach (var element in Children)
+                    else if (Layout is NonVirtualizingLayout nonVirtualLayout)
                     {
-                        if (GetVirtualizationInfo(element).IsRealized)
+                        // Walk through all the elements and make sure they are cleared for
+                        // non-virtualizing layouts.
+                        foreach (var element in Children)
                         {
-                            ClearElementImpl(element);
+                            if (GetVirtualizationInfo(element).IsRealized)
+                            {
+                                ClearElementImpl(element);
+                            }
                         }
+
+                        Children.Clear();
                     }
                 }
+                finally
+                {
+                    _processingItemsSourceChange = null;
+                }
 
                 InvalidateMeasure();
             }

+ 61 - 16
src/Avalonia.Controls/Repeater/ViewManager.cs

@@ -109,11 +109,22 @@ namespace Avalonia.Controls
 
         public void ClearElementToElementFactory(IControl element)
         {
-            var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
-            var clearedIndex = virtInfo.Index;
             _owner.OnElementClearing(element);
-            _owner.ItemTemplateShim.RecycleElement(_owner, element);
 
+            if (_owner.ItemTemplateShim != null)
+            {
+                _owner.ItemTemplateShim.RecycleElement(_owner, element);
+            }
+            else
+            {
+                // No ItemTemplate to recycle to, remove the element from the children collection.
+                if (!_owner.Children.Remove(element))
+                {
+                    throw new InvalidOperationException("ItemsRepeater's child not found in its Children collection.");
+                }
+            }
+
+            var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
             virtInfo.MoveOwnershipToElementFactory();
 
             if (_lastFocusedElement == element)
@@ -121,9 +132,8 @@ namespace Avalonia.Controls
                 // Focused element is going away. Remove the tracked last focused element
                 // and pick a reasonable next focus if we can find one within the layout 
                 // realized elements.
-                MoveFocusFromClearedIndex(clearedIndex);
+                MoveFocusFromClearedIndex(virtInfo.Index);
             }
-
         }
 
         private void MoveFocusFromClearedIndex(int clearedIndex)
@@ -190,7 +200,8 @@ namespace Avalonia.Controls
         {
             if (virtInfo == null)
             {
-                throw new ArgumentException("Element is not a child of this ItemsRepeater.");
+                //Element is not a child of this ItemsRepeater.
+                return -1;
             }
 
             return virtInfo.IsRealized || virtInfo.IsInUniqueIdResetPool ? virtInfo.Index : -1;
@@ -515,21 +526,52 @@ namespace Avalonia.Controls
             return element;
         }
 
+        // There are several cases handled here with respect to which element gets returned and when DataContext is modified.
+        //
+        // 1. If there is no ItemTemplate:
+        //    1.1 If data is an IControl -> the data is returned
+        //    1.2 If data is not an IControl -> a default DataTemplate is used to fetch element and DataContext is set to data
+        //
+        // 2. If there is an ItemTemplate:
+        //    2.1 If data is not an IControl -> Element is fetched from ElementFactory and DataContext is set to the data
+        //    2.2 If data is an IControl:
+        //        2.2.1 If Element returned by the ElementFactory is the same as the data -> Element (a.k.a. data) is returned as is
+        //        2.2.2 If Element returned by the ElementFactory is not the same as the data
+        //                 -> Element that is fetched from the ElementFactory is returned and
+        //                    DataContext is set to the data's DataContext (if it exists), otherwise it is set to the data itself
         private IControl GetElementFromElementFactory(int index)
         {
             // The view generator is the provider of last resort.
+            var data = _owner.ItemsSourceView.GetAt(index);
+            var providedElementFactory = _owner.ItemTemplateShim;
+
+            ItemTemplateWrapper GetElementFactory()
+            {
+                if (providedElementFactory == null)
+                {
+                    var factory = FuncDataTemplate.Default;
+                    _owner.ItemTemplate = factory;
+                    return _owner.ItemTemplateShim;
+                }
 
-            var itemTemplateFactory = _owner.ItemTemplateShim;
-            if (itemTemplateFactory == null)
+                return providedElementFactory;
+            }
+
+            IControl GetElement()
             {
-                // If no ItemTemplate was provided, use a default 
-                var factory = FuncDataTemplate.Default;
-                _owner.ItemTemplate = factory;
-                itemTemplateFactory = _owner.ItemTemplateShim;
+                if (providedElementFactory == null)
+                {
+                    if (data is IControl dataAsElement)
+                    {
+                        return dataAsElement;
+                    }
+                }
+
+                var elementFactory = GetElementFactory();
+                return elementFactory.GetElement(_owner, data);
             }
 
-            var data = _owner.ItemsSourceView.GetAt(index);
-            var element = itemTemplateFactory.GetElement(_owner, data);
+            var element = GetElement();
 
             var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element);
             if (virtInfo == null)
@@ -537,8 +579,11 @@ namespace Avalonia.Controls
                 virtInfo = ItemsRepeater.CreateAndInitializeVirtualizationInfo(element);
             }
 
-            // Prepare the element
-            element.DataContext = data;
+            if (data != element)
+            {
+                // Prepare the element
+                element.DataContext = data;
+            }
 
             virtInfo.MoveOwnershipToLayoutFromElementFactory(
                 index,

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

@@ -147,7 +147,7 @@ namespace Avalonia.Controls
 
             if (property == OrientationProperty)
             {
-                UpdatePseudoClasses(newValue.ValueOrDefault<Orientation>());
+                UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
             }
         }
 

+ 1 - 2
src/Avalonia.Controls/Templates/FuncDataTemplate.cs

@@ -3,7 +3,6 @@
 
 using System;
 using System.Reactive.Linq;
-using System.Reflection;
 
 namespace Avalonia.Controls.Templates
 {
@@ -102,7 +101,7 @@ namespace Avalonia.Controls.Templates
         /// </returns>
         private static bool IsInstance(object o, Type t)
         {
-            return (o != null) && t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo());
+            return t.IsInstanceOfType(o);
         }
     }
 }

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