Browse Source

Merge branch 'master' into listbox_control_selection

pull/9144/head
Emmanuel Hansen 4 years ago
committed by GitHub
parent
commit
5a442c0ded
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .editorconfig
  2. 1
      .github/FUNDING.yml
  3. 15
      azure-pipelines-integrationtests.yml
  4. 12
      azure-pipelines.yml
  5. 2
      build/SharedVersion.props
  6. 4
      global.json
  7. 4
      native/Avalonia.Native/src/OSX/Screens.mm
  8. 3
      packages/Avalonia/AvaloniaBuildTasks.targets
  9. 1
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  10. 5
      samples/ControlCatalog.Web/ControlCatalog.Web.csproj
  11. 1
      samples/ControlCatalog.Web/Roots.xml
  12. 8
      samples/ControlCatalog/Pages/ColorPickerPage.xaml
  13. 16
      samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs
  14. 4
      samples/ControlCatalog/Pages/ScreenPage.cs
  15. 2
      src/Avalonia.Base/AvaloniaProperty.cs
  16. 2
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  17. 12
      src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs
  18. 2
      src/Avalonia.Base/Input/DragDrop.cs
  19. 8
      src/Avalonia.Base/Input/DragDropDevice.cs
  20. 49
      src/Avalonia.Base/Media/Color.cs
  21. 30
      src/Avalonia.Base/Media/HslColor.cs
  22. 30
      src/Avalonia.Base/Media/HsvColor.cs
  23. 4
      src/Avalonia.Base/Media/PathMarkupParser.cs
  24. 2
      src/Avalonia.Base/Media/UnicodeRange.cs
  25. 2
      src/Avalonia.Base/Utilities/IdentifierParser.cs
  26. 49
      src/Avalonia.Base/Utilities/SpanHelpers.cs
  27. 5
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  28. 6
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  29. 3
      src/Avalonia.Build.Tasks/Program.cs
  30. 72
      src/Avalonia.Build.Tasks/SpanCompat.cs
  31. 32
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
  32. 17
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  33. 33
      src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs
  34. 53
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
  35. 24
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs
  36. 117
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
  37. 53
      src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs
  38. 71
      src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs
  39. 34
      src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
  40. 33
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  41. 33
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
  42. 4
      src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs
  43. 4
      src/Avalonia.Controls/AppBuilderBase.cs
  44. 2
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  45. 2
      src/Avalonia.Controls/DefinitionBase.cs
  46. 2
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  47. 4
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  48. 6
      src/Avalonia.Controls/Platform/IScreenImpl.cs
  49. 57
      src/Avalonia.Controls/Platform/Screen.cs
  50. 23
      src/Avalonia.Controls/Screens.cs
  51. 1
      src/Avalonia.Controls/Viewbox.cs
  52. 2
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs
  53. 3
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  54. 3
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
  55. 5
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  56. 4
      src/Avalonia.MicroCom/Avalonia.MicroCom.csproj
  57. 4
      src/Avalonia.MicroCom/CallbackBase.cs
  58. 9
      src/Avalonia.MicroCom/IMicroComExceptionCallback.cs
  59. 9
      src/Avalonia.MicroCom/IMicroComShadowContainer.cs
  60. 8
      src/Avalonia.MicroCom/IUnknown.cs
  61. 17
      src/Avalonia.MicroCom/LocalInterop.cs
  62. 110
      src/Avalonia.MicroCom/MicroComProxyBase.cs
  63. 143
      src/Avalonia.MicroCom/MicroComRuntime.cs
  64. 177
      src/Avalonia.MicroCom/MicroComShadow.cs
  65. 47
      src/Avalonia.MicroCom/MicroComVtblBase.cs
  66. 1
      src/Avalonia.Native/Avalonia.Native.csproj
  67. 1
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  68. 1
      src/Avalonia.Native/CallbackBase.cs
  69. 1
      src/Avalonia.Native/NativeControlHostImpl.cs
  70. 4
      src/Avalonia.Native/ScreenImpl.cs
  71. 2
      src/Avalonia.Native/WindowImplBase.cs
  72. 4
      src/Avalonia.Native/avn.idl
  73. 14
      src/Avalonia.Remote.Protocol/MetsysBson.cs
  74. 6
      src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml
  75. 6
      src/Avalonia.Themes.Simple/Controls/CaptionButtons.xaml
  76. 28
      src/Avalonia.X11/X11Screens.cs
  77. 2
      src/Avalonia.X11/X11Structs.cs
  78. 8
      src/Avalonia.X11/X11Window.cs
  79. 1
      src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
  80. 4
      src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs
  81. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  82. 4
      src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs
  83. 18
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  84. 4
      src/Web/Avalonia.Web/Avalonia.Web.props
  85. 30
      src/Web/Avalonia.Web/Avalonia.Web.targets
  86. 14
      src/Web/Avalonia.Web/AvaloniaView.cs
  87. 2
      src/Web/Avalonia.Web/Interop/CanvasHelper.cs
  88. 2
      src/Web/Avalonia.Web/webapp/build.js
  89. 35
      src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts
  90. 6
      src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts
  91. 26
      src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts
  92. 1
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  93. 7
      src/Windows/Avalonia.Win32/ClipboardImpl.cs
  94. 5
      src/Windows/Avalonia.Win32/DragSource.cs
  95. 6
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  96. 3
      src/Windows/Avalonia.Win32/OleContext.cs
  97. 2
      src/Windows/Avalonia.Win32/OleDataObject.cs
  98. 1
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  99. 8
      src/Windows/Avalonia.Win32/TrayIconImpl.cs
  100. 1
      src/Windows/Avalonia.Win32/Win32StorageProvider.cs

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

1
.github/FUNDING.yml

@ -1 +1,2 @@
github: avaloniaui
open_collective: avalonia

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'

2
build/SharedVersion.props

@ -8,7 +8,7 @@
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>CS1591</NoWarn>
<LangVersion>latest</LangVersion>
<LangVersion>preview</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Icon.png</PackageIcon>
<PackageDescription>Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.</PackageDescription>

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

4
native/Avalonia.Native/src/OSX/Screens.mm

@ -41,9 +41,9 @@ public:
ret->WorkingArea.X = [screen visibleFrame].origin.x;
ret->WorkingArea.Y = ConvertPointY(ToAvnPoint([screen visibleFrame].origin)).Y - ret->WorkingArea.Height;
ret->PixelDensity = [screen backingScaleFactor];
ret->Scaling = [screen backingScaleFactor];
ret->Primary = index == 0;
ret->IsPrimary = index == 0;
return S_OK;
}

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

5
samples/ControlCatalog.Web/ControlCatalog.Web.csproj

@ -16,9 +16,8 @@
<TrimMode>full</TrimMode>
<WasmBuildNative>true</WasmBuildNative>
<InvariantGlobalization>true</InvariantGlobalization>
<WasmEnableSIMD>true</WasmEnableSIMD>
<EmccCompileOptimizationFlag>-O3</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag>-O3</EmccLinkOptimizationFlag>
<EmccCompileOptimizationFlag>-O2</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag>-O2</EmccLinkOptimizationFlag>
</PropertyGroup>
<ItemGroup>

1
samples/ControlCatalog.Web/Roots.xml

@ -3,4 +3,5 @@
<assembly fullname="ControlCatalog.Web" preserve="All" />
<assembly fullname="Avalonia.Themes.Fluent" preserve="All" />
<assembly fullname="Avalonia.Themes.Simple" preserve="All" />
<assembly fullname="Avalonia.Controls.ColorPicker" preserve="All" />
</linker>

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

4
samples/ControlCatalog/Pages/ScreenPage.cs

@ -62,10 +62,10 @@ namespace ControlCatalog.Pages
CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20));
formattedText = CreateFormattedText($"Scaling: {screen.PixelDensity * 100}%");
formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40));
formattedText = CreateFormattedText($"Primary: {screen.Primary}");
formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60));
formattedText =

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

2
src/Avalonia.Base/Input/DragDrop.cs

@ -13,7 +13,7 @@ namespace Avalonia.Input
/// <summary>
/// Event which is raised, when a drag-and-drop operation leaves the element.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> DragLeaveEvent = RoutedEvent.Register<RoutedEventArgs>("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop));
public static readonly RoutedEvent<DragEventArgs> DragLeaveEvent = RoutedEvent.Register<DragEventArgs>("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation is updated while over the element.
/// </summary>

8
src/Avalonia.Base/Input/DragDropDevice.cs

@ -54,7 +54,7 @@ namespace Avalonia.Input
try
{
if (_lastTarget != null)
_lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent));
RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragLeaveEvent, effects, data, modifiers);
return RaiseDragEvent(target, inputRoot, point, DragDrop.DragEnterEvent, effects, data, modifiers);
}
finally
@ -63,13 +63,13 @@ namespace Avalonia.Input
}
}
private void DragLeave(IInputElement inputRoot)
private void DragLeave(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers)
{
if (_lastTarget == null)
return;
try
{
_lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent));
RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragLeaveEvent, effects, data, modifiers);
}
finally
{
@ -106,7 +106,7 @@ namespace Avalonia.Input
e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break;
case RawDragEventType.DragLeave:
DragLeave(e.Root);
DragLeave(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break;
case RawDragEventType.Drop:
e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);

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

4
src/Avalonia.Base/Media/PathMarkupParser.cs

@ -188,7 +188,7 @@ namespace Avalonia.Media
_isOpen = true;
}
private void SetFillRule(ref ReadOnlySpan<char> span)
private void SetFillRule(scoped ref ReadOnlySpan<char> span)
{
ThrowIfDisposed();
@ -452,7 +452,7 @@ namespace Avalonia.Media
return !span.IsEmpty && (span[0] == ',' || span[0] == '-' || span[0] == '.' || char.IsDigit(span[0]));
}
private static bool ReadArgument(ref ReadOnlySpan<char> remaining, out ReadOnlySpan<char> argument)
private static bool ReadArgument(scoped ref ReadOnlySpan<char> remaining, out ReadOnlySpan<char> argument)
{
remaining = SkipWhitespace(remaining);
if (remaining.IsEmpty)

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

6
src/Avalonia.Controls/Platform/IScreenImpl.cs

@ -6,8 +6,14 @@ namespace Avalonia.Platform
[Unstable]
public interface IScreenImpl
{
/// <summary>
/// Gets the total number of screens available on the device.
/// </summary>
int ScreenCount { get; }
/// <summary>
/// Gets the list of all screens available on the device.
/// </summary>
IReadOnlyList<Screen> AllScreens { get; }
Screen? ScreenFromWindow(IWindowBaseImpl window);

57
src/Avalonia.Controls/Platform/Screen.cs

@ -1,21 +1,64 @@
namespace Avalonia.Platform
using System;
namespace Avalonia.Platform
{
/// <summary>
/// Represents a single display screen.
/// </summary>
public class Screen
{
public double PixelDensity { get; }
/// <summary>
/// Gets the scaling factor applied to the screen by the operating system.
/// </summary>
/// <remarks>
/// Multiply this value by 100 to get a percentage.
/// Both X and Y scaling factors are assumed uniform.
/// </remarks>
public double Scaling { get; }
/// <inheritdoc cref="Scaling"/>
[Obsolete("Use the Scaling property instead.")]
public double PixelDensity => Scaling;
/// <summary>
/// Gets the overall pixel-size of the screen.
/// </summary>
/// <remarks>
/// This generally is the raw pixel counts in both the X and Y direction.
/// </remarks>
public PixelRect Bounds { get; }
/// <summary>
/// Gets the actual working-area pixel-size of the screen.
/// </summary>
/// <remarks>
/// This area may be smaller than <see href="Bounds"/> to account for notches and
/// other block-out areas such as taskbars etc.
/// </remarks>
public PixelRect WorkingArea { get; }
public bool Primary { get; }
public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary)
/// <summary>
/// Gets a value indicating whether the screen is the primary one.
/// </summary>
public bool IsPrimary { get; }
/// <inheritdoc cref="IsPrimary"/>
[Obsolete("Use the IsPrimary property instead.")]
public bool Primary => IsPrimary;
/// <summary>
/// Initializes a new instance of the <see cref="Screen"/> class.
/// </summary>
/// <param name="scaling">The scaling factor applied to the screen by the operating system.</param>
/// <param name="bounds">The overall pixel-size of the screen.</param>
/// <param name="workingArea">The actual working-area pixel-size of the screen.</param>
/// <param name="isPrimary">Whether the screen is the primary one.</param>
public Screen(double scaling, PixelRect bounds, PixelRect workingArea, bool isPrimary)
{
this.PixelDensity = pixelDensity;
this.Scaling = scaling;
this.Bounds = bounds;
this.WorkingArea = workingArea;
this.Primary = primary;
this.IsPrimary = isPrimary;
}
}
}

23
src/Avalonia.Controls/Screens.cs

@ -8,14 +8,31 @@ using Avalonia.VisualTree;
namespace Avalonia.Controls
{
/// <summary>
/// Represents all screens available on a device.
/// </summary>
public class Screens
{
private readonly IScreenImpl _iScreenImpl;
/// <summary>
/// Gets the total number of screens available on the device.
/// </summary>
public int ScreenCount => _iScreenImpl?.ScreenCount ?? 0;
/// <summary>
/// Gets the list of all screens available on the device.
/// </summary>
public IReadOnlyList<Screen> All => _iScreenImpl?.AllScreens ?? Array.Empty<Screen>();
public Screen? Primary => All.FirstOrDefault(x => x.Primary);
/// <summary>
/// Gets the primary screen on the device.
/// </summary>
public Screen? Primary => All.FirstOrDefault(x => x.IsPrimary);
/// <summary>
/// Initializes a new instance of the <see cref="Screens"/> class.
/// </summary>
public Screens(IScreenImpl iScreenImpl)
{
_iScreenImpl = iScreenImpl;
@ -25,14 +42,14 @@ namespace Avalonia.Controls
{
return _iScreenImpl.ScreenFromRect(bounds);
}
public Screen? ScreenFromWindow(IWindowBaseImpl window)
{
return _iScreenImpl.ScreenFromWindow(window);
}
public Screen? ScreenFromPoint(PixelPoint point)
{
{
return _iScreenImpl.ScreenFromPoint(point);
}

1
src/Avalonia.Controls/Viewbox.cs

@ -42,6 +42,7 @@ namespace Avalonia.Controls
// can be applied independently of the Viewbox and Child transforms.
_containerVisual = new ViewboxContainer();
_containerVisual.RenderTransformOrigin = RelativePoint.TopLeft;
((ISetLogicalParent)_containerVisual).SetParent(this);
VisualChildren.Add(_containerVisual);
}

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/Avalonia.Diagnostics.csproj

@ -19,7 +19,8 @@
<ProjectReference Include="..\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="3.8.0" />
</ItemGroup>
<Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\Rx.props" />

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
{

4
src/Avalonia.Native/ScreenImpl.cs

@ -30,10 +30,10 @@ namespace Avalonia.Native
var screen = _native.GetScreen(i);
result[i] = new Screen(
screen.PixelDensity,
screen.Scaling,
screen.Bounds.ToAvaloniaPixelRect(),
screen.WorkingArea.ToAvaloniaPixelRect(),
screen.Primary.FromComBool());
screen.IsPrimary.FromComBool());
}
return result;

2
src/Avalonia.Native/WindowImplBase.cs

@ -92,7 +92,7 @@ namespace Avalonia.Native
_savedScaling = RenderScaling;
_nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost());
var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
var monitor = Screen.AllScreens.OrderBy(x => x.Scaling)
.FirstOrDefault(m => m.Bounds.Contains(Position));
Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d), PlatformResizeReason.Layout);

4
src/Avalonia.Native/avn.idl

@ -256,8 +256,8 @@ struct AvnScreen
{
AvnRect Bounds;
AvnRect WorkingArea;
float PixelDensity;
bool Primary;
float Scaling;
bool IsPrimary;
}
enum AvnPixelFormat

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>

28
src/Avalonia.X11/X11Screens.cs

@ -9,7 +9,7 @@ using JetBrains.Annotations;
namespace Avalonia.X11
{
class X11Screens : IScreenImpl
class X11Screens : IScreenImpl
{
private IX11Screens _impl;
@ -218,7 +218,7 @@ namespace Avalonia.X11
public int ScreenCount => _impl.Screens.Length;
public IReadOnlyList<Screen> AllScreens =>
_impl.Screens.Select(s => new Screen(s.PixelDensity, s.Bounds, s.WorkingArea, s.Primary)).ToArray();
_impl.Screens.Select(s => new Screen(s.Scaling, s.Bounds, s.WorkingArea, s.IsPrimary)).ToArray();
}
interface IX11Screens
@ -281,30 +281,34 @@ namespace Avalonia.X11
{
private const int FullHDWidth = 1920;
private const int FullHDHeight = 1080;
public bool Primary { get; }
public bool IsPrimary { get; }
public string Name { get; set; }
public PixelRect Bounds { get; set; }
public Size? PhysicalSize { get; set; }
public double PixelDensity { get; set; }
public double Scaling { get; set; }
public PixelRect WorkingArea { get; set; }
public X11Screen(PixelRect bounds, bool primary,
string name, Size? physicalSize, double? pixelDensity)
public X11Screen(
PixelRect bounds,
bool isPrimary,
string name,
Size? physicalSize,
double? scaling)
{
Primary = primary;
IsPrimary = isPrimary;
Name = name;
Bounds = bounds;
if (physicalSize == null && pixelDensity == null)
if (physicalSize == null && scaling == null)
{
PixelDensity = 1;
Scaling = 1;
}
else if (pixelDensity == null)
else if (scaling == null)
{
PixelDensity = GuessPixelDensity(bounds, physicalSize.Value);
Scaling = GuessPixelDensity(bounds, physicalSize.Value);
}
else
{
PixelDensity = pixelDensity.Value;
Scaling = scaling.Value;
PhysicalSize = physicalSize;
}
}

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

8
src/Avalonia.X11/X11Window.cs

@ -120,7 +120,7 @@ namespace Avalonia.X11
if (!_popup && Screen != null)
{
var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
var monitor = Screen.AllScreens.OrderBy(x => x.Scaling)
.FirstOrDefault(m => m.Bounds.Contains(Position));
if (monitor != null)
@ -570,9 +570,9 @@ namespace Avalonia.X11
newScaling = _scalingOverride.Value;
else
{
var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity)
var monitor = _platform.X11Screens.Screens.OrderBy(x => x.Scaling)
.FirstOrDefault(m => m.Bounds.Contains(Position));
newScaling = monitor?.PixelDensity ?? RenderScaling;
newScaling = monitor?.Scaling ?? RenderScaling;
}
if (RenderScaling != newScaling)
@ -994,7 +994,7 @@ namespace Avalonia.X11
public IScreenImpl Screen => _platform.Screens;
public Size MaxAutoSizeHint => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity))
public Size MaxAutoSizeHint => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.Scaling))
.OrderByDescending(x => x.Width + x.Height).FirstOrDefault();

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

@ -5,7 +5,6 @@
<IsPackable>true</IsPackable>
<PackageId>Avalonia.Markup.Xaml.Loader</PackageId>
<DefineConstants>$(DefineConstants);XAMLX_INTERNAL</DefineConstants>
<LangVersion>10</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))
{

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

4
src/Web/Avalonia.Web/Avalonia.Web.props

@ -1,5 +1,5 @@
<Project>
<Project>
<PropertyGroup>
<EmccExtraLDFlags>$(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\interop.js"</EmccExtraLDFlags>
<EmccInitialHeapSize>16384000</EmccInitialHeapSize> <!-- must be a multiple of 64KiB, 1024000 * num MB, number grows -->
</PropertyGroup>
</Project>

30
src/Web/Avalonia.Web/Avalonia.Web.targets

@ -4,4 +4,34 @@
<NativeFileReference Include="$(HarfBuzzSharpStaticLibraryPath)\3.1.7\libHarfBuzzSharp.a" />
<NativeFileReference Include="$(SkiaSharpStaticLibraryPath)\3.1.7\libSkiaSharp.a" />
</ItemGroup>
<PropertyGroup>
<UseAvaloniaWasmDefaultOptimizations Condition="'$(UseAvaloniaWasmDefaultOptimizations)'==''">True</UseAvaloniaWasmDefaultOptimizations>
<EmccExtraLDFlags>$(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\interop.js"</EmccExtraLDFlags>
<EmccFlags>$(EmccExtraLDFlags) -sERROR_ON_UNDEFINED_SYMBOLS=0</EmccFlags>
<WasmBuildNative>true</WasmBuildNative>
</PropertyGroup>
<PropertyGroup Condition="'$(UseAvaloniaWasmDefaultOptimizations)'=='True'">
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
<InvariantGlobalization>true</InvariantGlobalization>
<EmccCompileOptimizationFlag>-Oz</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag>-Oz</EmccLinkOptimizationFlag>
<WasmEmitSymbolMap>false</WasmEmitSymbolMap>
<WasmNativeDebugSymbols>false</WasmNativeDebugSymbols>
<WasmDebugLevel>0</WasmDebugLevel>
<WasmEnableExceptionHandling>false</WasmEnableExceptionHandling>
<TrimmerRemoveSymbols>true</TrimmerRemoveSymbols>
<DebuggerSupport>false</DebuggerSupport>
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
<EventSourceSupport>false</EventSourceSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
<UseNativeHttpHandler>true</UseNativeHttpHandler>
<UseSystemResourceKeys>true</UseSystemResourceKeys>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<IncludeSatelliteDllsProjectOutputGroup>false</IncludeSatelliteDllsProjectOutputGroup>
</PropertyGroup>
</Project>

14
src/Web/Avalonia.Web/AvaloniaView.cs

@ -77,8 +77,7 @@ namespace Avalonia.Web
_topLevelImpl.SetCssCursor = (cursor) =>
{
InputHelper.SetCursor(_containerElement, cursor); // macOS
InputHelper.SetCursor(_canvas, cursor); // windows
InputHelper.SetCursor(_containerElement, cursor);
};
_topLevel.Prepare();
@ -136,6 +135,8 @@ namespace Avalonia.Web
DomHelper.ObserveSize(host, null, OnSizeChanged);
CanvasHelper.RequestAnimationFrame(_canvas, true);
InputHelper.FocusElement(_containerElement);
}
private static RawPointerPoint ExtractRawPointerFromJSArgs(JSObject args)
@ -252,7 +253,14 @@ namespace Avalonia.Web
private bool OnKeyDown (string code, string key, int modifier)
{
return _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier);
var handled = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier);
if (!handled && key.Length == 1)
{
handled = _topLevelImpl.RawTextEvent(key);
}
return handled;
}
private bool OnKeyUp(string code, string key, int modifier)

2
src/Web/Avalonia.Web/Interop/CanvasHelper.cs

@ -33,7 +33,7 @@ internal static partial class CanvasHelper
public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop);
[JSImport("Canvas.setCanvasSize", AvaloniaModule.MainModuleName)]
public static partial void SetCanvasSize(JSObject canvas, int height, int width);
public static partial void SetCanvasSize(JSObject canvas, int width, int height);
[JSImport("Canvas.initGL", AvaloniaModule.MainModuleName)]
private static partial JSObject InitGL(

2
src/Web/Avalonia.Web/webapp/build.js

@ -5,7 +5,7 @@ require("esbuild").build({
],
outdir: "../wwwroot",
bundle: true,
minify: false,
minify: true,
format: "esm",
target: "es2016",
platform: "browser",

35
src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts

@ -106,12 +106,6 @@ export class Canvas {
// add the draw to the next frame
this.renderLoopRequest = window.requestAnimationFrame(() => {
if (this.glInfo) {
const GL = (globalThis as any).AvaloniaGL;
// make current
GL.makeContextCurrent(this.glInfo.context);
}
if (this.htmlCanvas.width !== this.newWidth) {
this.htmlCanvas.width = this.newWidth ?? 0;
}
@ -131,6 +125,11 @@ export class Canvas {
}
public setCanvasSize(width: number, height: number): void {
if (this.renderLoopRequest !== 0) {
window.cancelAnimationFrame(this.renderLoopRequest);
this.renderLoopRequest = 0;
}
this.newWidth = width;
this.newHeight = height;
@ -142,11 +141,7 @@ export class Canvas {
this.htmlCanvas.height = this.newHeight;
}
if (this.glInfo) {
const GL = (globalThis as any).AvaloniaGL;
// make current
GL.makeContextCurrent(this.glInfo.context);
}
this.requestAnimationFrame();
}
public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number): void {
@ -210,23 +205,25 @@ interface SizeWatcherInstance {
export class SizeWatcher {
static observer: ResizeObserver;
static elements: Map<string, HTMLElement>;
private static lastMove: number;
public static observe(element: HTMLElement, elementId: string | undefined, callback: (width: number, height: number) => void): void {
if (!element || !callback) {
return;
}
SizeWatcher.init();
SizeWatcher.lastMove = Date.now();
const watcherElement = element as SizeWatcherElement;
watcherElement.SizeWatcher = {
callback
};
callback(element.clientWidth, element.clientHeight);
SizeWatcher.elements.set(elementId ?? element.id, element);
SizeWatcher.observer.observe(element);
const handleResize = (args: UIEvent) => {
if (Date.now() - SizeWatcher.lastMove > 33) {
callback(element.clientWidth, element.clientHeight);
SizeWatcher.lastMove = Date.now();
}
};
SizeWatcher.invoke(element);
window.addEventListener("resize", handleResize);
}
public static unobserve(elementId: string): void {

6
src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts

@ -11,6 +11,7 @@ export class AvaloniaDOM {
host.tabIndex = 0;
host.oncontextmenu = function () { return false; };
host.style.overflow = "hidden";
host.style.touchAction = "none";
// Rendering target canvas
const canvas = document.createElement("canvas");
@ -18,12 +19,11 @@ export class AvaloniaDOM {
canvas.classList.add("avalonia-canvas");
canvas.style.backgroundColor = "#ccc";
canvas.style.width = "100%";
canvas.style.height = "100%";
canvas.style.position = "absolute";
// Native controls host
const nativeHost = document.createElement("div");
canvas.id = `nativeHost${randomIdPart}`;
nativeHost.id = `nativeHost${randomIdPart}`;
nativeHost.classList.add("avalonia-native-host");
nativeHost.style.left = "0px";
nativeHost.style.top = "0px";
@ -33,7 +33,7 @@ export class AvaloniaDOM {
// IME
const inputElement = document.createElement("input");
canvas.id = `input${randomIdPart}`;
inputElement.id = `inputElement${randomIdPart}`;
inputElement.classList.add("avalonia-input-element");
inputElement.autocapitalize = "none";
inputElement.type = "text";

26
src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts

@ -98,27 +98,23 @@ export class InputHelper {
wheelCallback: (args: WheelEvent) => boolean
) {
const pointerMoveHandler = (args: PointerEvent) => {
if (pointerMoveCallback(args)) {
args.preventDefault();
}
pointerMoveCallback(args);
args.preventDefault();
};
const pointerDownHandler = (args: PointerEvent) => {
if (pointerDownCallback(args)) {
args.preventDefault();
}
pointerDownCallback(args);
args.preventDefault();
};
const pointerUpHandler = (args: PointerEvent) => {
if (pointerUpCallback(args)) {
args.preventDefault();
}
pointerUpCallback(args);
args.preventDefault();
};
const wheelHandler = (args: WheelEvent) => {
if (wheelCallback(args)) {
args.preventDefault();
}
wheelCallback(args);
args.preventDefault();
};
element.addEventListener("pointermove", pointerMoveHandler);
@ -159,7 +155,11 @@ export class InputHelper {
}
public static setCursor(inputElement: HTMLInputElement, kind: string) {
inputElement.style.cursor = kind;
if (kind === "pointer") {
inputElement.style.removeProperty("cursor");
} else {
inputElement.style.cursor = kind;
}
}
public static setBounds(inputElement: HTMLInputElement, x: number, y: number, caretWidth: number, caretHeight: number, caret: number) {

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

6
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
@ -1877,7 +1878,10 @@ namespace Avalonia.Win32.Interop
public static uint LGID(IntPtr HKL)
{
return (uint)(HKL.ToInt32() & 0xffff);
unchecked
{
return (uint)((ulong)HKL & 0xffff);
}
}
public const int SORT_DEFAULT = 0;

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

8
src/Windows/Avalonia.Win32/TrayIconImpl.cs

@ -216,7 +216,7 @@ namespace Avalonia.Win32
{
Anchor = PopupAnchor.TopLeft,
Gravity = PopupGravity.BottomRight,
AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.PixelDensity, new Size(1, 1)),
AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.Scaling, new Size(1, 1)),
Size = finalRect.Size,
ConstraintAdjustment = PopupPositionerConstraintAdjustment.FlipX | PopupPositionerConstraintAdjustment.FlipY,
});
@ -244,16 +244,16 @@ namespace Avalonia.Win32
{
var point = _hiddenWindow.Screens.Primary.Bounds.TopLeft;
var size = _hiddenWindow.Screens.Primary.Bounds.Size;
return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.PixelDensity, size.Height * _hiddenWindow.Screens.Primary.PixelDensity);
return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.Scaling, size.Height * _hiddenWindow.Screens.Primary.Scaling);
}
}
public void MoveAndResize(Point devicePoint, Size virtualSize)
{
_moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.PixelDensity);
_moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.Scaling);
}
public double Scaling => _hiddenWindow.Screens.Primary.PixelDensity;
public double Scaling => _hiddenWindow.Screens.Primary.Scaling;
}
}

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
{

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

Loading…
Cancel
Save