Browse Source

Merge branch 'master' into master

pull/9209/head
Robin Krom 4 years ago
committed by GitHub
parent
commit
0ee3413f5c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .editorconfig
  2. 15
      azure-pipelines-integrationtests.yml
  3. 12
      azure-pipelines.yml
  4. 4
      global.json
  5. 3
      packages/Avalonia/AvaloniaBuildTasks.targets
  6. 1
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  7. 8
      samples/ControlCatalog/Pages/ColorPickerPage.xaml
  8. 16
      samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs
  9. 2
      src/Avalonia.Base/AvaloniaProperty.cs
  10. 2
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  11. 12
      src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs
  12. 49
      src/Avalonia.Base/Media/Color.cs
  13. 30
      src/Avalonia.Base/Media/HslColor.cs
  14. 30
      src/Avalonia.Base/Media/HsvColor.cs
  15. 2
      src/Avalonia.Base/Media/UnicodeRange.cs
  16. 2
      src/Avalonia.Base/Utilities/IdentifierParser.cs
  17. 49
      src/Avalonia.Base/Utilities/SpanHelpers.cs
  18. 5
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  19. 6
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  20. 3
      src/Avalonia.Build.Tasks/Program.cs
  21. 72
      src/Avalonia.Build.Tasks/SpanCompat.cs
  22. 32
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
  23. 17
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  24. 33
      src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs
  25. 53
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
  26. 24
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs
  27. 117
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
  28. 53
      src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs
  29. 71
      src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs
  30. 34
      src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
  31. 33
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  32. 33
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
  33. 4
      src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs
  34. 4
      src/Avalonia.Controls/AppBuilderBase.cs
  35. 2
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  36. 2
      src/Avalonia.Controls/DefinitionBase.cs
  37. 2
      src/Avalonia.Controls/Documents/IInlineHost.cs
  38. 42
      src/Avalonia.Controls/Documents/InlineRun.cs
  39. 48
      src/Avalonia.Controls/Documents/InlineUIContainer.cs
  40. 2
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  41. 4
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  42. 88
      src/Avalonia.Controls/RichTextBlock.cs
  43. 2
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs
  44. 3
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
  45. 5
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  46. 4
      src/Avalonia.MicroCom/Avalonia.MicroCom.csproj
  47. 4
      src/Avalonia.MicroCom/CallbackBase.cs
  48. 9
      src/Avalonia.MicroCom/IMicroComExceptionCallback.cs
  49. 9
      src/Avalonia.MicroCom/IMicroComShadowContainer.cs
  50. 8
      src/Avalonia.MicroCom/IUnknown.cs
  51. 17
      src/Avalonia.MicroCom/LocalInterop.cs
  52. 110
      src/Avalonia.MicroCom/MicroComProxyBase.cs
  53. 143
      src/Avalonia.MicroCom/MicroComRuntime.cs
  54. 177
      src/Avalonia.MicroCom/MicroComShadow.cs
  55. 47
      src/Avalonia.MicroCom/MicroComVtblBase.cs
  56. 1
      src/Avalonia.Native/Avalonia.Native.csproj
  57. 1
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  58. 1
      src/Avalonia.Native/CallbackBase.cs
  59. 1
      src/Avalonia.Native/NativeControlHostImpl.cs
  60. 14
      src/Avalonia.Remote.Protocol/MetsysBson.cs
  61. 6
      src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml
  62. 6
      src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml
  63. 2
      src/Avalonia.X11/X11Structs.cs
  64. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
  65. 4
      src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs
  66. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  67. 1
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  68. 4
      src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs
  69. 18
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  70. 1
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  71. 7
      src/Windows/Avalonia.Win32/ClipboardImpl.cs
  72. 5
      src/Windows/Avalonia.Win32/DragSource.cs
  73. 1
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  74. 3
      src/Windows/Avalonia.Win32/OleContext.cs
  75. 2
      src/Windows/Avalonia.Win32/OleDataObject.cs
  76. 1
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  77. 1
      src/Windows/Avalonia.Win32/Win32StorageProvider.cs
  78. 1
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs
  79. 1
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
  80. 1
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs
  81. 1
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs
  82. 1
      src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs
  83. 1
      src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs
  84. 2
      tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs
  85. 1
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  86. 36
      tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs
  87. 4
      tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj
  88. 2
      tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
  89. 6
      tests/Directory.Build.props

8
.editorconfig

@ -139,10 +139,14 @@ dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomme
# CA1802: Use literals where appropriate
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
# CA1821: Remove empty finalizers
dotnet_diagnostic.CA1821.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
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'

12
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
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",

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
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'">

8
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
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()

2
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.");
}

2
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
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

49
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);
}
}

30
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);
}
}

30
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);
}
}

2
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;

2
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
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
}
}
}

5
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; }

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))

3
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
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));
}

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
{

17
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))

33
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;
}
}

53
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
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);
}
}
}

117
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;

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
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;
}
}
}

34
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;
}
}
}

33
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"

33
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"

4
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;
}

4
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

2
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();

2
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)

2
src/Avalonia.Controls/Documents/IInlineHost.cs

@ -4,8 +4,6 @@ namespace Avalonia.Controls.Documents
{
internal interface IInlineHost : ILogical
{
void AddVisualChild(IControl child);
void Invalidate();
}
}

42
src/Avalonia.Controls/Documents/InlineRun.cs

@ -0,0 +1,42 @@
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
namespace Avalonia.Controls.Documents
{
internal class EmbeddedControlRun : DrawableTextRun
{
public EmbeddedControlRun(IControl control, TextRunProperties properties)
{
Control = control;
Properties = properties;
}
public IControl Control { get; }
public override TextRunProperties? Properties { get; }
public override Size Size => Control.DesiredSize;
public override double Baseline
{
get
{
double baseline = Size.Height;
double baselineOffsetValue = Control.GetValue<double>(TextBlock.BaselineOffsetProperty);
if (!MathUtilities.IsZero(baselineOffsetValue))
{
baseline = baselineOffsetValue;
}
return -baseline;
}
}
public override void Draw(DrawingContext drawingContext, Point origin)
{
//noop
}
}
}

48
src/Avalonia.Controls/Documents/InlineUIContainer.cs

@ -3,7 +3,6 @@ using System.Text;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Metadata;
using Avalonia.Utilities;
namespace Avalonia.Controls.Documents
{
@ -59,56 +58,11 @@ namespace Avalonia.Controls.Documents
internal override void BuildTextRun(IList<TextRun> textRuns)
{
if(InlineHost == null)
{
return;
}
((ISetLogicalParent)Child).SetParent(InlineHost);
InlineHost.AddVisualChild(Child);
textRuns.Add(new InlineRun(Child, CreateTextRunProperties()));
textRuns.Add(new EmbeddedControlRun(Child, CreateTextRunProperties()));
}
internal override void AppendText(StringBuilder stringBuilder)
{
}
private class InlineRun : DrawableTextRun
{
public InlineRun(IControl control, TextRunProperties properties)
{
Control = control;
Properties = properties;
}
public IControl Control { get; }
public override TextRunProperties? Properties { get; }
public override Size Size => Control.DesiredSize;
public override double Baseline
{
get
{
double baseline = Size.Height;
double baselineOffsetValue = Control.GetValue<double>(TextBlock.BaselineOffsetProperty);
if (!MathUtilities.IsZero(baselineOffsetValue))
{
baseline = baselineOffsetValue;
}
return -baseline;
}
}
public override void Draw(DrawingContext drawingContext, Point origin)
{
//noop
}
}
}
}

2
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);
}

4
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;
}

88
src/Avalonia.Controls/RichTextBlock.cs

@ -61,6 +61,7 @@ namespace Avalonia.Controls
private int _selectionStart;
private int _selectionEnd;
private int _wordSelectionStart = -1;
private IReadOnlyList<TextRun>? _textRuns;
static RichTextBlock()
{
@ -277,8 +278,8 @@ namespace Avalonia.Controls
protected override void SetText(string? text)
{
var oldValue = GetText();
AddText(text);
AddText(text);
RaisePropertyChanged(TextProperty, oldValue, text);
}
@ -301,18 +302,9 @@ namespace Avalonia.Controls
ITextSource textSource;
if (HasComplexContent)
if (_textRuns != null)
{
var inlines = Inlines!;
var textRuns = new List<TextRun>();
foreach (var inline in inlines)
{
inline.BuildTextRun(textRuns);
}
textSource = new InlinesTextSource(textRuns);
textSource = new InlinesTextSource(_textRuns);
}
else
{
@ -546,27 +538,73 @@ namespace Avalonia.Controls
protected override Size MeasureOverride(Size availableSize)
{
foreach (var child in VisualChildren)
if(_textRuns != null)
{
if (child is Control control)
LogicalChildren.Clear();
VisualChildren.Clear();
_textRuns = null;
}
if (Inlines != null && Inlines.Count > 0)
{
var inlines = Inlines;
var textRuns = new List<TextRun>();
foreach (var inline in inlines)
{
inline.BuildTextRun(textRuns);
}
foreach (var textRun in textRuns)
{
control.Measure(Size.Infinity);
if (textRun is EmbeddedControlRun controlRun &&
controlRun.Control is Control control)
{
LogicalChildren.Add(control);
VisualChildren.Add(control);
control.Measure(Size.Infinity);
}
}
_textRuns = textRuns;
}
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (var child in VisualChildren)
if (HasComplexContent)
{
if (child is Control control)
var currentY = 0.0;
foreach (var textLine in TextLayout.TextLines)
{
control.Arrange(new Rect(control.DesiredSize));
var currentX = textLine.Start;
foreach (var run in textLine.TextRuns)
{
if (run is DrawableTextRun drawable)
{
if (drawable is EmbeddedControlRun controlRun
&& controlRun.Control is Control control)
{
control.Arrange(new Rect(new Point(currentX, currentY), control.DesiredSize));
}
currentX += drawable.Size.Width;
}
}
currentY += textLine.Height;
}
}
return base.ArrangeOverride(finalSize);
}
@ -618,14 +656,6 @@ namespace Avalonia.Controls
}
}
void IInlineHost.AddVisualChild(IControl child)
{
if (child.VisualParent == null)
{
VisualChildren.Add(child);
}
}
void IInlineHost.Invalidate()
{
InvalidateTextLayout();

2
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();

3
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
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)

4
src/Avalonia.MicroCom/Avalonia.MicroCom.csproj

@ -3,10 +3,8 @@
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<_AvaloniaPatchComInterop>true</_AvaloniaPatchComInterop>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MicroCom.Runtime" Version="0.10.5" />
<ProjectReference Include="..\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<ExcludeAssets>all</ExcludeAssets>

4
src/Avalonia.MicroCom/CallbackBase.cs

@ -1,4 +1,6 @@
namespace Avalonia.MicroCom
using MicroCom.Runtime;
namespace Avalonia.MicroCom
{
public abstract class CallbackBase : IUnknown, IMicroComShadowContainer
{

9
src/Avalonia.MicroCom/IMicroComExceptionCallback.cs

@ -1,9 +0,0 @@
using System;
namespace Avalonia.MicroCom
{
public interface IMicroComExceptionCallback
{
void RaiseException(Exception e);
}
}

9
src/Avalonia.MicroCom/IMicroComShadowContainer.cs

@ -1,9 +0,0 @@
namespace Avalonia.MicroCom
{
public interface IMicroComShadowContainer
{
MicroComShadow Shadow { get; set; }
void OnReferencedFromNative();
void OnUnreferencedFromNative();
}
}

8
src/Avalonia.MicroCom/IUnknown.cs

@ -1,8 +0,0 @@
using System;
namespace Avalonia.MicroCom
{
public interface IUnknown : IDisposable
{
}
}

17
src/Avalonia.MicroCom/LocalInterop.cs

@ -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;
}
}
}

110
src/Avalonia.MicroCom/MicroComProxyBase.cs

@ -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<T>() 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);
}
}
}

143
src/Avalonia.MicroCom/MicroComRuntime.cs

@ -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<Type, IntPtr> _vtables = new ConcurrentDictionary<Type, IntPtr>();
private static ConcurrentDictionary<Type, Func<IntPtr, bool, object>> _factories =
new ConcurrentDictionary<Type, Func<IntPtr, bool, object>>();
private static ConcurrentDictionary<Type, Guid> _guids = new ConcurrentDictionary<Type, Guid>();
private static ConcurrentDictionary<Guid, Type> _guidsToTypes = new ConcurrentDictionary<Guid, Type>();
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<IntPtr, bool, object> proxyFactory)
{
_factories[t] = proxyFactory;
_guids[t] = guid;
_guidsToTypes[guid] = t;
}
public static Guid GetGuidFor(Type type) => _guids[type];
public static T CreateProxyFor<T>(void* pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle);
public static T CreateProxyFor<T>(IntPtr pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), pObject, ownsHandle);
public static T CreateProxyOrNullFor<T>(void* pObject, bool ownsHandle) where T : class =>
pObject == null ? null : (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle);
public static T CreateProxyOrNullFor<T>(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<T>(this T obj, bool owned = false) where T : IUnknown
=> new IntPtr(GetNativePointer(obj, owned));
public static void* GetNativePointer<T>(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<T>(this T iface) where T : IUnknown
{
var proxy = (MicroComProxyBase)(object)iface;
var ownedPointer = GetNativePointer(iface, true);
return CreateProxyFor<T>(ownedPointer, true);
}
public static T QueryInterface<T>(this IUnknown unknown) where T : IUnknown
{
var proxy = (MicroComProxyBase)unknown;
return proxy.QueryInterface<T>();
}
public static void UnsafeAddRef(this IUnknown unknown)
{
((MicroComProxyBase)unknown).AddRef();
}
public static void UnsafeRelease(this IUnknown unknown)
{
((MicroComProxyBase)unknown).Release();
}
}
}

177
src/Avalonia.MicroCom/MicroComShadow.cs

@ -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<Type, IntPtr> _shadows = new Dictionary<Type, IntPtr>();
private readonly Dictionary<IntPtr, Type> _backShadows = new Dictionary<IntPtr, Type>();
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<Ccw>());
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<IntPtr> toRemove = null;
foreach (var kv in _backShadows)
{
var ccw = (Ccw*)kv.Key;
if (ccw->RefCount == 0)
{
toRemove ??= new List<IntPtr>();
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;
}
}

47
src/Avalonia.MicroCom/MicroComVtblBase.cs

@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Avalonia.MicroCom
{
public unsafe class MicroComVtblBase
{
private List<IntPtr> _methods = new List<IntPtr>();
[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);
}
}

1
src/Avalonia.Native/Avalonia.Native.csproj

@ -6,7 +6,6 @@
<IsPackable Condition="'$([MSBuild]::IsOSPlatform(OSX))' == 'True'">true</IsPackable>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<MicroComGeneratorRuntimeNamespace>Avalonia.MicroCom</MicroComGeneratorRuntimeNamespace>
</PropertyGroup>
<ItemGroup Condition="'$(PackAvaloniaNative)' == 'true'">

1
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
{

1
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
{

1
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
{

14
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);
}

6
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>

6
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>

2
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);

2
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>

4
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);
}
}
}
}

2
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
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" />

4
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;

18
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
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -3,7 +3,6 @@
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>Avalonia.Win32</PackageId>
<MicroComGeneratorRuntimeNamespace>Avalonia.MicroCom</MicroComGeneratorRuntimeNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />

7
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<Win32Com.IDataObject>(wrapper);
var ptr = wrapper.GetNativeIntPtr<Win32Com.IDataObject>();
var hr = UnmanagedMethods.OleSetClipboard(ptr);
if (hr == 0)
@ -107,7 +108,7 @@ namespace Avalonia.Win32
if (hr == 0)
{
using var proxy = MicroCom.MicroComRuntime.CreateProxyFor<Win32Com.IDataObject>(dataObject, true);
using var proxy = MicroComRuntime.CreateProxyFor<Win32Com.IDataObject>(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<Win32Com.IDataObject>(dataObject, true);
using var proxy = MicroComRuntime.CreateProxyFor<Win32Com.IDataObject>(dataObject, true);
using var wrapper = new OleDataObject(proxy);
var rv = wrapper.Get(format);
return rv;

5
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<Win32Com.IDataObject>(dataObject);
var srcPtr = MicroCom.MicroComRuntime.GetNativeIntPtr<Win32Com.IDropSource>(src);
var objPtr = MicroComRuntime.GetNativeIntPtr<Win32Com.IDataObject>(dataObject);
var srcPtr = MicroComRuntime.GetNativeIntPtr<Win32Com.IDropSource>(src);
UnmanagedMethods.DoDragDrop(objPtr, srcPtr, (int)allowed, out var finalEffect);

1
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

3
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;
}

2
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

1
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

1
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
{

1
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
{

1
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
{

1
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
{

1
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
{

1
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
{

1
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
{

2
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
{

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>

36
tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs

@ -1,4 +1,6 @@
using Avalonia.Controls.Documents;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Media;
using Avalonia.UnitTests;
using Xunit;
@ -92,5 +94,39 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(target, run.Parent);
}
}
[Fact]
public void InlineUIContainer_Child_Schould_Be_Arranged()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = new RichTextBlock();
var button = new Button { Content = "12345678" };
button.Template = new FuncControlTemplate<Button>((parent, scope) =>
new TextBlock
{
Name = "PART_ContentPresenter",
[!TextBlock.TextProperty] = parent[!ContentControl.ContentProperty],
}.RegisterInNameScope(scope)
);
target.Inlines!.Add("123456");
target.Inlines.Add(new InlineUIContainer(button));
target.Inlines.Add("123456");
target.Measure(Size.Infinity);
Assert.True(button.IsMeasureValid);
Assert.Equal(80, button.DesiredSize.Width);
target.Arrange(new Rect(new Size(200, 50)));
Assert.True(button.IsArrangeValid);
Assert.Equal(60, button.Bounds.Left);
}
}
}
}

4
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>

2
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
tests/Directory.Build.props

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