From 94afe971d9af03b8f5f7c36888b376c4476631dd Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 14 Oct 2022 15:13:12 +0200 Subject: [PATCH 01/15] feat: Enable Rule CA1820 --- .editorconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.editorconfig b/.editorconfig index 337760636b..09f0d3e6ac 100644 --- a/.editorconfig +++ b/.editorconfig @@ -141,6 +141,8 @@ dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomme dotnet_diagnostic.CA1802.severity = warning # CA1825: Avoid zero-length array allocations dotnet_diagnostic.CA1825.severity = warning +# CA1820: Test for empty strings using string length +dotnet_diagnostic.CA1820.severity = warning # Wrapping preferences csharp_wrap_before_ternary_opsigns = false From 36cfc43a596c2181278b1a86b00fb4761dca0fc8 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 14 Oct 2022 15:14:35 +0200 Subject: [PATCH 02/15] feat: Address CA1820 Rule --- src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs | 4 ++-- src/Avalonia.Controls/AppBuilderBase.cs | 4 ++-- src/Avalonia.Controls/DateTimePickers/TimePicker.cs | 2 +- src/Avalonia.Controls/DefinitionBase.cs | 2 +- .../Remote/HtmlTransport/SimpleWebSocketHttpServer.cs | 2 +- src/Avalonia.X11/X11Structs.cs | 2 +- .../Media/TextFormatting/GraphemeBreakTestDataGenerator.cs | 6 +++--- tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/TextBoxTests.cs | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs b/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs index 82670bf989..6144679b60 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs +++ b/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; } diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 6b7101cd49..1dfad7dcc5 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/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() - 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 diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index a709e4fb49..c3baa6f17f 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -129,7 +129,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(); diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index eb09ff397a..64a02ccb46 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/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) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs index 9a872df960..6bfae536c9 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs +++ b/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(); diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index 23abd31b2c..3f0a6aeb67 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/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); diff --git a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs index f6616d74a2..9b2cb481e0 100644 --- a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs +++ b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs @@ -68,7 +68,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting var graphemeChars = elements[0].Replace(" × ", " ").Split(' '); - var codepoints = graphemeChars.Where(x => x != "" && x != "×") + var codepoints = graphemeChars.Where(x => !string.IsNullOrEmpty(x) && x != "×") .Select(x => Convert.ToInt32(x, 16)).ToList(); var grapheme = codepoints.ToArray(); @@ -77,10 +77,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { var remainingChars = elements[1].Replace(" × ", " ").Split(' '); - var remaining = remainingChars.Where(x => x != "" && x != "×").Select(x => Convert.ToInt32(x, 16)).ToArray(); + var remaining = remainingChars.Where(x => !string.IsNullOrEmpty(x) && x != "×").Select(x => Convert.ToInt32(x, 16)).ToArray(); codepoints.AddRange(remaining); - } + } var data = new GraphemeBreakData { diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index c8bd289e54..122176fbe0 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -99,7 +99,7 @@ namespace Avalonia.Controls.UnitTests textbox.Text = String.Empty; Dispatcher.UIThread.RunJobs(); - Assert.True(control.SearchText == String.Empty); + Assert.True(string.IsNullOrEmpty(control.SearchText)); Assert.False(control.IsDropDownOpen); Assert.True(closeEvent); }); diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs index d1fa522206..a2d0842028 100644 --- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs @@ -559,7 +559,7 @@ namespace Avalonia.Controls.UnitTests Text = "0123456789" }; - Assert.True(target.SelectedText == ""); + Assert.True(string.IsNullOrEmpty(target.SelectedText)); target.SelectionStart = 2; target.SelectionEnd = 4; diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 23a330c96f..f2982046e5 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -473,7 +473,7 @@ namespace Avalonia.Controls.UnitTests target.ApplyTemplate(); - Assert.True(target.SelectedText == ""); + Assert.True(string.IsNullOrEmpty(target.SelectedText)); target.SelectionStart = 2; target.SelectionEnd = 4; From ee0c1b47781fd0ea48ec174c00552735452e1844 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 17 Oct 2022 14:19:24 +0000 Subject: [PATCH 03/15] Use span directly when available --- src/Avalonia.Base/Media/Color.cs | 5 +- src/Avalonia.Base/Utilities/SpanHelpers.cs | 31 ++++++++++ src/Avalonia.Build.Tasks/SpanCompat.cs | 61 +++++++++++++++++++ .../Markup/Parsers/SelectorGrammar.cs | 18 +++--- 4 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 src/Avalonia.Base/Utilities/SpanHelpers.cs diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index cb90404f6d..aee048cc99 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/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.TryParseFromHexToUInt(out var parsed)) { return false; } diff --git a/src/Avalonia.Base/Utilities/SpanHelpers.cs b/src/Avalonia.Base/Utilities/SpanHelpers.cs new file mode 100644 index 0000000000..e4fab5ac7f --- /dev/null +++ b/src/Avalonia.Base/Utilities/SpanHelpers.cs @@ -0,0 +1,31 @@ +using System; +using System.Globalization; +using System.Runtime.CompilerServices; + +namespace Avalonia.Utilities +{ + public static class SpanHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseFromHexToUInt(this ReadOnlySpan span, out uint value) + { +#if NETSTANDARD2_0 + return uint.TryParse(span.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture, + out value); +#else + return uint.TryParse(span, NumberStyles.HexNumber, CultureInfo.InvariantCulture, + out value); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseToInt(this ReadOnlySpan span, out int value) + { +#if NETSTANDARD2_0 + return int.TryParse(span.ToString(), out value); +#else + return int.TryParse(span, out value); +#endif + } + } +} diff --git a/src/Avalonia.Build.Tasks/SpanCompat.cs b/src/Avalonia.Build.Tasks/SpanCompat.cs index 25f8d0175a..6da1fbd07c 100644 --- a/src/Avalonia.Build.Tasks/SpanCompat.cs +++ b/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(string s) => new ReadOnlySpan(s); + public ReadOnlySpan(string s) : this(s, 0, s.Length) { @@ -63,8 +68,64 @@ namespace System return Slice(start); } + public ReadOnlySpan TrimEnd() + { + int end = Length - 1; + for (; end >= 0; end--) + { + if (!char.IsWhiteSpace(this[end])) + { + break; + } + } + return Slice(0, end + 1); + } + + public ReadOnlySpan Trim() + { + return TrimStart().TrimEnd(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryParseFromHexToUInt(out uint value) + { + return uint.TryParse(ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture, + out value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryParseToInt(out int value) + { + return int.TryParse(ToString(), out value); + } + public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length); + internal int IndexOf(string v, StringComparison ordinal, int start = 0) + { + if(Length == 0 || string.IsNullOrEmpty(v)) + { + 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(char[] arr) => new ReadOnlySpan(new string(arr)); } diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index 16856e674d..a5e11cd233 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/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.TryParseToInt(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(')').TryParseToInt(out offset)) { throw new ExpressionParseException(r.Position, "Couldn't parse nth-child offset value. Integer was expected."); } From 868e5a5488e3e0d4a6954990fd61df5de08cbc2d Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 17 Oct 2022 14:45:37 +0000 Subject: [PATCH 04/15] switch to using helper in the color classes --- src/Avalonia.Base/Media/Color.cs | 44 ++++++++-------------- src/Avalonia.Base/Media/HslColor.cs | 30 ++++++--------- src/Avalonia.Base/Media/HsvColor.cs | 30 ++++++--------- src/Avalonia.Base/Utilities/SpanHelpers.cs | 24 ++++++++++++ src/Avalonia.Build.Tasks/SpanCompat.cs | 18 ++++++++- 5 files changed, 80 insertions(+), 66 deletions(-) diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index aee048cc99..14ef357393 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -381,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; @@ -391,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; @@ -402,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 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).TryParseNumberToDouble( out double percentage); outByte = (byte)Math.Round((percentage / 100.0) * 255.0); @@ -420,37 +417,28 @@ namespace Avalonia.Media } else { - return byte.TryParse( - inString, - NumberStyles.Number, - CultureInfo.InvariantCulture, + return inString.TryParseNumberToByte( 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 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).TryParseNumberToDouble( + out double percentage); outDouble = percentage / 100.0; return result; } else { - return double.TryParse( - inString, - NumberStyles.Number, - CultureInfo.InvariantCulture, + return inString.TryParseNumberToDouble( out outDouble); } } diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs index 485bb1db16..5a9e5cd54b 100644 --- a/src/Avalonia.Base/Media/HslColor.cs +++ b/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().TryParseNumberToDouble(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().TryParseNumberToDouble(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 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).TryParseNumberToDouble( + out double percentage); outDouble = percentage / 100.0; return result; } else { - return double.TryParse( - inString, - NumberStyles.Number, - CultureInfo.InvariantCulture, + return inString.TryParseNumberToDouble( out outDouble); } } diff --git a/src/Avalonia.Base/Media/HsvColor.cs b/src/Avalonia.Base/Media/HsvColor.cs index 512e57ae07..30a6c7ef4e 100644 --- a/src/Avalonia.Base/Media/HsvColor.cs +++ b/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().TryParseNumberToDouble(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().TryParseNumberToDouble(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 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).TryParseNumberToDouble( + out double percentage); outDouble = percentage / 100.0; return result; } else { - return double.TryParse( - inString, - NumberStyles.Number, - CultureInfo.InvariantCulture, + return inString.TryParseNumberToDouble( out outDouble); } } diff --git a/src/Avalonia.Base/Utilities/SpanHelpers.cs b/src/Avalonia.Base/Utilities/SpanHelpers.cs index e4fab5ac7f..6b779dfd87 100644 --- a/src/Avalonia.Base/Utilities/SpanHelpers.cs +++ b/src/Avalonia.Base/Utilities/SpanHelpers.cs @@ -25,6 +25,30 @@ namespace Avalonia.Utilities return int.TryParse(span.ToString(), out value); #else return int.TryParse(span, out value); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseNumberToDouble(this ReadOnlySpan span, out double value) + { +#if NETSTANDARD2_0 + return double.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, + out value); +#else + return double.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, + out value); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseNumberToByte(this ReadOnlySpan span, out byte value) + { +#if NETSTANDARD2_0 + return byte.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, + out value); +#else + return byte.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, + out value); #endif } } diff --git a/src/Avalonia.Build.Tasks/SpanCompat.cs b/src/Avalonia.Build.Tasks/SpanCompat.cs index 6da1fbd07c..af78178b5e 100644 --- a/src/Avalonia.Build.Tasks/SpanCompat.cs +++ b/src/Avalonia.Build.Tasks/SpanCompat.cs @@ -99,11 +99,25 @@ namespace System return int.TryParse(ToString(), out value); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryParseNumberToDouble(out double value) + { + return double.TryParse(ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, + out value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryParseNumberToByte(out byte value) + { + return byte.TryParse(ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, + out value); + } + public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length); - internal int IndexOf(string v, StringComparison ordinal, int start = 0) + internal int IndexOf(ReadOnlySpan v, StringComparison ordinal, int start = 0) { - if(Length == 0 || string.IsNullOrEmpty(v)) + if(Length == 0 || v.IsEmpty) { return -1; } From 39bd2f074aefbc2efcb5f52e2fb4149493240995 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 18 Oct 2022 07:58:46 +0000 Subject: [PATCH 05/15] add parameters for number styles and provider to double parse --- src/Avalonia.Base/Media/Color.cs | 10 +++---- src/Avalonia.Base/Media/HslColor.cs | 8 +++--- src/Avalonia.Base/Media/HsvColor.cs | 8 +++--- src/Avalonia.Base/Utilities/SpanHelpers.cs | 26 +++++++------------ src/Avalonia.Build.Tasks/SpanCompat.cs | 17 +++++------- .../Markup/Parsers/SelectorGrammar.cs | 4 +-- 6 files changed, 32 insertions(+), 41 deletions(-) diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index 14ef357393..5470a735b3 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -296,7 +296,7 @@ namespace Avalonia.Media return false; } - if (!input.TryParseFromHexToUInt(out var parsed)) + if (!input.TryParseUInt(NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed)) { return false; } @@ -409,7 +409,7 @@ namespace Avalonia.Media if (percentIndex >= 0) { - var result = inString.Slice(0, percentIndex).TryParseNumberToDouble( + var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double percentage); outByte = (byte)Math.Round((percentage / 100.0) * 255.0); @@ -417,7 +417,7 @@ namespace Avalonia.Media } else { - return inString.TryParseNumberToByte( + return inString.TryParseByte(NumberStyles.Number, CultureInfo.InvariantCulture, out outByte); } } @@ -430,7 +430,7 @@ namespace Avalonia.Media if (percentIndex >= 0) { - var result = inString.Slice(0, percentIndex).TryParseNumberToDouble( + var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double percentage); outDouble = percentage / 100.0; @@ -438,7 +438,7 @@ namespace Avalonia.Media } else { - return inString.TryParseNumberToDouble( + return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out outDouble); } } diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs index 5a9e5cd54b..425a3138c3 100644 --- a/src/Avalonia.Base/Media/HslColor.cs +++ b/src/Avalonia.Base/Media/HslColor.cs @@ -302,7 +302,7 @@ namespace Avalonia.Media if (components.Length == 3) // HSL { - if (components[0].AsSpan().TryParseNumberToDouble(out double hue) && + 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)) { @@ -312,7 +312,7 @@ namespace Avalonia.Media } else if (components.Length == 4) // HSLA { - if (components[0].AsSpan().TryParseNumberToDouble(out double hue) && + 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)) @@ -330,7 +330,7 @@ namespace Avalonia.Media if (percentIndex >= 0) { - var result = inString.Slice(0, percentIndex).TryParseNumberToDouble( + var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double percentage); outDouble = percentage / 100.0; @@ -338,7 +338,7 @@ namespace Avalonia.Media } else { - return inString.TryParseNumberToDouble( + return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out outDouble); } } diff --git a/src/Avalonia.Base/Media/HsvColor.cs b/src/Avalonia.Base/Media/HsvColor.cs index 30a6c7ef4e..9f95b31518 100644 --- a/src/Avalonia.Base/Media/HsvColor.cs +++ b/src/Avalonia.Base/Media/HsvColor.cs @@ -302,7 +302,7 @@ namespace Avalonia.Media if (components.Length == 3) // HSV { - if (components[0].AsSpan().TryParseNumberToDouble(out double hue) && + 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)) { @@ -312,7 +312,7 @@ namespace Avalonia.Media } else if (components.Length == 4) // HSVA { - if (components[0].AsSpan().TryParseNumberToDouble(out double hue) && + 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)) @@ -330,7 +330,7 @@ namespace Avalonia.Media if (percentIndex >= 0) { - var result = inString.Slice(0, percentIndex).TryParseNumberToDouble( + var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double percentage); outDouble = percentage / 100.0; @@ -338,7 +338,7 @@ namespace Avalonia.Media } else { - return inString.TryParseNumberToDouble( + return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out outDouble); } } diff --git a/src/Avalonia.Base/Utilities/SpanHelpers.cs b/src/Avalonia.Base/Utilities/SpanHelpers.cs index 6b779dfd87..9a5dce9798 100644 --- a/src/Avalonia.Base/Utilities/SpanHelpers.cs +++ b/src/Avalonia.Base/Utilities/SpanHelpers.cs @@ -7,19 +7,17 @@ namespace Avalonia.Utilities public static class SpanHelpers { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryParseFromHexToUInt(this ReadOnlySpan span, out uint value) + public static bool TryParseUInt(this ReadOnlySpan span, NumberStyles style, IFormatProvider provider, out uint value) { #if NETSTANDARD2_0 - return uint.TryParse(span.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture, - out value); + return uint.TryParse(span.ToString(), style, provider, out value); #else - return uint.TryParse(span, NumberStyles.HexNumber, CultureInfo.InvariantCulture, - out value); + return uint.TryParse(span, style, provider, out value); #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryParseToInt(this ReadOnlySpan span, out int value) + public static bool TryParseInt(this ReadOnlySpan span, out int value) { #if NETSTANDARD2_0 return int.TryParse(span.ToString(), out value); @@ -29,26 +27,22 @@ namespace Avalonia.Utilities } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryParseNumberToDouble(this ReadOnlySpan span, out double value) + public static bool TryParseDouble(this ReadOnlySpan span, NumberStyles style, IFormatProvider provider, out double value) { #if NETSTANDARD2_0 - return double.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, - out value); + return double.TryParse(span.ToString(), style, provider, out value); #else - return double.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, - out value); + return double.TryParse(span, style, provider, out value); #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryParseNumberToByte(this ReadOnlySpan span, out byte value) + public static bool TryParseByte(this ReadOnlySpan span, NumberStyles style, IFormatProvider provider, out byte value) { #if NETSTANDARD2_0 - return byte.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, - out value); + return byte.TryParse(span.ToString(), style, provider, out value); #else - return byte.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, - out value); + return byte.TryParse(span, style, provider, out value); #endif } } diff --git a/src/Avalonia.Build.Tasks/SpanCompat.cs b/src/Avalonia.Build.Tasks/SpanCompat.cs index af78178b5e..be59ff8b6c 100644 --- a/src/Avalonia.Build.Tasks/SpanCompat.cs +++ b/src/Avalonia.Build.Tasks/SpanCompat.cs @@ -87,30 +87,27 @@ namespace System } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryParseFromHexToUInt(out uint value) + public bool TryParseUInt(NumberStyles style, IFormatProvider provider, out uint value) { - return uint.TryParse(ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture, - out value); + return uint.TryParse(ToString(), style, provider, out value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryParseToInt(out int value) + public bool TryParseInt(out int value) { return int.TryParse(ToString(), out value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryParseNumberToDouble(out double value) + public bool TryParseDouble(NumberStyles style, IFormatProvider provider, out double value) { - return double.TryParse(ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, - out value); + return double.TryParse(ToString(), style, provider, out value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryParseNumberToByte(out byte value) + public bool TryParseByte(NumberStyles style, IFormatProvider provider, out byte value) { - return byte.TryParse(ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, - out value); + return byte.TryParse(ToString(), style, provider, out value); } public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length); diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index a5e11cd233..4d6d16a3ce 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -417,7 +417,7 @@ namespace Avalonia.Markup.Parsers { stepOrOffset = -1; } - else if (!stepOrOffsetStr.TryParseToInt(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 - && !r.TakeUntil(')').TryParseToInt(out offset)) + && !r.TakeUntil(')').TryParseInt(out offset)) { throw new ExpressionParseException(r.Position, "Couldn't parse nth-child offset value. Integer was expected."); } From a763255f3f5f1ac9df6c7d3efc3bcc197e533b33 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 18 Oct 2022 17:10:52 +0200 Subject: [PATCH 06/15] fix: revert test changes --- .../Media/TextFormatting/GraphemeBreakTestDataGenerator.cs | 4 ++-- tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/TextBoxTests.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs index 9b2cb481e0..029f8e236c 100644 --- a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs +++ b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs @@ -68,7 +68,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting var graphemeChars = elements[0].Replace(" × ", " ").Split(' '); - var codepoints = graphemeChars.Where(x => !string.IsNullOrEmpty(x) && x != "×") + var codepoints = graphemeChars.Where(x => x != "" && x != "×") .Select(x => Convert.ToInt32(x, 16)).ToList(); var grapheme = codepoints.ToArray(); @@ -77,7 +77,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { var remainingChars = elements[1].Replace(" × ", " ").Split(' '); - var remaining = remainingChars.Where(x => !string.IsNullOrEmpty(x) && x != "×").Select(x => Convert.ToInt32(x, 16)).ToArray(); + var remaining = remainingChars.Where(x => x != "" && x != "×").Select(x => Convert.ToInt32(x, 16)).ToArray(); codepoints.AddRange(remaining); } diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index 122176fbe0..c8bd289e54 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -99,7 +99,7 @@ namespace Avalonia.Controls.UnitTests textbox.Text = String.Empty; Dispatcher.UIThread.RunJobs(); - Assert.True(string.IsNullOrEmpty(control.SearchText)); + Assert.True(control.SearchText == String.Empty); Assert.False(control.IsDropDownOpen); Assert.True(closeEvent); }); diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs index a2d0842028..d1fa522206 100644 --- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs @@ -559,7 +559,7 @@ namespace Avalonia.Controls.UnitTests Text = "0123456789" }; - Assert.True(string.IsNullOrEmpty(target.SelectedText)); + Assert.True(target.SelectedText == ""); target.SelectionStart = 2; target.SelectionEnd = 4; diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index f2982046e5..23a330c96f 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -473,7 +473,7 @@ namespace Avalonia.Controls.UnitTests target.ApplyTemplate(); - Assert.True(string.IsNullOrEmpty(target.SelectedText)); + Assert.True(target.SelectedText == ""); target.SelectionStart = 2; target.SelectionEnd = 4; From cb0993b5fc5615402fa17aa5e3fd447a83277687 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 18 Oct 2022 17:19:10 +0200 Subject: [PATCH 07/15] fix: double declaration OutputType --- tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj index 3f4978f544..754a1d6a24 100644 --- a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj +++ b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj @@ -1,6 +1,5 @@  - Exe net6.0 Exe false From 48c91fbac7ebc1a1a96b599000ddc0eabb866232 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 18 Oct 2022 19:07:05 +0200 Subject: [PATCH 08/15] fix: disable NETAnalyzers for tests --- tests/Directory.Build.props | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/Directory.Build.props diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 0000000000..52af0f5ab2 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,6 @@ + + + + false + + From 48bd894e6059f9c2062e5a35ccc5afc134cdeb25 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 19 Oct 2022 18:00:25 +0600 Subject: [PATCH 09/15] Use MicroCom.Runtime from nuget --- .../Avalonia.MicroCom.csproj | 1 + src/Avalonia.MicroCom/CallbackBase.cs | 4 +- .../IMicroComExceptionCallback.cs | 9 - .../IMicroComShadowContainer.cs | 9 - src/Avalonia.MicroCom/IUnknown.cs | 8 - src/Avalonia.MicroCom/LocalInterop.cs | 17 -- src/Avalonia.MicroCom/MicroComProxyBase.cs | 110 ----------- src/Avalonia.MicroCom/MicroComRuntime.cs | 143 -------------- src/Avalonia.MicroCom/MicroComShadow.cs | 177 ------------------ src/Avalonia.MicroCom/MicroComVtblBase.cs | 47 ----- src/Avalonia.Native/Avalonia.Native.csproj | 1 - src/Avalonia.Native/AvaloniaNativePlatform.cs | 1 + src/Avalonia.Native/CallbackBase.cs | 1 + src/Avalonia.Native/NativeControlHostImpl.cs | 1 + .../Avalonia.Win32/Avalonia.Win32.csproj | 1 - src/Windows/Avalonia.Win32/ClipboardImpl.cs | 7 +- src/Windows/Avalonia.Win32/DragSource.cs | 5 +- .../Interop/UnmanagedMethods.cs | 1 + src/Windows/Avalonia.Win32/OleContext.cs | 3 +- src/Windows/Avalonia.Win32/OleDataObject.cs | 2 +- src/Windows/Avalonia.Win32/OleDropTarget.cs | 1 + .../Avalonia.Win32/Win32StorageProvider.cs | 1 + .../Composition/WinUICompositedWindow.cs | 1 + .../Composition/WinUICompositorConnection.cs | 1 + .../WinRT/Composition/WinUIEffectBase.cs | 1 + .../WinUiCompositedWindowSurface.cs | 1 + .../WinRT/NativeWinRTMethods.cs | 1 + .../Avalonia.Win32/WinRT/WinRTInspectable.cs | 1 + 28 files changed, 26 insertions(+), 530 deletions(-) delete mode 100644 src/Avalonia.MicroCom/IMicroComExceptionCallback.cs delete mode 100644 src/Avalonia.MicroCom/IMicroComShadowContainer.cs delete mode 100644 src/Avalonia.MicroCom/IUnknown.cs delete mode 100644 src/Avalonia.MicroCom/LocalInterop.cs delete mode 100644 src/Avalonia.MicroCom/MicroComProxyBase.cs delete mode 100644 src/Avalonia.MicroCom/MicroComRuntime.cs delete mode 100644 src/Avalonia.MicroCom/MicroComShadow.cs delete mode 100644 src/Avalonia.MicroCom/MicroComVtblBase.cs diff --git a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj index d7f39f6642..43ae612699 100644 --- a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj +++ b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj @@ -7,6 +7,7 @@ <_AvaloniaPatchComInterop>true + false all diff --git a/src/Avalonia.MicroCom/CallbackBase.cs b/src/Avalonia.MicroCom/CallbackBase.cs index 6783ebe3dc..49d589a5fc 100644 --- a/src/Avalonia.MicroCom/CallbackBase.cs +++ b/src/Avalonia.MicroCom/CallbackBase.cs @@ -1,4 +1,6 @@ -namespace Avalonia.MicroCom +using MicroCom.Runtime; + +namespace Avalonia.MicroCom { public abstract class CallbackBase : IUnknown, IMicroComShadowContainer { diff --git a/src/Avalonia.MicroCom/IMicroComExceptionCallback.cs b/src/Avalonia.MicroCom/IMicroComExceptionCallback.cs deleted file mode 100644 index 08f20339ec..0000000000 --- a/src/Avalonia.MicroCom/IMicroComExceptionCallback.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Avalonia.MicroCom -{ - public interface IMicroComExceptionCallback - { - void RaiseException(Exception e); - } -} diff --git a/src/Avalonia.MicroCom/IMicroComShadowContainer.cs b/src/Avalonia.MicroCom/IMicroComShadowContainer.cs deleted file mode 100644 index a33d3a9811..0000000000 --- a/src/Avalonia.MicroCom/IMicroComShadowContainer.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Avalonia.MicroCom -{ - public interface IMicroComShadowContainer - { - MicroComShadow Shadow { get; set; } - void OnReferencedFromNative(); - void OnUnreferencedFromNative(); - } -} diff --git a/src/Avalonia.MicroCom/IUnknown.cs b/src/Avalonia.MicroCom/IUnknown.cs deleted file mode 100644 index 0dc4106423..0000000000 --- a/src/Avalonia.MicroCom/IUnknown.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Avalonia.MicroCom -{ - public interface IUnknown : IDisposable - { - } -} diff --git a/src/Avalonia.MicroCom/LocalInterop.cs b/src/Avalonia.MicroCom/LocalInterop.cs deleted file mode 100644 index 785f4e03a5..0000000000 --- a/src/Avalonia.MicroCom/LocalInterop.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Avalonia.MicroCom -{ - unsafe class LocalInterop - { - public static unsafe void CalliStdCallvoid(void* thisObject, void* methodPtr) - { - throw null; - } - - public static unsafe int CalliStdCallint(void* thisObject, Guid* guid, IntPtr* ppv, void* methodPtr) - { - throw null; - } - } -} diff --git a/src/Avalonia.MicroCom/MicroComProxyBase.cs b/src/Avalonia.MicroCom/MicroComProxyBase.cs deleted file mode 100644 index fe8a2bf9cf..0000000000 --- a/src/Avalonia.MicroCom/MicroComProxyBase.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; -using System.Threading; - -namespace Avalonia.MicroCom -{ - public unsafe class MicroComProxyBase : CriticalFinalizerObject, IUnknown - { - private IntPtr _nativePointer; - private bool _ownsHandle; - private SynchronizationContext _synchronizationContext; - - public IntPtr NativePointer - { - get - { - if (_nativePointer == IntPtr.Zero) - throw new ObjectDisposedException(this.GetType().FullName); - return _nativePointer; - } - } - - public void*** PPV => (void***)NativePointer; - - public MicroComProxyBase(IntPtr nativePointer, bool ownsHandle) - { - _nativePointer = nativePointer; - _ownsHandle = ownsHandle; - _synchronizationContext = SynchronizationContext.Current; - if(!_ownsHandle) - GC.SuppressFinalize(this); - } - - protected virtual int VTableSize => 3; - - public void AddRef() - { - LocalInterop.CalliStdCallvoid(PPV, (*PPV)[1]); - } - - public void Release() - { - LocalInterop.CalliStdCallvoid(PPV, (*PPV)[2]); - } - - public int QueryInterface(Guid guid, out IntPtr ppv) - { - IntPtr r = default; - var rv = LocalInterop.CalliStdCallint(PPV, &guid, &r, (*PPV)[0]); - ppv = r; - return rv; - } - - public T QueryInterface() where T : IUnknown - { - var guid = MicroComRuntime.GetGuidFor(typeof(T)); - var rv = QueryInterface(guid, out var ppv); - if (rv == 0) - return (T)MicroComRuntime.CreateProxyFor(typeof(T), ppv, true); - throw new COMException("QueryInterface failed", rv); - } - - public bool IsDisposed => _nativePointer == IntPtr.Zero; - - protected virtual void Dispose(bool disposing) - { - if(_nativePointer == IntPtr.Zero) - return; - if (_ownsHandle) - { - Release(); - _ownsHandle = false; - } - _nativePointer = IntPtr.Zero; - GC.SuppressFinalize(this); - } - - public void Dispose() => Dispose(true); - - public bool OwnsHandle => _ownsHandle; - - public void EnsureOwned() - { - if (!_ownsHandle) - { - GC.ReRegisterForFinalize(true); - AddRef(); - _ownsHandle = true; - } - } - - private static readonly SendOrPostCallback _disposeDelegate = DisposeOnContext; - - private static void DisposeOnContext(object state) - { - (state as MicroComProxyBase)?.Dispose(false); - } - - ~MicroComProxyBase() - { - if(!_ownsHandle) - return; - if (_synchronizationContext == null) - Dispose(); - else - _synchronizationContext.Post(_disposeDelegate, this); - } - } -} diff --git a/src/Avalonia.MicroCom/MicroComRuntime.cs b/src/Avalonia.MicroCom/MicroComRuntime.cs deleted file mode 100644 index b9a56a69ba..0000000000 --- a/src/Avalonia.MicroCom/MicroComRuntime.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Runtime.InteropServices; - -namespace Avalonia.MicroCom -{ - public static unsafe class MicroComRuntime - { - private static ConcurrentDictionary _vtables = new ConcurrentDictionary(); - - private static ConcurrentDictionary> _factories = - new ConcurrentDictionary>(); - private static ConcurrentDictionary _guids = new ConcurrentDictionary(); - private static ConcurrentDictionary _guidsToTypes = new ConcurrentDictionary(); - - internal static readonly Guid ManagedObjectInterfaceGuid = Guid.Parse("cd7687c0-a9c2-4563-b08e-a399df50c633"); - - static MicroComRuntime() - { - Register(typeof(IUnknown), new Guid("00000000-0000-0000-C000-000000000046"), - (ppv, owns) => new MicroComProxyBase(ppv, owns)); - RegisterVTable(typeof(IUnknown), MicroComVtblBase.Vtable); - } - - public static void RegisterVTable(Type t, IntPtr vtable) - { - _vtables[t] = vtable; - } - - public static void Register(Type t, Guid guid, Func proxyFactory) - { - _factories[t] = proxyFactory; - _guids[t] = guid; - _guidsToTypes[guid] = t; - } - - public static Guid GetGuidFor(Type type) => _guids[type]; - - public static T CreateProxyFor(void* pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle); - public static T CreateProxyFor(IntPtr pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), pObject, ownsHandle); - - public static T CreateProxyOrNullFor(void* pObject, bool ownsHandle) where T : class => - pObject == null ? null : (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle); - - public static T CreateProxyOrNullFor(IntPtr pObject, bool ownsHandle) where T : class => - pObject == IntPtr.Zero ? null : (T)CreateProxyFor(typeof(T), pObject, ownsHandle); - - public static object CreateProxyFor(Type type, IntPtr pObject, bool ownsHandle) => _factories[type](pObject, ownsHandle); - - public static IntPtr GetNativeIntPtr(this T obj, bool owned = false) where T : IUnknown - => new IntPtr(GetNativePointer(obj, owned)); - public static void* GetNativePointer(T obj, bool owned = false) where T : IUnknown - { - if (obj == null) - return null; - if (obj is MicroComProxyBase proxy) - { - if(owned) - proxy.AddRef(); - return (void*)proxy.NativePointer; - } - - if (obj is IMicroComShadowContainer container) - { - container.Shadow ??= new MicroComShadow(container); - void* ptr = null; - var res = container.Shadow.GetOrCreateNativePointer(typeof(T), &ptr); - if (res != 0) - throw new COMException( - "Unable to create native callable wrapper for type " + typeof(T) + " for instance of type " + - obj.GetType(), - res); - if (owned) - container.Shadow.AddRef((Ccw*)ptr); - return ptr; - } - throw new ArgumentException("Unable to get a native pointer for " + obj); - } - - public static object GetObjectFromCcw(IntPtr ccw) - { - var ptr = (Ccw*)ccw; - var shadow = (MicroComShadow)GCHandle.FromIntPtr(ptr->GcShadowHandle).Target; - return shadow.Target; - } - - public static bool IsComWrapper(IUnknown obj) => obj is MicroComProxyBase; - - public static object TryUnwrapManagedObject(IUnknown obj) - { - if (obj is not MicroComProxyBase proxy) - return null; - if (proxy.QueryInterface(ManagedObjectInterfaceGuid, out _) != 0) - return null; - // Successful QueryInterface always increments ref counter - proxy.Release(); - return GetObjectFromCcw(proxy.NativePointer); - } - - public static bool TryGetTypeForGuid(Guid guid, out Type t) => _guidsToTypes.TryGetValue(guid, out t); - - public static bool GetVtableFor(Type type, out IntPtr ptr) => _vtables.TryGetValue(type, out ptr); - - public static void UnhandledException(object target, Exception e) - { - if (target is IMicroComExceptionCallback cb) - { - try - { - cb.RaiseException(e); - } - catch - { - // We've tried - } - } - - } - - public static T CloneReference(this T iface) where T : IUnknown - { - var proxy = (MicroComProxyBase)(object)iface; - var ownedPointer = GetNativePointer(iface, true); - return CreateProxyFor(ownedPointer, true); - } - - public static T QueryInterface(this IUnknown unknown) where T : IUnknown - { - var proxy = (MicroComProxyBase)unknown; - return proxy.QueryInterface(); - } - - public static void UnsafeAddRef(this IUnknown unknown) - { - ((MicroComProxyBase)unknown).AddRef(); - } - - public static void UnsafeRelease(this IUnknown unknown) - { - ((MicroComProxyBase)unknown).Release(); - } - } -} diff --git a/src/Avalonia.MicroCom/MicroComShadow.cs b/src/Avalonia.MicroCom/MicroComShadow.cs deleted file mode 100644 index 765ff3b9ad..0000000000 --- a/src/Avalonia.MicroCom/MicroComShadow.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Threading; - -namespace Avalonia.MicroCom -{ - public unsafe class MicroComShadow : IDisposable - { - private readonly object _lock = new object(); - private readonly Dictionary _shadows = new Dictionary(); - private readonly Dictionary _backShadows = new Dictionary(); - private GCHandle? _handle; - private volatile int _refCount; - internal IMicroComShadowContainer Target { get; } - internal MicroComShadow(IMicroComShadowContainer target) - { - Target = target; - Target.Shadow = this; - } - - internal int QueryInterface(Ccw* ccw, Guid* guid, void** ppv) - { - if (MicroComRuntime.TryGetTypeForGuid(*guid, out var type)) - return QueryInterface(type, ppv); - else if (*guid == MicroComRuntime.ManagedObjectInterfaceGuid) - { - ccw->RefCount++; - *ppv = ccw; - return 0; - } - else - return unchecked((int)0x80004002u); - } - - internal int QueryInterface(Type type, void** ppv) - { - if (!type.IsInstanceOfType(Target)) - return unchecked((int)0x80004002u); - - var rv = GetOrCreateNativePointer(type, ppv); - if (rv == 0) - AddRef((Ccw*)*ppv); - return rv; - } - - internal int GetOrCreateNativePointer(Type type, void** ppv) - { - if (!MicroComRuntime.GetVtableFor(type, out var vtable)) - return unchecked((int)0x80004002u); - lock (_lock) - { - - if (_shadows.TryGetValue(type, out var shadow)) - { - var targetCcw = (Ccw*)shadow; - AddRef(targetCcw); - *ppv = targetCcw; - return 0; - } - else - { - var intPtr = Marshal.AllocHGlobal(Marshal.SizeOf()); - var targetCcw = (Ccw*)intPtr; - *targetCcw = default; - targetCcw->RefCount = 0; - targetCcw->VTable = vtable; - if (_handle == null) - _handle = GCHandle.Alloc(this); - targetCcw->GcShadowHandle = GCHandle.ToIntPtr(_handle.Value); - _shadows[type] = intPtr; - _backShadows[intPtr] = type; - *ppv = targetCcw; - - return 0; - } - } - } - - internal int AddRef(Ccw* ccw) - { - if (Interlocked.Increment(ref _refCount) == 1) - { - try - { - Target.OnReferencedFromNative(); - } - catch (Exception e) - { - MicroComRuntime.UnhandledException(Target, e); - } - } - - return Interlocked.Increment(ref ccw->RefCount); - } - - internal int Release(Ccw* ccw) - { - Interlocked.Decrement(ref _refCount); - var cnt = Interlocked.Decrement(ref ccw->RefCount); - if (cnt == 0) - return FreeCcw(ccw); - - return cnt; - } - - int FreeCcw(Ccw* ccw) - { - lock (_lock) - { - // Shadow got resurrected by a call to QueryInterface from another thread - if (ccw->RefCount != 0) - return ccw->RefCount; - - var intPtr = new IntPtr(ccw); - var type = _backShadows[intPtr]; - _backShadows.Remove(intPtr); - _shadows.Remove(type); - Marshal.FreeHGlobal(intPtr); - if (_shadows.Count == 0) - { - _handle?.Free(); - _handle = null; - try - { - Target.OnUnreferencedFromNative(); - } - catch(Exception e) - { - MicroComRuntime.UnhandledException(Target, e); - } - } - } - - return 0; - } - - /* - Needs to be called to support the following scenario: - 1) Object created - 2) Object passed to native code, shadow is created, CCW is created - 3) Native side has never called AddRef - - In that case the GC handle to the shadow object is still alive - */ - - public void Dispose() - { - lock (_lock) - { - List toRemove = null; - foreach (var kv in _backShadows) - { - var ccw = (Ccw*)kv.Key; - if (ccw->RefCount == 0) - { - toRemove ??= new List(); - toRemove.Add(kv.Key); - } - } - - if(toRemove != null) - foreach (var intPtr in toRemove) - FreeCcw((Ccw*)intPtr); - } - } - } - - [StructLayout(LayoutKind.Sequential)] - struct Ccw - { - public IntPtr VTable; - public IntPtr GcShadowHandle; - public volatile int RefCount; - public MicroComShadow GetShadow() => (MicroComShadow)GCHandle.FromIntPtr(GcShadowHandle).Target; - } -} diff --git a/src/Avalonia.MicroCom/MicroComVtblBase.cs b/src/Avalonia.MicroCom/MicroComVtblBase.cs deleted file mode 100644 index 7092f8131a..0000000000 --- a/src/Avalonia.MicroCom/MicroComVtblBase.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; - -namespace Avalonia.MicroCom -{ - public unsafe class MicroComVtblBase - { - private List _methods = new List(); - [UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)] - private delegate int AddRefDelegate(Ccw* ccw); - - [UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)] - private delegate int QueryInterfaceDelegate(Ccw* ccw, Guid* guid, void** ppv); - - public static IntPtr Vtable { get; } = new MicroComVtblBase().CreateVTable(); - public MicroComVtblBase() - { - AddMethod((QueryInterfaceDelegate)QueryInterface); - AddMethod((AddRefDelegate)AddRef); - AddMethod((AddRefDelegate)Release); - } - - protected void AddMethod(void* f) - { - _methods.Add(new IntPtr(f)); - } - - protected void AddMethod(Delegate d) - { - GCHandle.Alloc(d); - _methods.Add(Marshal.GetFunctionPointerForDelegate(d)); - } - - protected unsafe IntPtr CreateVTable() - { - var ptr = (IntPtr*)Marshal.AllocHGlobal((IntPtr.Size + 1) * _methods.Count); - for (var c = 0; c < _methods.Count; c++) - ptr[c] = _methods[c]; - return new IntPtr(ptr); - } - - static int QueryInterface(Ccw* ccw, Guid* guid, void** ppv) => ccw->GetShadow().QueryInterface(ccw, guid, ppv); - static int AddRef(Ccw* ccw) => ccw->GetShadow().AddRef(ccw); - static int Release(Ccw* ccw) => ccw->GetShadow().Release(ccw); - } -} diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index 2001a2fcbc..4ceb1be340 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -6,7 +6,6 @@ true net6.0;netstandard2.0 true - Avalonia.MicroCom diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index b45fe5559b..532893884a 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -10,6 +10,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using JetBrains.Annotations; +using MicroCom.Runtime; namespace Avalonia.Native { diff --git a/src/Avalonia.Native/CallbackBase.cs b/src/Avalonia.Native/CallbackBase.cs index 56f9505cb4..2d875dbc0e 100644 --- a/src/Avalonia.Native/CallbackBase.cs +++ b/src/Avalonia.Native/CallbackBase.cs @@ -2,6 +2,7 @@ using System.Runtime.ExceptionServices; using Avalonia.MicroCom; using Avalonia.Platform; +using MicroCom.Runtime; namespace Avalonia.Native { diff --git a/src/Avalonia.Native/NativeControlHostImpl.cs b/src/Avalonia.Native/NativeControlHostImpl.cs index 2c9c1728d3..8a3488d95e 100644 --- a/src/Avalonia.Native/NativeControlHostImpl.cs +++ b/src/Avalonia.Native/NativeControlHostImpl.cs @@ -4,6 +4,7 @@ using Avalonia.MicroCom; using Avalonia.Native.Interop; using Avalonia.Platform; using Avalonia.VisualTree; +using MicroCom.Runtime; namespace Avalonia.Native { diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 55d02b5d31..2f0bfd86a5 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -3,7 +3,6 @@ net6.0;netstandard2.0 true Avalonia.Win32 - Avalonia.MicroCom diff --git a/src/Windows/Avalonia.Win32/ClipboardImpl.cs b/src/Windows/Avalonia.Win32/ClipboardImpl.cs index 7cf8b14bed..7e058cb052 100644 --- a/src/Windows/Avalonia.Win32/ClipboardImpl.cs +++ b/src/Windows/Avalonia.Win32/ClipboardImpl.cs @@ -7,6 +7,7 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Threading; using Avalonia.Win32.Interop; +using MicroCom.Runtime; namespace Avalonia.Win32 { @@ -83,7 +84,7 @@ namespace Avalonia.Win32 while (true) { - var ptr = MicroCom.MicroComRuntime.GetNativeIntPtr(wrapper); + var ptr = wrapper.GetNativeIntPtr(); var hr = UnmanagedMethods.OleSetClipboard(ptr); if (hr == 0) @@ -107,7 +108,7 @@ namespace Avalonia.Win32 if (hr == 0) { - using var proxy = MicroCom.MicroComRuntime.CreateProxyFor(dataObject, true); + using var proxy = MicroComRuntime.CreateProxyFor(dataObject, true); using var wrapper = new OleDataObject(proxy); var formats = wrapper.GetDataFormats().ToArray(); return formats; @@ -131,7 +132,7 @@ namespace Avalonia.Win32 if (hr == 0) { - using var proxy = MicroCom.MicroComRuntime.CreateProxyFor(dataObject, true); + using var proxy = MicroComRuntime.CreateProxyFor(dataObject, true); using var wrapper = new OleDataObject(proxy); var rv = wrapper.Get(format); return rv; diff --git a/src/Windows/Avalonia.Win32/DragSource.cs b/src/Windows/Avalonia.Win32/DragSource.cs index 1159c5bfc9..53d4345c5e 100644 --- a/src/Windows/Avalonia.Win32/DragSource.cs +++ b/src/Windows/Avalonia.Win32/DragSource.cs @@ -3,6 +3,7 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Threading; using Avalonia.Win32.Interop; +using MicroCom.Runtime; namespace Avalonia.Win32 { @@ -19,8 +20,8 @@ namespace Avalonia.Win32 using var src = new OleDragSource(); var allowed = OleDropTarget.ConvertDropEffect(allowedEffects); - var objPtr = MicroCom.MicroComRuntime.GetNativeIntPtr(dataObject); - var srcPtr = MicroCom.MicroComRuntime.GetNativeIntPtr(src); + var objPtr = MicroComRuntime.GetNativeIntPtr(dataObject); + var srcPtr = MicroComRuntime.GetNativeIntPtr(src); UnmanagedMethods.DoDragDrop(objPtr, srcPtr, (int)allowed, out var finalEffect); diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index ea01d5cbdf..d1627aa13a 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices.ComTypes; using System.Text; using Avalonia.MicroCom; +using MicroCom.Runtime; using Avalonia.Win32.Win32Com; // ReSharper disable InconsistentNaming diff --git a/src/Windows/Avalonia.Win32/OleContext.cs b/src/Windows/Avalonia.Win32/OleContext.cs index c025d06fe7..e41423a334 100644 --- a/src/Windows/Avalonia.Win32/OleContext.cs +++ b/src/Windows/Avalonia.Win32/OleContext.cs @@ -5,6 +5,7 @@ using Avalonia.Platform; using Avalonia.Threading; using Avalonia.Win32.Interop; using Avalonia.Win32.Win32Com; +using MicroCom.Runtime; namespace Avalonia.Win32 { @@ -47,7 +48,7 @@ namespace Avalonia.Win32 return false; } - var trgPtr = MicroCom.MicroComRuntime.GetNativeIntPtr(target); + var trgPtr = target.GetNativeIntPtr(); return UnmanagedMethods.RegisterDragDrop(hwnd.Handle, trgPtr) == UnmanagedMethods.HRESULT.S_OK; } diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index f7345b3ff7..90992d803f 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -10,7 +10,7 @@ using Avalonia.Input; using Avalonia.MicroCom; using Avalonia.Utilities; using Avalonia.Win32.Interop; - +using MicroCom.Runtime; using IDataObject = Avalonia.Input.IDataObject; namespace Avalonia.Win32 diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 3d0d35228c..1c22fd025d 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -5,6 +5,7 @@ using Avalonia.Input.Raw; using Avalonia.MicroCom; using Avalonia.Platform; using Avalonia.Win32.Interop; +using MicroCom.Runtime; using DropEffect = Avalonia.Win32.Win32Com.DropEffect; namespace Avalonia.Win32 diff --git a/src/Windows/Avalonia.Win32/Win32StorageProvider.cs b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs index bb1de56f2b..8cc0a380f9 100644 --- a/src/Windows/Avalonia.Win32/Win32StorageProvider.cs +++ b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs @@ -12,6 +12,7 @@ using Avalonia.Platform.Storage; using Avalonia.Platform.Storage.FileIO; using Avalonia.Win32.Interop; using Avalonia.Win32.Win32Com; +using MicroCom.Runtime; namespace Avalonia.Win32 { diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs index a09918a3a6..ef3de9fbe1 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs @@ -6,6 +6,7 @@ using Avalonia.MicroCom; using Avalonia.OpenGL; using Avalonia.OpenGL.Egl; using Avalonia.Win32.Interop; +using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition { diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs index 9a6bd9572a..8a41c00add 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs @@ -12,6 +12,7 @@ using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Egl; using Avalonia.Rendering; using Avalonia.Win32.Interop; +using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition { diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs index ea75a2f311..32f44842f8 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Runtime.InteropServices; using Avalonia.MicroCom; +using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition { diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs index 4ed882552b..fc04dcda26 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs @@ -6,6 +6,7 @@ using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; using Avalonia.Utilities; using Avalonia.Win32.Interop; +using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition { diff --git a/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs b/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs index 89cde01ff4..1794b022fe 100644 --- a/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs +++ b/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; using System.Threading; using Avalonia.MicroCom; using Avalonia.Win32.Interop; +using MicroCom.Runtime; namespace Avalonia.Win32.WinRT { diff --git a/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs b/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs index d2ec957b8e..1fde57fe38 100644 --- a/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs +++ b/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Avalonia.MicroCom; +using MicroCom.Runtime; namespace Avalonia.Win32.WinRT { From 89faa52240e1fb3b1f537d09bc5bcec050dfe983 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 19 Oct 2022 16:04:09 +0200 Subject: [PATCH 10/15] Use DragEventArgs for DragLeave event. This follows the WPF/UWP API and allows pointer information to be retrieved. --- src/Avalonia.Base/Input/DragDrop.cs | 2 +- src/Avalonia.Base/Input/DragDropDevice.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Input/DragDrop.cs b/src/Avalonia.Base/Input/DragDrop.cs index 723d577964..fe75f678e1 100644 --- a/src/Avalonia.Base/Input/DragDrop.cs +++ b/src/Avalonia.Base/Input/DragDrop.cs @@ -13,7 +13,7 @@ namespace Avalonia.Input /// /// Event which is raised, when a drag-and-drop operation leaves the element. /// - public static readonly RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); /// /// Event which is raised, when a drag-and-drop operation is updated while over the element. /// diff --git a/src/Avalonia.Base/Input/DragDropDevice.cs b/src/Avalonia.Base/Input/DragDropDevice.cs index 30a08eda17..3c91856dcd 100644 --- a/src/Avalonia.Base/Input/DragDropDevice.cs +++ b/src/Avalonia.Base/Input/DragDropDevice.cs @@ -54,7 +54,7 @@ namespace Avalonia.Input try { if (_lastTarget != null) - _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); + RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragLeaveEvent, effects, data, modifiers); return RaiseDragEvent(target, inputRoot, point, DragDrop.DragEnterEvent, effects, data, modifiers); } finally @@ -63,13 +63,13 @@ namespace Avalonia.Input } } - private void DragLeave(IInputElement inputRoot) + private void DragLeave(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers) { if (_lastTarget == null) return; try { - _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); + RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragLeaveEvent, effects, data, modifiers); } finally { @@ -106,7 +106,7 @@ namespace Avalonia.Input e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers); break; case RawDragEventType.DragLeave: - DragLeave(e.Root); + DragLeave(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers); break; case RawDragEventType.Drop: e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers); From a88fdd56b97ad1b2aa9907b03546b1da292dc8cf Mon Sep 17 00:00:00 2001 From: Benedikt Date: Wed, 19 Oct 2022 16:16:12 +0200 Subject: [PATCH 11/15] Adjust Microsoft.CodeAnalysis package version --- src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index b16ee84b4d..fe694b5730 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -19,7 +19,8 @@ - + + From 1cf220016f7bd63447b6447f31cd82256d0f548a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 20 Oct 2022 13:31:16 +0100 Subject: [PATCH 12/15] focus the root container on page load. --- src/Web/Avalonia.Web/AvaloniaView.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index 3a31679424..12d31258b5 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -136,6 +136,8 @@ namespace Avalonia.Web DomHelper.ObserveSize(host, null, OnSizeChanged); CanvasHelper.RequestAnimationFrame(_canvas, true); + + InputHelper.FocusElement(_containerElement); } private static RawPointerPoint ExtractRawPointerFromJSArgs(JSObject args) From 3f4034f0caf06dbdb36438666b391c0a8f62bdd6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 20 Oct 2022 21:41:15 +0100 Subject: [PATCH 13/15] add github sponsors --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index c5a719ce90..df070c35cf 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ +github: avaloniaui open_collective: avalonia From 5d8e4998c1208180395f8c2345c618c12e6bb817 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 20 Oct 2022 22:05:39 +0100 Subject: [PATCH 14/15] prevent browser touch gestures happening outside of avalonia --- src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts index 2257d56a92..783710bb3e 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts @@ -11,6 +11,7 @@ export class AvaloniaDOM { host.tabIndex = 0; host.oncontextmenu = function () { return false; }; host.style.overflow = "hidden"; + host.style.touchAction = "none"; // Rendering target canvas const canvas = document.createElement("canvas"); From 668296b397210192d72c0107135f0cc76f1bc1c2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 20 Oct 2022 22:21:49 +0100 Subject: [PATCH 15/15] minify js --- src/Web/Avalonia.Web/webapp/build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Web/Avalonia.Web/webapp/build.js b/src/Web/Avalonia.Web/webapp/build.js index 6b6df4c300..81f863cac7 100644 --- a/src/Web/Avalonia.Web/webapp/build.js +++ b/src/Web/Avalonia.Web/webapp/build.js @@ -5,7 +5,7 @@ require("esbuild").build({ ], outdir: "../wwwroot", bundle: true, - minify: false, + minify: true, format: "esm", target: "es2016", platform: "browser",