Browse Source

Merge branch 'master' into disabled-items-should-not-be-selectable-with-keyboard

pull/7963/head
Tako 4 years ago
committed by GitHub
parent
commit
17caf1ba9f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      samples/ControlCatalog.Web/ControlCatalog.Web.csproj
  2. 28
      samples/ControlCatalog.Web/LinkerConfig.xml
  3. 2
      samples/ControlCatalog/Pages/CanvasPage.xaml
  4. 3
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  5. 19
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  6. 7
      src/Avalonia.Controls/ApiCompatBaseline.txt
  7. 168
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  8. 176
      src/Avalonia.Controls/TextBlock.cs
  9. 14
      src/Avalonia.Themes.Fluent/Controls/Button.xaml
  10. 6
      src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml
  11. 18
      src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml
  12. 10
      src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
  13. 16
      src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml
  14. 18
      src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml
  15. 2
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  16. 18
      src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml
  17. 6
      src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
  18. 2
      src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml
  19. 8
      src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml
  20. 6
      src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml
  21. 2
      src/Avalonia.Themes.Fluent/Controls/Slider.xaml
  22. 6
      src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml
  23. 6
      src/Avalonia.Themes.Fluent/Controls/TabItem.xaml
  24. 6
      src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml
  25. 12
      src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
  26. 22
      src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml
  27. 14
      src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml
  28. 442
      src/Avalonia.Visuals/Media/Color.cs
  29. 476
      src/Avalonia.Visuals/Media/HslColor.cs
  30. 259
      src/Avalonia.Visuals/Media/HsvColor.cs
  31. 2
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  32. 149
      tests/Avalonia.Benchmarks/Base/AvaloniaObject_Binding.cs
  33. 81
      tests/Avalonia.Benchmarks/Base/AvaloniaObject_Construct.cs
  34. 171
      tests/Avalonia.Benchmarks/Base/AvaloniaObject_GetObservable.cs
  35. 181
      tests/Avalonia.Benchmarks/Base/AvaloniaObject_GetValue.cs
  36. 95
      tests/Avalonia.Benchmarks/Base/AvaloniaObject_GetValueInherited.cs
  37. 130
      tests/Avalonia.Benchmarks/Base/AvaloniaObject_SetValue.cs
  38. 14
      tests/Avalonia.Benchmarks/Base/DirectPropertyBenchmark.cs
  39. 65
      tests/Avalonia.Benchmarks/Styling/Style_Activation.cs
  40. 89
      tests/Avalonia.Benchmarks/Styling/Style_Apply.cs
  41. 74
      tests/Avalonia.Benchmarks/Styling/Style_NonActive.cs
  42. 38
      tests/Avalonia.Benchmarks/TestBindingObservable.cs
  43. 112
      tests/Avalonia.Benchmarks/TestTypes.cs
  44. 1
      tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj
  45. 42
      tests/Avalonia.LeakTests/ControlTests.cs
  46. 124
      tests/Avalonia.Visuals.UnitTests/Media/ColorTests.cs

42
samples/ControlCatalog.Web/ControlCatalog.Web.csproj

@ -1,15 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
<Nullable>enable</Nullable>
<WasmBuildNative>True</WasmBuildNative>
<!--Temporal hack that fixes compilation in VS-->
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<EmccTotalMemory>16777216</EmccTotalMemory>
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
<BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
</PropertyGroup>
<ItemGroup>
<BlazorLinkerDescriptor Include="LinkerConfig.xml" />
</ItemGroup>
<!-- In debug, make builds faster by reducing optimizations -->
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<WasmNativeStrip>false</WasmNativeStrip>
@ -23,19 +22,36 @@
<EmccCompileOptimizationFlag>-O3</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag>-O3</EmccLinkOptimizationFlag>
<RunAOTCompilation>false</RunAOTCompilation>
<DebuggerSupport>false</DebuggerSupport>
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
<EventSourceSupport>false</EventSourceSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<InvariantGlobalization>true</InvariantGlobalization>
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
<UseNativeHttpHandler>true</UseNativeHttpHandler>
<UseSystemResourceKeys>true</UseSystemResourceKeys>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>link</TrimMode>
<TrimmerRemoveSymbols>true</TrimmerRemoveSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.0"/>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0" PrivateAssets="all"/>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0" PrivateAssets="all" />
</ItemGroup>
<Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.targets" />
<Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.CompilationTuning.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj"/>
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj"/>
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
</ItemGroup>
<Import Project="..\..\build\ReferenceCoreLibraries.props" />
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.targets" />
<Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.CompilationTuning.props" />
</Project>

28
samples/ControlCatalog.Web/LinkerConfig.xml

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
This file specifies which parts of the BCL or Blazor packages must not be
stripped by the IL Linker even if they aren't referenced by user code.
-->
<linker>
<assembly fullname="mscorlib">
<!--
Preserve the methods in WasmRuntime because its methods are called by
JavaScript client-side code to implement timers.
Fixes: https://github.com/dotnet/blazor/issues/239
-->
<type fullname="System.Threading.WasmRuntime"/>
</assembly>
<assembly fullname="System.Core">
<!--
System.Linq.Expressions* is required by Json.NET and any
expression.Compile caller. The assembly isn't stripped.
-->
<type fullname="System.Linq.Expressions*"/>
</assembly>
<!--
In this example, the app's entry point assembly is listed. The assembly
isn't stripped by the IL Linker.
-->
<assembly fullname="ControlCatalog" preserve="All" />
</linker>

2
samples/ControlCatalog/Pages/CanvasPage.xaml

@ -14,7 +14,7 @@
</LinearGradientBrush>
</Rectangle.OpacityMask>
</Rectangle>
<Rectangle Fill="Green" Stroke="Black" StrokeThickness="2" Width="40" Height="20" Canvas.Left="150" Canvas.Top="10" RadiusX="10" RadiusY="5" />
<Rectangle Fill="hsva(240, 83%, 73%, 90%)" Stroke="hsl(5, 85%, 85%)" StrokeThickness="2" Width="40" Height="20" Canvas.Left="150" Canvas.Top="10" RadiusX="10" RadiusY="5" />
<Ellipse Fill="Green" Width="58" Height="58" Canvas.Left="88" Canvas.Top="100"/>
<Path Fill="Orange" Data="M 0,0 c 0,0 50,0 50,-50 c 0,0 50,0 50,50 h -50 v 50 l -50,-50 Z" Canvas.Left="30" Canvas.Top="250"/>
<Path Fill="OrangeRed" Canvas.Left="180" Canvas.Top="250">

3
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -83,6 +83,9 @@
<Compile Include="../Avalonia.Visuals/Media/Color.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Visuals/Media/HslColor.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Visuals/Media/HsvColor.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>

19
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -2060,6 +2060,25 @@ namespace Avalonia.Controls
forceHorizontalScroll: true);
}
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (DataConnection.DataSource != null && !DataConnection.EventsWired)
{
DataConnection.WireEvents(DataConnection.DataSource);
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
// When wired to INotifyCollectionChanged, the DataGrid will be cleaned up by GC
if (DataConnection.DataSource != null && DataConnection.EventsWired)
{
DataConnection.UnWireEvents(DataConnection.DataSource);
}
}
/// <summary>
/// Arranges the content of the <see cref="T:Avalonia.Controls.DataGridRow" />.

7
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -36,6 +36,11 @@ MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.FontS
MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.FontWeight> Avalonia.AttachedProperty<Avalonia.Media.FontWeight> Avalonia.Controls.TextBlock.FontWeightProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.IBrush> Avalonia.AttachedProperty<Avalonia.Media.IBrush> Avalonia.Controls.TextBlock.ForegroundProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<System.Double> Avalonia.AttachedProperty<System.Double> Avalonia.Controls.TextBlock.FontSizeProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<Avalonia.Media.TextAlignment> Avalonia.StyledProperty<Avalonia.Media.TextAlignment> Avalonia.Controls.TextBlock.TextAlignmentProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<Avalonia.Media.TextTrimming> Avalonia.StyledProperty<Avalonia.Media.TextTrimming> Avalonia.Controls.TextBlock.TextTrimmingProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<Avalonia.Media.TextWrapping> Avalonia.StyledProperty<Avalonia.Media.TextWrapping> Avalonia.Controls.TextBlock.TextWrappingProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Double> Avalonia.StyledProperty<System.Double> Avalonia.Controls.TextBlock.LineHeightProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Int32> Avalonia.StyledProperty<System.Int32> Avalonia.Controls.TextBlock.MaxLinesProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.FontFamily Avalonia.Controls.TextBlock.GetFontFamily(Avalonia.Controls.Control)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.TextBlock.GetFontSize(Avalonia.Controls.Control)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.FontStyle Avalonia.Controls.TextBlock.GetFontStyle(Avalonia.Controls.Control)' does not exist in the implementation but it does exist in the contract.
@ -89,4 +94,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
Total Issues: 90
Total Issues: 95

168
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
@ -46,7 +47,73 @@ namespace Avalonia.Controls.Presenters
/// </summary>
public static readonly StyledProperty<BoxShadows> BoxShadowProperty =
Border.BoxShadowProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="Foreground"/> property.
/// </summary>
public static readonly AttachedProperty<IBrush?> ForegroundProperty =
TextElement.ForegroundProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="FontFamily"/> property.
/// </summary>
public static readonly AttachedProperty<FontFamily> FontFamilyProperty =
TextElement.FontFamilyProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="FontSize"/> property.
/// </summary>
public static readonly AttachedProperty<double> FontSizeProperty =
TextElement.FontSizeProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="FontStyle"/> property.
/// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty =
TextElement.FontStyleProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="FontWeight"/> property.
/// </summary>
public static readonly AttachedProperty<FontWeight> FontWeightProperty =
TextElement.FontWeightProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="FontStretch"/> property.
/// </summary>
public static readonly AttachedProperty<FontStretch> FontStretchProperty =
TextElement.FontStretchProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="TextAlignment"/> property
/// </summary>
public static readonly AttachedProperty<TextAlignment> TextAlignmentProperty =
TextBlock.TextAlignmentProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="TextWrapping"/> property
/// </summary>
public static readonly AttachedProperty<TextWrapping> TextWrappingProperty =
TextBlock.TextWrappingProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="TextTrimming"/> property
/// </summary>
public static readonly AttachedProperty<TextTrimming> TextTrimmingProperty =
TextBlock.TextTrimmingProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="LineHeight"/> property
/// </summary>
public static readonly AttachedProperty<double> LineHeightProperty =
TextBlock.LineHeightProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="MaxLines"/> property
/// </summary>
public static readonly AttachedProperty<int> MaxLinesProperty =
TextBlock.MaxLinesProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="Child"/> property.
/// </summary>
@ -159,6 +226,105 @@ namespace Avalonia.Controls.Presenters
set => SetValue(BoxShadowProperty, value);
}
/// <summary>
/// Gets or sets a brush used to paint the text.
/// </summary>
public IBrush? Foreground
{
get => GetValue(ForegroundProperty);
set => SetValue(ForegroundProperty, value);
}
/// <summary>
/// Gets or sets the font family.
/// </summary>
public FontFamily FontFamily
{
get => GetValue(FontFamilyProperty);
set => SetValue(FontFamilyProperty, value);
}
/// <summary>
/// Gets or sets the font size.
/// </summary>
public double FontSize
{
get => GetValue(FontSizeProperty);
set => SetValue(FontSizeProperty, value);
}
/// <summary>
/// Gets or sets the font style.
/// </summary>
public FontStyle FontStyle
{
get => GetValue(FontStyleProperty);
set => SetValue(FontStyleProperty, value);
}
/// <summary>
/// Gets or sets the font weight.
/// </summary>
public FontWeight FontWeight
{
get => GetValue(FontWeightProperty);
set => SetValue(FontWeightProperty, value);
}
/// <summary>
/// Gets or sets the font stretch.
/// </summary>
public FontStretch FontStretch
{
get => GetValue(FontStretchProperty);
set => SetValue(FontStretchProperty, value);
}
/// <summary>
/// Gets or sets the text alignment
/// </summary>
public TextAlignment TextAlignment
{
get => GetValue(TextAlignmentProperty);
set => SetValue(TextAlignmentProperty, value);
}
/// <summary>
/// Gets or sets the text wrapping
/// </summary>
public TextWrapping TextWrapping
{
get => GetValue(TextWrappingProperty);
set => SetValue(TextWrappingProperty, value);
}
/// <summary>
/// Gets or sets the text trimming
/// </summary>
public TextTrimming TextTrimming
{
get => GetValue(TextTrimmingProperty);
set => SetValue(TextTrimmingProperty, value);
}
/// <summary>
/// Gets or sets the line height
/// </summary>
public double LineHeight
{
get => GetValue(LineHeightProperty);
set => SetValue(LineHeightProperty, value);
}
/// <summary>
/// Gets or sets the max lines
/// </summary>
public int MaxLines
{
get => GetValue(MaxLinesProperty);
set => SetValue(MaxLinesProperty, value);
}
/// <summary>
/// Gets the control displayed by the presenter.
/// </summary>

176
src/Avalonia.Controls/TextBlock.cs

@ -76,19 +76,21 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="LineHeight"/> property.
/// </summary>
public static readonly StyledProperty<double> LineHeightProperty =
AvaloniaProperty.Register<TextBlock, double>(
public static readonly AttachedProperty<double> LineHeightProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, double>(
nameof(LineHeight),
double.NaN,
validate: IsValidLineHeight);
validate: IsValidLineHeight,
inherits: true);
/// <summary>
/// Defines the <see cref="MaxLines"/> property.
/// </summary>
public static readonly StyledProperty<int> MaxLinesProperty =
AvaloniaProperty.Register<TextBlock, int>(
public static readonly AttachedProperty<int> MaxLinesProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, int>(
nameof(MaxLines),
validate: IsValidMaxLines);
validate: IsValidMaxLines,
inherits: true);
/// <summary>
/// Defines the <see cref="Text"/> property.
@ -110,20 +112,24 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="TextAlignment"/> property.
/// </summary>
public static readonly StyledProperty<TextAlignment> TextAlignmentProperty =
AvaloniaProperty.Register<TextBlock, TextAlignment>(nameof(TextAlignment));
public static readonly AttachedProperty<TextAlignment> TextAlignmentProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, TextAlignment>(nameof(TextAlignment),
inherits: true);
/// <summary>
/// Defines the <see cref="TextWrapping"/> property.
/// </summary>
public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
AvaloniaProperty.Register<TextBlock, TextWrapping>(nameof(TextWrapping));
public static readonly AttachedProperty<TextWrapping> TextWrappingProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, TextWrapping>(nameof(TextWrapping),
inherits: true);
/// <summary>
/// Defines the <see cref="TextTrimming"/> property.
/// </summary>
public static readonly StyledProperty<TextTrimming> TextTrimmingProperty =
AvaloniaProperty.Register<TextBlock, TextTrimming>(nameof(TextTrimming), defaultValue: TextTrimming.None);
public static readonly AttachedProperty<TextTrimming> TextTrimmingProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, TextTrimming>(nameof(TextTrimming),
defaultValue: TextTrimming.None,
inherits: true);
/// <summary>
/// Defines the <see cref="TextDecorations"/> property.
@ -358,6 +364,152 @@ namespace Avalonia.Controls
control.SetValue(BaselineOffsetProperty, value);
}
/// <summary>
/// Reads the attached property from the given element
/// </summary>
/// <param name="control">The element to which to read the attached property.</param>
public static TextAlignment GetTextAlignment(Control control)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(TextAlignmentProperty);
}
/// <summary>
/// Writes the attached property BaselineOffset to the given element.
/// </summary>
/// <param name="control">The element to which to write the attached property.</param>
/// <param name="alignment">The property value to set</param>
public static void SetTextAlignment(Control control, TextAlignment alignment)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(TextAlignmentProperty, alignment);
}
/// <summary>
/// Reads the attached property from the given element
/// </summary>
/// <param name="control">The element to which to read the attached property.</param>
public static TextWrapping GetTextWrapping(Control control)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(TextWrappingProperty);
}
/// <summary>
/// Writes the attached property BaselineOffset to the given element.
/// </summary>
/// <param name="control">The element to which to write the attached property.</param>
/// <param name="wrapping">The property value to set</param>
public static void SetTextWrapping(Control control, TextWrapping wrapping)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(TextWrappingProperty, wrapping);
}
/// <summary>
/// Reads the attached property from the given element
/// </summary>
/// <param name="control">The element to which to read the attached property.</param>
public static TextTrimming GetTextTrimming(Control control)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(TextTrimmingProperty);
}
/// <summary>
/// Writes the attached property BaselineOffset to the given element.
/// </summary>
/// <param name="control">The element to which to write the attached property.</param>
/// <param name="trimming">The property value to set</param>
public static void SetTextTrimming(Control control, TextTrimming trimming)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(TextTrimmingProperty, trimming);
}
/// <summary>
/// Reads the attached property from the given element
/// </summary>
/// <param name="control">The element to which to read the attached property.</param>
public static double GetLineHeight(Control control)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(LineHeightProperty);
}
/// <summary>
/// Writes the attached property BaselineOffset to the given element.
/// </summary>
/// <param name="control">The element to which to write the attached property.</param>
/// <param name="height">The property value to set</param>
public static void SetLineHeight(Control control, double height)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(LineHeightProperty, height);
}
/// <summary>
/// Reads the attached property from the given element
/// </summary>
/// <param name="control">The element to which to read the attached property.</param>
public static int GetMaxLines(Control control)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(MaxLinesProperty);
}
/// <summary>
/// Writes the attached property BaselineOffset to the given element.
/// </summary>
/// <param name="control">The element to which to write the attached property.</param>
/// <param name="maxLines">The property value to set</param>
public static void SetMaxLines(Control control, int maxLines)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(MaxLinesProperty, maxLines);
}
/// <summary>
/// Renders the <see cref="TextBlock"/> to a drawing context.
/// </summary>

14
src/Avalonia.Themes.Fluent/Controls/Button.xaml

@ -43,37 +43,37 @@
<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
<Style Selector="Button:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
<Style Selector="Button:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
<Style Selector="Button.accent /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrush}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentButtonForeground}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}" />
</Style>
<Style Selector="Button.accent:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" />
</Style>
<Style Selector="Button.accent:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentButtonForegroundPressed}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPressed}" />
</Style>
<Style Selector="Button, RepeatButton, ToggleButton, DropDownButton">
@ -92,6 +92,6 @@
<Style Selector="Button.accent:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" />
</Style>
</Styles>

6
src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml

@ -61,17 +61,17 @@
</Style>
<Style Selector="Button.CalendarHeader:pointerover /template/ ContentPresenter#Text">
<Setter Property="BorderBrush" Value="{DynamicResource CalendarViewNavigationButtonBorderBrushPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource CalendarViewNavigationButtonForegroundPointerOver}"/>
<Setter Property="Foreground" Value="{DynamicResource CalendarViewNavigationButtonForegroundPointerOver}"/>
<!-- Prevent base button template:pointerover from overriding background here -->
<Setter Property="Background" Value="{DynamicResource CalendarViewNavigationButtonBackground}"/>
</Style>
<Style Selector="Button.CalendarHeader:pressed /template/ ContentPresenter#Text">
<Setter Property="TextElement.Foreground" Value="{DynamicResource CalendarViewNavigationButtonForegroundPressed}"/>
<Setter Property="Foreground" Value="{DynamicResource CalendarViewNavigationButtonForegroundPressed}"/>
<!-- Prevent base button template:pointerover from overriding background here -->
<Setter Property="Background" Value="{DynamicResource CalendarViewNavigationButtonBackground}"/>
</Style>
<Style Selector="Button.CalendarHeader:disabled /template/ ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource CalendarViewWeekDayForegroundDisabled}"/>
<Setter Property="Foreground" Value="{DynamicResource CalendarViewWeekDayForegroundDisabled}"/>
</Style>
</Border.Styles>
<!-- To keep calendar from resizing when switching DisplayMode

18
src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml

@ -76,7 +76,7 @@
<!-- Unchecked PointerOver State -->
<Style Selector="CheckBox:pointerover /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPointerOver}" />
</Style>
<Style Selector="CheckBox:pointerover /template/ Border#PART_Border">
@ -95,7 +95,7 @@
<!-- Unchecked Pressed State -->
<Style Selector="CheckBox:pressed /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPressed}" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPressed}" />
</Style>
<Style Selector="CheckBox:pressed /template/ Border#PART_Border">
@ -114,7 +114,7 @@
<!-- Unchecked Disabled state -->
<Style Selector="CheckBox:disabled /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedDisabled}" />
</Style>
<Style Selector="CheckBox:disabled /template/ Border#PART_Border">
@ -157,7 +157,7 @@
<!-- Checked PointerOver State -->
<Style Selector="CheckBox:checked:pointerover /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPointerOver}" />
</Style>
<Style Selector="CheckBox:checked:pointerover /template/ Border#PART_Border">
@ -176,7 +176,7 @@
<!-- Checked Pressed State -->
<Style Selector="CheckBox:checked:pressed /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPressed}" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPressed}" />
</Style>
<Style Selector="CheckBox:checked:pressed /template/ Border#PART_Border">
@ -195,7 +195,7 @@
<!-- Checked Disabled State -->
<Style Selector="CheckBox:checked:disabled /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundCheckedDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedDisabled}" />
</Style>
<Style Selector="CheckBox:checked:disabled /template/ Border#PART_Border">
@ -237,7 +237,7 @@
<!-- Indeterminate PointerOver State -->
<Style Selector="CheckBox:indeterminate:pointerover /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePointerOver}" />
</Style>
<Style Selector="CheckBox:indeterminate:pointerover /template/ Border#PART_Border">
@ -256,7 +256,7 @@
<!-- Indeterminate Pressed State -->
<Style Selector="CheckBox:indeterminate:pressed /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePressed}" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePressed}" />
</Style>
<Style Selector="CheckBox:indeterminate:pressed /template/ Border#PART_Border">
@ -275,7 +275,7 @@
<!-- Indeterminate Disabled State -->
<Style Selector="CheckBox:indeterminate:disabled /template/ ContentPresenter#ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminateDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminateDisabled}" />
</Style>
<Style Selector="CheckBox:indeterminate:disabled /template/ Border#PART_Border">

10
src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml

@ -54,7 +54,7 @@
Grid.Column="0"
Grid.ColumnSpan="2"
IsVisible="False"
TextElement.FontWeight="{DynamicResource ComboBoxHeaderThemeFontWeight}"
FontWeight="{DynamicResource ComboBoxHeaderThemeFontWeight}"
Margin="{DynamicResource ComboBoxTopHeaderMargin}"
VerticalAlignment="Top" />
<Border x:Name="Background"
@ -178,11 +178,11 @@
</Style>
<Style Selector="ComboBox:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
</Style>
<Style Selector="ComboBox:disabled /template/ ContentControl#ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
</Style>
<Style Selector="ComboBox:disabled /template/ TextBlock#PlaceholderTextBlock">
@ -200,7 +200,7 @@
</Style>
<Style Selector="ComboBox:focus-visible /template/ ContentControl#ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxForegroundFocused}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundFocused}" />
</Style>
<Style Selector="ComboBox:focus-visible /template/ TextBlock#PlaceholderTextBlock">
@ -213,7 +213,7 @@
<!-- Focus Pressed State -->
<Style Selector="ComboBox:focused:pressed /template/ ContentControl#ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxForegroundFocusedPressed}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundFocusedPressed}" />
</Style>
<Style Selector="ComboBox:focused:pressed /template/ TextBlock#PlaceholderTextBlock">

16
src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml

@ -25,7 +25,7 @@
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
TextElement.Foreground="{TemplateBinding Foreground}"
Foreground="{TemplateBinding Foreground}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
@ -43,48 +43,48 @@
<Style Selector="ComboBoxItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxItemForegroundPointerOver}" />
</Style>
<!-- Disabled state -->
<Style Selector="ComboBoxItem:disabled /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxItemForegroundDisabled}" />
</Style>
<!-- Pressed state -->
<Style Selector="ComboBoxItem:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundPressed}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxItemForegroundPressed}" />
</Style>
<!-- Selected state -->
<Style Selector="ComboBoxItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundSelected}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushSelected}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelected}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxItemForegroundSelected}" />
</Style>
<!-- Selected Disabled state -->
<Style Selector="ComboBoxItem:selected:disabled /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundSelectedDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushSelectedDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedDisabled}" />
</Style>
<!-- Selected PointerOver state -->
<Style Selector="ComboBoxItem:selected:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundSelectedPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushSelectedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedPointerOver}" />
</Style>
<!-- Selected Pressed state -->
<Style Selector="ComboBoxItem:selected:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ComboBoxItemBackgroundSelectedPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComboBoxItemBorderBrushSelectedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedPressed}" />
<Setter Property="Foreground" Value="{DynamicResource ComboBoxItemForegroundSelectedPressed}" />
</Style>
</Styles>

18
src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml

@ -43,7 +43,7 @@
</Style>
<Style Selector="ListBoxItem.DateTimePickerItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
</Style>
<Style Selector="ListBoxItem.DateTimePickerItem.MonthItem">
<Setter Property="Padding" Value="{DynamicResource DatePickerFlyoutPresenterMonthPadding}"/>
@ -70,7 +70,7 @@
BorderBrush="{DynamicResource DateTimePickerFlyoutButtonBorderBrush}"
BorderThickness="{DynamicResource DateTimeFlyoutButtonBorderThickness}"
Content="{TemplateBinding Content}"
TextElement.Foreground="{DynamicResource SystemControlHighlightAltBaseHighBrush}"
Foreground="{DynamicResource SystemControlHighlightAltBaseHighBrush}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
@ -84,13 +84,13 @@
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPointerOver}"/>
<Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPointerOver}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPointerOver}"/>
<Setter Property="Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPointerOver}"/>
</Style>
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPressed}"/>
<Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPressed}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPressed}"/>
<Setter Property="Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPressed}"/>
</Style>
@ -165,7 +165,7 @@
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
TextElement.Foreground="{TemplateBinding Foreground}"
Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
CornerRadius="{TemplateBinding CornerRadius}"/>
@ -212,7 +212,7 @@
</Setter>
</Style>
<Style Selector="DatePicker /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource DatePickerHeaderForeground}"/>
<Setter Property="Foreground" Value="{DynamicResource DatePickerHeaderForeground}"/>
</Style>
<Style Selector="DatePicker:disabled /template/ Rectangle">
<Setter Property="Fill" Value="{DynamicResource DatePickerSpacerFillDisabled}"/>
@ -221,19 +221,19 @@
<Style Selector="DatePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter">
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPointerOver}"/>
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPointerOver}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource DatePickerButtonForegroundPointerOver}"/>
<Setter Property="Foreground" Value="{DynamicResource DatePickerButtonForegroundPointerOver}"/>
</Style>
<Style Selector="DatePicker /template/ Button#FlyoutButton:pressed /template/ ContentPresenter">
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPressed}"/>
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPressed}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource DatePickerButtonForegroundPressed}"/>
<Setter Property="Foreground" Value="{DynamicResource DatePickerButtonForegroundPressed}"/>
</Style>
<Style Selector="DatePicker /template/ Button#FlyoutButton:disabled /template/ ContentPresenter">
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushDisabled}"/>
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundDisabled}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource DatePickerButtonForegroundDisabled}"/>
<Setter Property="Foreground" Value="{DynamicResource DatePickerButtonForegroundDisabled}"/>
</Style>
<!-- Changes foreground for watermark text when SelectedDate is null-->

2
src/Avalonia.Themes.Fluent/Controls/Expander.xaml

@ -110,7 +110,7 @@
BorderThickness="0"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
TextElement.Foreground="{DynamicResource ExpanderForeground}" />
Foreground="{DynamicResource ExpanderForeground}" />
<Border x:Name="ExpandCollapseChevronBorder"
Grid.Column="1"
Width="32"

18
src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml

@ -35,13 +35,13 @@
</Style>
<Style Selector="ListBoxItem /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.FontWeight" Value="Normal" />
<Setter Property="TextBlock.FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
</Style>
<!-- Disabled State -->
<Style Selector="ListBoxItem:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlDisabledBaseMediumLowBrush}" />
<Setter Property="Foreground" Value="{DynamicResource SystemControlDisabledBaseMediumLowBrush}" />
</Style>
<!-- PointerOver State -->
@ -49,7 +49,7 @@
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightListLowBrush}" />
</Style>
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
<Setter Property="Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style>
<!-- Pressed State -->
@ -57,7 +57,7 @@
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightListMediumBrush}" />
</Style>
<Style Selector="ListBoxItem:pressed /template/ ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
<Setter Property="Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style>
<!-- Selected State -->
@ -65,7 +65,7 @@
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightListAccentLowBrush}" />
</Style>
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
<Setter Property="Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style>
<!-- Selected Unfocused State -->
@ -73,7 +73,7 @@
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightListAccentLowBrush}" />
</Style>
<Style Selector="ListBoxItem:selected:not(:focus) /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
<Setter Property="Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style>
<!-- Selected PointerOver State -->
@ -81,7 +81,7 @@
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightListAccentMediumBrush}" />
</Style>
<Style Selector="ListBoxItem:selected:pointerover /template/ ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
<Setter Property="Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style>
<!-- Selected Pressed State -->
@ -89,6 +89,6 @@
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightListAccentHighBrush}" />
</Style>
<Style Selector="ListBoxItem:selected:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
<Setter Property="Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style>
</Styles>

6
src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml

@ -218,7 +218,7 @@
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackgroundPointerOver}" />
</Style>
<Style Selector="MenuItem:selected /template/ ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource MenuFlyoutItemForegroundPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemForegroundPointerOver}" />
</Style>
<Style Selector="MenuItem:selected /template/ TextBlock#PART_InputGestureText">
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForegroundPointerOver}" />
@ -232,7 +232,7 @@
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackgroundPressed}" />
</Style>
<Style Selector="MenuItem:pressed /template/ Border#PART_LayoutRoot:pointerover ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource MenuFlyoutItemForegroundPressed}" />
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemForegroundPressed}" />
</Style>
<Style Selector="MenuItem:pressed /template/ Border#PART_LayoutRoot:pointerover TextBlock#PART_InputGestureText">
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForegroundPressed}" />
@ -245,7 +245,7 @@
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackgroundDisabled}" />
</Style>
<Style Selector="MenuItem:disabled /template/ ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource MenuFlyoutItemForegroundDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemForegroundDisabled}" />
</Style>
<Style Selector="MenuItem:disabled /template/ TextBlock#PART_InputGestureText">
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForegroundDisabled}" />

2
src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml

@ -12,7 +12,7 @@
IsVisible="{Binding $parent[TopLevel].(NativeMenu.IsNativeMenuExported), Converter={StaticResource AvaloniaThemesDefaultNativeMenuBarInverseBooleanValueConverter}}"
Items="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}">
<Menu.Styles>
<Style x:DataType="NativeMenuItem" Selector="MenuItem">
<Style x:CompileBindings="False" Selector="MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="InputGesture" Value="{Binding Gesture}"/>
<Setter Property="Items" Value="{Binding Menu.Items}"/>

8
src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml

@ -49,7 +49,7 @@
<ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
TextElement.Foreground="{TemplateBinding Foreground}"
Foreground="{TemplateBinding Foreground}"
Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
@ -81,7 +81,7 @@
<!-- PointerOver State -->
<Style Selector="RadioButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource RadioButtonForegroundPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource RadioButtonForegroundPointerOver}" />
</Style>
<Style Selector="RadioButton:pointerover /template/ Border#RootBorder">
@ -107,7 +107,7 @@
<!-- Pressed State -->
<Style Selector="RadioButton:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource RadioButtonForegroundPressed}" />
<Setter Property="Foreground" Value="{DynamicResource RadioButtonForegroundPressed}" />
</Style>
<Style Selector="RadioButton:pressed /template/ Border#RootBorder">
@ -133,7 +133,7 @@
<!-- Disabled State -->
<Style Selector="RadioButton:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource RadioButtonForegroundDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource RadioButtonForegroundDisabled}" />
</Style>
<Style Selector="RadioButton:disabled /template/ Border#RootBorder">

6
src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml

@ -46,18 +46,18 @@
<Style Selector="RepeatButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource RepeatButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource RepeatButtonBorderBrushPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource RepeatButtonForegroundPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource RepeatButtonForegroundPointerOver}" />
</Style>
<Style Selector="RepeatButton:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource RepeatButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource RepeatButtonBorderBrushPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource RepeatButtonForegroundPressed}" />
<Setter Property="Foreground" Value="{DynamicResource RepeatButtonForegroundPressed}" />
</Style>
<Style Selector="RepeatButton:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource RepeatButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource RepeatButtonBorderBrushDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource RepeatButtonForegroundDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource RepeatButtonForegroundDisabled}" />
</Style>
</Styles>

2
src/Avalonia.Themes.Fluent/Controls/Slider.xaml

@ -208,7 +208,7 @@
<!-- Disabled State -->
<Style Selector="Slider:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource SliderHeaderForegroundDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource SliderHeaderForegroundDisabled}" />
</Style>
<Style Selector="Slider:disabled /template/ RepeatButton#PART_DecreaseButton">

6
src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml

@ -126,7 +126,7 @@
SplitButton /template/ Button#PART_SecondaryButton:pointerover /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundPointerOver}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundPointerOver}" />
</Style>
<Style Selector="SplitButton /template/ Button#PART_SecondaryButton:pointerover PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundPointerOver}" />
@ -137,7 +137,7 @@
SplitButton /template/ Button#PART_SecondaryButton:pressed /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
</Style>
<Style Selector="SplitButton /template/ Button#PART_SecondaryButton:pressed PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundPressed}" />
@ -207,7 +207,7 @@
SplitButton:checked /template/ Button#PART_SecondaryButton:pressed /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SplitButtonBackgroundCheckedPressed}" />
<Setter Property="Border.BorderBrush" Value="{DynamicResource SplitButtonBorderBrushCheckedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />
</Style>
<Style Selector="SplitButton:checked /template/ Button#PART_SecondaryButton:pressed PathIcon">
<Setter Property="Foreground" Value="{DynamicResource SplitButtonForegroundCheckedPressed}" />

6
src/Avalonia.Themes.Fluent/Controls/TabItem.xaml

@ -37,9 +37,9 @@
Content="{TemplateBinding Header}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
TextElement.FontFamily="{TemplateBinding FontFamily}"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}" />
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}" />
<Border Name="PART_SelectedPipe"
Background="{DynamicResource TabItemHeaderSelectedPipeFill}" />
</Panel>

6
src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml

@ -36,9 +36,9 @@
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
TextElement.FontFamily="{TemplateBinding FontFamily}"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}" />
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}" />
<Border Name="PART_SelectedPipe"
Background="{DynamicResource TabItemHeaderSelectedPipeFill}" />
</Panel>

12
src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml

@ -51,7 +51,7 @@
ContentTemplate="{TemplateBinding HeaderTemplate}"
Margin="{DynamicResource TimePickerTopHeaderMargin}"
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}"
TextElement.Foreground="{DynamicResource TimePickerHeaderForeground}"
Foreground="{DynamicResource TimePickerHeaderForeground}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top" />
@ -77,7 +77,7 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
Content="{TemplateBinding Content}"
TextElement.Foreground="{TemplateBinding Foreground}"
Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" />
</ControlTemplate>
@ -139,7 +139,7 @@
</Style>
<Style Selector="TimePicker:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource TimePickerHeaderForegroundDisabled}"/>
<Setter Property="Foreground" Value="{DynamicResource TimePickerHeaderForegroundDisabled}"/>
</Style>
<Style Selector="TimePicker:disabled /template/ Rectangle">
<Setter Property="Fill" Value="{DynamicResource TimePickerSpacerFillDisabled}"/>
@ -148,19 +148,19 @@
<Style Selector="TimePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundPointerOver}"/>
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushPointerOver}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource TimePickerButtonForegroundPointerOver}"/>
<Setter Property="Foreground" Value="{DynamicResource TimePickerButtonForegroundPointerOver}"/>
</Style>
<Style Selector="TimePicker /template/ Button:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundPressed}"/>
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushPressed}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource TimePickerButtonForegroundPressed}"/>
<Setter Property="Foreground" Value="{DynamicResource TimePickerButtonForegroundPressed}"/>
</Style>
<Style Selector="TimePicker /template/ Button:disabled /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundDisabled}"/>
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushDisabled}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource TimePickerButtonForegroundDisabled}"/>
<Setter Property="Foreground" Value="{DynamicResource TimePickerButtonForegroundDisabled}"/>
</Style>
<Style Selector="TimePicker:hasnotime /template/ Button#FlyoutButton TextBlock">

22
src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml

@ -48,66 +48,66 @@
<Style Selector="ToggleButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundPointerOver}" />
</Style>
<Style Selector="ToggleButton:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundPressed}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundPressed}" />
</Style>
<Style Selector="ToggleButton:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundDisabled}" />
</Style>
<Style Selector="ToggleButton:checked /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundChecked}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushChecked}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundChecked}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundChecked}" />
</Style>
<Style Selector="ToggleButton:checked:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushCheckedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedPointerOver}" />
</Style>
<Style Selector="ToggleButton:checked:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushCheckedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedPressed}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedPressed}" />
</Style>
<Style Selector="ToggleButton:checked:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushCheckedDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedDisabled}" />
</Style>
<Style Selector="ToggleButton:indeterminate /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundIndeterminate}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushIndeterminate}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminate}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminate}" />
</Style>
<Style Selector="ToggleButton:indeterminate:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundIndeterminatePointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushIndeterminatePointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminatePointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminatePointerOver}" />
</Style>
<Style Selector="ToggleButton:indeterminate:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundIndeterminatePressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushIndeterminatePressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminatePressed}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminatePressed}" />
</Style>
<Style Selector="ToggleButton:indeterminate:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundIndeterminateDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushIndeterminateDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminateDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundIndeterminateDisabled}" />
</Style>
</Styles>

14
src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml

@ -107,7 +107,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushPointerOver}" />
</Style>
<Style Selector="TreeViewItem /template/ Border#PART_LayoutRoot:pointerover > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource TreeViewItemForegroundPointerOver}" />
</Style>
<!-- Pressed state -->
@ -116,7 +116,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushPressed}" />
</Style>
<Style Selector="TreeViewItem:pressed /template/ Border#PART_LayoutRoot:pointerover > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundPressed}" />
<Setter Property="Foreground" Value="{DynamicResource TreeViewItemForegroundPressed}" />
</Style>
<!-- Disabled state -->
@ -125,7 +125,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushDisabled}" />
</Style>
<Style Selector="TreeViewItem:disabled /template/ Border#PART_LayoutRoot > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource TreeViewItemForegroundDisabled}" />
</Style>
<!-- Selected state -->
@ -134,7 +134,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushSelected}" />
</Style>
<Style Selector="TreeViewItem:selected /template/ Border#PART_LayoutRoot > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundSelected}" />
<Setter Property="Foreground" Value="{DynamicResource TreeViewItemForegroundSelected}" />
</Style>
<!-- Selected PointerOver state -->
@ -143,7 +143,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushSelectedPointerOver}" />
</Style>
<Style Selector="TreeViewItem:selected /template/ Border#PART_LayoutRoot:pointerover > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundSelectedPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource TreeViewItemForegroundSelectedPointerOver}" />
</Style>
<!-- Selected Pressed state -->
@ -152,7 +152,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushSelectedPressed}" />
</Style>
<Style Selector="TreeViewItem:pressed:selected /template/ Border#PART_LayoutRoot:pointerover > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundSelectedPressed}" />
<Setter Property="Foreground" Value="{DynamicResource TreeViewItemForegroundSelectedPressed}" />
</Style>
<!-- Disabled Selected state -->
@ -161,7 +161,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource TreeViewItemBorderBrushSelectedDisabled}" />
</Style>
<Style Selector="TreeViewItem:disabled:selected /template/ Border#PART_LayoutRoot > ContentPresenter#PART_HeaderPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource TreeViewItemForegroundSelectedDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource TreeViewItemForegroundSelectedDisabled}" />
</Style>
<!-- ExpandCollapseChevron Group states -->

442
src/Avalonia.Visuals/Media/Color.cs

@ -1,3 +1,10 @@
// Color conversion portions of this source file are adapted from the WinUI project
// (https://github.com/microsoft/microsoft-ui-xaml)
// and the Windows Community Toolkit project.
// (https://github.com/CommunityToolkit/WindowsCommunityToolkit)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Globalization;
#if !BUILDTASK
@ -14,6 +21,8 @@ namespace Avalonia.Media
#endif
readonly struct Color : IEquatable<Color>
{
private const double byteToDouble = 1.0 / 255;
static Color()
{
#if !BUILDTASK
@ -41,6 +50,13 @@ namespace Avalonia.Media
/// </summary>
public byte B { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="a">The alpha component.</param>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
public Color(byte a, byte r, byte g, byte b)
{
A = a;
@ -144,17 +160,46 @@ namespace Avalonia.Media
return false;
}
if (s[0] == '#' && TryParseInternal(s.AsSpan(), out color))
if (s[0] == '#' &&
TryParseHexFormat(s.AsSpan(), out color))
{
return true;
}
if (s.Length > 5 &&
(s[0] == 'r' || s[0] == 'R') &&
(s[1] == 'g' || s[1] == 'G') &&
(s[2] == 'b' || s[2] == 'B') &&
TryParseCssFormat(s, out color))
{
return true;
}
if (s.Length > 5 &&
(s[0] == 'h' || s[0] == 'H') &&
(s[1] == 's' || s[1] == 'S') &&
(s[2] == 'l' || s[2] == 'L') &&
HslColor.TryParse(s, out HslColor hslColor))
{
color = hslColor.ToRgb();
return true;
}
if (s.Length > 5 &&
(s[0] == 'h' || s[0] == 'H') &&
(s[1] == 's' || s[1] == 'S') &&
(s[2] == 'v' || s[2] == 'V') &&
HsvColor.TryParse(s, out HsvColor hsvColor))
{
color = hsvColor.ToRgb();
return true;
}
var knownColor = KnownColors.GetKnownColor(s);
if (knownColor != KnownColor.None)
{
color = knownColor.ToColor();
return true;
}
@ -172,21 +217,52 @@ namespace Avalonia.Media
if (s.Length == 0)
{
color = default;
return false;
}
if (s[0] == '#')
if (s[0] == '#' &&
TryParseHexFormat(s, out color))
{
return true;
}
// At this point all parsing uses strings
var str = s.ToString();
if (s.Length > 5 &&
(s[0] == 'r' || s[0] == 'R') &&
(s[1] == 'g' || s[1] == 'G') &&
(s[2] == 'b' || s[2] == 'B') &&
TryParseCssFormat(str, out color))
{
return true;
}
if (s.Length > 5 &&
(s[0] == 'h' || s[0] == 'H') &&
(s[1] == 's' || s[1] == 'S') &&
(s[2] == 'l' || s[2] == 'L') &&
HslColor.TryParse(str, out HslColor hslColor))
{
return TryParseInternal(s, out color);
color = hslColor.ToRgb();
return true;
}
var knownColor = KnownColors.GetKnownColor(s.ToString());
if (s.Length > 5 &&
(s[0] == 'h' || s[0] == 'H') &&
(s[1] == 's' || s[1] == 'S') &&
(s[2] == 'v' || s[2] == 'V') &&
HsvColor.TryParse(str, out HsvColor hsvColor))
{
color = hsvColor.ToRgb();
return true;
}
var knownColor = KnownColors.GetKnownColor(str);
if (knownColor != KnownColor.None)
{
color = knownColor.ToColor();
return true;
}
@ -195,7 +271,7 @@ namespace Avalonia.Media
return false;
}
private static bool TryParseInternal(ReadOnlySpan<char> s, out Color color)
private static bool TryParseHexFormat(ReadOnlySpan<char> s, out Color color)
{
static bool TryParseCore(ReadOnlySpan<char> input, ref Color color)
{
@ -249,6 +325,91 @@ namespace Avalonia.Media
return TryParseCore(input, ref color);
}
private static bool TryParseCssFormat(string s, out Color color)
{
color = default;
if (s is null)
{
return false;
}
string workingString = s.Trim();
if (workingString.Length == 0 ||
workingString.IndexOf(",", StringComparison.Ordinal) < 0)
{
return false;
}
if (workingString.Length > 6 &&
workingString.StartsWith("rgba(", StringComparison.OrdinalIgnoreCase) &&
workingString.EndsWith(")", StringComparison.Ordinal))
{
workingString = workingString.Substring(5, workingString.Length - 6);
}
if (workingString.Length > 5 &&
workingString.StartsWith("rgb(", StringComparison.OrdinalIgnoreCase) &&
workingString.EndsWith(")", StringComparison.Ordinal))
{
workingString = workingString.Substring(4, workingString.Length - 5);
}
string[] components = workingString.Split(',');
if (components.Length == 3) // RGB
{
if (byte.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out byte red) &&
byte.TryParse(components[1], NumberStyles.Number, CultureInfo.InvariantCulture, out byte green) &&
byte.TryParse(components[2], NumberStyles.Number, CultureInfo.InvariantCulture, out byte blue))
{
color = new Color(0xFF, red, green, blue);
return true;
}
}
else if (components.Length == 4) // RGBA
{
if (byte.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out byte red) &&
byte.TryParse(components[1], NumberStyles.Number, CultureInfo.InvariantCulture, out byte green) &&
byte.TryParse(components[2], NumberStyles.Number, CultureInfo.InvariantCulture, out byte blue) &&
TryInternalParse(components[3], out double alpha))
{
color = new Color((byte)(alpha * 255), red, green, blue);
return true;
}
}
// Local function to specially parse a double value with an optional percentage sign
bool TryInternalParse(string inString, out double outDouble)
{
// The percent sign, if it exists, must be at the end of the number
int percentIndex = inString.IndexOf("%", StringComparison.Ordinal);
if (percentIndex >= 0)
{
var result = double.TryParse(
inString.Substring(0, percentIndex),
NumberStyles.Number,
CultureInfo.InvariantCulture,
out double percentage);
outDouble = percentage / 100.0;
return result;
}
else
{
return double.TryParse(
inString,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out outDouble);
}
}
return false;
}
/// <summary>
/// Returns the string representation of the color.
/// </summary>
@ -272,15 +433,24 @@ namespace Avalonia.Media
return ((uint)A << 24) | ((uint)R << 16) | ((uint)G << 8) | (uint)B;
}
/// <summary>
/// Returns the HSL color model equivalent of this RGB color.
/// </summary>
/// <returns>The HSL equivalent color.</returns>
public HslColor ToHsl()
{
// Don't use the HslColor(Color) constructor to avoid an extra HslColor
return Color.ToHsl(R, G, B, A);
}
/// <summary>
/// Returns the HSV color model equivalent of this RGB color.
/// </summary>
/// <returns>The HSV equivalent color.</returns>
public HsvColor ToHsv()
{
// Use the by-channel conversion method directly for performance
// Don't use the HsvColor(Color) constructor to avoid an extra HsvColor
return HsvColor.FromRgb(R, G, B, A);
return Color.ToHsv(R, G, B, A);
}
/// <inheritdoc/>
@ -289,11 +459,13 @@ namespace Avalonia.Media
return A == other.A && R == other.R && G == other.G && B == other.B;
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is Color other && Equals(other);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
@ -306,11 +478,261 @@ namespace Avalonia.Media
}
}
/// <summary>
/// Converts the given RGB color to its HSL color equivalent.
/// </summary>
/// <param name="color">The color in the RGB color model.</param>
/// <returns>A new <see cref="HslColor"/> equivalent to the given RGBA values.</returns>
public static HslColor ToHsl(Color color)
{
// Normalize RGBA components into the 0..1 range
return Color.ToHsl(
(byteToDouble * color.R),
(byteToDouble * color.G),
(byteToDouble * color.B),
(byteToDouble * color.A));
}
/// <summary>
/// Converts the given RGBA color component values to their HSL color equivalent.
/// </summary>
/// <param name="red">The Red component in the RGB color model.</param>
/// <param name="green">The Green component in the RGB color model.</param>
/// <param name="blue">The Blue component in the RGB color model.</param>
/// <param name="alpha">The Alpha component.</param>
/// <returns>A new <see cref="HslColor"/> equivalent to the given RGBA values.</returns>
public static HslColor ToHsl(
byte red,
byte green,
byte blue,
byte alpha = 0xFF)
{
// Normalize RGBA components into the 0..1 range
return Color.ToHsl(
(byteToDouble * red),
(byteToDouble * green),
(byteToDouble * blue),
(byteToDouble * alpha));
}
/// <summary>
/// Converts the given RGBA color component values to their HSL color equivalent.
/// </summary>
/// <remarks>
/// Warning: No bounds checks or clamping is done on the input component values.
/// This method is for internal-use only and the caller must ensure bounds.
/// </remarks>
/// <param name="r">The Red component in the RGB color model within the range 0..1.</param>
/// <param name="g">The Green component in the RGB color model within the range 0..1.</param>
/// <param name="b">The Blue component in the RGB color model within the range 0..1.</param>
/// <param name="a">The Alpha component in the RGB color model within the range 0..1.</param>
/// <returns>A new <see cref="HslColor"/> equivalent to the given RGBA values.</returns>
internal static HslColor ToHsl(
double r,
double g,
double b,
double a = 1.0)
{
// Note: Conversion code is originally based on ColorHelper in the Windows Community Toolkit (licensed MIT)
// https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp/Helpers/ColorHelper.cs
// It has been modified.
double max = r >= g ? (r >= b ? r : b) : (g >= b ? g : b);
double min = r <= g ? (r <= b ? r : b) : (g <= b ? g : b);
double chroma = max - min;
double h1;
if (chroma == 0)
{
h1 = 0;
}
else if (max == r)
{
// The % operator doesn't do proper modulo on negative
// numbers, so we'll add 6 before using it
h1 = (((g - b) / chroma) + 6) % 6;
}
else if (max == g)
{
h1 = 2 + ((b - r) / chroma);
}
else
{
h1 = 4 + ((r - g) / chroma);
}
double lightness = 0.5 * (max + min);
double saturation = chroma == 0 ? 0 : chroma / (1 - Math.Abs((2 * lightness) - 1));
return new HslColor(a, 60 * h1, saturation, lightness, clampValues: false);
}
/// <summary>
/// Converts the given RGB color to its HSV color equivalent.
/// </summary>
/// <param name="color">The color in the RGB color model.</param>
/// <returns>A new <see cref="HsvColor"/> equivalent to the given RGBA values.</returns>
public static HsvColor ToHsv(Color color)
{
// Normalize RGBA components into the 0..1 range
return Color.ToHsv(
(byteToDouble * color.R),
(byteToDouble * color.G),
(byteToDouble * color.B),
(byteToDouble * color.A));
}
/// <summary>
/// Converts the given RGBA color component values to their HSV color equivalent.
/// </summary>
/// <param name="red">The Red component in the RGB color model.</param>
/// <param name="green">The Green component in the RGB color model.</param>
/// <param name="blue">The Blue component in the RGB color model.</param>
/// <param name="alpha">The Alpha component.</param>
/// <returns>A new <see cref="HsvColor"/> equivalent to the given RGBA values.</returns>
public static HsvColor ToHsv(
byte red,
byte green,
byte blue,
byte alpha = 0xFF)
{
// Normalize RGBA components into the 0..1 range
return Color.ToHsv(
(byteToDouble * red),
(byteToDouble * green),
(byteToDouble * blue),
(byteToDouble * alpha));
}
/// <summary>
/// Converts the given RGBA color component values to their HSV color equivalent.
/// </summary>
/// <remarks>
/// Warning: No bounds checks or clamping is done on the input component values.
/// This method is for internal-use only and the caller must ensure bounds.
/// </remarks>
/// <param name="r">The Red component in the RGB color model within the range 0..1.</param>
/// <param name="g">The Green component in the RGB color model within the range 0..1.</param>
/// <param name="b">The Blue component in the RGB color model within the range 0..1.</param>
/// <param name="a">The Alpha component in the RGB color model within the range 0..1.</param>
/// <returns>A new <see cref="HsvColor"/> equivalent to the given RGBA values.</returns>
internal static HsvColor ToHsv(
double r,
double g,
double b,
double a = 1.0)
{
// Note: Conversion code is originally based on the C++ in WinUI (licensed MIT)
// https://github.com/microsoft/microsoft-ui-xaml/blob/main/dev/Common/ColorConversion.cpp
// This was used because it is the best documented and likely most optimized for performance
// Alpha support was added
double hue;
double saturation;
double value;
double max = r >= g ? (r >= b ? r : b) : (g >= b ? g : b);
double min = r <= g ? (r <= b ? r : b) : (g <= b ? g : b);
// The value, a number between 0 and 1, is the largest of R, G, and B (divided by 255).
// Conceptually speaking, it represents how much color is present.
// If at least one of R, G, B is 255, then there exists as much color as there can be.
// If RGB = (0, 0, 0), then there exists no color at all - a value of zero corresponds
// to black (i.e., the absence of any color).
value = max;
// The "chroma" of the color is a value directly proportional to the extent to which
// the color diverges from greyscale. If, for example, we have RGB = (255, 255, 0),
// then the chroma is maximized - this is a pure yellow, no gray of any kind.
// On the other hand, if we have RGB = (128, 128, 128), then the chroma being zero
// implies that this color is pure greyscale, with no actual hue to be found.
var chroma = max - min;
// If the chrome is zero, then hue is technically undefined - a greyscale color
// has no hue. For the sake of convenience, we'll just set hue to zero, since
// it will be unused in this circumstance. Since the color is purely gray,
// saturation is also equal to zero - you can think of saturation as basically
// a measure of hue intensity, such that no hue at all corresponds to a
// nonexistent intensity.
if (chroma == 0)
{
hue = 0.0;
saturation = 0.0;
}
else
{
// In this block, hue is properly defined, so we'll extract both hue
// and saturation information from the RGB color.
// Hue can be thought of as a cyclical thing, between 0 degrees and 360 degrees.
// A hue of 0 degrees is red; 120 degrees is green; 240 degrees is blue; and 360 is back to red.
// Every other hue is somewhere between either red and green, green and blue, and blue and red,
// so every other hue can be thought of as an angle on this color wheel.
// These if/else statements determines where on this color wheel our color lies.
if (r == max)
{
// If the red channel is the most pronounced channel, then we exist
// somewhere between (-60, 60) on the color wheel - i.e., the section around 0 degrees
// where red dominates. We figure out where in that section we are exactly
// by considering whether the green or the blue channel is greater - by subtracting green from blue,
// then if green is greater, we'll nudge ourselves closer to 60, whereas if blue is greater, then
// we'll nudge ourselves closer to -60. We then divide by chroma (which will actually make the result larger,
// since chroma is a value between 0 and 1) to normalize the value to ensure that we get the right hue
// even if we're very close to greyscale.
hue = 60 * (g - b) / chroma;
}
else if (g == max)
{
// We do the exact same for the case where the green channel is the most pronounced channel,
// only this time we want to see if we should tilt towards the blue direction or the red direction.
// We add 120 to center our value in the green third of the color wheel.
hue = 120 + (60 * (b - r) / chroma);
}
else // blue == max
{
// And we also do the exact same for the case where the blue channel is the most pronounced channel,
// only this time we want to see if we should tilt towards the red direction or the green direction.
// We add 240 to center our value in the blue third of the color wheel.
hue = 240 + (60 * (r - g) / chroma);
}
// Since we want to work within the range [0, 360), we'll add 360 to any value less than zero -
// this will bump red values from within -60 to -1 to 300 to 359. The hue is the same at both values.
if (hue < 0.0)
{
hue += 360.0;
}
// The saturation, our final HSV axis, can be thought of as a value between 0 and 1 indicating how intense our color is.
// To find it, we divide the chroma - the distance between the minimum and the maximum RGB channels - by the maximum channel (i.e., the value).
// This effectively normalizes the chroma - if the maximum is 0.5 and the minimum is 0, the saturation will be (0.5 - 0) / 0.5 = 1,
// meaning that although this color is not as bright as it can be, the dark color is as intense as it possibly could be.
// If, on the other hand, the maximum is 0.5 and the minimum is 0.25, then the saturation will be (0.5 - 0.25) / 0.5 = 0.5,
// meaning that this color is partially washed out.
// A saturation value of 0 corresponds to a greyscale color, one in which the color is *completely* washed out and there is no actual hue.
saturation = chroma / value;
}
return new HsvColor(a, hue, saturation, value, clampValues: false);
}
/// <summary>
/// Indicates whether the values of two specified <see cref="Color"/> objects are equal.
/// </summary>
/// <param name="left">The first object to compare.</param>
/// <param name="right">The second object to compare.</param>
/// <returns>True if left and right are equal; otherwise, false.</returns>
public static bool operator ==(Color left, Color right)
{
return left.Equals(right);
}
/// <summary>
/// Indicates whether the values of two specified <see cref="Color"/> objects are not equal.
/// </summary>
/// <param name="left">The first object to compare.</param>
/// <param name="right">The second object to compare.</param>
/// <returns>True if left and right are not equal; otherwise, false.</returns>
public static bool operator !=(Color left, Color right)
{
return !left.Equals(right);

476
src/Avalonia.Visuals/Media/HslColor.cs

@ -0,0 +1,476 @@
// Color conversion portions of this source file are adapted from the Windows Community Toolkit project.
// (https://github.com/CommunityToolkit/WindowsCommunityToolkit)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Globalization;
using System.Text;
using Avalonia.Utilities;
namespace Avalonia.Media
{
/// <summary>
/// Defines a color using the hue/saturation/lightness (HSL) model.
/// </summary>
#if !BUILDTASK
public
#endif
readonly struct HslColor : IEquatable<HslColor>
{
/// <summary>
/// Initializes a new instance of the <see cref="HslColor"/> struct.
/// </summary>
/// <param name="alpha">The Alpha (transparency) component in the range from 0..1.</param>
/// <param name="hue">The Hue component in the range from 0..360.
/// Note that 360 is equivalent to 0 and will be adjusted automatically.</param>
/// <param name="saturation">The Saturation component in the range from 0..1.</param>
/// <param name="lightness">The Lightness component in the range from 0..1.</param>
public HslColor(
double alpha,
double hue,
double saturation,
double lightness)
{
A = MathUtilities.Clamp(alpha, 0.0, 1.0);
H = MathUtilities.Clamp(hue, 0.0, 360.0);
S = MathUtilities.Clamp(saturation, 0.0, 1.0);
L = MathUtilities.Clamp(lightness, 0.0, 1.0);
// The maximum value of Hue is technically 360 minus epsilon (just below 360).
// This is because, in a color circle, 360 degrees is equivalent to 0 degrees.
// However, that is too tricky to work with in code and isn't as intuitive.
// Therefore, since 360 == 0, just wrap 360 if needed back to 0.
H = (H == 360.0 ? 0 : H);
}
/// <summary>
/// Initializes a new instance of the <see cref="HslColor"/> struct.
/// </summary>
/// <remarks>
/// This constructor exists only for internal use where performance is critical.
/// Whether or not the component values are in the correct ranges must be known.
/// </remarks>
/// <param name="alpha">The Alpha (transparency) component in the range from 0..1.</param>
/// <param name="hue">The Hue component in the range from 0..360.
/// Note that 360 is equivalent to 0 and will be adjusted automatically.</param>
/// <param name="saturation">The Saturation component in the range from 0..1.</param>
/// <param name="lightness">The Lightness component in the range from 0..1.</param>
/// <param name="clampValues">Whether to clamp component values to their required ranges.</param>
internal HslColor(
double alpha,
double hue,
double saturation,
double lightness,
bool clampValues)
{
if (clampValues)
{
A = MathUtilities.Clamp(alpha, 0.0, 1.0);
H = MathUtilities.Clamp(hue, 0.0, 360.0);
S = MathUtilities.Clamp(saturation, 0.0, 1.0);
L = MathUtilities.Clamp(lightness, 0.0, 1.0);
// See comments in constructor above
H = (H == 360.0 ? 0 : H);
}
else
{
A = alpha;
H = hue;
S = saturation;
L = lightness;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="HslColor"/> struct.
/// </summary>
/// <param name="color">The RGB color to convert to HSL.</param>
public HslColor(Color color)
{
var hsl = Color.ToHsl(color);
A = hsl.A;
H = hsl.H;
S = hsl.S;
L = hsl.L;
}
/// <summary>
/// Gets the Alpha (transparency) component in the range from 0..1.
/// </summary>
public double A { get; }
/// <summary>
/// Gets the Hue component in the range from 0..360.
/// Note that 360 is equivalent to 0 and will be adjusted automatically.
/// </summary>
public double H { get; }
/// <summary>
/// Gets the Saturation component in the range from 0..1.
/// </summary>
public double S { get; }
/// <summary>
/// Gets the Lightness component in the range from 0..1.
/// </summary>
public double L { get; }
/// <inheritdoc/>
public bool Equals(HslColor other)
{
return other.A == A &&
other.H == H &&
other.S == S &&
other.L == L;
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
if (obj is HslColor hslColor)
{
return Equals(hslColor);
}
else
{
return false;
}
}
/// <summary>
/// Gets a hashcode for this object.
/// Hashcode is not guaranteed to be unique.
/// </summary>
/// <returns>The hashcode for this object.</returns>
public override int GetHashCode()
{
// Same algorithm as Color
// This is used instead of HashCode.Combine() due to .NET Standard 2.0 requirements
unchecked
{
int hashCode = A.GetHashCode();
hashCode = (hashCode * 397) ^ H.GetHashCode();
hashCode = (hashCode * 397) ^ S.GetHashCode();
hashCode = (hashCode * 397) ^ L.GetHashCode();
return hashCode;
}
}
/// <summary>
/// Returns the RGB color model equivalent of this HSL color.
/// </summary>
/// <returns>The RGB equivalent color.</returns>
public Color ToRgb()
{
// Use the by-component conversion method directly for performance
return HslColor.ToRgb(H, S, L, A);
}
/// <inheritdoc/>
public override string ToString()
{
var sb = new StringBuilder();
// Use a format similar to CSS. However:
// - To ensure precision is never lost, allow decimal places.
// This is especially important for round-trip serialization.
// - To maintain numerical consistency, do not use percent.
//
// Example:
//
// hsla(hue, saturation, lightness, alpha)
// hsla(230, 1.0, 0.5, 1.0)
//
sb.Append("hsva(");
sb.Append(H.ToString(CultureInfo.InvariantCulture));
sb.Append(", ");
sb.Append(S.ToString(CultureInfo.InvariantCulture));
sb.Append(", ");
sb.Append(L.ToString(CultureInfo.InvariantCulture));
sb.Append(", ");
sb.Append(A.ToString(CultureInfo.InvariantCulture));
sb.Append(')');
return sb.ToString();
}
/// <summary>
/// Parses an HSL color string.
/// </summary>
/// <param name="s">The HSL color string to parse.</param>
/// <returns>The parsed <see cref="HslColor"/>.</returns>
public static HslColor Parse(string s)
{
if (s is null)
{
throw new ArgumentNullException(nameof(s));
}
if (TryParse(s, out HslColor hslColor))
{
return hslColor;
}
throw new FormatException($"Invalid HSL color string: '{s}'.");
}
/// <summary>
/// Parses an HSL color string.
/// </summary>
/// <param name="s">The HSL color string to parse.</param>
/// <param name="hslColor">The parsed <see cref="HslColor"/>.</param>
/// <returns>True if parsing was successful; otherwise, false.</returns>
public static bool TryParse(string s, out HslColor hslColor)
{
hslColor = default;
if (s is null)
{
return false;
}
string workingString = s.Trim();
if (workingString.Length == 0 ||
workingString.IndexOf(",", StringComparison.Ordinal) < 0)
{
return false;
}
if (workingString.Length > 6 &&
workingString.StartsWith("hsla(", StringComparison.OrdinalIgnoreCase) &&
workingString.EndsWith(")", StringComparison.Ordinal))
{
workingString = workingString.Substring(5, workingString.Length - 6);
}
if (workingString.Length > 5 &&
workingString.StartsWith("hsl(", StringComparison.OrdinalIgnoreCase) &&
workingString.EndsWith(")", StringComparison.Ordinal))
{
workingString = workingString.Substring(4, workingString.Length - 5);
}
string[] components = workingString.Split(',');
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))
{
hslColor = new HslColor(1.0, hue, saturation, lightness);
return true;
}
}
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))
{
hslColor = new HslColor(alpha, hue, saturation, lightness);
return true;
}
}
// Local function to specially parse a double value with an optional percentage sign
bool TryInternalParse(string inString, out double outDouble)
{
// The percent sign, if it exists, must be at the end of the number
int percentIndex = inString.IndexOf("%", StringComparison.Ordinal);
if (percentIndex >= 0)
{
var result = double.TryParse(
inString.Substring(0, percentIndex),
NumberStyles.Number,
CultureInfo.InvariantCulture,
out double percentage);
outDouble = percentage / 100.0;
return result;
}
else
{
return double.TryParse(
inString,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out outDouble);
}
}
return false;
}
/// <summary>
/// Creates a new <see cref="HslColor"/> from individual color component values.
/// </summary>
/// <remarks>
/// This exists for symmetry with the <see cref="Color"/> struct; however, the
/// appropriate constructor should commonly be used instead.
/// </remarks>
/// <param name="a">The Alpha (transparency) component in the range from 0..1.</param>
/// <param name="h">The Hue component in the range from 0..360.</param>
/// <param name="s">The Saturation component in the range from 0..1.</param>
/// <param name="l">The Lightness component in the range from 0..1.</param>
/// <returns>A new <see cref="HslColor"/> built from the individual color component values.</returns>
public static HslColor FromAhsl(double a, double h, double s, double l)
{
return new HslColor(a, h, s, l);
}
/// <summary>
/// Creates a new <see cref="HslColor"/> from individual color component values.
/// </summary>
/// <remarks>
/// This exists for symmetry with the <see cref="Color"/> struct; however, the
/// appropriate constructor should commonly be used instead.
/// </remarks>
/// <param name="h">The Hue component in the range from 0..360.</param>
/// <param name="s">The Saturation component in the range from 0..1.</param>
/// <param name="l">The Lightness component in the range from 0..1.</param>
/// <returns>A new <see cref="HslColor"/> built from the individual color component values.</returns>
public static HslColor FromHsl(double h, double s, double l)
{
return new HslColor(1.0, h, s, l);
}
/// <summary>
/// Converts the given HSL color to its RGB color equivalent.
/// </summary>
/// <param name="hslColor">The color in the HSL color model.</param>
/// <returns>A new RGB <see cref="Color"/> equivalent to the given HSLA values.</returns>
public static Color ToRgb(HslColor hslColor)
{
return HslColor.ToRgb(hslColor.H, hslColor.S, hslColor.L, hslColor.A);
}
/// <summary>
/// Converts the given HSLA color component values to their RGB color equivalent.
/// </summary>
/// <param name="hue">The Hue component in the HSL color model in the range from 0..360.</param>
/// <param name="saturation">The Saturation component in the HSL color model in the range from 0..1.</param>
/// <param name="lightness">The Lightness component in the HSL color model in the range from 0..1.</param>
/// <param name="alpha">The Alpha component in the range from 0..1.</param>
/// <returns>A new RGB <see cref="Color"/> equivalent to the given HSLA values.</returns>
public static Color ToRgb(
double hue,
double saturation,
double lightness,
double alpha = 1.0)
{
// Note: Conversion code is originally based on ColorHelper in the Windows Community Toolkit (licensed MIT)
// https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp/Helpers/ColorHelper.cs
// It has been modified to ensure input ranges and not throw exceptions.
// We want the hue to be between 0 and 359,
// so we first ensure that that's the case.
while (hue >= 360.0)
{
hue -= 360.0;
}
while (hue < 0.0)
{
hue += 360.0;
}
// We similarly clamp saturation, lightness and alpha between 0 and 1.
saturation = saturation < 0.0 ? 0.0 : saturation;
saturation = saturation > 1.0 ? 1.0 : saturation;
lightness = lightness < 0.0 ? 0.0 : lightness;
lightness = lightness > 1.0 ? 1.0 : lightness;
alpha = alpha < 0.0 ? 0.0 : alpha;
alpha = alpha > 1.0 ? 1.0 : alpha;
double chroma = (1 - Math.Abs((2 * lightness) - 1)) * saturation;
double h1 = hue / 60;
double x = chroma * (1 - Math.Abs((h1 % 2) - 1));
double m = lightness - (0.5 * chroma);
double r1, g1, b1;
if (h1 < 1)
{
r1 = chroma;
g1 = x;
b1 = 0;
}
else if (h1 < 2)
{
r1 = x;
g1 = chroma;
b1 = 0;
}
else if (h1 < 3)
{
r1 = 0;
g1 = chroma;
b1 = x;
}
else if (h1 < 4)
{
r1 = 0;
g1 = x;
b1 = chroma;
}
else if (h1 < 5)
{
r1 = x;
g1 = 0;
b1 = chroma;
}
else
{
r1 = chroma;
g1 = 0;
b1 = x;
}
return Color.FromArgb(
(byte)Math.Round(255 * alpha),
(byte)Math.Round(255 * (r1 + m)),
(byte)Math.Round(255 * (g1 + m)),
(byte)Math.Round(255 * (b1 + m)));
}
/// <summary>
/// Indicates whether the values of two specified <see cref="HslColor"/> objects are equal.
/// </summary>
/// <param name="left">The first object to compare.</param>
/// <param name="right">The second object to compare.</param>
/// <returns>True if left and right are equal; otherwise, false.</returns>
public static bool operator ==(HslColor left, HslColor right)
{
return left.Equals(right);
}
/// <summary>
/// Indicates whether the values of two specified <see cref="HslColor"/> objects are not equal.
/// </summary>
/// <param name="left">The first object to compare.</param>
/// <param name="right">The second object to compare.</param>
/// <returns>True if left and right are not equal; otherwise, false.</returns>
public static bool operator !=(HslColor left, HslColor right)
{
return !(left == right);
}
/// <summary>
/// Explicit conversion from an <see cref="HslColor"/> to a <see cref="Color"/>.
/// </summary>
/// <param name="hslColor">The <see cref="HslColor"/> to convert.</param>
public static explicit operator Color(HslColor hslColor)
{
return hslColor.ToRgb();
}
}
}

259
src/Avalonia.Visuals/Media/HsvColor.cs

@ -1,6 +1,6 @@
// Color conversion portions of this source file are adapted from the WinUI project.
// (https://github.com/microsoft/microsoft-ui-xaml)
//
// Color conversion portions of this source file are adapted from the WinUI project.
// (https://github.com/microsoft/microsoft-ui-xaml)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
@ -21,11 +21,11 @@ namespace Avalonia.Media
/// <summary>
/// Initializes a new instance of the <see cref="HsvColor"/> struct.
/// </summary>
/// <param name="alpha">The Alpha (transparency) channel value in the range from 0..1.</param>
/// <param name="hue">The Hue channel value in the range from 0..360.
/// <param name="alpha">The Alpha (transparency) component in the range from 0..1.</param>
/// <param name="hue">The Hue component in the range from 0..360.
/// Note that 360 is equivalent to 0 and will be adjusted automatically.</param>
/// <param name="saturation">The Saturation channel value in the range from 0..1.</param>
/// <param name="value">The Value channel value in the range from 0..1.</param>
/// <param name="saturation">The Saturation component in the range from 0..1.</param>
/// <param name="value">The Value component in the range from 0..1.</param>
public HsvColor(
double alpha,
double hue,
@ -49,14 +49,14 @@ namespace Avalonia.Media
/// </summary>
/// <remarks>
/// This constructor exists only for internal use where performance is critical.
/// Whether or not the channel values are in the correct ranges must be known.
/// Whether or not the component values are in the correct ranges must be known.
/// </remarks>
/// <param name="alpha">The Alpha (transparency) channel value in the range from 0..1.</param>
/// <param name="hue">The Hue channel value in the range from 0..360.
/// <param name="alpha">The Alpha (transparency) component in the range from 0..1.</param>
/// <param name="hue">The Hue component in the range from 0..360.
/// Note that 360 is equivalent to 0 and will be adjusted automatically.</param>
/// <param name="saturation">The Saturation channel value in the range from 0..1.</param>
/// <param name="value">The Value channel value in the range from 0..1.</param>
/// <param name="clampValues">Whether to clamp channel values to their required ranges.</param>
/// <param name="saturation">The Saturation component in the range from 0..1.</param>
/// <param name="value">The Value component in the range from 0..1.</param>
/// <param name="clampValues">Whether to clamp component values to their required ranges.</param>
internal HsvColor(
double alpha,
double hue,
@ -89,7 +89,7 @@ namespace Avalonia.Media
/// <param name="color">The RGB color to convert to HSV.</param>
public HsvColor(Color color)
{
var hsv = HsvColor.FromRgb(color);
var hsv = Color.ToHsv(color);
A = hsv.A;
H = hsv.H;
@ -98,23 +98,23 @@ namespace Avalonia.Media
}
/// <summary>
/// Gets the Alpha (transparency) channel value in the range from 0..1.
/// Gets the Alpha (transparency) component in the range from 0..1.
/// </summary>
public double A { get; }
/// <summary>
/// Gets the Hue channel value in the range from 0..360.
/// Gets the Hue component in the range from 0..360.
/// Note that 360 is equivalent to 0 and will be adjusted automatically.
/// </summary>
public double H { get; }
/// <summary>
/// Gets the Saturation channel value in the range from 0..1.
/// Gets the Saturation component in the range from 0..1.
/// </summary>
public double S { get; }
/// <summary>
/// Gets the Value channel value in the range from 0..1.
/// Gets the Value component in the range from 0..1.
/// </summary>
public double V { get; }
@ -165,7 +165,7 @@ namespace Avalonia.Media
/// <returns>The RGB equivalent color.</returns>
public Color ToRgb()
{
// Use the by-channel conversion method directly for performance
// Use the by-component conversion method directly for performance
return HsvColor.ToRgb(H, S, V, A);
}
@ -174,26 +174,16 @@ namespace Avalonia.Media
{
var sb = new StringBuilder();
// Use a format similar to HSL in HTML/CSS "hsla(0, 100%, 50%, 0.5)"
//
// However:
// - To ensure precision is never lost, allow decimal places
// - To maintain numerical consistency do not use percent
// Use a format similar to CSS. However:
// - To ensure precision is never lost, allow decimal places.
// This is especially important for round-trip serialization.
// - To maintain numerical consistency, do not use percent.
//
// Example:
//
// hsva(hue, saturation, value, alpha)
// hsva(230, 1.0, 0.5, 1.0)
//
// Where:
//
// hue : double from 0 to 360
// saturation : double from 0 to 1
// (HTML uses a percentage)
// value : double from 0 to 1
// (HTML uses a percentage)
// alpha : double from 0 to 1
// (HTML does not use a percentage for alpha)
sb.Append("hsva(");
sb.Append(H.ToString(CultureInfo.InvariantCulture));
@ -270,8 +260,8 @@ namespace Avalonia.Media
if (components.Length == 3) // HSV
{
if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
double.TryParse(components[1], NumberStyles.Number, CultureInfo.InvariantCulture, out double saturation) &&
double.TryParse(components[2], NumberStyles.Number, CultureInfo.InvariantCulture, out double value))
TryInternalParse(components[1], out double saturation) &&
TryInternalParse(components[2], out double value))
{
hsvColor = new HsvColor(1.0, hue, saturation, value);
return true;
@ -280,37 +270,80 @@ namespace Avalonia.Media
else if (components.Length == 4) // HSVA
{
if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
double.TryParse(components[1], NumberStyles.Number, CultureInfo.InvariantCulture, out double saturation) &&
double.TryParse(components[2], NumberStyles.Number, CultureInfo.InvariantCulture, out double value) &&
double.TryParse(components[3], NumberStyles.Number, CultureInfo.InvariantCulture, out double alpha))
TryInternalParse(components[1], out double saturation) &&
TryInternalParse(components[2], out double value) &&
TryInternalParse(components[3], out double alpha))
{
hsvColor = new HsvColor(alpha, hue, saturation, value);
return true;
}
}
// Local function to specially parse a double value with an optional percentage sign
bool TryInternalParse(string inString, out double outDouble)
{
// The percent sign, if it exists, must be at the end of the number
int percentIndex = inString.IndexOf("%", StringComparison.Ordinal);
if (percentIndex >= 0)
{
var result = double.TryParse(
inString.Substring(0, percentIndex),
NumberStyles.Number,
CultureInfo.InvariantCulture,
out double percentage);
outDouble = percentage / 100.0;
return result;
}
else
{
return double.TryParse(
inString,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out outDouble);
}
}
return false;
}
/// <summary>
/// Creates a new <see cref="HsvColor"/> from individual color channel values.
/// Creates a new <see cref="HsvColor"/> from individual color component values.
/// </summary>
/// <remarks>
/// This exists for symmetry with the <see cref="Color"/> struct; however, the
/// appropriate constructor should commonly be used instead.
/// </remarks>
/// <param name="a">The Alpha (transparency) channel value in the range from 0..1.</param>
/// <param name="h">The Hue channel value in the range from 0..360.</param>
/// <param name="s">The Saturation channel value in the range from 0..1.</param>
/// <param name="v">The Value channel value in the range from 0..1.</param>
/// <returns>A new <see cref="HsvColor"/> built from the individual color channel values.</returns>
/// <param name="a">The Alpha (transparency) component in the range from 0..1.</param>
/// <param name="h">The Hue component in the range from 0..360.</param>
/// <param name="s">The Saturation component in the range from 0..1.</param>
/// <param name="v">The Value component in the range from 0..1.</param>
/// <returns>A new <see cref="HsvColor"/> built from the individual color component values.</returns>
public static HsvColor FromAhsv(double a, double h, double s, double v)
{
return new HsvColor(a, h, s, v);
}
/// <summary>
/// Converts the given HSV color to it's RGB color equivalent.
/// Creates a new <see cref="HsvColor"/> from individual color component values.
/// </summary>
/// <remarks>
/// This exists for symmetry with the <see cref="Color"/> struct; however, the
/// appropriate constructor should commonly be used instead.
/// </remarks>
/// <param name="h">The Hue component in the range from 0..360.</param>
/// <param name="s">The Saturation component in the range from 0..1.</param>
/// <param name="v">The Value component in the range from 0..1.</param>
/// <returns>A new <see cref="HsvColor"/> built from the individual color component values.</returns>
public static HsvColor FromHsv(double h, double s, double v)
{
return new HsvColor(1.0, h, s, v);
}
/// <summary>
/// Converts the given HSV color to its RGB color equivalent.
/// </summary>
/// <param name="hsvColor">The color in the HSV color model.</param>
/// <returns>A new RGB <see cref="Color"/> equivalent to the given HSVA values.</returns>
@ -320,12 +353,12 @@ namespace Avalonia.Media
}
/// <summary>
/// Converts the given HSVA color channel values to it's RGB color equivalent.
/// Converts the given HSVA color component values to their RGB color equivalent.
/// </summary>
/// <param name="hue">The hue channel value in the HSV color model in the range from 0..360.</param>
/// <param name="saturation">The saturation channel value in the HSV color model in the range from 0..1.</param>
/// <param name="value">The value channel value in the HSV color model in the range from 0..1.</param>
/// <param name="alpha">The alpha channel value in the range from 0..1.</param>
/// <param name="hue">The Hue component in the HSV color model in the range from 0..360.</param>
/// <param name="saturation">The Saturation component in the HSV color model in the range from 0..1.</param>
/// <param name="value">The Value component in the HSV color model in the range from 0..1.</param>
/// <param name="alpha">The Alpha component in the range from 0..1.</param>
/// <returns>A new RGB <see cref="Color"/> equivalent to the given HSVA values.</returns>
public static Color ToRgb(
double hue,
@ -336,7 +369,7 @@ namespace Avalonia.Media
// Note: Conversion code is originally based on the C++ in WinUI (licensed MIT)
// https://github.com/microsoft/microsoft-ui-xaml/blob/main/dev/Common/ColorConversion.cpp
// This was used because it is the best documented and likely most optimized for performance
// Alpha channel support was added
// Alpha support was added
// We want the hue to be between 0 and 359,
// so we first ensure that that's the case.
@ -457,130 +490,6 @@ namespace Avalonia.Media
(byte)Math.Round(b * 255));
}
/// <summary>
/// Converts the given RGB color to it's HSV color equivalent.
/// </summary>
/// <param name="color">The color in the RGB color model.</param>
/// <returns>A new <see cref="HsvColor"/> equivalent to the given RGBA values.</returns>
public static HsvColor FromRgb(Color color)
{
return HsvColor.FromRgb(color.R, color.G, color.B, color.A);
}
/// <summary>
/// Converts the given RGBA color channel values to it's HSV color equivalent.
/// </summary>
/// <param name="red">The red channel value in the RGB color model.</param>
/// <param name="green">The green channel value in the RGB color model.</param>
/// <param name="blue">The blue channel value in the RGB color model.</param>
/// <param name="alpha">The alpha channel value.</param>
/// <returns>A new <see cref="HsvColor"/> equivalent to the given RGBA values.</returns>
public static HsvColor FromRgb(
byte red,
byte green,
byte blue,
byte alpha = 0xFF)
{
// Note: Conversion code is originally based on the C++ in WinUI (licensed MIT)
// https://github.com/microsoft/microsoft-ui-xaml/blob/main/dev/Common/ColorConversion.cpp
// This was used because it is the best documented and likely most optimized for performance
// Alpha channel support was added
// Normalize RGBA channel values into the 0..1 range used by this algorithm
double r = red / 255.0;
double g = green / 255.0;
double b = blue / 255.0;
double a = alpha / 255.0;
double hue;
double saturation;
double value;
double max = r >= g ? (r >= b ? r : b) : (g >= b ? g : b);
double min = r <= g ? (r <= b ? r : b) : (g <= b ? g : b);
// The value, a number between 0 and 1, is the largest of R, G, and B (divided by 255).
// Conceptually speaking, it represents how much color is present.
// If at least one of R, G, B is 255, then there exists as much color as there can be.
// If RGB = (0, 0, 0), then there exists no color at all - a value of zero corresponds
// to black (i.e., the absence of any color).
value = max;
// The "chroma" of the color is a value directly proportional to the extent to which
// the color diverges from greyscale. If, for example, we have RGB = (255, 255, 0),
// then the chroma is maximized - this is a pure yellow, no gray of any kind.
// On the other hand, if we have RGB = (128, 128, 128), then the chroma being zero
// implies that this color is pure greyscale, with no actual hue to be found.
var chroma = max - min;
// If the chrome is zero, then hue is technically undefined - a greyscale color
// has no hue. For the sake of convenience, we'll just set hue to zero, since
// it will be unused in this circumstance. Since the color is purely gray,
// saturation is also equal to zero - you can think of saturation as basically
// a measure of hue intensity, such that no hue at all corresponds to a
// nonexistent intensity.
if (chroma == 0)
{
hue = 0.0;
saturation = 0.0;
}
else
{
// In this block, hue is properly defined, so we'll extract both hue
// and saturation information from the RGB color.
// Hue can be thought of as a cyclical thing, between 0 degrees and 360 degrees.
// A hue of 0 degrees is red; 120 degrees is green; 240 degrees is blue; and 360 is back to red.
// Every other hue is somewhere between either red and green, green and blue, and blue and red,
// so every other hue can be thought of as an angle on this color wheel.
// These if/else statements determines where on this color wheel our color lies.
if (r == max)
{
// If the red channel is the most pronounced channel, then we exist
// somewhere between (-60, 60) on the color wheel - i.e., the section around 0 degrees
// where red dominates. We figure out where in that section we are exactly
// by considering whether the green or the blue channel is greater - by subtracting green from blue,
// then if green is greater, we'll nudge ourselves closer to 60, whereas if blue is greater, then
// we'll nudge ourselves closer to -60. We then divide by chroma (which will actually make the result larger,
// since chroma is a value between 0 and 1) to normalize the value to ensure that we get the right hue
// even if we're very close to greyscale.
hue = 60 * (g - b) / chroma;
}
else if (g == max)
{
// We do the exact same for the case where the green channel is the most pronounced channel,
// only this time we want to see if we should tilt towards the blue direction or the red direction.
// We add 120 to center our value in the green third of the color wheel.
hue = 120 + (60 * (b - r) / chroma);
}
else // blue == max
{
// And we also do the exact same for the case where the blue channel is the most pronounced channel,
// only this time we want to see if we should tilt towards the red direction or the green direction.
// We add 240 to center our value in the blue third of the color wheel.
hue = 240 + (60 * (r - g) / chroma);
}
// Since we want to work within the range [0, 360), we'll add 360 to any value less than zero -
// this will bump red values from within -60 to -1 to 300 to 359. The hue is the same at both values.
if (hue < 0.0)
{
hue += 360.0;
}
// The saturation, our final HSV axis, can be thought of as a value between 0 and 1 indicating how intense our color is.
// To find it, we divide the chroma - the distance between the minimum and the maximum RGB channels - by the maximum channel (i.e., the value).
// This effectively normalizes the chroma - if the maximum is 0.5 and the minimum is 0, the saturation will be (0.5 - 0) / 0.5 = 1,
// meaning that although this color is not as bright as it can be, the dark color is as intense as it possibly could be.
// If, on the other hand, the maximum is 0.5 and the minimum is 0.25, then the saturation will be (0.5 - 0.25) / 0.5 = 0.5,
// meaning that this color is partially washed out.
// A saturation value of 0 corresponds to a greyscale color, one in which the color is *completely* washed out and there is no actual hue.
saturation = chroma / value;
}
return new HsvColor(a, hue, saturation, value, false);
}
/// <summary>
/// Indicates whether the values of two specified <see cref="HsvColor"/> objects are equal.
/// </summary>

2
tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

@ -19,7 +19,7 @@
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />

149
tests/Avalonia.Benchmarks/Base/AvaloniaObject_Binding.cs

@ -0,0 +1,149 @@
using System.Runtime.CompilerServices;
using Avalonia.Data;
using BenchmarkDotNet.Attributes;
#nullable enable
namespace Avalonia.Benchmarks.Base
{
[MemoryDiagnoser]
public class AvaloniaObject_Binding
{
private static TestClass _target = null!;
private static TestBindingObservable<string?> s_stringSource = new();
private static TestBindingObservable<Struct1> s_struct1Source = new();
private static TestBindingObservable<Struct2> s_struct2Source = new();
private static TestBindingObservable<Struct3> s_struct3Source = new();
private static TestBindingObservable<Struct4> s_struct4Source = new();
private static TestBindingObservable<Struct5> s_struct5Source = new();
private static TestBindingObservable<Struct6> s_struct6Source = new();
private static TestBindingObservable<Struct7> s_struct7Source = new();
private static TestBindingObservable<Struct8> s_struct8Source = new();
public AvaloniaObject_Binding()
{
RuntimeHelpers.RunClassConstructor(typeof(TestClass).TypeHandle);
}
[GlobalSetup]
public void Setup()
{
_target = new TestClass();
}
[Benchmark]
public void Setup_Dispose_LocalValue_Bindings()
{
var target = _target;
for (var i = 0; i < 100; ++i)
{
using var s0 = target.Bind(TestClass.StringProperty, s_stringSource);
using var s1 = target.Bind(TestClass.Struct1Property, s_struct1Source);
using var s2 = target.Bind(TestClass.Struct2Property, s_struct2Source);
using var s3 = target.Bind(TestClass.Struct3Property, s_struct3Source);
using var s4 = target.Bind(TestClass.Struct4Property, s_struct4Source);
using var s5 = target.Bind(TestClass.Struct5Property, s_struct5Source);
using var s6 = target.Bind(TestClass.Struct6Property, s_struct6Source);
using var s7 = target.Bind(TestClass.Struct7Property, s_struct7Source);
using var s8 = target.Bind(TestClass.Struct8Property, s_struct8Source);
}
}
[Benchmark]
public void Fire_LocalValue_Bindings()
{
var target = _target;
using var s0 = target.Bind(TestClass.StringProperty, s_stringSource);
using var s1 = target.Bind(TestClass.Struct1Property, s_struct1Source);
using var s2 = target.Bind(TestClass.Struct2Property, s_struct2Source);
using var s3 = target.Bind(TestClass.Struct3Property, s_struct3Source);
using var s4 = target.Bind(TestClass.Struct4Property, s_struct4Source);
using var s5 = target.Bind(TestClass.Struct5Property, s_struct5Source);
using var s6 = target.Bind(TestClass.Struct6Property, s_struct6Source);
using var s7 = target.Bind(TestClass.Struct7Property, s_struct7Source);
using var s8 = target.Bind(TestClass.Struct8Property, s_struct8Source);
for (var i = 0; i < 100; ++i)
{
s_stringSource.OnNext(i.ToString());
s_struct1Source.OnNext(new(i + 1));
s_struct2Source.OnNext(new(i + 1));
s_struct3Source.OnNext(new(i + 1));
s_struct4Source.OnNext(new(i + 1));
s_struct5Source.OnNext(new(i + 1));
s_struct6Source.OnNext(new(i + 1));
s_struct7Source.OnNext(new(i + 1));
s_struct8Source.OnNext(new(i + 1));
}
}
[GlobalSetup(Target = nameof(Fire_LocalValue_Bindings_With_Style_Values))]
public void SetupStyleValues()
{
_target = new TestClass();
_target.SetValue(TestClass.StringProperty, "foo", BindingPriority.Style);
_target.SetValue(TestClass.Struct1Property, new(), BindingPriority.Style);
_target.SetValue(TestClass.Struct2Property, new(), BindingPriority.Style);
_target.SetValue(TestClass.Struct3Property, new(), BindingPriority.Style);
_target.SetValue(TestClass.Struct4Property, new(), BindingPriority.Style);
_target.SetValue(TestClass.Struct5Property, new(), BindingPriority.Style);
_target.SetValue(TestClass.Struct6Property, new(), BindingPriority.Style);
_target.SetValue(TestClass.Struct7Property, new(), BindingPriority.Style);
_target.SetValue(TestClass.Struct8Property, new(), BindingPriority.Style);
}
[Benchmark]
public void Fire_LocalValue_Bindings_With_Style_Values()
{
var target = _target;
using var s0 = target.Bind(TestClass.StringProperty, s_stringSource);
using var s1 = target.Bind(TestClass.Struct1Property, s_struct1Source);
using var s2 = target.Bind(TestClass.Struct2Property, s_struct2Source);
using var s3 = target.Bind(TestClass.Struct3Property, s_struct3Source);
using var s4 = target.Bind(TestClass.Struct4Property, s_struct4Source);
using var s5 = target.Bind(TestClass.Struct5Property, s_struct5Source);
using var s6 = target.Bind(TestClass.Struct6Property, s_struct6Source);
using var s7 = target.Bind(TestClass.Struct7Property, s_struct7Source);
using var s8 = target.Bind(TestClass.Struct8Property, s_struct8Source);
for (var i = 0; i < 100; ++i)
{
s_stringSource.OnNext(i.ToString());
s_struct1Source.OnNext(new(i + 1));
s_struct2Source.OnNext(new(i + 1));
s_struct3Source.OnNext(new(i + 1));
s_struct4Source.OnNext(new(i + 1));
s_struct5Source.OnNext(new(i + 1));
s_struct6Source.OnNext(new(i + 1));
s_struct7Source.OnNext(new(i + 1));
s_struct8Source.OnNext(new(i + 1));
}
}
private class TestClass : AvaloniaObject
{
public static readonly StyledProperty<string?> StringProperty =
AvaloniaProperty.Register<TestClass, string?>("String");
public static readonly StyledProperty<Struct1> Struct1Property =
AvaloniaProperty.Register<TestClass, Struct1>("Struct1");
public static readonly StyledProperty<Struct2> Struct2Property =
AvaloniaProperty.Register<TestClass, Struct2>("Struct2");
public static readonly StyledProperty<Struct3> Struct3Property =
AvaloniaProperty.Register<TestClass, Struct3>("Struct3");
public static readonly StyledProperty<Struct4> Struct4Property =
AvaloniaProperty.Register<TestClass, Struct4>("Struct4");
public static readonly StyledProperty<Struct5> Struct5Property =
AvaloniaProperty.Register<TestClass, Struct5>("Struct5");
public static readonly StyledProperty<Struct6> Struct6Property =
AvaloniaProperty.Register<TestClass, Struct6>("Struct6");
public static readonly StyledProperty<Struct7> Struct7Property =
AvaloniaProperty.Register<TestClass, Struct7>("Struct7");
public static readonly StyledProperty<Struct8> Struct8Property =
AvaloniaProperty.Register<TestClass, Struct8>("Struct8");
}
}
}

81
tests/Avalonia.Benchmarks/Base/AvaloniaObject_Construct.cs

@ -0,0 +1,81 @@
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
#nullable enable
namespace Avalonia.Benchmarks.Base
{
[MemoryDiagnoser]
public class AvaloniaObject_Construct
{
public AvaloniaObject_Construct()
{
RuntimeHelpers.RunClassConstructor(typeof(TestClass).TypeHandle);
}
[Benchmark(Baseline = true)]
public void ConstructClrObject_And_Set_Values()
{
var target = new BaselineTestClass();
target.StringProperty = "foo";
target.Struct1Property = new(1);
target.Struct2Property = new(1);
target.Struct3Property = new(1);
target.Struct4Property = new(1);
target.Struct5Property = new(1);
target.Struct6Property = new(1);
target.Struct7Property = new(1);
target.Struct8Property = new(1);
}
[Benchmark]
public void Construct_And_Set_Values()
{
var target = new TestClass();
target.SetValue(TestClass.StringProperty, "foo");
target.SetValue(TestClass.Struct1Property, new(1));
target.SetValue(TestClass.Struct2Property, new(1));
target.SetValue(TestClass.Struct3Property, new(1));
target.SetValue(TestClass.Struct4Property, new(1));
target.SetValue(TestClass.Struct5Property, new(1));
target.SetValue(TestClass.Struct6Property, new(1));
target.SetValue(TestClass.Struct7Property, new(1));
target.SetValue(TestClass.Struct8Property, new(1));
}
private class TestClass : AvaloniaObject
{
public static readonly StyledProperty<string> StringProperty =
AvaloniaProperty.Register<TestClass, string>("String");
public static readonly StyledProperty<Struct1> Struct1Property =
AvaloniaProperty.Register<TestClass, Struct1>("Struct1");
public static readonly StyledProperty<Struct2> Struct2Property =
AvaloniaProperty.Register<TestClass, Struct2>("Struct2");
public static readonly StyledProperty<Struct3> Struct3Property =
AvaloniaProperty.Register<TestClass, Struct3>("Struct3");
public static readonly StyledProperty<Struct4> Struct4Property =
AvaloniaProperty.Register<TestClass, Struct4>("Struct4");
public static readonly StyledProperty<Struct5> Struct5Property =
AvaloniaProperty.Register<TestClass, Struct5>("Struct5");
public static readonly StyledProperty<Struct6> Struct6Property =
AvaloniaProperty.Register<TestClass, Struct6>("Struct6");
public static readonly StyledProperty<Struct7> Struct7Property =
AvaloniaProperty.Register<TestClass, Struct7>("Struct7");
public static readonly StyledProperty<Struct8> Struct8Property =
AvaloniaProperty.Register<TestClass, Struct8>("Struct8");
}
private class BaselineTestClass
{
public string? StringProperty { get; set; }
public Struct1 Struct1Property { get; set; }
public Struct2 Struct2Property { get; set; }
public Struct3 Struct3Property { get; set; }
public Struct4 Struct4Property { get; set; }
public Struct5 Struct5Property { get; set; }
public Struct6 Struct6Property { get; set; }
public Struct7 Struct7Property { get; set; }
public Struct8 Struct8Property { get; set; }
}
}
}

171
tests/Avalonia.Benchmarks/Base/AvaloniaObject_GetObservable.cs

@ -0,0 +1,171 @@
using System;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
#nullable enable
namespace Avalonia.Benchmarks.Base
{
[MemoryDiagnoser]
public class AvaloniaObject_GetObservable
{
private TestClass _target = null!;
public static int result;
public AvaloniaObject_GetObservable()
{
RuntimeHelpers.RunClassConstructor(typeof(TestClass).TypeHandle);
}
[GlobalSetup]
public void Setup()
{
_target = new();
}
[Benchmark(Baseline = true)]
public void PropertyChangedSubscription()
{
var target = _target;
static void ChangeHandler(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == TestClass.StringProperty)
{
var ev = (AvaloniaPropertyChangedEventArgs<string?>)e;
result += ev.NewValue.Value?.Length ?? 0;
}
else if (e.Property == TestClass.Struct1Property)
{
var ev = (AvaloniaPropertyChangedEventArgs<Struct1>)e;
result += ev.NewValue.Value.Int1;
}
else if (e.Property == TestClass.Struct2Property)
{
var ev = (AvaloniaPropertyChangedEventArgs<Struct2>)e;
result += ev.NewValue.Value.Int1;
}
else if (e.Property == TestClass.Struct3Property)
{
var ev = (AvaloniaPropertyChangedEventArgs<Struct3>)e;
result += ev.NewValue.Value.Int1;
}
else if (e.Property == TestClass.Struct4Property)
{
var ev = (AvaloniaPropertyChangedEventArgs<Struct4>)e;
result += ev.NewValue.Value.Int1;
}
else if (e.Property == TestClass.Struct5Property)
{
var ev = (AvaloniaPropertyChangedEventArgs<Struct5>)e;
result += ev.NewValue.Value.Int1;
}
else if (e.Property == TestClass.Struct6Property)
{
var ev = (AvaloniaPropertyChangedEventArgs<Struct6>)e;
result += ev.NewValue.Value.Int1;
}
else if (e.Property == TestClass.Struct7Property)
{
var ev = (AvaloniaPropertyChangedEventArgs<Struct7>)e;
result += ev.NewValue.Value.Int1;
}
else if (e.Property == TestClass.Struct8Property)
{
var ev = (AvaloniaPropertyChangedEventArgs<Struct8>)e;
result += ev.NewValue.Value.Int1;
}
}
target.PropertyChanged += ChangeHandler;
// GetObservable fires with the initial value so to compare like-for-like we also need
// to get the initial value here.
result += target.GetValue(TestClass.StringProperty)?.Length ?? 0;
result += target.GetValue(TestClass.Struct1Property).Int1;
result += target.GetValue(TestClass.Struct2Property).Int1;
result += target.GetValue(TestClass.Struct3Property).Int1;
result += target.GetValue(TestClass.Struct4Property).Int1;
result += target.GetValue(TestClass.Struct5Property).Int1;
result += target.GetValue(TestClass.Struct6Property).Int1;
result += target.GetValue(TestClass.Struct7Property).Int1;
result += target.GetValue(TestClass.Struct8Property).Int1;
for (var i = 0; i < 100; ++i)
{
target.SetValue(TestClass.StringProperty, "foo" + i);
target.SetValue(TestClass.Struct1Property, new(i + 1));
target.SetValue(TestClass.Struct2Property, new(i + 1));
target.SetValue(TestClass.Struct3Property, new(i + 1));
target.SetValue(TestClass.Struct4Property, new(i + 1));
target.SetValue(TestClass.Struct5Property, new(i + 1));
target.SetValue(TestClass.Struct6Property, new(i + 1));
target.SetValue(TestClass.Struct7Property, new(i + 1));
target.SetValue(TestClass.Struct8Property, new(i + 1));
}
target.PropertyChanged -= ChangeHandler;
}
[Benchmark]
public void GetObservables()
{
var target = _target;
var sub1 = target.GetObservable(TestClass.StringProperty).Subscribe(x => result += x?.Length ?? 0);
var sub2 = target.GetObservable(TestClass.Struct1Property).Subscribe(x => result += x.Int1);
var sub3 = target.GetObservable(TestClass.Struct2Property).Subscribe(x => result += x.Int1);
var sub4 = target.GetObservable(TestClass.Struct3Property).Subscribe(x => result += x.Int1);
var sub5 = target.GetObservable(TestClass.Struct4Property).Subscribe(x => result += x.Int1);
var sub6 = target.GetObservable(TestClass.Struct5Property).Subscribe(x => result += x.Int1);
var sub7 = target.GetObservable(TestClass.Struct6Property).Subscribe(x => result += x.Int1);
var sub8 = target.GetObservable(TestClass.Struct7Property).Subscribe(x => result += x.Int1);
var sub9 = target.GetObservable(TestClass.Struct8Property).Subscribe(x => result += x.Int1);
for (var i = 0; i < 100; ++i)
{
target.SetValue(TestClass.StringProperty, "foo" + i);
target.SetValue(TestClass.Struct1Property, new(i + 1));
target.SetValue(TestClass.Struct2Property, new(i + 1));
target.SetValue(TestClass.Struct3Property, new(i + 1));
target.SetValue(TestClass.Struct4Property, new(i + 1));
target.SetValue(TestClass.Struct5Property, new(i + 1));
target.SetValue(TestClass.Struct6Property, new(i + 1));
target.SetValue(TestClass.Struct7Property, new(i + 1));
target.SetValue(TestClass.Struct8Property, new(i + 1));
}
sub1.Dispose();
sub2.Dispose();
sub3.Dispose();
sub4.Dispose();
sub5.Dispose();
sub6.Dispose();
sub7.Dispose();
sub8.Dispose();
sub9.Dispose();
}
private class TestClass : AvaloniaObject
{
public static readonly StyledProperty<string> StringProperty =
AvaloniaProperty.Register<TestClass, string>("String");
public static readonly StyledProperty<Struct1> Struct1Property =
AvaloniaProperty.Register<TestClass, Struct1>("Struct1");
public static readonly StyledProperty<Struct2> Struct2Property =
AvaloniaProperty.Register<TestClass, Struct2>("Struct2");
public static readonly StyledProperty<Struct3> Struct3Property =
AvaloniaProperty.Register<TestClass, Struct3>("Struct3");
public static readonly StyledProperty<Struct4> Struct4Property =
AvaloniaProperty.Register<TestClass, Struct4>("Struct4");
public static readonly StyledProperty<Struct5> Struct5Property =
AvaloniaProperty.Register<TestClass, Struct5>("Struct5");
public static readonly StyledProperty<Struct6> Struct6Property =
AvaloniaProperty.Register<TestClass, Struct6>("Struct6");
public static readonly StyledProperty<Struct7> Struct7Property =
AvaloniaProperty.Register<TestClass, Struct7>("Struct7");
public static readonly StyledProperty<Struct8> Struct8Property =
AvaloniaProperty.Register<TestClass, Struct8>("Struct8");
}
}
}

181
tests/Avalonia.Benchmarks/Base/AvaloniaObject_GetValue.cs

@ -0,0 +1,181 @@
using System.Runtime.CompilerServices;
using Avalonia.Data;
using BenchmarkDotNet.Attributes;
#nullable enable
namespace Avalonia.Benchmarks.Base
{
[MemoryDiagnoser]
public class AvaloniaObject_GetValue
{
private BaselineTestClass _baseline = new(){ StringProperty = "foo" };
private TestClass _target = new();
public AvaloniaObject_GetValue()
{
RuntimeHelpers.RunClassConstructor(typeof(TestClass).TypeHandle);
}
[Benchmark(Baseline = true)]
public int GetClrPropertyValues()
{
var target = _baseline;
var result = 0;
for (var i = 0; i < 100; ++i)
{
result += target.StringProperty?.Length ?? 0;
result += target.Struct1Property.Int1;
result += target.Struct2Property.Int1;
result += target.Struct3Property.Int1;
result += target.Struct4Property.Int1;
result += target.Struct5Property.Int1;
result += target.Struct6Property.Int1;
result += target.Struct7Property.Int1;
result += target.Struct8Property.Int1;
}
return result;
}
[Benchmark]
public int GetDefaultValues()
{
var target = _target;
var result = 0;
for (var i = 0; i < 100; ++i)
{
result += target.GetValue(TestClass.StringProperty)?.Length ?? 0;
result += target.GetValue(TestClass.Struct1Property).Int1;
result += target.GetValue(TestClass.Struct2Property).Int1;
result += target.GetValue(TestClass.Struct3Property).Int1;
result += target.GetValue(TestClass.Struct4Property).Int1;
result += target.GetValue(TestClass.Struct5Property).Int1;
result += target.GetValue(TestClass.Struct6Property).Int1;
result += target.GetValue(TestClass.Struct7Property).Int1;
result += target.GetValue(TestClass.Struct8Property).Int1;
}
return result;
}
[GlobalSetup(Target = nameof(Get_Local_Values))]
public void SetupLocalValues()
{
_target.SetValue(TestClass.StringProperty, "foo");
_target.SetValue(TestClass.Struct1Property, new(1));
_target.SetValue(TestClass.Struct2Property, new(1));
_target.SetValue(TestClass.Struct3Property, new(1));
_target.SetValue(TestClass.Struct4Property, new(1));
_target.SetValue(TestClass.Struct5Property, new(1));
_target.SetValue(TestClass.Struct6Property, new(1));
_target.SetValue(TestClass.Struct7Property, new(1));
_target.SetValue(TestClass.Struct8Property, new(1));
}
[Benchmark]
public int Get_Local_Values()
{
var target = _target;
var result = 0;
for (var i = 0; i < 100; ++i)
{
result += target.GetValue(TestClass.StringProperty)?.Length ?? 0;
result += target.GetValue(TestClass.Struct1Property).Int1;
result += target.GetValue(TestClass.Struct2Property).Int1;
result += target.GetValue(TestClass.Struct3Property).Int1;
result += target.GetValue(TestClass.Struct4Property).Int1;
result += target.GetValue(TestClass.Struct5Property).Int1;
result += target.GetValue(TestClass.Struct6Property).Int1;
result += target.GetValue(TestClass.Struct7Property).Int1;
result += target.GetValue(TestClass.Struct8Property).Int1;
}
return result;
}
[GlobalSetup(Target = nameof(Get_Local_Values_With_Style_Values))]
public void SetupLocalValuesAndStyleValues()
{
var target = _target;
target.SetValue(TestClass.StringProperty, "foo");
target.SetValue(TestClass.Struct1Property, new(1));
target.SetValue(TestClass.Struct2Property, new(1));
target.SetValue(TestClass.Struct3Property, new(1));
target.SetValue(TestClass.Struct4Property, new(1));
target.SetValue(TestClass.Struct5Property, new(1));
target.SetValue(TestClass.Struct6Property, new(1));
target.SetValue(TestClass.Struct7Property, new(1));
target.SetValue(TestClass.Struct8Property, new(1));
target.SetValue(TestClass.StringProperty, "bar", BindingPriority.Style);
target.SetValue(TestClass.Struct1Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct2Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct3Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct4Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct5Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct6Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct7Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct8Property, new(), BindingPriority.Style);
}
[Benchmark]
public int Get_Local_Values_With_Style_Values()
{
var target = _target;
var result = 0;
for (var i = 0; i < 100; ++i)
{
result += target.GetValue(TestClass.StringProperty)?.Length ?? 0;
result += target.GetValue(TestClass.Struct1Property).Int1;
result += target.GetValue(TestClass.Struct2Property).Int1;
result += target.GetValue(TestClass.Struct3Property).Int1;
result += target.GetValue(TestClass.Struct4Property).Int1;
result += target.GetValue(TestClass.Struct5Property).Int1;
result += target.GetValue(TestClass.Struct6Property).Int1;
result += target.GetValue(TestClass.Struct7Property).Int1;
result += target.GetValue(TestClass.Struct8Property).Int1;
}
return result;
}
private class TestClass : AvaloniaObject
{
public static readonly StyledProperty<string> StringProperty =
AvaloniaProperty.Register<TestClass, string>("String");
public static readonly StyledProperty<Struct1> Struct1Property =
AvaloniaProperty.Register<TestClass, Struct1>("Struct1");
public static readonly StyledProperty<Struct2> Struct2Property =
AvaloniaProperty.Register<TestClass, Struct2>("Struct2");
public static readonly StyledProperty<Struct3> Struct3Property =
AvaloniaProperty.Register<TestClass, Struct3>("Struct3");
public static readonly StyledProperty<Struct4> Struct4Property =
AvaloniaProperty.Register<TestClass, Struct4>("Struct4");
public static readonly StyledProperty<Struct5> Struct5Property =
AvaloniaProperty.Register<TestClass, Struct5>("Struct5");
public static readonly StyledProperty<Struct6> Struct6Property =
AvaloniaProperty.Register<TestClass, Struct6>("Struct6");
public static readonly StyledProperty<Struct7> Struct7Property =
AvaloniaProperty.Register<TestClass, Struct7>("Struct7");
public static readonly StyledProperty<Struct8> Struct8Property =
AvaloniaProperty.Register<TestClass, Struct8>("Struct8");
}
private class BaselineTestClass
{
public string? StringProperty { get; set; }
public Struct1 Struct1Property { get; set; }
public Struct2 Struct2Property { get; set; }
public Struct3 Struct3Property { get; set; }
public Struct4 Struct4Property { get; set; }
public Struct5 Struct5Property { get; set; }
public Struct6 Struct6Property { get; set; }
public Struct7 Struct7Property { get; set; }
public Struct8 Struct8Property { get; set; }
}
}
}

95
tests/Avalonia.Benchmarks/Base/AvaloniaObject_GetValueInherited.cs

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using BenchmarkDotNet.Attributes;
#nullable enable
namespace Avalonia.Benchmarks.Base
{
[MemoryDiagnoser]
public class AvaloniaObject_GetValueInherited
{
private TestClass _root = null!;
private TestClass _target = null!;
public AvaloniaObject_GetValueInherited()
{
RuntimeHelpers.RunClassConstructor(typeof(TestClass).TypeHandle);
}
[Params(1, 2, 10, 50, 100, 200)]
public int Depth { get; set; }
[GlobalSetup]
public void Setup()
{
_root = new();
_root.SetValue(TestClass.StringProperty, "foo");
_root.SetValue(TestClass.Struct1Property, new(1));
_root.SetValue(TestClass.Struct2Property, new(1));
_root.SetValue(TestClass.Struct3Property, new(1));
_root.SetValue(TestClass.Struct4Property, new(1));
_root.SetValue(TestClass.Struct5Property, new(1));
_root.SetValue(TestClass.Struct6Property, new(1));
_root.SetValue(TestClass.Struct7Property, new(1));
_root.SetValue(TestClass.Struct8Property, new(1));
var parent = _root;
for (var i = 0; i < Depth; ++i)
{
var c = new TestClass();
((ISetLogicalParent)c).SetParent(parent);
parent = c;
}
_target = parent;
}
[Benchmark]
public int GetInheritedValues()
{
var target = _target;
var result = 0;
for (var i = 0; i < 100; ++i)
{
result += target.GetValue(TestClass.StringProperty)?.Length ?? 0;
result += target.GetValue(TestClass.Struct1Property).Int1;
result += target.GetValue(TestClass.Struct2Property).Int1;
result += target.GetValue(TestClass.Struct3Property).Int1;
result += target.GetValue(TestClass.Struct4Property).Int1;
result += target.GetValue(TestClass.Struct5Property).Int1;
result += target.GetValue(TestClass.Struct6Property).Int1;
result += target.GetValue(TestClass.Struct7Property).Int1;
result += target.GetValue(TestClass.Struct8Property).Int1;
}
return result;
}
private class TestClass : Control
{
public static readonly StyledProperty<string> StringProperty =
AvaloniaProperty.Register<TestClass, string>("String", inherits: true);
public static readonly StyledProperty<Struct1> Struct1Property =
AvaloniaProperty.Register<TestClass, Struct1>("Struct1", inherits: true);
public static readonly StyledProperty<Struct2> Struct2Property =
AvaloniaProperty.Register<TestClass, Struct2>("Struct2", inherits: true);
public static readonly StyledProperty<Struct3> Struct3Property =
AvaloniaProperty.Register<TestClass, Struct3>("Struct3", inherits: true);
public static readonly StyledProperty<Struct4> Struct4Property =
AvaloniaProperty.Register<TestClass, Struct4>("Struct4", inherits: true);
public static readonly StyledProperty<Struct5> Struct5Property =
AvaloniaProperty.Register<TestClass, Struct5>("Struct5", inherits: true);
public static readonly StyledProperty<Struct6> Struct6Property =
AvaloniaProperty.Register<TestClass, Struct6>("Struct6", inherits: true);
public static readonly StyledProperty<Struct7> Struct7Property =
AvaloniaProperty.Register<TestClass, Struct7>("Struct7", inherits: true);
public static readonly StyledProperty<Struct8> Struct8Property =
AvaloniaProperty.Register<TestClass, Struct8>("Struct8", inherits: true);
}
}
}

130
tests/Avalonia.Benchmarks/Base/AvaloniaObject_SetValue.cs

@ -0,0 +1,130 @@
using System.Runtime.CompilerServices;
using Avalonia.Data;
using BenchmarkDotNet.Attributes;
#nullable enable
namespace Avalonia.Benchmarks.Base
{
[MemoryDiagnoser]
public class AvaloniaObject_SetValue
{
private BaselineTestClass _baseline = new();
private TestClass _target = new();
public AvaloniaObject_SetValue()
{
RuntimeHelpers.RunClassConstructor(typeof(TestClass).TypeHandle);
}
[Benchmark(Baseline = true)]
public int SetClrPropertyValues()
{
var target = _baseline;
var result = 0;
for (var i = 0; i < 100; ++i)
{
target.StringProperty = "foo";
target.Struct1Property = new(i + 1);
target.Struct2Property = new(i + 1);
target.Struct3Property = new(i + 1);
target.Struct4Property = new(i + 1);
target.Struct5Property = new(i + 1);
target.Struct6Property = new(i + 1);
target.Struct7Property = new(i + 1);
target.Struct8Property = new(i + 1);
}
return result;
}
[Benchmark]
public void SetValues()
{
var target = _target;
for (var i = 0; i < 100; ++i)
{
target.SetValue(TestClass.StringProperty, "foo");
target.SetValue(TestClass.Struct1Property, new(i + 1));
target.SetValue(TestClass.Struct2Property, new(i + 1));
target.SetValue(TestClass.Struct3Property, new(i + 1));
target.SetValue(TestClass.Struct4Property, new(i + 1));
target.SetValue(TestClass.Struct5Property, new(i + 1));
target.SetValue(TestClass.Struct6Property, new(i + 1));
target.SetValue(TestClass.Struct7Property, new(i + 1));
target.SetValue(TestClass.Struct8Property, new(i + 1));
}
}
[GlobalSetup(Target = nameof(Set_Local_Values_With_Style_Values))]
public void SetupStyleValues()
{
var target = _target;
target.SetValue(TestClass.StringProperty, "foo", BindingPriority.Style);
target.SetValue(TestClass.Struct1Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct2Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct3Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct4Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct5Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct6Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct7Property, new(), BindingPriority.Style);
target.SetValue(TestClass.Struct8Property, new(), BindingPriority.Style);
}
[Benchmark]
public void Set_Local_Values_With_Style_Values()
{
var target = _target;
for (var i = 0; i < 100; ++i)
{
target.SetValue(TestClass.StringProperty, "foo");
target.SetValue(TestClass.Struct1Property, new(i + 1));
target.SetValue(TestClass.Struct2Property, new(i + 1));
target.SetValue(TestClass.Struct3Property, new(i + 1));
target.SetValue(TestClass.Struct4Property, new(i + 1));
target.SetValue(TestClass.Struct5Property, new(i + 1));
target.SetValue(TestClass.Struct6Property, new(i + 1));
target.SetValue(TestClass.Struct7Property, new(i + 1));
target.SetValue(TestClass.Struct8Property, new(i + 1));
}
}
private class TestClass : AvaloniaObject
{
public static readonly StyledProperty<string> StringProperty =
AvaloniaProperty.Register<TestClass, string>("String");
public static readonly StyledProperty<Struct1> Struct1Property =
AvaloniaProperty.Register<TestClass, Struct1>("Struct1");
public static readonly StyledProperty<Struct2> Struct2Property =
AvaloniaProperty.Register<TestClass, Struct2>("Struct2");
public static readonly StyledProperty<Struct3> Struct3Property =
AvaloniaProperty.Register<TestClass, Struct3>("Struct3");
public static readonly StyledProperty<Struct4> Struct4Property =
AvaloniaProperty.Register<TestClass, Struct4>("Struct4");
public static readonly StyledProperty<Struct5> Struct5Property =
AvaloniaProperty.Register<TestClass, Struct5>("Struct5");
public static readonly StyledProperty<Struct6> Struct6Property =
AvaloniaProperty.Register<TestClass, Struct6>("Struct6");
public static readonly StyledProperty<Struct7> Struct7Property =
AvaloniaProperty.Register<TestClass, Struct7>("Struct7");
public static readonly StyledProperty<Struct8> Struct8Property =
AvaloniaProperty.Register<TestClass, Struct8>("Struct8");
}
private class BaselineTestClass
{
public string? StringProperty { get; set; }
public Struct1 Struct1Property { get; set; }
public Struct2 Struct2Property { get; set; }
public Struct3 Struct3Property { get; set; }
public Struct4 Struct4Property { get; set; }
public Struct5 Struct5Property { get; set; }
public Struct6 Struct6Property { get; set; }
public Struct7 Struct7Property { get; set; }
public Struct8 Struct8Property { get; set; }
}
}
}

14
tests/Avalonia.Benchmarks/Base/DirectPropertyBenchmark.cs

@ -1,14 +1,22 @@
using BenchmarkDotNet.Attributes;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Base
{
[MemoryDiagnoser]
public class DirectPropertyBenchmark
{
private DirectClass _target = new();
public DirectPropertyBenchmark()
{
RuntimeHelpers.RunClassConstructor(typeof(DirectClass).TypeHandle);
}
[Benchmark(Baseline = true)]
public void SetAndRaiseOriginal()
{
var obj = new DirectClass();
var obj = _target;
for (var i = 0; i < 100; ++i)
{
@ -19,7 +27,7 @@ namespace Avalonia.Benchmarks.Base
[Benchmark]
public void SetAndRaiseSimple()
{
var obj = new DirectClass();
var obj = _target;
for (var i = 0; i < 100; ++i)
{

65
tests/Avalonia.Benchmarks/Styling/Style_Activation.cs

@ -0,0 +1,65 @@
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Styling;
using BenchmarkDotNet.Attributes;
#nullable enable
namespace Avalonia.Benchmarks.Styling
{
[MemoryDiagnoser]
public class Style_Activation
{
private TestClass _target = null!;
public Style_Activation()
{
RuntimeHelpers.RunClassConstructor(typeof(TestClass).TypeHandle);
}
[GlobalSetup]
public void Setup()
{
_target = new TestClass();
var style = new Style(x => x.OfType<TestClass>().Class("foo"))
{
Setters = { new Setter(TestClass.StringProperty, "foo") }
};
style.TryAttach(_target, null);
}
[Benchmark]
public void Toggle_Style_Activation_Via_Class()
{
for (var i = 0; i < 100; ++i)
{
_target.Classes.Add("foo");
_target.Classes.Remove("foo");
}
}
private class TestClass : Control
{
public static readonly StyledProperty<string?> StringProperty =
AvaloniaProperty.Register<TestClass, string?>("String");
public static readonly StyledProperty<Struct1> Struct1Property =
AvaloniaProperty.Register<TestClass, Struct1>("Struct1");
public static readonly StyledProperty<Struct2> Struct2Property =
AvaloniaProperty.Register<TestClass, Struct2>("Struct2");
public static readonly StyledProperty<Struct3> Struct3Property =
AvaloniaProperty.Register<TestClass, Struct3>("Struct3");
public static readonly StyledProperty<Struct4> Struct4Property =
AvaloniaProperty.Register<TestClass, Struct4>("Struct4");
public static readonly StyledProperty<Struct5> Struct5Property =
AvaloniaProperty.Register<TestClass, Struct5>("Struct5");
public static readonly StyledProperty<Struct6> Struct6Property =
AvaloniaProperty.Register<TestClass, Struct6>("Struct6");
public static readonly StyledProperty<Struct7> Struct7Property =
AvaloniaProperty.Register<TestClass, Struct7>("Struct7");
public static readonly StyledProperty<Struct8> Struct8Property =
AvaloniaProperty.Register<TestClass, Struct8>("Struct8");
}
}
}

89
tests/Avalonia.Benchmarks/Styling/Style_Apply.cs

@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Styling;
using BenchmarkDotNet.Attributes;
#nullable enable
namespace Avalonia.Benchmarks.Styling
{
[MemoryDiagnoser]
public class Style_Apply
{
private List<Style> _styles = new();
public Style_Apply()
{
RuntimeHelpers.RunClassConstructor(typeof(TestClass).TypeHandle);
}
[Params(1, 5, 50)]
public int MatchingStyles { get; set; }
[Params(1, 5, 50)]
public int NonMatchingStyles { get; set; }
[GlobalSetup]
public void Setup()
{
_styles.Clear();
for (var i = 0; i < MatchingStyles; ++i)
{
_styles.Add(new Style(x => x.OfType<TestClass>())
{
Setters = { new Setter(TestClass.StringProperty, "foo") }
});
}
for (var i = 0; i < NonMatchingStyles; ++i)
{
_styles.Add(new Style(x => x.OfType<TestClass2>().Class("missing"))
{
Setters = { new Setter(TestClass.StringProperty, "foo") }
});
}
}
[Benchmark]
public void Apply_Simple_Styles()
{
var target = new TestClass();
target.BeginBatchUpdate();
foreach (var style in _styles)
style.TryAttach(target, null);
target.EndBatchUpdate();
}
private class TestClass : Control
{
public static readonly StyledProperty<string?> StringProperty =
AvaloniaProperty.Register<TestClass, string?>("String");
public static readonly StyledProperty<Struct1> Struct1Property =
AvaloniaProperty.Register<TestClass, Struct1>("Struct1");
public static readonly StyledProperty<Struct2> Struct2Property =
AvaloniaProperty.Register<TestClass, Struct2>("Struct2");
public static readonly StyledProperty<Struct3> Struct3Property =
AvaloniaProperty.Register<TestClass, Struct3>("Struct3");
public static readonly StyledProperty<Struct4> Struct4Property =
AvaloniaProperty.Register<TestClass, Struct4>("Struct4");
public static readonly StyledProperty<Struct5> Struct5Property =
AvaloniaProperty.Register<TestClass, Struct5>("Struct5");
public static readonly StyledProperty<Struct6> Struct6Property =
AvaloniaProperty.Register<TestClass, Struct6>("Struct6");
public static readonly StyledProperty<Struct7> Struct7Property =
AvaloniaProperty.Register<TestClass, Struct7>("Struct7");
public static readonly StyledProperty<Struct8> Struct8Property =
AvaloniaProperty.Register<TestClass, Struct8>("Struct8");
}
private class TestClass2 : Control
{
}
}
}

74
tests/Avalonia.Benchmarks/Styling/Style_NonActive.cs

@ -0,0 +1,74 @@
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Styling;
using BenchmarkDotNet.Attributes;
#nullable enable
namespace Avalonia.Benchmarks.Styling
{
[MemoryDiagnoser]
public class Style_NonActive
{
private TestClass _target = null!;
public Style_NonActive()
{
RuntimeHelpers.RunClassConstructor(typeof(TestClass).TypeHandle);
}
[GlobalSetup]
public void Setup()
{
_target = new TestClass();
var style = new Style(x => x.OfType<TestClass>().Class("foo"))
{
Setters = { new Setter(TestClass.StringProperty, "foo") }
};
style.TryAttach(_target, null);
_target.SetValue(TestClass.StringProperty, "foo");
_target.SetValue(TestClass.Struct1Property, new(1));
_target.SetValue(TestClass.Struct2Property, new(1));
_target.SetValue(TestClass.Struct3Property, new(1));
_target.SetValue(TestClass.Struct4Property, new(1));
_target.SetValue(TestClass.Struct5Property, new(1));
_target.SetValue(TestClass.Struct6Property, new(1));
_target.SetValue(TestClass.Struct7Property, new(1));
_target.SetValue(TestClass.Struct8Property, new(1));
}
[Benchmark]
public void Toggle_NonActive_Style_Activation()
{
for (var i = 0; i < 100; ++i)
{
_target.Classes.Add("foo");
_target.Classes.Remove("foo");
}
}
private class TestClass : Control
{
public static readonly StyledProperty<string?> StringProperty =
AvaloniaProperty.Register<TestClass, string?>("String");
public static readonly StyledProperty<Struct1> Struct1Property =
AvaloniaProperty.Register<TestClass, Struct1>("Struct1");
public static readonly StyledProperty<Struct2> Struct2Property =
AvaloniaProperty.Register<TestClass, Struct2>("Struct2");
public static readonly StyledProperty<Struct3> Struct3Property =
AvaloniaProperty.Register<TestClass, Struct3>("Struct3");
public static readonly StyledProperty<Struct4> Struct4Property =
AvaloniaProperty.Register<TestClass, Struct4>("Struct4");
public static readonly StyledProperty<Struct5> Struct5Property =
AvaloniaProperty.Register<TestClass, Struct5>("Struct5");
public static readonly StyledProperty<Struct6> Struct6Property =
AvaloniaProperty.Register<TestClass, Struct6>("Struct6");
public static readonly StyledProperty<Struct7> Struct7Property =
AvaloniaProperty.Register<TestClass, Struct7>("Struct7");
public static readonly StyledProperty<Struct8> Struct8Property =
AvaloniaProperty.Register<TestClass, Struct8>("Struct8");
}
}
}

38
tests/Avalonia.Benchmarks/TestBindingObservable.cs

@ -0,0 +1,38 @@
using System;
using Avalonia.Data;
namespace Avalonia.Benchmarks
{
internal class TestBindingObservable<T> : IObservable<BindingValue<T?>>, IDisposable
{
private T? _value;
private IObserver<BindingValue<T?>>? _observer;
public TestBindingObservable(T? initialValue = default) => _value = initialValue;
public IDisposable Subscribe(IObserver<BindingValue<T?>> observer)
{
if (_observer is object)
throw new InvalidOperationException("The observable can only be subscribed once.");
_observer = observer;
observer.OnNext(_value);
return this;
}
public void Dispose() => _observer = null;
public void OnNext(T? value) => _observer?.OnNext(value);
public void PublishCompleted()
{
_observer?.OnCompleted();
_observer = null;
}
protected void PublishError(Exception error)
{
_observer?.OnError(error);
_observer = null;
}
}
}

112
tests/Avalonia.Benchmarks/TestTypes.cs

@ -0,0 +1,112 @@
using System;
namespace Avalonia.Benchmarks
{
internal record struct Struct1
{
public Struct1(int value)
{
Int1 = value;
}
public int Int1;
}
internal record struct Struct2
{
public Struct2(int value)
{
Int1 = Int2 = value;
}
public int Int1;
public int Int2;
}
internal record struct Struct3
{
public Struct3(int value)
{
Int1 = Int2 = Int3 = value;
}
public int Int1;
public int Int2;
public int Int3;
}
internal record struct Struct4
{
public Struct4(int value)
{
Int1 = Int2 = Int3 = Int4 = value;
}
public int Int1;
public int Int2;
public int Int3;
public int Int4;
}
internal record struct Struct5
{
public Struct5(int value)
{
Int1 = Int2 = Int3 = Int4 = Int5 = value;
}
public int Int1;
public int Int2;
public int Int3;
public int Int4;
public int Int5;
}
internal record struct Struct6
{
public Struct6(int value)
{
Int1 = Int2 = Int3 = Int4 = Int5 = Int6 = value;
}
public int Int1;
public int Int2;
public int Int3;
public int Int4;
public int Int5;
public int Int6;
}
internal record struct Struct7
{
public Struct7(int value)
{
Int1 = Int2 = Int3 = Int4 = Int5 = Int6 = Int7 = value;
}
public int Int1;
public int Int2;
public int Int3;
public int Int4;
public int Int5;
public int Int6;
public int Int7;
}
internal record struct Struct8
{
public Struct8(int value)
{
Int1 = Int2 = Int3 = Int4 = Int5 = Int6 = Int7 = Int8 = value;
}
public int Int1;
public int Int2;
public int Int3;
public int Int4;
public int Int5;
public int Int6;
public int Int7;
public int Int8;
}
}

1
tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj

@ -9,6 +9,7 @@
<Import Project="..\..\build\NetFX.props" />
<Import Project="..\..\build\SharedVersion.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />

42
tests/Avalonia.LeakTests/ControlTests.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Remoting.Contexts;
using Avalonia.Controls;
@ -24,11 +25,52 @@ namespace Avalonia.LeakTests
[DotMemoryUnit(FailIfRunWithoutSupport = false)]
public class ControlTests
{
// Need to have the collection as field, so GC will not free it
private readonly ObservableCollection<string> _observableCollection = new();
public ControlTests(ITestOutputHelper atr)
{
DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine);
}
[Fact]
public void DataGrid_Is_Freed()
{
using (Start())
{
// When attached to INotifyCollectionChanged, DataGrid will subscribe to it's events, potentially causing leak
Func<Window> run = () =>
{
var window = new Window
{
Content = new DataGrid
{
Items = _observableCollection
}
};
window.Show();
// Do a layout and make sure that DataGrid gets added to visual tree.
window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<DataGrid>(window.Presenter.Child);
// Clear the content and ensure the DataGrid is removed.
window.Content = null;
window.LayoutManager.ExecuteLayoutPass();
Assert.Null(window.Presenter.Child);
return window;
};
var result = run();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<DataGrid>()).ObjectsCount));
}
}
[Fact]
public void Canvas_Is_Freed()
{

124
tests/Avalonia.Visuals.UnitTests/Media/ColorTests.cs

@ -199,5 +199,129 @@ namespace Avalonia.Visuals.UnitTests.Media
{
Assert.False(Color.TryParse(input, out _));
}
[Fact]
public void Try_Parse_HslColor()
{
// Inline data requires constants, so the data is handled internally here
var data = new Tuple<string, HslColor>[]
{
// HSV
Tuple.Create("hsl(0, 0, 0)", new HslColor(1, 0, 0, 0)),
Tuple.Create("hsl(0, 0%, 0%)", new HslColor(1, 0, 0, 0)),
Tuple.Create("hsl(180, 0.5, 0.5)", new HslColor(1, 180, 0.5, 0.5)),
Tuple.Create("hsl(180, 50%, 50%)", new HslColor(1, 180, 0.5, 0.5)),
Tuple.Create("hsl(360, 1.0, 1.0)", new HslColor(1, 0, 1, 1)), // Wraps Hue to zero
Tuple.Create("hsl(360, 100%, 100%)", new HslColor(1, 0, 1, 1)), // Wraps Hue to zero
Tuple.Create("hsl(-1000, -1000, -1000)", new HslColor(1, 0, 0, 0)), // Clamps to min
Tuple.Create("hsl(-1000, -1000%, -1000%)", new HslColor(1, 0, 0, 0)), // Clamps to min
Tuple.Create("hsl(1000, 1000, 1000)", new HslColor(1, 0, 1, 1)), // Clamps to max
Tuple.Create("hsl(1000, 1000%, 1000%)", new HslColor(1, 0, 1, 1)), // Clamps to max
Tuple.Create("hsl(300, 0.8, 0.2)", new HslColor(1.0, 300, 0.8, 0.2)),
Tuple.Create("hsl(300, 80%, 20%)", new HslColor(1.0, 300, 0.8, 0.2)),
// HSVA
Tuple.Create("hsla(0, 0, 0, 0)", new HslColor(0, 0, 0, 0)),
Tuple.Create("hsla(0, 0%, 0%, 0%)", new HslColor(0, 0, 0, 0)),
Tuple.Create("hsla(180, 0.5, 0.5, 0.5)", new HslColor(0.5, 180, 0.5, 0.5)),
Tuple.Create("hsla(180, 50%, 50%, 50%)", new HslColor(0.5, 180, 0.5, 0.5)),
Tuple.Create("hsla(360, 1.0, 1.0, 1.0)", new HslColor(1, 0, 1, 1)), // Wraps Hue to zero
Tuple.Create("hsla(360, 100%, 100%, 100%)", new HslColor(1, 0, 1, 1)), // Wraps Hue to zero
Tuple.Create("hsla(-1000, -1000, -1000, -1000)", new HslColor(0, 0, 0, 0)), // Clamps to min
Tuple.Create("hsla(-1000, -1000%, -1000%, -1000%)", new HslColor(0, 0, 0, 0)), // Clamps to min
Tuple.Create("hsla(1000, 1000, 1000, 1000)", new HslColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero)
Tuple.Create("hsla(1000, 1000%, 1000%, 1000%)", new HslColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero)
Tuple.Create("hsla(300, 0.9, 0.2, 0.8)", new HslColor(0.8, 300, 0.9, 0.2)),
Tuple.Create("hsla(300, 90%, 20%, 0.8)", new HslColor(0.8, 300, 0.9, 0.2)),
};
foreach (var dataPoint in data)
{
Assert.True(HslColor.TryParse(dataPoint.Item1, out HslColor parsedHslColor));
Assert.True(dataPoint.Item2 == parsedHslColor);
}
}
[Fact]
public void Try_Parse_HsvColor()
{
// Inline data requires constants, so the data is handled internally here
var data = new Tuple<string, HsvColor>[]
{
// HSV
Tuple.Create("hsv(0, 0, 0)", new HsvColor(1, 0, 0, 0)),
Tuple.Create("hsv(0, 0%, 0%)", new HsvColor(1, 0, 0, 0)),
Tuple.Create("hsv(180, 0.5, 0.5)", new HsvColor(1, 180, 0.5, 0.5)),
Tuple.Create("hsv(180, 50%, 50%)", new HsvColor(1, 180, 0.5, 0.5)),
Tuple.Create("hsv(360, 1.0, 1.0)", new HsvColor(1, 0, 1, 1)), // Wraps Hue to zero
Tuple.Create("hsv(360, 100%, 100%)", new HsvColor(1, 0, 1, 1)), // Wraps Hue to zero
Tuple.Create("hsv(-1000, -1000, -1000)", new HsvColor(1, 0, 0, 0)), // Clamps to min
Tuple.Create("hsv(-1000, -1000%, -1000%)", new HsvColor(1, 0, 0, 0)), // Clamps to min
Tuple.Create("hsv(1000, 1000, 1000)", new HsvColor(1, 0, 1, 1)), // Clamps to max
Tuple.Create("hsv(1000, 1000%, 1000%)", new HsvColor(1, 0, 1, 1)), // Clamps to max
Tuple.Create("hsv(300, 0.8, 0.2)", new HsvColor(1.0, 300, 0.8, 0.2)),
Tuple.Create("hsv(300, 80%, 20%)", new HsvColor(1.0, 300, 0.8, 0.2)),
// HSVA
Tuple.Create("hsva(0, 0, 0, 0)", new HsvColor(0, 0, 0, 0)),
Tuple.Create("hsva(0, 0%, 0%, 0%)", new HsvColor(0, 0, 0, 0)),
Tuple.Create("hsva(180, 0.5, 0.5, 0.5)", new HsvColor(0.5, 180, 0.5, 0.5)),
Tuple.Create("hsva(180, 50%, 50%, 50%)", new HsvColor(0.5, 180, 0.5, 0.5)),
Tuple.Create("hsva(360, 1.0, 1.0, 1.0)", new HsvColor(1, 0, 1, 1)), // Wraps Hue to zero
Tuple.Create("hsva(360, 100%, 100%, 100%)", new HsvColor(1, 0, 1, 1)), // Wraps Hue to zero
Tuple.Create("hsva(-1000, -1000, -1000, -1000)", new HsvColor(0, 0, 0, 0)), // Clamps to min
Tuple.Create("hsva(-1000, -1000%, -1000%, -1000%)", new HsvColor(0, 0, 0, 0)), // Clamps to min
Tuple.Create("hsva(1000, 1000, 1000, 1000)", new HsvColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero)
Tuple.Create("hsva(1000, 1000%, 1000%, 1000%)", new HsvColor(1, 0, 1, 1)), // Clamps to max (Hue wraps to zero)
Tuple.Create("hsva(300, 0.9, 0.2, 0.8)", new HsvColor(0.8, 300, 0.9, 0.2)),
Tuple.Create("hsva(300, 90%, 20%, 0.8)", new HsvColor(0.8, 300, 0.9, 0.2)),
};
foreach (var dataPoint in data)
{
Assert.True(HsvColor.TryParse(dataPoint.Item1, out HsvColor parsedHsvColor));
Assert.True(dataPoint.Item2 == parsedHsvColor);
}
}
[Fact]
public void Try_Parse_All_Formats_With_Conversion()
{
// Inline data requires constants, so the data is handled internally here
var data = new Tuple<string, Color>[]
{
// RGB
Tuple.Create("White", new Color(0xff, 0xff, 0xff, 0xff)),
Tuple.Create("#123456", new Color(0xff, 0x12, 0x34, 0x56)),
Tuple.Create("rgb(100, 30, 45)", new Color(255, 100, 30, 45)),
Tuple.Create("rgba(100, 30, 45, 0.9)", new Color(229, 100, 30, 45)),
Tuple.Create("rgba(100, 30, 45, 90%)", new Color(229, 100, 30, 45)),
// HSL
Tuple.Create("hsl(296, 85%, 12%)", new Color(255, 53, 5, 57)),
Tuple.Create("hsla(296, 0.85, 0.12, 0.9)", new Color(230, 53, 5, 57)),
Tuple.Create("hsla(296, 85%, 12%, 90%)", new Color(230, 53, 5, 57)),
// HSV
Tuple.Create("hsv(240, 83%, 78%)", new Color(255, 34, 34, 199)),
Tuple.Create("hsva(240, 0.83, 0.78, 0.9)", new Color(230, 34, 34, 199)),
Tuple.Create("hsva(240, 83%, 78%, 90%)", new Color(230, 34, 34, 199)),
};
foreach (var dataPoint in data)
{
Assert.True(Color.TryParse(dataPoint.Item1, out Color parsedColor));
Assert.True(dataPoint.Item2 == parsedColor);
}
}
}
}

Loading…
Cancel
Save