Parcourir la source

Merge branch 'master' into safe_insets

Max Katz il y a 2 ans
Parent
commit
07708d2aad
100 fichiers modifiés avec 633 ajouts et 386 suppressions
  1. 1 0
      Avalonia.Desktop.slnf
  2. 12 5
      Avalonia.sln
  3. 1 8
      azure-pipelines-integrationtests.yml
  4. 5 0
      packages/Avalonia/Avalonia.props
  5. 1 0
      samples/ControlCatalog/ControlCatalog.csproj
  6. 4 4
      samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
  7. 4 4
      samples/ControlCatalog/Pages/PointerCanvas.cs
  8. 1 0
      samples/IntegrationTestApp/IntegrationTestApp.csproj
  9. 1 0
      samples/IntegrationTestApp/MainWindow.axaml
  10. 7 5
      samples/IntegrationTestApp/MainWindow.axaml.cs
  11. 18 4
      samples/IntegrationTestApp/Program.cs
  12. 2 3
      src/Avalonia.Base/Input/AccessKeyHandler.cs
  13. 0 1
      src/Avalonia.Base/Media/DrawingContext.cs
  14. 0 2
      src/Avalonia.Base/Media/DrawingGroup.cs
  15. 2 2
      src/Avalonia.Base/Media/GeometryDrawing.cs
  16. 7 7
      src/Avalonia.Base/Media/GlyphRun.cs
  17. 1 1
      src/Avalonia.Base/Media/GlyphRunDrawing.cs
  18. 1 2
      src/Avalonia.Base/Media/PolyLineSegment.cs
  19. 1 1
      src/Avalonia.Base/Media/TextDecoration.cs
  20. 3 3
      src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs
  21. 62 36
      src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
  22. 5 5
      src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
  23. 1 1
      src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
  24. 1 1
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  25. 1 1
      src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs
  26. 3 3
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  27. 104 51
      src/Avalonia.Base/Platform/AssetLoader.cs
  28. 1 2
      src/Avalonia.Base/Platform/IGlyphRunImpl.cs
  29. 1 1
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  30. 1 1
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  31. 4 4
      src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs
  32. 2 3
      src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
  33. 15 6
      src/Avalonia.Base/StyledElement.cs
  34. 8 5
      src/Avalonia.Base/Styling/OrSelector.cs
  35. 7 8
      src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
  36. 3 7
      src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs
  37. 17 0
      src/Avalonia.Base/Visual.cs
  38. 1 1
      src/Avalonia.Controls.DataGrid/Themes/Simple.xaml
  39. 1 1
      src/Avalonia.Controls.ItemsRepeater/Layout/StackLayout.cs
  40. 6 3
      src/Avalonia.Controls/MaskedTextBox.cs
  41. 20 14
      src/Avalonia.Controls/MenuItem.cs
  42. 1 1
      src/Avalonia.Controls/NativeMenuItem.cs
  43. 24 11
      src/Avalonia.Controls/Panel.cs
  44. 2 2
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  45. 1 0
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  46. 1 2
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  47. 1 1
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  48. 0 17
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  49. 3 3
      src/Avalonia.Controls/PullToRefresh/RefreshInfoProvider.cs
  50. 1 1
      src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
  51. 1 1
      src/Avalonia.Controls/TrayIcon.cs
  52. 1 1
      src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs
  53. 2 10
      src/Avalonia.Controls/VirtualizingCarouselPanel.cs
  54. 1 1
      src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs
  55. 2 2
      src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
  56. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Bold.otf
  57. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.otf
  58. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Light.otf
  59. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Medium.otf
  60. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Regular.otf
  61. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.otf
  62. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Thin.otf
  63. 13 0
      src/Avalonia.Fonts.Inter/Avalonia.Fonts.Inter.csproj
  64. 3 0
      src/Avalonia.Fonts.Inter/Properties/AssemblyInfo.cs
  65. 2 2
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  66. 1 1
      src/Avalonia.Themes.Fluent/Accents/Base.xaml
  67. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf
  68. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf
  69. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf
  70. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf
  71. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf
  72. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf
  73. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf
  74. 1 2
      src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
  75. 1 0
      src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
  76. 6 0
      src/Avalonia.Themes.Simple/Accents/Base.xaml
  77. 1 0
      src/Avalonia.Themes.Simple/Controls/ComboBox.xaml
  78. 0 1
      src/Avalonia.Themes.Simple/Controls/DataValidationErrors.xaml
  79. 1 0
      src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml
  80. 1 3
      src/Avalonia.Themes.Simple/Controls/OverlayPopupHost.xaml
  81. 1 3
      src/Avalonia.Themes.Simple/Controls/PopupRoot.xaml
  82. 1 0
      src/Avalonia.Themes.Simple/Controls/Window.xaml
  83. 14 13
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
  84. 8 8
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
  85. 36 7
      src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs
  86. 6 5
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  87. 3 2
      src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs
  88. 15 8
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  89. 34 9
      src/Skia/Avalonia.Skia/GeometryGroupImpl.cs
  90. 14 17
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  91. 2 2
      src/Skia/Avalonia.Skia/GlyphRunImpl.cs
  92. 0 1
      src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
  93. 3 2
      src/Skia/Avalonia.Skia/LineGeometryImpl.cs
  94. 2 2
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  95. 3 2
      src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs
  96. 64 26
      src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
  97. 1 3
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  98. 16 5
      src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs
  99. 1 1
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  100. 2 2
      src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs

+ 1 - 0
Avalonia.Desktop.slnf

@@ -21,6 +21,7 @@
       "src\\Avalonia.Desktop\\Avalonia.Desktop.csproj",
       "src\\Avalonia.Diagnostics\\Avalonia.Diagnostics.csproj",
       "src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj",
+      "src\\Avalonia.Fonts.Inter\\Avalonia.Fonts.Inter.csproj",
       "src\\Avalonia.FreeDesktop\\Avalonia.FreeDesktop.csproj",
       "src\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj",
       "src\\Avalonia.Headless\\Avalonia.Headless.csproj",

+ 12 - 5
Avalonia.sln

@@ -244,6 +244,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepe
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater.UnitTests", "tests\Avalonia.Controls.ItemsRepeater.UnitTests\Avalonia.Controls.ItemsRepeater.UnitTests.csproj", "{F4E36AA8-814E-4704-BC07-291F70F45193}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Fonts.Inter", "src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj", "{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -555,9 +557,14 @@ Global
 		{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.ActiveCfg = Release|Any CPU
+		{C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.Build.0 = Release|Any CPU
+		{C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.Build.0 = Release|Any CPU
 		{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -566,10 +573,10 @@ Global
 		{F4E36AA8-814E-4704-BC07-291F70F45193}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.Build.0 = Release|Any CPU
-		{C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.ActiveCfg = Release|Any CPU
-		{C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.Build.0 = Release|Any CPU
-		{C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.Build.0 = Release|Any CPU
+		{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -635,8 +642,8 @@ Global
 		{90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
-		{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
+		{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

+ 1 - 8
azure-pipelines-integrationtests.yml

@@ -1,11 +1,3 @@
-# Starter pipeline
-# Start with a minimal pipeline that you can customize to build and deploy your code.
-# Add steps that build, run tests, deploy, and more:
-# https://aka.ms/yaml
-
-trigger:
-- master
-
 jobs:
 - job: Mac
   pool:
@@ -25,6 +17,7 @@ jobs:
   - script: system_profiler SPDisplaysDataType |grep Resolution
   
   - script: |
+      sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
       pkill node
       appium &
       pkill IntegrationTestApp

+ 5 - 0
packages/Avalonia/Avalonia.props

@@ -6,4 +6,9 @@
     <AvaloniaUseExternalMSBuild>false</AvaloniaUseExternalMSBuild>
   </PropertyGroup>
   <Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.props"/>
+
+  <!-- Allow loading the AvaloniaVS extension when referencing the Avalonia nuget package -->
+  <ItemGroup>
+    <ProjectCapability Include="Avalonia"/>
+  </ItemGroup>
 </Project>

+ 1 - 0
samples/ControlCatalog/ControlCatalog.csproj

@@ -29,6 +29,7 @@
     <ProjectReference Include="..\..\src\Avalonia.Controls.ItemsRepeater\Avalonia.Controls.ItemsRepeater.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
     <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
     <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
   </ItemGroup>

+ 4 - 4
samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs

@@ -16,10 +16,10 @@ namespace ControlCatalog.Pages
         private Point _cursorPoint;
 
 
-        public StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Scale), 1.0d);
+        public static readonly StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Scale), 1.0d);
         public double Scale { get => GetValue(ScaleProperty); set => SetValue(ScaleProperty, value); }
 
-        public StyledProperty<double> RotationProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Rotation));
+        public static readonly StyledProperty<double> RotationProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Rotation));
         /// <summary>
         /// Rotation, measured in Radians!
         /// </summary>
@@ -33,10 +33,10 @@ namespace ControlCatalog.Pages
             }
         }
 
-        public StyledProperty<double> ViewportCenterYProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(ViewportCenterY), 0.0d);
+        public static readonly StyledProperty<double> ViewportCenterYProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(ViewportCenterY), 0.0d);
         public double ViewportCenterY { get => GetValue(ViewportCenterYProperty); set => SetValue(ViewportCenterYProperty, value); }
 
-        public StyledProperty<double> ViewportCenterXProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(ViewportCenterX), 0.0d);
+        public static readonly StyledProperty<double> ViewportCenterXProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(ViewportCenterX), 0.0d);
         public double ViewportCenterX { get => GetValue(ViewportCenterXProperty); set => SetValue(ViewportCenterXProperty, value); }
 
         private IPen _pen;

+ 4 - 4
samples/ControlCatalog/Pages/PointerCanvas.cs

@@ -93,7 +93,7 @@ public class PointerCanvas : Control
     }
 
     private int _threadSleep;
-    public static DirectProperty<PointerCanvas, int> ThreadSleepProperty =
+    public static readonly DirectProperty<PointerCanvas, int> ThreadSleepProperty =
         AvaloniaProperty.RegisterDirect<PointerCanvas, int>(nameof(ThreadSleep), c => c.ThreadSleep, (c, v) => c.ThreadSleep = v);
 
     public int ThreadSleep
@@ -103,7 +103,7 @@ public class PointerCanvas : Control
     }
 
     private bool _drawOnlyPoints;
-    public static DirectProperty<PointerCanvas, bool> DrawOnlyPointsProperty =
+    public static readonly DirectProperty<PointerCanvas, bool> DrawOnlyPointsProperty =
         AvaloniaProperty.RegisterDirect<PointerCanvas, bool>(nameof(DrawOnlyPoints), c => c.DrawOnlyPoints, (c, v) => c.DrawOnlyPoints = v);
 
     public bool DrawOnlyPoints
@@ -113,8 +113,8 @@ public class PointerCanvas : Control
     }
 
     private string? _status;
-    public static DirectProperty<PointerCanvas, string?> StatusProperty =
-        AvaloniaProperty.RegisterDirect<PointerCanvas, string?>(nameof(DrawOnlyPoints), c => c.Status, (c, v) => c.Status = v,
+    public static readonly DirectProperty<PointerCanvas, string?> StatusProperty =
+        AvaloniaProperty.RegisterDirect<PointerCanvas, string?>(nameof(Status), c => c.Status, (c, v) => c.Status = v,
             defaultBindingMode: Avalonia.Data.BindingMode.TwoWay);
 
     public string? Status

+ 1 - 0
samples/IntegrationTestApp/IntegrationTestApp.csproj

@@ -19,6 +19,7 @@
   <ItemGroup>
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
   </ItemGroup>
   
   <Import Project="..\..\build\BuildTargets.targets" />

+ 1 - 0
samples/IntegrationTestApp/MainWindow.axaml

@@ -25,6 +25,7 @@
     <StackPanel DockPanel.Dock="Bottom" Margin="4" Orientation="Horizontal">
       <TextBlock Margin="0,0,4,0">WindowState:</TextBlock>
       <TextBlock Name="MainWindowState" Text="{Binding WindowState}"/>
+      <TextBlock Name="AppOverlayPopups" Margin="8 0"/>
     </StackPanel>
     
     <TabControl TabStripPlacement="Left" Name="MainTabs">

+ 7 - 5
samples/IntegrationTestApp/MainWindow.axaml.cs

@@ -1,19 +1,17 @@
-using System;
 using System.Collections.Generic;
 using System.Linq;
 using Avalonia;
 using Avalonia.Automation;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Primitives.PopupPositioning;
 using Avalonia.Input;
 using Avalonia.Interactivity;
-using Avalonia.Media;
 using Avalonia.Markup.Xaml;
+using Avalonia.Media;
 using Avalonia.VisualTree;
 using Microsoft.CodeAnalysis;
-using Avalonia.Controls.Primitives;
-using Avalonia.Threading;
-using Avalonia.Controls.Primitives.PopupPositioning;
 
 namespace IntegrationTestApp
 {
@@ -25,6 +23,10 @@ namespace IntegrationTestApp
             InitializeViewMenu();
             InitializeGesturesTab();
             this.AttachDevTools();
+
+            var overlayPopups = this.Get<TextBlock>("AppOverlayPopups");
+            overlayPopups.Text = Program.OverlayPopups ? "Overlay Popups" : "Native Popups";
+
             AddHandler(Button.ClickEvent, OnButtonClick);
             ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList();
             DataContext = this;

+ 18 - 4
samples/IntegrationTestApp/Program.cs

@@ -1,17 +1,31 @@
 using System;
+using System.Linq;
 using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Controls.ApplicationLifetimes;
 
 namespace IntegrationTestApp
 {
     class Program
     {
+        public static bool OverlayPopups { get; private set; }
+
         // Initialization code. Don't use any Avalonia, third-party APIs or any
         // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
         // yet and stuff might break.
-        public static void Main(string[] args) => BuildAvaloniaApp()
-            .StartWithClassicDesktopLifetime(args);
+        public static void Main(string[] args)
+        {
+            OverlayPopups = args.Contains("--overlayPopups");
+            
+            BuildAvaloniaApp()
+                .With(new Win32PlatformOptions
+                {
+                    OverlayPopups = OverlayPopups,
+                })
+                .With(new AvaloniaNativePlatformOptions
+                {
+                    OverlayPopups = OverlayPopups,
+                })
+                .StartWithClassicDesktopLifetime(args);
+        }
 
         // Avalonia configuration, don't remove; also used by visual designer.
         public static AppBuilder BuildAvaloniaApp()

+ 2 - 3
src/Avalonia.Base/Input/AccessKeyHandler.cs

@@ -1,9 +1,8 @@
 using System;
 using System.Collections.Generic;
-using System.Globalization;
 using System.Linq;
 using Avalonia.Interactivity;
-using Avalonia.VisualTree;
+using Avalonia.LogicalTree;
 
 namespace Avalonia.Input
 {
@@ -190,7 +189,7 @@ namespace Avalonia.Input
                 // If the menu is open, only match controls in the menu's visual tree.
                 if (menuIsOpen)
                 {
-                    matches = matches.Where(x => x is not null && ((Visual)MainMenu!).IsVisualAncestorOf((Visual)x));
+                    matches = matches.Where(x => x is not null && ((Visual)MainMenu!).IsLogicalAncestorOf((Visual)x));
                 }
 
                 var match = matches.FirstOrDefault();

+ 0 - 1
src/Avalonia.Base/Media/DrawingContext.cs

@@ -277,7 +277,6 @@ namespace Avalonia.Media
         private readonly record struct RestoreState : IDisposable
         {
             private readonly DrawingContext _context;
-            private readonly Matrix _matrix;
             private readonly PushedStateType _type;
 
             public enum PushedStateType

+ 0 - 2
src/Avalonia.Base/Media/DrawingGroup.cs

@@ -107,8 +107,6 @@ namespace Avalonia.Media
             private readonly DrawingGroup _drawingGroup;
             private readonly IPlatformRenderInterface _platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
 
-            private Matrix _transform;
-
             private bool _disposed;
 
             // Root drawing created by this DrawingContext.

+ 2 - 2
src/Avalonia.Base/Media/GeometryDrawing.cs

@@ -27,8 +27,8 @@ namespace Avalonia.Media
         /// <summary>
         /// Defines the <see cref="Pen"/> property.
         /// </summary>
-        public static readonly StyledProperty<Pen?> PenProperty =
-            AvaloniaProperty.Register<GeometryDrawing, Pen?>(nameof(Pen));
+        public static readonly StyledProperty<IPen?> PenProperty =
+            AvaloniaProperty.Register<GeometryDrawing, IPen?>(nameof(Pen));
 
         /// <summary>
         /// Gets or sets the <see cref="Avalonia.Media.Geometry"/> that describes the shape of this <see cref="GeometryDrawing"/>.

+ 7 - 7
src/Avalonia.Base/Media/GlyphRun.cs

@@ -151,9 +151,9 @@ namespace Avalonia.Media
         }
 
         /// <summary>
-        ///     Gets or sets the conservative bounding box of the <see cref="GlyphRun"/>.
+        ///     Gets the conservative bounding box of the <see cref="GlyphRun"/>.
         /// </summary>
-        public Size Size => PlatformImpl.Item.Size;
+        public Rect Bounds => PlatformImpl.Item.Bounds;
 
         /// <summary>
         /// 
@@ -252,7 +252,7 @@ namespace Avalonia.Media
 
                 if (characterIndex > Metrics.LastCluster)
                 {
-                    return Size.Width;
+                    return Bounds.Width;
                 }
 
                 var glyphIndex = FindGlyphIndex(characterIndex);
@@ -287,7 +287,7 @@ namespace Avalonia.Media
 
                 if (characterIndex <= Metrics.FirstCluster)
                 {
-                    return Size.Width;
+                    return Bounds.Width;
                 }
 
                 for (var i = glyphIndex + 1; i < _glyphInfos.Count; i++)
@@ -295,7 +295,7 @@ namespace Avalonia.Media
                     distance += _glyphInfos[i].GlyphAdvance;
                 }
 
-                return Size.Width - distance;
+                return Bounds.Width - distance;
             }
         }
 
@@ -321,7 +321,7 @@ namespace Avalonia.Media
             }
 
             //After
-            if (distance >= Size.Width)
+            if (distance >= Bounds.Width)
             {
                 isInside = false;
 
@@ -354,7 +354,7 @@ namespace Avalonia.Media
             }
             else
             {
-                currentX = Size.Width;
+                currentX = Bounds.Width;
 
                 for (var index = _glyphInfos.Count - 1; index >= 0; index--)
                 {

+ 1 - 1
src/Avalonia.Base/Media/GlyphRunDrawing.cs

@@ -32,7 +32,7 @@
 
         public override Rect GetBounds()
         {
-            return GlyphRun != null ? new Rect(GlyphRun.Size) : default;
+            return GlyphRun != null ? GlyphRun.Bounds : default;
         }
     }
 }

+ 1 - 2
src/Avalonia.Base/Media/PolyLineSegment.cs

@@ -1,5 +1,4 @@
 using System.Collections.Generic;
-using Avalonia.Collections;
 
 namespace Avalonia.Media
 {
@@ -20,7 +19,7 @@ namespace Avalonia.Media
         /// <value>
         /// The points.
         /// </value>
-        public AvaloniaList<Point> Points
+        public Points Points
         {
             get => GetValue(PointsProperty);
             set => SetValue(PointsProperty, value);

+ 1 - 1
src/Avalonia.Base/Media/TextDecoration.cs

@@ -223,7 +223,7 @@ namespace Avalonia.Media
                 if (intersections.Count > 0)
                 {
                     var last = baselineOrigin.X;
-                    var finalPos = last + glyphRun.Size.Width;
+                    var finalPos = last + glyphRun.Bounds.Width;
                     var end = last;
 
                     var points = new List<double>();

+ 3 - 3
src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs

@@ -89,13 +89,13 @@ namespace Avalonia.Media.TextFormatting
 
                         var offset = Math.Max(0, currentPosition - glyphRun.Metrics.FirstCluster);
                         var glyphIndex = glyphRun.FindGlyphIndex(characterIndex - offset);
-                        var glyphInfo = shapedBuffer.GlyphInfos[glyphIndex];
+                        var glyphInfo = shapedBuffer[glyphIndex];
 
-                        shapedBuffer.GlyphInfos[glyphIndex] = new GlyphInfo(glyphInfo.GlyphIndex,
+                        shapedBuffer[glyphIndex] = new GlyphInfo(glyphInfo.GlyphIndex,
                             glyphInfo.GlyphCluster, glyphInfo.GlyphAdvance + spacing);
                     }
 
-                    glyphRun.GlyphInfos = shapedBuffer.GlyphInfos;
+                    glyphRun.GlyphInfos = shapedBuffer;
                 }
 
                 currentPosition += textRun.Length;

+ 62 - 36
src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs

@@ -2,6 +2,7 @@
 using System.Buffers;
 using System.Collections;
 using System.Collections.Generic;
+using System.Runtime.CompilerServices;
 using Avalonia.Utilities;
 
 namespace Avalonia.Media.TextFormatting
@@ -9,12 +10,13 @@ namespace Avalonia.Media.TextFormatting
     public sealed class ShapedBuffer : IReadOnlyList<GlyphInfo>, IDisposable
     {
         private GlyphInfo[]? _rentedBuffer;
+        private ArraySlice<GlyphInfo> _glyphInfos;
 
         public ShapedBuffer(ReadOnlyMemory<char> text, int bufferLength, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
         {
-            _rentedBuffer = ArrayPool<GlyphInfo>.Shared.Rent(bufferLength);
             Text = text;
-            GlyphInfos = new ArraySlice<GlyphInfo>(_rentedBuffer, 0, bufferLength);
+            _rentedBuffer = ArrayPool<GlyphInfo>.Shared.Rent(bufferLength);
+            _glyphInfos = new ArraySlice<GlyphInfo>(_rentedBuffer, 0, bufferLength);      
             GlyphTypeface = glyphTypeface;
             FontRenderingEmSize = fontRenderingEmSize;
             BidiLevel = bidiLevel;
@@ -23,27 +25,70 @@ namespace Avalonia.Media.TextFormatting
         internal ShapedBuffer(ReadOnlyMemory<char> text, ArraySlice<GlyphInfo> glyphInfos, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
         {
             Text = text;
-            GlyphInfos = glyphInfos;
+            _glyphInfos = glyphInfos;
             GlyphTypeface = glyphTypeface;
             FontRenderingEmSize = fontRenderingEmSize;
             BidiLevel = bidiLevel;
         }
 
-        internal ArraySlice<GlyphInfo> GlyphInfos { get; private set; }
-
-        public int Length
-            => GlyphInfos.Length;
+        /// <summary>
+        /// The buffer's length.
+        /// </summary>
+        public int Length => _glyphInfos.Length;
 
+        /// <summary>
+        /// The buffer's glyph typeface.
+        /// </summary>
         public IGlyphTypeface GlyphTypeface { get; }
 
+        /// <summary>
+        /// The buffers font rendering em size.
+        /// </summary>
         public double FontRenderingEmSize { get; }
 
+        /// <summary>
+        /// The buffer's bidi level.
+        /// </summary>
         public sbyte BidiLevel { get; }
 
+        /// <summary>
+        /// The buffer's reading direction.
+        /// </summary>
         public bool IsLeftToRight => (BidiLevel & 1) == 0;
 
+        /// <summary>
+        /// The text that is represended by this buffer.
+        /// </summary>
         public ReadOnlyMemory<char> Text { get; }
         
+        /// <summary>
+        /// Reverses the buffer.
+        /// </summary>
+        public void Reverse()
+        {
+            _glyphInfos.Span.Reverse();
+        }
+
+        public void Dispose()
+        {
+            if (_rentedBuffer is not null)
+            {
+                ArrayPool<GlyphInfo>.Shared.Return(_rentedBuffer);
+                _rentedBuffer = null;
+                _glyphInfos = ArraySlice<GlyphInfo>.Empty; // ensure we don't misuse the returned array
+            }
+        }
+
+        public GlyphInfo this[int index]
+        {
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            get => _glyphInfos[index];
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            set => _glyphInfos[index] = value;
+        }
+
+        public IEnumerator<GlyphInfo> GetEnumerator() => _glyphInfos.GetEnumerator();
+
         /// <summary>
         /// Finds a glyph index for given character index.
         /// </summary>
@@ -53,20 +98,19 @@ namespace Avalonia.Media.TextFormatting
         /// </returns>
         private int FindGlyphIndex(int characterIndex)
         {
-            if (characterIndex < GlyphInfos[0].GlyphCluster)
+            if (characterIndex < _glyphInfos[0].GlyphCluster)
             {
                 return 0;
             }
 
-            if (characterIndex > GlyphInfos[GlyphInfos.Length - 1].GlyphCluster)
+            if (characterIndex > _glyphInfos[_glyphInfos.Length - 1].GlyphCluster)
             {
-                return GlyphInfos.Length - 1;
+                return _glyphInfos.Length - 1;
             }
 
-
             var comparer = GlyphInfo.ClusterAscendingComparer;
 
-            var glyphInfos = GlyphInfos.Span;
+            var glyphInfos = _glyphInfos.Span;
 
             var searchValue = new GlyphInfo(default, characterIndex, default);
 
@@ -109,42 +153,24 @@ namespace Avalonia.Media.TextFormatting
                 return new SplitResult<ShapedBuffer>(this, null);
             }
 
-            var firstCluster = GlyphInfos[0].GlyphCluster;
-            var lastCluster = GlyphInfos[GlyphInfos.Length - 1].GlyphCluster;
+            var firstCluster = _glyphInfos[0].GlyphCluster;
+            var lastCluster = _glyphInfos[_glyphInfos.Length - 1].GlyphCluster;
 
             var start = firstCluster < lastCluster ? firstCluster : lastCluster;
 
             var glyphCount = FindGlyphIndex(start + length);
 
             var first = new ShapedBuffer(Text.Slice(0, length),
-                GlyphInfos.Take(glyphCount), GlyphTypeface, FontRenderingEmSize, BidiLevel);
+                _glyphInfos.Take(glyphCount), GlyphTypeface, FontRenderingEmSize, BidiLevel);
 
             var second = new ShapedBuffer(Text.Slice(length),
-                GlyphInfos.Skip(glyphCount), GlyphTypeface, FontRenderingEmSize, BidiLevel);
+                _glyphInfos.Skip(glyphCount), GlyphTypeface, FontRenderingEmSize, BidiLevel);
 
             return new SplitResult<ShapedBuffer>(first, second);
         }
 
-        int IReadOnlyCollection<GlyphInfo>.Count => GlyphInfos.Length;
-
-        public GlyphInfo this[int index]
-        {
-            get => GlyphInfos[index];
-            set => GlyphInfos[index] = value;
-        }
-
-        public IEnumerator<GlyphInfo> GetEnumerator() => GlyphInfos.GetEnumerator();
+        int IReadOnlyCollection<GlyphInfo>.Count => _glyphInfos.Length;
 
-        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
-
-        public void Dispose()
-        {
-            if (_rentedBuffer is not null)
-            {
-                ArrayPool<GlyphInfo>.Shared.Return(_rentedBuffer);
-                _rentedBuffer = null;
-                GlyphInfos = ArraySlice<GlyphInfo>.Empty; // ensure we don't misuse the returned array
-            }
-        }
+        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();       
     }
 }

+ 5 - 5
src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs

@@ -38,14 +38,14 @@ namespace Avalonia.Media.TextFormatting
 
         public override double Baseline => -TextMetrics.Ascent;
 
-        public override Size Size => GlyphRun.Size;
+        public override Size Size => GlyphRun.Bounds.Size;
 
         public GlyphRun GlyphRun => _glyphRun ??= CreateGlyphRun();
 
         /// <inheritdoc/>
         public override void Draw(DrawingContext drawingContext, Point origin)
         {
-            using (drawingContext.PushPreTransform(Matrix.CreateTranslation(origin)))
+            using (drawingContext.PushTransform(Matrix.CreateTranslation(origin)))
             {
                 if (GlyphRun.GlyphInfos.Count == 0)
                 {
@@ -85,7 +85,7 @@ namespace Avalonia.Media.TextFormatting
         {
             _glyphRun = null;
 
-            ShapedBuffer.GlyphInfos.Span.Reverse();
+            ShapedBuffer.Reverse();
 
             IsReversed = !IsReversed;
         }
@@ -106,7 +106,7 @@ namespace Avalonia.Media.TextFormatting
 
             for (var i = 0; i < ShapedBuffer.Length; i++)
             {
-                var advance = ShapedBuffer.GlyphInfos[i].GlyphAdvance;
+                var advance = ShapedBuffer[i].GlyphAdvance;
 
                 if (currentWidth + advance > availableWidth)
                 {
@@ -130,7 +130,7 @@ namespace Avalonia.Media.TextFormatting
 
             for (var i = ShapedBuffer.Length - 1; i >= 0; i--)
             {
-                var advance = ShapedBuffer.GlyphInfos[i].GlyphAdvance;
+                var advance = ShapedBuffer[i].GlyphAdvance;
 
                 if (width + advance > availableWidth)
                 {

+ 1 - 1
src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs

@@ -19,7 +19,7 @@ namespace Avalonia.Media.TextFormatting
             var collapsedLength = 0;
             var shapedSymbol = TextFormatterImpl.CreateSymbol(properties.Symbol, FlowDirection.LeftToRight);
 
-            if (properties.Width < shapedSymbol.GlyphRun.Size.Width)
+            if (properties.Width < shapedSymbol.GlyphRun.Bounds.Width)
             {
                 //Not enough space to fit in the symbol
                 return Array.Empty<TextRun>();

+ 1 - 1
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@@ -583,7 +583,7 @@ namespace Avalonia.Media.TextFormatting
                         {
                             if (shapedTextCharacters.ShapedBuffer.Length > 0)
                             {
-                                var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphInfos[0].GlyphCluster;
+                                var firstCluster = shapedTextCharacters.ShapedBuffer[0].GlyphCluster;
                                 var lastCluster = firstCluster;
 
                                 for (var j = 0; j < shapedTextCharacters.ShapedBuffer.Length; j++)

+ 1 - 1
src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs

@@ -60,7 +60,7 @@ namespace Avalonia.Media.TextFormatting
             var currentWidth = 0.0;
             var shapedSymbol = TextFormatterImpl.CreateSymbol(Symbol, FlowDirection.LeftToRight);
 
-            if (Width < shapedSymbol.GlyphRun.Size.Width)
+            if (Width < shapedSymbol.GlyphRun.Bounds.Width)
             {
                 return Array.Empty<TextRun>();
             }

+ 3 - 3
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@@ -423,7 +423,7 @@ namespace Avalonia.Media.TextFormatting
                     {
                         if (currentGlyphRun != null)
                         {
-                            currentDistance -= currentGlyphRun.Size.Width;
+                            currentDistance -= currentGlyphRun.Bounds.Width;
                         }
 
                         return currentDistance + distance;
@@ -477,7 +477,7 @@ namespace Avalonia.Media.TextFormatting
                         {
                             if (currentGlyphRun.IsLeftToRight || flowDirection == FlowDirection.RightToLeft)
                             {
-                                distance = currentGlyphRun.Size.Width;
+                                distance = currentGlyphRun.Bounds.Width;
                             }
 
                             return true;
@@ -1483,7 +1483,7 @@ namespace Avalonia.Media.TextFormatting
 
                     trailingWhitespaceLength += glyphRunMetrics.TrailingWhitespaceLength;
 
-                    var whitespaceWidth = glyphRun.Size.Width - glyphRunMetrics.Width;
+                    var whitespaceWidth = glyphRun.Bounds.Width - glyphRunMetrics.Width;
 
                     width -= whitespaceWidth;
                 }

+ 104 - 51
src/Avalonia.Base/Platform/AssetLoader.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Linq;
 using System.Reflection;
@@ -62,7 +63,7 @@ namespace Avalonia.Platform
         /// <returns>True if the asset could be found; otherwise false.</returns>
         public bool Exists(Uri uri, Uri? baseUri = null)
         {
-            return GetAsset(uri, baseUri) != null;
+            return TryGetAsset(uri, baseUri, out _);
         }
 
         /// <summary>
@@ -94,21 +95,27 @@ namespace Avalonia.Platform
         /// </exception>
         public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null)
         {
-            var asset = GetAsset(uri, baseUri);
-
-            if (asset == null)
+            if (TryGetAsset(uri, baseUri, out var assetDescriptor))
             {
-                throw new FileNotFoundException($"The resource {uri} could not be found.");
+                return (assetDescriptor.GetStream(), assetDescriptor.Assembly);
             }
 
-            return (asset.GetStream(), asset.Assembly);
+            throw new FileNotFoundException($"The resource {uri} could not be found.");
         }
 
         public Assembly? GetAssembly(Uri uri, Uri? baseUri)
         {
             if (!uri.IsAbsoluteUri && baseUri != null)
+            {
                 uri = new Uri(baseUri, uri);
-            return GetAssembly(uri)?.Assembly;
+            }
+
+            if (TryGetAssembly(uri, out var assemblyDescriptor))
+            {
+                return assemblyDescriptor.Assembly;
+            }
+
+            return null;
         }
 
         /// <summary>
@@ -121,99 +128,145 @@ namespace Avalonia.Platform
         {
             if (uri.IsAbsoluteResm())
             {
-                var assembly = GetAssembly(uri);
+                if (!TryGetAssembly(uri, out var assembly))
+                {
+                    assembly = _defaultResmAssembly;
+                }
 
                 return assembly?.Resources?
-                           .Where(x => x.Key.IndexOf(uri.GetUnescapeAbsolutePath(), StringComparison.Ordinal) >= 0)
-                           .Select(x =>new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
-                       Enumerable.Empty<Uri>();
+                        .Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath()))
+                        .Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
+                    Enumerable.Empty<Uri>();
             }
 
             uri = uri.EnsureAbsolute(baseUri);
+
             if (uri.IsAvares())
             {
-                var (asm, path) = GetResAsmAndPath(uri);
-                if (asm == null)
+                if (!TryGetResAsmAndPath(uri, out var assembly, out var path))
                 {
-                    throw new ArgumentException(
-                        "No default assembly, entry assembly or explicit assembly specified; " +
-                        "don't know where to look up for the resource, try specifying assembly explicitly.");
+                    return Enumerable.Empty<Uri>();
                 }
 
-                if (asm.AvaloniaResources == null)
+                if (assembly?.AvaloniaResources == null)
+                {
                     return Enumerable.Empty<Uri>();
+                }
 
-                if (path[path.Length - 1] != '/')
+                if (path.Length > 0 && path[path.Length - 1] != '/')
+                {
                     path += '/';
+                }
 
-                return asm.AvaloniaResources
+                return assembly.AvaloniaResources
                     .Where(r => r.Key.StartsWith(path, StringComparison.Ordinal))
-                    .Select(x => new Uri($"avares://{asm.Name}{x.Key}"));
+                    .Select(x => new Uri($"avares://{assembly.Name}{x.Key}"));
             }
 
             return Enumerable.Empty<Uri>();
         }
-        
-        private IAssetDescriptor? GetAsset(Uri uri, Uri? baseUri)
-        {           
+
+        private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor)
+        {
+            assetDescriptor = null;
+
             if (uri.IsAbsoluteResm())
             {
-                var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultResmAssembly;
-
-                if (asm == null)
+                if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly))
                 {
-                    throw new ArgumentException(
-                        "No default assembly, entry assembly or explicit assembly specified; " +
-                        "don't know where to look up for the resource, try specifying assembly explicitly.");
+                    assembly = _defaultResmAssembly;
                 }
 
-                var resourceKey = uri.AbsolutePath;
-                IAssetDescriptor? rv = null;
-                asm.Resources?.TryGetValue(resourceKey, out rv);
-                return rv;
+                if (assembly?.Resources != null)
+                {
+                    var resourceKey = uri.AbsolutePath;
+
+                    if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor))
+                    {
+                        return true;
+                    }
+                }
             }
 
             uri = uri.EnsureAbsolute(baseUri);
 
             if (uri.IsAvares())
             {
-                var (asm, path) = GetResAsmAndPath(uri);
-                if (asm.AvaloniaResources == null)
-                    return null;
-                asm.AvaloniaResources.TryGetValue(path, out var desc);
-                return desc;
+                if (TryGetResAsmAndPath(uri, out var assembly, out var path))
+                {
+                    if (assembly.AvaloniaResources == null)
+                    {
+                        return false;
+                    }
+
+                    if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor))
+                    {
+                        return true;
+                    }
+                }
             }
 
-            throw new ArgumentException($"Unsupported url type: " + uri.Scheme, nameof(uri));
+            return false;
         }
 
-        private static (IAssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri)
+        private static bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path)
         {
-            var asm = s_assemblyDescriptorResolver.GetAssembly(uri.Authority);
-            return (asm, uri.GetUnescapeAbsolutePath());
+            path = uri.GetUnescapeAbsolutePath();
+
+            if (TryLoadAssembly(uri.Authority, out assembly))
+            {
+                return true;
+            }
+
+            return false;
         }
-        
-        private static IAssemblyDescriptor? GetAssembly(Uri? uri)
+
+        private static bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
         {
+            assembly = null;
+
             if (uri != null)
             {
                 if (!uri.IsAbsoluteUri)
-                    return null;
-                if (uri.IsAvares())
-                    return GetResAsmAndPath(uri).asm;
+                {
+                    return false;
+                }
+
+                if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _))
+                {
+                    return true;
+                }
 
                 if (uri.IsResm())
                 {
                     var assemblyName = uri.GetAssemblyNameFromQuery();
-                    if (assemblyName.Length > 0)
-                        return s_assemblyDescriptorResolver.GetAssembly(assemblyName);
+
+                    if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly))
+                    {
+                        return true;
+                    }
                 }
             }
 
-            return null;
+            return false;
+        }
+
+        private static bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
+        {
+            assembly = null;
+
+            try
+            {
+                assembly = s_assemblyDescriptorResolver.GetAssembly(assemblyName);
+
+                return true;
+            }
+            catch (Exception) { }
+
+            return false;
         }
 #endif
-        
+
         public static void RegisterResUriParsers()
         {
             if (!UriParser.IsKnownScheme("avares"))

+ 1 - 2
src/Avalonia.Base/Platform/IGlyphRunImpl.cs

@@ -10,11 +10,10 @@ namespace Avalonia.Platform
     [Unstable]
     public interface IGlyphRunImpl : IDisposable 
     {
-
         /// <summary>
         ///     Gets the conservative bounding box of the glyph run./>.
         /// </summary>
-        Size Size { get; }
+        Rect Bounds { get; }
 
         /// <summary>
         ///     Gets the baseline origin of the glyph run./>.

+ 1 - 1
src/Avalonia.Base/PropertyStore/ValueStore.cs

@@ -924,7 +924,7 @@ namespace Avalonia.PropertyStore
                 {
                     _effectiveValues.GetKeyValue(i, out var key, out var e);
 
-                    if (e.Priority == BindingPriority.Unset)
+                    if (e.Priority == BindingPriority.Unset && !e.IsOverridenCurrentValue)
                     {
                         RemoveEffectiveValue(key, i);
                         e.DisposeAndRaiseUnset(this, key);

+ 1 - 1
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@@ -225,7 +225,7 @@ public class CompositingRenderer : IRendererWithCompositor
             sortedChildren.Dispose();
         }
         else
-            foreach (var ch in v.GetVisualChildren())
+            foreach (var ch in visualChildren)
             {
                 var compositionChild = ch.CompositionVisual;
                 if (compositionChild != null)

+ 4 - 4
src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs

@@ -20,7 +20,7 @@ namespace Avalonia.Rendering.Composition.Server
 
             for (var c = FirstChar; c <= LastChar; c++)
             {
-                var height = _runs[c - FirstChar].Size.Height;
+                var height = _runs[c - FirstChar].Bounds.Height;
                 if (height > maxHeight)
                 {
                     maxHeight = height;
@@ -51,8 +51,8 @@ namespace Avalonia.Rendering.Composition.Server
             {
                 var effectiveChar = c is >= FirstChar and <= LastChar ? c : ' ';
                 var run = _runs[effectiveChar - FirstChar];
-                width += run.Size.Width;
-                height = Math.Max(height, run.Size.Height);
+                width += run.Bounds.Width;
+                height = Math.Max(height, run.Bounds.Height);
             }
 
             return new Size(width, height);
@@ -69,7 +69,7 @@ namespace Avalonia.Rendering.Composition.Server
                 var run = _runs[effectiveChar - FirstChar];
                 context.Transform = originalTransform * Matrix.CreateTranslation(offset, 0.0);
                 context.DrawGlyphRun(foreground, run.PlatformImpl);
-                offset += run.Size.Width;
+                offset += run.Bounds.Width;
             }
 
             context.Transform = originalTransform;

+ 2 - 3
src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs

@@ -16,12 +16,11 @@ namespace Avalonia.Rendering.SceneGraph
         /// <param name="transform">The transform.</param>
         /// <param name="foreground">The foreground brush.</param>
         /// <param name="glyphRun">The glyph run to draw.</param>
-        /// <param name="aux">Auxiliary data required to draw the brush.</param>
         public GlyphRunNode(
             Matrix transform,
             IImmutableBrush foreground,
             IRef<IGlyphRunImpl> glyphRun)
-            : base(new Rect(glyphRun.Item.Size), transform, foreground)
+            : base(glyphRun.Item.Bounds, transform, foreground)
         {
             GlyphRun = glyphRun.Clone();
         }
@@ -54,7 +53,7 @@ namespace Avalonia.Rendering.SceneGraph
         }
 
         /// <inheritdoc/>
-        public override bool HitTest(Point p) => new Rect(GlyphRun.Item.Size).ContainsExclusive(p);
+        public override bool HitTest(Point p) => GlyphRun.Item.Bounds.ContainsExclusive(p);
 
         public override void Dispose()
         {

+ 15 - 6
src/Avalonia.Base/StyledElement.cs

@@ -803,8 +803,11 @@ namespace Avalonia
 
             if (theme.HasChildren)
             {
-                foreach (var child in theme.Children)
-                    ApplyStyle(child, null, type);
+                var children = theme.Children;
+                for (var i = 0; i < children.Count; i++)
+                {
+                    ApplyStyle(children[i], null, type);
+                }
             }
         }
 
@@ -816,8 +819,11 @@ namespace Avalonia
             
             if (host.IsStylesInitialized)
             {
-                foreach (var style in host.Styles)
-                    ApplyStyle(style, host, FrameType.Style);
+                var styles = host.Styles;
+                for (var i = 0; i < styles.Count; ++i)
+                {
+                    ApplyStyle(styles[i], host, FrameType.Style);
+                }
             }
         }
 
@@ -826,8 +832,11 @@ namespace Avalonia
             if (style is Style s)
                 s.TryAttach(this, host, type);
 
-            foreach (var child in style.Children)
-                ApplyStyle(child, host, type);
+            var children = style.Children;
+            for (var i = 0; i < children.Count; i++)
+            {
+                ApplyStyle(children[i], host, type);
+            }
         }
 
         private void ReevaluateImplicitTheme()

+ 8 - 5
src/Avalonia.Base/Styling/OrSelector.cs

@@ -71,9 +71,9 @@ namespace Avalonia.Styling
             var activators = new OrActivatorBuilder();
             var neverThisInstance = false;
 
-            foreach (var selector in _selectors)
+            for (var i = 0; i < _selectors.Count; i++)
             {
-                var match = selector.Match(control, parent, subscribe);
+                var match = _selectors[i].Match(control, parent, subscribe);
 
                 switch (match.Result)
                 {
@@ -108,16 +108,19 @@ namespace Avalonia.Styling
 
         internal override void ValidateNestingSelector(bool inControlTheme)
         {
-            foreach (var selector in _selectors)
-                selector.ValidateNestingSelector(inControlTheme);
+            for (var i = 0; i < _selectors.Count; i++)
+            {
+                _selectors[i].ValidateNestingSelector(inControlTheme);
+            }
         }
 
         private Type? EvaluateTargetType()
         {
             Type? result = null;
 
-            foreach (var selector in _selectors)
+            for (var i = 0; i < _selectors.Count; i++)
             {
+                var selector = _selectors[i];
                 if (selector.TargetType == null)
                 {
                     return null;

+ 7 - 8
src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using Avalonia.Controls;
 using Avalonia.Styling.Activators;
 using Avalonia.Utilities;
 
@@ -15,7 +14,7 @@ namespace Avalonia.Styling
     internal class TypeNameAndClassSelector : Selector
     {
         private readonly Selector? _previous;
-        private readonly Lazy<List<string>> _classes = new Lazy<List<string>>(() => new List<string>());
+        private List<string>? _classes;
         private Type? _targetType;
         private string? _selectorString;
 
@@ -81,7 +80,7 @@ namespace Avalonia.Styling
         /// <summary>
         /// The style classes which the selector matches.
         /// </summary>
-        public IList<string> Classes => _classes.Value;
+        public IList<string> Classes => _classes ??= new();
 
         /// <inheritdoc/>
         public override string ToString(Style? owner)
@@ -122,16 +121,16 @@ namespace Avalonia.Styling
                 return SelectorMatch.NeverThisInstance;
             }
 
-            if (_classes.IsValueCreated && _classes.Value.Count > 0)
+            if (_classes is { Count: > 0 })
             {
                 if (subscribe)
                 {
-                    var observable = new StyleClassActivator((Classes)control.Classes, _classes.Value);
+                    var observable = new StyleClassActivator(control.Classes, _classes);
 
                     return new SelectorMatch(observable);
                 }
 
-                if (!StyleClassActivator.AreClassesMatching(control.Classes, Classes))
+                if (!StyleClassActivator.AreClassesMatching(control.Classes, _classes))
                 {
                     return SelectorMatch.NeverThisInstance;
                 }
@@ -172,9 +171,9 @@ namespace Avalonia.Styling
                 builder.Append(Name);
             }
 
-            if (_classes.IsValueCreated && _classes.Value.Count > 0)
+            if (_classes is { Count: > 0 })
             {
-                foreach (var c in Classes)
+                foreach (var c in _classes)
                 {
                     if (!c.StartsWith(":"))
                     {

+ 3 - 7
src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs

@@ -304,13 +304,9 @@ namespace Avalonia.Utilities
             {
                 if (_entryCount == _entries!.Length)
                 {
-                    const double growthFactor = 1.2;
-                    var newSize = (int)(_entryCount * growthFactor);
-
-                    if (newSize == _entryCount)
-                    {
-                        newSize++;
-                    }
+                    var newSize = _entryCount == DefaultInitialCapacity ?
+                        DefaultInitialCapacity * 2 :
+                        (int)(_entryCount * 1.5);
 
                     var destEntries = new Entry[newSize];
 

+ 17 - 0
src/Avalonia.Base/Visual.cs

@@ -732,6 +732,23 @@ namespace Avalonia
             }
         }
 
+        internal override void OnTemplatedParentControlThemeChanged()
+        {
+            base.OnTemplatedParentControlThemeChanged();
+
+            var count = VisualChildren.Count;
+            var templatedParent = TemplatedParent;
+
+            for (var i = 0; i < count; ++i)
+            {
+                if (VisualChildren[i] is StyledElement child &&
+                    child.TemplatedParent == templatedParent)
+                {
+                    child.OnTemplatedParentControlThemeChanged();
+                }
+            }
+        }
+
         /// <summary>
         /// Computes the <see cref="HasMirrorTransform"/> value according to the 
         /// <see cref="FlowDirection"/> and <see cref="BypassFlowDirectionPolicies"/>

+ 1 - 1
src/Avalonia.Controls.DataGrid/Themes/Simple.xaml

@@ -202,7 +202,7 @@
 
       <Style Selector="^ /template/ Rectangle#BackgroundRectangle">
         <Setter Property="IsVisible" Value="False" />
-        <Setter Property="Fill" Value="{DynamicResource HighlightBrush}" />
+        <Setter Property="Fill" Value="{DynamicResource HighlightBrush2}" />
       </Style>
 
       <Style Selector="^:pointerover /template/ Rectangle#BackgroundRectangle">

+ 1 - 1
src/Avalonia.Controls.ItemsRepeater/Layout/StackLayout.cs

@@ -26,7 +26,7 @@ namespace Avalonia.Layout
         /// Defines the <see cref="Orientation"/> property.
         /// </summary>
         public static readonly StyledProperty<Orientation> OrientationProperty =
-            StackPanel.OrientationProperty.AddOwner<StackPanel>();
+            StackPanel.OrientationProperty.AddOwner<StackLayout>();
 
         /// <summary>
         /// Defines the <see cref="Spacing"/> property.

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

@@ -31,9 +31,6 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<string?> MaskProperty =
              AvaloniaProperty.Register<MaskedTextBox, string?>(nameof(Mask), string.Empty);
 
-        public static new readonly StyledProperty<char> PasswordCharProperty =
-             AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PasswordChar), '\0');
-
         public static readonly StyledProperty<char> PromptCharProperty =
              AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PromptChar), '_');
 
@@ -51,6 +48,12 @@ namespace Avalonia.Controls
 
         private bool _resetOnSpace = true;
 
+        static MaskedTextBox()
+        {
+            PasswordCharProperty
+                .OverrideDefaultValue<MaskedTextBox>('\0');
+        }
+
         public MaskedTextBox() { }
 
         /// <summary>

+ 20 - 14
src/Avalonia.Controls/MenuItem.cs

@@ -13,6 +13,7 @@ using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.LogicalTree;
+using Avalonia.Layout;
 
 namespace Avalonia.Controls
 {
@@ -85,16 +86,16 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="PointerEnteredItem"/> event.
         /// </summary>
-        public static readonly RoutedEvent<PointerEventArgs> PointerEnteredItemEvent =
-            RoutedEvent.Register<MenuItem, PointerEventArgs>(
+        public static readonly RoutedEvent<RoutedEventArgs> PointerEnteredItemEvent =
+            RoutedEvent.Register<MenuItem, RoutedEventArgs>(
                 nameof(PointerEnteredItem),
                 RoutingStrategies.Bubble);
 
         /// <summary>
         /// Defines the <see cref="PointerExitedItem"/> event.
         /// </summary>
-        public static readonly RoutedEvent<PointerEventArgs> PointerExitedItemEvent =
-            RoutedEvent.Register<MenuItem, PointerEventArgs>(
+        public static readonly RoutedEvent<RoutedEventArgs> PointerExitedItemEvent =
+            RoutedEvent.Register<MenuItem, RoutedEventArgs>(
                 nameof(PointerExitedItem),
                 RoutingStrategies.Bubble);
 
@@ -184,7 +185,7 @@ namespace Avalonia.Controls
         /// <remarks>
         /// A bubbling version of the <see cref="InputElement.PointerEntered"/> event for menu items.
         /// </remarks>
-        public event EventHandler<PointerEventArgs>? PointerEnteredItem
+        public event EventHandler<RoutedEventArgs>? PointerEnteredItem
         {
             add { AddHandler(PointerEnteredItemEvent, value); }
             remove { RemoveHandler(PointerEnteredItemEvent, value); }
@@ -196,7 +197,7 @@ namespace Avalonia.Controls
         /// <remarks>
         /// A bubbling version of the <see cref="InputElement.PointerExited"/> event for menu items.
         /// </remarks>
-        public event EventHandler<PointerEventArgs>? PointerExitedItem
+        public event EventHandler<RoutedEventArgs>? PointerExitedItem
         {
             add { AddHandler(PointerExitedItemEvent, value); }
             remove { RemoveHandler(PointerExitedItemEvent, value); }
@@ -437,20 +438,14 @@ namespace Avalonia.Controls
         protected override void OnPointerEntered(PointerEventArgs e)
         {
             base.OnPointerEntered(e);
-
-            var point = e.GetCurrentPoint(null);
-            RaiseEvent(new PointerEventArgs(PointerEnteredItemEvent, this, e.Pointer, (Visual?)VisualRoot, point.Position,
-                e.Timestamp, point.Properties, e.KeyModifiers));
+            RaiseEvent(new RoutedEventArgs(PointerEnteredItemEvent));
         }
 
         /// <inheritdoc/>
         protected override void OnPointerExited(PointerEventArgs e)
         {
             base.OnPointerExited(e);
-
-            var point = e.GetCurrentPoint(null);
-            RaiseEvent(new PointerEventArgs(PointerExitedItemEvent, this, e.Pointer, (Visual?)VisualRoot, point.Position,
-                e.Timestamp, point.Properties, e.KeyModifiers));
+            RaiseEvent(new RoutedEventArgs(PointerExitedItemEvent));
         }
 
         /// <summary>
@@ -686,6 +681,12 @@ namespace Avalonia.Controls
         /// <param name="e">The event args.</param>
         private void PopupOpened(object? sender, EventArgs e)
         {
+            // If we're using overlay popups, there's a chance we need to do a layout pass before
+            // the child items are added to the visual tree. If we don't do this here, then
+            // selection breaks.
+            if (Presenter?.IsAttachedToVisualTree == false)
+                UpdateLayout();
+
             var selected = SelectedIndex;
 
             if (selected != -1)
@@ -705,6 +706,11 @@ namespace Avalonia.Controls
             SelectedItem = null;
         }
 
+        private void UpdateLayout()
+        {
+            (VisualRoot as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
+        }
+
         void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e);
 
         void IClickableControl.RaiseClick()

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

@@ -137,7 +137,7 @@ namespace Avalonia.Controls
         /// Defines the <see cref="CommandParameter"/> property.
         /// </summary>
         public static readonly StyledProperty<object?> CommandParameterProperty =
-            Button.CommandParameterProperty.AddOwner<MenuItem>();
+            Button.CommandParameterProperty.AddOwner<NativeMenuItem>();
 
         public static readonly DirectProperty<NativeMenuItem, bool> IsEnabledProperty =
            AvaloniaProperty.RegisterDirect<NativeMenuItem, bool>(nameof(IsEnabled), o => o.IsEnabled, (o, v) => o.IsEnabled = v, true);

+ 24 - 11
src/Avalonia.Controls/Panel.cs

@@ -1,13 +1,12 @@
 using System;
-using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.ComponentModel;
 using System.Linq;
+using Avalonia.Controls.Presenters;
 using Avalonia.LogicalTree;
 using Avalonia.Media;
 using Avalonia.Metadata;
 using Avalonia.Reactive;
-using Avalonia.Styling;
 
 namespace Avalonia.Controls
 {
@@ -59,6 +58,11 @@ namespace Avalonia.Controls
             set { SetValue(BackgroundProperty, value); }
         }
 
+        /// <summary>
+        /// Gets whether the <see cref="Panel"/> hosts the items created by an <see cref="ItemsPresenter"/>.
+        /// </summary>
+        public bool IsItemsHost { get; internal set; }
+
         event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged
         {
             add
@@ -129,24 +133,29 @@ namespace Avalonia.Controls
         /// <param name="e">The event args.</param>
         protected virtual void ChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e)
         {
-            List<Control> controls;
-
             switch (e.Action)
             {
                 case NotifyCollectionChangedAction.Add:
-                    controls = e.NewItems!.OfType<Control>().ToList();
-                    LogicalChildren.InsertRange(e.NewStartingIndex, controls);
+                    if (!IsItemsHost)
+                    {
+                        LogicalChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType<Control>().ToList());
+                    }
                     VisualChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType<Visual>());
                     break;
 
                 case NotifyCollectionChangedAction.Move:
-                    LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
-                    VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
+                    if (!IsItemsHost)
+                    {
+                        LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
+                    }
+                    VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
                     break;
 
                 case NotifyCollectionChangedAction.Remove:
-                    controls = e.OldItems!.OfType<Control>().ToList();
-                    LogicalChildren.RemoveAll(controls);
+                    if (!IsItemsHost)
+                    {
+                        LogicalChildren.RemoveAll(e.OldItems!.OfType<Control>().ToList());
+                    }
                     VisualChildren.RemoveAll(e.OldItems!.OfType<Visual>());
                     break;
 
@@ -155,7 +164,10 @@ namespace Avalonia.Controls
                     {
                         var index = i + e.OldStartingIndex;
                         var child = (Control)e.NewItems![i]!;
-                        LogicalChildren[index] = child;
+                        if (!IsItemsHost)
+                        {
+                            LogicalChildren[index] = child;
+                        }
                         VisualChildren[index] = child;
                     }
                     break;
@@ -200,6 +212,7 @@ namespace Avalonia.Controls
             return child is Control control ? Children.IndexOf(control) : -1;
         }
 
+        /// <inheritdoc />
         public bool TryGetTotalCount(out int count)
         {
             count = Children.Count;

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

@@ -301,7 +301,7 @@ namespace Avalonia.Controls.Platform
             e.Handled = true;
         }
 
-        protected internal virtual void PointerEntered(object? sender, PointerEventArgs e)
+        protected internal virtual void PointerEntered(object? sender, RoutedEventArgs e)
         {
             var item = GetMenuItem(e.Source as Control);
 
@@ -368,7 +368,7 @@ namespace Avalonia.Controls.Platform
             }
         }
 
-        protected internal virtual void PointerExited(object? sender, PointerEventArgs e)
+        protected internal virtual void PointerExited(object? sender, RoutedEventArgs e)
         {
             var item = GetMenuItem(e.Source as Control);
 

+ 1 - 0
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@@ -167,6 +167,7 @@ namespace Avalonia.Controls.Presenters
 
                 Panel = ItemsPanel.Build();
                 Panel.SetValue(TemplatedParentProperty, TemplatedParent);
+                Panel.IsItemsHost = true;
                 _scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo;
                 LogicalChildren.Add(Panel);
                 VisualChildren.Add(Panel);

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

@@ -2,7 +2,6 @@ using System;
 using System.Collections.Specialized;
 using Avalonia.Media;
 using Avalonia.Reactive;
-using Avalonia.Rendering;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Controls.Primitives
@@ -38,7 +37,7 @@ namespace Avalonia.Controls.Primitives
         /// Defines the <see cref="DefaultFocusAdorner"/> property.
         /// </summary>
         public static readonly StyledProperty<ITemplate<Control>?> DefaultFocusAdornerProperty =
-            AvaloniaProperty.Register<Control, ITemplate<Control>?>(nameof(DefaultFocusAdorner));
+            AvaloniaProperty.Register<AdornerLayer, ITemplate<Control>?>(nameof(DefaultFocusAdorner));
         
         private static readonly AttachedProperty<AdornedElementInfo?> s_adornedElementInfoProperty =
             AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, AdornedElementInfo?>("AdornedElementInfo");

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

@@ -51,7 +51,7 @@ namespace Avalonia.Controls.Primitives
         }
 
         /// <inheritdoc />
-        protected internal override Interactive? InteractiveParent => (Interactive?)VisualParent;
+        protected internal override Interactive? InteractiveParent => Parent as Interactive;
 
         /// <inheritdoc />
         public void Dispose() => Hide();

+ 0 - 17
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@@ -405,22 +405,5 @@ namespace Avalonia.Controls.Primitives
                 }
             }
         }
-
-        internal override void OnTemplatedParentControlThemeChanged()
-        {
-            base.OnTemplatedParentControlThemeChanged();
-
-            var count = VisualChildren.Count;
-            var templatedParent = TemplatedParent;
-
-            for (var i = 0; i < count; ++i)
-            {
-                if (VisualChildren[i] is TemplatedControl child &&
-                    child.TemplatedParent == templatedParent)
-                {
-                    child.OnTemplatedParentControlThemeChanged();
-                }
-            }
-        }
     }
 }

+ 3 - 3
src/Avalonia.Controls/PullToRefresh/RefreshInfoProvider.cs

@@ -17,16 +17,16 @@ namespace Avalonia.Controls.PullToRefresh
         private double _interactionRatio;
         private bool _entered;
 
-        public DirectProperty<RefreshInfoProvider, bool> IsInteractingForRefreshProperty =
+        public static readonly  DirectProperty<RefreshInfoProvider, bool> IsInteractingForRefreshProperty =
             AvaloniaProperty.RegisterDirect<RefreshInfoProvider, bool>(nameof(IsInteractingForRefresh),
                 s => s.IsInteractingForRefresh, (s, o) => s.IsInteractingForRefresh = o);
 
 
-        public DirectProperty<RefreshInfoProvider, double> ExecutionRatioProperty =
+        public static readonly DirectProperty<RefreshInfoProvider, double> ExecutionRatioProperty =
             AvaloniaProperty.RegisterDirect<RefreshInfoProvider, double>(nameof(ExecutionRatio),
                 s => s.ExecutionRatio);
 
-        public DirectProperty<RefreshInfoProvider, double> InteractionRatioProperty =
+        public static readonly DirectProperty<RefreshInfoProvider, double> InteractionRatioProperty =
             AvaloniaProperty.RegisterDirect<RefreshInfoProvider, double>(nameof(InteractionRatio),
                 s => s.InteractionRatio, (s, o) => s.InteractionRatio = o);
 

+ 1 - 1
src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs

@@ -64,7 +64,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="RefreshInfoProvider"/> property.
         /// </summary>
-        internal DirectProperty<RefreshVisualizer, RefreshInfoProvider?> RefreshInfoProviderProperty =
+        internal static readonly DirectProperty<RefreshVisualizer, RefreshInfoProvider?> RefreshInfoProviderProperty =
             AvaloniaProperty.RegisterDirect<RefreshVisualizer, RefreshInfoProvider?>(nameof(RefreshInfoProvider),
                 s => s.RefreshInfoProvider, (s, o) => s.RefreshInfoProvider = o);
 

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

@@ -95,7 +95,7 @@ namespace Avalonia.Controls
         /// Defines the <see cref="CommandParameter"/> property.
         /// </summary>
         public static readonly StyledProperty<object?> CommandParameterProperty =
-            Button.CommandParameterProperty.AddOwner<MenuItem>();
+            Button.CommandParameterProperty.AddOwner<TrayIcon>();
 
         /// <summary>
         /// Defines the <see cref="TrayIcons"/> attached property.

+ 1 - 1
src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs

@@ -141,7 +141,7 @@ namespace Avalonia.Controls.Utils
                 else
                 {
                     var eCapture = e;
-                    Dispatcher.UIThread.Post(() => Notify(_collection, eCapture, l));
+                    Dispatcher.UIThread.Post(() => Notify(_collection, eCapture, l), DispatcherPriority.Send);
                 }
             }
         }

+ 2 - 10
src/Avalonia.Controls/VirtualizingCarouselPanel.cs

@@ -80,16 +80,8 @@ namespace Avalonia.Controls
             remove => _scrollInvalidated -= value;
         }
 
-        bool ILogicalScrollable.BringIntoView(Control target, Rect targetRect)
-        {
-            throw new NotImplementedException();
-        }
-
-        Control? ILogicalScrollable.GetControlInDirection(NavigationDirection direction, Control? from)
-        {
-            throw new NotImplementedException();
-        }
-
+        bool ILogicalScrollable.BringIntoView(Control target, Rect targetRect) => false;
+        Control? ILogicalScrollable.GetControlInDirection(NavigationDirection direction, Control? from) => null;
         void ILogicalScrollable.RaiseScrollInvalidated(EventArgs e) => _scrollInvalidated?.Invoke(this, e);
 
         protected override Size MeasureOverride(Size availableSize)

+ 1 - 1
src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Diagnostics.Controls
                 (o, v) => o.Header = v);
 
         public static readonly DirectProperty<ThicknessEditor, bool> IsPresentProperty =
-            AvaloniaProperty.RegisterDirect<ThicknessEditor, bool>(nameof(Header), o => o.IsPresent,
+            AvaloniaProperty.RegisterDirect<ThicknessEditor, bool>(nameof(IsPresent), o => o.IsPresent,
                 (o, v) => o.IsPresent = v);
 
         public static readonly DirectProperty<ThicknessEditor, double> LeftProperty =

+ 2 - 2
src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml

@@ -28,7 +28,7 @@
     </Style>
 
     <Style Selector="ListBoxItem.handled" >
-      <Setter Property="Background" Value="#d9ffdc" />
+      <Setter Property="Background" Value="#34c940" />
       <Setter Property="Foreground" Value="Black" />
     </Style>
   </UserControl.Styles>
@@ -129,7 +129,7 @@
 
       </DockPanel>
 
-      <StackPanel Orientation="Horizontal" Grid.Row="3" Spacing="2" Margin="0,2">
+      <StackPanel Orientation="Horizontal" Grid.Row="3" Spacing="2" Margin="2">
         <Button Content="Clear" Command="{Binding Clear}" />
       </StackPanel>
 

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Bold.otf


BIN
src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.otf


BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Light.otf


BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Medium.otf


BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Regular.otf


BIN
src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.otf


BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Thin.otf


+ 13 - 0
src/Avalonia.Fonts.Inter/Avalonia.Fonts.Inter.csproj

@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
+    <PackageId>Avalonia.Fonts.Inter</PackageId>
+  </PropertyGroup>
+  <ItemGroup>
+    <AvaloniaResource Include="Assets\*" />  
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
+  </ItemGroup>
+  <Import Project="..\..\build\BuildTargets.targets" />
+</Project>

+ 3 - 0
src/Avalonia.Fonts.Inter/Properties/AssemblyInfo.cs

@@ -0,0 +1,3 @@
+using Avalonia.Metadata;
+
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Fonts.Inter")]

+ 2 - 2
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@@ -118,7 +118,7 @@ namespace Avalonia.Headless
 
         public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
         {
-            return new HeadlessGeometryStub(new Rect(glyphRun.Size));
+            return new HeadlessGeometryStub(glyphRun.Bounds);
         }
 
         public IGlyphRunImpl CreateGlyphRun(
@@ -132,7 +132,7 @@ namespace Avalonia.Headless
 
         class HeadlessGlyphRunStub : IGlyphRunImpl
         {
-            public Size Size => new Size(8, 12);
+            public Rect Bounds => new Rect(new Size(8, 12));
 
             public Point BaselineOrigin => new Point(0, 8);
 

+ 1 - 1
src/Avalonia.Themes.Fluent/Accents/Base.xaml

@@ -3,7 +3,7 @@
                     xmlns:sys="using:System"
                     xmlns:converters="using:Avalonia.Controls.Converters">
   <!-- https://docs.microsoft.com/en-us/previous-versions/windows/apps/dn518235(v=win.10)?redirectedfrom=MSDN -->
-  <FontFamily x:Key="ContentControlThemeFontFamily">avares://Avalonia.Themes.Fluent/Assets#Inter</FontFamily>
+  <FontFamily x:Key="ContentControlThemeFontFamily">avares://Avalonia.Fonts.Inter/Assets#Inter, $Default</FontFamily>
   <sys:Double x:Key="ControlContentThemeFontSize">14</sys:Double>
 
   <SolidColorBrush x:Key="SystemControlTransparentBrush" Color="Transparent" />

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf


BIN
src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf


BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf


BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf


BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf


BIN
src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf


BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf


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

@@ -9,8 +9,7 @@
     <ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
     <ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
     <ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
-    <AvaloniaResource Include="**/*.xaml" />
-    <AvaloniaResource Include="Assets\*" />  
+    <AvaloniaResource Include="**/*.xaml" />  
   </ItemGroup>
   <Import Project="..\..\build\NullableEnable.props" />
   <Import Project="..\..\build\BuildTargets.targets" />

+ 1 - 0
src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml

@@ -4,6 +4,7 @@
     <Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
     <Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
     <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}"/>
+    <Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
     <Setter Property="Template">
       <ControlTemplate>
         <Panel>

+ 6 - 0
src/Avalonia.Themes.Simple/Accents/Base.xaml

@@ -17,6 +17,7 @@
       <Color x:Key="ThemeControlHighlightHighColor">#FF808080</Color>
       <Color x:Key="ThemeForegroundColor">#FF000000</Color>
       <Color x:Key="HighlightColor">#FF086F9E</Color>
+      <Color x:Key="HighlightColor2">#FF096085</Color>
 
       <SolidColorBrush x:Key="ThemeBackgroundBrush" Color="{StaticResource ThemeBackgroundColor}" />
       <SolidColorBrush x:Key="ThemeBorderLowBrush" Color="{StaticResource ThemeBorderLowColor}" />
@@ -33,6 +34,8 @@
       <SolidColorBrush x:Key="ThemeForegroundBrush" Color="{StaticResource ThemeForegroundColor}" />
 
       <SolidColorBrush x:Key="HighlightBrush" Color="{StaticResource HighlightColor}" />
+      <SolidColorBrush x:Key="HighlightBrush2" Color="{StaticResource HighlightColor2}" />
+
       <SolidColorBrush x:Key="RefreshVisualizerForeground" Color="Black" />
       <SolidColorBrush x:Key="RefreshVisualizerBackground" Color="Transparent" />
     </ResourceDictionary>
@@ -51,6 +54,7 @@
       <Color x:Key="ThemeControlHighlightHighColor">#FF505050</Color>
       <Color x:Key="ThemeForegroundColor">#FFDEDEDE</Color>
       <Color x:Key="HighlightColor">#FF119EDA</Color>
+      <Color x:Key="HighlightColor2">#FF096085</Color>
 
       <SolidColorBrush x:Key="ThemeBackgroundBrush" Color="{StaticResource ThemeBackgroundColor}" />
       <SolidColorBrush x:Key="ThemeBorderLowBrush" Color="{StaticResource ThemeBorderLowColor}" />
@@ -66,11 +70,13 @@
       <SolidColorBrush x:Key="ThemeControlHighlightHighBrush" Color="{StaticResource ThemeControlHighlightHighColor}" />
       <SolidColorBrush x:Key="ThemeForegroundBrush" Color="{StaticResource ThemeForegroundColor}" />
       <SolidColorBrush x:Key="HighlightBrush" Color="{StaticResource HighlightColor}" />
+      <SolidColorBrush x:Key="HighlightBrush2" Color="{StaticResource HighlightColor2}" />
 
       <SolidColorBrush x:Key="RefreshVisualizerForeground" Color="White" />
       <SolidColorBrush x:Key="RefreshVisualizerBackground" Color="Transparent" />
     </ResourceDictionary>
   </ResourceDictionary.ThemeDictionaries>
+  <FontFamily x:Key="ContentControlThemeFontFamily">avares://Avalonia.Fonts.Inter/Assets#Inter, $Default</FontFamily>
   <Color x:Key="ThemeAccentColor">#CC119EDA</Color>
   <Color x:Key="ThemeAccentColor2">#99119EDA</Color>
   <Color x:Key="ThemeAccentColor3">#66119EDA</Color>

+ 1 - 0
src/Avalonia.Themes.Simple/Controls/ComboBox.xaml

@@ -3,6 +3,7 @@
   <ControlTheme x:Key="{x:Type ComboBox}"
                 TargetType="ComboBox">
     <Setter Property="Background" Value="Transparent" />
+    <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
     <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
     <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}" />
     <Setter Property="HorizontalContentAlignment" Value="Stretch" />

+ 0 - 1
src/Avalonia.Themes.Simple/Controls/DataValidationErrors.xaml

@@ -30,7 +30,6 @@
                 Background="Transparent">
           <Canvas.Styles>
             <Style Selector="ToolTip">
-              <Setter Property="Background" Value="{DynamicResource ErrorLowBrush}" />
               <Setter Property="BorderBrush" Value="{DynamicResource ErrorBrush}" />
             </Style>
           </Canvas.Styles>

+ 1 - 0
src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml

@@ -5,6 +5,7 @@
     <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
     <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}" />
     <Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}" />
+    <Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
     <Setter Property="Template">
       <ControlTemplate>
         <Panel>

+ 1 - 3
src/Avalonia.Themes.Simple/Controls/OverlayPopupHost.xaml

@@ -4,9 +4,7 @@
                 TargetType="OverlayPopupHost">
     <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
     <Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}" />
-    <Setter Property="FontFamily" Value="{x:Static FontFamily.Default}" />
-    <Setter Property="FontWeight" Value="400" />
-    <Setter Property="FontStyle" Value="Normal" />
+    <Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
     <Setter Property="Template">
       <ControlTemplate>
         <!--  Do not forget to update Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent test  -->

+ 1 - 3
src/Avalonia.Themes.Simple/Controls/PopupRoot.xaml

@@ -6,9 +6,7 @@
     <Setter Property="TransparencyLevelHint" Value="Transparent" />
     <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
     <Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}" />
-    <Setter Property="FontFamily" Value="{x:Static FontFamily.Default}" />
-    <Setter Property="FontWeight" Value="400" />
-    <Setter Property="FontStyle" Value="Normal" />
+    <Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
     <Setter Property="Template">
       <ControlTemplate>
         <LayoutTransformControl LayoutTransform="{TemplateBinding Transform}">

+ 1 - 0
src/Avalonia.Themes.Simple/Controls/Window.xaml

@@ -2,6 +2,7 @@
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   <ControlTheme x:Key="{x:Type Window}"
                 TargetType="Window">
+    <Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
     <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}" />
     <Setter Property="TransparencyBackgroundFallback" Value="{DynamicResource HighlightForegroundColor}" />
     <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />

+ 14 - 13
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs

@@ -146,13 +146,23 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                         .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Tree")
                         ?.Values[0] is XamlAstTextNode treeTypeValue ? treeTypeValue.Text : "Visual";
 
-                    var ancestorTypeName = relativeSourceObject.Children
+                    var ancestorType = relativeSourceObject.Children
                         .OfType<XamlAstXamlPropertyValueNode>()
                         .FirstOrDefault(x => x.Property.GetClrProperty().Name == "AncestorType")
-                        ?.Values[0] as XamlAstTextNode;
+                        ?.Values[0] switch
+                        {
+                            XamlAstTextNode textNode => TypeReferenceResolver.ResolveType(
+                                context,
+                                textNode.Text,
+                                false,
+                                textNode,
+                                true).GetClrType(),
+                            XamlTypeExtensionNode typeExtensionNode => typeExtensionNode.Value.GetClrType(),
+                            null => null,
+                            _ => throw new XamlParseException($"Unsupported node for AncestorType property", relativeSourceObject)
+                        };
 
-                    IXamlType ancestorType = null;
-                    if (ancestorTypeName is null)
+                    if (ancestorType is null)
                     {
                         if (treeType == "Visual")
                         {
@@ -174,15 +184,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                             }
                         }
                     }
-                    else
-                    {
-                        ancestorType = TypeReferenceResolver.ResolveType(
-                                            context,
-                                            ancestorTypeName.Text,
-                                            false,
-                                            ancestorTypeName,
-                                            true).GetClrType();
-                    }
 
                     if (treeType == "Visual")
                     {

+ 8 - 8
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
-using System.Linq;
 using System.Reflection;
 using Avalonia.Controls;
 using Avalonia.Data.Core;
@@ -13,13 +12,14 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 {
     public class CompiledBindingPath
     {
-        private readonly List<ICompiledBindingPathElement> _elements = new List<ICompiledBindingPathElement>();
+        private readonly ICompiledBindingPathElement[] _elements;
 
-        public CompiledBindingPath() { }
+        public CompiledBindingPath()
+            => _elements = Array.Empty<ICompiledBindingPathElement>();
 
-        internal CompiledBindingPath(IEnumerable<ICompiledBindingPathElement> bindingPath, object rawSource)
+        internal CompiledBindingPath(ICompiledBindingPathElement[] elements, object rawSource)
         {
-            _elements = new List<ICompiledBindingPathElement>(bindingPath);
+            _elements = elements;
             RawSource = rawSource;
         }
 
@@ -78,13 +78,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
         internal IEnumerable<ICompiledBindingPathElement> Elements => _elements;
 
-        internal SourceMode SourceMode => _elements.OfType<IControlSourceBindingPathElement>().Any()
+        internal SourceMode SourceMode => Array.Exists(_elements, e => e is IControlSourceBindingPathElement)
             ? SourceMode.Control : SourceMode.Data;
 
         internal object RawSource { get; }
 
         public override string ToString()
-            => string.Concat(_elements);
+            => string.Concat((IEnumerable<ICompiledBindingPathElement>) _elements);
     }
 
     public class CompiledBindingPathBuilder
@@ -169,7 +169,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
             return this;
         }
 
-        public CompiledBindingPath Build() => new CompiledBindingPath(_elements, _rawSource);
+        public CompiledBindingPath Build() => new CompiledBindingPath(_elements.ToArray(), _rawSource);
     }
 
     public interface ICompiledBindingPathElement

+ 36 - 7
src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs

@@ -8,11 +8,25 @@ namespace Avalonia.Skia
     /// </summary>
     internal class CombinedGeometryImpl : GeometryImpl
     {
-        public CombinedGeometryImpl(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
+        public CombinedGeometryImpl(SKPath? stroke, SKPath? fill)
         {
-            var path1 = (g1.PlatformImpl as GeometryImpl)?.EffectivePath;
-            var path2 = (g2.PlatformImpl as GeometryImpl)?.EffectivePath;
+            StrokePath = stroke;
+            FillPath = fill;
+            Bounds = (stroke ?? fill)?.TightBounds.ToAvaloniaRect() ?? default;
+        }
+
+        public static CombinedGeometryImpl ForceCreate(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
+        {
+            if (g1.PlatformImpl is GeometryImpl i1
+                && g2.PlatformImpl is GeometryImpl i2
+                && TryCreate(combineMode, i1, i2) is { } result)
+                return result;
+            
+            return new(null, null);
+        }
 
+        public static CombinedGeometryImpl? TryCreate(GeometryCombineMode combineMode, GeometryImpl g1, GeometryImpl g2)
+        {
             var op = combineMode switch
             {
                 GeometryCombineMode.Intersect => SKPathOp.Intersect,
@@ -21,13 +35,28 @@ namespace Avalonia.Skia
                 _ => SKPathOp.Union
             };
 
-            var path = path1?.Op(path2, op);
+            var stroke =
+                g1.StrokePath != null && g2.StrokePath != null
+                    ? g1.StrokePath.Op(g2.StrokePath, op)
+                    : null;
+
+            SKPath? fill = null;
+            if (g1.FillPath != null && g2.FillPath != null)
+            {
+                // Reuse stroke if fill paths are the same
+                if (ReferenceEquals(g1.FillPath, g1.StrokePath) && ReferenceEquals(g2.FillPath, g2.StrokePath))
+                    fill = stroke;
+                else
+                    fill = g1.FillPath.Op(g2.FillPath, op);
+            }
 
-            EffectivePath = path;
-            Bounds = path?.Bounds.ToAvaloniaRect() ?? default;
+            if (stroke == null && fill == null)
+                return null;
+            return new CombinedGeometryImpl(stroke, fill);
         }
 
         public override Rect Bounds { get; }
-        public override SKPath? EffectivePath { get; }
+        public override SKPath? StrokePath { get; }
+        public override SKPath? FillPath { get; }
     }
 }

+ 6 - 5
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -230,20 +230,21 @@ namespace Avalonia.Skia
             var impl = (GeometryImpl) geometry;
             var size = geometry.Bounds.Size;
 
-            if (brush is not null)
+            if (brush is not null && impl.FillPath != null)
             {
                 using (var fill = CreatePaint(_fillPaint, brush, size))
                 {
-                    Canvas.DrawPath(impl.EffectivePath, fill.Paint);
+                    Canvas.DrawPath(impl.FillPath, fill.Paint);
                 }
             }
 
             if (pen is not null
+                && impl.StrokePath != null
                 && TryCreatePaint(_strokePaint, pen, size.Inflate(new Thickness(pen.Thickness / 2))) is { } stroke)
             {
                 using (stroke)
                 {
-                    Canvas.DrawPath(impl.EffectivePath, stroke.Paint);
+                    Canvas.DrawPath(impl.StrokePath, stroke.Paint);
                 }
             }
         }
@@ -515,7 +516,7 @@ namespace Avalonia.Skia
                 return;
             }
 
-            using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Item.Size))
+            using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Item.Bounds.Size))
             {
                 var glyphRunImpl = (GlyphRunImpl)glyphRun.Item;
 
@@ -639,7 +640,7 @@ namespace Avalonia.Skia
         {
             CheckLease();
             Canvas.Save();
-            Canvas.ClipPath(((GeometryImpl)clip).EffectivePath, SKClipOperation.Intersect, true);
+            Canvas.ClipPath(((GeometryImpl)clip).FillPath, SKClipOperation.Intersect, true);
         }
 
         /// <inheritdoc />

+ 3 - 2
src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs

@@ -8,14 +8,15 @@ namespace Avalonia.Skia
     internal class EllipseGeometryImpl : GeometryImpl
     {
         public override Rect Bounds { get; }
-        public override SKPath EffectivePath { get; }
+        public override SKPath StrokePath { get; }
+        public override SKPath FillPath => StrokePath;
 
         public EllipseGeometryImpl(Rect rect)
         {
             var path = new SKPath();
             path.AddOval(rect.ToSKRect());
 
-            EffectivePath = path;
+            StrokePath = path;
             Bounds = rect;
         }
     }

+ 15 - 8
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@@ -99,7 +99,20 @@ namespace Avalonia.Skia
         {
             SKTypeface? skTypeface = null;
 
-            if (typeface.FontFamily.Key is null)
+            if(typeface.FontFamily.Key is not null)
+            {
+                var fontCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(typeface.FontFamily);
+
+                skTypeface = fontCollection.Get(typeface);
+
+                if (skTypeface is null && !typeface.FontFamily.FamilyNames.HasFallbacks)
+                {
+                    throw new InvalidOperationException(
+                        $"Could not create glyph typeface for: {typeface.FontFamily.Name}.");
+                }
+            }
+
+            if (skTypeface is null)
             {
                 var defaultName = SKTypeface.Default.FamilyName;
 
@@ -128,13 +141,7 @@ namespace Avalonia.Skia
                 skTypeface ??= _skFontManager.MatchTypeface(SKTypeface.Default, fontStyle)
                     ?? SKTypeface.Default;
             }
-            else
-            {
-                var fontCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(typeface.FontFamily);
-
-                skTypeface = fontCollection.Get(typeface);
-            }
-
+           
             if (skTypeface == null)
             {
                 throw new InvalidOperationException(

+ 34 - 9
src/Skia/Avalonia.Skia/GeometryGroupImpl.cs

@@ -11,26 +11,51 @@ namespace Avalonia.Skia
     {
         public GeometryGroupImpl(FillRule fillRule, IReadOnlyList<Geometry> children)
         {
-            var path = new SKPath
+            var fillType = fillRule == FillRule.NonZero ? SKPathFillType.Winding : SKPathFillType.EvenOdd;
+            var count = children.Count;
+            
+            var stroke = new SKPath
             {
-                FillType = fillRule == FillRule.NonZero ? SKPathFillType.Winding : SKPathFillType.EvenOdd,
+                FillType = fillType
             };
-
-            var count = children.Count;
             
+            bool requiresFillPass = false;
             for (var i = 0; i < count; ++i)
             {
-                if (children[i].PlatformImpl is GeometryImpl { EffectivePath: { } effectivePath })
+                if (children[i].PlatformImpl is GeometryImpl geo)
                 {
-                    path.AddPath(effectivePath);
+                    if (geo.StrokePath != null)
+                        stroke.AddPath(geo.StrokePath);
+                    if (!ReferenceEquals(geo.StrokePath, geo.FillPath))
+                        requiresFillPass = true;
                 }
             }
+            
+            StrokePath = stroke;
+            
+            if (requiresFillPass)
+            {
+                var fill = new SKPath
+                {
+                    FillType = fillType
+                };
+
+                for (var i = 0; i < count; ++i)
+                {
+                    if (children[i].PlatformImpl is GeometryImpl { FillPath: { } fillPath })
+                        fill.AddPath(fillPath);
+                }
+
+                FillPath = fill;
+            }
+            else
+                FillPath = stroke;
 
-            EffectivePath = path;
-            Bounds = path.Bounds.ToAvaloniaRect();
+            Bounds = stroke.TightBounds.ToAvaloniaRect();
         }
 
         public override Rect Bounds { get; }
-        public override SKPath EffectivePath { get; }
+        public override SKPath StrokePath { get; }
+        public override SKPath FillPath { get; }
     }
 }

+ 14 - 17
src/Skia/Avalonia.Skia/GeometryImpl.cs

@@ -14,7 +14,7 @@ namespace Avalonia.Skia
         private PathCache _pathCache;
         private SKPathMeasure? _cachedPathMeasure;
 
-        private SKPathMeasure CachedPathMeasure => _cachedPathMeasure ??= new SKPathMeasure(EffectivePath!);
+        private SKPathMeasure CachedPathMeasure => _cachedPathMeasure ??= new SKPathMeasure(StrokePath!);
 
         /// <inheritdoc />
         public abstract Rect Bounds { get; }
@@ -24,19 +24,20 @@ namespace Avalonia.Skia
         {
             get
             {
-                if (EffectivePath is null)
+                if (StrokePath is null)
                     return 0;
 
                 return CachedPathMeasure.Length;
             }
         }
 
-        public abstract SKPath? EffectivePath { get; }
+        public abstract SKPath? StrokePath { get; }
+        public abstract SKPath? FillPath { get; }
 
         /// <inheritdoc />
         public bool FillContains(Point point)
         {
-            return PathContainsCore(EffectivePath, point);
+            return PathContainsCore(FillPath, point);
         }
 
         /// <inheritdoc />
@@ -74,7 +75,7 @@ namespace Avalonia.Skia
                 var paint = SKPaintCache.Shared.Get();
                 paint.IsStroke = true;
                 paint.StrokeWidth = strokeWidth;
-                paint.GetFillPath(EffectivePath, strokePath);
+                paint.GetFillPath(StrokePath, strokePath);
 
                 SKPaintCache.Shared.ReturnReset(paint);
 
@@ -96,14 +97,10 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public IGeometryImpl? Intersect(IGeometryImpl geometry)
         {
-            if (EffectivePath is { } path
-                && (geometry as GeometryImpl)?.EffectivePath is { } otherPath
-                && path.Op(otherPath, SKPathOp.Intersect) is { } result)
-            {
-                return new StreamGeometryImpl(result);
-            }
-
-            return null;
+            var other = geometry as GeometryImpl;
+            if (other == null)
+                return null;
+            return CombinedGeometryImpl.TryCreate(GeometryCombineMode.Intersect, this, other);
         }
 
         /// <inheritdoc />
@@ -128,7 +125,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public bool TryGetPointAtDistance(double distance, out Point point)
         {
-            if (EffectivePath is null)
+            if (StrokePath is null)
             {
                 point = new Point();
                 return false;
@@ -142,7 +139,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
         {
-            if (EffectivePath is null)
+            if (StrokePath is null)
             {
                 point = new Point();
                 tangent = new Point();
@@ -158,7 +155,7 @@ namespace Avalonia.Skia
         public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure,
             [NotNullWhen(true)] out IGeometryImpl? segmentGeometry)
         {
-            if (EffectivePath is null)
+            if (StrokePath is null)
             {
                 segmentGeometry = null;
                 return false;
@@ -172,7 +169,7 @@ namespace Avalonia.Skia
 
             if (res)
             {
-                segmentGeometry = new StreamGeometryImpl(_skPathSegment);
+                segmentGeometry = new StreamGeometryImpl(_skPathSegment, null);
             }
 
             return res;

+ 2 - 2
src/Skia/Avalonia.Skia/GlyphRunImpl.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Skia
         {
             TextBlob = textBlob ?? throw new ArgumentNullException(nameof(textBlob));
 
-            Size = size;
+            Bounds = new Rect(new Point(baselineOrigin.X, 0), size);
 
             BaselineOrigin = baselineOrigin;
         }
@@ -21,7 +21,7 @@ namespace Avalonia.Skia
         /// </summary>
         public SKTextBlob TextBlob { get; }
 
-        public Size Size { get; }
+        public Rect Bounds { get; }
 
         public Point BaselineOrigin { get; }
 

+ 0 - 1
src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs

@@ -13,7 +13,6 @@ namespace Avalonia.Skia.Helpers
         /// </summary>
         /// <param name="canvas"></param>
         /// <param name="dpi"></param>
-        /// <param name="visualBrushRenderer"></param>
         /// <returns>DrawingContext</returns>
         public static IDrawingContextImpl WrapSkiaCanvas(SKCanvas canvas, Vector dpi)
         {

+ 3 - 2
src/Skia/Avalonia.Skia/LineGeometryImpl.cs

@@ -9,7 +9,8 @@ namespace Avalonia.Skia
     internal class LineGeometryImpl : GeometryImpl
     {
         public override Rect Bounds { get; }
-        public override SKPath EffectivePath { get; }
+        public override SKPath StrokePath { get; }
+        public override SKPath? FillPath => null;
 
         public LineGeometryImpl(Point p1, Point p2)
         {
@@ -17,7 +18,7 @@ namespace Avalonia.Skia
             path.MoveTo(p1.ToSKPoint());
             path.LineTo(p2.ToSKPoint());
 
-            EffectivePath = path;
+            StrokePath = path;
             Bounds = new Rect(
                 new Point(Math.Min(p1.X, p2.X), Math.Min(p1.Y, p2.Y)), 
                 new Point(Math.Max(p1.X, p2.X), Math.Max(p1.Y, p2.Y)));

+ 2 - 2
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@@ -65,7 +65,7 @@ namespace Avalonia.Skia
 
         public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2)
         {
-            return new CombinedGeometryImpl(combineMode, g1, g2);
+            return CombinedGeometryImpl.ForceCreate(combineMode, g1, g2);
         }
 
         public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
@@ -104,7 +104,7 @@ namespace Avalonia.Skia
 
             SKFontCache.Shared.Return(skFont);
 
-            return new StreamGeometryImpl(path);
+            return new StreamGeometryImpl(path, path);
         }
 
         /// <inheritdoc />

+ 3 - 2
src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs

@@ -8,14 +8,15 @@ namespace Avalonia.Skia
     internal class RectangleGeometryImpl : GeometryImpl
     {
         public override Rect Bounds { get; }
-        public override SKPath EffectivePath { get; }
+        public override SKPath StrokePath { get; }
+        public override SKPath? FillPath => StrokePath;
 
         public RectangleGeometryImpl(Rect rect)
         {
             var path = new SKPath();
             path.AddRect(rect.ToSKRect());
 
-            EffectivePath = path;
+            StrokePath = path;
             Bounds = rect;
         }
     }

+ 64 - 26
src/Skia/Avalonia.Skia/StreamGeometryImpl.cs

@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
 using Avalonia.Media;
 using Avalonia.Platform;
 using SkiaSharp;
@@ -10,36 +11,39 @@ namespace Avalonia.Skia
     internal class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl
     {
         private Rect _bounds;
-        private readonly SKPath _effectivePath;
+        private readonly SKPath _strokePath;
+        private SKPath? _fillPath;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
         /// </summary>
-        /// <param name="path">An existing Skia <see cref="SKPath"/>.</param>
+        /// <param name="stroke">An existing Skia <see cref="SKPath"/> for the stroke.</param>
+        /// <param name="fill">An existing Skia <see cref="SKPath"/> for the fill, can also be null or the same as the stroke</param>
         /// <param name="bounds">Precomputed path bounds.</param>
-        public StreamGeometryImpl(SKPath path, Rect bounds)
+        public StreamGeometryImpl(SKPath stroke, SKPath? fill, Rect? bounds = null)
         {
-            _effectivePath = path;
-            _bounds = bounds;
+            _strokePath = stroke;
+            _fillPath = fill;
+            _bounds = bounds ?? stroke.TightBounds.ToAvaloniaRect();
         }
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
-        /// </summary>
-        /// <param name="path">An existing Skia <see cref="SKPath"/>.</param>
-        public StreamGeometryImpl(SKPath path) : this(path, path.TightBounds.ToAvaloniaRect())
+        private StreamGeometryImpl(SKPath path) : this(path, path, default(Rect))
         {
+
         }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
         /// </summary>
-        public StreamGeometryImpl() : this(CreateEmptyPath(), default)
+        public StreamGeometryImpl() : this(CreateEmptyPath())
         {
         }
-        
+
         /// <inheritdoc />
-        public override SKPath EffectivePath => _effectivePath;
+        public override SKPath? StrokePath => _strokePath;
+
+        /// <inheritdoc />
+        public override SKPath? FillPath => _fillPath;
 
         /// <inheritdoc />
         public override Rect Bounds => _bounds;
@@ -47,7 +51,9 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public IStreamGeometryImpl Clone()
         {
-            return new StreamGeometryImpl(_effectivePath.Clone(), Bounds);
+            var stroke = _strokePath.Clone();
+            var fill = _fillPath == _strokePath ? stroke : _fillPath.Clone();
+            return new StreamGeometryImpl(stroke, fill, Bounds);
         }
 
         /// <inheritdoc />
@@ -74,7 +80,10 @@ namespace Avalonia.Skia
         private class StreamContext : IStreamGeometryContextImpl
         {
             private readonly StreamGeometryImpl _geometryImpl;
-            private readonly SKPath _path;
+            private SKPath Stroke => _geometryImpl._strokePath;
+            private SKPath Fill => _geometryImpl._fillPath ??= new();
+            private bool _isFilled;
+            private bool Duplicate => _isFilled && !ReferenceEquals(_geometryImpl._fillPath, Stroke);
 
             /// <summary>
             /// Initializes a new instance of the <see cref="StreamContext"/> class.
@@ -83,52 +92,79 @@ namespace Avalonia.Skia
             public StreamContext(StreamGeometryImpl geometryImpl)
             {
                 _geometryImpl = geometryImpl;
-                _path = _geometryImpl._effectivePath;
             }
             
             /// <inheritdoc />
             /// <remarks>Will update bounds of passed geometry.</remarks>
             public void Dispose()
             {
-                _geometryImpl._bounds = _path.TightBounds.ToAvaloniaRect();
+                _geometryImpl._bounds = Stroke.TightBounds.ToAvaloniaRect();
                 _geometryImpl.InvalidateCaches();
             }
 
             /// <inheritdoc />
             public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
             {
-                _path.ArcTo(
+                var arc = isLargeArc ? SKPathArcSize.Large : SKPathArcSize.Small;
+                var sweep = sweepDirection == SweepDirection.Clockwise
+                    ? SKPathDirection.Clockwise
+                    : SKPathDirection.CounterClockwise;
+                Stroke.ArcTo(
                     (float)size.Width,
                     (float)size.Height,
                     (float)rotationAngle,
-                    isLargeArc ? SKPathArcSize.Large : SKPathArcSize.Small,
-                    sweepDirection == SweepDirection.Clockwise ? SKPathDirection.Clockwise : SKPathDirection.CounterClockwise,
+                    arc,
+                    sweep,
                     (float)point.X,
                     (float)point.Y);
+                if(Duplicate)
+                    Fill.ArcTo(
+                        (float)size.Width,
+                        (float)size.Height,
+                        (float)rotationAngle,
+                        arc,
+                        sweep,
+                        (float)point.X,
+                        (float)point.Y);
             }
 
             /// <inheritdoc />
             public void BeginFigure(Point startPoint, bool isFilled)
             {
-                _path.MoveTo((float)startPoint.X, (float)startPoint.Y);
+                if (!isFilled)
+                {
+                    if (Stroke == Fill)
+                        _geometryImpl._fillPath = Stroke.Clone();
+                }
+                
+                _isFilled = isFilled;
+                Stroke.MoveTo((float)startPoint.X, (float)startPoint.Y);
+                if(Duplicate)
+                    Fill.MoveTo((float)startPoint.X, (float)startPoint.Y);
             }
 
             /// <inheritdoc />
             public void CubicBezierTo(Point point1, Point point2, Point point3)
             {
-                _path.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
+                Stroke.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
+                if(Duplicate)
+                    Fill.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
             }
 
             /// <inheritdoc />
             public void QuadraticBezierTo(Point point1, Point point2)
             {
-                _path.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
+                Stroke.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
+                if(Duplicate)
+                    Fill.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
             }
 
             /// <inheritdoc />
             public void LineTo(Point point)
             {
-                _path.LineTo((float)point.X, (float)point.Y);
+                Stroke.LineTo((float)point.X, (float)point.Y);
+                if(Duplicate)
+                    Fill.LineTo((float)point.X, (float)point.Y);
             }
 
             /// <inheritdoc />
@@ -136,14 +172,16 @@ namespace Avalonia.Skia
             {
                 if (isClosed)
                 {
-                    _path.Close();
+                    Stroke.Close();
+                    if (Duplicate)
+                        Fill.Close();
                 }
             }
 
             /// <inheritdoc />
             public void SetFillRule(FillRule fillRule)
             {
-                _path.FillType = fillRule == FillRule.EvenOdd ? SKPathFillType.EvenOdd : SKPathFillType.Winding;
+                Fill.FillType = fillRule == FillRule.EvenOdd ? SKPathFillType.EvenOdd : SKPathFillType.Winding;
             }
         }
     }

+ 1 - 3
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@@ -56,8 +56,6 @@ namespace Avalonia.Skia
 
                 var shapedBuffer = new ShapedBuffer(text, bufferLength, typeface, fontRenderingEmSize, bidiLevel);
 
-                var targetInfos = shapedBuffer.GlyphInfos;
-
                 var glyphInfos = buffer.GetGlyphInfoSpan();
 
                 var glyphPositions = buffer.GetGlyphPositionSpan();
@@ -83,7 +81,7 @@ namespace Avalonia.Skia
                             4 * typeface.GetGlyphAdvance(glyphIndex) * textScale;
                     }
 
-                    targetInfos[i] = new Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset);
+                    shapedBuffer[i] = new Media.TextFormatting.GlyphInfo(glyphIndex, glyphCluster, glyphAdvance, glyphOffset);
                 }
 
                 return shapedBuffer;

+ 16 - 5
src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs

@@ -17,16 +17,27 @@ namespace Avalonia.Skia
         {
             SourceGeometry = source;
             Transform = transform;
+            var matrix = transform.ToSKMatrix();
 
-            var transformedPath = source.EffectivePath.Clone();
-            transformedPath?.Transform(transform.ToSKMatrix());
-
-            EffectivePath = transformedPath;
+            var transformedPath = StrokePath =  source.StrokePath.Clone();
+            transformedPath?.Transform(matrix);
+            
             Bounds = transformedPath?.TightBounds.ToAvaloniaRect() ?? default;
+            
+            if (ReferenceEquals(source.StrokePath, source.FillPath))
+                FillPath = transformedPath;
+            else if (source.FillPath != null)
+            {
+                FillPath = transformedPath = source.FillPath.Clone();
+                transformedPath.Transform(matrix);
+            }
         }
 
         /// <inheritdoc />
-        public override SKPath? EffectivePath { get; }
+        public override SKPath? StrokePath { get; }
+        
+        /// <inheritdoc />
+        public override SKPath? FillPath { get; }
 
         /// <inheritdoc />
         public IGeometryImpl SourceGeometry { get; }

+ 1 - 1
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@@ -387,7 +387,7 @@ namespace Avalonia.Direct2D1.Media
         /// <param name="glyphRun">The glyph run.</param>
         public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun)
         {
-            using (var brush = CreateBrush(foreground, glyphRun.Item.Size))
+            using (var brush = CreateBrush(foreground, glyphRun.Item.Bounds.Size))
             {
                 var glyphRunImpl = (GlyphRunImpl)glyphRun.Item;
 

+ 2 - 2
src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs

@@ -9,12 +9,12 @@ namespace Avalonia.Direct2D1.Media
     {
         public GlyphRunImpl(GlyphRun glyphRun, Size size, Point baselineOrigin)
         {
-            Size = size;
+            Bounds = new Rect(new Point(baselineOrigin.X, 0), size);
             BaselineOrigin = baselineOrigin;
             GlyphRun = glyphRun;
         }
 
-        public Size Size { get; }
+        public Rect Bounds{ get; }
 
         public Point BaselineOrigin { get; }
 

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff