Browse Source

Merge branch 'master' into refactor/new-value-store

Steven Kirk 3 years ago
parent
commit
22576b07a2
100 changed files with 1150 additions and 1378 deletions
  1. 3 0
      .gitignore
  2. 3 3
      build/HarfBuzzSharp.props
  3. 1 1
      build/ReactiveUI.props
  4. 1 1
      build/SharedVersion.props
  5. 3 3
      build/SkiaSharp.props
  6. 22 68
      nukebuild/Build.cs
  7. 0 4
      nukebuild/BuildParameters.cs
  8. 57 0
      nukebuild/DotNetConfigHelper.cs
  9. 0 4
      samples/ControlCatalog.NetCore/Program.cs
  10. 34 0
      samples/ControlCatalog/Converter/HexConverter.cs
  11. 1 1
      samples/ControlCatalog/DecoratedWindow.xaml
  12. 3 3
      samples/ControlCatalog/MainView.xaml.cs
  13. 3 3
      samples/ControlCatalog/MainWindow.xaml
  14. 4 4
      samples/ControlCatalog/Models/Person.cs
  15. 1 1
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
  16. 11 13
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  17. 14 11
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs
  18. 1 1
      samples/ControlCatalog/Pages/ButtonsPage.xaml.cs
  19. 1 1
      samples/ControlCatalog/Pages/CarouselPage.xaml.cs
  20. 47 23
      samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
  21. 1 1
      samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
  22. 8 12
      samples/ControlCatalog/Pages/CompositionPage.axaml.cs
  23. 11 11
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs
  24. 12 11
      samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs
  25. 2 2
      samples/ControlCatalog/Pages/DataGridPage.xaml.cs
  26. 10 3
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  27. 10 10
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  28. 1 1
      samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs
  29. 5 5
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  30. 14 2
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  31. 2 4
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  32. 3 3
      samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
  33. 2 2
      samples/ControlCatalog/Pages/PointerCanvas.cs
  34. 18 15
      samples/ControlCatalog/Pages/PointersPage.xaml.cs
  35. 4 4
      samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
  36. 3 4
      samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
  37. 3 3
      samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs
  38. 19 17
      samples/IntegrationTestApp/MainWindow.axaml.cs
  39. 136 129
      samples/RenderDemo/Pages/DrawingPage.xaml
  40. 2 2
      samples/RenderDemo/Pages/TextFormatterPage.axaml.cs
  41. 16 29
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  42. 0 12
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  43. 13 4
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  44. 251 0
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
  45. 0 85
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  46. 35 45
      src/Avalonia.Base/Animation/Animation.cs
  47. 5 2
      src/Avalonia.Base/Animation/AnimatorKeyFrame.cs
  48. 2 2
      src/Avalonia.Base/Animation/Animators/Animator`1.cs
  49. 8 9
      src/Avalonia.Base/Animation/Animators/BaseBrushAnimator.cs
  50. 0 13
      src/Avalonia.Base/Animation/Animators/SolidColorBrushAnimator.cs
  51. 1 0
      src/Avalonia.Base/Avalonia.Base.csproj
  52. 0 35
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  53. 0 15
      src/Avalonia.Base/AvaloniaProperty`1.cs
  54. 0 15
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  55. 0 15
      src/Avalonia.Base/DirectPropertyBase.cs
  56. 0 4
      src/Avalonia.Base/EnumExtensions.cs
  57. 2 1
      src/Avalonia.Base/Input/PointerOverPreProcessor.cs
  58. 15 15
      src/Avalonia.Base/Input/PointerPoint.cs
  59. 4 0
      src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs
  60. 0 18
      src/Avalonia.Base/Interactivity/RoutedEvent.cs
  61. 0 11
      src/Avalonia.Base/Layout/ILayoutManager.cs
  62. 0 11
      src/Avalonia.Base/Layout/LayoutManager.cs
  63. 0 28
      src/Avalonia.Base/Layout/Layoutable.cs
  64. 0 1
      src/Avalonia.Base/Media/DashStyle.cs
  65. 0 16
      src/Avalonia.Base/Media/Imaging/Bitmap.cs
  66. 1 13
      src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
  67. 9 6
      src/Avalonia.Base/Media/KnownColors.cs
  68. 8 4
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
  69. 9 0
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
  70. 31 2
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  71. 1 3
      src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
  72. 7 3
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  73. 1 1
      src/Avalonia.Base/Rendering/Composition/Visual.cs
  74. 7 26
      src/Avalonia.Base/Rendering/DirtyVisuals.cs
  75. 35 0
      src/Avalonia.Base/Utilities/MathUtilities.cs
  76. 0 25
      src/Avalonia.Base/Utilities/WeakObservable.cs
  77. 0 193
      src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs
  78. 0 16
      src/Avalonia.Base/Visual.cs
  79. 0 19
      src/Avalonia.Base/VisualTree/IVisualTreeHost.cs
  80. 14 1
      src/Avalonia.Base/VisualTree/VisualExtensions.cs
  81. 1 0
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  82. 14 9
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml
  83. 14 9
      src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml
  84. 0 10
      src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs
  85. 30 0
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  86. 0 1
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  87. 6 5
      src/Avalonia.Controls/AutoCompleteBox.cs
  88. 35 42
      src/Avalonia.Controls/Calendar/Calendar.cs
  89. 25 46
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  90. 1 1
      src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs
  91. 2 4
      src/Avalonia.Controls/Control.cs
  92. 0 2
      src/Avalonia.Controls/DesktopApplicationExtensions.cs
  93. 2 13
      src/Avalonia.Controls/Documents/InlineUIContainer.cs
  94. 0 61
      src/Avalonia.Controls/DrawingPresenter.cs
  95. 1 1
      src/Avalonia.Controls/GridLength.cs
  96. 1 12
      src/Avalonia.Controls/LoggingExtensions.cs
  97. 0 7
      src/Avalonia.Controls/NativeMenuItem.cs
  98. 2 11
      src/Avalonia.Controls/NativeMenuItemSeparator.cs
  99. 4 7
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  100. 61 53
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

+ 3 - 0
.gitignore

@@ -212,3 +212,6 @@ coc-settings.json
 *.map
 src/Web/Avalonia.Web.Blazor/wwwroot/*.js
 src/Web/Avalonia.Web.Blazor/Interop/Typescript/*.js
+node_modules
+src/Web/Avalonia.Web.Blazor/webapp/package-lock.json
+src/Web/Avalonia.Web.Blazor/wwwroot

+ 3 - 3
build/HarfBuzzSharp.props

@@ -1,7 +1,7 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="HarfBuzzSharp" Version="2.8.2" />
-    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2" />
-    <PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2" />
+    <PackageReference Include="HarfBuzzSharp" Version="2.8.2.1-preview.108" />
+    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2.1-preview.108" />
+    <PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2.1-preview.108" />
   </ItemGroup>
 </Project>

+ 1 - 1
build/ReactiveUI.props

@@ -1,5 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="ReactiveUI" Version="13.2.10" />
+    <PackageReference Include="ReactiveUI" Version="18.3.1" />
   </ItemGroup>
 </Project>

+ 1 - 1
build/SharedVersion.props

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

+ 3 - 3
build/SkiaSharp.props

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

+ 22 - 68
nukebuild/Build.cs

@@ -36,25 +36,6 @@ partial class Build : NukeBuild
 {
     [Solution("Avalonia.sln")] readonly Solution Solution;
 
-    static Lazy<string> MsBuildExe = new Lazy<string>(() =>
-    {
-        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-            return null;
-
-        var msBuildDirectory = VSWhere("-latest -nologo -property installationPath -format value -prerelease").FirstOrDefault().Text;
-
-        if (!string.IsNullOrWhiteSpace(msBuildDirectory))
-        {
-            string msBuildExe = Path.Combine(msBuildDirectory, @"MSBuild\Current\Bin\MSBuild.exe");
-            if (!System.IO.File.Exists(msBuildExe))
-                msBuildExe = Path.Combine(msBuildDirectory, @"MSBuild\15.0\Bin\MSBuild.exe");
-
-            return msBuildExe;
-        }
-
-        return null;
-    }, false);
-
     BuildParameters Parameters { get; set; }
     protected override void OnBuildInitialized()
     {
@@ -89,25 +70,28 @@ partial class Build : NukeBuild
         }
         ExecWait("dotnet version:", "dotnet", "--info");
         ExecWait("dotnet workloads:", "dotnet", "workload list");
+        Information("Processor count: " + Environment.ProcessorCount);
+        Information("Available RAM: " + GC.GetGCMemoryInfo().TotalAvailableMemoryBytes / 0x100000 + "MB");
     }
 
-    IReadOnlyCollection<Output> MsBuildCommon(
-        string projectFile,
-        Configure<MSBuildSettings> configurator = null)
+    DotNetConfigHelper ApplySettingCore(DotNetConfigHelper c)
     {
-        return MSBuild(c => c
-            .SetProjectFile(projectFile)
-            // This is required for VS2019 image on Azure Pipelines
-            .When(Parameters.IsRunningOnWindows &&
-                  Parameters.IsRunningOnAzure, _ => _
-                .AddProperty("JavaSdkDirectory", GetVariable<string>("JAVA_HOME_11_X64")))
-            .AddProperty("PackageVersion", Parameters.Version)
+        if (Parameters.IsRunningOnAzure)
+            c.AddProperty("JavaSdkDirectory", GetVariable<string>("JAVA_HOME_11_X64"));
+        c.AddProperty("PackageVersion", Parameters.Version)
             .AddProperty("iOSRoslynPathHackRequired", true)
-            .SetProcessToolPath(MsBuildExe.Value)
             .SetConfiguration(Parameters.Configuration)
-            .SetVerbosity(MSBuildVerbosity.Minimal)
-            .Apply(configurator));
+            .SetVerbosity(DotNetVerbosity.Minimal);
+        return c;
     }
+    DotNetBuildSettings ApplySetting(DotNetBuildSettings c, Configure<DotNetBuildSettings> configurator = null) =>
+        ApplySettingCore(c).Build.Apply(configurator);
+
+    DotNetPackSettings ApplySetting(DotNetPackSettings c, Configure<DotNetPackSettings> configurator = null) =>
+        ApplySettingCore(c).Pack.Apply(configurator);
+
+    DotNetTestSettings ApplySetting(DotNetTestSettings c, Configure<DotNetTestSettings> configurator = null) =>
+        ApplySettingCore(c).Test.Apply(configurator);
 
     Target Clean => _ => _.Executes(() =>
     {
@@ -149,20 +133,11 @@ partial class Build : NukeBuild
     Target Compile => _ => _
         .DependsOn(Clean, CompileNative)
         .DependsOn(CompileHtmlPreviewer)
-        .Executes(async () =>
+        .Executes(() =>
         {
-            if (Parameters.IsRunningOnWindows)
-                MsBuildCommon(Parameters.MSBuildSolution, c => c
-                    .SetProcessArgumentConfigurator(a => a.Add("/r"))
-                    .AddTargets("Build")
-                );
-
-            else
-                DotNetBuild(c => c
-                    .SetProjectFile(Parameters.MSBuildSolution)
-                    .AddProperty("PackageVersion", Parameters.Version)
-                    .SetConfiguration(Parameters.Configuration)
-                );
+            DotNetBuild(c => ApplySetting(c)
+                .SetProjectFile(Parameters.MSBuildSolution)
+            );
         });
 
     void RunCoreTest(string projectName)
@@ -182,9 +157,8 @@ partial class Build : NukeBuild
 
             Information($"Running for {projectName} ({fw}) ...");
 
-            DotNetTest(c => c
+            DotNetTest(c => ApplySetting(c)
                 .SetProjectFile(project)
-                .SetConfiguration(Parameters.Configuration)
                 .SetFramework(fw)
                 .EnableNoBuild()
                 .EnableNoRestore()
@@ -263,19 +237,7 @@ partial class Build : NukeBuild
         .Executes(() =>
         {
             var data = Parameters;
-            var pathToProjectSource = RootDirectory / "samples" / "ControlCatalog.NetCore";
-            var pathToPublish = pathToProjectSource / "bin" / data.Configuration / "publish";
-
-            DotNetPublish(c => c
-                .SetProject(pathToProjectSource / "ControlCatalog.NetCore.csproj")
-                .EnableNoBuild()
-                .SetConfiguration(data.Configuration)
-                .AddProperty("PackageVersion", data.Version)
-                .AddProperty("PublishDir", pathToPublish));
-
-            Zip(data.ZipCoreArtifacts, data.BinRoot);
             Zip(data.ZipNuGetArtifacts, data.NugetRoot);
-            Zip(data.ZipTargetControlCatalogNetCoreDir, pathToPublish);
         });
 
     Target CreateIntermediateNugetPackages => _ => _
@@ -283,15 +245,7 @@ partial class Build : NukeBuild
         .After(RunTests)
         .Executes(() =>
         {
-            if (Parameters.IsRunningOnWindows)
-
-                MsBuildCommon(Parameters.MSBuildSolution, c => c
-                    .AddTargets("Pack"));
-            else
-                DotNetPack(c => c
-                    .SetProject(Parameters.MSBuildSolution)
-                    .SetConfiguration(Parameters.Configuration)
-                    .AddProperty("PackageVersion", Parameters.Version));
+            DotNetPack(c => ApplySetting(c).SetProject(Parameters.MSBuildSolution));
         });
 
     Target CreateNugetPackages => _ => _

+ 0 - 4
nukebuild/BuildParameters.cs

@@ -51,14 +51,12 @@ public partial class Build
         public AbsolutePath NugetIntermediateRoot { get; }
         public AbsolutePath NugetRoot { get; }
         public AbsolutePath ZipRoot { get; }
-        public AbsolutePath BinRoot { get; }
         public AbsolutePath TestResultsRoot { get; }
         public string DirSuffix { get; }
         public List<string> BuildDirs { get; }
         public string FileZipSuffix { get; }
         public AbsolutePath ZipCoreArtifacts { get; }
         public AbsolutePath ZipNuGetArtifacts { get; }
-        public AbsolutePath ZipTargetControlCatalogNetCoreDir { get; }
 
 
         public BuildParameters(Build b)
@@ -121,14 +119,12 @@ public partial class Build
             NugetRoot = ArtifactsDir / "nuget";
             NugetIntermediateRoot = RootDirectory / "build-intermediate" / "nuget";
             ZipRoot = ArtifactsDir / "zip";
-            BinRoot = ArtifactsDir / "bin";
             TestResultsRoot = ArtifactsDir / "test-results";
             BuildDirs = GlobDirectories(RootDirectory, "**bin").Concat(GlobDirectories(RootDirectory, "**obj")).ToList();
             DirSuffix = Configuration;
             FileZipSuffix = Version + ".zip";
             ZipCoreArtifacts = ZipRoot / ("Avalonia-" + FileZipSuffix);
             ZipNuGetArtifacts = ZipRoot / ("Avalonia-NuGet-" + FileZipSuffix);
-            ZipTargetControlCatalogNetCoreDir = ZipRoot / ("ControlCatalog.NetCore-" + FileZipSuffix);
         }
 
         string GetVersion()

+ 57 - 0
nukebuild/DotNetConfigHelper.cs

@@ -0,0 +1,57 @@
+using System.Globalization;
+using JetBrains.Annotations;
+using Nuke.Common.Tools.DotNet;
+// ReSharper disable ReturnValueOfPureMethodIsNotUsed
+
+public class DotNetConfigHelper
+{
+    public DotNetBuildSettings Build;
+    public DotNetPackSettings Pack;
+    public DotNetTestSettings Test;
+
+    public DotNetConfigHelper(DotNetBuildSettings s)
+    {
+        Build = s;
+    }
+
+    public DotNetConfigHelper(DotNetPackSettings s)
+    {
+        Pack = s;
+    }
+
+    public DotNetConfigHelper(DotNetTestSettings s)
+    {
+        Test = s;
+    }
+
+    public DotNetConfigHelper AddProperty(string key, bool value) =>
+        AddProperty(key, value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
+    public DotNetConfigHelper AddProperty(string key, string value)
+    {
+        Build = Build?.AddProperty(key, value);
+        Pack = Pack?.AddProperty(key, value);
+        Test = Test?.AddProperty(key, value);
+
+        return this;
+    }
+
+    public DotNetConfigHelper SetConfiguration(string configuration)
+    {
+        Build = Build?.SetConfiguration(configuration);
+        Pack = Pack?.SetConfiguration(configuration);
+        Test = Test?.SetConfiguration(configuration);
+        return this;
+    }
+
+    public DotNetConfigHelper SetVerbosity(DotNetVerbosity verbosity)
+    {
+        Build = Build?.SetVerbosity(verbosity);
+        Pack = Pack?.SetVerbostiy(verbosity);
+        Test = Test?.SetVerbosity(verbosity);
+        return this;
+    }
+
+    public static implicit operator DotNetConfigHelper(DotNetBuildSettings s) => new DotNetConfigHelper(s);
+    public static implicit operator DotNetConfigHelper(DotNetPackSettings s) => new DotNetConfigHelper(s);
+    public static implicit operator DotNetConfigHelper(DotNetTestSettings s) => new DotNetConfigHelper(s);
+}

+ 0 - 4
samples/ControlCatalog.NetCore/Program.cs

@@ -115,10 +115,6 @@ namespace ControlCatalog.NetCore
                     UseDBusMenu = true,
                     EnableIme = true
                 })
-                .With(new Win32PlatformOptions
-                {
-                    EnableMultitouch = true
-                })
                 .UseSkia()
                 .AfterSetup(builder =>
                 {

+ 34 - 0
samples/ControlCatalog/Converter/HexConverter.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Globalization;
+using Avalonia;
+using Avalonia.Data.Converters;
+
+namespace ControlCatalog.Converter;
+
+public class HexConverter : IValueConverter
+{
+    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        var str = value?.ToString();
+        if (str == null)
+            return AvaloniaProperty.UnsetValue;
+        if (int.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int x))
+            return (decimal)x;
+        return AvaloniaProperty.UnsetValue;
+
+    }
+
+    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        try
+        {
+            if (value is decimal d)
+                return ((int)d).ToString("X8");
+            return AvaloniaProperty.UnsetValue;
+        }
+        catch
+        {
+            return AvaloniaProperty.UnsetValue;
+        }
+    }
+}

+ 1 - 1
samples/ControlCatalog/DecoratedWindow.xaml

@@ -2,7 +2,7 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         x:Class="ControlCatalog.DecoratedWindow"
         Title="Avalonia Control Gallery"
-        xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window">
+        xmlns:local="clr-namespace:ControlCatalog" SystemDecorations="None" Name="Window">
         <NativeMenu.Menu>
     <NativeMenu>
       <NativeMenuItem Header="Decorated">

+ 3 - 3
samples/ControlCatalog/MainView.xaml.cs

@@ -22,8 +22,8 @@ namespace ControlCatalog
 
             if (AvaloniaLocator.Current?.GetService<IRuntimePlatform>()?.GetRuntimeInfo().IsDesktop == true)
             {
-                IList tabItems = ((IList)sideBar.Items);
-                tabItems.Add(new TabItem()
+                var tabItems = (sideBar.Items as IList);
+                tabItems?.Add(new TabItem()
                 {
                     Header = "Screens",
                     Content = new ScreenPage()
@@ -36,7 +36,7 @@ namespace ControlCatalog
             {
                 if (themes.SelectedItem is CatalogTheme theme)
                 {
-                    var themeStyle = Application.Current.Styles[0];
+                    var themeStyle = Application.Current!.Styles[0];
                     if (theme == CatalogTheme.FluentLight)
                     {
                         if (App.Fluent.Mode != FluentThemeMode.Light)

+ 3 - 3
samples/ControlCatalog/MainWindow.xaml

@@ -18,15 +18,15 @@
     <NativeMenu>
       <NativeMenuItem Header="File">
         <NativeMenu>
-          <NativeMenuItem Icon="/Assets/test_icon.ico" Header="Open" Clicked="OnOpenClicked" Gesture="Ctrl+O"/>
-          <NativeMenuItemSeperator/><!-- Uses incorrect spelling to demonstrate backwards compatibility -->
+          <NativeMenuItem Icon="/Assets/test_icon.ico" Header="Open" Click="OnOpenClicked" Gesture="Ctrl+O"/>
+          <NativeMenuItemSeparator/>
           <NativeMenuItem Icon="/Assets/github_icon.png" Header="Recent">
             <NativeMenu/>
           </NativeMenuItem>
           <NativeMenuItemSeparator/>
           <NativeMenuItem Header="{x:Static local:MainWindow.MenuQuitHeader}"
                           Gesture="{x:Static local:MainWindow.MenuQuitGesture}"
-                          Clicked="OnCloseClicked" />
+                          Click="OnCloseClicked" />
         </NativeMenu>
       </NativeMenuItem>
       <NativeMenuItem Header="Edit">

+ 4 - 4
samples/ControlCatalog/Models/Person.cs

@@ -85,7 +85,7 @@ namespace ControlCatalog.Models
             }
             else
             {
-                if (_errorLookup.TryGetValue(propertyName, out List<string> errorList))
+                if (_errorLookup.TryGetValue(propertyName, out var errorList))
                 {
                     errorList.Clear();
                     errorList.Add(error!);
@@ -114,12 +114,12 @@ namespace ControlCatalog.Models
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
 
-        public IEnumerable? GetErrors(string propertyName)
+        public IEnumerable GetErrors(string? propertyName)
         {
-            if (_errorLookup.TryGetValue(propertyName, out List<string> errorList))
+            if (propertyName is { } && _errorLookup.TryGetValue(propertyName, out var errorList))
                 return errorList;
             else
-                return null;
+                return Array.Empty<object>();
         }
     }
 }

+ 1 - 1
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

@@ -28,7 +28,7 @@
       </StackPanel>
       <StackPanel>
         <TextBlock Text="MinimumPopulateDelay: 1s" />
-        <AutoCompleteBox MinimumPopulateDelay="1" />
+        <AutoCompleteBox MinimumPopulateDelay="00:00:01" />
       </StackPanel>
       <StackPanel>
         <TextBlock Text="MaxDropDownHeight: 60" />

+ 11 - 13
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@@ -1,8 +1,6 @@
 using Avalonia.Controls;
 using Avalonia.LogicalTree;
-using Avalonia.Markup;
 using Avalonia.Markup.Xaml;
-using Avalonia.Markup.Data;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -161,23 +159,23 @@ namespace ControlCatalog.Pages
         private bool LastWordContains(string? searchText, string? item)
         {
             var words = searchText?.Split(' ') ?? Array.Empty<string>();
-            var options = Sentences.Select(x => x.First).ToArray();
+            var options = Sentences.Select(x => x.First)
+                .ToArray<LinkedListNode<string>?>();
             for (var i = 0; i < words.Length; ++i)
             {
                 var word = words[i];
                 for (var j = 0; word is { } && j < options.Length; ++j)
                 {
-                    var option = options[j];
-                    if (option == null)
-                        continue;
-
-                    if (i == words.Length - 1)
-                    {
-                        options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null;
-                    }
-                    else
+                    if (options[i] is { } option)
                     {
-                        options[j] = option.Value.Equals(word, StringComparison.InvariantCultureIgnoreCase) ? option.Next : null;
+                        if (i == words.Length - 1)
+                        {
+                            options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null;
+                        }
+                        else
+                        {
+                            options[j] = option.Value.Equals(word, StringComparison.InvariantCultureIgnoreCase) ? option.Next : null;
+                        }
                     }
                 }
             }

+ 14 - 11
samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs

@@ -21,20 +21,23 @@ namespace ControlCatalog.Pages
         public void OnSpin(object sender, SpinEventArgs e)
         {
             var spinner = (ButtonSpinner)sender;
-            var txtBox = (TextBlock)spinner.Content;
 
-            int value = Array.IndexOf(_mountains, txtBox?.Text);
-            if (e.Direction == SpinDirection.Increase)
-                value++;
-            else
-                value--;
+            if (spinner.Content is TextBlock txtBox)
+            {
+                int value = Array.IndexOf(_mountains, txtBox.Text);
+                if (e.Direction == SpinDirection.Increase)
+                    value++;
+                else
+                    value--;
 
-            if (value < 0)
-                value = _mountains.Length - 1;
-            else if (value >= _mountains.Length)
-                value = 0;
+                if (value < 0)
+                    value = _mountains.Length - 1;
+                else if (value >= _mountains.Length)
+                    value = 0;
+
+                txtBox.Text = _mountains[value];
+            }
 
-            txtBox.Text = _mountains[value];
         }
 
         private readonly string[] _mountains = new[]

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

@@ -19,7 +19,7 @@ namespace ControlCatalog.Pages
             AvaloniaXamlLoader.Load(this);
         }
 
-        public void OnRepeatButtonClick(object sender, object args)
+        public void OnRepeatButtonClick(object? sender, object args)
         {
             repeatButtonClickCount++;
             var textBlock = this.Get<TextBlock>("RepeatButtonTextBlock");

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

@@ -33,7 +33,7 @@ namespace ControlCatalog.Pages
 
         }
 
-        private void TransitionChanged(object sender, SelectionChangedEventArgs e)
+        private void TransitionChanged(object? sender, SelectionChangedEventArgs e)
         {
             switch (_transition.SelectedIndex)
             {

+ 47 - 23
samples/ControlCatalog/Pages/ClipboardPage.xaml.cs

@@ -23,55 +23,79 @@ namespace ControlCatalog.Pages
             AvaloniaXamlLoader.Load(this);
         }
 
-        private async void CopyText(object sender, RoutedEventArgs args)
+        private async void CopyText(object? sender, RoutedEventArgs args)
         {
-            await Application.Current.Clipboard.SetTextAsync(ClipboardContent.Text);
+            if (Application.Current!.Clipboard is { } clipboard && ClipboardContent is { } clipboardContent)
+                await clipboard.SetTextAsync(clipboardContent.Text ?? String.Empty);
         }
 
-        private async void PasteText(object sender, RoutedEventArgs args)
+        private async void PasteText(object? sender, RoutedEventArgs args)
         {
-            ClipboardContent.Text = await Application.Current.Clipboard.GetTextAsync();
+            if(Application.Current!.Clipboard is { } clipboard)
+            {
+                ClipboardContent.Text = await clipboard.GetTextAsync();
+            }
         }
 
-        private async void CopyTextDataObject(object sender, RoutedEventArgs args)
+        private async void CopyTextDataObject(object? sender, RoutedEventArgs args)
         {
-            var dataObject = new DataObject();
-            dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty);
-            await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
+            if (Application.Current!.Clipboard is { } clipboard)
+            {
+                var dataObject = new DataObject();
+                dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty);
+                await clipboard.SetDataObjectAsync(dataObject);
+            }
         }
 
-        private async void PasteTextDataObject(object sender, RoutedEventArgs args)
+        private async void PasteTextDataObject(object? sender, RoutedEventArgs args)
         {
-            ClipboardContent.Text = await Application.Current.Clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty;
+            if (Application.Current!.Clipboard is { } clipboard)
+            {
+                ClipboardContent.Text = await clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty;
+            }
         }
 
-        private async void CopyFilesDataObject(object sender, RoutedEventArgs args)
+        private async void CopyFilesDataObject(object? sender, RoutedEventArgs args)
         {
-            var files = ClipboardContent.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
-            if (files.Length == 0)
+            if (Application.Current!.Clipboard is { } clipboard)
             {
-                return;
+                var files = (ClipboardContent.Text ?? String.Empty)
+                .Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
+                if (files.Length == 0)
+                {
+                    return;
+                }
+                var dataObject = new DataObject();
+                dataObject.Set(DataFormats.FileNames, files);
+                await clipboard.SetDataObjectAsync(dataObject);
             }
-            var dataObject = new DataObject();
-            dataObject.Set(DataFormats.FileNames, files);
-            await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
         }
 
-        private async void PasteFilesDataObject(object sender, RoutedEventArgs args)
+        private async void PasteFilesDataObject(object? sender, RoutedEventArgs args)
         {
-            var fiels = await Application.Current.Clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable<string>;
-            ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty;
+            if (Application.Current!.Clipboard is { } clipboard)
+            {
+                var fiels = await clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable<string>;
+                ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty;
+            }
         }
 
         private async void GetFormats(object sender, RoutedEventArgs args)
         {
-            var formats = await Application.Current.Clipboard.GetFormatsAsync();
-            ClipboardContent.Text = string.Join(Environment.NewLine, formats);
+            if (Application.Current!.Clipboard is { } clipboard)
+            {
+                var formats = await clipboard.GetFormatsAsync();
+                ClipboardContent.Text = string.Join(Environment.NewLine, formats);
+            }
         }
 
         private async void Clear(object sender, RoutedEventArgs args)
         {
-            await Application.Current.Clipboard.ClearAsync();
+            if (Application.Current!.Clipboard is { } clipboard)
+            {
+                await clipboard.ClearAsync();
+            }
+                
         }
     }
 }

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

@@ -17,7 +17,7 @@ namespace ControlCatalog.Pages
         private void InitializeComponent()
         {
             AvaloniaXamlLoader.Load(this);
-            var fontComboBox = this.Find<ComboBox>("fontComboBox");
+            var fontComboBox = this.Get<ComboBox>("fontComboBox");
             fontComboBox.Items = FontManager.Current.GetInstalledFontFamilyNames().Select(x => new FontFamily(x));
             fontComboBox.SelectedIndex = 0;
         }

+ 8 - 12
samples/ControlCatalog/Pages/CompositionPage.axaml.cs

@@ -1,14 +1,8 @@
 using System;
 using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-using System.Threading.Tasks;
 using Avalonia;
 using Avalonia.Controls;
-using Avalonia.Controls.Primitives;
-using Avalonia.Interactivity;
 using Avalonia.Markup.Xaml;
-using Avalonia.Markup.Xaml.Templates;
 using Avalonia.Media;
 using Avalonia.Rendering.Composition;
 using Avalonia.Rendering.Composition.Animations;
@@ -18,7 +12,7 @@ namespace ControlCatalog.Pages;
 
 public partial class CompositionPage : UserControl
 {
-    private ImplicitAnimationCollection _implicitAnimations;
+    private ImplicitAnimationCollection? _implicitAnimations;
 
     public CompositionPage()
     {
@@ -28,7 +22,7 @@ public partial class CompositionPage : UserControl
     protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
     {
         base.OnAttachedToVisualTree(e);
-        this.FindControl<ItemsControl>("Items").Items = CreateColorItems();
+        this.Get<ItemsControl>("Items").Items = CreateColorItems();
     }
 
     private List<CompositionPageColorItem> CreateColorItems()
@@ -115,7 +109,6 @@ public partial class CompositionPage : UserControl
 
     public static void SetEnableAnimations(Border border, bool value)
     {
-        
         var page = border.FindAncestorOfType<CompositionPage>();
         if (page == null)
         {
@@ -127,8 +120,11 @@ public partial class CompositionPage : UserControl
             return;
 
         page.EnsureImplicitAnimations();
-        ElementComposition.GetElementVisual((Visual)border.GetVisualParent()).ImplicitAnimations =
-            page._implicitAnimations;
+        if (border.GetVisualParent() is Visual visualParent 
+            && ElementComposition.GetElementVisual(visualParent) is CompositionVisual compositionVisual)
+        {
+            compositionVisual.ImplicitAnimations = page._implicitAnimations;
+        }
     }
 }
 
@@ -150,4 +146,4 @@ public class CompositionPageColorItem
     {
         Color = color;
     }
-}
+}

+ 11 - 11
samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs

@@ -52,13 +52,13 @@ namespace ControlCatalog.Pages
             base.OnDataContextChanged(e);
         }
 
-        private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e)
+        private void ContextFlyoutPage_Closing(object? sender, CancelEventArgs e)
         {
             var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelCloseCheckBox");
             e.Cancel = cancelCloseCheckBox?.IsChecked ?? false;
         }
 
-        private void ContextFlyoutPage_Opening(object sender, EventArgs e)
+        private void ContextFlyoutPage_Opening(object? sender, EventArgs e)
         {
             if (e is CancelEventArgs cancelArgs)
             {
@@ -67,20 +67,20 @@ namespace ControlCatalog.Pages
             }
         }
 
-        private void CloseFlyout(object sender, RoutedEventArgs e)
+        private void CloseFlyout(object? sender, RoutedEventArgs e)
         {
             _textBox.ContextFlyout?.Hide();
         }
 
-        public void CustomContextRequested(object sender, ContextRequestedEventArgs e)
+        public void CustomContextRequested(object? sender, ContextRequestedEventArgs e)
         {
-            var border = (Border)sender;
-            var textBlock = (TextBlock)border.Child;
-
-            textBlock.Text = e.TryGetPosition(border, out var point)
-                ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}"
-                : "Context was requested without pointer";
-            e.Handled = true;
+            if (sender is Border border && border.Child is TextBlock textBlock)
+            {
+                textBlock.Text = e.TryGetPosition(border, out var point)
+                    ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}"
+                    : "Context was requested without pointer";
+                e.Handled = true;
+            }
         }
 
         private void InitializeComponent()

+ 12 - 11
samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs

@@ -35,30 +35,31 @@ namespace ControlCatalog.Pages
             base.OnDataContextChanged(e);
         }
 
-        private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e)
+        private void ContextFlyoutPage_Closing(object? sender, CancelEventArgs e)
         {
             var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelCloseCheckBox");
-            e.Cancel = cancelCloseCheckBox.IsChecked ?? false;
+            e.Cancel = cancelCloseCheckBox?.IsChecked ?? false;
         }
 
-        private void ContextFlyoutPage_Opening(object sender, EventArgs e)
+        private void ContextFlyoutPage_Opening(object? sender, EventArgs e)
         {
             if (e is CancelEventArgs cancelArgs)
             {
                 var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelOpenCheckBox");
-                cancelArgs.Cancel = cancelCloseCheckBox.IsChecked ?? false;
+                cancelArgs.Cancel = cancelCloseCheckBox?.IsChecked ?? false;
             }
         }
 
-        public void CustomContextRequested(object sender, ContextRequestedEventArgs e)
+        public void CustomContextRequested(object? sender, ContextRequestedEventArgs e)
         {
-            var border = (Border)sender;
-            var textBlock = (TextBlock)border.Child;
+            if (sender is Border border && border.Child is TextBlock textBlock)
+            {
+                textBlock.Text = e.TryGetPosition(border, out var point)
+                    ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}"
+                    : "Context was requested without pointer";
+                e.Handled = true;
+            }
 
-            textBlock.Text = e.TryGetPosition(border, out var point)
-                ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}"
-                : "Context was requested without pointer";
-            e.Handled = true;
         }
 
         private void InitializeComponent()

+ 2 - 2
samples/ControlCatalog/Pages/DataGridPage.xaml.cs

@@ -62,7 +62,7 @@ namespace ControlCatalog.Pages
             addButton.Click += (a, b) => collectionView3.AddNew();
         }
 
-        private void Dg1_LoadingRow(object sender, DataGridRowEventArgs e)
+        private void Dg1_LoadingRow(object? sender, DataGridRowEventArgs e)
         {
             e.Row.Header = e.Row.GetIndex() + 1;
         }
@@ -74,7 +74,7 @@ namespace ControlCatalog.Pages
 
         private class ReversedStringComparer : IComparer<object>, IComparer
         {
-            public int Compare(object x, object y)
+            public int Compare(object? x, object? y)
             {
                 if (x is string left && y is string right)
                 {

+ 10 - 3
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@@ -111,9 +111,16 @@ namespace ControlCatalog.Pages
                     Title = "Select folder",
                     Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null
                 }.ShowAsync(GetWindow());
-                lastSelectedDirectory = new BclStorageFolder(new System.IO.DirectoryInfo(result));
-                results.Items = new [] { result };
-                resultsVisible.IsVisible = result != null;
+                if (string.IsNullOrEmpty(result))
+                {
+                    resultsVisible.IsVisible = false;
+                }
+                else
+                {
+                    lastSelectedDirectory = new BclStorageFolder(new System.IO.DirectoryInfo(result));
+                    results.Items = new[] { result };
+                    resultsVisible.IsVisible = true;
+                }
             };
             this.Get<Button>("OpenBoth").Click += async delegate
             {

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

@@ -1,9 +1,9 @@
-using Avalonia.Controls;
-using Avalonia.Input;
-using Avalonia.Markup.Xaml;
-using System;
+using System;
 using System.Linq;
 using System.Reflection;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Markup.Xaml;
 
 namespace ControlCatalog.Pages
 {
@@ -27,9 +27,9 @@ namespace ControlCatalog.Pages
         void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects)
         {
             var dragMe = this.Get<Border>("DragMe" + suffix);
-            var dragState = this.Get<TextBlock>("DragState"+suffix);
+            var dragState = this.Get<TextBlock>("DragState" + suffix);
 
-            async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
+            async void DoDrag(object? sender, Avalonia.Input.PointerPressedEventArgs e)
             {
                 var dragData = new DataObject();
                 factory(dragData);
@@ -55,7 +55,7 @@ namespace ControlCatalog.Pages
                 }
             }
 
-            void DragOver(object sender, DragEventArgs e)
+            void DragOver(object? sender, DragEventArgs e)
             {
                 if (e.Source is Control c && c.Name == "MoveTarget")
                 {
@@ -73,7 +73,7 @@ namespace ControlCatalog.Pages
                     e.DragEffects = DragDropEffects.None;
             }
 
-            void Drop(object sender, DragEventArgs e)
+            void Drop(object? sender, DragEventArgs e)
             {
                 if (e.Source is Control c && c.Name == "MoveTarget")
                 {
@@ -83,11 +83,11 @@ namespace ControlCatalog.Pages
                 {
                     e.DragEffects = e.DragEffects & (DragDropEffects.Copy);
                 }
-                
+
                 if (e.Data.Contains(DataFormats.Text))
                     _DropState.Text = e.Data.GetText();
                 else if (e.Data.Contains(DataFormats.FileNames))
-                    _DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames());
+                    _DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames() ?? Array.Empty<string>());
                 else if (e.Data.Contains(CustomFormat))
                     _DropState.Text = "Custom: " + e.Data.Get(CustomFormat);
             }

+ 1 - 1
samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs

@@ -20,7 +20,7 @@ namespace ControlCatalog.Pages
             SetXamlTexts();
         }
 
-        private void Afp_DoubleTapped(object sender, RoutedEventArgs e)
+        private void Afp_DoubleTapped(object? sender, RoutedEventArgs e)
         {
             if (sender is Panel p)
             {

+ 5 - 5
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@@ -123,7 +123,7 @@ namespace ControlCatalog.Pages
             element.BringIntoView();
         }
 
-        private void RepeaterClick(object sender, PointerPressedEventArgs e)
+        private void RepeaterClick(object? sender, PointerPressedEventArgs e)
         {
             if ((e.Source as TextBlock)?.DataContext is ItemsRepeaterPageViewModel.Item item)
             {
@@ -132,7 +132,7 @@ namespace ControlCatalog.Pages
             }
         }
 
-        private void RepeaterOnKeyDown(object sender, KeyEventArgs e)
+        private void RepeaterOnKeyDown(object? sender, KeyEventArgs e)
         {
             if (e.Key == Key.F5)
             {
@@ -140,17 +140,17 @@ namespace ControlCatalog.Pages
             }
         }
 
-        private void scrollToLast_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        private void scrollToLast_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
         {
             ScrollTo(_viewModel.Items.Count - 1);
         }
 
-        private void scrollToRandom_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        private void scrollToRandom_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
         {
             ScrollTo(_random.Next(_viewModel.Items.Count - 1));
         }
 
-        private void scrollToSelected_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        private void scrollToSelected_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
         {
             ScrollTo(_selectedIndex);
         }

+ 14 - 2
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@@ -1,6 +1,7 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:sys="clr-namespace:System;assembly=netstandard"
+             xmlns:converter="clr-namespace:ControlCatalog.Converter"
              x:Class="ControlCatalog.Pages.NumericUpDownPage">
   <StackPanel Orientation="Vertical" Spacing="4"
               MaxWidth="800">
@@ -54,11 +55,11 @@
       <Grid Grid.Row="0" Grid.Column="2" Margin="8" RowDefinitions="Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto, Auto">
         <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Minimum:</TextBlock>
         <NumericUpDown Grid.Row="0" Grid.Column="1" Value="{Binding #upDown.Minimum}"
-                       CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Margin="2" HorizontalAlignment="Center"/>
+                       NumberFormat="{Binding #upDown.NumberFormat}" VerticalAlignment="Center" Margin="2" HorizontalAlignment="Center"/>
 
         <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Maximum:</TextBlock>
         <NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding #upDown.Maximum}"
-                       CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Margin="2" HorizontalAlignment="Center"/>
+                       NumberFormat="{Binding #upDown.NumberFormat}" VerticalAlignment="Center" Margin="2" HorizontalAlignment="Center"/>
 
         <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Increment:</TextBlock>
         <NumericUpDown Grid.Row="2" Grid.Column="1" Value="{Binding #upDown.Increment}" VerticalAlignment="Center"
@@ -97,6 +98,17 @@
           </DataValidationErrors.Error>
         </NumericUpDown>
       </StackPanel>
+
+      <StackPanel Orientation="Vertical" Margin="10">
+        <Label Target="HexUpDown" FontSize="14" FontWeight="Bold" VerticalAlignment="Center">NumericUpDown in HEX mode:</Label>
+        <NumericUpDown x:Name="HexUpDown" Value="0"
+                       VerticalAlignment="Center">
+          <NumericUpDown.TextConverter>
+            <converter:HexConverter></converter:HexConverter>
+          </NumericUpDown.TextConverter>
+        </NumericUpDown>
+        
+      </StackPanel>
     </WrapPanel>
 
   </StackPanel>

+ 2 - 4
samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs

@@ -2,9 +2,7 @@
 using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
-using Avalonia;
 using Avalonia.Controls;
-using Avalonia.Controls.Primitives;
 using Avalonia.Markup.Xaml;
 using MiniMvvm;
 
@@ -29,7 +27,7 @@ namespace ControlCatalog.Pages
     public class NumbersPageViewModel : ViewModelBase
     {
         private IList<FormatObject>? _formats;
-        private FormatObject _selectedFormat;
+        private FormatObject? _selectedFormat;
         private IList<Location>? _spinnerLocations;
 
         private double _doubleValue;
@@ -89,7 +87,7 @@ namespace ControlCatalog.Pages
             .Where(c => new[] { "en-US", "en-GB", "fr-FR", "ar-DZ", "zh-CH", "cs-CZ" }.Contains(c.Name))
             .ToArray();
 
-        public FormatObject SelectedFormat
+        public FormatObject? SelectedFormat
         {
             get { return _selectedFormat; }
             set { this.RaiseAndSetIfChanged(ref _selectedFormat, value); }

+ 3 - 3
samples/ControlCatalog/Pages/OpenGlPage.xaml.cs

@@ -68,7 +68,7 @@ namespace ControlCatalog.Pages
             set => SetAndRaise(DiscoProperty, ref _disco, value);
         }
 
-        private string _info;
+        private string _info = string.Empty;
 
         public static readonly DirectProperty<OpenGlPageControl, string> InfoProperty =
             AvaloniaProperty.RegisterDirect<OpenGlPageControl, string>("Info", o => o.Info, (o, v) => o.Info = v);
@@ -205,7 +205,7 @@ namespace ControlCatalog.Pages
         public OpenGlPageControl()
         {
             var name = typeof(OpenGlPage).Assembly.GetManifestResourceNames().First(x => x.Contains("teapot.bin"));
-            using (var sr = new BinaryReader(typeof(OpenGlPage).Assembly.GetManifestResourceStream(name)))
+            using (var sr = new BinaryReader(typeof(OpenGlPage).Assembly.GetManifestResourceStream(name)!))
             {
                 var buf = new byte[sr.ReadInt32()];
                 sr.Read(buf, 0, buf.Length);
@@ -345,7 +345,7 @@ namespace ControlCatalog.Pages
                     0.01f, 1000);
 
 
-            var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0));
+            var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, 1, 0));
             var model = Matrix4x4.CreateFromYawPitchRoll(_yaw, _pitch, _roll);
             var modelLoc = GL.GetUniformLocationString(_shaderProgram, "uModel");
             var viewLoc = GL.GetUniformLocationString(_shaderProgram, "uView");

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

@@ -174,9 +174,9 @@ Twist: {_lastProperties?.Twist}";
         var lastPointer = e.GetCurrentPoint(this);
         _lastProperties = lastPointer.Properties;
 
-        if (_lastProperties.PointerUpdateKind != PointerUpdateKind.Other)
+        if (_lastProperties?.PointerUpdateKind != PointerUpdateKind.Other)
         {
-            _lastNonOtherUpdateKind = _lastProperties.PointerUpdateKind;
+            _lastNonOtherUpdateKind = _lastProperties?.PointerUpdateKind;
         }
 
         if (e.RoutedEvent == PointerReleasedEvent && e.Pointer.Type == PointerType.Touch)

+ 18 - 15
samples/ControlCatalog/Pages/PointersPage.xaml.cs

@@ -1,8 +1,6 @@
 using System;
-using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Input;
-using Avalonia.Interactivity;
 using Avalonia.Markup.Xaml;
 
 namespace ControlCatalog.Pages;
@@ -31,43 +29,48 @@ public class PointersPage : UserControl
         border2.PointerExited += Border_PointerUpdated;
     }
 
-    private void Border_PointerUpdated(object sender, PointerEventArgs e)
+    private void Border_PointerUpdated(object? sender, PointerEventArgs e)
     {
-        var textBlock = (TextBlock)((Border)sender).Child;
-        var position = e.GetPosition((Border)sender);
-        textBlock.Text = @$"Type: {e.Pointer.Type}
+        if (sender is Border border && border.Child is TextBlock textBlock)
+        {
+            var position = e.GetPosition(border);
+            textBlock.Text = @$"Type: {e.Pointer.Type}
 Captured: {e.Pointer.Captured == sender}
 PointerId: {e.Pointer.Id}
 Position: {(int)position.X} {(int)position.Y}";
-        e.Handled = true;
+            e.Handled = true;
+        }
     }
 
-    private void Border_PointerCaptureLost(object sender, PointerCaptureLostEventArgs e)
+    private void Border_PointerCaptureLost(object? sender, PointerCaptureLostEventArgs e)
     {
-        var textBlock = (TextBlock)((Border)sender).Child;
-        textBlock.Text = @$"Type: {e.Pointer.Type}
+        if (sender is Border border && border.Child is TextBlock textBlock)
+        {
+            textBlock.Text = @$"Type: {e.Pointer.Type}
 Captured: {e.Pointer.Captured == sender}
 PointerId: {e.Pointer.Id}
 Position: ??? ???";
-        e.Handled = true;
+            e.Handled = true;
+
+        }
     }
 
-    private void Border_PointerReleased(object sender, PointerReleasedEventArgs e)
+    private void Border_PointerReleased(object? sender, PointerReleasedEventArgs e)
     {
         if (e.Pointer.Captured == sender)
         {
             e.Pointer.Capture(null);
             e.Handled = true;
         }
-        else
+        else if (e.Pointer.Captured is not null)
         {
             throw new InvalidOperationException("How?");
         }
     }
 
-    private void Border_PointerPressed(object sender, PointerPressedEventArgs e)
+    private void Border_PointerPressed(object? sender, PointerPressedEventArgs e)
     {
-        e.Pointer.Capture((Border)sender);
+        e.Pointer.Capture(sender as Border);
         e.Handled = true;
     }
 

+ 4 - 4
samples/ControlCatalog/ViewModels/ContextPageViewModel.cs

@@ -53,14 +53,14 @@ namespace ControlCatalog.ViewModels
             var window = View?.GetVisualRoot() as Window;
             if (window == null)
                 return;
-            var dialog = new OpenFileDialog();
-            var result = await dialog.ShowAsync(window);
+
+            var result = await window.StorageProvider.OpenFilePickerAsync(new Avalonia.Platform.Storage.FilePickerOpenOptions() { AllowMultiple = true });
 
             if (result != null)
             {
-                foreach (var path in result)
+                foreach (var file in result)
                 {
-                    System.Diagnostics.Debug.WriteLine($"Opened: {path}");
+                    System.Diagnostics.Debug.WriteLine($"Opened: {file.Name}");
                 }
             }
         }

+ 3 - 4
samples/ControlCatalog/ViewModels/MenuPageViewModel.cs

@@ -74,14 +74,13 @@ namespace ControlCatalog.ViewModels
             var window = View?.GetVisualRoot() as Window;
             if (window == null)
                 return;
-            var dialog = new OpenFileDialog();
-            var result = await dialog.ShowAsync(window);
+            var result = await window.StorageProvider.OpenFilePickerAsync(new Avalonia.Platform.Storage.FilePickerOpenOptions() { AllowMultiple = true });
 
             if (result != null)
             {
-                foreach (var path in result)
+                foreach (var file in result)
                 {
-                    System.Diagnostics.Debug.WriteLine($"Opened: {path}");
+                    System.Diagnostics.Debug.WriteLine($"Opened: {file.Name}");
                 }
             }
         }

+ 3 - 3
samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs

@@ -117,9 +117,9 @@ namespace ControlCatalog.ViewModels
             PageTransitions[3].Transition = new PageSlide(TimeSpan.FromMilliseconds(Duration), PageSlide.SlideAxis.Vertical);
 
             var compositeTransition = new CompositePageTransition();
-            compositeTransition.PageTransitions.Add(PageTransitions[1].Transition);
-            compositeTransition.PageTransitions.Add(PageTransitions[2].Transition);
-            compositeTransition.PageTransitions.Add(PageTransitions[3].Transition);
+            compositeTransition.PageTransitions.Add(PageTransitions[1].Transition!);
+            compositeTransition.PageTransitions.Add(PageTransitions[2].Transition!);
+            compositeTransition.PageTransitions.Add(PageTransitions[3].Transition!);
             PageTransitions[4].Transition = compositeTransition;
 
             PageTransitions[5].Transition = new CustomTransition(TimeSpan.FromMilliseconds(Duration));

+ 19 - 17
samples/IntegrationTestApp/MainWindow.axaml.cs

@@ -1,4 +1,3 @@
-using System;
 using System.Collections.Generic;
 using System.Linq;
 using Avalonia;
@@ -31,20 +30,23 @@ namespace IntegrationTestApp
 
         private void InitializeViewMenu()
         {
-            var mainTabs = this.FindControl<TabControl>("MainTabs");
+            var mainTabs = this.Get<TabControl>("MainTabs");
             var viewMenu = (NativeMenuItem)NativeMenu.GetMenu(this).Items[1];
 
-            foreach (TabItem tabItem in mainTabs.Items)
+            if (mainTabs.Items is not null)
             {
-                var menuItem = new NativeMenuItem
+                foreach (TabItem tabItem in mainTabs.Items)
                 {
-                    Header = (string)tabItem.Header!,
-                    IsChecked = tabItem.IsSelected,
-                    ToggleType = NativeMenuItemToggleType.Radio,
-                };
-
-                menuItem.Click += (s, e) => tabItem.IsSelected = true;
-                viewMenu.Menu.Items.Add(menuItem);
+                    var menuItem = new NativeMenuItem
+                    {
+                        Header = (string)tabItem.Header!,
+                        IsChecked = tabItem.IsSelected,
+                        ToggleType = NativeMenuItemToggleType.Radio,
+                    };
+
+                    menuItem.Click += (s, e) => tabItem.IsSelected = true;
+                    viewMenu?.Menu?.Items.Add(menuItem);
+                }
             }
         }
 
@@ -107,8 +109,8 @@ namespace IntegrationTestApp
 
         private void MenuClicked(object? sender, RoutedEventArgs e)
         {
-            var clickedMenuItemTextBlock = this.FindControl<TextBlock>("ClickedMenuItem");
-            clickedMenuItemTextBlock.Text = ((MenuItem)sender!).Header.ToString();
+            var clickedMenuItemTextBlock = this.Get<TextBlock>("ClickedMenuItem");
+            clickedMenuItemTextBlock.Text = (sender as MenuItem)?.Header?.ToString();
         }
 
         private void OnButtonClick(object? sender, RoutedEventArgs e)
@@ -116,13 +118,13 @@ namespace IntegrationTestApp
             var source = e.Source as Button;
 
             if (source?.Name == "ComboBoxSelectionClear")
-                this.FindControl<ComboBox>("BasicComboBox").SelectedIndex = -1;
+                this.Get<ComboBox>("BasicComboBox").SelectedIndex = -1;
             if (source?.Name == "ComboBoxSelectFirst")
-                this.FindControl<ComboBox>("BasicComboBox").SelectedIndex = 0;
+                this.Get<ComboBox>("BasicComboBox").SelectedIndex = 0;
             if (source?.Name == "ListBoxSelectionClear")
-                this.FindControl<ListBox>("BasicListBox").SelectedIndex = -1;
+                this.Get<ListBox>("BasicListBox").SelectedIndex = -1;
             if (source?.Name == "MenuClickedMenuItemReset")
-                this.FindControl<TextBlock>("ClickedMenuItem").Text = "None";
+                this.Get<TextBlock>("ClickedMenuItem").Text = "None";
             if (source?.Name == "ShowWindow")
                 ShowWindow();
             if (source?.Name == "SendToBack")

+ 136 - 129
samples/RenderDemo/Pages/DrawingPage.xaml

@@ -1,134 +1,141 @@
-<UserControl 
-    xmlns="https://github.com/avaloniaui" 
+<UserControl
+    xmlns="https://github.com/avaloniaui"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     x:Class="RenderDemo.Pages.DrawingPage">
-    <UserControl.Styles>
-        <Style>
-            <Style.Resources>
-                <DrawingGroup x:Key="Bulb">
-                    <DrawingGroup.Transform>
-                        <MatrixTransform Matrix="1,0,0,1,0,-1028.4" />
-                    </DrawingGroup.Transform>
-                    <DrawingGroup>
-                        <DrawingGroup.Transform>
-                            <MatrixTransform Matrix="1,0,0,1.25,-10,1031.4" />
-                        </DrawingGroup.Transform>
-                        <GeometryDrawing Brush="#FF7F8C8D"
-                                         Geometry="F1 M24,14 A2,2,0,1,1,20,14 A2,2,0,1,1,24,14 z" />
-                    </DrawingGroup>
-                    <GeometryDrawing Brush="#FFF39C12"
-                                     Geometry="F1 M12,1030.4 C8.134,1030.4 5,1033.6 5,1037.6 5,1040.7 8.125,1043.5 9,1045.4 9.875,1047.2 9,1050.4 9,1050.4 L12,1049.9 15,1050.4 C15,1050.4 14.125,1047.2 15,1045.4 15.875,1043.5 19,1040.7 19,1037.6 19,1033.6 15.866,1030.4 12,1030.4 z" />
-                    <GeometryDrawing Brush="#FFF1C40F"
-                                     Geometry="F1 M12,1030.4 C15.866,1030.4 19,1033.6 19,1037.6 19,1040.7 15.875,1043.5 15,1045.4 14.125,1047.2 15,1050.4 15,1050.4 L12,1049.9 12,1030.4 z" />
-                    <GeometryDrawing Brush="#FFE67E22"
-                                     Geometry="F1 M9,1036.4 L8,1037.4 12,1049.4 16,1037.4 15,1036.4 14,1037.4 13,1036.4 12,1037.4 11,1036.4 10,1037.4 9,1036.4 z M9,1037.4 L10,1038.4 10.5,1037.9 11,1037.4 11.5,1037.9 12,1038.4 12.5,1037.9 13,1037.4 13.5,1037.9 14,1038.4 15,1037.4 15.438,1037.8 12,1048.1 8.5625,1037.8 9,1037.4 z" />
-                    <DrawingGroup>
-                        <DrawingGroup.Transform>
-                            <MatrixTransform Matrix="1,0,0,1,9,1045.4" />
-                        </DrawingGroup.Transform>
-                        <GeometryDrawing Brush="#FFBDC3C7">
-                            <GeometryDrawing.Geometry>
-                                <RectangleGeometry Rect="0,0,6,5" />
-                            </GeometryDrawing.Geometry>
-                        </GeometryDrawing>
-                    </DrawingGroup>
-                    <GeometryDrawing Brush="#FF95A5A6"
-                                     Geometry="F1 M9,1045.4 L9,1050.4 12,1050.4 12,1049.4 15,1049.4 15,1048.4 12,1048.4 12,1047.4 15,1047.4 15,1046.4 12,1046.4 12,1045.4 9,1045.4 z" />
-                    <GeometryDrawing Brush="#FF7F8C8D"
-                                     Geometry="F1 M9,1046.4 L9,1047.4 12,1047.4 12,1046.4 9,1046.4 z M9,1048.4 L9,1049.4 12,1049.4 12,1048.4 9,1048.4 z" />
-                </DrawingGroup>
-            </Style.Resources>
-        </Style>
-    </UserControl.Styles>
-    <Grid RowDefinitions="Auto,Auto,Auto"
-          ColumnDefinitions="Auto,Auto,Auto,Auto">
-        <TextBlock Text="None"
-                   Margin="3" />
-        <Border Grid.Column="0"
-                Grid.Row="1"
-                VerticalAlignment="Top"
-                HorizontalAlignment="Left"
-                BorderThickness="1"
-                BorderBrush="Gray"
-                Margin="5">
-            <DrawingPresenter Drawing="{DynamicResource Bulb}" />
-        </Border>
-        <TextBlock Text="Fill"
-                   Margin="3"
-                   Grid.Column="1" />
-        <Border Grid.Column="1"
-                Grid.Row="1"
-                VerticalAlignment="Top"
-                HorizontalAlignment="Left"
-                BorderThickness="1"
-                BorderBrush="Gray"
-                Margin="5">
-            <DrawingPresenter Drawing="{DynamicResource Bulb}"
-                              Width="100"
-                              Height="50"
-                              Stretch="Fill" />
-        </Border>
-        <TextBlock Text="Uniform"
-                   Margin="3"
-                   Grid.Column="2" />
-        <Border Grid.Column="2"
-                Grid.Row="1"
-                VerticalAlignment="Top"
-                HorizontalAlignment="Left"
-                BorderThickness="1"
-                BorderBrush="Gray"
-                Margin="5">
-            <DrawingPresenter Drawing="{DynamicResource Bulb}"
-                              Width="100"
-                              Height="50"
-                              Stretch="Uniform" />
-        </Border>
-        <TextBlock Text="UniformToFill"
-                   Margin="3"
-                   Grid.Column="3" />
-        <Border Grid.Column="3"
-                Grid.Row="1"
-                VerticalAlignment="Top"
-                HorizontalAlignment="Left"
-                BorderThickness="1"
-                BorderBrush="Gray"
-                Margin="5">
-            <DrawingPresenter Drawing="{DynamicResource Bulb}"
-                              Width="100"
-                              Height="50"
-                              Stretch="UniformToFill" />
-        </Border>
+  <UserControl.Styles>
+    <Style>
+      <Style.Resources>
+        <DrawingGroup x:Key="Bulb">
+          <DrawingGroup.Transform>
+            <MatrixTransform Matrix="1,0,0,1,0,-1028.4" />
+          </DrawingGroup.Transform>
+          <DrawingGroup>
+            <DrawingGroup.Transform>
+              <MatrixTransform Matrix="1,0,0,1.25,-10,1031.4" />
+            </DrawingGroup.Transform>
+            <GeometryDrawing Brush="#FF7F8C8D"
+                             Geometry="F1 M24,14 A2,2,0,1,1,20,14 A2,2,0,1,1,24,14 z" />
+          </DrawingGroup>
+          <GeometryDrawing Brush="#FFF39C12"
+                           Geometry="F1 M12,1030.4 C8.134,1030.4 5,1033.6 5,1037.6 5,1040.7 8.125,1043.5 9,1045.4 9.875,1047.2 9,1050.4 9,1050.4 L12,1049.9 15,1050.4 C15,1050.4 14.125,1047.2 15,1045.4 15.875,1043.5 19,1040.7 19,1037.6 19,1033.6 15.866,1030.4 12,1030.4 z" />
+          <GeometryDrawing Brush="#FFF1C40F"
+                           Geometry="F1 M12,1030.4 C15.866,1030.4 19,1033.6 19,1037.6 19,1040.7 15.875,1043.5 15,1045.4 14.125,1047.2 15,1050.4 15,1050.4 L12,1049.9 12,1030.4 z" />
+          <GeometryDrawing Brush="#FFE67E22"
+                           Geometry="F1 M9,1036.4 L8,1037.4 12,1049.4 16,1037.4 15,1036.4 14,1037.4 13,1036.4 12,1037.4 11,1036.4 10,1037.4 9,1036.4 z M9,1037.4 L10,1038.4 10.5,1037.9 11,1037.4 11.5,1037.9 12,1038.4 12.5,1037.9 13,1037.4 13.5,1037.9 14,1038.4 15,1037.4 15.438,1037.8 12,1048.1 8.5625,1037.8 9,1037.4 z" />
+          <DrawingGroup>
+            <DrawingGroup.Transform>
+              <MatrixTransform Matrix="1,0,0,1,9,1045.4" />
+            </DrawingGroup.Transform>
+            <GeometryDrawing Brush="#FFBDC3C7">
+              <GeometryDrawing.Geometry>
+                <RectangleGeometry Rect="0,0,6,5" />
+              </GeometryDrawing.Geometry>
+            </GeometryDrawing>
+          </DrawingGroup>
+          <GeometryDrawing Brush="#FF95A5A6"
+                           Geometry="F1 M9,1045.4 L9,1050.4 12,1050.4 12,1049.4 15,1049.4 15,1048.4 12,1048.4 12,1047.4 15,1047.4 15,1046.4 12,1046.4 12,1045.4 9,1045.4 z" />
+          <GeometryDrawing Brush="#FF7F8C8D"
+                           Geometry="F1 M9,1046.4 L9,1047.4 12,1047.4 12,1046.4 9,1046.4 z M9,1048.4 L9,1049.4 12,1049.4 12,1048.4 9,1048.4 z" />
+        </DrawingGroup>
+      </Style.Resources>
+    </Style>
+  </UserControl.Styles>
+  <Grid RowDefinitions="Auto,Auto,Auto"
+        ColumnDefinitions="Auto,Auto,Auto,Auto">
+    <TextBlock Text="None"
+               Margin="3" />
+    <Border Grid.Column="0"
+            Grid.Row="1"
+            VerticalAlignment="Top"
+            HorizontalAlignment="Left"
+            BorderThickness="1"
+            BorderBrush="Gray"
+            Margin="5">
+      <Image>
+        <Image.Source>
+          <DrawingImage Drawing="{DynamicResource Bulb}" />
+        </Image.Source>
+      </Image>
+    </Border>
+    <TextBlock Text="Fill"
+               Margin="3"
+               Grid.Column="1" />
+    <Border Grid.Column="1"
+            Grid.Row="1"
+            VerticalAlignment="Top"
+            HorizontalAlignment="Left"
+            BorderThickness="1"
+            BorderBrush="Gray"
+            Margin="5">
+      <Image Width="100" Height="50" Stretch="Fill">
+        <Image.Source>
+          <DrawingImage Drawing="{DynamicResource Bulb}" />
+        </Image.Source>
+      </Image>
+    </Border>
+    <TextBlock Text="Uniform"
+               Margin="3"
+               Grid.Column="2" />
+    <Border Grid.Column="2"
+            Grid.Row="1"
+            VerticalAlignment="Top"
+            HorizontalAlignment="Left"
+            BorderThickness="1"
+            BorderBrush="Gray"
+            Margin="5">
+      <Image Width="100" Height="50" Stretch="Uniform">
+        <Image.Source>
+          <DrawingImage Drawing="{DynamicResource Bulb}" />
+        </Image.Source>
+      </Image>
+    </Border>
+    <TextBlock Text="UniformToFill"
+               Margin="3"
+               Grid.Column="3" />
+    <Border Grid.Column="3"
+            Grid.Row="1"
+            VerticalAlignment="Top"
+            HorizontalAlignment="Left"
+            BorderThickness="1"
+            BorderBrush="Gray"
+            Margin="5">
+      <Image Width="100" Height="50" Stretch="UniformToFill">
+        <Image.Source>
+          <DrawingImage Drawing="{DynamicResource Bulb}" />
+        </Image.Source>
+      </Image>
+    </Border>
 
-        <!-- For comparison -->
+    <!-- For comparison -->
 
-        <Ellipse Grid.Row="2"
-                 Grid.Column="0"
-                 Width="100"
-                 Height="50"
-                 Stretch="None"
-                 Fill="Blue"
-                 Margin="5"/>
-        <Ellipse Grid.Row="2"
-                 Grid.Column="1"
-                 Width="100"
-                 Height="50"
-                 Stretch="Fill"
-                 Fill="Blue"
-                 Margin="5" />
-        <Ellipse Grid.Row="2"
-                 Grid.Column="2"
-                 Width="100"
-                 Height="50"
-                 Stretch="Uniform"
-                 Fill="Blue"
-                 Margin="5" />
-        <Ellipse Grid.Row="2"
-                 Grid.Column="3"
-                 Width="100"
-                 Height="50"
-                 Stretch="UniformToFill"
-                 Fill="Blue"
-                 Margin="5" />
+    <Ellipse Grid.Row="2"
+             Grid.Column="0"
+             Width="100"
+             Height="50"
+             Stretch="None"
+             Fill="Blue"
+             Margin="5"/>
+    <Ellipse Grid.Row="2"
+             Grid.Column="1"
+             Width="100"
+             Height="50"
+             Stretch="Fill"
+             Fill="Blue"
+             Margin="5" />
+    <Ellipse Grid.Row="2"
+             Grid.Column="2"
+             Width="100"
+             Height="50"
+             Stretch="Uniform"
+             Fill="Blue"
+             Margin="5" />
+    <Ellipse Grid.Row="2"
+             Grid.Column="3"
+             Width="100"
+             Height="50"
+             Stretch="UniformToFill"
+             Fill="Blue"
+             Margin="5" />
 
-    </Grid>
-</UserControl>
+  </Grid>
+</UserControl>

+ 2 - 2
samples/RenderDemo/Pages/TextFormatterPage.axaml.cs

@@ -78,7 +78,7 @@ namespace RenderDemo.Pages
                 _defaultProperties = defaultProperties;
             }
             
-            public TextRun? GetTextRun(int textSourceIndex)
+            public TextRun GetTextRun(int textSourceIndex)
             {
                 if (textSourceIndex >= _text.Length * 2 + TextRun.DefaultTextSourceLength)
                 {
@@ -107,7 +107,7 @@ namespace RenderDemo.Pages
             public Control Control => _control;
             public override Size Size => _control.DesiredSize;
             public override double Baseline => 0;
-            public override TextRunProperties? Properties { get; }
+            public override TextRunProperties Properties { get; }
 
             public override void Draw(DrawingContext drawingContext, Point origin)
             {

+ 16 - 29
src/Android/Avalonia.Android/AndroidThreadingInterface.cs

@@ -27,46 +27,33 @@ namespace Avalonia.Android
         {
             if (interval.TotalMilliseconds < 10)
                 interval = TimeSpan.FromMilliseconds(10);
-            object l = new object();
+
             var stopped = false;
             Timer timer = null;
-            var scheduled = false;
             timer = new Timer(_ =>
             {
-                lock (l)
+                if (stopped)
+                    return;
+
+                EnsureInvokeOnMainThread(() =>
                 {
-                    if (stopped)
+                    try
                     {
-                        timer.Dispose();
-                        return;
+                        tick();
                     }
-                    if (scheduled)
-                        return;
-                    scheduled = true;
-                    EnsureInvokeOnMainThread(() =>
+                    finally
                     {
-                        try
-                        {
-                            tick();
-                        }
-                        finally
-                        {
-                            lock (l)
-                            {
-                                scheduled = false;
-                            }
-                        }
-                    });
-                }
-            }, null, TimeSpan.Zero, interval);
+                        if (!stopped)
+                            timer.Change(interval, Timeout.InfiniteTimeSpan);
+                    }
+                });
+            },
+            null, interval, Timeout.InfiniteTimeSpan);
 
             return Disposable.Create(() =>
             {
-                lock (l)
-                {
-                    stopped = true;
-                    timer.Dispose();
-                }
+                stopped = true;
+                timer.Dispose();
             });
         }
 

+ 0 - 12
src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs

@@ -47,18 +47,6 @@ namespace Avalonia.Android
             }
         }
 
-        [Obsolete("deprecated")]
-        public override void Invalidate(global::Android.Graphics.Rect dirty)
-        {
-            Invalidate();
-        }
-
-        [Obsolete("deprecated")]
-        public override void Invalidate(int l, int t, int r, int b)
-        {
-            Invalidate();
-        }
-
         public void SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height)
         {
             Log.Info("AVALONIA", "Surface Changed");

+ 13 - 4
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+
 using Android.Content;
 using Android.Graphics;
 using Android.Views;
@@ -30,7 +31,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
         private readonly IFramebufferPlatformSurface _framebuffer;
 
         private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
-        private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper;
+        private readonly AndroidMotionEventsHelper _pointerHelper;
         private readonly ITextInputMethodImpl _textInputMethod;
         private ViewImpl _view;
 
@@ -39,8 +40,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             _view = new ViewImpl(avaloniaView.Context, this, placeOnTop);
             _textInputMethod = new AndroidInputMethod<ViewImpl>(_view);
             _keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
-            _touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
-                GetAvaloniaPointFromEvent);
+            _pointerHelper = new AndroidMotionEventsHelper(this);
             _gl = GlPlatformSurface.TryCreate(this);
             _framebuffer = new FramebufferManager(this);
 
@@ -160,10 +160,19 @@ namespace Avalonia.Android.Platform.SkiaPlatform
                 _tl.Draw();
             }
 
+            protected override bool DispatchGenericPointerEvent(MotionEvent e)
+            {
+                bool callBase;
+                bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase);
+                bool baseResult = callBase ? base.DispatchGenericPointerEvent(e) : false;
+
+                return result != null ? result.Value : baseResult;
+            }
+
             public override bool DispatchTouchEvent(MotionEvent e)
             {
                 bool callBase;
-                bool? result = _tl._touchHelper.DispatchTouchEvent(e, out callBase);
+                bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase);
                 bool baseResult = callBase ? base.DispatchTouchEvent(e) : false;
 
                 return result != null ? result.Value : baseResult;

+ 251 - 0
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs

@@ -0,0 +1,251 @@
+using System;
+using System.Collections.Generic;
+
+using Android.Views;
+
+using Avalonia.Android.Platform.SkiaPlatform;
+using Avalonia.Collections.Pooled;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+
+#nullable enable
+
+namespace Avalonia.Android.Platform.Specific.Helpers
+{
+    internal class AndroidMotionEventsHelper : IDisposable
+    {
+        private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new(ClearMode.Never);
+        private static readonly float s_radiansToDegree = (float)(180f * Math.PI);
+        private readonly TouchDevice _touchDevice;
+        private readonly MouseDevice _mouseDevice;
+        private readonly PenDevice _penDevice;
+        private readonly TopLevelImpl _view;
+        private bool _disposed;
+
+        public AndroidMotionEventsHelper(TopLevelImpl view)
+        {
+            _touchDevice = new TouchDevice();
+            _penDevice = new PenDevice();
+            _mouseDevice = new MouseDevice();
+            _view = view;
+        }
+
+        public bool? DispatchMotionEvent(MotionEvent e, out bool callBase)
+        {
+            callBase = true;
+            if (_disposed)
+            {
+                return null;
+            }
+
+            var eventTime = (ulong)DateTime.Now.Millisecond;
+            var inputRoot = _view.InputRoot;
+            var actionMasked = e.ActionMasked;
+            var modifiers = GetModifiers(e.MetaState, e.ButtonState);
+
+            if (actionMasked == MotionEventActions.Move)
+            {
+                for (int index = 0; index < e.PointerCount; index++)
+                {
+                    var toolType = e.GetToolType(index);
+                    var device = GetDevice(toolType);
+                    var eventType = toolType == MotionEventToolType.Finger ? RawPointerEventType.TouchUpdate : RawPointerEventType.Move;
+                    var point = CreatePoint(e, index);
+                    modifiers |= GetToolModifiers(toolType);
+
+                    // ButtonState reports only mouse buttons, but not touch or stylus pointer.
+                    if (toolType != MotionEventToolType.Mouse)
+                    {
+                        modifiers |= RawInputModifiers.LeftMouseButton;
+                    }
+
+                    var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index))
+                    {
+                        IntermediatePoints = new Lazy<IReadOnlyList<RawPointerPoint>?>(() =>
+                        {
+                            var site = e.HistorySize;
+                            s_intermediatePointsPooledList.Clear();
+                            s_intermediatePointsPooledList.Capacity = site;
+
+                            for (int pos = 0; pos < site; pos++)
+                            {
+                                s_intermediatePointsPooledList.Add(CreateHistoricalPoint(e, index, pos));
+                            }
+
+                            return s_intermediatePointsPooledList;
+                        })
+                    };
+                    _view.Input(args);
+                }
+            }
+            else
+            {
+                var index = e.ActionIndex;
+                var toolType = e.GetToolType(index);
+                var device = GetDevice(toolType);
+                modifiers |= GetToolModifiers(toolType);
+                var point = CreatePoint(e, index);
+
+                if (actionMasked == MotionEventActions.Scroll && toolType == MotionEventToolType.Mouse)
+                {
+                    var delta = new Vector(e.GetAxisValue(Axis.Hscroll), e.GetAxisValue(Axis.Vscroll));
+                    var args = new RawMouseWheelEventArgs(device, eventTime, inputRoot, point.Position, delta, RawInputModifiers.None);
+                    _view.Input(args);
+                }
+                else
+                {
+                    var eventType = GetActionType(e, actionMasked, toolType);
+                    if (eventType >= 0)
+                    {
+                        var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index));
+                        _view.Input(args);
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        private static RawInputModifiers GetModifiers(MetaKeyStates metaState, MotionEventButtonState buttonState)
+        {
+            var modifiers = RawInputModifiers.None;
+            if (metaState.HasAnyFlag(MetaKeyStates.ShiftOn))
+            {
+                modifiers |= RawInputModifiers.Shift;
+            }
+            if (metaState.HasAnyFlag(MetaKeyStates.CtrlOn))
+            {
+                modifiers |= RawInputModifiers.Control;
+            }
+            if (metaState.HasAnyFlag(MetaKeyStates.AltOn))
+            {
+                modifiers |= RawInputModifiers.Alt;
+            }
+            if (metaState.HasAnyFlag(MetaKeyStates.MetaOn))
+            {
+                modifiers |= RawInputModifiers.Meta;
+            }
+            if (buttonState.HasAnyFlag(MotionEventButtonState.Primary))
+            {
+                modifiers |= RawInputModifiers.LeftMouseButton;
+            }
+            if (buttonState.HasAnyFlag(MotionEventButtonState.Secondary))
+            {
+                modifiers |= RawInputModifiers.RightMouseButton;
+            }
+            if (buttonState.HasAnyFlag(MotionEventButtonState.Tertiary))
+            {
+                modifiers |= RawInputModifiers.MiddleMouseButton;
+            }
+            if (buttonState.HasAnyFlag(MotionEventButtonState.Back))
+            {
+                modifiers |= RawInputModifiers.XButton1MouseButton;
+            }
+            if (buttonState.HasAnyFlag(MotionEventButtonState.Forward))
+            {
+                modifiers |= RawInputModifiers.XButton2MouseButton;
+            }
+            if (buttonState.HasAnyFlag(MotionEventButtonState.StylusPrimary))
+            {
+                modifiers |= RawInputModifiers.PenBarrelButton;
+            }
+            return modifiers;
+        }
+
+#pragma warning disable CA1416 // Validate platform compatibility
+        private static RawPointerEventType GetActionType(MotionEvent e, MotionEventActions actionMasked, MotionEventToolType toolType)
+        {
+            var isTouch = toolType == MotionEventToolType.Finger;
+            var isMouse = toolType == MotionEventToolType.Mouse;
+            switch (actionMasked)
+            {
+                // DOWN
+                case MotionEventActions.Down when !isMouse:
+                case MotionEventActions.PointerDown when !isMouse:
+                    return isTouch ? RawPointerEventType.TouchBegin : RawPointerEventType.LeftButtonDown;
+                case MotionEventActions.ButtonPress:
+                    return e.ActionButton switch
+                    {
+                        MotionEventButtonState.Back => RawPointerEventType.XButton1Down,
+                        MotionEventButtonState.Forward => RawPointerEventType.XButton2Down,
+                        MotionEventButtonState.Primary => RawPointerEventType.LeftButtonDown,
+                        MotionEventButtonState.Secondary => RawPointerEventType.RightButtonDown,
+                        MotionEventButtonState.StylusPrimary => RawPointerEventType.LeftButtonDown,
+                        MotionEventButtonState.StylusSecondary => RawPointerEventType.RightButtonDown,
+                        MotionEventButtonState.Tertiary => RawPointerEventType.MiddleButtonDown,
+                        _ => RawPointerEventType.LeftButtonDown
+                    };
+                // UP
+                case MotionEventActions.Up when !isMouse:
+                case MotionEventActions.PointerUp when !isMouse:
+                    return isTouch ? RawPointerEventType.TouchEnd : RawPointerEventType.LeftButtonUp;
+                case MotionEventActions.ButtonRelease:
+                    return e.ActionButton switch
+                    {
+                        MotionEventButtonState.Back => RawPointerEventType.XButton1Up,
+                        MotionEventButtonState.Forward => RawPointerEventType.XButton2Up,
+                        MotionEventButtonState.Primary => RawPointerEventType.LeftButtonUp,
+                        MotionEventButtonState.Secondary => RawPointerEventType.RightButtonUp,
+                        MotionEventButtonState.StylusPrimary => RawPointerEventType.LeftButtonUp,
+                        MotionEventButtonState.StylusSecondary => RawPointerEventType.RightButtonUp,
+                        MotionEventButtonState.Tertiary => RawPointerEventType.MiddleButtonUp,
+                        _ => RawPointerEventType.LeftButtonUp
+                    };
+                // MOVE
+                case MotionEventActions.Outside:
+                case MotionEventActions.HoverMove:
+                case MotionEventActions.Move:
+                    return isTouch ? RawPointerEventType.TouchUpdate : RawPointerEventType.Move;
+                // CANCEL
+                case MotionEventActions.Cancel:
+                    return isTouch ? RawPointerEventType.TouchCancel : RawPointerEventType.LeaveWindow;
+                default:
+                    return (RawPointerEventType)(-1);
+            }
+        }
+#pragma warning restore CA1416 // Validate platform compatibility
+
+        private IPointerDevice GetDevice(MotionEventToolType type)
+        {
+            return type switch
+            {
+                MotionEventToolType.Mouse => _mouseDevice,
+                MotionEventToolType.Stylus => _penDevice,
+                MotionEventToolType.Eraser => _penDevice,
+                MotionEventToolType.Finger => _touchDevice,
+                _ => _touchDevice
+            };
+        }
+
+        private RawPointerPoint CreatePoint(MotionEvent e, int index)
+        {
+            return new RawPointerPoint
+            {
+                Position = new Point(e.GetX(index), e.GetY(index)) / _view.RenderScaling,
+                Pressure = Math.Min(e.GetPressure(index), 1), // android pressure can depend on the device, can be mixed up with "GetSize", may be larger than 1.0f on some devices
+                Twist = e.GetOrientation(index) * s_radiansToDegree
+            };
+        }
+
+        private RawPointerPoint CreateHistoricalPoint(MotionEvent e, int index, int pos)
+        {
+            return new RawPointerPoint
+            {
+                Position = new Point(e.GetHistoricalX(index, pos), e.GetHistoricalY(index, pos)) / _view.RenderScaling,
+                Pressure = Math.Min(e.GetHistoricalPressure(index, pos), 1),
+                Twist = e.GetHistoricalOrientation(index, pos) * s_radiansToDegree
+            };
+        }
+
+        private static RawInputModifiers GetToolModifiers(MotionEventToolType toolType)
+        {
+            // Android "Eraser" indicates Inverted pen OR actual Eraser. So we have to go both here.
+            return toolType == MotionEventToolType.Eraser ? RawInputModifiers.PenInverted | RawInputModifiers.PenEraser : RawInputModifiers.None;
+        }
+
+        public void Dispose()
+        {
+            _disposed = true;
+        }
+    }
+}

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

@@ -1,85 +0,0 @@
-using System;
-using Android.Views;
-using Avalonia.Input;
-using Avalonia.Input.Raw;
-using Avalonia.Platform;
-
-namespace Avalonia.Android.Platform.Specific.Helpers
-{
-    public class AndroidTouchEventsHelper<TView> : IDisposable where TView : ITopLevelImpl, IAndroidView
-    {
-        private TView _view;
-        public bool HandleEvents { get; set; }
-
-        public AndroidTouchEventsHelper(TView view, Func<IInputRoot> getInputRoot, Func<MotionEvent, int, Point> getPointfunc)
-        {
-            this._view = view;
-            HandleEvents = true;
-            _getPointFunc = getPointfunc;
-            _getInputRoot = getInputRoot;
-        }
-
-        private TouchDevice _touchDevice = new TouchDevice();
-        private Func<MotionEvent, int, Point> _getPointFunc;
-        private Func<IInputRoot> _getInputRoot;
-
-        public bool? DispatchTouchEvent(MotionEvent e, out bool callBase)
-        {
-            if (!HandleEvents)
-            {
-                callBase = true;
-                return null;
-            }
-
-            var eventTime = DateTime.Now;
-
-            //Basic touch support
-            var pointerEventType = e.Action switch
-            {
-                MotionEventActions.Down => RawPointerEventType.TouchBegin,
-                MotionEventActions.Up => RawPointerEventType.TouchEnd,
-                MotionEventActions.Cancel => RawPointerEventType.TouchCancel,
-                _ => RawPointerEventType.TouchUpdate
-            };
-
-            if (e.Action.HasFlag(MotionEventActions.PointerDown))
-            {
-                pointerEventType = RawPointerEventType.TouchBegin;
-            }
-
-            if (e.Action.HasFlag(MotionEventActions.PointerUp))
-            {
-                pointerEventType = RawPointerEventType.TouchEnd;
-            }
-
-            for (int i = 0; i < e.PointerCount; i++)
-            {
-                //if point is in view otherwise it's possible avalonia not to find the proper window to dispatch the event
-                var point = _getPointFunc(e, i);
-
-                double x = _view.View.GetX();
-                double y = _view.View.GetY();
-                double r = x + _view.View.Width;
-                double b = y + _view.View.Height;
-
-                if (x <= point.X && r >= point.X && y <= point.Y && b >= point.Y)
-                {
-                    var inputRoot = _getInputRoot();
-
-                    var mouseEvent = new RawTouchEventArgs(_touchDevice, (uint)eventTime.Ticks, inputRoot,
-                        i == e.ActionIndex ? pointerEventType : RawPointerEventType.TouchUpdate, point, RawInputModifiers.None, e.GetPointerId(i));
-                    _view.Input(mouseEvent);
-                }
-            }
-
-            callBase = true;
-            //if return false events for move and up are not received!!!
-            return e.Action != MotionEventActions.Up;
-        }
-
-        public void Dispose()
-        {
-            HandleEvents = false;
-        }
-    }
-}

+ 35 - 45
src/Avalonia.Base/Animation/Animation.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Reactive.Disposables;
 using System.Reactive.Linq;
@@ -172,23 +173,6 @@ namespace Avalonia.Animation
             set { SetAndRaise(SpeedRatioProperty, ref _speedRatio, value); }
         }
 
-        /// <summary>
-        /// Obsolete: Do not use this property, use <see cref="IterationCount"/> instead.
-        /// </summary>
-        /// <value></value>
-        [Obsolete("This property has been superceded by IterationCount.")]
-        public string RepeatCount
-        {
-            get { return IterationCount.ToString(); }
-            set
-            {
-                var val = value.ToUpper();
-                val = val.Replace("LOOP", "INFINITE");
-                val = val.Replace("NONE", "1");
-                IterationCount = IterationCount.Parse(val);
-            }
-        }
-
         /// <summary>
         /// Gets the children of the <see cref="Animation"/>.
         /// </summary>
@@ -196,14 +180,14 @@ namespace Avalonia.Animation
         public KeyFrames Children { get; } = new KeyFrames();
 
         // Store values for the Animator attached properties for IAnimationSetter objects.
-        private static readonly Dictionary<IAnimationSetter, Type> s_animators = new Dictionary<IAnimationSetter, Type>();
+        private static readonly Dictionary<IAnimationSetter, (Type Type, Func<IAnimator> Factory)> s_animators = new();
 
         /// <summary>
         /// Gets the value of the Animator attached property for a setter.
         /// </summary>
         /// <param name="setter">The animation setter.</param>
         /// <returns>The property animator type.</returns>
-        public static Type? GetAnimator(IAnimationSetter setter)
+        public static (Type Type, Func<IAnimator> Factory)? GetAnimator(IAnimationSetter setter)
         {
             if (s_animators.TryGetValue(setter, out var type))
             {
@@ -217,24 +201,28 @@ namespace Avalonia.Animation
         /// </summary>
         /// <param name="setter">The animation setter.</param>
         /// <param name="value">The property animator value.</param>
-        public static void SetAnimator(IAnimationSetter setter, Type value)
+        public static void SetAnimator(IAnimationSetter setter, 
+#if NET6_0_OR_GREATER
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicMethods)]
+#endif
+            Type value)
         {
-            s_animators[setter] = value;
+            s_animators[setter] = (value, () => (IAnimator)Activator.CreateInstance(value)!);
         }
 
-        private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)>
+        private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator, Func<IAnimator> Factory)> Animators = new()
         {
-            ( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator) ),
-            ( prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator) ),
-            ( prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator) ),
-            ( prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator) ),
-            ( prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator) ),
-            ( prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator) ),
-            ( prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator) ),
-            ( prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator) ),
-            ( prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator) ),
-            ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ),
-            ( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator) ),
+            ( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator), () => new BoolAnimator() ),
+            ( prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator), () => new ByteAnimator() ),
+            ( prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator), () => new Int16Animator() ),
+            ( prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator), () => new Int32Animator() ),
+            ( prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator), () => new Int64Animator() ),
+            ( prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator), () => new UInt16Animator() ),
+            ( prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator), () => new UInt32Animator() ),
+            ( prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator), () => new UInt64Animator() ),
+            ( prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator), () => new FloatAnimator() ),
+            ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator), () => new DoubleAnimator() ),
+            ( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator), () => new DecimalAnimator() ),
         };
 
         /// <summary>
@@ -249,18 +237,18 @@ namespace Avalonia.Animation
         /// The type of the animator to instantiate.
         /// </typeparam>
         public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
-            where TAnimator : IAnimator
+            where TAnimator : IAnimator, new()
         {
-            Animators.Insert(0, (condition, typeof(TAnimator)));
+            Animators.Insert(0, (condition, typeof(TAnimator), () => new TAnimator()));
         }
 
-        private static Type? GetAnimatorType(AvaloniaProperty property)
+        private static (Type Type, Func<IAnimator> Factory)? GetAnimatorType(AvaloniaProperty property)
         {
-            foreach (var (condition, type) in Animators)
+            foreach (var (condition, type, factory) in Animators)
             {
                 if (condition(property))
                 {
-                    return type;
+                    return (type, factory);
                 }
             }
             return null;
@@ -268,7 +256,7 @@ namespace Avalonia.Animation
 
         private (IList<IAnimator> Animators, IList<IDisposable> subscriptions) InterpretKeyframes(Animatable control)
         {
-            var handlerList = new List<(Type type, AvaloniaProperty property)>();
+            var handlerList = new Dictionary<(Type type, AvaloniaProperty Property), Func<IAnimator>>();
             var animatorKeyFrames = new List<AnimatorKeyFrame>();
             var subscriptions = new List<IDisposable>();
 
@@ -288,8 +276,10 @@ namespace Avalonia.Animation
                         throw new InvalidOperationException($"No animator registered for the property {setter.Property}. Add an animator to the Animation.Animators collection that matches this property to animate it.");
                     }
 
-                    if (!handlerList.Contains((handler, setter.Property)))
-                        handlerList.Add((handler, setter.Property));
+                    var (type, factory) = handler.Value;
+
+                    if (!handlerList.ContainsKey((type, setter.Property)))
+                        handlerList[(type, setter.Property)] = factory;
 
                     var cue = keyframe.Cue;
 
@@ -298,7 +288,7 @@ namespace Avalonia.Animation
                         cue = new Cue(keyframe.KeyTime.TotalSeconds / Duration.TotalSeconds);
                     }
 
-                    var newKF = new AnimatorKeyFrame(handler, cue, keyframe.KeySpline);
+                    var newKF = new AnimatorKeyFrame(type, factory, cue, keyframe.KeySpline);
 
                     subscriptions.Add(newKF.BindSetter(setter, control));
 
@@ -308,10 +298,10 @@ namespace Avalonia.Animation
 
             var newAnimatorInstances = new List<IAnimator>();
 
-            foreach (var (handlerType, property) in handlerList)
+            foreach (var handler in handlerList)
             {
-                var newInstance = (IAnimator)Activator.CreateInstance(handlerType)!;
-                newInstance.Property = property;
+                var newInstance = handler.Value();
+                newInstance.Property = handler.Key.Property;
                 newAnimatorInstances.Add(newInstance);
             }
 

+ 5 - 2
src/Avalonia.Base/Animation/AnimatorKeyFrame.cs

@@ -20,22 +20,25 @@ namespace Avalonia.Animation
 
         }
 
-        public AnimatorKeyFrame(Type? animatorType, Cue cue)
+        public AnimatorKeyFrame(Type? animatorType, Func<IAnimator>? animatorFactory, Cue cue)
         {
             AnimatorType = animatorType;
+            AnimatorFactory = animatorFactory;
             Cue = cue;
             KeySpline = null;
         }
 
-        public AnimatorKeyFrame(Type? animatorType, Cue cue, KeySpline? keySpline)
+        public AnimatorKeyFrame(Type? animatorType, Func<IAnimator>? animatorFactory, Cue cue, KeySpline? keySpline)
         {
             AnimatorType = animatorType;
+            AnimatorFactory = animatorFactory;
             Cue = cue;
             KeySpline = keySpline;
         }
 
         internal bool isNeutral;
         public Type? AnimatorType { get; }
+        public Func<IAnimator>? AnimatorFactory { get; }
         public Cue Cue { get; }
         public KeySpline? KeySpline { get; }
         public AvaloniaProperty? Property { get; private set; }

+ 2 - 2
src/Avalonia.Base/Animation/Animators/Animator`1.cs

@@ -171,12 +171,12 @@ namespace Avalonia.Animation.Animators
         {
             if (!hasStartKey)
             {
-                _convertedKeyframes.Insert(0, new AnimatorKeyFrame(null, new Cue(0.0d)) { Value = default(T), isNeutral = true });
+                _convertedKeyframes.Insert(0, new AnimatorKeyFrame(null, null, new Cue(0.0d)) { Value = default(T), isNeutral = true });
             }
 
             if (!hasEndKey)
             {
-                _convertedKeyframes.Add(new AnimatorKeyFrame(null, new Cue(1.0d)) { Value = default(T), isNeutral = true });
+                _convertedKeyframes.Add(new AnimatorKeyFrame(null, null, new Cue(1.0d)) { Value = default(T), isNeutral = true });
             }
         }
     }

+ 8 - 9
src/Avalonia.Base/Animation/Animators/BaseBrushAnimator.cs

@@ -17,8 +17,7 @@ namespace Avalonia.Animation.Animators
     /// </summary>
     public class BaseBrushAnimator : Animator<IBrush?>
     {
-        private static readonly List<(Func<Type, bool> Match, Type AnimatorType)> _brushAnimators =
-            new List<(Func<Type, bool> Match, Type AnimatorType)>();
+        private static readonly List<(Func<Type, bool> Match, Type AnimatorType, Func<IAnimator> AnimatorFactory)> _brushAnimators = new();
 
         /// <summary>
         /// Register an <see cref="Animator{T}"/> that handles a specific
@@ -34,7 +33,7 @@ namespace Avalonia.Animation.Animators
         public static void RegisterBrushAnimator<TAnimator>(Func<Type, bool> condition)
             where TAnimator : IAnimator, new()
         {
-            _brushAnimators.Insert(0, (condition, typeof(TAnimator)));
+            _brushAnimators.Insert(0, (condition, typeof(TAnimator), () => new TAnimator()));
         }
 
         /// <inheritdoc/>
@@ -85,14 +84,14 @@ namespace Avalonia.Animation.Animators
             {
                 if (keyframe.Value is ISolidColorBrush solidColorBrush)
                 {
-                    gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
+                    gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), () => new GradientBrushAnimator(), keyframe.Cue, keyframe.KeySpline)
                     {
                         Value = GradientBrushAnimator.ConvertSolidColorBrushToGradient(firstGradient, solidColorBrush)
                     });
                 }
                 else if (keyframe.Value is IGradientBrush)
                 {
-                    gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
+                    gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), () => new GradientBrushAnimator(), keyframe.Cue, keyframe.KeySpline)
                     {
                         Value = keyframe.Value
                     });
@@ -117,7 +116,7 @@ namespace Avalonia.Animation.Animators
             {
                 if (keyframe.Value is ISolidColorBrush)
                 {
-                    solidColorBrushAnimator.Add(new AnimatorKeyFrame(typeof(ISolidColorBrushAnimator), keyframe.Cue, keyframe.KeySpline)
+                    solidColorBrushAnimator.Add(new AnimatorKeyFrame(typeof(ISolidColorBrushAnimator), () => new ISolidColorBrushAnimator(), keyframe.Cue, keyframe.KeySpline)
                     {
                         Value = keyframe.Value
                     });
@@ -137,18 +136,18 @@ namespace Avalonia.Animation.Animators
         {
             if (_brushAnimators.Count > 0 && this[0].Value?.GetType() is Type firstKeyType)
             {
-                foreach (var (match, animatorType) in _brushAnimators)
+                foreach (var (match, animatorType, animatorFactory) in _brushAnimators)
                 {
                     if (!match(firstKeyType))
                         continue;
 
-                    animator = (IAnimator?)Activator.CreateInstance(animatorType);
+                    animator = animatorFactory();
                     if (animator != null)
                     {
                         animator.Property = Property;
                         foreach (var keyframe in this)
                         {
-                            animator.Add(new AnimatorKeyFrame(animatorType, keyframe.Cue, keyframe.KeySpline)
+                            animator.Add(new AnimatorKeyFrame(animatorType, animatorFactory, keyframe.Cue, keyframe.KeySpline)
                             {
                                 Value = keyframe.Value
                             });

+ 0 - 13
src/Avalonia.Base/Animation/Animators/SolidColorBrushAnimator.cs

@@ -37,17 +37,4 @@ namespace Avalonia.Animation.Animators
         }
     }
     
-    [Obsolete("Use ISolidColorBrushAnimator instead")]
-    public class SolidColorBrushAnimator : Animator<SolidColorBrush?>
-    {    
-        public override SolidColorBrush? Interpolate(double progress, SolidColorBrush? oldValue, SolidColorBrush? newValue)
-        {
-            if (oldValue is null || newValue is null)
-            {
-                return progress >= 0.5 ? newValue : oldValue;
-            }
-
-            return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
-        }
-    }
 }

+ 1 - 0
src/Avalonia.Base/Avalonia.Base.csproj

@@ -6,6 +6,7 @@
     <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
     <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
     <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
+    <IsTrimmable>true</IsTrimmable>
   </PropertyGroup>
   <ItemGroup>
     <EmbeddedResource Include="Assets\*.trie" />

+ 0 - 35
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@@ -464,41 +464,6 @@ namespace Avalonia
             });
         }
 
-        /// <summary>
-        /// Subscribes to a property changed notifications for changes that originate from a
-        /// <typeparamref name="TTarget"/>.
-        /// </summary>
-        /// <typeparam name="TTarget">The type of the property change sender.</typeparam>
-        /// <param name="observable">The property changed observable.</param>
-        /// <param name="handler">Given a TTarget, returns the handler.</param>
-        /// <returns>A disposable that can be used to terminate the subscription.</returns>
-        [Obsolete("Use overload taking Action<TTarget, AvaloniaPropertyChangedEventArgs>.")]
-        public static IDisposable AddClassHandler<TTarget>(
-            this IObservable<AvaloniaPropertyChangedEventArgs> observable,
-            Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler)
-            where TTarget : class
-        {
-            return observable.Subscribe(e => SubscribeAdapter(e, handler));
-        }
-
-        /// <summary>
-        /// Observer method for <see cref="AddClassHandler{TTarget}(IObservable{AvaloniaPropertyChangedEventArgs},
-        /// Func{TTarget, Action{AvaloniaPropertyChangedEventArgs}})"/>.
-        /// </summary>
-        /// <typeparam name="TTarget">The sender type to accept.</typeparam>
-        /// <param name="e">The event args.</param>
-        /// <param name="handler">Given a TTarget, returns the handler.</param>
-        private static void SubscribeAdapter<TTarget>(
-            AvaloniaPropertyChangedEventArgs e,
-            Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler)
-            where TTarget : class
-        {
-            if (e.Sender is TTarget target)
-            {
-                handler(target)(e);
-            }
-        }
-
         private class BindingAdaptor : IBinding
         {
             private IObservable<object?> _source;

+ 0 - 15
src/Avalonia.Base/AvaloniaProperty`1.cs

@@ -30,21 +30,6 @@ namespace Avalonia
             _changed = new Subject<AvaloniaPropertyChangedEventArgs<TValue>>();
         }
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class.
-        /// </summary>
-        /// <param name="source">The property to copy.</param>
-        /// <param name="ownerType">The new owner type.</param>
-        /// <param name="metadata">Optional overridden metadata.</param>
-        [Obsolete("Use constructor with AvaloniaProperty<TValue> instead.", true)]
-        protected AvaloniaProperty(
-            AvaloniaProperty source,
-            Type ownerType,
-            AvaloniaPropertyMetadata? metadata)
-            : this(source as AvaloniaProperty<TValue> ?? throw new InvalidOperationException(), ownerType, metadata)
-        {
-        }
-
         /// <summary>
         /// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class.
         /// </summary>

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

@@ -140,21 +140,6 @@ namespace Avalonia.Collections
             }
         }
 
-        [Obsolete("Causes memory leaks. Use DynamicData or similar instead.")]
-        public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(
-            this IAvaloniaReadOnlyList<TSource> collection,
-            Func<TSource, TDerived> select)
-        {
-            var result = new AvaloniaList<TDerived>();
-
-            collection.ForEachItem(
-                (i, item) => result.Insert(i, select(item)),
-                (i, item) => result.RemoveAt(i),
-                () => result.Clear());
-
-            return result;
-        }
-
         /// <summary>
         /// Listens for property changed events from all items in a collection.
         /// </summary>

+ 0 - 15
src/Avalonia.Base/DirectPropertyBase.cs

@@ -30,21 +30,6 @@ namespace Avalonia
         {
         }
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="DirectPropertyBase{TValue}"/> class.
-        /// </summary>
-        /// <param name="source">The property to copy.</param>
-        /// <param name="ownerType">The new owner type.</param>
-        /// <param name="metadata">Optional overridden metadata.</param>
-        [Obsolete("Use constructor with DirectPropertyBase<TValue> instead.", true)]
-        protected DirectPropertyBase(
-            AvaloniaProperty source,
-            Type ownerType,
-            AvaloniaPropertyMetadata metadata)
-            : this(source as DirectPropertyBase<TValue> ?? throw new InvalidOperationException(), ownerType, metadata)
-        {
-        }
-
         /// <summary>
         /// Initializes a new instance of the <see cref="DirectPropertyBase{TValue}"/> class.
         /// </summary>

+ 0 - 4
src/Avalonia.Base/EnumExtensions.cs

@@ -8,10 +8,6 @@ namespace Avalonia
     /// </summary>
     public static class EnumExtensions
     {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        [Obsolete("This method is obsolete. Use HasAllFlags instead.")]
-        public static bool HasFlagCustom<T>(this T value, T flag) where T : unmanaged, Enum
-            => value.HasAllFlags(flag);
             
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static unsafe bool HasAllFlags<T>(this T value, T flags) where T : unmanaged, Enum

+ 2 - 1
src/Avalonia.Base/Input/PointerOverPreProcessor.cs

@@ -158,6 +158,8 @@ namespace Avalonia.Input
                     ClearPointerOver(pointer, root, timestamp, position, properties, inputModifiers);
                 }
             }
+
+            _lastPointer = (pointer, root.PointToScreen(position));
         }
 
         private void SetPointerOverToElement(IPointer pointer, IInputRoot root, IInputElement element,
@@ -195,7 +197,6 @@ namespace Avalonia.Input
             }
 
             el = root.PointerOverElement = element;
-            _lastPointer = (pointer, root.PointToScreen(position));
 
             e.RoutedEvent = InputElement.PointerEnteredEvent;
 

+ 15 - 15
src/Avalonia.Base/Input/PointerPoint.cs

@@ -5,7 +5,7 @@ namespace Avalonia.Input
     /// <summary>
     /// Provides basic properties for the input pointer associated with a single mouse, pen/stylus, or touch contact.
     /// </summary>
-    public sealed class PointerPoint
+    public struct PointerPoint
     {
         public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties)
         {
@@ -33,47 +33,47 @@ namespace Avalonia.Input
     /// <summary>
     /// Provides extended properties for a PointerPoint object.
     /// </summary>
-    public sealed class PointerPointProperties
+    public struct PointerPointProperties
     {
         /// <summary>
         /// Gets a value that indicates whether the pointer input was triggered by the primary action mode of an input device.
         /// </summary>
-        public bool IsLeftButtonPressed { get; }
+        public bool IsLeftButtonPressed { get; } = false;
 
         /// <summary>
         /// Gets a value that indicates whether the pointer input was triggered by the tertiary action mode of an input device.
         /// </summary>
-        public bool IsMiddleButtonPressed { get; }
+        public bool IsMiddleButtonPressed { get; } = false;
 
         /// <summary>
         /// Gets a value that indicates whether the pointer input was triggered by the secondary action mode (if supported) of an input device.
         /// </summary>
-        public bool IsRightButtonPressed { get; }
+        public bool IsRightButtonPressed { get; } = false;
 
         /// <summary>
         /// Gets a value that indicates whether the pointer input was triggered by the first extended mouse button (XButton1).
         /// </summary>
-        public bool IsXButton1Pressed { get; }
+        public bool IsXButton1Pressed { get; } = false;
 
         /// <summary>
         /// Gets a value that indicates whether the pointer input was triggered by the second extended mouse button (XButton2).
         /// </summary>
-        public bool IsXButton2Pressed { get; }
+        public bool IsXButton2Pressed { get; } = false;
 
         /// <summary>
         /// Gets a value that indicates whether the barrel button of the pen/stylus device is pressed.
         /// </summary>
-        public bool IsBarrelButtonPressed { get; }
+        public bool IsBarrelButtonPressed { get; } = false;
 
         /// <summary>
         /// Gets a value that indicates whether the input is from a pen eraser.
         /// </summary>
-        public bool IsEraser { get; }
+        public bool IsEraser { get; } = false;
 
         /// <summary>
         /// Gets a value that indicates whether the digitizer pen is inverted.
         /// </summary>
-        public bool IsInverted { get; }
+        public bool IsInverted { get; } = false;
 
         /// <summary>
         /// Gets the clockwise rotation in degrees of a pen device around its own major axis (such as when the user spins the pen in their fingers).
@@ -81,7 +81,7 @@ namespace Avalonia.Input
         /// <returns>
         /// A value between 0.0 and 359.0 in degrees of rotation. The default value is 0.0.
         /// </returns>
-        public float Twist { get; }
+        public float Twist { get; } = 0.0F;
 
         /// <summary>
         /// Gets a value that indicates the force that the pointer device (typically a pen/stylus) exerts on the surface of the digitizer.
@@ -97,7 +97,7 @@ namespace Avalonia.Input
         /// <returns>
         /// The value is 0.0 when the finger or pen is perpendicular to the digitizer surface, between 0.0 and 90.0 when tilted to the right of perpendicular, and between 0.0 and -90.0 when tilted to the left of perpendicular. The default value is 0.0.
         /// </returns>
-        public float XTilt { get; }
+        public float XTilt { get; } = 0.0F;
 
         /// <summary>
         /// Gets the plane angle between the X-Z plane and the plane that contains the X axis and the axis of the input device (typically a pen/stylus).
@@ -105,14 +105,14 @@ namespace Avalonia.Input
         /// <returns>
         /// The value is 0.0 when the finger or pen is perpendicular to the digitizer surface, between 0.0 and 90.0 when tilted towards the user, and between 0.0 and -90.0 when tilted away from the user. The default value is 0.0.
         /// </returns>
-        public float YTilt { get; }
+        public float YTilt { get; } = 0.0F;
 
         /// <summary>
         /// Gets the kind of pointer state change.
         /// </summary>
-        public PointerUpdateKind PointerUpdateKind { get; }
+        public PointerUpdateKind PointerUpdateKind { get; } = PointerUpdateKind.LeftButtonPressed;
 
-        private PointerPointProperties()
+        public PointerPointProperties()
         {
         }
 

+ 4 - 0
src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs

@@ -137,9 +137,13 @@ namespace Avalonia.Input.Raw
         /// </summary>
         public Point Position { get; set; }
 
+        /// <inheritdoc cref="PointerPointProperties.Twist" />
         public float Twist { get; set; }
+        /// <inheritdoc cref="PointerPointProperties.Pressure" />
         public float Pressure { get; set; }
+        /// <inheritdoc cref="PointerPointProperties.XTilt" />
         public float XTilt { get; set; }
+        /// <inheritdoc cref="PointerPointProperties.YTilt" />
         public float YTilt { get; set; }
 
 

+ 0 - 18
src/Avalonia.Base/Interactivity/RoutedEvent.cs

@@ -113,24 +113,6 @@ namespace Avalonia.Interactivity
         {
         }
 
-        [Obsolete("Use overload taking Action<TTarget, TEventArgs>.")]
-        public IDisposable AddClassHandler<TTarget>(
-            Func<TTarget, Action<TEventArgs>> handler,
-            RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
-            bool handledEventsToo = false)
-            where TTarget : class, IInteractive
-        {
-            void Adapter(object? sender, RoutedEventArgs e)
-            {
-                if (sender is TTarget target && e is TEventArgs args)
-                {
-                    handler(target)(args);
-                }
-            }
-
-            return AddClassHandler(typeof(TTarget), Adapter, routes, handledEventsToo);
-        }
-
         public IDisposable AddClassHandler<TTarget>(
             Action<TTarget, TEventArgs> handler,
             RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,

+ 0 - 11
src/Avalonia.Base/Layout/ILayoutManager.cs

@@ -44,17 +44,6 @@ namespace Avalonia.Layout
         /// </remarks>
         void ExecuteInitialLayoutPass();
 
-        /// <summary>
-        /// Executes the initial layout pass on a layout root.
-        /// </summary>
-        /// <param name="root">The control to lay out.</param>
-        /// <remarks>
-        /// You should not usually need to call this method explictly, the layout root will call
-        /// it to carry out the initial layout of the control.
-        /// </remarks>
-        [Obsolete("Call ExecuteInitialLayoutPass without parameter")]
-        void ExecuteInitialLayoutPass(ILayoutRoot root);
-
         /// <summary>
         /// Registers a control as wanting to receive effective viewport notifications.
         /// </summary>

+ 0 - 11
src/Avalonia.Base/Layout/LayoutManager.cs

@@ -196,17 +196,6 @@ namespace Avalonia.Layout
             ExecuteLayoutPass();
         }
 
-        [Obsolete("Call ExecuteInitialLayoutPass without parameter")]
-        public void ExecuteInitialLayoutPass(ILayoutRoot root)
-        {
-            if (root != _owner)
-            {
-                throw new ArgumentException("ExecuteInitialLayoutPass called with incorrect root.");
-            }
-
-            ExecuteInitialLayoutPass();
-        }
-
         public void Dispose()
         {
             _disposed = true;

+ 0 - 28
src/Avalonia.Base/Layout/Layoutable.cs

@@ -460,20 +460,6 @@ namespace Avalonia.Layout
             _effectiveViewportChanged?.Invoke(this, e);
         }
 
-        /// <summary>
-        /// Marks a property as affecting the control's measurement.
-        /// </summary>
-        /// <param name="properties">The properties.</param>
-        /// <remarks>
-        /// After a call to this method in a control's static constructor, any change to the
-        /// property will cause <see cref="InvalidateMeasure"/> to be called on the element.
-        /// </remarks>
-        [Obsolete("Use AffectsMeasure<T> and specify the control type.")]
-        protected static void AffectsMeasure(params AvaloniaProperty[] properties)
-        {
-            AffectsMeasure<Layoutable>(properties);
-        }
-
         /// <summary>
         /// Marks a property as affecting the control's measurement.
         /// </summary>
@@ -497,20 +483,6 @@ namespace Avalonia.Layout
             }
         }
 
-        /// <summary>
-        /// Marks a property as affecting the control's arrangement.
-        /// </summary>
-        /// <param name="properties">The properties.</param>
-        /// <remarks>
-        /// After a call to this method in a control's static constructor, any change to the
-        /// property will cause <see cref="InvalidateArrange"/> to be called on the element.
-        /// </remarks>
-        [Obsolete("Use AffectsArrange<T> and specify the control type.")]
-        protected static void AffectsArrange(params AvaloniaProperty[] properties)
-        {
-            AffectsArrange<Layoutable>(properties);
-        }
-
         /// <summary>
         /// Marks a property as affecting the control's arrangement.
         /// </summary>

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

@@ -35,7 +35,6 @@ namespace Avalonia.Media
         /// Initializes a new instance of the <see cref="DashStyle"/> class.
         /// </summary>
         public DashStyle()
-            : this(null, 0)
         {
         }
 

+ 0 - 16
src/Avalonia.Base/Media/Imaging/Bitmap.cs

@@ -89,22 +89,6 @@ namespace Avalonia.Media.Imaging
             PlatformImpl.Dispose();
         }
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="Bitmap"/> class.
-        /// </summary>
-        /// <param name="format">The pixel format.</param>
-        /// <param name="data">The pointer to the source bytes.</param>
-        /// <param name="size">The size of the bitmap in device pixels.</param>
-        /// <param name="dpi">The DPI of the bitmap.</param>
-        /// <param name="stride">The number of bytes per row.</param>
-        [Obsolete("Use overload taking an AlphaFormat.")]
-        public Bitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
-        {
-            var ri = GetFactory();
-            PlatformImpl = RefCountable.Create(ri
-                .LoadBitmap(format, ri.DefaultAlphaFormat, data, size, dpi, stride));
-        }
-
         /// <summary>
         /// Initializes a new instance of the <see cref="Bitmap"/> class.
         /// </summary>

+ 1 - 13
src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs

@@ -9,18 +9,6 @@ namespace Avalonia.Media.Imaging
     /// </summary>
     public class WriteableBitmap : Bitmap
     {
-        /// <summary>
-        /// Initializes a new instance of the <see cref="WriteableBitmap"/> class.
-        /// </summary>
-        /// <param name="size">The size of the bitmap in device pixels.</param>
-        /// <param name="dpi">The DPI of the bitmap.</param>
-        /// <param name="format">The pixel format (optional).</param>
-        /// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns>
-        [Obsolete("Use overload taking an AlphaFormat.")]
-        public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null)
-            : base(CreatePlatformImpl(size, dpi, format, null))
-        {
-        }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="WriteableBitmap"/> class.
@@ -30,7 +18,7 @@ namespace Avalonia.Media.Imaging
         /// <param name="format">The pixel format (optional).</param>
         /// <param name="alphaFormat">The alpha format (optional).</param>
         /// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns>
-        public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat format, AlphaFormat alphaFormat) 
+        public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null, AlphaFormat? alphaFormat = null) 
             : base(CreatePlatformImpl(size, dpi, format, alphaFormat))
         {
         }

+ 9 - 6
src/Avalonia.Base/Media/KnownColors.cs

@@ -1,10 +1,11 @@
 using System;
 using System.Reflection;
 using System.Collections.Generic;
+using Avalonia.SourceGenerator;
 
 namespace Avalonia.Media
 {
-    internal static class KnownColors
+    internal static partial class KnownColors
     {
         private static readonly IReadOnlyDictionary<string, KnownColor> _knownColorNames;
         private static readonly IReadOnlyDictionary<uint, string> _knownColors;
@@ -12,23 +13,25 @@ namespace Avalonia.Media
         private static readonly Dictionary<KnownColor, ISolidColorBrush> _knownBrushes;
 #endif
 
+        [GenerateEnumValueDictionary()]
+        private static partial Dictionary<string, KnownColor> GetKnownColors();
+
         static KnownColors()
         {
             var knownColorNames = new Dictionary<string, KnownColor>(StringComparer.OrdinalIgnoreCase);
             var knownColors = new Dictionary<uint, string>();
 
-            foreach (var field in typeof(KnownColor).GetRuntimeFields())
+            foreach (var field in GetKnownColors())
             {
-                if (field.FieldType != typeof(KnownColor)) continue;
-                var knownColor = (KnownColor)field.GetValue(null)!;
+                var knownColor = field.Value;
                 if (knownColor == KnownColor.None) continue;
 
-                knownColorNames.Add(field.Name, knownColor);
+                knownColorNames.Add(field.Key, knownColor);
 
                 // some known colors have the same value, so use the first
                 if (!knownColors.ContainsKey((uint)knownColor))
                 {
-                    knownColors.Add((uint)knownColor, field.Name);
+                    knownColors.Add((uint)knownColor, field.Key);
                 }
             }
 

+ 8 - 4
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs

@@ -12,6 +12,11 @@ public class BclStorageFile : IStorageBookmarkFile
 {
     private readonly FileInfo _fileInfo;
 
+    public BclStorageFile(string fileName)
+    {
+        _fileInfo = new FileInfo(fileName);
+    }
+
     public BclStorageFile(FileInfo fileInfo)
     {
         _fileInfo = fileInfo ?? throw new ArgumentNullException(nameof(fileInfo));
@@ -27,15 +32,14 @@ public class BclStorageFile : IStorageBookmarkFile
 
     public Task<StorageItemProperties> GetBasicPropertiesAsync()
     {
-        var props = new StorageItemProperties();
         if (_fileInfo.Exists)
         {
-            props = new StorageItemProperties(
+            return Task.FromResult(new StorageItemProperties(
                 (ulong)_fileInfo.Length,
                 _fileInfo.CreationTimeUtc,
-                _fileInfo.LastAccessTimeUtc);
+                _fileInfo.LastAccessTimeUtc));
         }
-        return Task.FromResult(props);
+        return Task.FromResult(new StorageItemProperties());
     }
 
     public Task<IStorageFolder?> GetParentAsync()

+ 9 - 0
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs

@@ -14,6 +14,15 @@ public class BclStorageFolder : IStorageBookmarkFolder
 {
     private readonly DirectoryInfo _directoryInfo;
 
+    public BclStorageFolder(string path)
+    {
+        _directoryInfo = new DirectoryInfo(path);
+        if (!_directoryInfo.Exists)
+        {
+            throw new ArgumentException("Directory must exist");
+        }
+    }
+
     public BclStorageFolder(DirectoryInfo directoryInfo)
     {
         _directoryInfo = directoryInfo ?? throw new ArgumentNullException(nameof(directoryInfo));

+ 31 - 2
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@@ -29,6 +29,7 @@ public class CompositingRenderer : IRendererWithCompositor
     private bool _queuedUpdate;
     private Action _update;
     private Action _invalidateScene;
+    private bool _updating;
 
     internal CompositionTarget CompositionTarget;
     
@@ -77,6 +78,8 @@ public class CompositingRenderer : IRendererWithCompositor
     /// <inheritdoc/>
     public void AddDirty(IVisual visual)
     {
+        if (_updating)
+            throw new InvalidOperationException("Visual was invalidated during the render pass");
         _dirty.Add((Visual)visual);
         QueueUpdate();
     }
@@ -84,7 +87,16 @@ public class CompositingRenderer : IRendererWithCompositor
     /// <inheritdoc/>
     public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool>? filter)
     {
-        var res = CompositionTarget.TryHitTest(p, filter);
+        Func<CompositionVisual, bool>? f = null;
+        if (filter != null)
+            f = v =>
+            {
+                if (v is CompositionDrawListVisual dlv)
+                    return filter(dlv.Visual);
+                return true;
+            };
+        
+        var res = CompositionTarget.TryHitTest(p, f);
         if(res == null)
             yield break;
         foreach(var v in res)
@@ -107,6 +119,8 @@ public class CompositingRenderer : IRendererWithCompositor
     /// <inheritdoc/>
     public void RecalculateChildren(IVisual visual)
     {
+        if (_updating)
+            throw new InvalidOperationException("Visual was invalidated during the render pass");
         _recalculateChildren.Add((Visual)visual);
         QueueUpdate();
     }
@@ -191,7 +205,7 @@ public class CompositingRenderer : IRendererWithCompositor
     private void InvalidateScene() =>
         SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
 
-    private void Update()
+    private void UpdateCore()
     {
         _queuedUpdate = false;
         foreach (var visual in _dirty)
@@ -240,6 +254,21 @@ public class CompositingRenderer : IRendererWithCompositor
         CompositionTarget.Scaling = _root.RenderScaling;
         Compositor.InvokeOnNextCommit(_invalidateScene);
     }
+
+    private void Update()
+    {
+        if(_updating)
+            return;
+        _updating = true;
+        try
+        {
+            UpdateCore();
+        }
+        finally
+        {
+            _updating = false;
+        }
+    }
     
     public void Resized(Size size)
     {

+ 1 - 3
src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs

@@ -54,13 +54,11 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
         Visual = visual;
     }
 
-    internal override bool HitTest(Point pt, Func<IVisual, bool>? filter)
+    internal override bool HitTest(Point pt)
     {
         var custom = Visual as ICustomHitTest;
         if (DrawList == null && custom == null)
             return false;
-        if (filter != null && !filter(Visual))
-            return false;
         if (custom != null)
         {
             // Simulate the old behavior

+ 7 - 3
src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs

@@ -31,7 +31,7 @@ namespace Avalonia.Rendering.Composition
         /// <param name="point"></param>
         /// <param name="filter"></param>
         /// <returns></returns>
-        public PooledList<CompositionVisual>? TryHitTest(Point point, Func<IVisual, bool>? filter)
+        public PooledList<CompositionVisual>? TryHitTest(Point point, Func<CompositionVisual, bool>? filter)
         {
             Server.Readback.NextRead();
             if (Root == null)
@@ -88,10 +88,14 @@ namespace Avalonia.Rendering.Composition
         }
         
         void HitTestCore(CompositionVisual visual, Point globalPoint, PooledList<CompositionVisual> result,
-            Func<IVisual, bool>? filter)
+            Func<CompositionVisual, bool>? filter)
         {
             if (visual.Visible == false)
                 return;
+            
+            if (filter != null && !filter(visual))
+                return;
+            
             if (!TryTransformTo(visual, globalPoint, out var point))
                 return;
 
@@ -111,7 +115,7 @@ namespace Avalonia.Rendering.Composition
                 }
             
             // Hit-test the current node
-            if (visual.HitTest(point, filter)) 
+            if (visual.HitTest(point)) 
                 result.Add(visual);
         }
 

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

@@ -53,6 +53,6 @@ namespace Avalonia.Rendering.Composition
         
         internal object? Tag { get; set; }
 
-        internal virtual bool HitTest(Point point, Func<IVisual, bool>? filter) => true;
+        internal virtual bool HitTest(Point point) => true;
     }
 }

+ 7 - 26
src/Avalonia.Base/Rendering/DirtyVisuals.cs

@@ -17,8 +17,7 @@ namespace Avalonia.Rendering
     {
         private SortedDictionary<int, List<IVisual>> _inner = new SortedDictionary<int, List<IVisual>>();
         private Dictionary<IVisual, int> _index = new Dictionary<IVisual, int>();
-        private List<IVisual> _deferredChanges = new List<IVisual>();
-        private int _deferring;
+        private int _enumerating;
 
         /// <summary>
         /// Gets the number of dirty visuals.
@@ -31,10 +30,9 @@ namespace Avalonia.Rendering
         /// <param name="visual">The dirty visual.</param>
         public void Add(IVisual visual)
         {
-            if (_deferring > 0)
+            if (_enumerating > 0)
             {
-                _deferredChanges.Add(visual);
-                return;
+                throw new InvalidOperationException("Visual was invalidated during a render pass");
             }
 
             var distance = visual.CalculateDistanceFromAncestor(visual.VisualRoot);
@@ -65,7 +63,7 @@ namespace Avalonia.Rendering
         /// </summary>
         public void Clear()
         {
-            if (_deferring > 0)
+            if (_enumerating > 0)
             {
                 throw new InvalidOperationException("Cannot clear while enumerating");
             }
@@ -80,7 +78,7 @@ namespace Avalonia.Rendering
         /// <returns>A collection of visuals.</returns>
         public IEnumerator<IVisual> GetEnumerator()
         {
-            BeginDefer();
+            _enumerating++;
             try
             {
                 foreach (var i in _inner)
@@ -93,27 +91,10 @@ namespace Avalonia.Rendering
             }
             finally
             {
-                EndDefer();
+                _enumerating--;
             }
         }
-
-        private void BeginDefer()
-        {
-            ++_deferring;
-        }
-
-        private void EndDefer()
-        {
-            if (--_deferring > 0) return;
-
-            foreach (var visual in _deferredChanges)
-            {
-                Add(visual);
-            }
-
-            _deferredChanges.Clear();
-        }
-
+        
         /// <summary>
         /// Gets the dirty visuals, in ascending order of distance to their root.
         /// </summary>

+ 35 - 0
src/Avalonia.Base/Utilities/MathUtilities.cs

@@ -324,6 +324,41 @@ namespace Avalonia.Utilities
             return angle * 2 * Math.PI;
         }
 
+        /// <summary>
+        /// Calculates the point of an angle on an ellipse.
+        /// </summary>
+        /// <param name="centre">The centre point of the ellipse.</param>
+        /// <param name="radiusX">The x radius of the ellipse.</param>
+        /// <param name="radiusY">The y radius of the ellipse.</param>
+        /// <param name="angle">The angle in radians.</param>
+        /// <returns>A point on the ellipse.</returns>
+        public static Point GetEllipsePoint(Point centre, double radiusX, double radiusY, double angle)
+        {
+            return new Point(radiusX * Math.Cos(angle) + centre.X, radiusY * Math.Sin(angle) + centre.Y);
+        }
+
+        /// <summary>
+        /// Gets the minimum and maximum from the specified numbers.
+        /// </summary>
+        /// <param name="a">The first number.</param>
+        /// <param name="b">The second number.</param>
+        /// <returns>A tuple containing the minimum and maximum of the two specified numbers.</returns>
+        public static (double min, double max) GetMinMax(double a, double b)
+        {
+            return a < b ? (a, b) : (b, a);
+        }
+
+        /// <summary>
+        /// Gets the minimum and maximum from the specified number and the difference with that number.
+        /// </summary>
+        /// <param name="initialValue">The initial value to use.</param>
+        /// <param name="delta">The difference for <paramref name="initialValue"/>.</param>
+        /// <returns>A tuple containing the minimum and maximum of the specified number and the difference with that number.</returns>
+        public static (double min, double max) GetMinMaxFromDelta(double initialValue, double delta)
+        {
+            return GetMinMax(initialValue, initialValue + delta);
+        }
+
         private static void ThrowCannotBeGreaterThanException<T>(T min, T max)
         {
             throw new ArgumentException($"{min} cannot be greater than {max}.");

+ 0 - 25
src/Avalonia.Base/Utilities/WeakObservable.cs

@@ -9,31 +9,6 @@ namespace Avalonia.Utilities
     /// </summary>
     public static class WeakObservable
     {
-        /// <summary>
-        /// Converts a .NET event conforming to the standard .NET event pattern into an observable
-        /// sequence, subscribing weakly.
-        /// </summary>
-        /// <typeparam name="TTarget">The type of target.</typeparam>
-        /// <typeparam name="TEventArgs">The type of the event args.</typeparam>
-        /// <param name="target">Object instance that exposes the event to convert.</param>
-        /// <param name="eventName">Name of the event to convert.</param>
-        /// <returns></returns>
-        [Obsolete("Use WeakEvent-based overload")]
-        public static IObservable<EventPattern<object, TEventArgs>> FromEventPattern<TTarget, TEventArgs>(
-            TTarget target, 
-            string eventName)
-            where TEventArgs : EventArgs
-        {
-            _ = target ?? throw new ArgumentNullException(nameof(target));
-            _ = eventName ?? throw new ArgumentNullException(nameof(eventName));
-
-            return Observable.Create<EventPattern<object, TEventArgs>>(observer =>
-            {
-                var handler = new Handler<TEventArgs>(observer);
-                WeakSubscriptionManager.Subscribe(target, eventName, handler);
-                return () => WeakSubscriptionManager.Unsubscribe(target, eventName, handler);
-            }).Publish().RefCount();
-        }
 
         private class Handler<TEventArgs> 
             : IWeakSubscriber<TEventArgs>,

+ 0 - 193
src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs

@@ -1,193 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-namespace Avalonia.Utilities
-{
-    /// <summary>
-    /// Manages subscriptions to events using weak listeners.
-    /// </summary>
-    public static class WeakSubscriptionManager
-    {
-        /// <summary>
-        /// Subscribes to an event on an object using a weak subscription.
-        /// </summary>
-        /// <typeparam name="TTarget">The type of the target.</typeparam>
-        /// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
-        /// <param name="target">The event source.</param>
-        /// <param name="eventName">The name of the event.</param>
-        /// <param name="subscriber">The subscriber.</param>
-        [Obsolete("Use WeakEvent")]
-        public static void Subscribe<TTarget, TEventArgs>(TTarget target, string eventName, IWeakSubscriber<TEventArgs> subscriber)
-            where TEventArgs : EventArgs
-        {
-            _ = target ?? throw new ArgumentNullException(nameof(target));
-
-            var dic = SubscriptionTypeStorage<TEventArgs>.Subscribers.GetOrCreateValue(target);
-
-            if (!dic.TryGetValue(eventName, out var sub))
-            {
-                dic[eventName] = sub = new Subscription<TEventArgs>(dic, typeof(TTarget), target, eventName);
-            }
-
-            sub.Add(new WeakReference<IWeakSubscriber<TEventArgs>>(subscriber));
-        }
-
-        /// <summary>
-        /// Unsubscribes from an event.
-        /// </summary>
-        /// <typeparam name="T">The type of the event arguments.</typeparam>
-        /// <param name="target">The event source.</param>
-        /// <param name="eventName">The name of the event.</param>
-        /// <param name="subscriber">The subscriber.</param>
-        public static void Unsubscribe<T>(object target, string eventName, IWeakSubscriber<T> subscriber)
-            where T : EventArgs
-        {
-            if (SubscriptionTypeStorage<T>.Subscribers.TryGetValue(target, out var dic))
-            {
-                if (dic.TryGetValue(eventName, out var sub))
-                {
-                    sub.Remove(subscriber);
-                }
-            }
-        }
-
-        private static class SubscriptionTypeStorage<T>
-            where T : EventArgs
-        {
-            public static readonly ConditionalWeakTable<object, SubscriptionDic<T>> Subscribers
-                = new ConditionalWeakTable<object, SubscriptionDic<T>>();
-        }
-
-        private class SubscriptionDic<T> : Dictionary<string, Subscription<T>>
-            where T : EventArgs
-        {
-        }
-
-        private static readonly Dictionary<Type, Dictionary<string, EventInfo>> Accessors
-            = new Dictionary<Type, Dictionary<string, EventInfo>>();
-
-        private class Subscription<T> where T : EventArgs
-        {
-            private readonly EventInfo _info;
-            private readonly SubscriptionDic<T> _sdic;
-            private readonly object _target;
-            private readonly string _eventName;
-            private readonly Delegate _delegate;
-
-            private WeakReference<IWeakSubscriber<T>>?[] _data = new WeakReference<IWeakSubscriber<T>>?[16];
-            private int _count = 0;
-
-            public Subscription(SubscriptionDic<T> sdic, Type targetType, object target, string eventName)
-            {
-                _sdic = sdic;
-                _target = target;
-                _eventName = eventName;
-                if (!Accessors.TryGetValue(targetType, out var evDic))
-                    Accessors[targetType] = evDic = new Dictionary<string, EventInfo>();
-
-                if (evDic.TryGetValue(eventName, out var info))
-                {
-                    _info = info;
-                }
-                else
-                {
-                    var ev = targetType.GetRuntimeEvents().FirstOrDefault(x => x.Name == eventName);
-
-                    if (ev == null)
-                    {
-                        throw new ArgumentException(
-                            $"The event {eventName} was not found on {target.GetType()}.");
-                    }
-
-                    evDic[eventName] = _info = ev;
-                }
-
-                var del = new Action<object, T>(OnEvent);
-                _delegate = del.GetMethodInfo().CreateDelegate(_info.EventHandlerType!, del.Target);
-                _info.AddMethod!.Invoke(target, new[] { _delegate });
-            }
-
-            void Destroy()
-            {
-                _info.RemoveMethod!.Invoke(_target, new[] { _delegate });
-                _sdic.Remove(_eventName);
-            }
-
-            public void Add(WeakReference<IWeakSubscriber<T>> s)
-            {
-                if (_count == _data.Length)
-                {
-                    //Extend capacity
-                    var ndata = new WeakReference<IWeakSubscriber<T>>?[_data.Length*2];
-                    Array.Copy(_data, ndata, _data.Length);
-                    _data = ndata;
-                }
-                _data[_count] = s!;
-                _count++;
-            }
-
-            public void Remove(IWeakSubscriber<T> s)
-            {
-                var removed = false;
-
-                for (int c = 0; c < _count; ++c)
-                {
-                    var reference = _data[c];
-                    IWeakSubscriber<T>? instance;
-
-                    if (reference != null && reference.TryGetTarget(out instance) && instance == s)
-                    {
-                        _data[c] = null;
-                        removed = true;
-                    }
-                }
-
-                if (removed)
-                {
-                    Compact();
-                }
-            }
-
-            void Compact()
-            {
-                int empty = -1;
-                for (int c = 0; c < _count; c++)
-                {
-                    var r = _data[c];
-                    //Mark current index as first empty
-                    if (r == null && empty == -1)
-                        empty = c;
-                    //If current element isn't null and we have an empty one
-                    if (r != null && empty != -1)
-                    {
-                        _data[c] = null;
-                        _data[empty] = r;
-                        empty++;
-                    }
-                }
-                if (empty != -1)
-                    _count = empty;
-                if (_count == 0)
-                    Destroy();
-            }
-
-            void OnEvent(object sender, T eventArgs)
-            {
-                var needCompact = false;
-                for(var c=0; c<_count; c++)
-                {
-                    var r = _data[c];
-                    if (r?.TryGetTarget(out var sub) == true)
-                        sub!.OnEvent(sender, eventArgs);
-                    else
-                        needCompact = true;
-                }
-                if (needCompact)
-                    Compact();
-            }
-        }
-    }
-}

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

@@ -338,22 +338,6 @@ namespace Avalonia
             Contract.Requires<ArgumentNullException>(context != null);
         }
 
-        /// <summary>
-        /// Indicates that a property change should cause <see cref="InvalidateVisual"/> to be
-        /// called.
-        /// </summary>
-        /// <param name="properties">The properties.</param>
-        /// <remarks>
-        /// This method should be called in a control's static constructor with each property
-        /// on the control which when changed should cause a redraw. This is similar to WPF's
-        /// FrameworkPropertyMetadata.AffectsRender flag.
-        /// </remarks>
-        [Obsolete("Use AffectsRender<T> and specify the control type.")]
-        protected static void AffectsRender(params AvaloniaProperty[] properties)
-        {
-            AffectsRender<Visual>(properties);
-        }
-
         /// <summary>
         /// Indicates that a property change should cause <see cref="InvalidateVisual"/> to be
         /// called.

+ 0 - 19
src/Avalonia.Base/VisualTree/IVisualTreeHost.cs

@@ -1,19 +0,0 @@
-using System;
-
-namespace Avalonia.VisualTree
-{
-    /// <summary>
-    /// Interface for controls that host their own separate visual tree, such as popups.
-    /// </summary>
-    [Obsolete]
-    public interface IVisualTreeHost
-    {
-        /// <summary>
-        /// Gets the root of the hosted visual tree.
-        /// </summary>
-        /// <value>
-        /// The root of the hosted visual tree.
-        /// </value>
-        IVisual? Root { get; }
-    }
-}

+ 14 - 1
src/Avalonia.Base/VisualTree/VisualExtensions.cs

@@ -413,7 +413,7 @@ namespace Avalonia.VisualTree
                     Index = index,
                     ZIndex = element.ZIndex,
                 })
-                .OrderBy(x => x, null)
+                .OrderBy(x => x, ZOrderElement.Comparer)
                 .Select(x => x.Element!);
         }
 
@@ -448,6 +448,19 @@ namespace Avalonia.VisualTree
             public int Index { get; set; }
             public int ZIndex { get; set; }
 
+            class ZOrderComparer : IComparer<ZOrderElement>
+            {
+                public int Compare(ZOrderElement? x, ZOrderElement? y)
+                {
+                    if (ReferenceEquals(x, y)) return 0;
+                    if (ReferenceEquals(null, y)) return 1;
+                    if (ReferenceEquals(null, x)) return -1;
+                    return x.CompareTo(y);
+                }
+            }
+
+            public static IComparer<ZOrderElement> Comparer { get; } = new ZOrderComparer();
+            
             public int CompareTo(ZOrderElement? other)
             {
                 if (other is null)

+ 1 - 0
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@@ -108,4 +108,5 @@
       <PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" PrivateAssets="All" />
       <PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
     </ItemGroup>
+  <Import Project="..\..\build\SourceGenerators.props" />
 </Project>

+ 14 - 9
src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml

@@ -11,15 +11,20 @@
                    Stretch="Uniform"
                    DestinationRect="0,0,8,8">
         <VisualBrush.Visual>
-          <DrawingPresenter Width="8"
-                            Height="8">
-            <DrawingGroup>
-              <GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z"
-                               Brush="Transparent" />
-              <GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z"
-                               Brush="#19808080" />
-            </DrawingGroup>
-          </DrawingPresenter>
+          <Image Width="8" Height="8">
+            <Image.Source>
+              <DrawingImage>
+                <DrawingImage.Drawing>
+                    <DrawingGroup>
+                      <GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z"
+                                       Brush="Transparent" />
+                      <GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z"
+                                       Brush="#19808080" />
+                    </DrawingGroup>
+                </DrawingImage.Drawing>
+              </DrawingImage>
+            </Image.Source>
+          </Image>
         </VisualBrush.Visual>
       </VisualBrush>
 

+ 14 - 9
src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml

@@ -11,15 +11,20 @@
                    Stretch="Uniform"
                    DestinationRect="0,0,8,8">
         <VisualBrush.Visual>
-          <DrawingPresenter Width="8"
-                            Height="8">
-            <DrawingGroup>
-              <GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z"
-                               Brush="Transparent" />
-              <GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z"
-                               Brush="#19808080" />
-            </DrawingGroup>
-          </DrawingPresenter>
+          <Image Width="8" Height="8">
+            <Image.Source>
+              <DrawingImage>
+                <DrawingImage.Drawing>
+                  <DrawingGroup>
+                    <GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z"
+                                     Brush="Transparent" />
+                    <GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z"
+                                     Brush="#19808080" />
+                  </DrawingGroup>
+                </DrawingImage.Drawing>
+              </DrawingImage>
+            </Image.Source>
+          </Image>
         </VisualBrush.Visual>
       </VisualBrush>
 

+ 0 - 10
src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs

@@ -12,9 +12,6 @@ namespace Avalonia.Collections
     {
         public virtual string PropertyPath => null;
 
-        [Obsolete("Use Direction property to read or override sorting direction.")]
-        public virtual bool Descending => Direction == ListSortDirection.Descending;
-
         public virtual ListSortDirection Direction => ListSortDirection.Ascending;
         public bool HasPropertyPath => !String.IsNullOrEmpty(PropertyPath);
         public abstract IComparer<object> Comparer { get; }
@@ -254,13 +251,6 @@ namespace Avalonia.Collections
             return new DataGridPathSortDescription(propertyPath, direction, null, culture);
         }
 
-
-        [Obsolete("Use overload taking a ListSortDirection.")]
-        public static DataGridSortDescription FromPath(string propertyPath, bool descending, CultureInfo culture = null)
-        {
-            return new DataGridPathSortDescription(propertyPath, descending ? ListSortDirection.Descending : ListSortDirection.Ascending, null, culture);
-        }
-
         public static DataGridSortDescription FromPath(string propertyPath, ListSortDirection direction, IComparer comparer)
         {
             return new DataGridPathSortDescription(propertyPath, direction, comparer, null);

+ 30 - 0
src/Avalonia.Controls.DataGrid/DataGrid.cs

@@ -2167,7 +2167,23 @@ namespace Avalonia.Controls
 
             return desiredSize;
         }
+        
+        /// <inheritdoc/>
+        protected override void OnDataContextBeginUpdate()
+        {
+            base.OnDataContextBeginUpdate();
 
+            NotifyDataContextPropertyForAllRowCells(GetAllRows(), true);
+        }
+
+        /// <inheritdoc/>
+        protected override void OnDataContextEndUpdate()
+        {
+            base.OnDataContextEndUpdate();
+
+            NotifyDataContextPropertyForAllRowCells(GetAllRows(), false);
+        }
+        
         /// <summary>
         /// Raises the BeginningEdit event.
         /// </summary>
@@ -3165,6 +3181,20 @@ namespace Avalonia.Controls
             }
         }
 
+        private static void NotifyDataContextPropertyForAllRowCells(IEnumerable<DataGridRow> rowSource, bool arg2)
+        {
+            foreach (DataGridRow row in rowSource)
+            {
+                foreach (DataGridCell cell in row.Cells)
+                {
+                    if (cell.Content is StyledElement cellContent)
+                    {
+                        DataContextProperty.Notifying?.Invoke(cellContent, arg2);
+                    }
+                }
+            }
+        }
+
         private void UpdateRowDetailsVisibilityMode(DataGridRowDetailsVisibilityMode newDetailsMode)
         {
             int itemCount = DataConnection.Count;

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

@@ -53,7 +53,6 @@
       <Setter Property="VerticalAlignment" Value="Center" />
     </ControlTheme>
     <ControlTheme x:Key="DataGridCellTextBoxTheme" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
-      <Setter Property="Margin" Value="12,0,12,0" />
       <Setter Property="VerticalAlignment" Value="Stretch" />
       <Setter Property="Background" Value="Transparent" />
       <Style Selector="^ /template/ DataValidationErrors">

+ 6 - 5
src/Avalonia.Controls/AutoCompleteBox.cs

@@ -392,6 +392,8 @@ namespace Avalonia.Controls
         private AutoCompleteSelector<object>? _itemSelector;
         private AutoCompleteSelector<string?>? _textSelector;
 
+        private readonly EventHandler _populateDropDownHandler;
+
         public static readonly RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent =
             RoutedEvent.Register<SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox));
 
@@ -668,6 +670,7 @@ namespace Avalonia.Controls
 
                 if (newValue == TimeSpan.Zero)
                 {
+                    _delayTimer.Tick -= _populateDropDownHandler;
                     _delayTimer = null;
                 }
             }
@@ -678,7 +681,7 @@ namespace Avalonia.Controls
                 if (_delayTimer == null)
                 {
                     _delayTimer = new DispatcherTimer();
-                    _delayTimer.Tick += PopulateDropDown;
+                    _delayTimer.Tick += _populateDropDownHandler;
                 }
 
                 // Set the new tick interval
@@ -864,6 +867,7 @@ namespace Avalonia.Controls
         /// </summary>
         public AutoCompleteBox()
         {
+            _populateDropDownHandler = PopulateDropDown;
             ClearView();
         }
 
@@ -1771,10 +1775,7 @@ namespace Avalonia.Controls
         /// <param name="e">The event arguments.</param>
         private void PopulateDropDown(object? sender, EventArgs e)
         {
-            if (_delayTimer != null)
-            {
-                _delayTimer.Stop();
-            }
+            _delayTimer?.Stop();
 
             // Update the prefix/search text.
             SearchText = Text;

+ 35 - 42
src/Avalonia.Controls/Calendar/Calendar.cs

@@ -224,7 +224,7 @@ namespace Avalonia.Controls
     /// </para>
     /// </remarks>
     [TemplatePart(PART_ElementMonth, typeof(CalendarItem))]
-    [TemplatePart(PART_ElementRoot,  typeof(Panel))]
+    [TemplatePart(PART_ElementRoot, typeof(Panel))]
     public class Calendar : TemplatedControl
     {
         internal const int RowsPerMonth = 7;
@@ -338,14 +338,11 @@ namespace Avalonia.Controls
         /// <param name="e">The DependencyPropertyChangedEventArgs.</param>
         private void OnIsTodayHighlightedChanged(AvaloniaPropertyChangedEventArgs e)
         {
-            if (DisplayDate != null)
-            {
-                int i = DateTimeHelper.CompareYearMonth(DisplayDateInternal, DateTime.Today);
+            int i = DateTimeHelper.CompareYearMonth(DisplayDateInternal, DateTime.Today);
 
-                if (i > -2 && i < 2)
-                {
-                    UpdateMonths();
-                }
+            if (i > -2 && i < 2)
+            {
+                UpdateMonths();
             }
         }
 
@@ -655,7 +652,7 @@ namespace Avalonia.Controls
                 SelectedDatesChanged?.Invoke(this, e);
             }
         }
-        
+
         internal Collection<DateTime> RemovedItems { get; set; }
         internal DateTime? LastSelectedDateInternal { get; set; }
         internal DateTime? LastSelectedDate
@@ -914,7 +911,7 @@ namespace Avalonia.Controls
                 o => o.DisplayDateEnd,
                 (o, v) => o.DisplayDateEnd = v,
                 defaultBindingMode: BindingMode.TwoWay);
-        
+
         /// <summary>
         /// Gets or sets the last date to be displayed.
         /// </summary>
@@ -1242,7 +1239,7 @@ namespace Avalonia.Controls
                                     {
                                         b.IsSelected = false;
                                     }
-                                } 
+                                }
                             }
                         }
                     }
@@ -1278,7 +1275,7 @@ namespace Avalonia.Controls
 
         internal void OnPreviousClick()
         {
-            if (DisplayMode == CalendarMode.Month && DisplayDate != null)
+            if (DisplayMode == CalendarMode.Month)
             {
                 DateTime? d = DateTimeHelper.AddMonths(DateTimeHelper.DiscardDayTime(DisplayDate), -1);
                 if (d.HasValue)
@@ -1326,7 +1323,7 @@ namespace Avalonia.Controls
         }
         internal void OnNextClick()
         {
-            if (DisplayMode == CalendarMode.Month && DisplayDate != null)
+            if (DisplayMode == CalendarMode.Month)
             {
                 DateTime? d = DateTimeHelper.AddMonths(DateTimeHelper.DiscardDayTime(DisplayDate), 1);
                 if (d.HasValue)
@@ -1645,7 +1642,7 @@ namespace Avalonia.Controls
         {
             if (DisplayMode == CalendarMode.Month)
             {
-                if (LastSelectedDate.HasValue && DisplayDateInternal != null)
+                if (LastSelectedDate.HasValue)
                 {
                     // If a blackout day is inactive, when clicked on it, the
                     // previous inactive day which is not a blackout day can get
@@ -1897,24 +1894,21 @@ namespace Avalonia.Controls
             {
                 case CalendarMode.Month:
                     {
-                        if (DisplayDate != null)
-                        {
-                            DateTime? selectedDate = new DateTime(DisplayDateInternal.Year, DisplayDateInternal.Month, 1);
+                        DateTime? selectedDate = new DateTime(DisplayDateInternal.Year, DisplayDateInternal.Month, 1);
 
-                            if (DateTimeHelper.CompareYearMonth(DateTime.MaxValue, selectedDate.Value) > 0)
-                            {
-                                // since DisplayDate is not equal to
-                                // DateTime.MaxValue we are sure selectedDate is\
-                                // not null
-                                selectedDate = DateTimeHelper.AddMonths(selectedDate.Value, 1)!.Value;
-                                selectedDate = DateTimeHelper.AddDays(selectedDate.Value, -1)!.Value;
-                            }
-                            else
-                            {
-                                selectedDate = DateTime.MaxValue;
-                            }
-                            ProcessSelection(shift, selectedDate, null);
+                        if (DateTimeHelper.CompareYearMonth(DateTime.MaxValue, selectedDate.Value) > 0)
+                        {
+                            // since DisplayDate is not equal to
+                            // DateTime.MaxValue we are sure selectedDate is\
+                            // not null
+                            selectedDate = DateTimeHelper.AddMonths(selectedDate.Value, 1)!.Value;
+                            selectedDate = DateTimeHelper.AddDays(selectedDate.Value, -1)!.Value;
                         }
+                        else
+                        {
+                            selectedDate = DateTime.MaxValue;
+                        }
+                        ProcessSelection(shift, selectedDate, null);
                         break;
                     }
                 case CalendarMode.Year:
@@ -2026,7 +2020,6 @@ namespace Avalonia.Controls
                             focusDate = DisplayDate;
                             LastSelectedDate = DisplayDate;
                         }
-                        Debug.Assert(focusDate != null, "focusDate should not be null!");
                         FocusButton = FindDayButtonFromDay(focusDate);
 
                         if (FocusButton != null)
@@ -2091,17 +2084,17 @@ namespace Avalonia.Controls
 
         static Calendar()
         {
-            IsEnabledProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnIsEnabledChanged(e));
-            FirstDayOfWeekProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnFirstDayOfWeekChanged(e));
-            IsTodayHighlightedProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnIsTodayHighlightedChanged(e));
-            DisplayModeProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayModePropertyChanged(e));
-            SelectionModeProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnSelectionModeChanged(e));
-            SelectedDateProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnSelectedDateChanged(e));
-            DisplayDateProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateChanged(e));
-            DisplayDateStartProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateStartChanged(e));
-            DisplayDateEndProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateEndChanged(e));
-            KeyDownEvent.AddClassHandler<Calendar>((x,e) => x.Calendar_KeyDown(e));
-            KeyUpEvent.AddClassHandler<Calendar>((x,e) => x.Calendar_KeyUp(e));
+            IsEnabledProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnIsEnabledChanged(e));
+            FirstDayOfWeekProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnFirstDayOfWeekChanged(e));
+            IsTodayHighlightedProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnIsTodayHighlightedChanged(e));
+            DisplayModeProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnDisplayModePropertyChanged(e));
+            SelectionModeProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnSelectionModeChanged(e));
+            SelectedDateProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnSelectedDateChanged(e));
+            DisplayDateProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnDisplayDateChanged(e));
+            DisplayDateStartProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnDisplayDateStartChanged(e));
+            DisplayDateEndProperty.Changed.AddClassHandler<Calendar>((x, e) => x.OnDisplayDateEndChanged(e));
+            KeyDownEvent.AddClassHandler<Calendar>((x, e) => x.Calendar_KeyDown(e));
+            KeyUpEvent.AddClassHandler<Calendar>((x, e) => x.Calendar_KeyUp(e));
         }
 
         /// <summary>

+ 25 - 46
src/Avalonia.Controls/Calendar/CalendarItem.cs

@@ -4,7 +4,6 @@
 // All other rights reserved.
 
 using System;
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.Globalization;
 using Avalonia.Collections.Pooled;
@@ -353,7 +352,6 @@ namespace Avalonia.Controls.Primitives
         {
             if (Owner != null)
             {
-                Debug.Assert(Owner.DisplayDate != null, "The Owner Calendar's DisplayDate should not be null!");
                 _currentMonth = Owner.DisplayDateInternal;
             }
             else
@@ -361,17 +359,14 @@ namespace Avalonia.Controls.Primitives
                 _currentMonth = DateTime.Today;
             }
 
-            if (_currentMonth != null)
-            {
-                SetMonthModeHeaderButton();
-                SetMonthModePreviousButton(_currentMonth);
-                SetMonthModeNextButton(_currentMonth);
+            SetMonthModeHeaderButton();
+            SetMonthModePreviousButton(_currentMonth);
+            SetMonthModeNextButton(_currentMonth);
 
-                if (MonthView != null)
-                {
-                    SetDayTitles();
-                    SetCalendarDayButtons(_currentMonth);
-                }
+            if (MonthView != null)
+            {
+                SetDayTitles();
+                SetCalendarDayButtons(_currentMonth);
             }
         }
         private void SetMonthModeHeaderButton()
@@ -592,7 +587,6 @@ namespace Avalonia.Controls.Primitives
         {
             if (Owner != null)
             {
-                Debug.Assert(Owner.SelectedMonth != null, "The Owner Calendar's SelectedMonth should not be null!");
                 _currentMonth = (DateTime)Owner.SelectedMonth;
             }
             else
@@ -600,16 +594,13 @@ namespace Avalonia.Controls.Primitives
                 _currentMonth = DateTime.Today;
             }
 
-            if (_currentMonth != null)
-            {
-                SetYearModeHeaderButton();
-                SetYearModePreviousButton();
-                SetYearModeNextButton();
+            SetYearModeHeaderButton();
+            SetYearModePreviousButton();
+            SetYearModeNextButton();
 
-                if (YearView != null)
-                {
-                    SetMonthButtonsForYearMode();
-                }
+            if (YearView != null)
+            {
+                SetMonthButtonsForYearMode();
             }
         }
         private void SetYearModeHeaderButton()
@@ -660,7 +651,6 @@ namespace Avalonia.Controls.Primitives
                         childButton.IsCalendarButtonFocused = false;
                     }
 
-                    Debug.Assert(Owner.DisplayDateInternal != null, "The Owner Calendar's DisplayDateInternal should not be null!");
                     childButton.IsSelected = (DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateInternal) == 0);
 
                     if (DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateRangeStart) < 0 || DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateRangeEnd) > 0)
@@ -685,7 +675,6 @@ namespace Avalonia.Controls.Primitives
 
             if (Owner != null)
             {
-                Debug.Assert(Owner.SelectedYear != null, "The owning Calendar's selected year should not be null!");
                 selectedYear = Owner.SelectedYear;
                 _currentMonth = (DateTime)Owner.SelectedMonth;
             }
@@ -695,19 +684,16 @@ namespace Avalonia.Controls.Primitives
                 selectedYear = DateTime.Today;
             }
 
-            if (_currentMonth != null)
-            {
-                int decade = DateTimeHelper.DecadeOfDate(selectedYear);
-                int decadeEnd = DateTimeHelper.EndOfDecade(selectedYear);
+            int decade = DateTimeHelper.DecadeOfDate(selectedYear);
+            int decadeEnd = DateTimeHelper.EndOfDecade(selectedYear);
 
-                SetDecadeModeHeaderButton(decade, decadeEnd);
-                SetDecadeModePreviousButton(decade);
-                SetDecadeModeNextButton(decadeEnd);
+            SetDecadeModeHeaderButton(decade, decadeEnd);
+            SetDecadeModePreviousButton(decade);
+            SetDecadeModeNextButton(decadeEnd);
 
-                if (YearView != null)
-                {
-                    SetYearButtons(decade, decadeEnd);
-                }
+            if (YearView != null)
+            {
+                SetYearButtons(decade, decadeEnd);
             }
         }
         internal void UpdateYearViewSelection(CalendarButton calendarButton)
@@ -822,22 +808,15 @@ namespace Avalonia.Controls.Primitives
                 {
                     if (Owner.DisplayMode == CalendarMode.Month)
                     {
-                        if (Owner.DisplayDate != null)
-                        {
-                            d = Owner.DisplayDateInternal;
-                            Owner.SelectedMonth = new DateTime(d.Year, d.Month, 1);
-                        }
+                        d = Owner.DisplayDateInternal;
+                        Owner.SelectedMonth = new DateTime(d.Year, d.Month, 1);
                         Owner.DisplayMode = CalendarMode.Year;
                     }
                     else
                     {
                         Debug.Assert(Owner.DisplayMode == CalendarMode.Year, "The Owner Calendar's DisplayMode should be Year!");
-
-                        if (Owner.SelectedMonth != null)
-                        {
-                            d = Owner.SelectedMonth;
-                            Owner.SelectedYear = new DateTime(d.Year, d.Month, 1);
-                        }
+                        d = Owner.SelectedMonth;
+                        Owner.SelectedYear = new DateTime(d.Year, d.Month, 1);
                         Owner.DisplayMode = CalendarMode.Decade;
                     }
                 }

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

@@ -297,7 +297,7 @@ namespace Avalonia.Controls.Primitives
                 }
                 else
                 {
-                    if (item != null && DateTime.Compare(this[index], item) != 0 && Calendar.IsValidDateSelection(_owner, item))
+                    if (DateTime.Compare(this[index], item) != 0 && Calendar.IsValidDateSelection(_owner, item))
                     {
                         removedItems.Add(this[index]);
                         base.SetItem(index, item);

+ 2 - 4
src/Avalonia.Controls/Control.cs

@@ -8,6 +8,7 @@ using Avalonia.Controls.Templates;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
 using Avalonia.Interactivity;
+using Avalonia.LogicalTree;
 using Avalonia.Media;
 using Avalonia.Rendering;
 using Avalonia.Styling;
@@ -104,7 +105,6 @@ namespace Avalonia.Controls
         private static readonly HashSet<Control> _loadedQueue = new HashSet<Control>();
         private static readonly HashSet<Control> _loadedProcessingQueue = new HashSet<Control>();
 
-        private bool _isAttachedToVisualTree = false;
         private bool _isLoaded = false;
         private DataTemplates? _dataTemplates;
         private IControl? _focusAdorner;
@@ -347,7 +347,7 @@ namespace Avalonia.Controls
         internal void OnLoadedCore()
         {
             if (_isLoaded == false &&
-                _isAttachedToVisualTree)
+                ((ILogical)this).IsAttachedToLogicalTree)
             {
                 _isLoaded = true;
                 OnLoaded();
@@ -395,7 +395,6 @@ namespace Avalonia.Controls
         protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
         {
             base.OnAttachedToVisualTreeCore(e);
-            _isAttachedToVisualTree = true;
 
             InitializeIfNeeded();
 
@@ -406,7 +405,6 @@ namespace Avalonia.Controls
         protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
         {
             base.OnDetachedFromVisualTreeCore(e);
-            _isAttachedToVisualTree = false;
 
             OnUnloadedCore();
         }

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

@@ -8,8 +8,6 @@ namespace Avalonia.Controls
 {
     public static class DesktopApplicationExtensions
     {
-        [Obsolete("Running application without a cancellation token and a lifetime is no longer supported, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details", true)]
-        public static void Run(this Application app) => throw new NotSupportedException();
 
         /// <summary>
         /// On desktop-style platforms runs the application's main loop until closable is closed

+ 2 - 13
src/Avalonia.Controls/Documents/InlineUIContainer.cs

@@ -87,18 +87,7 @@ namespace Avalonia.Controls.Documents
 
             public override TextRunProperties? Properties { get; }
 
-            public override Size Size
-            {
-                get
-                {
-                    if (!Control.IsMeasureValid)
-                    {
-                        Control.Measure(Size.Infinity);
-                    }
-
-                    return Control.DesiredSize;
-                }
-            }
+            public override Size Size => Control.DesiredSize;
 
             public override double Baseline
             {
@@ -118,7 +107,7 @@ namespace Avalonia.Controls.Documents
 
             public override void Draw(DrawingContext drawingContext, Point origin)
             {             
-                Control.Arrange(new Rect(origin, Size));                
+                //noop            
             }
         }
     }

+ 0 - 61
src/Avalonia.Controls/DrawingPresenter.cs

@@ -1,61 +0,0 @@
-using System;
-using Avalonia.Controls.Shapes;
-using Avalonia.Media;
-using Avalonia.Metadata;
-
-namespace Avalonia.Controls
-{
-    [Obsolete("Use Image control with DrawingImage source")]
-    public class DrawingPresenter : Control
-    {
-        static DrawingPresenter()
-        {
-            AffectsMeasure<DrawingPresenter>(DrawingProperty);
-            AffectsRender<DrawingPresenter>(DrawingProperty);
-        }
-
-        public static readonly StyledProperty<Drawing> DrawingProperty =
-            AvaloniaProperty.Register<DrawingPresenter, Drawing>(nameof(Drawing));
-
-        public static readonly StyledProperty<Stretch> StretchProperty =
-            AvaloniaProperty.Register<DrawingPresenter, Stretch>(nameof(Stretch), Stretch.Uniform);
-
-        [Content]
-        public Drawing Drawing
-        {
-            get => GetValue(DrawingProperty);
-            set => SetValue(DrawingProperty, value);
-        }
-
-        public Stretch Stretch
-        {
-            get => GetValue(StretchProperty);
-            set => SetValue(StretchProperty, value);
-        }
-
-        private Matrix _transform = Matrix.Identity;
-
-        protected override Size MeasureOverride(Size availableSize)
-        {
-            if (Drawing == null) return new Size();
-
-            var (size, transform) = Shape.CalculateSizeAndTransform(availableSize, Drawing.GetBounds(), Stretch);
-
-            _transform = transform;
-
-            return size;
-        }
-
-        public override void Render(DrawingContext context)
-        {
-            if (Drawing != null)
-            {
-                using (context.PushPreTransform(_transform))
-                using (context.PushClip(new Rect(Bounds.Size)))
-                {
-                    Drawing.Draw(context);
-                }
-            }
-        }
-    }
-}

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

@@ -180,7 +180,7 @@ namespace Avalonia.Controls
                 return "Auto";
             }
 
-            string s = _value.ToString();
+            string s = _value.ToString(CultureInfo.InvariantCulture);
             return IsStar ? s + "*" : s;
         }
 

+ 1 - 12
src/Avalonia.Controls/LoggingExtensions.cs

@@ -1,21 +1,10 @@
-using System;
-using Avalonia.Controls;
+using Avalonia.Controls;
 using Avalonia.Logging;
 
 namespace Avalonia
 {
     public static class LoggingExtensions
     {
-        [Obsolete("Use LogToTrace")]
-        public static T LogToDebug<T>(
-            this T builder,
-            LogEventLevel level = LogEventLevel.Warning,
-            params string[] areas)
-                where T : AppBuilderBase<T>, new()
-        {
-            return LogToTrace(builder, level, areas);
-        }
-
         /// <summary>
         /// Logs Avalonia events to the <see cref="System.Diagnostics.Trace"/> sink.
         /// </summary>

+ 0 - 7
src/Avalonia.Controls/NativeMenuItem.cs

@@ -186,13 +186,6 @@ namespace Avalonia.Controls
         /// </summary>
         public event EventHandler? Click;
 
-        [Obsolete("Use Click event.")]
-        public event EventHandler Clicked
-        {
-            add => Click += value;
-            remove => Click -= value;
-        }
-
         void INativeMenuItemExporterEventsImplBridge.RaiseClicked()
         {
             Click?.Invoke(this, new EventArgs());

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

@@ -1,16 +1,7 @@
-using System;
-
-namespace Avalonia.Controls
+namespace Avalonia.Controls
 {
-
-    [Obsolete("This class exists to maintain backwards compatibility with existing code. Use NativeMenuItemSeparator instead")]
-    public class NativeMenuItemSeperator : NativeMenuItemSeparator 
-    {
-    }
-
     public class NativeMenuItemSeparator : NativeMenuItemBase
     {
-        [Obsolete("This is a temporary hack to make our MenuItem recognize this as a separator, don't use", true)]
-        public string Header => "-";
+
     }
 }

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

@@ -102,15 +102,12 @@ namespace Avalonia.Controls.Notifications
                 Content = content
             };
 
-            if (notification != null)
+            notificationControl.NotificationClosed += (sender, args) =>
             {
-                notificationControl.NotificationClosed += (sender, args) =>
-                {
-                    notification.OnClose?.Invoke();
+                notification?.OnClose?.Invoke();
 
-                    _items?.Remove(sender);
-                };
-            }
+                _items?.Remove(sender);
+            };
 
             notificationControl.PointerPressed += (sender, args) =>
             {

+ 61 - 53
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@@ -5,6 +5,7 @@ using System.Linq;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Primitives;
 using Avalonia.Data;
+using Avalonia.Data.Converters;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Layout;
@@ -45,14 +46,6 @@ namespace Avalonia.Controls
             AvaloniaProperty.RegisterDirect<NumericUpDown, bool>(nameof(ClipValueToMinMax),
                 updown => updown.ClipValueToMinMax, (updown, b) => updown.ClipValueToMinMax = b);
 
-        /// <summary>
-        /// Defines the <see cref="CultureInfo"/> property.
-        /// </summary>
-        [Obsolete]
-        public static readonly DirectProperty<NumericUpDown, CultureInfo?> CultureInfoProperty =
-            AvaloniaProperty.RegisterDirect<NumericUpDown, CultureInfo?>(nameof(CultureInfo), o => o.CultureInfo,
-                (o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture);
-
         /// <summary>
         /// Defines the <see cref="NumberFormat"/> property.
         /// </summary>
@@ -104,6 +97,13 @@ namespace Avalonia.Controls
             AvaloniaProperty.RegisterDirect<NumericUpDown, string?>(nameof(Text), o => o.Text, (o, v) => o.Text = v,
                 defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
 
+        /// <summary>
+        /// Defines the <see cref="TextConverter"/> property.
+        /// </summary>
+        public static readonly DirectProperty<NumericUpDown, IValueConverter?> TextConverterProperty =
+            AvaloniaProperty.RegisterDirect<NumericUpDown, IValueConverter?>(nameof(TextConverter),
+                updown => updown.TextConverter, (o, v) => o.TextConverter = v, null, BindingMode.OneWay, false);
+
         /// <summary>
         /// Defines the <see cref="Value"/> property.
         /// </summary>
@@ -133,6 +133,7 @@ namespace Avalonia.Controls
 
         private decimal? _value;
         private string? _text;
+        private IValueConverter? _textConverter;
         private bool _internalValueSet;
         private bool _clipValueToMinMax;
         private bool _isSyncingTextAndValueProperties;
@@ -187,21 +188,6 @@ namespace Avalonia.Controls
             set { SetAndRaise(ClipValueToMinMaxProperty, ref _clipValueToMinMax, value); }
         }
 
-        /// <summary>
-        /// Gets or sets the current CultureInfo.
-        /// </summary>
-        [Obsolete("CultureInfo is obsolete, please use NumberFormat instead.")]
-        public CultureInfo? CultureInfo
-        {
-            get { return _cultureInfo; }
-            set
-            {
-                SetAndRaise(CultureInfoProperty, ref _cultureInfo, value);
-                //Set and Raise the NumberFormatProperty when CultureInfo is changed.
-                SetAndRaise(NumberFormatProperty, ref _numberFormat, value?.NumberFormat);
-            }
-        }
-
         /// <summary>
         /// Gets or sets the current NumberFormatInfo
         /// </summary>
@@ -258,6 +244,8 @@ namespace Avalonia.Controls
 
         /// <summary>
         /// Gets or sets the parsing style (AllowLeadingWhite, Float, AllowHexSpecifier, ...). By default, Any.
+        /// Note that Hex style does not work with decimal. 
+        /// For hexadecimal display, use <see cref="TextConverter"/>.
         /// </summary>
         public NumberStyles ParsingNumberStyle
         {
@@ -274,6 +262,17 @@ namespace Avalonia.Controls
             set { SetAndRaise(TextProperty, ref _text, value); }
         }
 
+        /// <summary>
+        /// Gets or sets the custom bidirectional Text-Value converter.
+        /// Non-null converter overrides <see cref="ParsingNumberStyle"/>, providing finer control over 
+        /// string representation of the underlying value.
+        /// </summary>
+        public IValueConverter? TextConverter
+        {
+            get { return _textConverter; }
+            set { SetAndRaise(TextConverterProperty, ref _textConverter, value); }
+        }
+
         /// <summary>
         /// Gets or sets the value.
         /// </summary>
@@ -335,9 +334,6 @@ namespace Avalonia.Controls
         /// </summary>
         static NumericUpDown()
         {
-#pragma warning disable CS0612 // Type or member is obsolete
-            CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged);
-#pragma warning restore CS0612 // Type or member is obsolete
             NumberFormatProperty.Changed.Subscribe(OnNumberFormatChanged);
             FormatStringProperty.Changed.Subscribe(FormatStringChanged);
             IncrementProperty.Changed.Subscribe(IncrementChanged);
@@ -345,6 +341,7 @@ namespace Avalonia.Controls
             MaximumProperty.Changed.Subscribe(OnMaximumChanged);
             MinimumProperty.Changed.Subscribe(OnMinimumChanged);
             TextProperty.Changed.Subscribe(OnTextChanged);
+            TextConverterProperty.Changed.Subscribe(OnTextConverterChanged);
             ValueProperty.Changed.Subscribe(OnValueChanged);
         }
 
@@ -416,19 +413,6 @@ namespace Avalonia.Controls
             }
         }
 
-        /// <summary>
-        /// Called when the <see cref="CultureInfo"/> property value changed.
-        /// </summary>
-        /// <param name="oldValue">The old value.</param>
-        /// <param name="newValue">The new value.</param>
-        protected virtual void OnCultureInfoChanged(CultureInfo? oldValue, CultureInfo? newValue)
-        {
-            if (IsInitialized)
-            {
-                SyncTextAndValueProperties(false, null);
-            }
-        }
-
         /// <summary>
         /// Called when the <see cref="NumberFormat"/> property value changed.
         /// </summary>
@@ -524,6 +508,19 @@ namespace Avalonia.Controls
                 SyncTextAndValueProperties(true, Text);
             }
         }
+        
+        /// <summary>
+        /// Called when the <see cref="Text"/> property value changed.
+        /// </summary>
+        /// <param name="oldValue">The old value.</param>
+        /// <param name="newValue">The new value.</param>
+        protected virtual void OnTextConverterChanged(IValueConverter? oldValue, IValueConverter? newValue)
+        {
+            if (IsInitialized)
+            {
+                SyncTextAndValueProperties(false, null);
+            }
+        }
 
         /// <summary>
         /// Called when the <see cref="Value"/> property value changed.
@@ -651,6 +648,10 @@ namespace Avalonia.Controls
         /// <returns></returns>
         private string? ConvertValueToText()
         {
+            if (TextConverter != null)
+            {
+                return TextConverter.ConvertBack(Value, typeof(string), null, CultureInfo.CurrentCulture)?.ToString();
+            }
             //Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind.
             if (FormatString.Contains("{0"))
             {
@@ -729,20 +730,6 @@ namespace Avalonia.Controls
             }
         }
 
-        /// <summary>
-        /// Called when the <see cref="CultureInfo"/> property value changed.
-        /// </summary>
-        /// <param name="e">The event args.</param>
-        private static void OnCultureInfoChanged(AvaloniaPropertyChangedEventArgs e)
-        {
-            if (e.Sender is NumericUpDown upDown)
-            {
-                var oldValue = (CultureInfo?)e.OldValue;
-                var newValue = (CultureInfo?)e.NewValue;
-                upDown.OnCultureInfoChanged(oldValue, newValue);
-            }
-        }
-
         /// <summary>
         /// Called when the <see cref="NumberFormat"/> property value changed.
         /// </summary>
@@ -841,6 +828,21 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <summary>
+        /// Called when the <see cref="TextConverter"/> property value changed.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void OnTextConverterChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Sender is NumericUpDown upDown)
+            {
+                var oldValue = (IValueConverter?)e.OldValue;
+                var newValue = (IValueConverter?)e.NewValue;
+                upDown.OnTextConverterChanged(oldValue, newValue);
+            }
+        }
+
+
         /// <summary>
         /// Called when the <see cref="Value"/> property value changed.
         /// </summary>
@@ -1065,6 +1067,12 @@ namespace Avalonia.Controls
                 return null;
             }
             
+            if (TextConverter != null)
+            {
+                var valueFromText = TextConverter.Convert(text, typeof(decimal?), null, CultureInfo.CurrentCulture);
+                return (decimal?)valueFromText;
+            }
+
             if (IsPercent(FormatString))
             {
                 result = ParsePercent(text, NumberFormat);

Some files were not shown because too many files changed in this diff