Browse Source

Merge branch 'master' into headless-platform

danwalmsley 5 years ago
parent
commit
8ce7e2a38d
95 changed files with 2341 additions and 764 deletions
  1. 5 0
      .ncrunch/NativeEmbedSample.v3.ncrunchproject
  2. 4 4
      Avalonia.sln
  3. 3 2
      native/Avalonia.Native/inc/avalonia-native.h
  4. 18 3
      native/Avalonia.Native/src/OSX/controlhost.mm
  5. 28 5
      native/Avalonia.Native/src/OSX/window.mm
  6. 1 1
      nukebuild/Numerge
  7. 4 0
      packages/Avalonia/Avalonia.csproj
  8. 9 1
      packages/Avalonia/AvaloniaBuildTasks.props
  9. 20 2
      packages/Avalonia/AvaloniaBuildTasks.targets
  10. 18 0
      packages/Avalonia/AvaloniaItemSchema.xaml
  11. 1 1
      readme.md
  12. 8 18
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  13. 9 0
      samples/interop/NativeEmbedSample/MainWindow.xaml
  14. 1 0
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  15. 4 2
      src/Avalonia.Base/Threading/DispatcherTimer.cs
  16. 2 2
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  17. 2 1
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  18. 1 1
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  19. 1 1
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
  20. 73 16
      src/Avalonia.Controls/NativeControlHost.cs
  21. 2 2
      src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
  22. 132 32
      src/Avalonia.Controls/ProgressBar.cs
  23. 22 45
      src/Avalonia.Controls/Repeater/ViewportManager.cs
  24. 0 2
      src/Avalonia.Controls/SelectionModel.cs
  25. 6 3
      src/Avalonia.Controls/TopLevel.cs
  26. 6 3
      src/Avalonia.Controls/Utils/IEnumerableUtils.cs
  27. 2 2
      src/Avalonia.Controls/Window.cs
  28. 1 1
      src/Avalonia.Controls/WindowBase.cs
  29. 9 1
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  30. 28 1
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
  31. 14 9
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  32. 10 4
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
  33. 78 0
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs
  34. 9 1
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  35. 27 10
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
  36. 20 2
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  37. 24 0
      src/Avalonia.Layout/EffectiveViewportChangedEventArgs.cs
  38. 23 1
      src/Avalonia.Layout/ILayoutManager.cs
  39. 7 0
      src/Avalonia.Layout/ILayoutable.cs
  40. 193 12
      src/Avalonia.Layout/LayoutManager.cs
  41. 8 1
      src/Avalonia.Layout/LayoutQueue.cs
  42. 59 11
      src/Avalonia.Layout/Layoutable.cs
  43. 4 5
      src/Avalonia.Native/NativeControlHostImpl.cs
  44. 12 0
      src/Avalonia.Native/PopupImpl.cs
  45. 1 1
      src/Avalonia.Native/WindowImplBase.cs
  46. 10 2
      src/Avalonia.Themes.Default/ProgressBar.xaml
  47. 7 36
      src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
  48. 8 37
      src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
  49. 9 0
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  50. 8 0
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  51. 1 0
      src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
  52. 23 0
      src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml
  53. 34 10
      src/Avalonia.Themes.Fluent/FocusAdorner.xaml
  54. 143 48
      src/Avalonia.Themes.Fluent/ProgressBar.xaml
  55. 12 4
      src/Avalonia.Themes.Fluent/ToolTip.xaml
  56. 29 32
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  57. BIN
      src/Avalonia.Visuals/Assets/GraphemeBreak.trie
  58. BIN
      src/Avalonia.Visuals/Assets/UnicodeData.trie
  59. 1 1
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs
  60. 33 32
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs
  61. 15 19
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs
  62. 2 2
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs
  63. 3 1
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
  64. 178 0
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs
  65. 7 3
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/Script.cs
  66. 15 0
      src/Avalonia.Visuals/Rect.cs
  67. 15 7
      src/Avalonia.X11/X11NativeControlHost.cs
  68. 8 4
      src/Windows/Avalonia.Win32/Win32NativeControlHost.cs
  69. 16 3
      src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs
  70. 1 1
      tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs
  71. 1 1
      tests/Avalonia.Benchmarks/Layout/Measure.cs
  72. 1 1
      tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs
  73. 1 1
      tests/Avalonia.Controls.UnitTests/GridTests.cs
  74. 1 1
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  75. 7 2
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
  76. 16 11
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
  77. 3 3
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
  78. 21 4
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  79. 32 16
      tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
  80. 1 3
      tests/Avalonia.Layout.UnitTests/LayoutableTests.cs
  81. 424 0
      tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs
  82. 10 10
      tests/Avalonia.LeakTests/ControlTests.cs
  83. 3 1
      tests/Avalonia.UnitTests/TestRoot.cs
  84. 2 1
      tests/Avalonia.UnitTests/TestTemplatedRoot.cs
  85. 34 33
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt
  86. 24 37
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs
  87. 10 73
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs
  88. 1 1
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs
  89. 85 0
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs
  90. 34 27
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs
  91. 24 1
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs
  92. 107 74
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs
  93. 4 4
      tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs
  94. 3 3
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  95. 5 5
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs

+ 5 - 0
.ncrunch/NativeEmbedSample.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 4 - 4
Avalonia.sln

@@ -201,9 +201,9 @@ 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}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
 EndProject
@@ -215,8 +215,8 @@ Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5
-		src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
-		src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
+		src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 5
+		src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5
 		src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5
 		src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13

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

@@ -278,6 +278,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
     virtual HRESULT SetTitleBarColor (AvnColor color) = 0;
     virtual HRESULT SetWindowState(AvnWindowState state) = 0;
     virtual HRESULT GetWindowState(AvnWindowState*ret) = 0;
+    virtual HRESULT TakeFocusFromChildren() = 0;
 };
 
 AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown
@@ -493,8 +494,8 @@ AVNCOM(IAvnNativeControlHostTopLevelAttachment, 21) : IUnknown
     virtual void* GetParentHandle() = 0;
     virtual HRESULT InitializeWithChildHandle(void* child) = 0;
     virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0;
-    virtual void MoveTo(float x, float y, float width, float height) = 0;
-    virtual void Hide() = 0;
+    virtual void ShowInBounds(float x, float y, float width, float height) = 0;
+    virtual void HideWithSize(float width, float height) = 0;
     virtual void ReleaseChild() = 0;
 };
 

+ 18 - 3
native/Avalonia.Native/src/OSX/controlhost.mm

@@ -97,7 +97,7 @@ public:
         return S_OK;
     };
     
-    virtual void MoveTo(float x, float y, float width, float height) override
+    virtual void ShowInBounds(float x, float y, float width, float height) override
     {
         if(_child == nil)
             return;
@@ -106,7 +106,7 @@ public:
             IAvnNativeControlHostTopLevelAttachment* slf = this;
             slf->AddRef();
             dispatch_async(dispatch_get_main_queue(), ^{
-                slf->MoveTo(x, y, width, height);
+                slf->ShowInBounds(x, y, width, height);
                 slf->Release();
             });
             return;
@@ -122,9 +122,24 @@ public:
             [[_holder superview] setNeedsDisplay:true];
     }
     
-    virtual void Hide() override
+    virtual void HideWithSize(float width, float height) override
     {
+        if(_child == nil)
+            return;
+        if(AvnInsidePotentialDeadlock::IsInside())
+        {
+            IAvnNativeControlHostTopLevelAttachment* slf = this;
+            slf->AddRef();
+            dispatch_async(dispatch_get_main_queue(), ^{
+                slf->HideWithSize(width, height);
+                slf->Release();
+            });
+            return;
+        }
+        
+        NSRect frame = {0, 0, width, height};
         [_holder setHidden: true];
+        [_child setFrame: frame];
     }
     
     virtual void ReleaseChild() override

+ 28 - 5
native/Avalonia.Native/src/OSX/window.mm

@@ -116,10 +116,15 @@ public:
         {
             SetPosition(lastPositionSet);
             UpdateStyle();
-            
-            [Window makeKeyAndOrderFront:Window];
-            [NSApp activateIgnoringOtherApps:YES];
-            
+            if(ShouldTakeFocusOnShow())
+            {
+                [Window makeKeyAndOrderFront:Window];
+                [NSApp activateIgnoringOtherApps:YES];
+            }
+            else
+            {
+                [Window orderFront: Window];
+            }
             [Window setTitle:_lastTitle];
             
             _shown = true;
@@ -128,6 +133,11 @@ public:
         }
     }
     
+    virtual bool ShouldTakeFocusOnShow()
+    {
+        return true;
+    }
+    
     virtual HRESULT Hide () override
     {
         @autoreleasepool
@@ -774,6 +784,15 @@ private:
         }
     }
     
+    virtual HRESULT TakeFocusFromChildren () override
+    {
+        if(Window == nil)
+            return S_OK;
+        if([Window isKeyWindow])
+            [Window makeFirstResponder: View];
+        return S_OK;
+    }
+    
     void EnterFullScreenMode ()
     {
         _fullScreenActive = true;
@@ -1858,7 +1877,6 @@ private:
         WindowEvents = events;
         [Window setLevel:NSPopUpMenuWindowLevel];
     }
-    
 protected:
     virtual NSWindowStyleMask GetStyle() override
     {
@@ -1876,6 +1894,11 @@ protected:
             return S_OK;
         }
     }
+public:
+    virtual bool ShouldTakeFocusOnShow() override
+    {
+        return false;
+    }
 };
 
 extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)

+ 1 - 1
nukebuild/Numerge

@@ -1 +1 @@
-Subproject commit 4464343aef5c8ab7a42fcb20a483a6058199f8b8
+Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5

+ 4 - 0
packages/Avalonia/Avalonia.csproj

@@ -41,6 +41,10 @@
       <Pack>true</Pack>
       <PackagePath>build\</PackagePath>
     </Content>
+    <Content Include="AvaloniaItemSchema.xaml">
+      <Pack>true</Pack>
+      <PackagePath>build\</PackagePath>
+    </Content>
   </ItemGroup>
   <Import Project="..\..\build\SharedVersion.props" />
   <Import Project="..\..\build\NetFX.props" />

+ 9 - 1
packages/Avalonia/AvaloniaBuildTasks.props

@@ -1,3 +1,11 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-
+  <ItemGroup>
+    <AvailableItemName Include="AvaloniaXaml" />
+    <AvailableItemName Include="AvaloniaResource" />
+    <PropertyPageSchema Include="$(MSBuildThisFileDirectory)AvaloniaItemSchema.xaml" />
+  </ItemGroup>
+  <ItemGroup Condition="'$(EnableDefaultItems)'=='True'">
+    <AvaloniaXaml Include="**\*.axaml" SubType="Designer" />
+    <AvaloniaXaml Include="**\*.paml" SubType="Designer" />
+  </ItemGroup>
 </Project>

+ 20 - 2
packages/Avalonia/AvaloniaBuildTasks.targets

@@ -4,6 +4,20 @@
     <_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false</_AvaloniaUseExternalMSBuild>
     <AvaloniaXamlReportImportance Condition="'$(AvaloniaXamlReportImportance)' == ''">low</AvaloniaXamlReportImportance>
   </PropertyGroup>
+
+  <!-- Unfortunately we have to update default items in .targets since custom nuget props are improted before Microsoft.NET.Sdk.DefaultItems.props -->
+  <ItemGroup Condition="'$(EnableDefaultItems)'=='True'">
+    <Compile Update="**\*.paml.cs">
+      <DependentUpon>%(Filename)</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Update="**\*.axaml.cs">
+      <DependentUpon>%(Filename)</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+    <None Remove="**\*.axaml" />
+    <None Remove="**\*.paml" />
+  </ItemGroup>
   
   <UsingTask TaskName="GenerateAvaloniaResourcesTask"
              AssemblyFile="$(AvaloniaBuildTasksLocation)"
@@ -31,9 +45,12 @@
   
   <Target Name="GenerateAvaloniaResources" 
           BeforeTargets="CoreCompile;CoreResGen"
-          Inputs="@(AvaloniaResource);$(MSBuildAllProjects)"
+          Inputs="@(AvaloniaResource);@(AvaloniaXaml);$(MSBuildAllProjects)"
           Outputs="$(AvaloniaResourcesTemporaryFilePath)"
-          DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)">
+	  DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)">
+    <ItemGroup>
+        <AvaloniaResource Include="@(AvaloniaXaml)"/>
+    </ItemGroup>
     <GenerateAvaloniaResourcesTask
       Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
       Output="$(AvaloniaResourcesTemporaryFilePath)"
@@ -79,5 +96,6 @@
   
   <ItemGroup>
     <UpToDateCheckInput Include="@(AvaloniaResource)" />
+    <UpToDateCheckInput Include="@(AvaloniaXaml)" />
   </ItemGroup>
 </Project>

+ 18 - 0
packages/Avalonia/AvaloniaItemSchema.xaml

@@ -0,0 +1,18 @@
+<ProjectSchemaDefinitions xmlns="http://schemas.microsoft.com/build/2009/properties">
+    <ContentType
+        Name="AvaloniaXaml"
+        DisplayName="Avalonia XAML"
+        ItemType="AvaloniaXaml">
+      <NameValuePair Name="DependentFileExtensions" Value=".cs" />
+      <NameValuePair Name="DefaultMetadata_SubType" Value="Designer" />
+    </ContentType>
+
+    <ItemType Name="AvaloniaXaml" DisplayName="Avalonia XAML" />
+    <FileExtension Name=".axaml" ContentType="AvaloniaXaml" />
+    <FileExtension Name=".paml" ContentType="AvaloniaXaml" />
+    <ContentType
+        Name="AvaloniaResource"
+        DisplayName="Avalonia Resource"
+        ItemType="AvaloniaResource"
+    />
+</ProjectSchemaDefinitions>

+ 1 - 1
readme.md

@@ -68,7 +68,7 @@ Avalonia is licenced under the [MIT licence](licence.md).
 
 ## Contributors
 
-This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing/contributing)].
+This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing)].
 <a href="https://github.com/AvaloniaUI/Avalonia/graphs/contributors"><img src="https://opencollective.com/Avalonia/contributors.svg?width=890&button=false" /></a>
 
 ### Backers

+ 8 - 18
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@@ -1,29 +1,19 @@
-<UserControl xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="ControlCatalog.Pages.ProgressBarPage">
+<UserControl xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.Pages.ProgressBarPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">ProgressBar</TextBlock>
     <TextBlock Classes="h2">A progress bar control</TextBlock>
-
     <StackPanel>
-      <CheckBox
-          x:Name="showProgress"
-          Margin="10,16,0,0"
-          Content="Show Progress Text" />
-      <StackPanel Orientation="Horizontal"
-                  Margin="0,16,0,0"
-                  HorizontalAlignment="Center"
-                  Spacing="16">
+      <CheckBox x:Name="showProgress" Margin="10,16,0,0" Content="Show Progress Text" />
+      <CheckBox x:Name="isIndeterminate" Margin="10,16,0,0" Content="Toggle Indeterminate" />
+      <StackPanel Orientation="Horizontal" Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16">
         <StackPanel Spacing="16">
-          <ProgressBar ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #hprogress.Value}" />
-          <ProgressBar IsIndeterminate="True"/>
+          <ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #hprogress.Value}" />
         </StackPanel>
-        <ProgressBar ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #vprogress.Value}" Orientation="Vertical" />
-        <ProgressBar Orientation="Vertical" IsIndeterminate="True" />
+        <ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #vprogress.Value}" Orientation="Vertical" />
       </StackPanel>
       <StackPanel Margin="16">
-        <Slider Name="hprogress" Maximum="100" Value="40"/>
-        <Slider Name="vprogress" Maximum="100" Value="60"/>
+        <Slider Name="hprogress" Maximum="100" Value="40" />
+        <Slider Name="vprogress" Maximum="100" Value="60" />
       </StackPanel>
     </StackPanel>
   </StackPanel>

+ 9 - 0
samples/interop/NativeEmbedSample/MainWindow.xaml

@@ -20,7 +20,16 @@
     <DockPanel DockPanel.Dock="Top">
       <Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button>
       <Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button>
+      <Border DockPanel.Dock="Right" Background="#c0c0c0">
+        <ToolTip.Tip>
+          <ToolTip>
+              <TextBlock>Text</TextBlock>
+          </ToolTip>
+        </ToolTip.Tip>
+        <TextBlock>Tooltip</TextBlock>
+      </Border>
       <TextBox Text="Lorem ipsum dolor sit amet"/>
+      
     </DockPanel>
     <Grid ColumnDefinitions="*,5,*">
       <DockPanel>

+ 1 - 0
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@@ -140,6 +140,7 @@ namespace Avalonia.Collections
             }
         }
 
+        [Obsolete("Causes memory leaks. Use DynamicData or similar instead.")]
         public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(
             this IAvaloniaReadOnlyList<TSource> collection,
             Func<TSource, TDerived> select)

+ 4 - 2
src/Avalonia.Base/Threading/DispatcherTimer.cs

@@ -14,7 +14,7 @@ namespace Avalonia.Threading
         private readonly DispatcherPriority _priority;
 
         private TimeSpan _interval;
-        
+
         /// <summary>
         /// Initializes a new instance of the <see cref="DispatcherTimer"/> class.
         /// </summary>
@@ -154,6 +154,8 @@ namespace Avalonia.Threading
             TimeSpan interval,
             DispatcherPriority priority = DispatcherPriority.Normal)
         {
+            interval = (interval != TimeSpan.Zero) ? interval : TimeSpan.FromTicks(1);
+            
             var timer = new DispatcherTimer(priority) { Interval = interval };
 
             timer.Tick += (s, e) =>
@@ -197,7 +199,7 @@ namespace Avalonia.Threading
             }
         }
 
-        
+
 
         /// <summary>
         /// Raises the <see cref="Tick"/> event on the dispatcher thread.

+ 2 - 2
src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs

@@ -107,7 +107,7 @@ namespace Avalonia.Build.Tasks
             
             foreach (var s in sources.ToList())
             {
-                if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml"))
+                if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml") || s.Path.ToLowerInvariant().EndsWith(".axaml"))
                 {
                     XamlFileInfo info;
                     try
@@ -150,7 +150,7 @@ namespace Avalonia.Build.Tasks
 
             BuildEngine.LogMessage($"GenerateAvaloniaResourcesTask -> Root: {Root}, {Resources?.Count()} resources, Output:{Output}", _reportImportance < MessageImportance.Low ? MessageImportance.High : _reportImportance);
 
-            foreach (var r in EmbeddedResources.Where(r => r.ItemSpec.EndsWith(".xaml") || r.ItemSpec.EndsWith(".paml")))
+            foreach (var r in EmbeddedResources.Where(r => r.ItemSpec.EndsWith(".xaml") || r.ItemSpec.EndsWith(".paml") || r.ItemSpec.EndsWith(".axaml")))
                 BuildEngine.LogWarning(BuildEngineErrorCode.LegacyResmScheme, r.ItemSpec,
                     "XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work");
             var resources = BuildResourceSources();

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

@@ -25,7 +25,8 @@ namespace Avalonia.Build.Tasks
     public static partial class XamlCompilerTaskExecutor
     {
         static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml")
-                                               || r.Name.ToLowerInvariant().EndsWith(".paml");
+                                               || r.Name.ToLowerInvariant().EndsWith(".paml")
+                                               || r.Name.ToLowerInvariant().EndsWith(".axaml");
         
         public class CompileResult
         {

+ 1 - 1
src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs

@@ -25,7 +25,7 @@ namespace Avalonia.Controls.Embedding
         {
             EnsureInitialized();
             ApplyTemplate();
-            LayoutManager.ExecuteInitialLayoutPass(this);
+            LayoutManager.ExecuteInitialLayoutPass();
         }
 
         private void EnsureInitialized()

+ 1 - 1
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs

@@ -18,7 +18,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
         {
             EnsureInitialized();
             ApplyTemplate();
-            LayoutManager.ExecuteInitialLayoutPass(this);
+            LayoutManager.ExecuteInitialLayoutPass();
         }
 
         private void EnsureInitialized()

+ 73 - 16
src/Avalonia.Controls/NativeControlHost.cs

@@ -1,7 +1,9 @@
+using System;
+using System.Collections.Generic;
 using Avalonia.Controls.Platform;
-using Avalonia.LogicalTree;
 using Avalonia.Platform;
 using Avalonia.Threading;
+using Avalonia.VisualTree;
 
 namespace Avalonia.Controls
 {
@@ -12,14 +14,18 @@ namespace Avalonia.Controls
         private INativeControlHostControlTopLevelAttachment _attachment;
         private IPlatformHandle _nativeControlHandle;
         private bool _queuedForDestruction;
+        private bool _queuedForMoveResize;
+        private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>();
+        private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler;
         static NativeControlHost()
         {
             IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(OnVisibleChanged);
-            TransformedBoundsProperty.Changed.AddClassHandler<NativeControlHost>(OnBoundsChanged);
         }
 
-        private static void OnBoundsChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) 
-            => host.UpdateHost();
+        public NativeControlHost()
+        {
+            _propertyChangedHandler = PropertyChangedHandler;
+        }
 
         private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2)
             => host.UpdateHost();
@@ -27,21 +33,46 @@ namespace Avalonia.Controls
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
         {
             _currentRoot = e.Root as TopLevel;
+            var visual = (IVisual)this;
+            while (visual != _currentRoot)
+            {
+
+                if (visual is Visual v)
+                {
+                    v.PropertyChanged += _propertyChangedHandler;
+                    _propertyChangedSubscriptions.Add(v);
+                }
+
+                visual = visual.GetVisualParent();
+            }
+
             UpdateHost();
         }
 
+        private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.IsEffectiveValueChange && e.Property == BoundsProperty)
+                EnqueueForMoveResize();
+        }
+
         protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
         {
             _currentRoot = null;
+            if (_propertyChangedSubscriptions != null)
+            {
+                foreach (var v in _propertyChangedSubscriptions)
+                    v.PropertyChanged -= _propertyChangedHandler;
+                _propertyChangedSubscriptions.Clear();
+            }
             UpdateHost();
         }
 
 
-        void UpdateHost()
+        private void UpdateHost()
         {
+            _queuedForMoveResize = false;
             _currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost;
             var needsAttachment = _currentHost != null;
-            var needsShow = needsAttachment && IsEffectivelyVisible && TransformedBounds.HasValue;
             
             if (needsAttachment)
             {
@@ -93,22 +124,46 @@ namespace Avalonia.Controls
                 }
             }
 
-            if (needsShow)
-                _attachment?.ShowInBounds(TransformedBounds.Value);
-            else if (needsAttachment)
-                _attachment?.Hide();
+            if (_attachment?.AttachedTo != _currentHost)
+                return;
+
+            TryUpdateNativeControlPosition();
+        }
+
+        
+        private Rect? GetAbsoluteBounds()
+        {
+            var bounds = Bounds;
+            var position = this.TranslatePoint(bounds.Position, _currentRoot);
+            if (position == null)
+                return null;
+            return new Rect(position.Value, bounds.Size);
+        }
+
+        void EnqueueForMoveResize()
+        {
+            if(_queuedForMoveResize)
+                return;
+            _queuedForMoveResize = true;
+            Dispatcher.UIThread.Post(UpdateHost, DispatcherPriority.Render);
         }
 
         public bool TryUpdateNativeControlPosition()
         {
-            var needsShow = _currentHost != null && IsEffectivelyVisible && TransformedBounds.HasValue;
+            if (_currentHost == null)
+                return false;
+            
+            var bounds = GetAbsoluteBounds();
+            var needsShow = IsEffectivelyVisible && bounds.HasValue;
 
-            if(needsShow)
-                _attachment?.ShowInBounds(TransformedBounds.Value);
-            return needsShow;
+            if (needsShow)
+                _attachment?.ShowInBounds(bounds.Value);
+            else
+                _attachment?.HideWithSize(Bounds.Size);
+            return false;
         }
 
-        void CheckDestruction()
+        private void CheckDestruction()
         {
             _queuedForDestruction = false;
             if (_currentRoot == null)
@@ -117,10 +172,12 @@ namespace Avalonia.Controls
         
         protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
         {
+            if (_currentHost == null)
+                throw new InvalidOperationException();
             return _currentHost.CreateDefaultChild(parent);
         }
 
-        void DestroyNativeControl()
+        private void DestroyNativeControl()
         {
             if (_nativeControlHandle != null)
             {

+ 2 - 2
src/Avalonia.Controls/Platform/INativeControlHostImpl.cs

@@ -21,8 +21,8 @@ namespace Avalonia.Controls.Platform
     {
         INativeControlHostImpl AttachedTo { get; set; }
         bool IsCompatibleWith(INativeControlHostImpl host);
-        void Hide();
-        void ShowInBounds(TransformedBounds transformedBounds);
+        void HideWithSize(Size size);
+        void ShowInBounds(Rect rect);
     }
 
     public interface ITopLevelImplWithNativeControlHost

+ 132 - 32
src/Avalonia.Controls/ProgressBar.cs

@@ -1,8 +1,7 @@
-
 using System;
 using Avalonia.Controls.Primitives;
-using Avalonia.Data;
 using Avalonia.Layout;
+using Avalonia.Media;
 
 namespace Avalonia.Controls
 {
@@ -11,6 +10,92 @@ namespace Avalonia.Controls
     /// </summary>
     public class ProgressBar : RangeBase
     {
+        public class ProgressBarTemplateProperties : AvaloniaObject
+        {
+            private double _container2Width;
+            private double _containerWidth;
+            private double _containerAnimationStartPosition;
+            private double _containerAnimationEndPosition;
+            private double _container2AnimationStartPosition;
+            private double _container2AnimationEndPosition;
+
+            public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerAnimationStartPositionProperty =
+           AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
+               nameof(ContainerAnimationStartPosition),
+               p => p.ContainerAnimationStartPosition,
+               (p, o) => p.ContainerAnimationStartPosition = o, 0d);
+
+            public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerAnimationEndPositionProperty =
+                AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
+                    nameof(ContainerAnimationEndPosition),
+                    p => p.ContainerAnimationEndPosition,
+                    (p, o) => p.ContainerAnimationEndPosition = o, 0d);
+
+            public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2AnimationStartPositionProperty =
+                AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
+                    nameof(Container2AnimationStartPosition),
+                    p => p.Container2AnimationStartPosition,
+                    (p, o) => p.Container2AnimationStartPosition = o, 0d);
+
+            public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2AnimationEndPositionProperty =
+                AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
+                    nameof(Container2AnimationEndPosition),
+                    p => p.Container2AnimationEndPosition,
+                    (p, o) => p.Container2AnimationEndPosition = o);
+
+            public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2WidthProperty =
+                AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
+                    nameof(Container2Width),
+                    p => p.Container2Width,
+                    (p, o) => p.Container2Width = o);
+
+            public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerWidthProperty =
+                AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
+                    nameof(ContainerWidth),
+                    p => p.ContainerWidth,
+                    (p, o) => p.ContainerWidth = o);
+
+            public double ContainerAnimationStartPosition
+            {
+                get => _containerAnimationStartPosition;
+                set => SetAndRaise(ContainerAnimationStartPositionProperty, ref _containerAnimationStartPosition, value);
+            }
+
+            public double ContainerAnimationEndPosition
+            {
+                get => _containerAnimationEndPosition;
+                set => SetAndRaise(ContainerAnimationEndPositionProperty, ref _containerAnimationEndPosition, value);
+            }
+
+            public double Container2AnimationStartPosition
+            {
+                get => _container2AnimationStartPosition;
+                set => SetAndRaise(Container2AnimationStartPositionProperty, ref _container2AnimationStartPosition, value);
+            }
+
+            public double Container2Width
+            {
+                get => _container2Width;
+                set => SetAndRaise(Container2WidthProperty, ref _container2Width, value);
+            }
+
+            public double ContainerWidth
+            {
+                get => _containerWidth;
+                set => SetAndRaise(ContainerWidthProperty, ref _containerWidth, value);
+            }
+
+            public double Container2AnimationEndPosition
+            {
+                get => _container2AnimationEndPosition;
+                set => SetAndRaise(Container2AnimationEndPositionProperty, ref _container2AnimationEndPosition, value);
+            }
+        }
+
+        private double _indeterminateStartingOffset;
+        private double _indeterminateEndingOffset;
+        private Border _indicator;
+
         public static readonly StyledProperty<bool> IsIndeterminateProperty =
             AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate));
 
@@ -20,19 +105,33 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<Orientation> OrientationProperty =
             AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal);
 
-        private static readonly DirectProperty<ProgressBar, double> IndeterminateStartingOffsetProperty =
+        [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
+        public static readonly DirectProperty<ProgressBar, double> IndeterminateStartingOffsetProperty =
             AvaloniaProperty.RegisterDirect<ProgressBar, double>(
                 nameof(IndeterminateStartingOffset),
                 p => p.IndeterminateStartingOffset,
                 (p, o) => p.IndeterminateStartingOffset = o);
 
-        private static readonly DirectProperty<ProgressBar, double> IndeterminateEndingOffsetProperty =
+        [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
+        public static readonly DirectProperty<ProgressBar, double> IndeterminateEndingOffsetProperty =
             AvaloniaProperty.RegisterDirect<ProgressBar, double>(
                 nameof(IndeterminateEndingOffset),
                 p => p.IndeterminateEndingOffset,
                 (p, o) => p.IndeterminateEndingOffset = o);
 
-        private Border _indicator;
+        [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
+        public double IndeterminateStartingOffset
+        {
+            get => _indeterminateStartingOffset;
+            set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
+        }
+
+        [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
+        public double IndeterminateEndingOffset
+        {
+            get => _indeterminateEndingOffset;
+            set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value);
+        }
 
         static ProgressBar()
         {
@@ -45,6 +144,8 @@ namespace Avalonia.Controls
             UpdatePseudoClasses(IsIndeterminate, Orientation);
         }
 
+        public ProgressBarTemplateProperties TemplateProperties { get; } = new ProgressBarTemplateProperties();
+
         public bool IsIndeterminate
         {
             get => GetValue(IsIndeterminateProperty);
@@ -62,19 +163,6 @@ namespace Avalonia.Controls
             get => GetValue(OrientationProperty);
             set => SetValue(OrientationProperty, value);
         }
-        private double _indeterminateStartingOffset;
-        private double IndeterminateStartingOffset
-        {
-            get => _indeterminateStartingOffset;
-            set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
-        }
-
-        private double _indeterminateEndingOffset;
-        private double IndeterminateEndingOffset
-        {
-            get => _indeterminateEndingOffset;
-            set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value);
-        }
 
         /// <inheritdoc/>
         protected override Size ArrangeOverride(Size finalSize)
@@ -111,21 +199,33 @@ namespace Avalonia.Controls
             {
                 if (IsIndeterminate)
                 {
-                    if (Orientation == Orientation.Horizontal)
-                    {
-                        var width = bounds.Width / 5.0;
-                        IndeterminateStartingOffset = -width;
-                        _indicator.Width = width;
-                        IndeterminateEndingOffset = bounds.Width;
+                    // Pulled from ModernWPF.
 
-                    }
-                    else
-                    {
-                        var height = bounds.Height / 5.0;
-                        IndeterminateStartingOffset = -bounds.Height;
-                        _indicator.Height = height;
-                        IndeterminateEndingOffset = height;
-                    }
+                    var dim = Orientation == Orientation.Horizontal ? bounds.Width : bounds.Height;
+                    var barIndicatorWidth = dim * 0.4; // Indicator width at 40% of ProgressBar
+                    var barIndicatorWidth2 = dim * 0.6; // Indicator width at 60% of ProgressBar
+
+                    TemplateProperties.ContainerWidth = barIndicatorWidth;
+                    TemplateProperties.Container2Width = barIndicatorWidth2;
+
+                    TemplateProperties.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180%
+                    TemplateProperties.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300%
+
+                    TemplateProperties.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150%
+                    TemplateProperties.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166%
+
+                    // Remove these properties when we switch to fluent as default and removed the old one.
+                    IndeterminateStartingOffset = -(dim / 5d);
+                    IndeterminateEndingOffset = dim;
+
+                    var padding = Padding;
+                    var rectangle = new RectangleGeometry(
+                        new Rect(
+                            padding.Left,
+                            padding.Top,
+                            bounds.Width - (padding.Right + padding.Left),
+                            bounds.Height - (padding.Bottom + padding.Top)
+                            ));
                 }
                 else
                 {

+ 22 - 45
src/Avalonia.Controls/Repeater/ViewportManager.cs

@@ -49,8 +49,8 @@ namespace Avalonia.Controls
         // For non-virtualizing layouts, we do not need to keep
         // updating viewports and invalidating measure often. So when
         // a non virtualizing layout is used, we stop doing all that work.
-        bool _managingViewportDisabled;
-        private IDisposable _effectiveViewportChangedRevoker;
+        private bool _managingViewportDisabled;
+        private bool _effectiveViewportChangedSubscribed;
         private bool _layoutUpdatedSubscribed;
 
         public ViewportManager(ItemsRepeater owner)
@@ -228,11 +228,15 @@ namespace Avalonia.Controls
             _pendingViewportShift = default;
             _unshiftableShift = default;
 
-            _effectiveViewportChangedRevoker?.Dispose();
-
-            if (!_managingViewportDisabled)
+            if (_managingViewportDisabled && _effectiveViewportChangedSubscribed)
             {
-                _effectiveViewportChangedRevoker = SubscribeToEffectiveViewportChanged(_owner);
+                _owner.EffectiveViewportChanged -= OnEffectiveViewportChanged;
+                _effectiveViewportChangedSubscribed = false;
+            }
+            else if (!_managingViewportDisabled && !_effectiveViewportChangedSubscribed)
+            {
+                _owner.EffectiveViewportChanged += OnEffectiveViewportChanged;
+                _effectiveViewportChangedSubscribed = true;
             }
         }
 
@@ -340,6 +344,11 @@ namespace Avalonia.Controls
                 // Note that the element being brought into view could be a descendant.
                 var targetChild = GetImmediateChildOfRepeater((IControl)args.TargetObject);
 
+                if (targetChild is null)
+                {
+                    return;
+                }
+
                 // Make sure that only the target child can be the anchor during the bring into view operation.
                 foreach (var child in _owner.Children)
                 {
@@ -373,7 +382,7 @@ namespace Avalonia.Controls
 
             if (parent == null)
             {
-                throw new InvalidOperationException("OnBringIntoViewRequested called with args.target element not under the ItemsRepeater that recieved the call");
+                return null;
             }
 
             return targetChild;
@@ -415,15 +424,15 @@ namespace Avalonia.Controls
                 _scroller = null;
             }
 
-            _effectiveViewportChangedRevoker?.Dispose();
-            _effectiveViewportChangedRevoker = null;
+            _owner.EffectiveViewportChanged -= OnEffectiveViewportChanged;
+            _effectiveViewportChangedSubscribed = false;
             _ensuredScroller = false;
         }
 
-        private void OnEffectiveViewportChanged(Rect effectiveViewport)
+        private void OnEffectiveViewportChanged(object sender, EffectiveViewportChangedEventArgs e)
         {
             Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: EffectiveViewportChanged event callback", _owner.Layout.LayoutId);
-            UpdateViewport(effectiveViewport);
+            UpdateViewport(e.EffectiveViewport);
 
             _pendingViewportShift = default;
             _unshiftableShift = default;
@@ -468,8 +477,8 @@ namespace Avalonia.Controls
                 }
                 else if (!_managingViewportDisabled)
                 {
-                    _effectiveViewportChangedRevoker?.Dispose();
-                    _effectiveViewportChangedRevoker = SubscribeToEffectiveViewportChanged(_owner);
+                    _owner.EffectiveViewportChanged += OnEffectiveViewportChanged;
+                    _effectiveViewportChangedSubscribed = true;
                 }
 
                 _ensuredScroller = true;
@@ -529,38 +538,6 @@ namespace Avalonia.Controls
             }
         }
 
-        private IDisposable SubscribeToEffectiveViewportChanged(IControl control)
-        {
-            // HACK: This is a bit of a hack. We need the effective viewport of the ItemsRepeater -
-            // we can get this from TransformedBounds, but this property is updated after layout has
-            // run, which is too late. Instead, for now lets just hook into an internal event on
-            // ScrollContentPresenter to find out what the offset and viewport will be after arrange
-            // and use those values. Note that this doesn't handle nested ScrollViewers at all, but
-            // it's enough to get scrolling to non-uniformly sized items working for now.
-            //
-            // UWP uses the EffectiveViewportChanged event (which I think was implemented specially
-            // for this case): we need to implement that in Avalonia, but the semantics of it aren't
-            // clear to me. Hopefully the source for this event will be released with WinUI 3.
-            if (control.VisualParent is ScrollContentPresenter scp)
-            {
-                scp.PreArrange += ScrollContentPresenterPreArrange;
-                return Disposable.Create(() => scp.PreArrange -= ScrollContentPresenterPreArrange);
-            }
-
-            return Disposable.Empty;
-        }
-
-        private void ScrollContentPresenterPreArrange(object sender, VectorEventArgs e)
-        {
-            var scp = (ScrollContentPresenter)sender;
-            var effectiveViewport = new Rect((Point)scp.Offset, new Size(e.Vector.X, e.Vector.Y));
-
-            if (effectiveViewport != _visibleWindow)
-            {
-                OnEffectiveViewportChanged(effectiveViewport);
-            }
-        }
-
         private class ScrollerInfo
         {
             public ScrollerInfo(ScrollViewer scroller)

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

@@ -189,8 +189,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                var isSelected = IsSelectedWithPartialAt(value);
-
                 if (!IsSelectedAt(value) || SelectedItems.Count > 1)
                 {
                     using var operation = new Operation(this);

+ 6 - 3
src/Avalonia.Controls/TopLevel.cs

@@ -318,7 +318,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Creates the layout manager for this <see cref="TopLevel" />.
         /// </summary>
-        protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager();
+        protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this);
 
         /// <summary>
         /// Handles a paint notification from <see cref="ITopLevelImpl.Resized"/>.
@@ -340,6 +340,9 @@ namespace Avalonia.Controls
                 _globalStyles.GlobalStylesRemoved -= ((IStyleHost)this).StylesRemoved;
             }
 
+            Renderer?.Dispose();
+            Renderer = null;
+            
             var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
             ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
 
@@ -349,8 +352,8 @@ namespace Avalonia.Controls
             (this as IInputRoot).MouseDevice?.TopLevelClosed(this);
             PlatformImpl = null;
             OnClosed(EventArgs.Empty);
-            Renderer?.Dispose();
-            Renderer = null;
+
+            LayoutManager?.Dispose();
         }
 
         /// <summary>

+ 6 - 3
src/Avalonia.Controls/Utils/IEnumerableUtils.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections;
+using System.Collections.Generic;
 using System.Linq;
 
 namespace Avalonia.Controls.Utils
@@ -15,12 +16,14 @@ namespace Avalonia.Controls.Utils
         {
             if (items != null)
             {
-                var collection = items as ICollection;
-
-                if (collection != null)
+                if (items is ICollection collection)
                 {
                     return collection.Count;
                 }
+                else if (items is IReadOnlyCollection<object> readOnly)
+                {
+                    return readOnly.Count;
+                }
                 else
                 {
                     return Enumerable.Count(items.Cast<object>());

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

@@ -519,7 +519,7 @@ namespace Avalonia.Controls
                 }
             }
 
-            LayoutManager.ExecuteInitialLayoutPass(this);
+            LayoutManager.ExecuteInitialLayoutPass();
 
             using (BeginAutoSizing())
             {
@@ -592,7 +592,7 @@ namespace Avalonia.Controls
                 }
             }
 
-            LayoutManager.ExecuteInitialLayoutPass(this);
+            LayoutManager.ExecuteInitialLayoutPass();
 
             var result = new TaskCompletionSource<TResult>();
 

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

@@ -162,7 +162,7 @@ namespace Avalonia.Controls
 
                 if (!_hasExecutedInitialLayoutPass)
                 {
-                    LayoutManager.ExecuteInitialLayoutPass(this);
+                    LayoutManager.ExecuteInitialLayoutPass();
                     _hasExecutedInitialLayoutPass = true;
                 }
                 PlatformImpl?.Show();

+ 9 - 1
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@@ -45,7 +45,15 @@ namespace Avalonia.Diagnostics
 
                 window.Closed += DevToolsClosed;
                 s_open.Add(root, window);
-                window.Show();
+
+                if (root is Window inspectedWindow)
+                {
+                    window.Show(inspectedWindow);
+                }
+                else
+                {
+                    window.Show();
+                }
             }
 
             return Disposable.Create(() => window?.Close());

+ 28 - 1
src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs

@@ -1,3 +1,4 @@
+using System;
 using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.LogicalTree;
@@ -9,7 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels
         public LogicalTreeNode(ILogical logical, TreeNode parent)
             : base((Control)logical, parent)
         {
-            Children = logical.LogicalChildren.CreateDerivedList(x => new LogicalTreeNode(x, this));
+            Children = new LogicalTreeNodeCollection(this, logical);
         }
 
         public static LogicalTreeNode[] Create(object control)
@@ -17,5 +18,31 @@ namespace Avalonia.Diagnostics.ViewModels
             var logical = control as ILogical;
             return logical != null ? new[] { new LogicalTreeNode(logical, null) } : null;
         }
+
+        internal class LogicalTreeNodeCollection : TreeNodeCollection
+        {
+            private readonly ILogical _control;
+            private IDisposable _subscription;
+
+            public LogicalTreeNodeCollection(TreeNode owner, ILogical control)
+                : base(owner)
+            {
+                _control = control;
+            }
+
+            public override void Dispose()
+            {
+                base.Dispose();
+                _subscription?.Dispose();
+            }
+
+            protected override void Initialize(AvaloniaList<TreeNode> nodes)
+            {
+                _subscription = _control.LogicalChildren.ForEachItem(
+                    (i, item) => nodes.Insert(i, new LogicalTreeNode(item, Owner)),
+                    (i, item) => nodes.RemoveAt(i),
+                    () => nodes.Clear());
+            }
+        }
     }
 }

+ 14 - 9
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@@ -1,4 +1,5 @@
 using System;
+using System.ComponentModel;
 using Avalonia.Controls;
 using Avalonia.Diagnostics.Models;
 using Avalonia.Input;
@@ -12,6 +13,7 @@ namespace Avalonia.Diagnostics.ViewModels
         private readonly TreePageViewModel _logicalTree;
         private readonly TreePageViewModel _visualTree;
         private readonly EventsPageViewModel _events;
+        private readonly IDisposable _pointerOverSubscription;
         private ViewModelBase _content;
         private int _selectedTab;
         private string _focusedControl;
@@ -25,16 +27,9 @@ namespace Avalonia.Diagnostics.ViewModels
             _events = new EventsPageViewModel(root);
 
             UpdateFocusedControl();
-            KeyboardDevice.Instance.PropertyChanged += (s, e) =>
-            {
-                if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
-                {
-                    UpdateFocusedControl();
-                }
-            };
-
+            KeyboardDevice.Instance.PropertyChanged += KeyboardPropertyChanged;
             SelectedTab = 0;
-            root.GetObservable(TopLevel.PointerOverElementProperty)
+            _pointerOverSubscription = root.GetObservable(TopLevel.PointerOverElementProperty)
                 .Subscribe(x => PointerOverElement = x?.GetType().Name);
             Console = new ConsoleViewModel(UpdateConsoleContext);
         }
@@ -129,6 +124,8 @@ namespace Avalonia.Diagnostics.ViewModels
 
         public void Dispose()
         {
+            KeyboardDevice.Instance.PropertyChanged -= KeyboardPropertyChanged;
+            _pointerOverSubscription.Dispose();
             _logicalTree.Dispose();
             _visualTree.Dispose();
         }
@@ -137,5 +134,13 @@ namespace Avalonia.Diagnostics.ViewModels
         {
             FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
         }
+
+        private void KeyboardPropertyChanged(object sender, PropertyChangedEventArgs e)
+        {
+            if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
+            {
+                UpdateFocusedControl();
+            }
+        }
     }
 }

+ 10 - 4
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs

@@ -3,15 +3,15 @@ using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Reactive;
 using System.Reactive.Linq;
-using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.LogicalTree;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Diagnostics.ViewModels
 {
-    internal class TreeNode : ViewModelBase
+    internal class TreeNode : ViewModelBase, IDisposable
     {
+        private IDisposable _classesSubscription;
         private string _classes;
         private bool _isExpanded;
 
@@ -33,7 +33,7 @@ namespace Avalonia.Diagnostics.ViewModels
                         x => control.Classes.CollectionChanged -= x)
                     .TakeUntil(removed);
 
-                classesChanged.Select(_ => Unit.Default)
+                _classesSubscription = classesChanged.Select(_ => Unit.Default)
                     .StartWith(Unit.Default)
                     .Subscribe(_ =>
                     {
@@ -49,7 +49,7 @@ namespace Avalonia.Diagnostics.ViewModels
             }
         }
 
-        public IAvaloniaReadOnlyList<TreeNode> Children
+        public TreeNodeCollection Children
         {
             get;
             protected set;
@@ -104,6 +104,12 @@ namespace Avalonia.Diagnostics.ViewModels
             }
         }
 
+        public void Dispose()
+        {
+            _classesSubscription.Dispose();
+            Children.Dispose();
+        }
+
         private static int IndexOf(IReadOnlyList<TreeNode> collection, TreeNode item)
         {
             var count = collection.Count;

+ 78 - 0
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs

@@ -0,0 +1,78 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using Avalonia.Collections;
+
+namespace Avalonia.Diagnostics.ViewModels
+{
+    internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList<TreeNode>, IDisposable
+    {
+        private AvaloniaList<TreeNode> _inner;
+
+        public TreeNodeCollection(TreeNode owner) => Owner = owner;
+
+        public TreeNode this[int index]
+        {
+            get
+            {
+                EnsureInitialized();
+                return _inner[index];
+            }
+        }
+
+        public int Count
+        {
+            get
+            {
+                EnsureInitialized();
+                return _inner.Count;
+            }
+        }
+
+        protected TreeNode Owner { get; }
+
+        public event NotifyCollectionChangedEventHandler CollectionChanged
+        {
+            add => _inner.CollectionChanged += value;
+            remove => _inner.CollectionChanged -= value;
+        }
+
+        public event PropertyChangedEventHandler PropertyChanged
+        {
+            add => _inner.PropertyChanged += value;
+            remove => _inner.PropertyChanged -= value;
+        }
+
+        public virtual void Dispose()
+        {
+            if (_inner is object)
+            {
+                foreach (var node in _inner)
+                {
+                    node.Dispose();
+                }
+            }
+        }
+
+        public IEnumerator<TreeNode> GetEnumerator()
+        {
+            EnsureInitialized();
+            return _inner.GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+        protected abstract void Initialize(AvaloniaList<TreeNode> nodes);
+
+        private void EnsureInitialized()
+        {
+            if (_inner is null)
+            {
+                _inner = new AvaloniaList<TreeNode>();
+                Initialize(_inner);
+            }
+        }
+    }
+}

+ 9 - 1
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@@ -62,7 +62,15 @@ namespace Avalonia.Diagnostics.ViewModels
             }
         }
 
-        public void Dispose() => _details?.Dispose();
+        public void Dispose()
+        {
+            foreach (var node in Nodes)
+            {
+                node.Dispose();
+            }
+
+            _details?.Dispose();
+        }
 
         public TreeNode FindNode(IControl control)
         {

+ 27 - 10
src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs

@@ -1,3 +1,4 @@
+using System;
 using Avalonia.Collections;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
@@ -9,16 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels
         public VisualTreeNode(IVisual visual, TreeNode parent)
             : base(visual, parent)
         {
-            var host = visual as IVisualTreeHost;
-
-            if (host?.Root == null)
-            {
-                Children = visual.VisualChildren.CreateDerivedList(x => new VisualTreeNode(x, this));
-            }
-            else
-            {
-                Children = new AvaloniaList<VisualTreeNode>(new[] { new VisualTreeNode(host.Root, this) });
-            }
+            Children = new VisualTreeNodeCollection(this, visual);
 
             if ((Visual is IStyleable styleable))
             {
@@ -33,5 +25,30 @@ namespace Avalonia.Diagnostics.ViewModels
             var visual = control as IVisual;
             return visual != null ? new[] { new VisualTreeNode(visual, null) } : null;
         }
+
+        internal class VisualTreeNodeCollection : TreeNodeCollection
+        {
+            private readonly IVisual _control;
+            private IDisposable _subscription;
+
+            public VisualTreeNodeCollection(TreeNode owner, IVisual control)
+                : base(owner)
+            {
+                _control = control;
+            }
+
+            public override void Dispose()
+            {
+                _subscription?.Dispose();
+            }
+
+            protected override void Initialize(AvaloniaList<TreeNode> nodes)
+            {
+                _subscription = _control.VisualChildren.ForEachItem(
+                    (i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)),
+                    (i, item) => nodes.RemoveAt(i),
+                    () => nodes.Clear());
+            }
+        }
     }
 }

+ 20 - 2
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@@ -14,8 +14,8 @@ namespace Avalonia.Diagnostics.Views
 {
     internal class MainWindow : Window, IStyleHost
     {
+        private readonly IDisposable _keySubscription;
         private TopLevel _root;
-        private IDisposable _keySubscription;
 
         public MainWindow()
         {
@@ -33,8 +33,22 @@ namespace Avalonia.Diagnostics.Views
             {
                 if (_root != value)
                 {
+                    if (_root != null)
+                    {
+                        _root.Closed -= RootClosed;
+                    }
+
                     _root = value;
-                    DataContext = new MainViewModel(value);
+
+                    if (_root != null)
+                    {
+                        _root.Closed += RootClosed;
+                        DataContext = new MainViewModel(value);
+                    }
+                    else
+                    {
+                        DataContext = null;
+                    }
                 }
             }
         }
@@ -45,6 +59,8 @@ namespace Avalonia.Diagnostics.Views
         {
             base.OnClosed(e);
             _keySubscription.Dispose();
+            _root.Closed -= RootClosed;
+            _root = null;
             ((MainViewModel)DataContext)?.Dispose();
         }
 
@@ -70,5 +86,7 @@ namespace Avalonia.Diagnostics.Views
                 }
             }
         }
+
+        private void RootClosed(object sender, EventArgs e) => Close();
     }
 }

+ 24 - 0
src/Avalonia.Layout/EffectiveViewportChangedEventArgs.cs

@@ -0,0 +1,24 @@
+using System;
+
+namespace Avalonia.Layout
+{
+    /// <summary>
+    /// Provides data for the <see cref="Layoutable.EffectiveViewportChanged"/> event.
+    /// </summary>
+    public class EffectiveViewportChangedEventArgs : EventArgs
+    {
+        public EffectiveViewportChangedEventArgs(Rect effectiveViewport)
+        {
+            EffectiveViewport = effectiveViewport;
+        }
+
+        /// <summary>
+        /// Gets the <see cref="Rect"/> representing the effective viewport.
+        /// </summary>
+        /// <remarks>
+        /// The viewport is expressed in coordinates relative to the control that the event is
+        /// raised on.
+        /// </remarks>
+        public Rect EffectiveViewport { get; }
+    }
+}

+ 23 - 1
src/Avalonia.Layout/ILayoutManager.cs

@@ -7,7 +7,7 @@ namespace Avalonia.Layout
     /// <summary>
     /// Manages measuring and arranging of controls.
     /// </summary>
-    public interface ILayoutManager
+    public interface ILayoutManager : IDisposable
     {
         /// <summary>
         /// Raised when the layout manager completes a layout pass.
@@ -35,6 +35,15 @@ namespace Avalonia.Layout
         /// </remarks>
         void ExecuteLayoutPass();
 
+        /// <summary>
+        /// Executes the initial layout pass on a layout root.
+        /// </summary>
+        /// <remarks>
+        /// You should not usually need to call this method explictly, the layout root will call
+        /// it to carry out the initial layout of the control.
+        /// </remarks>
+        void ExecuteInitialLayoutPass();
+
         /// <summary>
         /// Executes the initial layout pass on a layout root.
         /// </summary>
@@ -43,6 +52,19 @@ namespace Avalonia.Layout
         /// You should not usually need to call this method explictly, the layout root will call
         /// it to carry out the initial layout of the control.
         /// </remarks>
+        [Obsolete("Call ExecuteInitialLayoutPass without parameter")]
         void ExecuteInitialLayoutPass(ILayoutRoot root);
+
+        /// <summary>
+        /// Registers a control as wanting to receive effective viewport notifications.
+        /// </summary>
+        /// <param name="control">The control.</param>
+        void RegisterEffectiveViewportListener(ILayoutable control);
+
+        /// <summary>
+        /// Registers a control as no longer wanting to receive effective viewport notifications.
+        /// </summary>
+        /// <param name="control">The control.</param>
+        void UnregisterEffectiveViewportListener(ILayoutable control);
     }
 }

+ 7 - 0
src/Avalonia.Layout/ILayoutable.cs

@@ -111,5 +111,12 @@ namespace Avalonia.Layout
         /// </summary>
         /// <param name="control">The child control.</param>
         void ChildDesiredSizeChanged(ILayoutable control);
+
+        /// <summary>
+        /// Used by the <see cref="LayoutManager"/> to notify the control that its effective
+        /// viewport is changed.
+        /// </summary>
+        /// <param name="e">The viewport information.</param>
+        void EffectiveViewportChanged(EffectiveViewportChangedEventArgs e);
     }
 }

+ 193 - 12
src/Avalonia.Layout/LayoutManager.cs

@@ -1,7 +1,10 @@
 using System;
+using System.Buffers;
+using System.Collections.Generic;
 using System.Diagnostics;
 using Avalonia.Logging;
 using Avalonia.Threading;
+using Avalonia.VisualTree;
 
 #nullable enable
 
@@ -10,16 +13,21 @@ namespace Avalonia.Layout
     /// <summary>
     /// Manages measuring and arranging of controls.
     /// </summary>
-    public class LayoutManager : ILayoutManager
+    public class LayoutManager : ILayoutManager, IDisposable
     {
+        private const int MaxPasses = 3;
+        private readonly ILayoutRoot _owner;
         private readonly LayoutQueue<ILayoutable> _toMeasure = new LayoutQueue<ILayoutable>(v => !v.IsMeasureValid);
         private readonly LayoutQueue<ILayoutable> _toArrange = new LayoutQueue<ILayoutable>(v => !v.IsArrangeValid);
         private readonly Action _executeLayoutPass;
+        private List<EffectiveViewportChangedListener>? _effectiveViewportChangedListeners;
+        private bool _disposed;
         private bool _queued;
         private bool _running;
 
-        public LayoutManager()
+        public LayoutManager(ILayoutRoot owner)
         {
+            _owner = owner ?? throw new ArgumentNullException(nameof(owner));
             _executeLayoutPass = ExecuteLayoutPass;
         }
 
@@ -31,6 +39,11 @@ namespace Avalonia.Layout
             control = control ?? throw new ArgumentNullException(nameof(control));
             Dispatcher.UIThread.VerifyAccess();
 
+            if (_disposed)
+            {
+                return;
+            }
+
             if (!control.IsAttachedToVisualTree)
             {
 #if DEBUG
@@ -41,6 +54,11 @@ namespace Avalonia.Layout
 #endif
             }
 
+            if (control.VisualRoot != _owner)
+            {
+                throw new ArgumentException("Attempt to call InvalidateMeasure on wrong LayoutManager.");
+            }
+
             _toMeasure.Enqueue(control);
             _toArrange.Enqueue(control);
             QueueLayoutPass();
@@ -52,6 +70,11 @@ namespace Avalonia.Layout
             control = control ?? throw new ArgumentNullException(nameof(control));
             Dispatcher.UIThread.VerifyAccess();
 
+            if (_disposed)
+            {
+                return;
+            }
+
             if (!control.IsAttachedToVisualTree)
             {
 #if DEBUG
@@ -62,6 +85,11 @@ namespace Avalonia.Layout
 #endif
             }
 
+            if (control.VisualRoot != _owner)
+            {
+                throw new ArgumentException("Attempt to call InvalidateArrange on wrong LayoutManager.");
+            }
+
             _toArrange.Enqueue(control);
             QueueLayoutPass();
         }
@@ -69,14 +97,15 @@ namespace Avalonia.Layout
         /// <inheritdoc/>
         public virtual void ExecuteLayoutPass()
         {
-            const int MaxPasses = 3;
-
             Dispatcher.UIThread.VerifyAccess();
 
-            if (!_running)
+            if (_disposed)
             {
-                _running = true;
+                return;
+            }
 
+            if (!_running)
+            {
                 Stopwatch? stopwatch = null;
 
                 const LogEventLevel timingLogLevel = LogEventLevel.Information;
@@ -99,12 +128,13 @@ namespace Avalonia.Layout
 
                 try
                 {
+                    _running = true;
+
                     for (var pass = 0; pass < MaxPasses; ++pass)
                     {
-                        ExecuteMeasurePass();
-                        ExecuteArrangePass();
+                        InnerLayoutPass();
 
-                        if (_toMeasure.Count == 0)
+                        if (!RaiseEffectiveViewportChanged())
                         {
                             break;
                         }
@@ -131,13 +161,18 @@ namespace Avalonia.Layout
         }
 
         /// <inheritdoc/>
-        public virtual void ExecuteInitialLayoutPass(ILayoutRoot root)
+        public virtual void ExecuteInitialLayoutPass()
         {
+            if (_disposed)
+            {
+                return;
+            }
+
             try
             {
                 _running = true;
-                Measure(root);
-                Arrange(root);
+                Measure(_owner);
+                Arrange(_owner);
             }
             finally
             {
@@ -151,6 +186,60 @@ namespace Avalonia.Layout
             ExecuteLayoutPass();
         }
 
+        [Obsolete("Call ExecuteInitialLayoutPass without parameter")]
+        public void ExecuteInitialLayoutPass(ILayoutRoot root)
+        {
+            if (root != _owner)
+            {
+                throw new ArgumentException("ExecuteInitialLayoutPass called with incorrect root.");
+            }
+
+            ExecuteInitialLayoutPass();
+        }
+
+        public void Dispose()
+        {
+            _disposed = true;
+            _toMeasure.Dispose();
+            _toArrange.Dispose();
+        }
+
+        void ILayoutManager.RegisterEffectiveViewportListener(ILayoutable control)
+        {
+            _effectiveViewportChangedListeners ??= new List<EffectiveViewportChangedListener>();
+            _effectiveViewportChangedListeners.Add(new EffectiveViewportChangedListener(
+                control,
+                CalculateEffectiveViewport(control)));
+        }
+
+        void ILayoutManager.UnregisterEffectiveViewportListener(ILayoutable control)
+        {
+            if (_effectiveViewportChangedListeners is object)
+            {
+                for (var i = _effectiveViewportChangedListeners.Count - 1; i >= 0; --i)
+                {
+                    if (_effectiveViewportChangedListeners[i].Listener == control)
+                    {
+                        _effectiveViewportChangedListeners.RemoveAt(i);
+                    }
+                }
+            }
+        }
+
+        private void InnerLayoutPass()
+        {
+            for (var pass = 0; pass < MaxPasses; ++pass)
+            {
+                ExecuteMeasurePass();
+                ExecuteArrangePass();
+
+                if (_toMeasure.Count == 0)
+                {
+                    break;
+                }
+            }
+        }
+
         private void ExecuteMeasurePass()
         {
             while (_toMeasure.Count > 0)
@@ -234,5 +323,97 @@ namespace Avalonia.Layout
                 _queued = true;
             }
         }
+
+        private bool RaiseEffectiveViewportChanged()
+        {
+            var startCount = _toMeasure.Count + _toArrange.Count;
+
+            if (_effectiveViewportChangedListeners is object)
+            {
+                var count = _effectiveViewportChangedListeners.Count;
+                var pool = ArrayPool<EffectiveViewportChangedListener>.Shared;
+                var listeners = pool.Rent(count);
+
+                _effectiveViewportChangedListeners.CopyTo(listeners);
+
+                try
+                {
+                    for (var i = 0; i < count; ++i)
+                    {
+                        var l = _effectiveViewportChangedListeners[i];
+
+                        if (!l.Listener.IsAttachedToVisualTree)
+                        {
+                            continue;
+                        }
+
+                        var viewport = CalculateEffectiveViewport(l.Listener);
+
+                        if (viewport != l.Viewport)
+                        {
+                            l.Listener.EffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport));
+                            _effectiveViewportChangedListeners[i] = new EffectiveViewportChangedListener(l.Listener, viewport);
+                        }
+                    }
+                }
+                finally
+                {
+                    pool.Return(listeners, clearArray: true);
+                }
+            }
+
+            return startCount != _toMeasure.Count + _toArrange.Count;
+        }
+
+        private Rect CalculateEffectiveViewport(IVisual control)
+        {
+            var viewport = new Rect(0, 0, double.PositiveInfinity, double.PositiveInfinity);
+            CalculateEffectiveViewport(control, control, ref viewport);
+            return viewport;
+        }
+
+        private void CalculateEffectiveViewport(IVisual target, IVisual control, ref Rect viewport)
+        {
+            // Recurse until the top level control.
+            if (control.VisualParent is object)
+            {
+                CalculateEffectiveViewport(target, control.VisualParent, ref viewport);
+            }
+            else
+            {
+                viewport = new Rect(control.Bounds.Size);
+            }
+
+            // Apply the control clip bounds if it's not the target control. We don't apply it to
+            // the target control because it may itself be clipped to bounds and if so the viewport
+            // we calculate would be of no use.
+            if (control != target && control.ClipToBounds)
+            {
+                viewport = control.Bounds.Intersect(viewport);
+            }
+
+            // Translate the viewport into this control's coordinate space.
+            viewport = viewport.Translate(-control.Bounds.Position);
+
+            if (control != target && control.RenderTransform is object)
+            {
+                var origin = control.RenderTransformOrigin.ToPixels(control.Bounds.Size);
+                var offset = Matrix.CreateTranslation(origin);
+                var renderTransform = (-offset) * control.RenderTransform.Value.Invert() * (offset);
+                viewport = viewport.TransformToAABB(renderTransform);
+            }
+        }
+
+        private readonly struct EffectiveViewportChangedListener
+        {
+            public EffectiveViewportChangedListener(ILayoutable listener, Rect viewport)
+            {
+                Listener = listener;
+                Viewport = viewport;
+            }
+
+            public ILayoutable Listener { get; }
+            public Rect Viewport { get; }
+        }
     }
 }

+ 8 - 1
src/Avalonia.Layout/LayoutQueue.cs

@@ -4,7 +4,7 @@ using System.Collections.Generic;
 
 namespace Avalonia.Layout
 {
-    internal class LayoutQueue<T> : IReadOnlyCollection<T>
+    internal class LayoutQueue<T> : IReadOnlyCollection<T>, IDisposable
     {
         private struct Info
         {
@@ -84,5 +84,12 @@ namespace Avalonia.Layout
 
             _notFinalizedBuffer.Clear();
         }
+
+        public void Dispose()
+        {
+            _inner.Clear();
+            _loopQueueInfo.Clear();
+            _notFinalizedBuffer.Clear();
+        }
     }
 }

+ 59 - 11
src/Avalonia.Layout/Layoutable.cs

@@ -132,6 +132,7 @@ namespace Avalonia.Layout
         private bool _measuring;
         private Size? _previousMeasure;
         private Rect? _previousArrange;
+        private EventHandler<EffectiveViewportChangedEventArgs>? _effectiveViewportChanged;
         private EventHandler? _layoutUpdated;
 
         /// <summary>
@@ -152,6 +153,32 @@ namespace Avalonia.Layout
                 VerticalAlignmentProperty);
         }
 
+        /// <summary>
+        /// Occurs when the element's effective viewport changes.
+        /// </summary>
+        public event EventHandler<EffectiveViewportChangedEventArgs>? EffectiveViewportChanged
+        {
+            add
+            {
+                if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r)
+                {
+                    r.LayoutManager.RegisterEffectiveViewportListener(this);
+                }
+
+                _effectiveViewportChanged += value;
+            }
+
+            remove
+            {
+                _effectiveViewportChanged -= value;
+
+                if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r)
+                {
+                    r.LayoutManager.UnregisterEffectiveViewportListener(this);
+                }
+            }
+        }
+
         /// <summary>
         /// Occurs when a layout pass completes for the control.
         /// </summary>
@@ -384,13 +411,6 @@ namespace Avalonia.Layout
             }
         }
 
-        /// <summary>
-        /// Called by InvalidateMeasure
-        /// </summary>
-        protected virtual void OnMeasureInvalidated()
-        {
-        }
-
         /// <summary>
         /// Invalidates the measurement of the control and queues a new layout pass.
         /// </summary>
@@ -436,6 +456,11 @@ namespace Avalonia.Layout
             }
         }
 
+        void ILayoutable.EffectiveViewportChanged(EffectiveViewportChangedEventArgs e)
+        {
+            _effectiveViewportChanged?.Invoke(this, e);
+        }
+
         /// <summary>
         /// Marks a property as affecting the control's measurement.
         /// </summary>
@@ -717,9 +742,17 @@ namespace Avalonia.Layout
         {
             base.OnAttachedToVisualTreeCore(e);
 
-            if (_layoutUpdated is object && e.Root is ILayoutRoot r)
+            if (e.Root is ILayoutRoot r)
             {
-                r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated;
+                if (_layoutUpdated is object)
+                {
+                    r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated;
+                }
+
+                if (_effectiveViewportChanged is object)
+                {
+                    r.LayoutManager.RegisterEffectiveViewportListener(this);
+                }
             }
         }
 
@@ -727,12 +760,27 @@ namespace Avalonia.Layout
         {
             base.OnDetachedFromVisualTreeCore(e);
 
-            if (_layoutUpdated is object && e.Root is ILayoutRoot r)
+            if (e.Root is ILayoutRoot r)
             {
-                r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated;
+                if (_layoutUpdated is object)
+                {
+                    r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated;
+                }
+
+                if (_effectiveViewportChanged is object)
+                {
+                    r.LayoutManager.UnregisterEffectiveViewportListener(this);
+                }
             }
         }
 
+        /// <summary>
+        /// Called by InvalidateMeasure
+        /// </summary>
+        protected virtual void OnMeasureInvalidated()
+        {
+        }
+
         /// <inheritdoc/>
         protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
         {

+ 4 - 5
src/Avalonia.Native/NativeControlHostImpl.cs

@@ -114,19 +114,18 @@ namespace Avalonia.Native
 
             public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl;
 
-            public void Hide()
+            public void HideWithSize(Size size)
             {
-                _native?.Hide();
+                _native.HideWithSize(Math.Max(1, (float)size.Width), Math.Max(1, (float)size.Height));
             }
             
-            public void ShowInBounds(TransformedBounds transformedBounds)
+            public void ShowInBounds(Rect bounds)
             {
                 if (_attachedTo == null)
                     throw new InvalidOperationException("Native control isn't attached to a toplevel");
-                var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform);
                 bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width),
                     Math.Max(1, bounds.Height));
-                _native.MoveTo((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height);
+                _native.ShowInBounds((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height);
             }
 
             public void InitWithChild(IPlatformHandle handle) 

+ 12 - 0
src/Avalonia.Native/PopupImpl.cs

@@ -10,6 +10,7 @@ namespace Avalonia.Native
         private readonly IAvaloniaNativeFactory _factory;
         private readonly AvaloniaNativePlatformOptions _opts;
         private readonly GlPlatformFeature _glFeature;
+        private readonly IWindowBaseImpl _parent;
 
         public PopupImpl(IAvaloniaNativeFactory factory,
             AvaloniaNativePlatformOptions opts,
@@ -19,6 +20,7 @@ namespace Avalonia.Native
             _factory = factory;
             _opts = opts;
             _glFeature = glFeature;
+            _parent = parent;
             using (var e = new PopupEvents(this))
             {
                 var context = _opts.UseGpu ? glFeature?.DeferredContext : null;
@@ -58,6 +60,16 @@ namespace Avalonia.Native
             }
         }
 
+        public override void Show()
+        {
+            var parent = _parent;
+            while (parent is PopupImpl p) 
+                parent = p._parent;
+            if (parent is WindowImpl w)
+                w.Native.TakeFocusFromChildren();
+            base.Show();
+        }
+
         public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this);
 
         public void SetWindowManagerAddShadowHint(bool enabled)

+ 1 - 1
src/Avalonia.Native/WindowImplBase.cs

@@ -319,7 +319,7 @@ namespace Avalonia.Native
         }
 
 
-        public void Show()
+        public virtual void Show()
         {
             _native.Show();
         }

+ 10 - 2
src/Avalonia.Themes.Default/ProgressBar.xaml

@@ -1,4 +1,12 @@
 <Styles xmlns="https://github.com/avaloniaui">
+  <Design.PreviewWith>
+    <Border Padding="20">
+      <StackPanel>
+        <ProgressBar VerticalAlignment="Center" IsIndeterminate="True" />
+        <ProgressBar HorizontalAlignment="Left" IsIndeterminate="True" Orientation="Vertical" />
+      </StackPanel>
+    </Border>
+  </Design.PreviewWith>
   <Style Selector="ProgressBar">
     <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
     <Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/>
@@ -69,11 +77,11 @@
                  Easing="LinearEasing">
         <KeyFrame Cue="0%">
           <Setter Property="TranslateTransform.Y"
-                  Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
+                  Value="{Binding TemplateProperties.IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
         </KeyFrame>
         <KeyFrame Cue="100%">
           <Setter Property="TranslateTransform.Y"
-                  Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
+                  Value="{Binding TemplateProperties.IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
         </KeyFrame>
       </Animation>
     </Style.Animations>

+ 7 - 36
src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml

@@ -2,35 +2,8 @@
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:sys="clr-namespace:System;assembly=netstandard">
   <Style.Resources>
-    <Color x:Key="SystemAccentColor">#FF0078D7</Color>
-    <Color x:Key="SystemAltHighColor">#FF000000</Color>
-    <Color x:Key="SystemAltLowColor">#FF000000</Color>
-    <Color x:Key="SystemAltMediumColor">#FF000000</Color>
-    <Color x:Key="SystemAltMediumHighColor">#FF000000</Color>
-    <Color x:Key="SystemAltMediumLowColor">#FF000000</Color>
-    <Color x:Key="SystemBaseHighColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemBaseLowColor">#FF333333</Color>
-    <Color x:Key="SystemBaseMediumColor">#FF9A9A9A</Color>
-    <Color x:Key="SystemBaseMediumHighColor">#FFB4B4B4</Color>
-    <Color x:Key="SystemBaseMediumLowColor">#FF676767</Color>
-    <Color x:Key="SystemChromeAltLowColor">#FFB4B4B4</Color>
-    <Color x:Key="SystemChromeBlackHighColor">#FF000000</Color>
-    <Color x:Key="SystemChromeBlackLowColor">#FFB4B4B4</Color>
-    <Color x:Key="SystemChromeBlackMediumColor">#FF000000</Color>
-    <Color x:Key="SystemChromeBlackMediumLowColor">#FF000000</Color>
-    <Color x:Key="SystemChromeDisabledHighColor">#FF333333</Color>
-    <Color x:Key="SystemChromeGrayColor">#FF808080</Color>
-    <Color x:Key="SystemChromeHighColor">#FF808080</Color>
-    <Color x:Key="SystemChromeLowColor">#FF151515</Color>
-    <Color x:Key="SystemChromeMediumColor">#FF1D1D1D</Color>
-    <Color x:Key="SystemChromeMediumLowColor">#FF2C2C2C</Color>
-    <Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemListLowColor">#FF1D1D1D</Color>
-    <Color x:Key="SystemListMediumColor">#FF333333</Color>
-    <Color x:Key="SystemChromeAltMediumHighColor">#CC000000</Color>
-    <Color x:Key="SystemChromeAltHighColor">#FF333333</Color>
-    <Color x:Key="SystemRevealListLowColor">#FF1D1D1D</Color>
-    <Color x:Key="SystemRevealListMediumColor">#FF333333</Color>
+    <Color x:Key="SystemRevealListLowColor">#18FFFFFF</Color>
+    <Color x:Key="SystemRevealListMediumColor">#30FFFFFF</Color>
     <!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />-->
     <!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
     <SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
@@ -67,13 +40,6 @@
     <Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness>
     <x:Double x:Key="PersonPictureEllipseBadgeStrokeThickness">1</x:Double>
     
-    <!-- Override system generated accent colors -->
-    <Color x:Key="SystemAccentColorDark1">#FF005A9E</Color>
-    <Color x:Key="SystemAccentColorDark2">#FF004275</Color>
-    <Color x:Key="SystemAccentColorDark3">#FF002642</Color>
-    <Color x:Key="SystemAccentColorLight1">#FF429CE3</Color>
-    <Color x:Key="SystemAccentColorLight2">#FF76B9ED</Color>
-    <Color x:Key="SystemAccentColorLight3">#FFA6D8FF</Color>
     <Color x:Key="RegionColor">#FF000000</Color>
     <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
     
@@ -218,6 +184,11 @@
     <SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99000000" />
     <SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" />
     <SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
+    <!-- BaseResources for ProgressBar.xaml -->
+    <SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#30000000" />
+    <SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
+    <SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF4617B4" />
+    <SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF4617B4" />
 
     <!-- BaseResources for TextBox.xaml -->
     <StaticResource x:Key="TextControlForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />

+ 8 - 37
src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml

@@ -2,36 +2,8 @@
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:sys="clr-namespace:System;assembly=netstandard">
   <Style.Resources>
-    <Color x:Key="SystemAccentColor">#FF0078D7</Color>
-    <Color x:Key="SystemAltHighColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemAltLowColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemAltMediumColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemAltMediumHighColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemAltMediumLowColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemBaseHighColor">#FF000000</Color>
-    <Color x:Key="SystemBaseLowColor">#FFCCCCCC</Color>
-    <Color x:Key="SystemBaseMediumColor">#FF898989</Color>
-    <Color x:Key="SystemBaseMediumHighColor">#FF5D5D5D</Color>
-    <Color x:Key="SystemBaseMediumLowColor">#FF737373</Color>
-    <Color x:Key="SystemChromeAltLowColor">#FF5D5D5D</Color>
-    <Color x:Key="SystemChromeBlackHighColor">#FF000000</Color>
-    <Color x:Key="SystemChromeBlackLowColor">#FFCCCCCC</Color>
-    <Color x:Key="SystemChromeBlackMediumColor">#FF5D5D5D</Color>
-    <Color x:Key="SystemChromeBlackMediumLowColor">#FF898989</Color>
-    <Color x:Key="SystemChromeDisabledHighColor">#FFCCCCCC</Color>
-    <Color x:Key="SystemChromeDisabledLowColor">#FF898989</Color>
-    <Color x:Key="SystemChromeGrayColor">#FF737373</Color>
-    <Color x:Key="SystemChromeHighColor">#FFCCCCCC</Color>
-    <Color x:Key="SystemChromeLowColor">#FFECECEC</Color>
-    <Color x:Key="SystemChromeMediumColor">#FFE6E6E6</Color>
-    <Color x:Key="SystemChromeMediumLowColor">#FFECECEC</Color>
-    <Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemListLowColor">#FFE6E6E6</Color>
-    <Color x:Key="SystemListMediumColor">#FFCCCCCC</Color>
-    <Color x:Key="SystemChromeAltMediumHighColor">#CCFFFFFF</Color>
-    <Color x:Key="SystemChromeAltHighColor">#FFCCCCCC</Color>
-    <Color x:Key="SystemRevealListLowColor">#FFE6E6E6</Color>
-    <Color x:Key="SystemRevealListMediumColor">#FFCCCCCC</Color>
+    <Color x:Key="SystemRevealListLowColor">#17000000</Color>
+    <Color x:Key="SystemRevealListMediumColor">#2E000000</Color>
     <!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />-->
     <!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
     <SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
@@ -68,13 +40,6 @@
     <Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness>
     <x:Double x:Key="PersonPictureEllipseBadgeStrokeThickness">1</x:Double>
     
-    <!-- Override system generated accent colors -->
-    <Color x:Key="SystemAccentColorDark1">#FF005A9E</Color>
-    <Color x:Key="SystemAccentColorDark2">#FF004275</Color>
-    <Color x:Key="SystemAccentColorDark3">#FF002642</Color>
-    <Color x:Key="SystemAccentColorLight1">#FF429CE3</Color>
-    <Color x:Key="SystemAccentColorLight2">#FF76B9ED</Color>
-    <Color x:Key="SystemAccentColorLight3">#FFA6D8FF</Color>
     <!--<RevealBackgroundBrush x:Key="SystemControlHighlightListLowRevealBackgroundBrush" TargetTheme="Light" Color="{ThemeResource SystemRevealListMediumColor}" FallbackColor="{ StaticResource SystemListMediumColor}" />-->
     <Color x:Key="RegionColor">#FFFFFFFF</Color>
     <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
@@ -222,6 +187,12 @@
     <SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" />
     <SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
     
+    <!-- BaseResources for ProgressBar.xaml -->
+    <SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#59FFFFFF" />
+    <SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
+    <SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF5B2EC5" />
+    <SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF8A57FF" />
+
     <!-- BaseResources for TextBox.xaml -->
     <StaticResource x:Key="TextControlForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
     <StaticResource x:Key="TextControlForegroundPointerOver" ResourceKey="SystemControlForegroundBaseHighBrush" />

+ 9 - 0
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml

@@ -336,6 +336,15 @@
     <StaticResource x:Key="MenuFlyoutSubItemRevealBorderBrushDisabled" ResourceKey="SystemControlTransparentBrush" />-->
     <!--<Thickness x:Key="LanguageSwitcherMenuFlyoutItemPlaceholderThemeThickness">44,0,0,0</Thickness>-->
 
+    <!-- Resources for ProgressBar.xaml -->
+    <x:Double x:Key="ProgressBarIndicatorPauseOpacity">0.6</x:Double>
+    <x:Double x:Key="ProgressBarThemeMinHeight">4</x:Double>
+    <Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>        
+    <SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#59FFFFFF" />
+    <SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
+    <SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF5B2EC5" />
+    <SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF8A57FF" />
+    
     <!-- Resources for TextBox.xaml -->
     <SolidColorBrush x:Key="TextBoxForegroundHeaderThemeBrush" Color="#FFFFFFFF" />
     <SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#AB000000" />

+ 8 - 0
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml

@@ -248,6 +248,14 @@
     <SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99FFFFFF" />
     <SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" />
     <SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
+    <!-- Resources for ProgressBar.xaml -->
+    <x:Double x:Key="ProgressBarIndicatorPauseOpacity">0.6</x:Double>
+    <x:Double x:Key="ProgressBarThemeMinHeight">4</x:Double>
+    <Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>
+    <SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#30000000" />
+    <SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
+    <SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF4617B4" />
+    <SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF4617B4" />
 
     <!-- Resources for MenuFlyout.xaml (Menu, ContextMenu, etc) -->
     <x:Double x:Key="MenuFlyoutSeparatorThemeHeight">1</x:Double>

+ 1 - 0
src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj

@@ -14,6 +14,7 @@
     <ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
     <AvaloniaResource Include="FluentTheme.xaml" />
     <AvaloniaResource Include="Accents/*.xaml" />
+    <AvaloniaResource Include="DensityStyles/*.xaml" />
     <!-- Compatibility with old apps, probably need to replace with AvaloniaResource -->
     <EmbeddedResource Include="**/*.xaml" />
   </ItemGroup>

+ 23 - 0
src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml

@@ -0,0 +1,23 @@
+<Styles xmlns="https://github.com/avaloniaui" 
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+    <Style Selector="TextBlock" >
+        <Setter Property="FontSize" Value="14" />
+    </Style>
+    <Style>
+        <Style.Resources>
+            <x:Double x:Key="ControlContentThemeFontSize">14</x:Double>
+            <x:Double x:Key="ContentControlFontSize">14</x:Double>
+            <x:Double x:Key="TextControlThemeMinHeight">24</x:Double>
+            <Thickness x:Key="TextControlThemePadding">2,2,6,1</Thickness>
+            <x:Double x:Key="ListViewItemMinHeight">32</x:Double>
+            <x:Double x:Key="TreeViewItemMinHeight">24</x:Double>
+            <Thickness x:Key="TimePickerHostPadding">0,1,0,2</Thickness>
+            <Thickness x:Key="DatePickerHostPadding">0,1,0,2</Thickness>
+            <Thickness x:Key="DatePickerHostMonthPadding">9,0,0,1</Thickness>
+            <Thickness x:Key="ComboBoxEditableTextPadding">10,0,30,0</Thickness>
+            <Thickness x:Key="ComboBoxMinHeight">24</Thickness>
+            <Thickness x:Key="ComboBoxPadding">12,1,0,3</Thickness>
+            <x:Double x:Key="NavigationViewItemOnLeftMinHeight">32</x:Double>
+        </Style.Resources>
+    </Style>
+</Styles>

+ 34 - 10
src/Avalonia.Themes.Fluent/FocusAdorner.xaml

@@ -1,10 +1,34 @@
-<Style xmlns="https://github.com/avaloniaui" Selector=":is(Control)">
-  <Setter Property="FocusAdorner">
-    <FocusAdornerTemplate>
-      <Rectangle Stroke="Black"
-                 StrokeThickness="1"
-                 StrokeDashArray="1,2"
-                 Margin="1"/>
-    </FocusAdornerTemplate>
-  </Setter>
-</Style>
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <Styles.Resources>
+    <Thickness x:Key="SystemControlFocusVisualMargin">0</Thickness>
+    <Thickness x:Key="SystemControlFocusVisualPrimaryThickness">2</Thickness>
+    <Thickness x:Key="SystemControlFocusVisualSecondaryThickness">1</Thickness>
+  </Styles.Resources>
+
+  <!--  HighVisibility FocusAdorner  -->
+  <Style Selector=":is(Control)">
+    <Setter Property="FocusAdorner">
+      <FocusAdornerTemplate>
+        <Border BorderThickness="{StaticResource SystemControlFocusVisualPrimaryThickness}"
+                BorderBrush="{DynamicResource SystemControlFocusVisualPrimaryBrush}"
+                Margin="{StaticResource SystemControlFocusVisualMargin}">
+          <Border BorderThickness="{StaticResource SystemControlFocusVisualSecondaryThickness}"
+                  BorderBrush="{DynamicResource SystemControlFocusVisualSecondaryBrush}" />
+        </Border>
+      </FocusAdornerTemplate>
+    </Setter>
+  </Style>
+
+  <!--  DottedLine FocusAdorner  -->
+  <Style Selector="Window.DottedLineFocusAdorner :is(Control)">
+    <Setter Property="FocusAdorner">
+      <FocusAdornerTemplate>
+        <Rectangle Stroke="{StaticResource SystemControlFocusVisualPrimaryBrush}"
+                   StrokeThickness="1"
+                   StrokeDashArray="1,2"
+                   Margin="1" />
+      </FocusAdornerTemplate>
+    </Setter>
+  </Style>
+</Styles>

+ 143 - 48
src/Avalonia.Themes.Fluent/ProgressBar.xaml

@@ -1,81 +1,176 @@
-<Styles xmlns="https://github.com/avaloniaui">
+<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <Design.PreviewWith>
+    <Border Padding="20">
+      <StackPanel>
+        <ProgressBar VerticalAlignment="Center" IsIndeterminate="True" />
+        <ProgressBar HorizontalAlignment="Left" IsIndeterminate="True" Orientation="Vertical" />
+      </StackPanel>
+    </Border>
+  </Design.PreviewWith>
   <Style Selector="ProgressBar">
-    <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
-    <Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/>
+    <Setter Property="Foreground" Value="{DynamicResource SystemControlHighlightAccentBrush}" />
+    <Setter Property="Background" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
+    <Setter Property="BorderThickness" Value="{DynamicResource ProgressBarBorderThemeThickness}" />
+    <Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightTransparentBrush}" />
+    <Setter Property="Maximum" Value="100" />
+    <Setter Property="MinHeight" Value="{DynamicResource ProgressBarThemeMinHeight}" />
+    <Setter Property="VerticalAlignment" Value="Center" />
     <Setter Property="Template">
       <ControlTemplate>
-        <Grid>
-          <Border Background="{TemplateBinding Background}"
-                  BorderBrush="{TemplateBinding BorderBrush}"
-                  BorderThickness="{TemplateBinding BorderThickness}">
-            <Border Name="PART_Indicator" Background="{TemplateBinding Foreground}"/>
-          </Border>
-          <LayoutTransformControl
-                HorizontalAlignment="Center"
-                VerticalAlignment="Center"
-                IsVisible="{Binding ShowProgressText, RelativeSource={RelativeSource TemplatedParent}}"
-                Name="PART_LayoutTransformControl">
-            <TextBlock
-                Foreground="{DynamicResource ThemeForegroundBrush}"
-                Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, StringFormat={}{0:0}%}" />
-          </LayoutTransformControl>
-        </Grid>
+        <Border x:Name="ProgressBarRoot" ClipToBounds="True" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{DynamicResource ControlCornerRadius}">          
+            <Panel>
+              <Panel x:Name="DeterminateRoot">
+                <Border CornerRadius="{DynamicResource ControlCornerRadius}" x:Name="PART_Indicator" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
+              </Panel>
+              <Panel x:Name="IndeterminateRoot">
+                <Border x:Name="IndeterminateProgressBarIndicator" CornerRadius="{DynamicResource ControlCornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
+                <Border x:Name="IndeterminateProgressBarIndicator2" CornerRadius="{DynamicResource ControlCornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
+              </Panel>
+            </Panel>          
+        </Border>
       </ControlTemplate>
     </Setter>
   </Style>
   <Style Selector="ProgressBar:horizontal /template/ Border#PART_Indicator">
-    <Setter Property="HorizontalAlignment" Value="Left"/>
-    <Setter Property="VerticalAlignment" Value="Stretch"/>
+    <Setter Property="HorizontalAlignment" Value="Left" />
+    <Setter Property="VerticalAlignment" Value="Stretch" />
   </Style>
   <Style Selector="ProgressBar:vertical /template/ Border#PART_Indicator">
-    <Setter Property="HorizontalAlignment" Value="Stretch"/>
-    <Setter Property="VerticalAlignment" Value="Bottom"/>
+    <Setter Property="HorizontalAlignment" Value="Stretch" />
+    <Setter Property="VerticalAlignment" Value="Bottom" />
   </Style>
   <Style Selector="ProgressBar:horizontal">
-    <Setter Property="MinWidth" Value="200"/>
-    <Setter Property="MinHeight" Value="16"/>
+    <Setter Property="MinWidth" Value="200" />
+    <Setter Property="MinHeight" Value="4" />
   </Style>
   <Style Selector="ProgressBar:vertical">
-    <Setter Property="MinWidth" Value="16"/>
-    <Setter Property="MinHeight" Value="200"/>
+    <Setter Property="MinWidth" Value="4" />
+    <Setter Property="MinHeight" Value="200" />
   </Style>
   <Style Selector="ProgressBar:vertical /template/ LayoutTransformControl#PART_LayoutTransformControl">
     <Setter Property="LayoutTransform">
       <Setter.Value>
-        <RotateTransform Angle="90"/>
+        <RotateTransform Angle="90" />
       </Setter.Value>
     </Setter>
   </Style>
-  <Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#PART_Indicator">
+  <!-- FadeInAnimation mockup-->
+  <Style Selector="ProgressBar /template/ Panel#DeterminateRoot">
+    <Setter Property="Transitions">
+      <Transitions>
+        <DoubleTransition Property="Opacity" Duration="0:0:0.197" />
+      </Transitions>
+    </Setter>
+  </Style>
+  <Style Selector="ProgressBar /template/ Panel#IndeterminateRoot">
+    <Setter Property="Transitions">
+      <Transitions>
+        <DoubleTransition Property="Opacity" Duration="0:0:0.197" />
+      </Transitions>
+    </Setter>
+  </Style>
+  <Style Selector="ProgressBar /template/ Panel#DeterminateRoot">
+    <Setter Property="Opacity" Value="1" />
+  </Style>
+  <Style Selector="ProgressBar /template/ Panel#IndeterminateRoot">
+    <Setter Property="Opacity" Value="0" />
+  </Style>
+  <Style Selector="ProgressBar:indeterminate /template/ Panel#DeterminateRoot">
+    <Setter Property="Opacity" Value="0" />
+  </Style>
+  <Style Selector="ProgressBar:indeterminate /template/ Panel#IndeterminateRoot">
+    <Setter Property="Opacity" Value="1" />
+  </Style>
+  <Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#IndeterminateProgressBarIndicator">
+    <Style.Animations>
+      <Animation Duration="0:0:2" IterationCount="Infinite">
+        <KeyFrame KeyTime="0:0:0" KeySpline="0.4,0,0.6,1">
+          <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
+        </KeyFrame>
+        <KeyFrame KeyTime="0:0:1.5" KeySpline="0.4,0,0.6,1">
+          <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationEndPosition}" />
+        </KeyFrame>
+        <KeyFrame KeyTime="0:0:2" KeySpline="0.4,0,0.6,1">
+          <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationEndPosition}" />
+        </KeyFrame>
+      </Animation>
+    </Style.Animations>
+  </Style>
+  <Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#IndeterminateProgressBarIndicator2">
+    <Style.Animations>
+      <Animation Duration="0:0:2" IterationCount="Infinite">
+        <KeyFrame KeyTime="0:0:0" KeySpline="0.4,0,0.6,1">
+          <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
+        </KeyFrame>
+        <KeyFrame KeyTime="0:0:0.75" KeySpline="0.4,0,0.6,1">
+          <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
+        </KeyFrame>
+        <KeyFrame KeyTime="0:0:2" KeySpline="0.4,0,0.6,1">
+          <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationEndPosition}" />
+        </KeyFrame>
+      </Animation>
+    </Style.Animations>
+  </Style>
+  <Style Selector="ProgressBar:vertical:indeterminate /template/ Border#IndeterminateProgressBarIndicator">
     <Style.Animations>
-      <Animation Duration="0:0:3"
-                 IterationCount="Infinite"
-                 Easing="LinearEasing">
-        <KeyFrame Cue="0%">
-          <Setter Property="TranslateTransform.X"
-                  Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
+      <Animation Duration="0:0:2" IterationCount="Infinite">
+        <KeyFrame KeyTime="0:0:0" KeySpline="0.4,0,0.6,1">
+          <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
+        </KeyFrame>
+        <KeyFrame KeyTime="0:0:1.5" KeySpline="0.4,0,0.6,1">
+          <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationEndPosition}" />
         </KeyFrame>
-        <KeyFrame Cue="100%">
-          <Setter Property="TranslateTransform.X"
-                  Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
+        <KeyFrame KeyTime="0:0:2" KeySpline="0.4,0,0.6,1">
+          <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationEndPosition}" />
         </KeyFrame>
       </Animation>
     </Style.Animations>
   </Style>
-  <Style Selector="ProgressBar:vertical:indeterminate /template/ Border#PART_Indicator">
+  <Style Selector="ProgressBar:vertical:indeterminate /template/ Border#IndeterminateProgressBarIndicator2">
     <Style.Animations>
-      <Animation Duration="0:0:3"
-                 IterationCount="Infinite"
-                 Easing="LinearEasing">
-        <KeyFrame Cue="0%">
-          <Setter Property="TranslateTransform.Y"
-                  Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
+      <Animation Duration="0:0:2" IterationCount="Infinite">
+        <KeyFrame KeyTime="0:0:0" KeySpline="0.4,0,0.6,1">
+          <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
         </KeyFrame>
-        <KeyFrame Cue="100%">
-          <Setter Property="TranslateTransform.Y"
-                  Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
+        <KeyFrame KeyTime="0:0:0.75" KeySpline="0.4,0,0.6,1">
+          <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
+        </KeyFrame>
+        <KeyFrame KeyTime="0:0:2" KeySpline="0.4,0,0.6,1">
+          <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationEndPosition}" />
         </KeyFrame>
       </Animation>
     </Style.Animations>
   </Style>
+  <Style Selector="ProgressBar:horizontal /template/ Border#IndeterminateProgressBarIndicator">
+    <Setter Property="Width" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerWidth}" />
+    <Setter Property="RenderTransform">
+      <Setter.Value>
+        <TranslateTransform X="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
+      </Setter.Value>
+    </Setter>
+  </Style>
+  <Style Selector="ProgressBar:horizontal /template/ Border#IndeterminateProgressBarIndicator2">
+    <Setter Property="Width" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2Width}" />
+    <Setter Property="RenderTransform">
+      <Setter.Value>
+        <TranslateTransform X="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
+      </Setter.Value>
+    </Setter>
+  </Style>
+  <Style Selector="ProgressBar:vertical /template/ Border#IndeterminateProgressBarIndicator">
+    <Setter Property="Height" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerWidth}" />
+    <Setter Property="RenderTransform">
+      <Setter.Value>
+        <TranslateTransform Y="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
+      </Setter.Value>
+    </Setter>
+  </Style>
+  <Style Selector="ProgressBar:vertical /template/ Border#IndeterminateProgressBarIndicator2">
+    <Setter Property="Height" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2Width}" />
+    <Setter Property="RenderTransform">
+      <Setter.Value>
+        <TranslateTransform Y="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
+      </Setter.Value>
+    </Setter>
+  </Style>
 </Styles>

+ 12 - 4
src/Avalonia.Themes.Fluent/ToolTip.xaml

@@ -1,11 +1,14 @@
-<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
+        xmlns:sys="clr-namespace:System;assembly=netstandard">
+
   <Design.PreviewWith>
     <Grid RowDefinitions="Auto,Auto"
           ColumnDefinitions="Auto,Auto"
           HorizontalAlignment="Center">
       <Border Grid.Column="0"
               Grid.Row="1"
-              Background="{DynamicResource ThemeAccentBrush}"
+              Background="{DynamicResource SystemControlBackgroundAccentBrush}"
               Margin="5"
               Padding="50"
               ToolTip.Tip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.">
@@ -19,7 +22,7 @@
       <Border Name="Border"
               Grid.Column="1"
               Grid.Row="1"
-              Background="{DynamicResource ThemeAccentBrush}"
+              Background="{DynamicResource SystemControlBackgroundAccentBrush}"
               Margin="5"
               Padding="50"
               ToolTip.Placement="Bottom">
@@ -34,6 +37,10 @@
     </Grid>
   </Design.PreviewWith>
 
+  <Styles.Resources>
+    <sys:Double x:Key="ToolTipContentMaxWidth">320</sys:Double>
+  </Styles.Resources>
+
   <Style Selector="ToolTip">
     <Setter Property="Foreground" Value="{DynamicResource ToolTipForeground}" />
     <Setter Property="Background" Value="{DynamicResource ToolTipBackground}" />
@@ -42,6 +49,7 @@
     <Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
     <Setter Property="FontSize" Value="{DynamicResource ToolTipContentThemeFontSize}" />
     <Setter Property="Padding" Value="{DynamicResource ToolTipBorderThemePadding}" />
+    <Setter Property="MaxWidth" Value="{DynamicResource ToolTipContentMaxWidth}" />
     <Setter Property="Transitions">
       <Transitions>
         <DoubleTransition Property="Opacity" Duration="0:0:0.15" />
@@ -56,7 +64,7 @@
                 Padding="{TemplateBinding Padding}"
                 CornerRadius="{DynamicResource OverlayCornerRadius}">
           <ContentPresenter Name="PART_ContentPresenter"
-                            MaxWidth="320"
+                            MaxWidth="{TemplateBinding MaxWidth}"
                             Content="{TemplateBinding Content}"
                             ContentTemplate="{TemplateBinding ContentTemplate}" />
         </Border>

+ 29 - 32
src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Reactive.Disposables;
-using Avalonia.Logging;
 using Avalonia.Media;
 using Avalonia.Media.Immutable;
 
@@ -11,9 +10,9 @@ namespace Avalonia.Animation.Animators
     /// </summary>
     public class SolidColorBrushAnimator : Animator<SolidColorBrush>
     {
-        ColorAnimator _colorAnimator;
+        private ColorAnimator _colorAnimator;
 
-        void InitializeColorAnimator()
+        private void InitializeColorAnimator()
         {
             _colorAnimator = new ColorAnimator();
 
@@ -27,46 +26,44 @@ namespace Avalonia.Animation.Animators
 
         public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> match, Action onComplete)
         {
+            // Preprocess keyframe values to Color if the xaml parser converts them to ISCB.
             foreach (var keyframe in this)
             {
-                if (keyframe.Value as ISolidColorBrush == null)
-                    return Disposable.Empty;
-
-                // Preprocess keyframe values to Color if the xaml parser converts them to ISCB.
-                if (keyframe.Value.GetType() == typeof(ImmutableSolidColorBrush))
+                if (keyframe.Value is ISolidColorBrush colorBrush)
                 {
-                    keyframe.Value = ((ImmutableSolidColorBrush)keyframe.Value).Color;
+                    keyframe.Value = colorBrush.Color;
+                }
+                else
+                {
+                    return Disposable.Empty;
                 }
             }
 
-            // Add SCB if the target prop is empty.
-            if (control.GetValue(Property) == null)
-                control.SetValue(Property, new SolidColorBrush(Colors.Transparent));
-
+            SolidColorBrush finalTarget;
             var targetVal = control.GetValue(Property);
-
-            // Continue if target prop is not empty & is a SolidColorBrush derivative. 
-            if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType()))
+            if (targetVal is null)
+            {
+                finalTarget = new SolidColorBrush(Colors.Transparent);
+                control.SetValue(Property, finalTarget);
+            }
+            else if (targetVal is ImmutableSolidColorBrush immutableSolidColorBrush)
+            {
+                finalTarget = new SolidColorBrush(immutableSolidColorBrush.Color);
+                control.SetValue(Property, finalTarget);
+            }
+            else if (targetVal is ISolidColorBrush)
             {
-                if (_colorAnimator == null)
-                    InitializeColorAnimator();
-
-                SolidColorBrush finalTarget;
-
-                // If it's ISCB, change it back to SCB.
-                if (targetVal.GetType() == typeof(ImmutableSolidColorBrush))
-                {
-                    var col = (ImmutableSolidColorBrush)targetVal;
-                    targetVal = new SolidColorBrush(col.Color);
-                    control.SetValue(Property, targetVal);
-                }
-
                 finalTarget = targetVal as SolidColorBrush;
-
-                return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete);
             }
+            else
+            {
+                return Disposable.Empty;
+            }
+
+            if (_colorAnimator == null)
+                InitializeColorAnimator();
 
-            return Disposable.Empty;
+            return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete);
         }
 
         public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null;

BIN
src/Avalonia.Visuals/Assets/GraphemeBreak.trie


BIN
src/Avalonia.Visuals/Assets/UnicodeData.trie


+ 1 - 1
src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs

@@ -2,6 +2,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
 {
     public enum BiDiClass
     {
+        LeftToRight, //L
         ArabicLetter, //AL
         ArabicNumber, //AN
         ParagraphSeparator, //B
@@ -11,7 +12,6 @@ namespace Avalonia.Media.TextFormatting.Unicode
         EuropeanSeparator, //ES
         EuropeanTerminator, //ET
         FirstStrongIsolate, //FSI
-        LeftToRight, //L
         LeftToRightEmbedding, //LRE
         LeftToRightIsolate, //LRI
         LeftToRightOverride, //LRO

+ 33 - 32
src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs

@@ -4,38 +4,39 @@ namespace Avalonia.Media.TextFormatting.Unicode
     {
         private static readonly byte[][] s_breakPairTable = 
             {
-             new byte[] {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4},
-             new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {4,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1},
-             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,0,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,1,0,1,1,0,0,4,2,4,1,1,1,1,1,0,1,1,1},
-             new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {0,4,4,1,0,1,4,4,4,0,0,1,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {0,4,4,1,0,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,4,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0},
-             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,1,1,1,1,0,0,0,0,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,1,0,0,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,1,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
-             new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,1,0,1,1,0,0,4,2,4,0,0,0,0,0,0,1,1,1},
+             new byte[] {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4,4},
+             new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {4,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
+             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,0,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,1,0,1,1,0,0,4,2,4,1,1,1,1,1,0,1,1,1,0},
+             new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {0,4,4,1,0,1,4,4,4,0,0,1,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {0,4,4,1,0,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,0},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,4,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0},
+             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,1,1,1,1,0,0,0,0,1,0},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,1,0,0,1,0},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,1,1,0},
+             new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+             new byte[] {0,4,4,1,1,0,4,4,4,0,0,0,0,0,0,0,0,0,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
         };
 
         public static PairBreakType Map(LineBreakClass first, LineBreakClass second)

+ 15 - 19
src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs

@@ -2,24 +2,20 @@ namespace Avalonia.Media.TextFormatting.Unicode
 {
     public enum GraphemeBreakClass
     {
-        Control, //CN
-        CR, //CR
-        EBase, //EB
-        EBaseGAZ, //EBG
-        EModifier, //EM
-        Extend, //EX
-        GlueAfterZwj, //GAZ
-        L, //L
-        LF, //LF
-        LV, //LV
-        LVT, //LVT
-        Prepend, //PP
-        RegionalIndicator, //RI
-        SpacingMark, //SM
-        T, //T
-        V, //V
-        Other, //XX
-        ZWJ, //ZWJ
-        ExtendedPictographic
+        Other,
+        CR,
+        LF,
+        Control,
+        Extend,
+        ZWJ,
+        RegionalIndicator,
+        Prepend,
+        SpacingMark,
+        L,
+        V,
+        T,
+        LV,
+        LVT,
+        ExtendedPictographic,
     }
 }

+ 2 - 2
src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs

@@ -34,10 +34,11 @@ namespace Avalonia.Media.TextFormatting.Unicode
         EBase, //EB
         EModifier, //EM
         ZWJ, //ZWJ
+        ContingentBreak, //CB
 
+        Unknown, //XX
         Ambiguous, //AI
         MandatoryBreak, //BK
-        ContingentBreak, //CB
         ConditionalJapaneseStarter, //CJ
         CarriageReturn, //CR
         LineFeed, //LF
@@ -45,6 +46,5 @@ namespace Avalonia.Media.TextFormatting.Unicode
         ComplexContext, //SA
         Surrogate, //SG
         Space, //SP
-        Unknown, //XX
     }
 }

+ 3 - 1
src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs

@@ -95,7 +95,8 @@ namespace Avalonia.Media.TextFormatting.Unicode
 
                     if (_nextClass.Value == LineBreakClass.MandatoryBreak)
                     {
-                        Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos);
+                        _lastPos = _pos;
+                        Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos, true);
                         return true;
                     }
 
@@ -108,6 +109,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
                 {
                     case PairBreakType.DI: // Direct break
                         shouldBreak = true;
+                        _lastPos = _pos;
                         break;
 
                     case PairBreakType.IN: // possible indirect break

+ 178 - 0
src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs

@@ -0,0 +1,178 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Media.TextFormatting.Unicode
+{
+    internal static class PropertyValueAliasHelper
+    {
+        private static readonly Dictionary<Script, string> s_scriptToTag = 
+            new Dictionary<Script, string>{
+                { Script.Unknown, "Zzzz"},
+                { Script.Common, "Zyyy"},
+                { Script.Inherited, "Zinh"},
+                { Script.Adlam, "Adlm"},
+                { Script.CaucasianAlbanian, "Aghb"},
+                { Script.Ahom, "Ahom"},
+                { Script.Arabic, "Arab"},
+                { Script.ImperialAramaic, "Armi"},
+                { Script.Armenian, "Armn"},
+                { Script.Avestan, "Avst"},
+                { Script.Balinese, "Bali"},
+                { Script.Bamum, "Bamu"},
+                { Script.BassaVah, "Bass"},
+                { Script.Batak, "Batk"},
+                { Script.Bengali, "Beng"},
+                { Script.Bhaiksuki, "Bhks"},
+                { Script.Bopomofo, "Bopo"},
+                { Script.Brahmi, "Brah"},
+                { Script.Braille, "Brai"},
+                { Script.Buginese, "Bugi"},
+                { Script.Buhid, "Buhd"},
+                { Script.Chakma, "Cakm"},
+                { Script.CanadianAboriginal, "Cans"},
+                { Script.Carian, "Cari"},
+                { Script.Cham, "Cham"},
+                { Script.Cherokee, "Cher"},
+                { Script.Chorasmian, "Chrs"},
+                { Script.Coptic, "Copt"},
+                { Script.Cypriot, "Cprt"},
+                { Script.Cyrillic, "Cyrl"},
+                { Script.Devanagari, "Deva"},
+                { Script.DivesAkuru, "Diak"},
+                { Script.Dogra, "Dogr"},
+                { Script.Deseret, "Dsrt"},
+                { Script.Duployan, "Dupl"},
+                { Script.EgyptianHieroglyphs, "Egyp"},
+                { Script.Elbasan, "Elba"},
+                { Script.Elymaic, "Elym"},
+                { Script.Ethiopic, "Ethi"},
+                { Script.Georgian, "Geor"},
+                { Script.Glagolitic, "Glag"},
+                { Script.GunjalaGondi, "Gong"},
+                { Script.MasaramGondi, "Gonm"},
+                { Script.Gothic, "Goth"},
+                { Script.Grantha, "Gran"},
+                { Script.Greek, "Grek"},
+                { Script.Gujarati, "Gujr"},
+                { Script.Gurmukhi, "Guru"},
+                { Script.Hangul, "Hang"},
+                { Script.Han, "Hani"},
+                { Script.Hanunoo, "Hano"},
+                { Script.Hatran, "Hatr"},
+                { Script.Hebrew, "Hebr"},
+                { Script.Hiragana, "Hira"},
+                { Script.AnatolianHieroglyphs, "Hluw"},
+                { Script.PahawhHmong, "Hmng"},
+                { Script.NyiakengPuachueHmong, "Hmnp"},
+                { Script.KatakanaOrHiragana, "Hrkt"},
+                { Script.OldHungarian, "Hung"},
+                { Script.OldItalic, "Ital"},
+                { Script.Javanese, "Java"},
+                { Script.KayahLi, "Kali"},
+                { Script.Katakana, "Kana"},
+                { Script.Kharoshthi, "Khar"},
+                { Script.Khmer, "Khmr"},
+                { Script.Khojki, "Khoj"},
+                { Script.KhitanSmallScript, "Kits"},
+                { Script.Kannada, "Knda"},
+                { Script.Kaithi, "Kthi"},
+                { Script.TaiTham, "Lana"},
+                { Script.Lao, "Laoo"},
+                { Script.Latin, "Latn"},
+                { Script.Lepcha, "Lepc"},
+                { Script.Limbu, "Limb"},
+                { Script.LinearA, "Lina"},
+                { Script.LinearB, "Linb"},
+                { Script.Lisu, "Lisu"},
+                { Script.Lycian, "Lyci"},
+                { Script.Lydian, "Lydi"},
+                { Script.Mahajani, "Mahj"},
+                { Script.Makasar, "Maka"},
+                { Script.Mandaic, "Mand"},
+                { Script.Manichaean, "Mani"},
+                { Script.Marchen, "Marc"},
+                { Script.Medefaidrin, "Medf"},
+                { Script.MendeKikakui, "Mend"},
+                { Script.MeroiticCursive, "Merc"},
+                { Script.MeroiticHieroglyphs, "Mero"},
+                { Script.Malayalam, "Mlym"},
+                { Script.Modi, "Modi"},
+                { Script.Mongolian, "Mong"},
+                { Script.Mro, "Mroo"},
+                { Script.MeeteiMayek, "Mtei"},
+                { Script.Multani, "Mult"},
+                { Script.Myanmar, "Mymr"},
+                { Script.Nandinagari, "Nand"},
+                { Script.OldNorthArabian, "Narb"},
+                { Script.Nabataean, "Nbat"},
+                { Script.Newa, "Newa"},
+                { Script.Nko, "Nkoo"},
+                { Script.Nushu, "Nshu"},
+                { Script.Ogham, "Ogam"},
+                { Script.OlChiki, "Olck"},
+                { Script.OldTurkic, "Orkh"},
+                { Script.Oriya, "Orya"},
+                { Script.Osage, "Osge"},
+                { Script.Osmanya, "Osma"},
+                { Script.Palmyrene, "Palm"},
+                { Script.PauCinHau, "Pauc"},
+                { Script.OldPermic, "Perm"},
+                { Script.PhagsPa, "Phag"},
+                { Script.InscriptionalPahlavi, "Phli"},
+                { Script.PsalterPahlavi, "Phlp"},
+                { Script.Phoenician, "Phnx"},
+                { Script.Miao, "Plrd"},
+                { Script.InscriptionalParthian, "Prti"},
+                { Script.Rejang, "Rjng"},
+                { Script.HanifiRohingya, "Rohg"},
+                { Script.Runic, "Runr"},
+                { Script.Samaritan, "Samr"},
+                { Script.OldSouthArabian, "Sarb"},
+                { Script.Saurashtra, "Saur"},
+                { Script.SignWriting, "Sgnw"},
+                { Script.Shavian, "Shaw"},
+                { Script.Sharada, "Shrd"},
+                { Script.Siddham, "Sidd"},
+                { Script.Khudawadi, "Sind"},
+                { Script.Sinhala, "Sinh"},
+                { Script.Sogdian, "Sogd"},
+                { Script.OldSogdian, "Sogo"},
+                { Script.SoraSompeng, "Sora"},
+                { Script.Soyombo, "Soyo"},
+                { Script.Sundanese, "Sund"},
+                { Script.SylotiNagri, "Sylo"},
+                { Script.Syriac, "Syrc"},
+                { Script.Tagbanwa, "Tagb"},
+                { Script.Takri, "Takr"},
+                { Script.TaiLe, "Tale"},
+                { Script.NewTaiLue, "Talu"},
+                { Script.Tamil, "Taml"},
+                { Script.Tangut, "Tang"},
+                { Script.TaiViet, "Tavt"},
+                { Script.Telugu, "Telu"},
+                { Script.Tifinagh, "Tfng"},
+                { Script.Tagalog, "Tglg"},
+                { Script.Thaana, "Thaa"},
+                { Script.Thai, "Thai"},
+                { Script.Tibetan, "Tibt"},
+                { Script.Tirhuta, "Tirh"},
+                { Script.Ugaritic, "Ugar"},
+                { Script.Vai, "Vaii"},
+                { Script.WarangCiti, "Wara"},
+                { Script.Wancho, "Wcho"},
+                { Script.OldPersian, "Xpeo"},
+                { Script.Cuneiform, "Xsux"},
+                { Script.Yezidi, "Yezi"},
+                { Script.Yi, "Yiii"},
+                { Script.ZanabazarSquare, "Zanb"},
+        };
+
+        public static string GetTag(Script script)
+        {
+            if(!s_scriptToTag.ContainsKey(script))
+            {
+                return "Zzzz";
+            }
+            return s_scriptToTag[script];
+        }
+    }
+}

+ 7 - 3
src/Avalonia.Visuals/Media/TextFormatting/Unicode/Script.cs

@@ -2,6 +2,9 @@ namespace Avalonia.Media.TextFormatting.Unicode
 {
     public enum Script
     {
+        Unknown, //Zzzz
+        Common, //Zyyy
+        Inherited, //Zinh
         Adlam, //Adlm
         CaucasianAlbanian, //Aghb
         Ahom, //Ahom
@@ -25,10 +28,12 @@ namespace Avalonia.Media.TextFormatting.Unicode
         Carian, //Cari
         Cham, //Cham
         Cherokee, //Cher
+        Chorasmian, //Chrs
         Coptic, //Copt
         Cypriot, //Cprt
         Cyrillic, //Cyrl
         Devanagari, //Deva
+        DivesAkuru, //Diak
         Dogra, //Dogr
         Deseret, //Dsrt
         Duployan, //Dupl
@@ -63,6 +68,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
         Kharoshthi, //Khar
         Khmer, //Khmr
         Khojki, //Khoj
+        KhitanSmallScript, //Kits
         Kannada, //Knda
         Kaithi, //Kthi
         TaiTham, //Lana
@@ -151,10 +157,8 @@ namespace Avalonia.Media.TextFormatting.Unicode
         Wancho, //Wcho
         OldPersian, //Xpeo
         Cuneiform, //Xsux
+        Yezidi, //Yezi
         Yi, //Yiii
         ZanabazarSquare, //Zanb
-        Inherited, //Zinh
-        Common, //Zyyy
-        Unknown, //Zzzz
     }
 }

+ 15 - 0
src/Avalonia.Visuals/Rect.cs

@@ -211,6 +211,21 @@ namespace Avalonia
                 rect.Width * scale.X,
                 rect.Height * scale.Y);
         }
+        
+        /// <summary>
+        /// Multiplies a rectangle by a scale.
+        /// </summary>
+        /// <param name="rect">The rectangle.</param>
+        /// <param name="scale">The scale.</param>
+        /// <returns>The scaled rectangle.</returns>
+        public static Rect operator *(Rect rect, double scale)
+        {
+            return new Rect(
+                rect.X * scale,
+                rect.Y * scale,
+                rect.Width * scale,
+                rect.Height * scale);
+        }
 
         /// <summary>
         /// Divides a rectangle by a vector.

+ 15 - 7
src/Avalonia.X11/X11NativeControlHost.cs

@@ -157,21 +157,30 @@ namespace Avalonia.X11
 
             public bool IsCompatibleWith(INativeControlHostImpl host) => host is X11NativeControlHost;
 
-            public void Hide()
+            public void HideWithSize(Size size)
             {
                 if(_attachedTo == null || _child == null)
                     return;
-                _mapped = false;
-                XUnmapWindow(_display, _holder.Handle);
+                if (_mapped)
+                {
+                    _mapped = false;
+                    XUnmapWindow(_display, _holder.Handle);
+                }
+
+                size *= _attachedTo.Window.Scaling;
+                XResizeWindow(_display, _child.Handle,
+                    Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height));
             }
             
-            public void ShowInBounds(TransformedBounds transformedBounds)
+            
+            
+            public void ShowInBounds(Rect bounds)
             {
                 CheckDisposed();
                 if (_attachedTo == null)
                     throw new InvalidOperationException("The control isn't currently attached to a toplevel");
-                var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) *
-                             new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
+                bounds *= _attachedTo.Window.Scaling;
+                
                 var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
                     Math.Max(1, (int)bounds.Height));
                 XMoveResizeWindow(_display, _child.Handle, 0, 0, pixelRect.Width, pixelRect.Height);
@@ -183,7 +192,6 @@ namespace Avalonia.X11
                     XRaiseWindow(_display, _holder.Handle);
                     _mapped = true;
                 }
-                Console.WriteLine($"Moved {_child.Handle} to {pixelRect}");
             }
         }
     }

+ 8 - 4
src/Windows/Avalonia.Win32/Win32NativeControlHost.cs

@@ -168,21 +168,25 @@ namespace Avalonia.Win32
 
             public bool IsCompatibleWith(INativeControlHostImpl host) => host is Win32NativeControlHost;
 
-            public void Hide()
+            public void HideWithSize(Size size)
             {
                 UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero,
                     -100, -100, 1, 1,
                     UnmanagedMethods.SetWindowPosFlags.SWP_HIDEWINDOW |
                     UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE);
+                if (_attachedTo == null || _child == null)
+                    return;
+                size *= _attachedTo.Window.Scaling;
+                UnmanagedMethods.MoveWindow(_child.Handle, 0, 0,
+                    Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height), false);
             }
             
-            public unsafe void ShowInBounds(TransformedBounds transformedBounds)
+            public unsafe void ShowInBounds(Rect bounds)
             {
                 CheckDisposed();
                 if (_attachedTo == null)
                     throw new InvalidOperationException("The control isn't currently attached to a toplevel");
-                var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) *
-                             new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
+                bounds *= _attachedTo.Window.Scaling;
                 var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
                     Math.Max(1, (int)bounds.Height));
                 

+ 16 - 3
src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs

@@ -5,12 +5,13 @@ using System.Linq;
 using System.Reactive.Disposables;
 using System.Reactive.Linq;
 using Avalonia.Controls.Platform;
+using Avalonia.Logging;
 
 namespace Avalonia.Win32
 {
     internal class WindowsMountedVolumeInfoListener : IDisposable
     {
-        private readonly CompositeDisposable _disposables;        
+        private readonly CompositeDisposable _disposables;
         private bool _beenDisposed = false;
         private ObservableCollection<MountedVolumeInfo> mountedDrives;
 
@@ -32,10 +33,22 @@ namespace Avalonia.Win32
             var allDrives = DriveInfo.GetDrives();
 
             var mountVolInfos = allDrives
-                                .Where(p => p.IsReady)
+                                .Where(p =>
+                                {
+                                    try
+                                    {
+                                        var ret = p.IsReady;
+                                        return ret;
+                                    }
+                                    catch (Exception e)
+                                    {
+                                        Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(this, $"Error in Windows drive enumeration: {e.Message}");
+                                    }
+                                    return false;
+                                })
                                 .Select(p => new MountedVolumeInfo()
                                 {
-                                    VolumeLabel = string.IsNullOrEmpty(p.VolumeLabel.Trim()) ? p.RootDirectory.FullName 
+                                    VolumeLabel = string.IsNullOrEmpty(p.VolumeLabel.Trim()) ? p.RootDirectory.FullName
                                                                                              : $"{p.VolumeLabel} ({p.Name})",
                                     VolumePath = p.RootDirectory.FullName,
                                     VolumeSizeBytes = (ulong)p.TotalSize

+ 1 - 1
tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs

@@ -24,7 +24,7 @@ namespace Avalonia.Benchmarks.Layout
                 Renderer = new NullRenderer()
             };
 
-            _root.LayoutManager.ExecuteInitialLayoutPass(_root);
+            _root.LayoutManager.ExecuteInitialLayoutPass();
         }
 
         [Benchmark]

+ 1 - 1
tests/Avalonia.Benchmarks/Layout/Measure.cs

@@ -25,7 +25,7 @@ namespace Avalonia.Benchmarks.Layout
             _controls.Add(panel);
             _controls = ControlHierarchyCreator.CreateChildren(_controls, panel, 3, 5, 5);
 
-            _root.LayoutManager.ExecuteInitialLayoutPass(_root);
+            _root.LayoutManager.ExecuteInitialLayoutPass();
         }
 
         [Benchmark, MethodImpl(MethodImplOptions.NoInlining)]

+ 1 - 1
tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs

@@ -26,7 +26,7 @@ namespace Avalonia.Benchmarks.Traversal
 
             _shuffledControls = _controls.OrderBy(r => random.Next()).ToList();
 
-            _root.LayoutManager.ExecuteInitialLayoutPass(_root);
+            _root.LayoutManager.ExecuteInitialLayoutPass();
         }
 
         [Benchmark]

+ 1 - 1
tests/Avalonia.Controls.UnitTests/GridTests.cs

@@ -1194,7 +1194,7 @@ namespace Avalonia.Controls.UnitTests
                 Height = 50,
             };
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
 
             PrintColumnDefinitions(grids[0]);
             Assert.Equal(5, grids[0].ColumnDefinitions[0].ActualWidth);

+ 1 - 1
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@@ -329,7 +329,7 @@ namespace Avalonia.Controls.UnitTests
                     return tb;
                 }, true);
 
-                lm.ExecuteInitialLayoutPass(wnd);
+                lm.ExecuteInitialLayoutPass();
 
                 target.Items = items;
 

+ 7 - 2
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs

@@ -232,7 +232,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
                 var scroll = (TestScroller)target.Parent;
 
                 scroll.Width = scroll.Height = 100;
-                scroll.LayoutManager.ExecuteInitialLayoutPass(scroll);
+                scroll.LayoutManager.ExecuteInitialLayoutPass();
 
                 // Ensure than an intermediate measure pass doesn't add more controls than it
                 // should. This can happen if target gets measured with Size.Infinity which
@@ -324,6 +324,11 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
         private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot
         {
+            public TestScroller()
+            {
+                LayoutManager = new LayoutManager(this);
+            }
+
             public IRenderer Renderer { get; }
             public Size ClientSize { get; }
             public double RenderScaling => 1;
@@ -332,7 +337,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
             public double LayoutScaling => 1;
 
-            public ILayoutManager LayoutManager { get; } = new LayoutManager();
+            public ILayoutManager LayoutManager { get; }
 
             public IRenderTarget CreateRenderTarget() => throw new NotImplementedException();
             public void Invalidate(Rect rect) => throw new NotImplementedException();

+ 16 - 11
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs

@@ -723,7 +723,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
             var scroller = (TestScroller)target.Parent;
 
             scroller.Width = scroller.Height = 100;
-            scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
+            scroller.LayoutManager.ExecuteInitialLayoutPass();
 
             var last = (target.Items as IList)[10];
 
@@ -740,7 +740,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
             var scroller = (TestScroller)target.Parent;
 
             scroller.Width = scroller.Height = 100;
-            scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
+            scroller.LayoutManager.ExecuteInitialLayoutPass();
 
             var last = (target.Items as IList)[10];
 
@@ -838,7 +838,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
                 var scroller = (TestScroller)target.Parent;
 
                 scroller.Width = scroller.Height = 100;
-                scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
+                scroller.LayoutManager.ExecuteInitialLayoutPass();
 
                 var from = target.Panel.Children[5];
                 var result = ((ILogicalScrollable)target).GetControlInDirection(
@@ -855,7 +855,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
                 var scroller = (TestScroller)target.Parent;
 
                 scroller.Width = scroller.Height = 100;
-                scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
+                scroller.LayoutManager.ExecuteInitialLayoutPass();
 
                 var from = target.Panel.Children[9];
                 var result = ((ILogicalScrollable)target).GetControlInDirection(
@@ -874,7 +874,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
                 scroller.Width = 100;
                 scroller.Height = 95;
-                scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
+                scroller.LayoutManager.ExecuteInitialLayoutPass();
 
                 var from = target.Panel.Children[8];
                 var result = ((ILogicalScrollable)target).GetControlInDirection(
@@ -893,7 +893,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
                 scroller.Width = 100;
                 scroller.Height = 95;
-                scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
+                scroller.LayoutManager.ExecuteInitialLayoutPass();
                 ((ILogicalScrollable)target).Offset = new Vector(0, 11);
 
                 var from = target.Panel.Children[1];
@@ -946,7 +946,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
                 var scroller = (TestScroller)target.Parent;
 
                 scroller.Width = scroller.Height = 100;
-                scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
+                scroller.LayoutManager.ExecuteInitialLayoutPass();
 
                 var from = target.Panel.Children[5];
                 var result = ((ILogicalScrollable)target).GetControlInDirection(
@@ -963,7 +963,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
                 var scroller = (TestScroller)target.Parent;
 
                 scroller.Width = scroller.Height = 100;
-                scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
+                scroller.LayoutManager.ExecuteInitialLayoutPass();
 
                 var from = target.Panel.Children[9];
                 var result = ((ILogicalScrollable)target).GetControlInDirection(
@@ -982,7 +982,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
                 scroller.Width = 95;
                 scroller.Height = 100;
-                scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
+                scroller.LayoutManager.ExecuteInitialLayoutPass();
 
                 var from = target.Panel.Children[8];
                 var result = ((ILogicalScrollable)target).GetControlInDirection(
@@ -1001,7 +1001,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
                 scroller.Width = 95;
                 scroller.Height = 100;
-                scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
+                scroller.LayoutManager.ExecuteInitialLayoutPass();
                 ((ILogicalScrollable)target).Offset = new Vector(11, 0);
 
                 var from = target.Panel.Children[1];
@@ -1062,6 +1062,11 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
         private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot, ILogicalRoot
         {
+            public TestScroller()
+            {
+                LayoutManager = new LayoutManager(this);
+            }
+
             public IRenderer Renderer { get; }
             public Size ClientSize { get; }
             public double RenderScaling => 1;
@@ -1070,7 +1075,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
             public double LayoutScaling => 1;
 
-            public ILayoutManager LayoutManager { get; } = new LayoutManager();
+            public ILayoutManager LayoutManager { get; }
 
             public IRenderTarget CreateRenderTarget() => throw new NotImplementedException();
             public void Invalidate(Rect rect) => throw new NotImplementedException();

+ 3 - 3
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@@ -158,7 +158,7 @@ namespace Avalonia.Controls.UnitTests
             target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
             target.Offset = new Vector(10, 10);
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
 
             target.ScrollChanged += (s, e) =>
             {
@@ -188,7 +188,7 @@ namespace Avalonia.Controls.UnitTests
             target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
             target.Offset = new Vector(10, 10);
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
 
             target.ScrollChanged += (s, e) =>
             {
@@ -218,7 +218,7 @@ namespace Avalonia.Controls.UnitTests
             target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
             target.Offset = new Vector(10, 10);
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
 
             target.ScrollChanged += (s, e) =>
             {

+ 21 - 4
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@@ -106,7 +106,7 @@ namespace Avalonia.Controls.UnitTests
                     }
                 };
 
-                target.LayoutManager.ExecuteInitialLayoutPass(target);
+                target.LayoutManager.ExecuteInitialLayoutPass();
 
                 Assert.Equal(new Rect(0, 0, 321, 432), target.Bounds);
             }
@@ -267,6 +267,23 @@ namespace Avalonia.Controls.UnitTests
             }
         }
 
+        [Fact]
+        public void Close_Should_Dispose_LayoutManager()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var impl = new Mock<ITopLevelImpl>();
+                impl.SetupAllProperties();
+
+                var layoutManager = new Mock<ILayoutManager>();
+                var target = new TestTopLevel(impl.Object, layoutManager.Object);
+
+                impl.Object.Closed();
+
+                layoutManager.Verify(x => x.Dispose());
+            }
+        }
+
         [Fact]
         public void Reacts_To_Changes_In_Global_Styles()
         {
@@ -282,7 +299,7 @@ namespace Avalonia.Controls.UnitTests
                     Content = child,
                 };
 
-                target.LayoutManager.ExecuteInitialLayoutPass(target);
+                target.LayoutManager.ExecuteInitialLayoutPass();
 
                 Assert.Equal(new Thickness(0), child.BorderThickness);
 
@@ -295,7 +312,7 @@ namespace Avalonia.Controls.UnitTests
                 };
 
                 Application.Current.Styles.Add(style);
-                target.LayoutManager.ExecuteInitialLayoutPass(target);
+                target.LayoutManager.ExecuteInitialLayoutPass();
 
                 Assert.Equal(new Thickness(2), child.BorderThickness);
 
@@ -323,7 +340,7 @@ namespace Avalonia.Controls.UnitTests
             public TestTopLevel(ITopLevelImpl impl, ILayoutManager layoutManager = null)
                 : base(impl)
             {
-                _layoutManager = layoutManager ?? new LayoutManager();
+                _layoutManager = layoutManager ?? new LayoutManager(this);
             }
 
             protected override ILayoutManager CreateLayoutManager() => _layoutManager;

+ 32 - 16
tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs

@@ -13,7 +13,7 @@ namespace Avalonia.Layout.UnitTests
             var control = new LayoutTestControl();
             var root = new LayoutTestRoot { Child = control };
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
             control.Measured = control.Arranged = false;
 
             control.InvalidateMeasure();
@@ -23,13 +23,29 @@ namespace Avalonia.Layout.UnitTests
             Assert.True(control.Arranged);
         }
 
+        [Fact]
+        public void Doesnt_Measure_And_Arrange_InvalidateMeasured_Control_When_TopLevel_Is_Not_Visible()
+        {
+            var control = new LayoutTestControl();
+            var root = new LayoutTestRoot { Child = control, IsVisible = false };
+
+            root.LayoutManager.ExecuteInitialLayoutPass();
+            control.Measured = control.Arranged = false;
+
+            control.InvalidateMeasure();
+            root.LayoutManager.ExecuteLayoutPass();
+
+            Assert.False(control.Measured);
+            Assert.False(control.Arranged);
+        }
+
         [Fact]
         public void Arranges_InvalidateArranged_Control()
         {
             var control = new LayoutTestControl();
             var root = new LayoutTestRoot { Child = control };
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
             control.Measured = control.Arranged = false;
 
             control.InvalidateArrange();
@@ -45,7 +61,7 @@ namespace Avalonia.Layout.UnitTests
             var control = new LayoutTestControl();
             var root = new LayoutTestRoot();
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
             root.Child = control;
             root.Measured = root.Arranged = false;
 
@@ -80,7 +96,7 @@ namespace Avalonia.Layout.UnitTests
             root.DoMeasureOverride = MeasureOverride;
             control1.DoMeasureOverride = MeasureOverride;
             control2.DoMeasureOverride = MeasureOverride;
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
 
             control2.InvalidateMeasure();
             control1.InvalidateMeasure();
@@ -115,7 +131,7 @@ namespace Avalonia.Layout.UnitTests
             root.DoMeasureOverride = MeasureOverride;
             control1.DoMeasureOverride = MeasureOverride;
             control2.DoMeasureOverride = MeasureOverride;
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
 
             control2.InvalidateMeasure();
             root.InvalidateMeasure();
@@ -132,7 +148,7 @@ namespace Avalonia.Layout.UnitTests
             var control = new LayoutTestControl();
             var root = new LayoutTestRoot { Child = control };
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
             root.Measured = root.Arranged = false;
             control.Measured = control.Arranged = false;
 
@@ -151,7 +167,7 @@ namespace Avalonia.Layout.UnitTests
             var control = new LayoutTestControl();
             var root = new LayoutTestRoot { Child = control };
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
             control.Measured = control.Arranged = false;
 
             control.InvalidateMeasure();
@@ -177,7 +193,7 @@ namespace Avalonia.Layout.UnitTests
                 return new Size(100, 100);
             };
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
 
             Assert.Equal(Size.Infinity, availableSize);
         }
@@ -199,7 +215,7 @@ namespace Avalonia.Layout.UnitTests
                 return s;
             };
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
             Assert.Equal(new Size(100, 100), arrangeSize);
 
             root.Width = 120;
@@ -225,7 +241,7 @@ namespace Avalonia.Layout.UnitTests
                 }
             };
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
             Assert.Equal(new Size(0, 0), root.DesiredSize);
 
             border.Width = 100;
@@ -241,7 +257,7 @@ namespace Avalonia.Layout.UnitTests
             var control = new LayoutTestControl();
             var root = new LayoutTestRoot { Child = control };
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
             control.Measured = false;
 
             int cnt = 0;
@@ -272,7 +288,7 @@ namespace Avalonia.Layout.UnitTests
             var control = new LayoutTestControl();
             var root = new LayoutTestRoot { Child = control };
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
             control.Arranged = false;
 
             int cnt = 0;
@@ -313,7 +329,7 @@ namespace Avalonia.Layout.UnitTests
             panel.Children.AddRange(nonArrageableTargets);
             panel.Children.AddRange(targets);
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
 
             foreach (var c in panel.Children.OfType<LayoutTestControl>())
             {
@@ -347,7 +363,7 @@ namespace Avalonia.Layout.UnitTests
             var control = new LayoutTestControl();
             var root = new LayoutTestRoot { Child = control };
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
             control.Measured = false;
 
             control.DoMeasureOverride = (l, s) =>
@@ -380,7 +396,7 @@ namespace Avalonia.Layout.UnitTests
             var root = new LayoutTestRoot { Child = control };
             var count = 0;
 
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
             control.Measured = false;
 
             control.DoMeasureOverride = (l, s) =>
@@ -399,7 +415,7 @@ namespace Avalonia.Layout.UnitTests
 
             root.InvalidateMeasure();
             control.InvalidateMeasure();
-            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            root.LayoutManager.ExecuteInitialLayoutPass();
 
             Assert.Equal(new Size(200, 200), control.Bounds.Size);
             Assert.Equal(new Size(200, 200), control.DesiredSize);

+ 1 - 3
tests/Avalonia.Layout.UnitTests/LayoutableTests.cs

@@ -208,14 +208,12 @@ namespace Avalonia.Layout.UnitTests
         {
             Border border1;
             Border border2;
-            var layoutManager = new LayoutManager();
             var root = new TestRoot
             {
                 Child = border1 = new Border
                 {
                     Child = border2 = new Border(),
                 },
-                LayoutManager = layoutManager,
             };
             var raised = 0;
 
@@ -233,7 +231,7 @@ namespace Avalonia.Layout.UnitTests
             root.Measure(new Size(100, 100));
             root.Arrange(new Rect(0, 0, 100, 100));
             
-            layoutManager.ExecuteLayoutPass();
+            root.LayoutManager.ExecuteLayoutPass();
 
             Assert.Equal(3, raised);
             Assert.Equal(new Rect(0, 0, 100, 100), border1.Bounds);

+ 424 - 0
tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs

@@ -0,0 +1,424 @@
+using System;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Media;
+using Avalonia.UnitTests;
+using Xunit;
+
+namespace Avalonia.Layout.UnitTests
+{
+    public class LayoutableTests_EffectiveViewportChanged
+    {
+        [Fact]
+        public async Task EffectiveViewportChanged_Not_Raised_When_Control_Added_To_Tree()
+        {
+            await RunOnUIThread.Execute(async () =>
+            {
+                var root = CreateRoot();
+                var target = new Canvas();
+                var raised = 0;
+
+                target.EffectiveViewportChanged += (s, e) =>
+                {
+                    ++raised;
+                };
+
+                root.Child = target;
+
+                Assert.Equal(0, raised);
+            });
+        }
+
+        [Fact]
+        public async Task EffectiveViewportChanged_Raised_Before_LayoutUpdated()
+        {
+            await RunOnUIThread.Execute(async () =>
+            {
+                var root = CreateRoot();
+                var target = new Canvas();
+                var raised = 0;
+
+                target.EffectiveViewportChanged += (s, e) =>
+                {
+                    ++raised;
+                };
+
+                root.Child = target;
+
+                await ExecuteInitialLayoutPass(root);
+
+                Assert.Equal(1, raised);
+            });
+        }
+
+        [Fact]
+        public async Task Parent_Affects_EffectiveViewport()
+        {
+            await RunOnUIThread.Execute(async () =>
+            {
+                var root = CreateRoot();
+                var target = new Canvas { Width = 100, Height = 100 };
+                var parent = new Border { Width = 200, Height = 200, Child = target };
+                var raised = 0;
+
+                root.Child = parent;
+
+                target.EffectiveViewportChanged += (s, e) =>
+                {
+                    Assert.Equal(new Rect(-550, -400, 1200, 900), e.EffectiveViewport);
+                    ++raised;
+                };
+
+                await ExecuteInitialLayoutPass(root);
+            });
+        }
+
+        [Fact]
+        public async Task Invalidating_In_Handler_Causes_Layout_To_Be_Rerun_Before_LayoutUpdated_Raised()
+        {
+            await RunOnUIThread.Execute(async () =>
+            {
+                var root = CreateRoot();
+                var target = new TestCanvas();
+                var raised = 0;
+                var layoutUpdatedRaised = 0;
+
+                root.LayoutUpdated += (s, e) =>
+                {
+                    Assert.Equal(2, target.MeasureCount);
+                    Assert.Equal(2, target.ArrangeCount);
+                    ++layoutUpdatedRaised;
+                };
+
+                target.EffectiveViewportChanged += (s, e) =>
+                {
+                    target.InvalidateMeasure();
+                    ++raised;
+                };
+
+                root.Child = target;
+
+                await ExecuteInitialLayoutPass(root);
+
+                Assert.Equal(1, raised);
+                Assert.Equal(1, layoutUpdatedRaised);
+            });
+        }
+
+        [Fact]
+        public async Task Viewport_Extends_Beyond_Centered_Control()
+        {
+            await RunOnUIThread.Execute(async () =>
+            {
+                var root = CreateRoot();
+                var target = new Canvas { Width = 52, Height = 52, };
+                var raised = 0;
+
+                target.EffectiveViewportChanged += (s, e) =>
+                {
+                    Assert.Equal(new Rect(-574, -424, 1200, 900), e.EffectiveViewport);
+                    ++raised;
+                };
+
+                root.Child = target;
+
+                await ExecuteInitialLayoutPass(root);
+                Assert.Equal(1, raised);
+            });
+        }
+
+        [Fact]
+        public async Task Viewport_Extends_Beyond_Nested_Centered_Control()
+        {
+            await RunOnUIThread.Execute(async () =>
+            {
+                var root = CreateRoot();
+                var target = new Canvas { Width = 52, Height = 52 };
+                var parent = new Border { Width = 100, Height = 100, Child = target };
+                var raised = 0;
+
+                target.EffectiveViewportChanged += (s, e) =>
+                {
+                    Assert.Equal(new Rect(-574, -424, 1200, 900), e.EffectiveViewport);
+                    ++raised;
+                };
+
+                root.Child = parent;
+
+                await ExecuteInitialLayoutPass(root);
+                Assert.Equal(1, raised);
+            });
+        }
+
+        [Fact]
+        public async Task ScrollViewer_Determines_EffectiveViewport()
+        {
+            await RunOnUIThread.Execute(async () =>
+            {
+                var root = CreateRoot();
+                var target = new Canvas { Width = 200, Height = 200 };
+                var scroller = new ScrollViewer { Width = 100, Height = 100, Content = target, Template = ScrollViewerTemplate() };
+                var raised = 0;
+
+                target.EffectiveViewportChanged += (s, e) =>
+                {
+                    Assert.Equal(new Rect(0, 0, 100, 100), e.EffectiveViewport);
+                    ++raised;
+                };
+
+                root.Child = scroller;
+
+                await ExecuteInitialLayoutPass(root);
+                Assert.Equal(1, raised);
+            });
+        }
+
+        [Fact]
+        public async Task Scrolled_ScrollViewer_Determines_EffectiveViewport()
+        {
+            await RunOnUIThread.Execute(async () =>
+            {
+                var root = CreateRoot();
+                var target = new Canvas { Width = 200, Height = 200 };
+                var scroller = new ScrollViewer { Width = 100, Height = 100, Content = target, Template = ScrollViewerTemplate() };
+                var raised = 0;
+
+                root.Child = scroller;
+
+                await ExecuteInitialLayoutPass(root);
+                scroller.Offset = new Vector(0, 10);
+
+                await ExecuteScrollerLayoutPass(root, scroller, target, (s, e) =>
+                {
+                    Assert.Equal(new Rect(0, 10, 100, 100), e.EffectiveViewport);
+                    ++raised;
+                });
+
+                Assert.Equal(1, raised);
+            });
+        }
+
+        [Fact]
+        public async Task Moving_Parent_Updates_EffectiveViewport()
+        {
+            await RunOnUIThread.Execute(async () =>
+            {
+                var root = CreateRoot();
+                var target = new Canvas { Width = 100, Height = 100 };
+                var parent = new Border { Width = 200, Height = 200, Child = target };
+                var raised = 0;
+
+                root.Child = parent;
+
+                await ExecuteInitialLayoutPass(root);
+
+                target.EffectiveViewportChanged += (s, e) =>
+                {
+                    Assert.Equal(new Rect(-554, -400, 1200, 900), e.EffectiveViewport);
+                    ++raised;
+                };
+
+                parent.Margin = new Thickness(8, 0, 0, 0);
+                await ExecuteLayoutPass(root);
+
+                Assert.Equal(1, raised);
+            });
+        }
+
+        [Fact]
+        public async Task Translate_Transform_Doesnt_Affect_EffectiveViewport()
+        {
+            await RunOnUIThread.Execute(async () =>
+            {
+                var root = CreateRoot();
+                var target = new Canvas { Width = 100, Height = 100 };
+                var parent = new Border { Width = 200, Height = 200, Child = target };
+                var raised = 0;
+
+                root.Child = parent;
+
+                await ExecuteInitialLayoutPass(root);
+                target.EffectiveViewportChanged += (s, e) => ++raised;
+                target.RenderTransform = new TranslateTransform { X = 8 };
+                target.InvalidateMeasure();
+                await ExecuteLayoutPass(root);
+
+                Assert.Equal(0, raised);
+            });
+        }
+
+        [Fact]
+        public async Task Translate_Transform_On_Parent_Affects_EffectiveViewport()
+        {
+            await RunOnUIThread.Execute(async () =>
+            {
+                var root = CreateRoot();
+                var target = new Canvas { Width = 100, Height = 100 };
+                var parent = new Border { Width = 200, Height = 200, Child = target };
+                var raised = 0;
+
+                root.Child = parent;
+
+                await ExecuteInitialLayoutPass(root);
+
+                target.EffectiveViewportChanged += (s, e) =>
+                {
+                    Assert.Equal(new Rect(-558, -400, 1200, 900), e.EffectiveViewport);
+                    ++raised;
+                };
+
+                // Change the parent render transform to move it. A layout is then needed before
+                // EffectiveViewportChanged is raised.
+                parent.RenderTransform = new TranslateTransform { X = 8 };
+                parent.InvalidateMeasure();
+                await ExecuteLayoutPass(root);
+
+                Assert.Equal(1, raised);
+            });
+        }
+
+        [Fact]
+        public async Task Rotate_Transform_On_Parent_Affects_EffectiveViewport()
+        {
+            await RunOnUIThread.Execute(async () =>
+            {
+                var root = CreateRoot();
+                var target = new Canvas { Width = 100, Height = 100 };
+                var parent = new Border { Width = 200, Height = 200, Child = target };
+                var raised = 0;
+
+                root.Child = parent;
+
+                await ExecuteInitialLayoutPass(root);
+
+                target.EffectiveViewportChanged += (s, e) =>
+                {
+                    AssertArePixelEqual(new Rect(-651, -792, 1484, 1484), e.EffectiveViewport);
+                    ++raised;
+                };
+
+                parent.RenderTransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute);
+                parent.RenderTransform = new RotateTransform { Angle = 45 };
+                parent.InvalidateMeasure();
+                await ExecuteLayoutPass(root);
+
+                Assert.Equal(1, raised);
+            });
+        }
+
+        private TestRoot CreateRoot() => new TestRoot { Width = 1200, Height = 900 };
+
+        private Task ExecuteInitialLayoutPass(TestRoot root)
+        {
+            root.LayoutManager.ExecuteInitialLayoutPass();
+            return Task.CompletedTask;
+        }
+
+        private Task ExecuteLayoutPass(TestRoot root)
+        {
+            root.LayoutManager.ExecuteLayoutPass();
+            return Task.CompletedTask;
+        }
+
+        private Task ExecuteScrollerLayoutPass(
+            TestRoot root,
+            ScrollViewer scroller,
+            Control target,
+            Action<object, EffectiveViewportChangedEventArgs> handler)
+        {
+            void ViewportChanged(object sender, EffectiveViewportChangedEventArgs e)
+            {
+                handler(sender, e);
+            }
+
+            target.EffectiveViewportChanged += ViewportChanged;
+            root.LayoutManager.ExecuteLayoutPass();
+            return Task.CompletedTask;
+        }
+        private IControlTemplate ScrollViewerTemplate()
+        {
+            return new FuncControlTemplate<ScrollViewer>((control, scope) => new Grid
+            {
+                ColumnDefinitions = new ColumnDefinitions
+                {
+                    new ColumnDefinition(1, GridUnitType.Star),
+                    new ColumnDefinition(GridLength.Auto),
+                },
+                RowDefinitions = new RowDefinitions
+                {
+                    new RowDefinition(1, GridUnitType.Star),
+                    new RowDefinition(GridLength.Auto),
+                },
+                Children =
+                {
+                    new ScrollContentPresenter
+                    {
+                        Name = "PART_ContentPresenter",
+                        [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
+                        [~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty],
+                        [~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty],
+                        [~~ScrollContentPresenter.ViewportProperty] = control[~~ScrollViewer.ViewportProperty],
+                        [~ScrollContentPresenter.CanHorizontallyScrollProperty] = control[~ScrollViewer.CanHorizontallyScrollProperty],
+                        [~ScrollContentPresenter.CanVerticallyScrollProperty] = control[~ScrollViewer.CanVerticallyScrollProperty],
+                    }.RegisterInNameScope(scope),
+                    new ScrollBar
+                    {
+                        Name = "horizontalScrollBar",
+                        Orientation = Orientation.Horizontal,
+                        [~RangeBase.MaximumProperty] = control[~ScrollViewer.HorizontalScrollBarMaximumProperty],
+                        [~~RangeBase.ValueProperty] = control[~~ScrollViewer.HorizontalScrollBarValueProperty],
+                        [~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.HorizontalScrollBarViewportSizeProperty],
+                        [~ScrollBar.VisibilityProperty] = control[~ScrollViewer.HorizontalScrollBarVisibilityProperty],
+                        [Grid.RowProperty] = 1,
+                    }.RegisterInNameScope(scope),
+                    new ScrollBar
+                    {
+                        Name = "verticalScrollBar",
+                        Orientation = Orientation.Vertical,
+                        [~RangeBase.MaximumProperty] = control[~ScrollViewer.VerticalScrollBarMaximumProperty],
+                        [~~RangeBase.ValueProperty] = control[~~ScrollViewer.VerticalScrollBarValueProperty],
+                        [~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.VerticalScrollBarViewportSizeProperty],
+                        [~ScrollBar.VisibilityProperty] = control[~ScrollViewer.VerticalScrollBarVisibilityProperty],
+                        [Grid.ColumnProperty] = 1,
+                    }.RegisterInNameScope(scope),
+                },
+            });
+        }
+
+        private void AssertArePixelEqual(Rect expected, Rect actual)
+        {
+            var expectedRounded = new Rect((int)expected.X, (int)expected.Y, (int)expected.Width, (int)expected.Height);
+            var actualRounded = new Rect((int)actual.X, (int)actual.Y, (int)actual.Width, (int)actual.Height);
+            Assert.Equal(expectedRounded, actualRounded);
+        }
+
+        private class TestCanvas : Canvas
+        {
+            public int MeasureCount { get; private set; }
+            public int ArrangeCount { get; private set; }
+
+            protected override Size MeasureOverride(Size availableSize)
+            {
+                ++MeasureCount;
+                return base.MeasureOverride(availableSize);
+            }
+
+            protected override Size ArrangeOverride(Size finalSize)
+            {
+                ++ArrangeCount;
+                return base.ArrangeOverride(finalSize);
+            }
+        }
+
+        private static class RunOnUIThread
+        {
+            public static async Task Execute(Func<Task> func)
+            {
+                await func();
+            }
+        }
+    }
+}

+ 10 - 10
tests/Avalonia.LeakTests/ControlTests.cs

@@ -44,7 +44,7 @@ namespace Avalonia.LeakTests
                     window.Show();
 
                     // Do a layout and make sure that Canvas gets added to visual tree.
-                    window.LayoutManager.ExecuteInitialLayoutPass(window);
+                    window.LayoutManager.ExecuteInitialLayoutPass();
                     Assert.IsType<Canvas>(window.Presenter.Child);
 
                     // Clear the content and ensure the Canvas is removed.
@@ -82,7 +82,7 @@ namespace Avalonia.LeakTests
                     window.Show();
 
                     // Do a layout and make sure that Canvas gets added to visual tree.
-                    window.LayoutManager.ExecuteInitialLayoutPass(window);
+                    window.LayoutManager.ExecuteInitialLayoutPass();
                     Assert.IsType<Canvas>(window.Find<Canvas>("foo"));
                     Assert.IsType<Canvas>(window.Presenter.Child);
 
@@ -122,7 +122,7 @@ namespace Avalonia.LeakTests
 
                     // Do a layout and make sure that ScrollViewer gets added to visual tree and its 
                     // template applied.
-                    window.LayoutManager.ExecuteInitialLayoutPass(window);
+                    window.LayoutManager.ExecuteInitialLayoutPass();
                     Assert.IsType<ScrollViewer>(window.Presenter.Child);
                     Assert.IsType<Canvas>(((ScrollViewer)window.Presenter.Child).Presenter.Child);
 
@@ -159,7 +159,7 @@ namespace Avalonia.LeakTests
 
                     // Do a layout and make sure that TextBox gets added to visual tree and its 
                     // template applied.
-                    window.LayoutManager.ExecuteInitialLayoutPass(window);
+                    window.LayoutManager.ExecuteInitialLayoutPass();
                     Assert.IsType<TextBox>(window.Presenter.Child);
                     Assert.NotEmpty(window.Presenter.Child.GetVisualChildren());
 
@@ -203,7 +203,7 @@ namespace Avalonia.LeakTests
 
                     // Do a layout and make sure that TextBox gets added to visual tree and its 
                     // Text property set.
-                    window.LayoutManager.ExecuteInitialLayoutPass(window);
+                    window.LayoutManager.ExecuteInitialLayoutPass();
                     Assert.IsType<TextBox>(window.Presenter.Child);
                     Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text);
 
@@ -241,7 +241,7 @@ namespace Avalonia.LeakTests
 
                 // Do a layout and make sure that TextBox gets added to visual tree and its 
                 // template applied.
-                window.LayoutManager.ExecuteInitialLayoutPass(window);
+                window.LayoutManager.ExecuteInitialLayoutPass();
                 Assert.Same(textBox, window.Presenter.Child);
 
                 // Get the border from the TextBox template.
@@ -295,7 +295,7 @@ namespace Avalonia.LeakTests
                     window.Show();
 
                     // Do a layout and make sure that TreeViewItems get realized.
-                    window.LayoutManager.ExecuteInitialLayoutPass(window);
+                    window.LayoutManager.ExecuteInitialLayoutPass();
                     Assert.Single(target.ItemContainerGenerator.Containers);
 
                     // Clear the content and ensure the TreeView is removed.
@@ -329,7 +329,7 @@ namespace Avalonia.LeakTests
                     window.Show();
 
                     // Do a layout and make sure that Slider gets added to visual tree.
-                    window.LayoutManager.ExecuteInitialLayoutPass(window);
+                    window.LayoutManager.ExecuteInitialLayoutPass();
                     Assert.IsType<Slider>(window.Presenter.Child);
 
                     // Clear the content and ensure the Slider is removed.
@@ -403,7 +403,7 @@ namespace Avalonia.LeakTests
 
                     // Do a layout and make sure that Canvas gets added to visual tree with
                     // its render transform.
-                    window.LayoutManager.ExecuteInitialLayoutPass(window);
+                    window.LayoutManager.ExecuteInitialLayoutPass();
                     var canvas = Assert.IsType<Canvas>(window.Presenter.Child);
                     Assert.IsType<RotateTransform>(canvas.RenderTransform);
 
@@ -514,7 +514,7 @@ namespace Avalonia.LeakTests
 
                     window.Show();
 
-                    window.LayoutManager.ExecuteInitialLayoutPass(window);
+                    window.LayoutManager.ExecuteInitialLayoutPass();
                     Assert.IsType<Path>(window.Presenter.Child);
 
                     window.Content = null;

+ 3 - 1
tests/Avalonia.UnitTests/TestRoot.cs

@@ -19,6 +19,8 @@ namespace Avalonia.UnitTests
         public TestRoot()
         {
             Renderer = Mock.Of<IRenderer>();
+            LayoutManager = new LayoutManager(this);
+            IsVisible = true;
         }
 
         public TestRoot(IControl child)
@@ -44,7 +46,7 @@ namespace Avalonia.UnitTests
 
         public double LayoutScaling { get; set; } = 1;
 
-        public ILayoutManager LayoutManager { get; set; } = new LayoutManager();
+        public ILayoutManager LayoutManager { get; set; }
 
         public double RenderScaling => 1;
 

+ 2 - 1
tests/Avalonia.UnitTests/TestTemplatedRoot.cs

@@ -16,6 +16,7 @@ namespace Avalonia.UnitTests
 
         public TestTemplatedRoot()
         {
+            LayoutManager = new LayoutManager(this);
             Template = new FuncControlTemplate<TestTemplatedRoot>((x, scope) => new ContentPresenter
             {
                 Name = "PART_ContentPresenter",
@@ -28,7 +29,7 @@ namespace Avalonia.UnitTests
 
         public double LayoutScaling => 1;
 
-        public ILayoutManager LayoutManager { get; set; } = new LayoutManager();
+        public ILayoutManager LayoutManager { get; set; }
 
         public double RenderScaling => 1;
 

+ 34 - 33
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt

@@ -1,33 +1,34 @@
-	OP	CL	CP	QU	GL	NS	EX	SY	IS	PR	PO	NU	AL	HL	ID	IN	HY	BA	BB	B2	ZW	CM	WJ	H2	H3	JL	JV	JT	RI	EB	EM	ZWJ
-OP	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	@	^	^	^	^	^	^	^	^	^	^
-CL	_	^	^	%	%	^	^	^	^	%	%	_	_	_	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-CP	_	^	^	%	%	^	^	^	^	%	%	%	%	%	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-QU	^	^	^	%	%	%	^	^	^	%	%	%	%	%	%	%	%	%	%	%	^	#	^	%	%	%	%	%	%	%	%	%
-GL	%	^	^	%	%	%	^	^	^	%	%	%	%	%	%	%	%	%	%	%	^	#	^	%	%	%	%	%	%	%	%	%
-NS	_	^	^	%	%	%	^	^	^	_	_	_	_	_	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-EX	_	^	^	%	%	%	^	^	^	_	_	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-SY	_	^	^	%	%	%	^	^	^	_	_	%	_	%	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-IS	_	^	^	%	%	%	^	^	^	_	_	%	%	%	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-PR	%	^	^	%	%	%	^	^	^	_	_	%	%	%	%	_	%	%	_	_	^	#	^	%	%	%	%	%	_	%	%	%
-PO	%	^	^	%	%	%	^	^	^	_	_	%	%	%	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-NU	%	^	^	%	%	%	^	^	^	%	%	%	%	%	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-AL	%	^	^	%	%	%	^	^	^	%	%	%	%	%	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-HL	%	^	^	%	%	%	^	^	^	%	%	%	%	%	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-ID	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-IN	_	^	^	%	%	%	^	^	^	_	_	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-HY	_	^	^	%	_	%	^	^	^	_	_	%	_	_	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-BA	_	^	^	%	_	%	^	^	^	_	_	_	_	_	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-BB	%	^	^	%	%	%	^	^	^	%	%	%	%	%	%	%	%	%	%	%	^	#	^	%	%	%	%	%	%	%	%	%
-B2	_	^	^	%	%	%	^	^	^	_	_	_	_	_	_	_	%	%	_	^	^	#	^	_	_	_	_	_	_	_	_	%
-ZW	_	_	_	_	_	_	_	_	_	_	_	_	_	_	_	_	_	_	_	_	^	_	_	_	_	_	_	_	_	_	_	_
-CM	%	^	^	%	%	%	^	^	^	%	%	%	%	%	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-WJ	%	^	^	%	%	%	^	^	^	%	%	%	%	%	%	%	%	%	%	%	^	#	^	%	%	%	%	%	%	%	%	%
-H2	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	%	%	_	_	_	%
-H3	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	%	_	_	_	%
-JL	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	%	%	%	%	_	_	_	_	%
-JV	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	%	%	_	_	_	%
-JT	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	%	_	_	_	%
-RI	_	^	^	%	%	%	^	^	^	_	_	_	_	_	_	_	%	%	_	_	^	#	^	_	_	_	_	_	%	_	_	%
-EB	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	%	%
-EM	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%
-ZWJ	_	^	^	%	%	%	^	^	^	_	_	_	_	_	%	_	%	%	_	_	^	#	^	_	_	_	_	_	_	%	%	%
+	OP	CL	CP	QU	GL	NS	EX	SY	IS	PR	PO	NU	AL	HL	ID	IN	HY	BA	BB	B2	ZW	CM	WJ	H2	H3	JL	JV	JT	RI	EB	EM	ZWJ	CB
+OP	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	^	@	^	^	^	^	^	^	^	^	^	^	^
+CL	_	^	^	%	%	^	^	^	^	%	%	_	_	_	_	^	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+CP	_	^	^	%	%	^	^	^	^	%	%	%	%	%	_	^	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+QU	^	^	^	%	%	%	^	^	^	%	%	%	%	%	%	%	%	%	%	%	^	#	^	%	%	%	%	%	%	%	%	%	%
+GL	%	^	^	%	%	%	^	^	^	%	%	%	%	%	%	%	%	%	%	%	^	#	^	%	%	%	%	%	%	%	%	%	%
+NS	_	^	^	%	%	%	^	^	^	_	_	_	_	_	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+EX	_	^	^	%	%	%	^	^	^	_	_	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+SY	_	^	^	%	%	%	^	^	^	_	_	%	_	%	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+IS	_	^	^	%	%	%	^	^	^	_	_	%	%	%	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+PR	%	^	^	%	%	%	^	^	^	_	_	%	%	%	%	_	%	%	_	_	^	#	^	%	%	%	%	%	_	%	%	%	_
+PO	%	^	^	%	%	%	^	^	^	_	_	%	%	%	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+NU	%	^	^	%	%	%	^	^	^	%	%	%	%	%	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+AL	%	^	^	%	%	%	^	^	^	%	%	%	%	%	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+HL	%	^	^	%	%	%	^	^	^	%	%	%	%	%	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+ID	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+IN	_	^	^	%	%	%	^	^	^	_	_	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+HY	_	^	^	%	_	%	^	^	^	_	_	%	_	_	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+BA	_	^	^	%	_	%	^	^	^	_	_	_	_	_	_	_	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+BB	%	^	^	%	%	%	^	^	^	%	%	%	%	%	%	%	%	%	%	%	^	#	^	%	%	%	%	%	%	%	%	%	_
+B2	_	^	^	%	%	%	^	^	^	_	_	_	_	_	_	_	%	%	_	^	^	#	^	_	_	_	_	_	_	_	_	%	_
+ZW	_	_	_	_	_	_	_	_	_	_	_	_	_	_	_	_	_	_	_	_	^	_	_	_	_	_	_	_	_	_	_	_	_
+CM	%	^	^	%	%	%	^	^	^	%	%	%	%	%	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+WJ	%	^	^	%	%	%	^	^	^	%	%	%	%	%	%	%	%	%	%	%	^	#	^	%	%	%	%	%	%	%	%	%	%
+H2	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	%	%	_	_	_	%	_
+H3	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	%	_	_	_	%	_
+JL	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	%	%	%	%	_	_	_	_	%	_
+JV	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	%	%	_	_	_	%	_
+JT	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	%	_	_	_	%	_
+RI	_	^	^	%	%	%	^	^	^	_	_	_	_	_	_	_	%	%	_	_	^	#	^	_	_	_	_	_	%	_	_	%	_
+EB	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	%	%	_
+EM	_	^	^	%	%	%	^	^	^	_	%	_	_	_	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+ZWJ	%	^	^	%	%	%	^	^	^	%	%	%	%	%	_	%	%	%	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_
+CB	_	^	^	%	%	_	^	^	^	_	_	_	_	_	_	_	_	_	_	_	^	#	^	_	_	_	_	_	_	_	_	%	_

+ 24 - 37
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
-using System.Linq;
 using System.Net.Http;
 using System.Text.RegularExpressions;
 using Avalonia.Media.TextFormatting.Unicode;
@@ -12,6 +11,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
     {
         public static void Execute()
         {
+            if (!Directory.Exists("Generated"))
+            {
+                Directory.CreateDirectory("Generated");
+            }
+
             using (var stream = File.Create("Generated\\GraphemeBreak.trie"))
             {
                 var trie = GenerateBreakTypeTrie();
@@ -22,48 +26,29 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
 
         private static UnicodeTrie GenerateBreakTypeTrie()
         {
-            var graphemeBreakClassValues = UnicodeEnumsGenerator.GetPropertyValueAliases("# Grapheme_Cluster_Break (GCB)");
-
-            var graphemeBreakClassMapping = graphemeBreakClassValues.Select(x => x.name).ToList();
-
             var trieBuilder = new UnicodeTrieBuilder();
 
-            var graphemeBreakData = ReadBreakData(
-                "https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakProperty.txt");
-
-            foreach (var (start, end, graphemeBreakType) in graphemeBreakData)
-            {
-                if (!graphemeBreakClassMapping.Contains(graphemeBreakType))
-                {
-                    continue;
-                }
-
-                if (start == end)
-                {
-                    trieBuilder.Set(start, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
-                }
-                else
-                {
-                    trieBuilder.SetRange(start, end, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
-                }
-            }
+            var graphemeBreakData = ReadBreakData(Path.Combine(UnicodeDataGenerator.Ucd, "auxiliary/GraphemeBreakProperty.txt"));
 
-            var emojiBreakData = ReadBreakData("https://unicode.org/Public/emoji/12.0/emoji-data.txt");
+            var emojiBreakData = ReadBreakData(Path.Combine(UnicodeDataGenerator.Ucd, "emoji/emoji-data.txt"));
 
-            foreach (var (start, end, graphemeBreakType) in emojiBreakData)
+            foreach (var breakData in new [] { graphemeBreakData, emojiBreakData })
             {
-                if (!graphemeBreakClassMapping.Contains(graphemeBreakType))
+                foreach (var (start, end, graphemeBreakType) in breakData)
                 {
-                    continue;
-                }
+                    if (!Enum.TryParse<GraphemeBreakClass>(graphemeBreakType, out var value))
+                    {
+                        continue;
+                    }
 
-                if (start == end)
-                {
-                    trieBuilder.Set(start, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
-                }
-                else
-                {
-                    trieBuilder.SetRange(start, end, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
+                    if (start == end)
+                    {
+                        trieBuilder.Set(start, (uint)value);
+                    }
+                    else
+                    {
+                        trieBuilder.SetRange(start, end, (uint)value);
+                    }
                 }
             }
 
@@ -113,7 +98,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
                                 end = Convert.ToInt32(match.Groups[2].Value, 16);
                             }
 
-                            data.Add((start, end, match.Groups[3].Value));
+                            var breakType = match.Groups[3].Value;
+
+                            data.Add((start, end, breakType));
                         }
                     }
                 }

+ 10 - 73
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs

@@ -1,9 +1,4 @@
 using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net.Http;
 using Avalonia.Media.TextFormatting.Unicode;
 using Xunit;
 
@@ -16,10 +11,12 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
     public class GraphemeBreakClassTrieGeneratorTests
     {
         [Theory(Skip = "Only run when we update the trie.")]
-        [ClassData(typeof(GraphemeEnumeratorTestDataGenerator))]
+        [ClassData(typeof(GraphemeBreakTestDataGenerator))]
         public void Should_Enumerate(string text, int expectedLength)
         {
-            var enumerator = new GraphemeEnumerator(text.AsMemory());
+            var textMemory = text.AsMemory();
+
+            var enumerator = new GraphemeEnumerator(textMemory);
 
             Assert.True(enumerator.MoveNext());
 
@@ -31,7 +28,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
         {
             const string text = "ABCDEFGHIJ";
 
-            var enumerator = new GraphemeEnumerator(text.AsMemory());
+            var textMemory = text.AsMemory();
+
+            var enumerator = new GraphemeEnumerator(textMemory);
 
             var count = 0;
 
@@ -51,73 +50,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
             GraphemeBreakClassTrieGenerator.Execute();
         }
 
-        public class GraphemeEnumeratorTestDataGenerator : IEnumerable<object[]>
+        private class GraphemeBreakTestDataGenerator : TestDataGenerator
         {
-            private readonly List<object[]> _testData;
-
-            public GraphemeEnumeratorTestDataGenerator()
-            {
-                _testData = ReadTestData();
-            }
-
-            public IEnumerator<object[]> GetEnumerator()
-            {
-                return _testData.GetEnumerator();
-            }
-
-            IEnumerator IEnumerable.GetEnumerator()
-            {
-                return GetEnumerator();
-            }
-
-            private static List<object[]> ReadTestData()
+            public GraphemeBreakTestDataGenerator() 
+                : base("auxiliary/GraphemeBreakTest.txt")
             {
-                var testData = new List<object[]>();
-
-                using (var client = new HttpClient())
-                {
-                    using (var result = client.GetAsync("https://www.unicode.org/Public/UNIDATA/auxiliary/GraphemeBreakTest.txt").GetAwaiter().GetResult())
-                    {
-                        if (!result.IsSuccessStatusCode)
-                            return testData;
-
-                        using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
-                        using (var reader = new StreamReader(stream))
-                        {
-                            while (!reader.EndOfStream)
-                            {
-                                var line = reader.ReadLine();
-
-                                if (line == null)
-                                {
-                                    break;
-                                }
-
-                                if (line.StartsWith("#") || string.IsNullOrEmpty(line))
-                                {
-                                    continue;
-                                }
-
-                                var elements = line.Split('#')[0].Replace("÷\t", "÷").Trim('÷').Split('÷');
-
-                                var chars = elements[0].Replace(" × ", " ").Split(' ');
-
-                                var codepoints = chars.Where(x => x != "" && x != "×")
-                                    .Select(x => Convert.ToInt32(x, 16)).ToArray();
-
-                                var text = string.Join(null, codepoints.Select(char.ConvertFromUtf32));
-
-                                var length = codepoints.Select(x => x > ushort.MaxValue ? 2 : 1).Sum();
-
-                                var data = new object[] { text, length };
-
-                                testData.Add(data);
-                            }
-                        }
-                    }
-                }
-
-                return testData;
             }
         }
     }

+ 1 - 1
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs

@@ -3,7 +3,7 @@ using Avalonia.Media.TextFormatting.Unicode;
 using Avalonia.Utility;
 using Xunit;
 
-namespace Avalonia.Visuals.UnitTests.Media.Text
+namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
 {
     public class LineBreakerTests
     {

+ 85 - 0
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs

@@ -0,0 +1,85 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+
+namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
+{
+    public abstract class TestDataGenerator : IEnumerable<object[]>
+    {
+        private readonly string _fileName;
+        private readonly List<object[]> _testData;
+
+        protected TestDataGenerator(string fileName)
+        {
+            _fileName = fileName;
+            _testData = ReadTestData();
+        }
+
+        public IEnumerator<object[]> GetEnumerator()
+        {
+            return _testData.GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        private List<object[]> ReadTestData()
+        {
+            var testData = new List<object[]>();
+
+            using (var client = new HttpClient())
+            {
+                var url = Path.Combine(UnicodeDataGenerator.Ucd, _fileName);
+
+                using (var result = client.GetAsync(url).GetAwaiter().GetResult())
+                {
+                    if (!result.IsSuccessStatusCode)
+                        return testData;
+
+                    using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
+                    using (var reader = new StreamReader(stream))
+                    {
+                        while (!reader.EndOfStream)
+                        {
+                            var line = reader.ReadLine();
+
+                            if (line == null)
+                            {
+                                break;
+                            }
+
+                            if (line.StartsWith("#") || string.IsNullOrEmpty(line))
+                            {
+                                continue;
+                            }
+
+                            var elements = line.Split('#');
+
+                            elements = elements[0].Replace("÷\t", "÷").Trim('÷').Split('÷');
+
+                            var chars = elements[0].Replace(" × ", " ").Split(' ');
+
+                            var codepoints = chars.Where(x => x != "" && x != "×")
+                                .Select(x => Convert.ToInt32(x, 16)).ToArray();
+
+                            var text = string.Join(null, codepoints.Select(char.ConvertFromUtf32));
+
+                            var length = codepoints.Select(x => x > ushort.MaxValue ? 2 : 1).Sum();
+
+                            var data = new object[] { text, length };
+
+                            testData.Add(data);
+                        }
+                    }
+                }
+            }
+
+            return testData;
+        }
+    }
+}

+ 34 - 27
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs

@@ -9,13 +9,16 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
 {
     internal static class UnicodeDataGenerator
     {
+        public const string Ucd = "https://www.unicode.org/Public/13.0.0/ucd/";
+
         public static void Execute()
         {
             var codepoints = new Dictionary<int, UnicodeDataItem>();
 
-            var generalCategoryValues = UnicodeEnumsGenerator.CreateGeneralCategoryEnum();
+            var generalCategoryEntries =
+                UnicodeEnumsGenerator.CreateGeneralCategoryEnum();
 
-            var generalCategoryMappings = CreateTagToIndexMappings(generalCategoryValues);
+            var generalCategoryMappings = CreateTagToIndexMappings(generalCategoryEntries);
 
             var generalCategoryData = ReadGeneralCategoryData();
 
@@ -26,23 +29,23 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
                 AddGeneralCategoryRange(codepoints, range, generalCategory);
             }
 
-            var scriptValues = UnicodeEnumsGenerator.CreateScriptEnum();
+            var scriptEntries = UnicodeEnumsGenerator.CreateScriptEnum();
 
-            var scriptMappings = CreateNameToIndexMappings(scriptValues);
+            var scriptMappings = CreateNameToIndexMappings(scriptEntries);
 
             var scriptData = ReadScriptData();
 
             foreach (var (range, name) in scriptData)
             {
-                var script = scriptMappings[name.Replace("_", "")];
+                var script = scriptMappings[name];
 
                 AddScriptRange(codepoints, range, script);
-
             }
 
-            var biDiClassValues = UnicodeEnumsGenerator.CreateBiDiClassEnum();
+            var biDiClassEntries =
+                    UnicodeEnumsGenerator.CreateBiDiClassEnum();
 
-            var biDiClassMappings = CreateTagToIndexMappings(biDiClassValues);
+            var biDiClassMappings = CreateTagToIndexMappings(biDiClassEntries);
 
             var biDiData = ReadBiDiData();
 
@@ -53,9 +56,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
                 AddBiDiClassRange(codepoints, range, biDiClass);
             }
 
-            var lineBreakClassValues = UnicodeEnumsGenerator.CreateLineBreakClassEnum();
+            var lineBreakClassEntries =
+                UnicodeEnumsGenerator.CreateLineBreakClassEnum();
 
-            var lineBreakClassMappings = CreateTagToIndexMappings(lineBreakClassValues);
+            var lineBreakClassMappings = CreateTagToIndexMappings(lineBreakClassEntries);
 
             var lineBreakClassData = ReadLineBreakClassData();
 
@@ -66,11 +70,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
                 AddLineBreakClassRange(codepoints, range, lineBreakClass);
             }
 
-            const int initialValue = ((int)LineBreakClass.Unknown << UnicodeData.LINEBREAK_SHIFT) |
-                                      ((int)BiDiClass.LeftToRight << UnicodeData.BIDI_SHIFT) |
-                                      ((int)Script.Unknown << UnicodeData.SCRIPT_SHIFT) | (int)GeneralCategory.Other;
+            //const int initialValue = (0 << UnicodeData.LINEBREAK_SHIFT) |
+            //                          (0 << UnicodeData.BIDI_SHIFT) |
+            //                          (0 << UnicodeData.SCRIPT_SHIFT) | (int)GeneralCategory.Other;
 
-            var builder = new UnicodeTrieBuilder(initialValue);
+            var builder = new UnicodeTrieBuilder(/*initialValue*/);
 
             foreach (var properties in codepoints.Values)
             {
@@ -88,27 +92,30 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
 
                 trie.Save(stream);
             }
+
+            UnicodeEnumsGenerator.CreatePropertyValueAliasHelper(scriptEntries, generalCategoryEntries,
+                biDiClassEntries, lineBreakClassEntries);
         }
 
-        private static Dictionary<string, int> CreateTagToIndexMappings(List<(string name, string tag, string comment)> values)
+        private static Dictionary<string, int> CreateTagToIndexMappings(List<DataEntry> entries)
         {
             var mappings = new Dictionary<string, int>();
 
-            for (var i = 0; i < values.Count; i++)
+            for (var i = 0; i < entries.Count; i++)
             {
-                mappings.Add(values[i].tag, i);
+                mappings.Add(entries[i].Tag, i);
             }
 
             return mappings;
         }
 
-        private static Dictionary<string, int> CreateNameToIndexMappings(List<(string name, string tag, string comment)> values)
+        private static Dictionary<string, int> CreateNameToIndexMappings(List<DataEntry> entries)
         {
             var mappings = new Dictionary<string, int>();
 
-            for (var i = 0; i < values.Count; i++)
+            for (var i = 0; i < entries.Count; i++)
             {
-                mappings.Add(values[i].name, i);
+                mappings.Add(entries[i].Name, i);
             }
 
             return mappings;
@@ -180,24 +187,22 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
 
         public static List<(CodepointRange, string)> ReadGeneralCategoryData()
         {
-            return ReadUnicodeData(
-                "https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedGeneralCategory.txt");
+            return ReadUnicodeData("extracted/DerivedGeneralCategory.txt");
         }
 
         public static List<(CodepointRange, string)> ReadScriptData()
         {
-            return ReadUnicodeData("https://www.unicode.org/Public/UCD/latest/ucd/Scripts.txt");
+            return ReadUnicodeData("Scripts.txt");
         }
 
         public static List<(CodepointRange, string)> ReadBiDiData()
         {
-            return ReadUnicodeData("https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedBidiClass.txt");
+            return ReadUnicodeData("extracted/DerivedBidiClass.txt");
         }
 
         public static List<(CodepointRange, string)> ReadLineBreakClassData()
         {
-            return ReadUnicodeData(
-                "https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedLineBreak.txt");
+            return ReadUnicodeData("extracted/DerivedLineBreak.txt");
         }
 
         private static List<(CodepointRange, string)> ReadUnicodeData(string file)
@@ -208,7 +213,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
 
             using (var client = new HttpClient())
             {
-                using (var result = client.GetAsync(file).GetAwaiter().GetResult())
+                var url = Path.Combine(Ucd, file);
+
+                using (var result = client.GetAsync(url).GetAwaiter().GetResult())
                 {
                     if (!result.IsSuccessStatusCode)
                     {

+ 24 - 1
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs

@@ -1,4 +1,6 @@
-using Xunit;
+using System;
+using Avalonia.Media.TextFormatting.Unicode;
+using Xunit;
 
 namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
 {
@@ -13,5 +15,26 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
         {
             UnicodeDataGenerator.Execute();
         }
+        [Theory(Skip = "Only run when we update the trie.")]
+        [ClassData(typeof(LineBreakTestDataGenerator))]
+
+        public void Should_Enumerate_LineBreaks(string text, int expectedLength)
+        {
+            var textMemory = text.AsMemory();
+
+            var enumerator = new LineBreakEnumerator(textMemory);
+
+            Assert.True(enumerator.MoveNext());
+
+            Assert.Equal(expectedLength, enumerator.Current.PositionWrap);
+        }
+
+        private class LineBreakTestDataGenerator : TestDataGenerator
+        {
+            public LineBreakTestDataGenerator()
+                : base("auxiliary/LineBreakTest.txt")
+            {
+            }
+        }
     }
 }

+ 107 - 74
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs

@@ -8,9 +8,16 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
 {
     internal static class UnicodeEnumsGenerator
     {
-        public static List<(string name, string tag, string comment)> CreateScriptEnum()
+        public static List<DataEntry> CreateScriptEnum()
         {
-            var scriptValues = GetPropertyValueAliases("# Script (sc)");
+            var entries = new List<DataEntry>
+            {
+                new DataEntry("Unknown", "Zzzz", string.Empty),
+                new DataEntry("Common", "Zyyy", string.Empty),
+                new DataEntry("Inherited", "Zinh", string.Empty)
+            };
+
+            ParseDataEntries("# Script (sc)", entries);
 
             using (var stream = File.Create("Generated\\Script.cs"))
             using (var writer = new StreamWriter(stream))
@@ -20,22 +27,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
                 writer.WriteLine("    public enum Script");
                 writer.WriteLine("    {");
 
-                foreach (var (name, tag, comment) in scriptValues)
+                foreach (var entry in entries)
                 {
-                    writer.WriteLine("        " + name + ", //" + tag +
-                                     (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
+                    writer.WriteLine("        " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
+                                     (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
                 }
 
                 writer.WriteLine("    }");
                 writer.WriteLine("}");
             }
 
-            return scriptValues;
+            return entries;
         }
 
-        public static List<(string name, string tag, string comment)> CreateGeneralCategoryEnum()
+        public static List<DataEntry> CreateGeneralCategoryEnum()
         {
-            var generalCategoryValues = GetPropertyValueAliases("# General_Category (gc)");
+            var entries = new List<DataEntry> { new DataEntry("Other", "C", " Cc | Cf | Cn | Co | Cs") };
+
+            ParseDataEntries("# General_Category (gc)", entries);
 
             using (var stream = File.Create("Generated\\GeneralCategory.cs"))
             using (var writer = new StreamWriter(stream))
@@ -45,22 +54,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
                 writer.WriteLine("    public enum GeneralCategory");
                 writer.WriteLine("    {");
 
-                foreach (var (name, tag, comment) in generalCategoryValues)
+                foreach (var entry in entries)
                 {
-                    writer.WriteLine("        " + name + ", //" + tag +
-                                     (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
+                    writer.WriteLine("        " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
+                                     (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
                 }
 
                 writer.WriteLine("    }");
                 writer.WriteLine("}");
             }
 
-            return generalCategoryValues;
+            return entries;
         }
 
-        public static List<(string name, string tag, string comment)> CreateGraphemeBreakTypeEnum()
+        public static List<DataEntry> CreateGraphemeBreakTypeEnum()
         {
-            var graphemeClusterBreakValues = GetPropertyValueAliases("# Grapheme_Cluster_Break (GCB)");
+            var entries = new List<DataEntry> { new DataEntry("Other", "XX", string.Empty) };
+
+            ParseDataEntries("# Grapheme_Cluster_Break (GCB)", entries);
 
             using (var stream = File.Create("Generated\\GraphemeBreakClass.cs"))
             using (var writer = new StreamWriter(stream))
@@ -70,10 +81,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
                 writer.WriteLine("    public enum GraphemeBreakClass");
                 writer.WriteLine("    {");
 
-                foreach (var (name, tag, comment) in graphemeClusterBreakValues)
+                foreach (var entry in entries)
                 {
-                    writer.WriteLine("        " + name + ", //" + tag +
-                                     (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
+                    writer.WriteLine("        " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
+                                     (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
                 }
 
                 writer.WriteLine("        ExtendedPictographic");
@@ -82,7 +93,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
                 writer.WriteLine("}");
             }
 
-            return graphemeClusterBreakValues;
+            return entries;
         }
 
         private static List<string> GenerateBreakPairTable()
@@ -185,20 +196,32 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
             }
         }
 
-        public static List<(string name, string tag, string comment)> CreateLineBreakClassEnum()
+        public static List<DataEntry> CreateLineBreakClassEnum()
         {
             var usedLineBreakClasses = GenerateBreakPairTable();
 
-            var lineBreakValues = GetPropertyValueAliases("# Line_Break (lb)");
+            var entries = new List<DataEntry> { new DataEntry("Unknown", "XX", string.Empty) };
+
+            ParseDataEntries("# Line_Break (lb)", entries);
 
-            var lineBreakClassMappings = lineBreakValues.ToDictionary(x => x.tag, x => (x.name, x.tag, x.comment));
+            var orderedLineBreakEntries = new Dictionary<string, DataEntry>();
 
-            var orderedLineBreakValues = usedLineBreakClasses.Select(x =>
+            foreach (var tag in usedLineBreakClasses)
             {
-                var value = lineBreakClassMappings[x];
-                lineBreakClassMappings.Remove(x);
-                return value;
-            }).ToList();
+                var entry = entries.Single(x => x.Tag == tag);
+
+                orderedLineBreakEntries.Add(tag, entry);
+            }
+
+            foreach (var entry in entries)
+            {
+                if (orderedLineBreakEntries.ContainsKey(entry.Tag))
+                {
+                    continue;
+                }
+
+                orderedLineBreakEntries.Add(entry.Tag, entry);
+            }
 
             using (var stream = File.Create("Generated\\LineBreakClass.cs"))
             using (var writer = new StreamWriter(stream))
@@ -208,32 +231,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
                 writer.WriteLine("    public enum LineBreakClass");
                 writer.WriteLine("    {");
 
-                foreach (var (name, tag, comment) in orderedLineBreakValues)
-                {
-                    writer.WriteLine("        " + name + ", //" + tag +
-                                     (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
-                }
-
-                writer.WriteLine();
-
-                foreach (var (name, tag, comment) in lineBreakClassMappings.Values)
+                foreach (var entry in orderedLineBreakEntries.Values)
                 {
-                    writer.WriteLine("        " + name + ", //" + tag +
-                                     (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
+                    writer.WriteLine("        " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
+                                     (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
                 }
 
                 writer.WriteLine("    }");
                 writer.WriteLine("}");
             }
 
-            orderedLineBreakValues.AddRange(lineBreakClassMappings.Values);
-
-            return orderedLineBreakValues;
+            return orderedLineBreakEntries.Values.ToList();
         }
 
-        public static List<(string name, string tag, string comment)> CreateBiDiClassEnum()
+        public static List<DataEntry> CreateBiDiClassEnum()
         {
-            var biDiClassValues = GetPropertyValueAliases("# Bidi_Class (bc)");
+            var entries = new List<DataEntry> { new DataEntry("Left_To_Right", "L", string.Empty) };
+
+            ParseDataEntries("# Bidi_Class (bc)", entries);
 
             using (var stream = File.Create("Generated\\BiDiClass.cs"))
             using (var writer = new StreamWriter(stream))
@@ -243,23 +258,21 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
                 writer.WriteLine("    public enum BiDiClass");
                 writer.WriteLine("    {");
 
-                foreach (var (name, tag, comment) in biDiClassValues)
+                foreach (var entry in entries)
                 {
-                    writer.WriteLine("        " + name + ", //" + tag +
-                                     (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
+                    writer.WriteLine("        " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
+                                     (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
                 }
 
                 writer.WriteLine("    }");
                 writer.WriteLine("}");
             }
 
-            return biDiClassValues;
+            return entries;
         }
 
-        public static void CreatePropertyValueAliasHelper(List<(string name, string tag, string comment)> scriptValues,
-            List<(string name, string tag, string comment)> generalCategoryValues,
-            List<(string name, string tag, string comment)> biDiClassValues,
-            List<(string name, string tag, string comment)> lineBreakValues)
+        public static void CreatePropertyValueAliasHelper(List<DataEntry> scriptEntries, IEnumerable<DataEntry> generalCategoryEntries,
+            IEnumerable<DataEntry> biDiClassEntries, IEnumerable<DataEntry> lineBreakClassEntries)
         {
             using (var stream = File.Create("Generated\\PropertyValueAliasHelper.cs"))
             using (var writer = new StreamWriter(stream))
@@ -269,35 +282,35 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
 
                 writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode");
                 writer.WriteLine("{");
-                writer.WriteLine("    public static class PropertyValueAliasHelper");
+                writer.WriteLine("    internal static class PropertyValueAliasHelper");
                 writer.WriteLine("    {");
 
-                WritePropertyValueAliasGetTag(writer, scriptValues, "Script", "Zzzz");
+                WritePropertyValueAliasGetTag(writer, scriptEntries, "Script", "Zzzz");
 
-                WritePropertyValueAlias(writer, scriptValues, "Script", "Unknown");
+                WritePropertyValueAlias(writer, scriptEntries, "Script", "Unknown");
 
-                WritePropertyValueAlias(writer, generalCategoryValues, "GeneralCategory", "Other");
+                WritePropertyValueAlias(writer, generalCategoryEntries, "GeneralCategory", "Other");
 
-                WritePropertyValueAlias(writer, biDiClassValues, "BiDiClass", "LeftToRight");
+                WritePropertyValueAlias(writer, biDiClassEntries, "BiDiClass", "LeftToRight");
 
-                WritePropertyValueAlias(writer, lineBreakValues, "LineBreakClass", "Unknown");
+                WritePropertyValueAlias(writer, lineBreakClassEntries, "LineBreakClass", "Unknown");
 
                 writer.WriteLine("    }");
                 writer.WriteLine("}");
             }
         }
 
-        public static List<(string name, string tag, string comment)> GetPropertyValueAliases(string property)
+        public static void ParseDataEntries(string property, List<DataEntry> entries)
         {
-            var data = new List<(string name, string tag, string comment)>();
-
             using (var client = new HttpClient())
             {
-                using (var result = client.GetAsync("https://www.unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt").GetAwaiter().GetResult())
+                var url = Path.Combine(UnicodeDataGenerator.Ucd, "PropertyValueAliases.txt");
+
+                using (var result = client.GetAsync(url).GetAwaiter().GetResult())
                 {
                     if (!result.IsSuccessStatusCode)
                     {
-                        return data;
+                        return;
                     }
 
                     using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
@@ -337,7 +350,12 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
 
                             elements = elements[2].Split('#');
 
-                            var name = elements[0].Trim().Replace("_", string.Empty);
+                            var name = elements[0].Trim();
+
+                            if (entries.Any(x => x.Name == name))
+                            {
+                                continue;
+                            }
 
                             var comment = string.Empty;
 
@@ -346,24 +364,25 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
                                 comment = elements[1];
                             }
 
-                            data.Add((name, tag, comment));
+                            var entry = new DataEntry(name, tag, comment);
+
+                            entries.Add(entry);
                         }
                     }
                 }
             }
-
-            return data;
         }
 
-        private static void WritePropertyValueAliasGetTag(TextWriter writer,
-            IEnumerable<(string name, string tag, string comment)> values, string typeName, string defaultValue)
+        private static void WritePropertyValueAliasGetTag(TextWriter writer, IEnumerable<DataEntry> entries,
+            string typeName, string defaultValue)
         {
-            writer.WriteLine($"        private static readonly Dictionary<{typeName}, string> s_{typeName.ToLower()}ToTag = ");
+            writer.WriteLine(
+                $"        private static readonly Dictionary<{typeName}, string> s_{typeName.ToLower()}ToTag = ");
             writer.WriteLine($"            new Dictionary<{typeName}, string>{{");
 
-            foreach (var (name, tag, comment) in values)
+            foreach (var entry in entries)
             {
-                writer.WriteLine($"                {{ {typeName}.{name}, \"{tag}\"}},");
+                writer.WriteLine($"                {{ {typeName}.{entry.Name.Replace("_", "")}, \"{entry.Tag}\"}},");
             }
 
             writer.WriteLine("        };");
@@ -382,15 +401,15 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
             writer.WriteLine();
         }
 
-        private static void WritePropertyValueAlias(TextWriter writer,
-            IEnumerable<(string name, string tag, string comment)> values, string typeName, string defaultValue)
+        private static void WritePropertyValueAlias(TextWriter writer, IEnumerable<DataEntry> entries, string typeName,
+            string defaultValue)
         {
             writer.WriteLine($"        private static readonly Dictionary<string, {typeName}> s_tagTo{typeName} = ");
             writer.WriteLine($"            new Dictionary<string,{typeName}>{{");
 
-            foreach (var (name, tag, comment) in values)
+            foreach (var entry in entries)
             {
-                writer.WriteLine($"                {{ \"{tag}\", {typeName}.{name}}},");
+                writer.WriteLine($"                {{ \"{entry.Tag}\", {typeName}.{entry.Name.Replace("_", "")}}},");
             }
 
             writer.WriteLine("        };");
@@ -409,4 +428,18 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
             writer.WriteLine();
         }
     }
+
+    public readonly struct DataEntry
+    {
+        public DataEntry(string name, string tag, string comment)
+        {
+            Name = name;
+            Tag = tag;
+            Comment = comment;
+        }
+
+        public string Name { get; }
+        public string Tag { get; }
+        public string Comment { get; }
+    }
 }

+ 4 - 4
tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs

@@ -134,7 +134,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                 var root = new TestRoot(child);
                 root.Renderer = new ImmediateRenderer(root);
 
-                root.LayoutManager.ExecuteInitialLayoutPass(root);
+                root.LayoutManager.ExecuteInitialLayoutPass();
 
                 root.Measure(new Size(50, 100));
                 root.Arrange(new Rect(new Size(50, 100)));
@@ -171,7 +171,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                 var root = new TestRoot(child);
                 root.Renderer = new ImmediateRenderer(root);
 
-                root.LayoutManager.ExecuteInitialLayoutPass(root);
+                root.LayoutManager.ExecuteInitialLayoutPass();
 
                 root.Measure(new Size(300, 100));
                 root.Arrange(new Rect(new Size(300, 100)));
@@ -222,7 +222,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
 
                 var root = new TestRoot(rootGrid);
                 root.Renderer = new ImmediateRenderer(root);
-                root.LayoutManager.ExecuteInitialLayoutPass(root);
+                root.LayoutManager.ExecuteInitialLayoutPass();
 
                 var rootSize = new Size(RootWidth, RootHeight);
                 root.Measure(rootSize);
@@ -277,7 +277,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
 
                 var root = new TestRoot(rootGrid);
                 root.Renderer = new ImmediateRenderer(root);
-                root.LayoutManager.ExecuteInitialLayoutPass(root);
+                root.LayoutManager.ExecuteInitialLayoutPass();
 
                 var rootSize = new Size(RootWidth, RootHeight);
                 root.Measure(rootSize);

+ 3 - 3
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@@ -653,7 +653,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 };
 
                 var layout = tree.LayoutManager;
-                layout.ExecuteInitialLayoutPass(tree);
+                layout.ExecuteInitialLayoutPass();
 
                 var scene = new Scene(tree);
                 var sceneBuilder = new SceneBuilder();
@@ -696,7 +696,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 };
 
                 var layout = tree.LayoutManager;
-                layout.ExecuteInitialLayoutPass(tree);
+                layout.ExecuteInitialLayoutPass();
 
                 var scene = new Scene(tree);
                 var sceneBuilder = new SceneBuilder();
@@ -744,7 +744,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 };
 
                 var layout = tree.LayoutManager;
-                layout.ExecuteInitialLayoutPass(tree);
+                layout.ExecuteInitialLayoutPass();
 
                 var scene = new Scene(tree);
                 var sceneBuilder = new SceneBuilder();

+ 5 - 5
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs

@@ -40,7 +40,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 };
 
                 var layout = tree.LayoutManager;
-                layout.ExecuteInitialLayoutPass(tree);
+                layout.ExecuteInitialLayoutPass();
 
                 var animation = new BehaviorSubject<double>(0.5);
                 border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
@@ -105,7 +105,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 };
 
                 var layout = tree.LayoutManager;
-                layout.ExecuteInitialLayoutPass(tree);
+                layout.ExecuteInitialLayoutPass();
 
                 var animation = new BehaviorSubject<double>(0.5);
                 border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
@@ -147,7 +147,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 };
 
                 var layout = tree.LayoutManager;
-                layout.ExecuteInitialLayoutPass(tree);
+                layout.ExecuteInitialLayoutPass();
 
                 var animation = new BehaviorSubject<double>(0.5);
                 border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
@@ -197,7 +197,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 };
 
                 var layout = tree.LayoutManager;
-                layout.ExecuteInitialLayoutPass(tree);
+                layout.ExecuteInitialLayoutPass();
 
                 var animation = new BehaviorSubject<double>(0.5);
                 border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
@@ -241,7 +241,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                 };
 
                 var layout = tree.LayoutManager;
-                layout.ExecuteInitialLayoutPass(tree);
+                layout.ExecuteInitialLayoutPass();
 
                 var animation = new BehaviorSubject<double>(0.5);
                 border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);