浏览代码

Merge branch 'master' into features/NetAnalyzers/CA1822

Giuseppe Lippolis 3 年之前
父节点
当前提交
ed8b4fe5c0
共有 57 个文件被更改,包括 602 次插入330 次删除
  1. 4 0
      .editorconfig
  2. 15 0
      azure-pipelines-integrationtests.yml
  3. 6 6
      azure-pipelines.yml
  4. 4 0
      global.json
  5. 0 3
      packages/Avalonia/AvaloniaBuildTasks.targets
  6. 1 0
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  7. 3 5
      samples/ControlCatalog/Pages/ColorPickerPage.xaml
  8. 16 0
      samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs
  9. 1 1
      src/Avalonia.Base/AvaloniaProperty.cs
  10. 1 1
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  11. 12 0
      src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs
  12. 18 31
      src/Avalonia.Base/Media/Color.cs
  13. 12 18
      src/Avalonia.Base/Media/HslColor.cs
  14. 12 18
      src/Avalonia.Base/Media/HsvColor.cs
  15. 1 1
      src/Avalonia.Base/Media/UnicodeRange.cs
  16. 1 1
      src/Avalonia.Base/Utilities/IdentifierParser.cs
  17. 49 0
      src/Avalonia.Base/Utilities/SpanHelpers.cs
  18. 1 4
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  19. 0 6
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  20. 1 2
      src/Avalonia.Build.Tasks/Program.cs
  21. 72 0
      src/Avalonia.Build.Tasks/SpanCompat.cs
  22. 0 32
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
  23. 5 12
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  24. 30 3
      src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs
  25. 48 5
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
  26. 24 0
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs
  27. 82 35
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
  28. 0 53
      src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs
  29. 71 0
      src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs
  30. 26 8
      src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
  31. 15 18
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  32. 15 18
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
  33. 2 2
      src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs
  34. 2 2
      src/Avalonia.Controls/AppBuilderBase.cs
  35. 1 1
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  36. 1 1
      src/Avalonia.Controls/DefinitionBase.cs
  37. 1 1
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  38. 2 2
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  39. 1 1
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs
  40. 1 2
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
  41. 5 0
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  42. 0 3
      src/Avalonia.MicroCom/Avalonia.MicroCom.csproj
  43. 7 7
      src/Avalonia.Remote.Protocol/MetsysBson.cs
  44. 3 3
      src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml
  45. 3 3
      src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml
  46. 1 1
      src/Avalonia.X11/X11Structs.cs
  47. 1 1
      src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
  48. 2 2
      src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs
  49. 1 1
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  50. 1 0
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  51. 2 2
      src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs
  52. 9 9
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  53. 1 1
      tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs
  54. 0 1
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  55. 2 2
      tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj
  56. 1 1
      tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
  57. 6 0
      tests/Directory.Build.props

+ 4 - 0
.editorconfig

@@ -139,12 +139,16 @@ dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomme
 
 # CA1802: Use literals where appropriate
 dotnet_diagnostic.CA1802.severity = warning
+# CA1820: Test for empty strings using string length
+dotnet_diagnostic.CA1820.severity = warning
 # CA1821: Remove empty finalizers
 dotnet_diagnostic.CA1821.severity = warning
 # CA1822: Mark members as static
 dotnet_diagnostic.CA1822.severity = warning
 # CA1825: Avoid zero-length array allocations
 dotnet_diagnostic.CA1825.severity = warning
+#CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters
+dotnet_diagnostic.CA1847.severity = warning
 
 # Wrapping preferences
 csharp_wrap_before_ternary_opsigns = false

+ 15 - 0
azure-pipelines-integrationtests.yml

@@ -12,6 +12,16 @@ jobs:
     name: 'AvaloniaMacPool'
 
   steps:
+  - task: UseDotNet@2
+    displayName: 'Use .NET Core SDK 6.0.401'
+    inputs:
+      version: 6.0.401
+
+  - task: UseDotNet@2
+    displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
+    inputs:
+      version: 7.0.100-rc.2.22477.23
+      
   - script: system_profiler SPDisplaysDataType |grep Resolution
   
   - script: |
@@ -45,6 +55,11 @@ jobs:
     inputs:
       version: 6.0.401
 
+  - task: UseDotNet@2
+    displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
+    inputs:
+      version: 7.0.100-rc.2.22477.23
+
   - task: Windows Application Driver@0
     inputs:
       OperationType: 'Start'

+ 6 - 6
azure-pipelines.yml

@@ -35,9 +35,9 @@ jobs:
       version: 6.0.401
 
   - task: UseDotNet@2
-    displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12'
+    displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
     inputs:
-      version: 7.0.100-rc.1.22431.12
+      version: 7.0.100-rc.2.22477.23
 
   - task: CmdLine@2
     displayName: 'Install Workloads'
@@ -72,9 +72,9 @@ jobs:
       version: 6.0.401
 
   - task: UseDotNet@2
-    displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12'
+    displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
     inputs:
-      version: 7.0.100-rc.1.22431.12
+      version: 7.0.100-rc.2.22477.23
 
   - task: CmdLine@2
     displayName: 'Install Workloads'
@@ -143,9 +143,9 @@ jobs:
       version: 6.0.401
 
   - task: UseDotNet@2
-    displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12'
+    displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
     inputs:
-      version: 7.0.100-rc.1.22431.12
+      version: 7.0.100-rc.2.22477.23
 
   - task: CmdLine@2
     displayName: 'Install Workloads'

+ 4 - 0
global.json

@@ -1,4 +1,8 @@
 {
+    "sdk": {
+        "version": "7.0.100-rc.2.22477.23",
+        "rollForward": "latestFeature"
+    },
     "msbuild-sdks": {
         "Microsoft.Build.Traversal": "1.0.43",
         "MSBuild.Sdk.Extras": "3.0.22",

+ 0 - 3
packages/Avalonia/AvaloniaBuildTasks.targets

@@ -3,7 +3,6 @@
     <_AvaloniaUseExternalMSBuild>$(AvaloniaUseExternalMSBuild)</_AvaloniaUseExternalMSBuild>
     <_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false</_AvaloniaUseExternalMSBuild>
     <AvaloniaXamlReportImportance Condition="'$(AvaloniaXamlReportImportance)' == ''">low</AvaloniaXamlReportImportance>
-    <_AvaloniaPatchComInterop Condition="'$(_AvaloniaPatchComInterop)' == ''">false</_AvaloniaPatchComInterop>
     <_AvaloniaSkipXamlCompilation Condition="'$(_AvaloniaSkipXamlCompilation)' == ''">false</_AvaloniaSkipXamlCompilation>
   </PropertyGroup>
 
@@ -71,7 +70,6 @@
       Output="$(AvaloniaResourcesTemporaryFilePath)"
       Root="$(MSBuildProjectDirectory)"
       Resources="@(AvaloniaResource)"
-      EmbeddedResources="@(EmbeddedResources)"
       ReportImportance="$(AvaloniaXamlReportImportance)"/>
     <Exec 
       Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
@@ -106,7 +104,6 @@
       AssemblyOriginatorKeyFile="$(AssemblyOriginatorKeyFile)"
       SignAssembly="$(SignAssembly)"
       DelaySign="$(DelaySign)"
-      EnableComInteropPatching="$(_AvaloniaPatchComInterop)"
       SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
       DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
     />

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

@@ -5,6 +5,7 @@
     <TargetFramework>net6.0</TargetFramework>
     <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <RuntimeFrameworkVersion>6.0.9</RuntimeFrameworkVersion>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(RunNativeAotCompilation)' == 'true'">

+ 3 - 5
samples/ControlCatalog/Pages/ColorPickerPage.xaml

@@ -11,16 +11,17 @@
              x:Class="ControlCatalog.Pages.ColorPickerPage">
 
   <UserControl.Resources>
-    <pc:ThirdComponentConverter x:Key="ThirdComponent" />
   </UserControl.Resources>
 
-  <Grid ColumnDefinitions="Auto,10,Auto,10,Auto"
+  <Grid x:Name="LayoutRoot"
+        ColumnDefinitions="Auto,10,Auto"
         RowDefinitions="Auto,Auto">
     <ColorView Grid.Column="0"
                Grid.Row="0"
                ColorSpectrumShape="Ring" />
     <ColorPicker Grid.Column="0"
                  Grid.Row="1"
+                 HsvColor="hsv(120, 1, 1)"
                  Margin="0,50,0,0">
       <ColorPicker.Palette>
         <controls:FlatColorPalette />
@@ -56,8 +57,5 @@
                       IsAccentColorsVisible="False"
                       HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
     </Grid>
-    <Grid Grid.Column="4"
-          Grid.Row="0">
-    </Grid>
   </Grid>
 </UserControl>

+ 16 - 0
samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs

@@ -1,6 +1,8 @@
 using Avalonia;
 using Avalonia.Controls;
+using Avalonia.Layout;
 using Avalonia.Markup.Xaml;
+using Avalonia.Media;
 
 namespace ControlCatalog.Pages
 {
@@ -9,6 +11,20 @@ namespace ControlCatalog.Pages
         public ColorPickerPage()
         {
             InitializeComponent();
+
+            var layoutRoot = this.GetControl<Grid>("LayoutRoot");
+
+            // ColorPicker added from code-behind
+            var colorPicker = new ColorPicker()
+            {
+                Color = Colors.Blue,
+                Margin = new Thickness(0, 50, 0, 0),
+                HorizontalAlignment = HorizontalAlignment.Center,
+            };
+            Grid.SetColumn(colorPicker, 2);
+            Grid.SetRow(colorPicker, 1);
+            
+            layoutRoot.Children.Add(colorPicker);
         }
 
         private void InitializeComponent()

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

@@ -41,7 +41,7 @@ namespace Avalonia
         {
             _ = name ?? throw new ArgumentNullException(nameof(name));
 
-            if (name.Contains("."))
+            if (name.Contains('.'))
             {
                 throw new ArgumentException("'name' may not contain periods.");
             }

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

@@ -228,7 +228,7 @@ namespace Avalonia
             _ = type ?? throw new ArgumentNullException(nameof(type));
             _ = name ?? throw new ArgumentNullException(nameof(name));
 
-            if (name.Contains("."))
+            if (name.Contains('.'))
             {
                 throw new InvalidOperationException("Attached properties not supported.");
             }

+ 12 - 0
src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs

@@ -0,0 +1,12 @@
+using System.Runtime.CompilerServices;
+
+namespace System;
+
+#if !NET6_0_OR_GREATER
+public static class StringCompatibilityExtensions
+{
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static bool Contains(this string str, char search) =>
+        str.Contains(search.ToString());
+}
+#endif

+ 18 - 31
src/Avalonia.Base/Media/Color.cs

@@ -9,6 +9,7 @@ using System;
 using System.Globalization;
 #if !BUILDTASK
 using Avalonia.Animation.Animators;
+using static Avalonia.Utilities.SpanHelpers;
 #endif
 
 namespace Avalonia.Media
@@ -295,9 +296,7 @@ namespace Avalonia.Media
                     return false;
                 }
 
-                // TODO: (netstandard 2.1) Can use allocation free parsing.
-                if (!uint.TryParse(input.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture,
-                    out var parsed))
+                if (!input.TryParseUInt(NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed))
                 {
                     return false;
                 }
@@ -382,9 +381,9 @@ namespace Avalonia.Media
 
             if (components.Length == 3) // RGB
             {
-                if (InternalTryParseByte(components[0], out byte red) &&
-                    InternalTryParseByte(components[1], out byte green) &&
-                    InternalTryParseByte(components[2], out byte blue))
+                if (InternalTryParseByte(components[0].AsSpan(), out byte red) &&
+                    InternalTryParseByte(components[1].AsSpan(), out byte green) &&
+                    InternalTryParseByte(components[2].AsSpan(), out byte blue))
                 {
                     color = new Color(0xFF, red, green, blue);
                     return true;
@@ -392,10 +391,10 @@ namespace Avalonia.Media
             }
             else if (components.Length == 4) // RGBA
             {
-                if (InternalTryParseByte(components[0], out byte red) &&
-                    InternalTryParseByte(components[1], out byte green) &&
-                    InternalTryParseByte(components[2], out byte blue) &&
-                    InternalTryParseDouble(components[3], out double alpha))
+                if (InternalTryParseByte(components[0].AsSpan(), out byte red) &&
+                    InternalTryParseByte(components[1].AsSpan(), out byte green) &&
+                    InternalTryParseByte(components[2].AsSpan(), out byte blue) &&
+                    InternalTryParseDouble(components[3].AsSpan(), out double alpha))
                 {
                     color = new Color((byte)Math.Round(alpha * 255.0), red, green, blue);
                     return true;
@@ -403,17 +402,14 @@ namespace Avalonia.Media
             }
 
             // Local function to specially parse a byte value with an optional percentage sign
-            bool InternalTryParseByte(string inString, out byte outByte)
+            bool InternalTryParseByte(ReadOnlySpan<char> inString, out byte outByte)
             {
                 // The percent sign, if it exists, must be at the end of the number
-                int percentIndex = inString.IndexOf("%", StringComparison.Ordinal);
+                int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal);
 
                 if (percentIndex >= 0)
                 {
-                    var result = double.TryParse(
-                        inString.Substring(0, percentIndex),
-                        NumberStyles.Number,
-                        CultureInfo.InvariantCulture,
+                    var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
                         out double percentage);
 
                     outByte = (byte)Math.Round((percentage / 100.0) * 255.0);
@@ -421,37 +417,28 @@ namespace Avalonia.Media
                 }
                 else
                 {
-                    return byte.TryParse(
-                        inString,
-                        NumberStyles.Number,
-                        CultureInfo.InvariantCulture,
+                    return inString.TryParseByte(NumberStyles.Number, CultureInfo.InvariantCulture,
                         out outByte);
                 }
             }
 
             // Local function to specially parse a double value with an optional percentage sign
-            bool InternalTryParseDouble(string inString, out double outDouble)
+            bool InternalTryParseDouble(ReadOnlySpan<char> inString, out double outDouble)
             {
                 // The percent sign, if it exists, must be at the end of the number
-                int percentIndex = inString.IndexOf("%", StringComparison.Ordinal);
+                int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal);
 
                 if (percentIndex >= 0)
                 {
-                    var result = double.TryParse(
-                        inString.Substring(0, percentIndex),
-                        NumberStyles.Number,
-                        CultureInfo.InvariantCulture,
-                        out double percentage);
+                    var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
+                         out double percentage);
 
                     outDouble = percentage / 100.0;
                     return result;
                 }
                 else
                 {
-                    return double.TryParse(
-                        inString,
-                        NumberStyles.Number,
-                        CultureInfo.InvariantCulture,
+                    return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
                         out outDouble);
                 }
             }

+ 12 - 18
src/Avalonia.Base/Media/HslColor.cs

@@ -302,9 +302,9 @@ namespace Avalonia.Media
 
             if (components.Length == 3) // HSL
             {
-                if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
-                    TryInternalParse(components[1], out double saturation) &&
-                    TryInternalParse(components[2], out double lightness))
+                if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
+                    TryInternalParse(components[1].AsSpan(), out double saturation) &&
+                    TryInternalParse(components[2].AsSpan(), out double lightness))
                 {
                     hslColor = new HslColor(1.0, hue, saturation, lightness);
                     return true;
@@ -312,10 +312,10 @@ namespace Avalonia.Media
             }
             else if (components.Length == 4) // HSLA
             {
-                if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
-                    TryInternalParse(components[1], out double saturation) &&
-                    TryInternalParse(components[2], out double lightness) &&
-                    TryInternalParse(components[3], out double alpha))
+                if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
+                    TryInternalParse(components[1].AsSpan(), out double saturation) &&
+                    TryInternalParse(components[2].AsSpan(), out double lightness) &&
+                    TryInternalParse(components[3].AsSpan(), out double alpha))
                 {
                     hslColor = new HslColor(alpha, hue, saturation, lightness);
                     return true;
@@ -323,28 +323,22 @@ namespace Avalonia.Media
             }
 
             // Local function to specially parse a double value with an optional percentage sign
-            bool TryInternalParse(string inString, out double outDouble)
+            bool TryInternalParse(ReadOnlySpan<char> inString, out double outDouble)
             {
                 // The percent sign, if it exists, must be at the end of the number
-                int percentIndex = inString.IndexOf("%", StringComparison.Ordinal);
+                int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal);
 
                 if (percentIndex >= 0)
                 {
-                    var result = double.TryParse(
-                        inString.Substring(0, percentIndex),
-                        NumberStyles.Number,
-                        CultureInfo.InvariantCulture,
-                        out double percentage);
+                    var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
+                         out double percentage);
 
                     outDouble = percentage / 100.0;
                     return result;
                 }
                 else
                 {
-                    return double.TryParse(
-                        inString,
-                        NumberStyles.Number,
-                        CultureInfo.InvariantCulture,
+                    return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
                         out outDouble);
                 }
             }

+ 12 - 18
src/Avalonia.Base/Media/HsvColor.cs

@@ -302,9 +302,9 @@ namespace Avalonia.Media
 
             if (components.Length == 3) // HSV
             {
-                if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
-                    TryInternalParse(components[1], out double saturation) &&
-                    TryInternalParse(components[2], out double value))
+                if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
+                    TryInternalParse(components[1].AsSpan(), out double saturation) &&
+                    TryInternalParse(components[2].AsSpan(), out double value))
                 {
                     hsvColor = new HsvColor(1.0, hue, saturation, value);
                     return true;
@@ -312,10 +312,10 @@ namespace Avalonia.Media
             }
             else if (components.Length == 4) // HSVA
             {
-                if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
-                    TryInternalParse(components[1], out double saturation) &&
-                    TryInternalParse(components[2], out double value) &&
-                    TryInternalParse(components[3], out double alpha))
+                if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
+                    TryInternalParse(components[1].AsSpan(), out double saturation) &&
+                    TryInternalParse(components[2].AsSpan(), out double value) &&
+                    TryInternalParse(components[3].AsSpan(), out double alpha))
                 {
                     hsvColor = new HsvColor(alpha, hue, saturation, value);
                     return true;
@@ -323,28 +323,22 @@ namespace Avalonia.Media
             }
 
             // Local function to specially parse a double value with an optional percentage sign
-            bool TryInternalParse(string inString, out double outDouble)
+            bool TryInternalParse(ReadOnlySpan<char> inString, out double outDouble)
             {
                 // The percent sign, if it exists, must be at the end of the number
-                int percentIndex = inString.IndexOf("%", StringComparison.Ordinal);
+                int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal);
 
                 if (percentIndex >= 0)
                 {
-                    var result = double.TryParse(
-                        inString.Substring(0, percentIndex),
-                        NumberStyles.Number,
-                        CultureInfo.InvariantCulture,
-                        out double percentage);
+                    var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
+                         out double percentage);
 
                     outDouble = percentage / 100.0;
                     return result;
                 }
                 else
                 {
-                    return double.TryParse(
-                        inString,
-                        NumberStyles.Number,
-                        CultureInfo.InvariantCulture,
+                    return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
                         out outDouble);
                 }
             }

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

@@ -163,7 +163,7 @@ namespace Avalonia.Media
                             throw new FormatException("Could not parse specified Unicode range segment.");
                         }
 
-                        if (!single.Value.Contains("?"))
+                        if (!single.Value.Contains('?'))
                         {
                             start = int.Parse(single.Groups[1].Value, System.Globalization.NumberStyles.HexNumber);
                             end = start;

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

@@ -8,7 +8,7 @@ namespace Avalonia.Utilities
 #endif
     static class IdentifierParser
     {
-        public static ReadOnlySpan<char> ParseIdentifier(this ref CharacterReader r)
+        public static ReadOnlySpan<char> ParseIdentifier(this scoped ref CharacterReader r)
         {
             if (IsValidIdentifierStart(r.Peek))
             {

+ 49 - 0
src/Avalonia.Base/Utilities/SpanHelpers.cs

@@ -0,0 +1,49 @@
+using System;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+
+namespace Avalonia.Utilities
+{
+    public static class SpanHelpers
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool TryParseUInt(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out uint value)
+        {
+#if NETSTANDARD2_0
+            return uint.TryParse(span.ToString(), style, provider, out value);
+#else
+            return uint.TryParse(span, style, provider, out value);
+#endif
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool TryParseInt(this ReadOnlySpan<char> span, out int value)
+        {
+#if NETSTANDARD2_0
+            return int.TryParse(span.ToString(), out value);
+#else
+            return int.TryParse(span, out value);
+#endif
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool TryParseDouble(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out double value)
+        {
+#if NETSTANDARD2_0
+            return double.TryParse(span.ToString(), style, provider, out value);
+#else
+            return double.TryParse(span, style, provider, out value);
+#endif
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool TryParseByte(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out byte value)
+        {
+#if NETSTANDARD2_0
+            return byte.TryParse(span.ToString(), style, provider, out value);
+#else
+            return byte.TryParse(span, style, provider, out value);
+#endif
+        }
+    }
+}

+ 1 - 4
src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs

@@ -37,8 +37,7 @@ namespace Avalonia.Build.Tasks
             var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input,
                 File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
                 ProjectDirectory, OutputPath, VerifyIl, outputImportance,
-                (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
-                EnableComInteropPatching, SkipXamlCompilation, DebuggerLaunch);
+                (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, SkipXamlCompilation, DebuggerLaunch);
             if (!res.Success)
                 return false;
             if (!res.WrittenFile)
@@ -72,8 +71,6 @@ namespace Avalonia.Build.Tasks
         public string OutputPath { get; set; }
 
         public bool VerifyIl { get; set; }
-        
-        public bool EnableComInteropPatching { get; set; }
         public bool SkipXamlCompilation { get; set; }
         
         public string AssemblyOriginatorKeyFile { get; set; }

+ 0 - 6
src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs

@@ -18,8 +18,6 @@ namespace Avalonia.Build.Tasks
         public string Root { get; set; }
         [Required]
         public string Output { get; set; }
-        [Required]
-        public ITaskItem[] EmbeddedResources { get; set; }
 
         public string ReportImportance { get; set; }
 
@@ -148,10 +146,6 @@ namespace Avalonia.Build.Tasks
             Enum.TryParse<MessageImportance>(ReportImportance, out _reportImportance);
 
             BuildEngine.LogMessage($"GenerateAvaloniaResourcesTask -> Root: {Root}, {Resources?.Count()} resources, Output:{Output}", _reportImportance < MessageImportance.Low ? MessageImportance.High : _reportImportance);
-
-            foreach (var r in EmbeddedResources.Where(r => r.ItemSpec.EndsWith(".xaml") || r.ItemSpec.EndsWith(".paml") || r.ItemSpec.EndsWith(".axaml")))
-                BuildEngine.LogWarning(BuildEngineErrorCode.LegacyResmScheme, r.ItemSpec,
-                    "XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work");
             var resources = BuildResourceSources();
 
             if (!PreProcessXamlFiles(resources))

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

@@ -44,8 +44,7 @@ namespace Avalonia.Build.Tasks
                 OutputPath = args[2],
                 BuildEngine = new ConsoleBuildEngine(),
                 ProjectDirectory = Directory.GetCurrentDirectory(),
-                VerifyIl = true,
-                EnableComInteropPatching = true
+                VerifyIl = true
             }.Execute() ?
                 0 :
                 2;

+ 72 - 0
src/Avalonia.Build.Tasks/SpanCompat.cs

@@ -1,4 +1,7 @@
 #if !NETCOREAPP3_1_OR_GREATER
+using System.Globalization;
+using System.Runtime.CompilerServices;
+
 namespace System
 {
     // This is a hack to enable our span code to work inside MSBuild task without referencing System.Memory
@@ -9,6 +12,8 @@ namespace System
         private int _length;
         public int Length => _length;
 
+        public static implicit operator ReadOnlySpan<T>(string s) => new ReadOnlySpan<T>(s);
+
         public ReadOnlySpan(string s) : this(s, 0, s.Length)
         {
             
@@ -63,8 +68,75 @@ namespace System
             return Slice(start);
         }
 
+        public ReadOnlySpan<char> TrimEnd()
+        {
+            int end = Length - 1;
+            for (; end >= 0; end--)
+            {
+                if (!char.IsWhiteSpace(this[end]))
+                {
+                    break;
+                }
+            }
+            return Slice(0, end + 1);
+        }
+
+        public ReadOnlySpan<char> Trim()
+        {
+            return TrimStart().TrimEnd();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool TryParseUInt(NumberStyles style, IFormatProvider provider, out uint value)
+        {
+            return uint.TryParse(ToString(), style, provider, out value);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool TryParseInt(out int value)
+        {
+            return int.TryParse(ToString(), out value);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool TryParseDouble(NumberStyles style, IFormatProvider provider, out double value)
+        {
+            return double.TryParse(ToString(), style, provider, out value);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool TryParseByte(NumberStyles style, IFormatProvider provider, out byte value)
+        {
+            return byte.TryParse(ToString(), style, provider, out value);
+        }
+
         public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length);
 
+        internal int IndexOf(ReadOnlySpan<char> v, StringComparison ordinal, int start = 0)
+        {
+            if(Length == 0 || v.IsEmpty)
+            {
+                return -1;
+            }
+
+            for (var c = start; c < _length; c++)
+            {
+                if (this[c] == v[0])
+                {
+                    for(var i = 0; i < v.Length; i++)
+                    {
+                        if (this[c + i] != v[i])
+                        {
+                            break;
+                        }
+                    } 
+                    return c;
+                }
+            }
+
+            return -1;
+        }
+
         public static implicit operator ReadOnlySpan<T>(char[] arr) => new ReadOnlySpan<T>(new string(arr));
     }
 

+ 0 - 32
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs

@@ -24,38 +24,6 @@ namespace Avalonia.Build.Tasks
             string Name { get; }
             IEnumerable<IResource> Resources { get; }
         }
-        
-        class EmbeddedResources : IResourceGroup
-        {
-            private readonly AssemblyDefinition _asm;
-            public string Name => "EmbeddedResource";
-
-            public IEnumerable<IResource> Resources => _asm.MainModule.Resources.OfType<EmbeddedResource>()
-                .Select(r => new WrappedResource(_asm, r)).ToList();
-
-            public EmbeddedResources(AssemblyDefinition asm)
-            {
-                _asm = asm;
-            }
-            class WrappedResource : IResource
-            {
-                private readonly AssemblyDefinition _asm;
-                private readonly EmbeddedResource _res;
-
-                public WrappedResource(AssemblyDefinition asm, EmbeddedResource res)
-                {
-                    _asm = asm;
-                    _res = res;
-                }
-
-                public string Uri => $"resm:{Name}?assembly={_asm.Name.Name}";
-                public string Name => _res.Name;
-                public string FilePath => Name;
-                public byte[] FileContents => _res.GetResourceData();
-
-                public void Remove() => _asm.MainModule.Resources.Remove(_res);
-            }
-        }
 
         class AvaloniaResources : IResourceGroup
         {

+ 5 - 12
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@@ -39,15 +39,15 @@ namespace Avalonia.Build.Tasks
 
         public static CompileResult Compile(IBuildEngine engine, string input, string[] references,
             string projectDirectory,
-            string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool patchCom,
+            string output, bool verifyIl, MessageImportance logImportance, string strongNameKey,
             bool skipXamlCompilation)
         {
-            return Compile(engine, input, references, projectDirectory, output, verifyIl, logImportance, strongNameKey, patchCom, skipXamlCompilation, debuggerLaunch:false);
+            return Compile(engine, input, references, projectDirectory, output, verifyIl, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false);
         }
 
         internal static CompileResult Compile(IBuildEngine engine, string input, string[] references,
             string projectDirectory,
-            string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool patchCom, bool skipXamlCompilation, bool debuggerLaunch)
+            string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch)
         {
             var typeSystem = new CecilTypeSystem(
                 references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")),
@@ -58,15 +58,12 @@ namespace Avalonia.Build.Tasks
             if (!skipXamlCompilation)
             {
                 var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, logImportance, debuggerLaunch);
-                if (compileRes == null && !patchCom)
+                if (compileRes == null)
                     return new CompileResult(true);
                 if (compileRes == false)
                     return new CompileResult(false);
             }
 
-            if (patchCom)
-                ComInteropHelper.PatchAssembly(asm, typeSystem);
-
             var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
             if (!string.IsNullOrWhiteSpace(strongNameKey))
                 writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
@@ -112,9 +109,8 @@ namespace Avalonia.Build.Tasks
                 }
             }
             var asm = typeSystem.TargetAssemblyDefinition;
-            var emres = new EmbeddedResources(asm);
             var avares = new AvaloniaResources(asm, projectDirectory);
-            if (avares.Resources.Count(CheckXamlName) == 0 && emres.Resources.Count(CheckXamlName) == 0)
+            if (avares.Resources.Count(CheckXamlName) == 0)
                 // Nothing to do
                 return null;
 
@@ -436,9 +432,6 @@ namespace Avalonia.Build.Tasks
                 return true;
             }
             
-            if (emres.Resources.Count(CheckXamlName) != 0)
-                if (!CompileGroup(emres))
-                    return false;
             if (avares.Resources.Count(CheckXamlName) != 0)
             {
                 if (!CompileGroup(avares))

+ 30 - 3
src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs

@@ -1,4 +1,6 @@
-namespace Avalonia.Controls
+using Avalonia.Controls.Primitives;
+
+namespace Avalonia.Controls
 {
     /// <summary>
     /// Presents a color for user editing using a spectrum, palette and component sliders within a drop down.
@@ -11,8 +13,33 @@
         /// </summary>
         public ColorPicker() : base()
         {
-            // Completely ignore property changes here
-            // The ColorView in the control template is responsible to manage this
+        }
+
+        /// <inheritdoc/>
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+        {
+            base.OnApplyTemplate(e);
+
+            // Until this point the ColorPicker itself is responsible to process property updates.
+            // This, for example, syncs Color with HsvColor and updates primitive controls.
+            //
+            // However, when the template is created, hand-off this change processing to the
+            // ColorView within the control template itself. Remember ColorPicker derives from
+            // ColorView so we don't want two instances of the same logic fighting each other.
+            // It is best to hand-off to the ColorView in the control template because that is the
+            // primary point of user-interaction for the overall control. It also simplifies binding.
+            //
+            // Keep in mind this hand-off is not possible until the template controls are created
+            // which is done after the ColorPicker is instantiated. The ColorPicker must still
+            // process updates before the template is applied to ensure all property changes in
+            // XAML or object initializers are handled correctly. Otherwise, there can be bugs
+            // such as setting the Color property doesn't work because the HsvColor is never updated
+            // and then the Color value is lost once the template loads (and the template ColorView
+            // takes over).
+            //
+            // In order to complete this hand-off, completely ignore property changes here in the
+            // ColorPicker. This means the ColorView in the control template is now responsible to
+            // process property changes and handle primary calculations.
             base.ignorePropertyChanged = true;
         }
     }

+ 48 - 5
src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs

@@ -2,6 +2,7 @@
 using Avalonia.Controls.Metadata;
 using Avalonia.Layout;
 using Avalonia.Media;
+using Avalonia.Media.Imaging;
 using Avalonia.Utilities;
 
 namespace Avalonia.Controls.Primitives
@@ -31,6 +32,8 @@ namespace Avalonia.Controls.Primitives
 
         protected bool ignorePropertyChanged = false;
 
+        private WriteableBitmap? _backgroundBitmap;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ColorSlider"/> class.
         /// </summary>
@@ -38,6 +41,18 @@ namespace Avalonia.Controls.Primitives
         {
         }
 
+        /// <inheritdoc/>
+        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnAttachedToVisualTree(e);
+        }
+
+        /// <inheritdoc/>
+        protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnDetachedFromVisualTree(e);
+        }
+
         /// <summary>
         /// Updates the visual state of the control by applying latest PseudoClasses.
         /// </summary>
@@ -98,7 +113,7 @@ namespace Avalonia.Controls.Primitives
 
             if (pixelWidth != 0 && pixelHeight != 0)
             {
-                var bitmap = await ColorPickerHelpers.CreateComponentBitmapAsync(
+                ArrayList<byte> bgraPixelData = await ColorPickerHelpers.CreateComponentBitmapAsync(
                     pixelWidth,
                     pixelHeight,
                     Orientation,
@@ -108,9 +123,27 @@ namespace Avalonia.Controls.Primitives
                     IsAlphaMaxForced,
                     IsSaturationValueMaxForced);
 
-                if (bitmap != null)
+                if (bgraPixelData != null)
                 {
-                    Background = new ImageBrush(ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight));
+                    if (_backgroundBitmap != null)
+                    {
+                        // TODO: CURRENTLY DISABLED DUE TO INTERMITTENT CRASHES IN SKIA/RENDERER
+                        //
+                        // Re-use the existing WriteableBitmap
+                        // This assumes the height, width and byte counts are the same and must be set to null
+                        // elsewhere if that assumption is ever not true.
+                        // ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData);
+
+                        // TODO: ALSO DISABLED DISPOSE DUE TO INTERMITTENT CRASHES
+                        //_backgroundBitmap?.Dispose();
+                        _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight);
+                    }
+                    else
+                    {
+                        _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight);
+                    }
+
+                    Background = new ImageBrush(_backgroundBitmap);
                 }
             }
         }
@@ -350,11 +383,11 @@ namespace Avalonia.Controls.Primitives
                 return;
             }
 
-            // Always keep the two color properties in sync
             if (change.Property == ColorProperty)
             {
                 ignorePropertyChanged = true;
 
+                // Always keep the two color properties in sync
                 HsvColor = Color.ToHsv();
 
                 SetColorToSliderValues();
@@ -367,7 +400,10 @@ namespace Avalonia.Controls.Primitives
 
                 ignorePropertyChanged = false;
             }
-            else if (change.Property == ColorModelProperty)
+            else if (change.Property == ColorComponentProperty ||
+                     change.Property == ColorModelProperty ||
+                     change.Property == IsAlphaMaxForcedProperty ||
+                     change.Property == IsSaturationValueMaxForcedProperty)
             {
                 ignorePropertyChanged = true;
 
@@ -381,6 +417,7 @@ namespace Avalonia.Controls.Primitives
             {
                 ignorePropertyChanged = true;
 
+                // Always keep the two color properties in sync
                 Color = HsvColor.ToRgb();
 
                 SetColorToSliderValues();
@@ -399,7 +436,13 @@ namespace Avalonia.Controls.Primitives
             }
             else if (change.Property == BoundsProperty)
             {
+                // If the control's overall dimensions have changed the background bitmap size also needs to change.
+                // This means the existing bitmap must be released to be recreated correctly in UpdateBackground().
+                _backgroundBitmap?.Dispose();
+                _backgroundBitmap = null;
+
                 UpdateBackground();
+                UpdatePseudoClasses();
             }
             else if (change.Property == ValueProperty ||
                      change.Property == MinimumProperty ||

+ 24 - 0
src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs

@@ -93,6 +93,14 @@ namespace Avalonia.Controls.Primitives
                 nameof(Shape),
                 ColorSpectrumShape.Box);
 
+        /// <summary>
+        /// Defines the <see cref="ThirdComponent"/> property.
+        /// </summary>
+        public static readonly StyledProperty<ColorComponent> ThirdComponentProperty =
+            AvaloniaProperty.Register<ColorSpectrum, ColorComponent>(
+                nameof(ThirdComponent),
+                ColorComponent.Component3); // Value
+
         /// <summary>
         /// Gets or sets the currently selected color in the RGB color model.
         /// </summary>
@@ -218,5 +226,21 @@ namespace Avalonia.Controls.Primitives
             get => GetValue(ShapeProperty);
             set => SetValue(ShapeProperty, value);
         }
+
+        /// <summary>
+        /// Gets the third HSV color component that is NOT displayed by the spectrum.
+        /// This is automatically calculated from the <see cref="Components"/> property.
+        /// </summary>
+        /// <remarks>
+        /// This property should be used for any external color slider that represents the
+        /// third component of the color. Note that this property uses the generic
+        /// <see cref="ColorComponent"/> type instead of the more accurate <see cref="HsvComponent"/>
+        /// to allow direct usage by the generalized color sliders.
+        /// </remarks>
+        public ColorComponent ThirdComponent
+        {
+            get => GetValue(ThirdComponentProperty);
+            private set => SetValue(ThirdComponentProperty, value);
+        }
     }
 }

+ 82 - 35
src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs

@@ -73,6 +73,8 @@ namespace Avalonia.Controls.Primitives
         private WriteableBitmap? _saturationMaximumBitmap;
 
         private WriteableBitmap? _valueBitmap;
+        private WriteableBitmap? _minBitmap;
+        private WriteableBitmap? _maxBitmap;
 
         // Fields used by UpdateEllipse() to ensure that it's using the data
         // associated with the last call to CreateBitmapsAndColorMap(),
@@ -95,7 +97,7 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Initializes a new instance of the <see cref="ColorSpectrum"/> class.
         /// </summary>
-        public ColorSpectrum()
+        public ColorSpectrum() : base()
         {
             _shapeFromLastBitmapCreation = Shape;
             _componentsFromLastBitmapCreation = Components;
@@ -171,6 +173,18 @@ namespace Avalonia.Controls.Primitives
         {
             base.OnAttachedToVisualTree(e);
 
+            // If the color was updated while this ColorSpectrum was not part of the visual tree,
+            // the selection ellipse may be in an incorrect position. This is because the spectrum
+            // renders based on layout scaling to avoid color banding; however, layout scale is only
+            // available when the control is attached to the visual tree. The ColorSpectrum's color
+            // may be updated from code-behind or from binding with another control when it's not
+            // part of the visual tree.
+            //
+            //  See discussion: https://github.com/AvaloniaUI/Avalonia/discussions/9077
+            //
+            // To work-around this issue the selection ellipse is refreshed here.
+            UpdateEllipse();
+
             // OnAttachedToVisualTree is called after OnApplyTemplate so events cannot be connected here
         }
 
@@ -489,6 +503,23 @@ namespace Avalonia.Controls.Primitives
             }
             else if (change.Property == ComponentsProperty)
             {
+                // Calculate and update the ThirdComponent value
+                switch (Components)
+                {
+                    case ColorSpectrumComponents.HueSaturation:
+                    case ColorSpectrumComponents.SaturationHue:
+                        ThirdComponent = (ColorComponent)HsvComponent.Value;
+                        break;
+                    case ColorSpectrumComponents.HueValue:
+                    case ColorSpectrumComponents.ValueHue:
+                        ThirdComponent = (ColorComponent)HsvComponent.Saturation;
+                        break;
+                    case ColorSpectrumComponents.SaturationValue:
+                    case ColorSpectrumComponents.ValueSaturation:
+                        ThirdComponent = (ColorComponent)HsvComponent.Hue;
+                        break;
+                }
+
                 CreateBitmapsAndColorMap();
             }
 
@@ -588,6 +619,10 @@ namespace Avalonia.Controls.Primitives
             RaiseColorChanged();
         }
 
+        /// <summary>
+        /// Updates the selected <see cref="HsvColor"/> and <see cref="Color"/> based on a point within the color spectrum.
+        /// </summary>
+        /// <param name="point">The point on the spectrum representing the color.</param>
         private void UpdateColorFromPoint(PointerPoint point)
         {
             // If we haven't initialized our HSV value array yet, then we should just ignore any user input -
@@ -664,6 +699,9 @@ namespace Avalonia.Controls.Primitives
             UpdateColor(hsvAtPoint);
         }
 
+        /// <summary>
+        /// Updates the position of the selection ellipse on the spectrum which indicates the selected color.
+        /// </summary>
         private void UpdateEllipse()
         {
             if (_selectionEllipsePanel == null)
@@ -832,6 +870,8 @@ namespace Avalonia.Controls.Primitives
             }
 
             // Remember the bitmap size follows physical device pixels
+            // Warning: LayoutHelper.GetLayoutScale() doesn't work unless the control is visible
+            // This will not be true in all cases if the color is updated from another control or code-behind
             var scale = LayoutHelper.GetLayoutScale(this);
             Canvas.SetLeft(_selectionEllipsePanel, (xPosition / scale) - (_selectionEllipsePanel.Width / 2));
             Canvas.SetTop(_selectionEllipsePanel, (yPosition / scale) - (_selectionEllipsePanel.Height / 2));
@@ -973,13 +1013,13 @@ namespace Avalonia.Controls.Primitives
 
             // The middle 4 are only needed and used in the case of hue as the third dimension.
             // Saturation and luminosity need only a min and max.
-            List<byte> bgraMinPixelData = new List<byte>();
-            List<byte> bgraMiddle1PixelData = new List<byte>();
-            List<byte> bgraMiddle2PixelData = new List<byte>();
-            List<byte> bgraMiddle3PixelData = new List<byte>();
-            List<byte> bgraMiddle4PixelData = new List<byte>();
-            List<byte> bgraMaxPixelData = new List<byte>();
-            List<Hsv> newHsvValues = new List<Hsv>();
+            ArrayList<byte> bgraMinPixelData;
+            ArrayList<byte> bgraMiddle1PixelData;
+            ArrayList<byte> bgraMiddle2PixelData;
+            ArrayList<byte> bgraMiddle3PixelData;
+            ArrayList<byte> bgraMiddle4PixelData;
+            ArrayList<byte> bgraMaxPixelData;
+            List<Hsv> newHsvValues;
 
             // In Avalonia, Bounds returns the actual device-independent pixel size of a control.
             // However, this is not necessarily the size of the control rendered on a display.
@@ -990,20 +1030,27 @@ namespace Avalonia.Controls.Primitives
             int pixelDimension = (int)Math.Round(minDimension * scale);
             var pixelCount = pixelDimension * pixelDimension;
             var pixelDataSize = pixelCount * 4;
-            bgraMinPixelData.Capacity = pixelDataSize;
+
+            bgraMinPixelData = new ArrayList<byte>(pixelDataSize);
+            bgraMaxPixelData = new ArrayList<byte>(pixelDataSize);
+            newHsvValues = new List<Hsv>(pixelCount);
 
             // We'll only save pixel data for the middle bitmaps if our third dimension is hue.
             if (components == ColorSpectrumComponents.ValueSaturation ||
                 components == ColorSpectrumComponents.SaturationValue)
             {
-                bgraMiddle1PixelData.Capacity = pixelDataSize;
-                bgraMiddle2PixelData.Capacity = pixelDataSize;
-                bgraMiddle3PixelData.Capacity = pixelDataSize;
-                bgraMiddle4PixelData.Capacity = pixelDataSize;
+                bgraMiddle1PixelData = new ArrayList<byte>(pixelDataSize);
+                bgraMiddle2PixelData = new ArrayList<byte>(pixelDataSize);
+                bgraMiddle3PixelData = new ArrayList<byte>(pixelDataSize);
+                bgraMiddle4PixelData = new ArrayList<byte>(pixelDataSize);
+            }
+            else
+            {
+                bgraMiddle1PixelData = new ArrayList<byte>(0);
+                bgraMiddle2PixelData = new ArrayList<byte>(0);
+                bgraMiddle3PixelData = new ArrayList<byte>(0);
+                bgraMiddle4PixelData = new ArrayList<byte>(0);
             }
-
-            bgraMaxPixelData.Capacity = pixelDataSize;
-            newHsvValues.Capacity = pixelCount;
 
             await Task.Run(() =>
             {
@@ -1056,28 +1103,28 @@ namespace Avalonia.Controls.Primitives
 
                 ColorSpectrumComponents components2 = Components;
 
-                WriteableBitmap minBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMinPixelData, pixelWidth, pixelHeight);
-                WriteableBitmap maxBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMaxPixelData, pixelWidth, pixelHeight);
+                _minBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMinPixelData, pixelWidth, pixelHeight);
+                _maxBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMaxPixelData, pixelWidth, pixelHeight);
 
                 switch (components2)
                 {
                     case ColorSpectrumComponents.HueValue:
                     case ColorSpectrumComponents.ValueHue:
-                        _saturationMinimumBitmap = minBitmap;
-                        _saturationMaximumBitmap = maxBitmap;
+                        _saturationMinimumBitmap = _minBitmap;
+                        _saturationMaximumBitmap = _maxBitmap;
                         break;
                     case ColorSpectrumComponents.HueSaturation:
                     case ColorSpectrumComponents.SaturationHue:
-                        _valueBitmap = maxBitmap;
+                        _valueBitmap = _maxBitmap;
                         break;
                     case ColorSpectrumComponents.ValueSaturation:
                     case ColorSpectrumComponents.SaturationValue:
-                        _hueRedBitmap = minBitmap;
+                        _hueRedBitmap = _minBitmap;
                         _hueYellowBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle1PixelData, pixelWidth, pixelHeight);
                         _hueGreenBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle2PixelData, pixelWidth, pixelHeight);
                         _hueCyanBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle3PixelData, pixelWidth, pixelHeight);
                         _hueBlueBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle4PixelData, pixelWidth, pixelHeight);
-                        _huePurpleBitmap = maxBitmap;
+                        _huePurpleBitmap = _maxBitmap;
                         break;
                 }
 
@@ -1111,12 +1158,12 @@ namespace Avalonia.Controls.Primitives
             double maxSaturation,
             double minValue,
             double maxValue,
-            List<byte> bgraMinPixelData,
-            List<byte> bgraMiddle1PixelData,
-            List<byte> bgraMiddle2PixelData,
-            List<byte> bgraMiddle3PixelData,
-            List<byte> bgraMiddle4PixelData,
-            List<byte> bgraMaxPixelData,
+            ArrayList<byte> bgraMinPixelData,
+            ArrayList<byte> bgraMiddle1PixelData,
+            ArrayList<byte> bgraMiddle2PixelData,
+            ArrayList<byte> bgraMiddle3PixelData,
+            ArrayList<byte> bgraMiddle4PixelData,
+            ArrayList<byte> bgraMaxPixelData,
             List<Hsv> newHsvValues)
         {
             double hMin = minHue;
@@ -1271,12 +1318,12 @@ namespace Avalonia.Controls.Primitives
             double maxSaturation,
             double minValue,
             double maxValue,
-            List<byte> bgraMinPixelData,
-            List<byte> bgraMiddle1PixelData,
-            List<byte> bgraMiddle2PixelData,
-            List<byte> bgraMiddle3PixelData,
-            List<byte> bgraMiddle4PixelData,
-            List<byte> bgraMaxPixelData,
+            ArrayList<byte> bgraMinPixelData,
+            ArrayList<byte> bgraMiddle1PixelData,
+            ArrayList<byte> bgraMiddle2PixelData,
+            ArrayList<byte> bgraMiddle3PixelData,
+            ArrayList<byte> bgraMiddle4PixelData,
+            ArrayList<byte> bgraMaxPixelData,
             List<Hsv> newHsvValues)
         {
             double hMin = minHue;

+ 0 - 53
src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs

@@ -1,53 +0,0 @@
-using System;
-using System.Globalization;
-using Avalonia.Data.Converters;
-
-namespace Avalonia.Controls.Primitives.Converters
-{
-    /// <summary>
-    /// Gets the third <see cref="ColorComponent"/> corresponding with a given
-    /// <see cref="ColorSpectrumComponents"/> that represents the other two components.
-    /// </summary>
-    /// <remarks>
-    /// This is a highly-specialized converter for the color picker.
-    /// </remarks>
-    public class ThirdComponentConverter : IValueConverter
-    {
-        /// <inheritdoc/>
-        public object? Convert(
-            object? value,
-            Type targetType,
-            object? parameter,
-            CultureInfo culture)
-        {
-            if (value is ColorSpectrumComponents components)
-            {
-                // Note: Alpha is not relevant here
-                switch (components)
-                {
-                    case ColorSpectrumComponents.HueSaturation:
-                    case ColorSpectrumComponents.SaturationHue:
-                        return (ColorComponent)HsvComponent.Value;
-                    case ColorSpectrumComponents.HueValue:
-                    case ColorSpectrumComponents.ValueHue:
-                        return (ColorComponent)HsvComponent.Saturation;
-                    case ColorSpectrumComponents.SaturationValue:
-                    case ColorSpectrumComponents.ValueSaturation:
-                        return (ColorComponent)HsvComponent.Hue;
-                }
-            }
-
-            return AvaloniaProperty.UnsetValue;
-        }
-
-        /// <inheritdoc/>
-        public object? ConvertBack(
-            object? value,
-            Type targetType,
-            object? parameter,
-            CultureInfo culture)
-        {
-            return AvaloniaProperty.UnsetValue;
-        }
-    }
-}

+ 71 - 0
src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs

@@ -0,0 +1,71 @@
+namespace Avalonia.Controls.Primitives
+{
+    /// <summary>
+    /// A thin wrapper over an <see cref="System.Array"/> that allows some additional list-like functionality.
+    /// </summary>
+    /// <remarks>
+    /// This is only for internal ColorPicker-related functionality and should not be used elsewhere.
+    /// It is added for performance to enjoy the simplicity of the IList.Add() method without requiring
+    /// an additional copy to turn a list into an array for bitmaps.
+    /// </remarks>
+    /// <typeparam name="T">The type of items in the array.</typeparam>
+    internal class ArrayList<T>
+    {
+        private int _nextIndex = 0;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ArrayList{T}"/> class.
+        /// </summary>
+        public ArrayList(int capacity)
+        {
+            Capacity = capacity;
+            Array = new T[capacity];
+        }
+
+        /// <summary>
+        /// Provides access to the underlying array by index.
+        /// This exists for simplification and the <see cref="Array"/> property
+        /// may also be used.
+        /// </summary>
+        /// <param name="i">The index of the item to get or set.</param>
+        /// <returns>The item at the given index.</returns>
+        public T this[int i]
+        {
+            get => Array[i];
+            set => Array[i] = value;
+        }
+
+        /// <summary>
+        /// Gets the underlying array.
+        /// </summary>
+        public T[] Array { get; private set; }
+
+        /// <summary>
+        /// Gets the fixed capacity/size of the array.
+        /// This must be set during construction.
+        /// </summary>
+        public int Capacity { get; private set; }
+
+        /// <summary>
+        /// Adds the given item to the array at the next available index.
+        /// WARNING: This must be used carefully and only once, in sequence.
+        /// </summary>
+        /// <param name="item">The item to add.</param>
+        public void Add(T item)
+        {
+            if (_nextIndex >= 0 &&
+                _nextIndex < Capacity)
+            {
+                Array[_nextIndex] = item;
+                _nextIndex++;
+            }
+            else
+            {
+                // If necessary an exception could be thrown here
+                // throw new IndexOutOfRangeException();
+            }
+
+            return;
+        }
+    }
+}

+ 26 - 8
src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs

@@ -37,7 +37,7 @@ namespace Avalonia.Controls.Primitives
         /// during calculation with the HSVA color model.
         /// This will ensure colors are always discernible regardless of saturation/value.</param>
         /// <returns>A new bitmap representing a gradient of color component values.</returns>
-        public static async Task<byte[]> CreateComponentBitmapAsync(
+        public static async Task<ArrayList<byte>> CreateComponentBitmapAsync(
             int width,
             int height,
             Orientation orientation,
@@ -49,14 +49,14 @@ namespace Avalonia.Controls.Primitives
         {
             if (width == 0 || height == 0)
             {
-                return Array.Empty<byte>();
+                return new ArrayList<byte>(0);
             }
 
-            var bitmap = await Task.Run<byte[]>(() =>
+            var bitmap = await Task.Run<ArrayList<byte>>(() =>
             {
                 int pixelDataIndex = 0;
                 double componentStep;
-                byte[] bgraPixelData;
+                ArrayList<byte> bgraPixelData;
                 Color baseRgbColor = Colors.White;
                 Color rgbColor;
                 int bgraPixelDataHeight;
@@ -64,7 +64,7 @@ namespace Avalonia.Controls.Primitives
 
                 // Allocate the buffer
                 // BGRA formatted color components 1 byte each (4 bytes in a pixel)
-                bgraPixelData       = new byte[width * height * 4];
+                bgraPixelData       = new ArrayList<byte>(width * height * 4);
                 bgraPixelDataHeight = height * 4;
                 bgraPixelDataWidth  = width * 4;
 
@@ -604,7 +604,7 @@ namespace Avalonia.Controls.Primitives
         /// <param name="pixelHeight">The pixel height of the bitmap.</param>
         /// <returns>A new <see cref="WriteableBitmap"/>.</returns>
         public static WriteableBitmap CreateBitmapFromPixelData(
-            IList<byte> bgraPixelData,
+            ArrayList<byte> bgraPixelData,
             int pixelWidth,
             int pixelHeight)
         {
@@ -617,13 +617,31 @@ namespace Avalonia.Controls.Primitives
                 PixelFormat.Bgra8888,
                 AlphaFormat.Premul);
 
-            // Warning: This is highly questionable
             using (var frameBuffer = bitmap.Lock())
             {
-                Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count);
+                Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length);
             }
 
             return bitmap;
         }
+
+        /// <summary>
+        /// Updates the given <see cref="WriteableBitmap"/> with new, raw BGRA pre-multiplied alpha pixel data.
+        /// TODO: THIS METHOD IS CURRENTLY PROVIDED AS REFERENCE BUT CAUSES INTERMITTENT CRASHES IF USED.
+        /// WARNING: The bitmap's width, height and byte count MUST not have changed and MUST be enforced externally.
+        /// </summary>
+        /// <param name="bitmap">The existing <see cref="WriteableBitmap"/> to update.</param>
+        /// <param name="bgraPixelData">The bitmap (in raw BGRA pre-multiplied alpha pixels).</param>
+        public static void UpdateBitmapFromPixelData(
+            WriteableBitmap bitmap,
+            ArrayList<byte> bgraPixelData)
+        {
+            using (var frameBuffer = bitmap.Lock())
+            {
+                Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length);
+            }
+
+            return;
+        }
     }
 }

+ 15 - 18
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml

@@ -8,7 +8,6 @@
                     x:CompileBindings="True">
 
   <pc:ContrastBrushConverter x:Key="ContrastBrushConverter" />
-  <pc:ThirdComponentConverter x:Key="ThirdComponentConverter" />
   <converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
   <converters:ColorToHexConverter x:Key="ColorToHexConverter" />
   <converters:DoNothingForNullConverter x:Key="DoNothingForNullConverter" />
@@ -241,23 +240,21 @@
     <Setter Property="VerticalContentAlignment" Value="Center" />
     <Setter Property="Template">
       <ControlTemplate>
-        <Border
-            Name="PART_LayoutRoot"
-            Background="{TemplateBinding Background}"
-            BorderBrush="{TemplateBinding BorderBrush}"
-            BorderThickness="{TemplateBinding BorderThickness}"
-            CornerRadius="{TemplateBinding CornerRadius}"
-            Padding="{TemplateBinding Padding}">
+        <Border Name="PART_LayoutRoot"
+                Background="{TemplateBinding Background}"
+                BorderBrush="{TemplateBinding BorderBrush}"
+                BorderThickness="{TemplateBinding BorderThickness}"
+                CornerRadius="{TemplateBinding CornerRadius}"
+                Padding="{TemplateBinding Padding}">
           <Panel>
-            <ContentPresenter
-                Name="PART_ContentPresenter"
-                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
-                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
-                Content="{TemplateBinding Header}"
-                ContentTemplate="{TemplateBinding HeaderTemplate}"
-                FontFamily="{TemplateBinding FontFamily}"
-                FontSize="{TemplateBinding FontSize}"
-                FontWeight="{TemplateBinding FontWeight}" />
+            <ContentPresenter Name="PART_ContentPresenter"
+                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
+                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
+                              Content="{TemplateBinding Header}"
+                              ContentTemplate="{TemplateBinding HeaderTemplate}"
+                              FontFamily="{TemplateBinding FontFamily}"
+                              FontSize="{TemplateBinding FontSize}"
+                              FontWeight="{TemplateBinding FontWeight}" />
             <Border Name="PART_SelectedPipe"
                     Height="{DynamicResource TabItemPipeThickness}"
                     Margin="0,0,0,2"
@@ -370,7 +367,7 @@
                                         IsSaturationValueMaxForced="False"
                                         Orientation="Vertical"
                                         ColorModel="Hsva"
-                                        ColorComponent="{Binding Components, ElementName=ColorSpectrum, Converter={StaticResource ThirdComponentConverter}}"
+                                        ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}"
                                         HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}"
                                         HorizontalAlignment="Center"
                                         VerticalAlignment="Stretch"

+ 15 - 18
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml

@@ -8,7 +8,6 @@
                     x:CompileBindings="True">
 
   <pc:ContrastBrushConverter x:Key="ContrastBrushConverter" />
-  <pc:ThirdComponentConverter x:Key="ThirdComponentConverter" />
   <converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
   <converters:ColorToHexConverter x:Key="ColorToHexConverter" />
   <converters:DoNothingForNullConverter x:Key="DoNothingForNullConverter" />
@@ -215,23 +214,21 @@
     <Setter Property="VerticalContentAlignment" Value="Center" />
     <Setter Property="Template">
       <ControlTemplate>
-        <Border
-            Name="PART_LayoutRoot"
-            Background="{TemplateBinding Background}"
-            BorderBrush="{TemplateBinding BorderBrush}"
-            BorderThickness="{TemplateBinding BorderThickness}"
-            CornerRadius="{TemplateBinding CornerRadius}"
-            Padding="{TemplateBinding Padding}">
+        <Border Name="PART_LayoutRoot"
+                Background="{TemplateBinding Background}"
+                BorderBrush="{TemplateBinding BorderBrush}"
+                BorderThickness="{TemplateBinding BorderThickness}"
+                CornerRadius="{TemplateBinding CornerRadius}"
+                Padding="{TemplateBinding Padding}">
           <Panel>
-            <ContentPresenter
-                Name="PART_ContentPresenter"
-                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
-                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
-                Content="{TemplateBinding Header}"
-                ContentTemplate="{TemplateBinding HeaderTemplate}"
-                FontFamily="{TemplateBinding FontFamily}"
-                FontSize="{TemplateBinding FontSize}"
-                FontWeight="{TemplateBinding FontWeight}" />
+            <ContentPresenter Name="PART_ContentPresenter"
+                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
+                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
+                              Content="{TemplateBinding Header}"
+                              ContentTemplate="{TemplateBinding HeaderTemplate}"
+                              FontFamily="{TemplateBinding FontFamily}"
+                              FontSize="{TemplateBinding FontSize}"
+                              FontWeight="{TemplateBinding FontWeight}" />
             <Border Name="PART_SelectedPipe"
                     Height="2"
                     Margin="0,0,0,2"
@@ -332,7 +329,7 @@
                                         IsSaturationValueMaxForced="False"
                                         Orientation="Vertical"
                                         ColorModel="Hsva"
-                                        ColorComponent="{Binding Components, ElementName=ColorSpectrum, Converter={StaticResource ThirdComponentConverter}}"
+                                        ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}"
                                         HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}"
                                         HorizontalAlignment="Center"
                                         VerticalAlignment="Stretch"

+ 2 - 2
src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs

@@ -28,8 +28,8 @@ namespace Avalonia.Controls
         {
             if (targetType != null && targetType.IsNullableType())
             {
-                String strValue = value as String;
-                if (strValue == String.Empty)
+                var strValue = value as string;
+                if (string.IsNullOrEmpty(strValue))
                 {
                     return null;
                 }

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

@@ -210,9 +210,9 @@ namespace Avalonia.Controls
         {
             var moduleInitializers = from assembly in AppDomain.CurrentDomain.GetAssemblies()
                                      from attribute in assembly.GetCustomAttributes<ExportAvaloniaModuleAttribute>()
-                                     where attribute.ForWindowingSubsystem == ""
+                                     where string.IsNullOrEmpty(attribute.ForWindowingSubsystem)
                                       || attribute.ForWindowingSubsystem == WindowingSubsystemName
-                                     where attribute.ForRenderingSubsystem == ""
+                                     where string.IsNullOrEmpty(attribute.ForRenderingSubsystem)
                                       || attribute.ForRenderingSubsystem == RenderingSubsystemName
                                      group attribute by attribute.Name into exports
                                      select (from export in exports

+ 1 - 1
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@@ -99,7 +99,7 @@ namespace Avalonia.Controls
             get => _clockIdentifier;
             set
             {
-                if (!(string.IsNullOrEmpty(value) || value == "" || value == "12HourClock" || value == "24HourClock"))
+                if (!(string.IsNullOrEmpty(value) || value == "12HourClock" || value == "24HourClock"))
                     throw new ArgumentException("Invalid ClockIdentifier");
                 SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value);
                 SetGrid();

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

@@ -366,7 +366,7 @@ namespace Avalonia.Controls
 
             string id = (string)value;
 
-            if (id != string.Empty)
+            if (!string.IsNullOrEmpty(id))
             {
                 int i = -1;
                 while (++i < id.Length)

+ 1 - 1
src/Avalonia.Controls/Flyouts/FlyoutBase.cs

@@ -597,7 +597,7 @@ namespace Avalonia.Controls.Primitives
             for (int i = presenter.Classes.Count - 1; i >= 0; i--)
             {
                 if (!classes.Contains(presenter.Classes[i]) &&
-                    !presenter.Classes[i].Contains(":"))
+                    !presenter.Classes[i].Contains(':'))
                 {
                     presenter.Classes.RemoveAt(i);
                 }

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

@@ -1151,8 +1151,8 @@ namespace Avalonia.Controls
             if (PIndex >= 0)
             {
                 //stringToTest contains a "P" between 2 "'", it's considered as text, not percent
-                var isText = stringToTest.Substring(0, PIndex).Contains("'")
-                             && stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains("'");
+                var isText = stringToTest.Substring(0, PIndex).Contains('\'')
+                             && stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains('\'');
 
                 return !isText;
             }

+ 1 - 1
src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs

@@ -72,7 +72,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport
                 while (true)
                 {
                     line = await ReadLineAsync();
-                    if (line == "")
+                    if (string.IsNullOrEmpty(line))
                         break;
                     sp = line.Split(new[] {':'}, 2);
                     headers[sp[0]] = sp[1].TrimStart();

+ 1 - 2
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml

@@ -3,8 +3,7 @@
         xmlns:views="clr-namespace:Avalonia.Diagnostics.Views"
         xmlns:diag="clr-namespace:Avalonia.Diagnostics"
         Title="Avalonia DevTools"
-        x:Class="Avalonia.Diagnostics.Views.MainWindow"
-        Theme="{StaticResource {x:Type Window}}">
+        x:Class="Avalonia.Diagnostics.Views.MainWindow">
   <Window.DataTemplates>
     <diag:ViewLocator/>
   </Window.DataTemplates>

+ 5 - 0
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@@ -28,6 +28,11 @@ namespace Avalonia.Diagnostics.Views
         {
             InitializeComponent();
 
+            // Apply the SimpleTheme.Window theme; this must be done after the XAML is parsed as
+            // the theme is included in the MainWindow's XAML.
+            if (Theme is null && this.FindResource(typeof(Window)) is ControlTheme windowTheme)
+                Theme = windowTheme;
+
             _keySubscription = InputManager.Instance?.Process
                 .OfType<RawKeyEventArgs>()
                 .Where(x => x.Type == RawKeyEventType.KeyDown)

+ 0 - 3
src/Avalonia.MicroCom/Avalonia.MicroCom.csproj

@@ -3,9 +3,6 @@
       <TargetFramework>netstandard2.0</TargetFramework>
       <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
-  <PropertyGroup>
-    <_AvaloniaPatchComInterop>true</_AvaloniaPatchComInterop>
-  </PropertyGroup>
   <ItemGroup>
     <ProjectReference Include="..\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj">
       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>

+ 7 - 7
src/Avalonia.Remote.Protocol/MetsysBson.cs

@@ -1364,13 +1364,13 @@ namespace Metsys.Bson
             var optionsString = ReadName();
 
             var options = RegexOptions.None;
-            if (optionsString.Contains("e")) options = options | RegexOptions.ECMAScript;
-            if (optionsString.Contains("i")) options = options | RegexOptions.IgnoreCase;
-            if (optionsString.Contains("l")) options = options | RegexOptions.CultureInvariant;
-            if (optionsString.Contains("m")) options = options | RegexOptions.Multiline;
-            if (optionsString.Contains("s")) options = options | RegexOptions.Singleline;
-            if (optionsString.Contains("w")) options = options | RegexOptions.IgnorePatternWhitespace;
-            if (optionsString.Contains("x")) options = options | RegexOptions.ExplicitCapture;
+            if (optionsString.Contains('e')) options = options | RegexOptions.ECMAScript;
+            if (optionsString.Contains('i')) options = options | RegexOptions.IgnoreCase;
+            if (optionsString.Contains('l')) options = options | RegexOptions.CultureInvariant;
+            if (optionsString.Contains('m')) options = options | RegexOptions.Multiline;
+            if (optionsString.Contains('s')) options = options | RegexOptions.Singleline;
+            if (optionsString.Contains('w')) options = options | RegexOptions.IgnorePatternWhitespace;
+            if (optionsString.Contains('x')) options = options | RegexOptions.ExplicitCapture;
 
             return new Regex(pattern, options);
         }

+ 3 - 3
src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml

@@ -83,14 +83,14 @@
     <Style Selector="^:maximized /template/ Path#RestoreButtonPath">
       <Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" />
     </Style>
-    <Style Selector="^:fullscreen /template/ Path#PART_FullScreenButtonPath">
+    <Style Selector="^:fullscreen /template/ Path#FullScreenButtonPath">
       <Setter Property="IsVisible" Value="True" />
       <Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" />
     </Style>
-    <Style Selector="^:fullscreen /template/ Panel#PART_RestoreButton">
+    <Style Selector="^:fullscreen /template/ Button#PART_RestoreButton">
       <Setter Property="IsVisible" Value="False" />
     </Style>
-    <Style Selector="^:fullscreen /template/ Panel#PART_MinimiseButton">
+    <Style Selector="^:fullscreen /template/ Button#PART_MinimiseButton">
       <Setter Property="IsVisible" Value="False" />
     </Style>
   </ControlTheme>

+ 3 - 3
src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml

@@ -92,14 +92,14 @@
     <Style Selector="^:maximized /template/ Path#RestoreButtonPath">
       <Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" />
     </Style>
-    <Style Selector="^:fullscreen /template/ Path#PART_FullScreenButtonPath">
+    <Style Selector="^:fullscreen /template/ Path#FullScreenButtonPath">
       <Setter Property="IsVisible" Value="True" />
       <Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" />
     </Style>
-    <Style Selector="^:fullscreen /template/ Panel#PART_RestoreButton">
+    <Style Selector="^:fullscreen /template/ Button#PART_RestoreButton">
       <Setter Property="IsVisible" Value="False" />
     </Style>
-    <Style Selector="^:fullscreen /template/ Panel#PART_MinimiseButton">
+    <Style Selector="^:fullscreen /template/ Button#PART_MinimiseButton">
       <Setter Property="IsVisible" Value="False" />
     </Style>
   </ControlTheme>

+ 1 - 1
src/Avalonia.X11/X11Structs.cs

@@ -661,7 +661,7 @@ namespace Avalonia.X11 {
 			Type type = ev.GetType ();
 			FieldInfo [] fields = type.GetFields (System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Instance);
 			for (int i = 0; i < fields.Length; i++) {
-				if (result != string.Empty) {
+				if (!string.IsNullOrEmpty(result)) {
 					result += ", ";
 				}
 				object value = fields [i].GetValue (ev);

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj

@@ -5,7 +5,7 @@
     <IsPackable>true</IsPackable>
     <PackageId>Avalonia.Markup.Xaml.Loader</PackageId>
     <DefineConstants>$(DefineConstants);XAMLX_INTERNAL</DefineConstants>
-    <LangVersion>10</LangVersion>
+    <LangVersion>11</LangVersion>
   </PropertyGroup>
   <!--Disable Net Perf. analyzer for submodule to avoid commit issue -->
   <PropertyGroup>

+ 2 - 2
src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Markup.Xaml.Converters
         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
         {
             var valueStr = (string)value;
-            if (!valueStr.Contains(":"))
+            if (!valueStr.Contains(':'))
             {
                 // shorthand seconds format (ie. "0.25")
                 var secs = double.Parse(valueStr, CultureInfo.InvariantCulture);
@@ -25,4 +25,4 @@ namespace Avalonia.Markup.Xaml.Converters
             return base.ConvertFrom(context, culture, value);
         }
     }
-}
+}

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@@ -56,7 +56,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
                 // We need to implement compile-time merging of resource dictionaries and this
                 // hack can be removed.
                 if (previousWasControlTheme &&
-                    parent is ResourceDictionary hack &&
+                    parent is IResourceProvider hack &&
                     hack.Owner?.GetType().FullName == "Avalonia.Diagnostics.Views.MainWindow" &&
                     hack.Owner.TryGetResource(ResourceKey, out value))
                 {

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

@@ -2,6 +2,7 @@
   <PropertyGroup>
     <TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
     <RootNamespace>Avalonia</RootNamespace>
+    <LangVersion>11</LangVersion>
   </PropertyGroup>
   <ItemGroup>
     <None Remove="Markup\Parsers\Nodes\ExpressionGrammer" />

+ 2 - 2
src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs

@@ -168,7 +168,7 @@ namespace Avalonia.Markup.Parsers
             }
         }
 
-        private static State ParseAttachedProperty(ref CharacterReader r, List<INode> nodes)
+        private static State ParseAttachedProperty(scoped ref CharacterReader r, List<INode> nodes)
         {
             var (ns, owner) = ParseTypeName(ref r);
 
@@ -318,7 +318,7 @@ namespace Avalonia.Markup.Parsers
             return State.AfterMember;
         }
 
-        private static TypeName ParseTypeName(ref CharacterReader r)
+        private static TypeName ParseTypeName(scoped ref CharacterReader r)
         {
             ReadOnlySpan<char> ns, typeName;
             ns = ReadOnlySpan<char>.Empty;

+ 9 - 9
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@@ -376,28 +376,28 @@ namespace Avalonia.Markup.Parsers
 
             if (r.Peek == 'o')
             {
-                var constArg = r.TakeUntil(')').ToString().Trim();
-                if (constArg.Equals("odd", StringComparison.Ordinal))
+                var constArg = r.TakeUntil(')').Trim();
+                if (constArg.SequenceEqual("odd".AsSpan()))
                 {
                     step = 2;
                     offset = 1;
                 }
                 else
                 {
-                    throw new ExpressionParseException(r.Position, $"Expected nth-child(odd). Actual '{constArg}'.");
+                    throw new ExpressionParseException(r.Position, $"Expected nth-child(odd). Actual '{constArg.ToString()}'.");
                 }
             }
             else if (r.Peek == 'e')
             {
-                var constArg = r.TakeUntil(')').ToString().Trim();
-                if (constArg.Equals("even", StringComparison.Ordinal))
+                var constArg = r.TakeUntil(')').Trim();
+                if (constArg.SequenceEqual("even".AsSpan()))
                 {
                     step = 2;
                     offset = 0;
                 }
                 else
                 {
-                    throw new ExpressionParseException(r.Position, $"Expected nth-child(even). Actual '{constArg}'.");
+                    throw new ExpressionParseException(r.Position, $"Expected nth-child(even). Actual '{constArg.ToString()}'.");
                 }
             }
             else
@@ -405,7 +405,7 @@ namespace Avalonia.Markup.Parsers
                 r.SkipWhitespace();
 
                 var stepOrOffset = 0;
-                var stepOrOffsetStr = r.TakeWhile(c => char.IsDigit(c) || c == '-' || c == '+').ToString();
+                var stepOrOffsetStr = r.TakeWhile(c => char.IsDigit(c) || c == '-' || c == '+');
                 if (stepOrOffsetStr.Length == 0
                     || (stepOrOffsetStr.Length == 1
                     && stepOrOffsetStr[0] == '+'))
@@ -417,7 +417,7 @@ namespace Avalonia.Markup.Parsers
                 {
                     stepOrOffset = -1;
                 }
-                else if (!int.TryParse(stepOrOffsetStr.ToString(), out stepOrOffset))
+                else if (!stepOrOffsetStr.TryParseInt(out stepOrOffset))
                 {
                     throw new ExpressionParseException(r.Position, "Couldn't parse nth-child step or offset value. Integer was expected.");
                 }
@@ -462,7 +462,7 @@ namespace Avalonia.Markup.Parsers
                         r.SkipWhitespace();
 
                         if (sign != 0
-                            && !int.TryParse(r.TakeUntil(')').ToString(), out offset))
+                            && !r.TakeUntil(')').TryParseInt(out offset))
                         {
                             throw new ExpressionParseException(r.Position, "Couldn't parse nth-child offset value. Integer was expected.");
                         }

+ 1 - 1
tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs

@@ -80,7 +80,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
                             var remaining = remainingChars.Where(x => x != "" && x != "×").Select(x => Convert.ToInt32(x, 16)).ToArray();
 
                             codepoints.AddRange(remaining);
-                        }                     
+                        }
 
                         var data = new GraphemeBreakData
                         {

+ 0 - 1
tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

@@ -1,6 +1,5 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <OutputType>Exe</OutputType>
     <TargetFramework>net6.0</TargetFramework>
     <OutputType>Exe</OutputType>
     <IsPackable>false</IsPackable>

+ 2 - 2
tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj

@@ -8,9 +8,9 @@
     <Compile Update="**\*.xaml.cs">
       <DependentUpon>%(Filename)</DependentUpon>
     </Compile>
-    <EmbeddedResource Include="**\*.xaml">
+    <AvaloniaResource Include="**\*.xaml">
       <SubType>Designer</SubType>
-    </EmbeddedResource>
+    </AvaloniaResource>
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 1
tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs

@@ -109,7 +109,7 @@ namespace Avalonia.IntegrationTests.Appium
 
             using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Owned, WindowStartupLocation.Manual))
             {
-                mainWindow.Click();
+                mainWindow.SendClick();
                 var secondaryWindowIndex = GetWindowOrder("SecondaryWindow");
                 Assert.Equal(1, secondaryWindowIndex);
             }

+ 6 - 0
tests/Directory.Build.props

@@ -0,0 +1,6 @@
+<Project>
+  <Import Project="..\Directory.Build.props" />
+  <PropertyGroup>
+    <EnableNETAnalyzers>false</EnableNETAnalyzers>
+  </PropertyGroup>
+</Project>