Browse Source

Merge remote-tracking branch 'upstream/master' into features/NetAnalyzers/CA1847

pull/9190/head
Giuseppe Lippolis 4 years ago
parent
commit
ef66584c76
  1. 2
      .editorconfig
  2. 13
      dirs.proj
  3. 24
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml
  4. 4
      samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
  5. 6
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  6. 3
      src/Android/Avalonia.Android/AvaloniaView.cs
  7. 11
      src/Android/Avalonia.Android/IActivityResultHandler.cs
  8. 4
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  9. 6
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
  10. 15
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
  11. 5
      src/Avalonia.Base/Avalonia.Base.csproj
  12. 12
      src/Avalonia.Base/Media/FontManager.cs
  13. 58
      src/Avalonia.Base/Media/FontMetrics.cs
  14. 95
      src/Avalonia.Base/Media/GlyphRun.cs
  15. 125
      src/Avalonia.Base/Media/GlyphTypeface.cs
  16. 68
      src/Avalonia.Base/Media/IGlyphTypeface.cs
  17. 16
      src/Avalonia.Base/Media/TextDecoration.cs
  18. 6
      src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
  19. 9
      src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs
  20. 26
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  21. 24
      src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs
  22. 4
      src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs
  23. 5
      src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs
  24. 2
      src/Avalonia.Base/Media/Typeface.cs
  25. 2
      src/Avalonia.Base/Platform/IFontManagerImpl.cs
  26. 22
      src/Avalonia.Base/Platform/IGlyphRunBuffer.cs
  27. 37
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  28. 2
      src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
  29. 2
      src/Avalonia.Base/Utilities/MathUtilities.cs
  30. 2
      src/Avalonia.Base/Utilities/ReadOnlySlice.cs
  31. 781
      src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs
  32. 150
      src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs
  33. 662
      src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs
  34. 150
      src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs
  35. 229
      src/Avalonia.Controls.ColorPicker/ColorPalettes/SixteenColorPalette.cs
  36. 29
      src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
  37. 463
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs
  38. 754
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  39. 131
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteFilterMode.cs
  40. 39
      src/Avalonia.Controls/AutoCompleteBox/PopulatedEventArgs.cs
  41. 39
      src/Avalonia.Controls/AutoCompleteBox/PopulatingEventArgs.cs
  42. 30
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  43. 30
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  44. 2
      src/Avalonia.Controls/Documents/IInlineHost.cs
  45. 42
      src/Avalonia.Controls/Documents/InlineRun.cs
  46. 48
      src/Avalonia.Controls/Documents/InlineUIContainer.cs
  47. 2
      src/Avalonia.Controls/Primitives/RangeBase.cs
  48. 88
      src/Avalonia.Controls/RichTextBlock.cs
  49. 2
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  50. 38
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  51. 24
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  52. 7
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  53. 7
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  54. 13
      src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml
  55. 30
      src/Avalonia.Themes.Fluent/Controls/Slider.xaml
  56. 20
      src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
  57. 2
      src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml
  58. 13
      src/Avalonia.Themes.Simple/Controls/DatePicker.xaml
  59. 19
      src/Avalonia.Themes.Simple/Controls/TimePicker.xaml
  60. 5
      src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
  61. 2
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  62. 90
      src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
  63. 184
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  64. 2
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  65. 91
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  66. 2
      src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
  67. 105
      src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs
  68. 2
      src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
  69. 2
      src/iOS/Avalonia.iOS/TextInputResponder.cs
  70. 4
      src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
  71. 2
      tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
  72. 15
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  73. 16
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  74. 36
      tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs
  75. 2
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  76. 2
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  77. 4
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  78. 2
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
  79. 2
      tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs
  80. 96
      tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs
  81. 2
      tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs
  82. 2
      tests/Avalonia.UnitTests/MockFontManagerImpl.cs
  83. 35
      tests/Avalonia.UnitTests/MockGlyphTypeface.cs
  84. 15
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

2
.editorconfig

@ -141,6 +141,8 @@ dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomme
dotnet_diagnostic.CA1802.severity = warning dotnet_diagnostic.CA1802.severity = warning
# CA1825: Avoid zero-length array allocations # CA1825: Avoid zero-length array allocations
dotnet_diagnostic.CA1825.severity = warning dotnet_diagnostic.CA1825.severity = warning
# CA1821: Remove empty finalizers
dotnet_diagnostic.CA1821.severity = warning
#CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters #CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters
dotnet_diagnostic.CA1847.severity = warning dotnet_diagnostic.CA1847.severity = warning

13
dirs.proj

@ -9,21 +9,18 @@
<ProjectReference Remove="**/*.shproj" /> <ProjectReference Remove="**/*.shproj" />
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" /> <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/**/*.*proj" /> <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/**/*.*proj" />
<ProjectReference Remove="tests/Avalonia.ReactiveUI.Events.UnitTests/Avalonia.ReactiveUI.Events.UnitTests.csproj" /> <!-- Exclude iOS, Android and Web samples from build -->
<ProjectReference Remove="samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj" /> <ProjectReference Remove="samples/*.iOS/*.csproj" />
<ProjectReference Remove="samples/MobileSandbox.iOS/MobileSandbox.iOS.csproj" /> <ProjectReference Remove="samples/*.Android/*.csproj" />
<ProjectReference Remove="samples/ControlCatalog.iOS.Legacy/ControlCatalog.iOS.Legacy.csproj" /> <ProjectReference Remove="samples/*.Web/*.csproj" />
<ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" />
<ProjectReference Remove="samples/MobileSandbox.Android/MobileSandbox.Android.csproj" />
<ProjectReference Remove="src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows')) OR '$(MSBuildRuntimeType)' != 'Full'"> <ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows')) OR '$(MSBuildRuntimeType)' != 'Full'">
<ProjectReference Remove="src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj" /> <ProjectReference Remove="src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj" />
<ProjectReference Remove="samples/interop/**/*.*proj" /> <ProjectReference Remove="samples/interop/**/*.*proj" />
<ProjectReference Remove="samples/ControlCatalog.Desktop/*.*proj" /> <ProjectReference Remove="samples/ControlCatalog.Desktop/*.*proj" />
</ItemGroup> </ItemGroup>
<!-- Build android and iOS projects only on Windows, where we have installed android workload -->
<!-- Build android and iOS projects only on Windows, where we have installed android workload -->
<ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows'))"> <ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows'))">
<ProjectReference Remove="src/Android/**/*.*proj" /> <ProjectReference Remove="src/Android/**/*.*proj" />
<ProjectReference Remove="src/iOS/**/*.*proj" /> <ProjectReference Remove="src/iOS/**/*.*proj" />

24
samples/ControlCatalog/Pages/DateTimePickerPage.xaml

@ -13,17 +13,17 @@
Margin="16" Margin="16"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Spacing="16"> Spacing="16">
<TextBlock FontSize="18">A simple DatePicker with a header</TextBlock> <TextBlock FontSize="18">A simple DatePicker</TextBlock>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<DatePicker Header="Pick a date" /> <DatePicker />
</Border> </Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15"> <TextBlock Padding="15">
<TextBlock.Text> <TextBlock.Text>
<x:String> <x:String>
&lt;DatePicker Header="Pick a date" /&gt; &lt;DatePicker/&gt;
</x:String> </x:String>
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>
@ -33,7 +33,7 @@
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<DatePicker Header="Pick a date"> <DatePicker >
<DataValidationErrors.Error> <DataValidationErrors.Error>
<sys:Exception /> <sys:Exception />
</DataValidationErrors.Error> </DataValidationErrors.Error>
@ -79,24 +79,24 @@
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<TimePicker Header="Pick a time"> <TimePicker>
<DataValidationErrors.Error> <DataValidationErrors.Error>
<sys:Exception /> <sys:Exception />
</DataValidationErrors.Error> </DataValidationErrors.Error>
</TimePicker> </TimePicker>
</Border> </Border>
<TextBlock FontSize="18">A TimePicker with a header and minute increments specified.</TextBlock> <TextBlock FontSize="18">A TimePicker with minute increments specified.</TextBlock>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<TimePicker Header="Arrival time" MinuteIncrement="15" /> <TimePicker MinuteIncrement="15" />
</Border> </Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15"> <TextBlock Padding="15">
<TextBlock.Text> <TextBlock.Text>
<x:String> <x:String>
&lt;TimePicker Header="Arrival time" MinuteIncrement="15" /&gt; &lt;TimePicker MinuteIncrement="15" /&gt;
</x:String> </x:String>
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>
@ -107,13 +107,13 @@
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" /> <TimePicker ClockIdentifier="12HourClock"/>
</Border> </Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15"> <TextBlock Padding="15">
<TextBlock.Text> <TextBlock.Text>
<x:String> <x:String>
&lt;TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" /&gt; &lt;TimePicker ClockIdentifier="12HourClock" /&gt;
</x:String> </x:String>
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>
@ -124,13 +124,13 @@
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" /> <TimePicker ClockIdentifier="24HourClock" />
</Border> </Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15"> <TextBlock Padding="15">
<TextBlock.Text> <TextBlock.Text>
<x:String> <x:String>
&lt;TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" /&gt; &lt;TimePicker ClockIdentifier="24HourClock" /&gt;
</x:String> </x:String>
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>

4
samples/RenderDemo/Pages/GlyphRunPage.xaml.cs

@ -22,7 +22,7 @@ namespace RenderDemo.Pages
public class GlyphRunControl : Control public class GlyphRunControl : Control
{ {
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random(); private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1]; private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1]; private char[] _characters = new char[1];
@ -81,7 +81,7 @@ namespace RenderDemo.Pages
public class GlyphRunGeometryControl : Control public class GlyphRunGeometryControl : Control
{ {
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random(); private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1]; private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1]; private char[] _characters = new char[1];

6
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -9,11 +9,11 @@ using AndroidX.Lifecycle;
namespace Avalonia.Android namespace Avalonia.Android
{ {
public abstract class AvaloniaMainActivity : AppCompatActivity public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler
{ {
internal static object ViewContent; internal static object ViewContent;
internal Action<int, Result, Intent> ActivityResult; public Action<int, Result, Intent> ActivityResult { get; set; }
internal AvaloniaView View; internal AvaloniaView View;
protected override void OnCreate(Bundle savedInstanceState) protected override void OnCreate(Bundle savedInstanceState)
@ -24,8 +24,6 @@ namespace Avalonia.Android
View.Content = ViewContent; View.Content = ViewContent;
} }
View.Prepare();
if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime) if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime)
{ {
lifetime.View = View; lifetime.View = View;

3
src/Android/Avalonia.Android/AvaloniaView.cs

@ -21,10 +21,7 @@ namespace Avalonia.Android
{ {
_view = new ViewImpl(this); _view = new ViewImpl(this);
AddView(_view.View); AddView(_view.View);
}
internal void Prepare ()
{
_root = new EmbeddableControlRoot(_view); _root = new EmbeddableControlRoot(_view);
_root.Prepare(); _root.Prepare();
} }

11
src/Android/Avalonia.Android/IActivityResultHandler.cs

@ -0,0 +1,11 @@
using System;
using Android.App;
using Android.Content;
namespace Avalonia.Android
{
public interface IActivityResultHandler
{
public Action<int, Result, Intent> ActivityResult { get; set; }
}
}

4
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Android.App;
using Android.Content; using Android.Content;
using Android.Graphics; using Android.Graphics;
using Android.Runtime; using Android.Runtime;
@ -52,7 +52,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling); _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView); NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
StorageProvider = new AndroidStorageProvider((AvaloniaMainActivity)avaloniaView.Context); StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
} }
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>

6
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs

@ -15,7 +15,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
internal class AndroidMotionEventsHelper : IDisposable internal class AndroidMotionEventsHelper : IDisposable
{ {
private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new(ClearMode.Never); private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new(ClearMode.Never);
private static readonly float s_radiansToDegree = (float)(180f * Math.PI); private const float RadiansToDegree = (float)(180f * Math.PI);
private readonly TouchDevice _touchDevice; private readonly TouchDevice _touchDevice;
private readonly MouseDevice _mouseDevice; private readonly MouseDevice _mouseDevice;
private readonly PenDevice _penDevice; private readonly PenDevice _penDevice;
@ -223,7 +223,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
{ {
Position = new Point(e.GetX(index), e.GetY(index)) / _view.RenderScaling, Position = new Point(e.GetX(index), e.GetY(index)) / _view.RenderScaling,
Pressure = Math.Min(e.GetPressure(index), 1), // android pressure can depend on the device, can be mixed up with "GetSize", may be larger than 1.0f on some devices Pressure = Math.Min(e.GetPressure(index), 1), // android pressure can depend on the device, can be mixed up with "GetSize", may be larger than 1.0f on some devices
Twist = e.GetOrientation(index) * s_radiansToDegree Twist = e.GetOrientation(index) * RadiansToDegree
}; };
} }
@ -233,7 +233,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
{ {
Position = new Point(e.GetHistoricalX(index, pos), e.GetHistoricalY(index, pos)) / _view.RenderScaling, Position = new Point(e.GetHistoricalX(index, pos), e.GetHistoricalY(index, pos)) / _view.RenderScaling,
Pressure = Math.Min(e.GetHistoricalPressure(index, pos), 1), Pressure = Math.Min(e.GetHistoricalPressure(index, pos), 1),
Twist = e.GetHistoricalOrientation(index, pos) * s_radiansToDegree Twist = e.GetHistoricalOrientation(index, pos) * RadiansToDegree
}; };
} }

15
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs

@ -14,10 +14,10 @@ namespace Avalonia.Android.Platform.Storage;
internal class AndroidStorageProvider : IStorageProvider internal class AndroidStorageProvider : IStorageProvider
{ {
private readonly AvaloniaMainActivity _activity; private readonly Activity _activity;
private int _lastRequestCode = 20000; private int _lastRequestCode = 20000;
public AndroidStorageProvider(AvaloniaMainActivity activity) public AndroidStorageProvider(Activity activity)
{ {
_activity = activity; _activity = activity;
} }
@ -119,7 +119,10 @@ internal class AndroidStorageProvider : IStorageProvider
var tcs = new TaskCompletionSource<Intent?>(); var tcs = new TaskCompletionSource<Intent?>();
var currentRequestCode = _lastRequestCode++; var currentRequestCode = _lastRequestCode++;
_activity.ActivityResult += OnActivityResult; if (_activity is IActivityResultHandler mainActivity)
{
mainActivity.ActivityResult += OnActivityResult;
}
_activity.StartActivityForResult(pickerIntent, currentRequestCode); _activity.StartActivityForResult(pickerIntent, currentRequestCode);
var result = await tcs.Task; var result = await tcs.Task;
@ -158,7 +161,11 @@ internal class AndroidStorageProvider : IStorageProvider
return; return;
} }
_activity.ActivityResult -= OnActivityResult;
if (_activity is IActivityResultHandler mainActivity)
{
mainActivity.ActivityResult -= OnActivityResult;
}
_ = tcs.TrySetResult(resultCode == Result.Ok ? data : null); _ = tcs.TrySetResult(resultCode == Result.Ok ? data : null);
} }

5
src/Avalonia.Base/Avalonia.Base.csproj

@ -21,6 +21,9 @@
<Import Project="..\..\build\NullableEnable.props" /> <Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" /> <Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\SourceGenerators.props" /> <Import Project="..\..\build\SourceGenerators.props" />
<ItemGroup>
<Compile Include="..\Shared\IsExternalInit.cs" Link="IsExternalInit.cs" />
</ItemGroup>
<ItemGroup Label="InternalsVisibleTo"> <ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.Base.UnitTests, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Base.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
@ -38,7 +41,7 @@
<InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)"/> <InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" /> <InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
</ItemGroup> </ItemGroup>

12
src/Avalonia.Base/Media/FontManager.cs

@ -13,8 +13,8 @@ namespace Avalonia.Media
/// </summary> /// </summary>
public sealed class FontManager public sealed class FontManager
{ {
private readonly ConcurrentDictionary<Typeface, GlyphTypeface> _glyphTypefaceCache = private readonly ConcurrentDictionary<Typeface, IGlyphTypeface> _glyphTypefaceCache =
new ConcurrentDictionary<Typeface, GlyphTypeface>(); new ConcurrentDictionary<Typeface, IGlyphTypeface>();
private readonly FontFamily _defaultFontFamily; private readonly FontFamily _defaultFontFamily;
private readonly IReadOnlyList<FontFallback>? _fontFallbacks; private readonly IReadOnlyList<FontFallback>? _fontFallbacks;
@ -81,13 +81,13 @@ namespace Avalonia.Media
PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates); PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates);
/// <summary> /// <summary>
/// Returns a new <see cref="GlyphTypeface"/>, or an existing one if a matching <see cref="GlyphTypeface"/> exists. /// Returns a new <see cref="IGlyphTypeface"/>, or an existing one if a matching <see cref="IGlyphTypeface"/> exists.
/// </summary> /// </summary>
/// <param name="typeface">The typeface.</param> /// <param name="typeface">The typeface.</param>
/// <returns> /// <returns>
/// The <see cref="GlyphTypeface"/>. /// The <see cref="IGlyphTypeface"/>.
/// </returns> /// </returns>
public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface) public IGlyphTypeface GetOrAddGlyphTypeface(Typeface typeface)
{ {
while (true) while (true)
{ {
@ -96,7 +96,7 @@ namespace Avalonia.Media
return glyphTypeface; return glyphTypeface;
} }
glyphTypeface = new GlyphTypeface(typeface); glyphTypeface = PlatformImpl.CreateGlyphTypeface(typeface);
if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface)) if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface))
{ {

58
src/Avalonia.Base/Media/FontMetrics.cs

@ -0,0 +1,58 @@
namespace Avalonia.Media
{
/// <summary>
/// The font metrics is holding information about a font's ascent, descent, etc. in design em units.
/// </summary>
public readonly struct FontMetrics
{
/// <summary>
/// Gets the font design units per em.
/// </summary>
public short DesignEmHeight { get; init; }
/// <summary>
/// A <see cref="bool"/> value indicating whether all glyphs in the font have the same advancement.
/// </summary>
public bool IsFixedPitch { get; init; }
/// <summary>
/// Gets the recommended distance above the baseline in design em size.
/// </summary>
public int Ascent { get; init; }
/// <summary>
/// Gets the recommended distance under the baseline in design em size.
/// </summary>
public int Descent { get; init; }
/// <summary>
/// Gets the recommended additional space between two lines of text in design em size.
/// </summary>
public int LineGap { get; init; }
/// <summary>
/// Gets the recommended line spacing of a formed text line.
/// </summary>
public int LineSpacing => Descent - Ascent + LineGap;
/// <summary>
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
/// </summary>
public int UnderlinePosition { get; init; }
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int UnderlineThickness { get; init; }
/// <summary>
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
/// </summary>
public int StrikethroughPosition { get; init; }
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int StrikethroughThickness { get; init; }
}
}

95
src/Avalonia.Base/Media/GlyphRun.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -15,7 +16,7 @@ namespace Avalonia.Media
private static readonly IComparer<int> s_descendingComparer = new ReverseComparer<int>(); private static readonly IComparer<int> s_descendingComparer = new ReverseComparer<int>();
private IGlyphRunImpl? _glyphRunImpl; private IGlyphRunImpl? _glyphRunImpl;
private GlyphTypeface _glyphTypeface; private IGlyphTypeface _glyphTypeface;
private double _fontRenderingEmSize; private double _fontRenderingEmSize;
private int _biDiLevel; private int _biDiLevel;
private Point? _baselineOrigin; private Point? _baselineOrigin;
@ -42,7 +43,7 @@ namespace Avalonia.Media
/// <param name="glyphClusters">The glyph clusters.</param> /// <param name="glyphClusters">The glyph clusters.</param>
/// <param name="biDiLevel">The bidi level.</param> /// <param name="biDiLevel">The bidi level.</param>
public GlyphRun( public GlyphRun(
GlyphTypeface glyphTypeface, IGlyphTypeface glyphTypeface,
double fontRenderingEmSize, double fontRenderingEmSize,
ReadOnlySlice<char> characters, ReadOnlySlice<char> characters,
IReadOnlyList<ushort> glyphIndices, IReadOnlyList<ushort> glyphIndices,
@ -69,9 +70,9 @@ namespace Avalonia.Media
} }
/// <summary> /// <summary>
/// Gets the <see cref="Media.GlyphTypeface"/> for the <see cref="GlyphRun"/>. /// Gets the <see cref="IGlyphTypeface"/> for the <see cref="GlyphRun"/>.
/// </summary> /// </summary>
public GlyphTypeface GlyphTypeface => _glyphTypeface; public IGlyphTypeface GlyphTypeface => _glyphTypeface;
/// <summary> /// <summary>
/// Gets or sets the em size used for rendering the <see cref="GlyphRun"/>. /// Gets or sets the em size used for rendering the <see cref="GlyphRun"/>.
@ -171,7 +172,7 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets the scale of the current <see cref="Media.GlyphTypeface"/> /// Gets the scale of the current <see cref="Media.GlyphTypeface"/>
/// </summary> /// </summary>
internal double Scale => FontRenderingEmSize / GlyphTypeface.DesignEmHeight; internal double Scale => FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight;
/// <summary> /// <summary>
/// Returns <c>true</c> if the text direction is left-to-right. Otherwise, returns <c>false</c>. /// Returns <c>true</c> if the text direction is left-to-right. Otherwise, returns <c>false</c>.
@ -612,7 +613,7 @@ namespace Avalonia.Media
/// <returns>The baseline origin.</returns> /// <returns>The baseline origin.</returns>
private Point CalculateBaselineOrigin() private Point CalculateBaselineOrigin()
{ {
return new Point(0, -GlyphTypeface.Ascent * Scale); return new Point(0, -GlyphTypeface.Metrics.Ascent * Scale);
} }
private GlyphRunMetrics CreateGlyphRunMetrics() private GlyphRunMetrics CreateGlyphRunMetrics()
@ -636,7 +637,7 @@ namespace Avalonia.Media
} }
var isReversed = firstCluster > lastCluster; var isReversed = firstCluster > lastCluster;
var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; var height = GlyphTypeface.Metrics.LineSpacing * Scale;
var widthIncludingTrailingWhitespace = 0d; var widthIncludingTrailingWhitespace = 0d;
var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount); var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount);
@ -854,9 +855,87 @@ namespace Avalonia.Media
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
_glyphRunImpl = CreateGlyphRunImpl();
}
private IGlyphRunImpl CreateGlyphRunImpl()
{
IGlyphRunImpl glyphRunImpl;
var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>(); var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
var count = GlyphIndices.Count;
var scale = (float)(FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight);
if (GlyphOffsets == null)
{
if (GlyphTypeface.Metrics.IsFixedPitch)
{
var buffer = platformRenderInterface.AllocateGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
var glyphs = buffer.GlyphIndices;
for (int i = 0; i < glyphs.Length; i++)
{
glyphs[i] = GlyphIndices[i];
}
glyphRunImpl = buffer.Build();
}
else
{
var buffer = platformRenderInterface.AllocateHorizontalGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
var glyphs = buffer.GlyphIndices;
var positions = buffer.GlyphPositions;
var width = 0d;
for (var i = 0; i < count; i++)
{
positions[i] = (float)width;
if (GlyphAdvances == null)
{
width += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale;
}
else
{
width += GlyphAdvances[i];
}
glyphs[i] = GlyphIndices[i];
}
glyphRunImpl = buffer.Build();
}
}
else
{
var buffer = platformRenderInterface.AllocatePositionedGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
var glyphs = buffer.GlyphIndices;
var glyphPositions = buffer.GlyphPositions;
var currentX = 0.0;
for (var i = 0; i < count; i++)
{
var glyphOffset = GlyphOffsets[i];
glyphPositions[i] = new PointF((float)(currentX + glyphOffset.X), (float)glyphOffset.Y);
if (GlyphAdvances == null)
{
currentX += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale;
}
else
{
currentX += GlyphAdvances[i];
}
glyphs[i] = GlyphIndices[i];
}
glyphRunImpl = buffer.Build();
}
_glyphRunImpl = platformRenderInterface.CreateGlyphRun(this); return glyphRunImpl;
} }
void IDisposable.Dispose() void IDisposable.Dispose()

125
src/Avalonia.Base/Media/GlyphTypeface.cs

@ -1,125 +0,0 @@
using System;
using Avalonia.Platform;
namespace Avalonia.Media
{
public sealed class GlyphTypeface : IDisposable
{
public GlyphTypeface(Typeface typeface)
: this(FontManager.Current.PlatformImpl.CreateGlyphTypeface(typeface))
{
}
public GlyphTypeface(IGlyphTypefaceImpl platformImpl)
{
PlatformImpl = platformImpl;
}
public IGlyphTypefaceImpl PlatformImpl { get; }
/// <summary>
/// Gets the font design units per em.
/// </summary>
public short DesignEmHeight => PlatformImpl.DesignEmHeight;
/// <summary>
/// Gets the recommended distance above the baseline in design em size.
/// </summary>
public int Ascent => PlatformImpl.Ascent;
/// <summary>
/// Gets the recommended distance under the baseline in design em size.
/// </summary>
public int Descent => PlatformImpl.Descent;
/// <summary>
/// Gets the recommended additional space between two lines of text in design em size.
/// </summary>
public int LineGap => PlatformImpl.LineGap;
/// <summary>
/// Gets the recommended line height.
/// </summary>
public int LineHeight => Descent - Ascent + LineGap;
/// <summary>
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
/// </summary>
public int UnderlinePosition => PlatformImpl.UnderlinePosition;
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int UnderlineThickness => PlatformImpl.UnderlineThickness;
/// <summary>
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
/// </summary>
public int StrikethroughPosition => PlatformImpl.StrikethroughPosition;
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int StrikethroughThickness => PlatformImpl.StrikethroughThickness;
/// <summary>
/// A <see cref="bool"/> value indicating whether all glyphs in the font have the same advancement.
/// </summary>
public bool IsFixedPitch => PlatformImpl.IsFixedPitch;
/// <summary>
/// Returns an glyph index for the specified codepoint.
/// </summary>
/// <remarks>
/// Returns a replacement glyph if a glyph isn't found.
/// </remarks>
/// <param name="codepoint">The codepoint.</param>
/// <returns>
/// A glyph index.
/// </returns>
public ushort GetGlyph(uint codepoint) => PlatformImpl.GetGlyph(codepoint);
/// <summary>
/// Tries to get an glyph index for specified codepoint.
/// </summary>
/// <param name="codepoint">The codepoint.</param>
/// <param name="glyph">A glyph index.</param>
/// <returns>
/// <c>true</c> if an glyph index was found, <c>false</c> otherwise.
/// </returns>
public bool TryGetGlyph(uint codepoint, out ushort glyph)
{
glyph = PlatformImpl.GetGlyph(codepoint);
return glyph != 0;
}
/// <summary>
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
/// </summary>
/// <param name="codepoints">The codepoints to map.</param>
/// <returns></returns>
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) => PlatformImpl.GetGlyphs(codepoints);
/// <summary>
/// Returns the glyph advance for the specified glyph.
/// </summary>
/// <param name="glyph">The glyph.</param>
/// <returns>
/// The advance.
/// </returns>
public int GetGlyphAdvance(ushort glyph) => PlatformImpl.GetGlyphAdvance(glyph);
/// <summary>
/// Returns an array of glyph advances in design em size.
/// </summary>
/// <param name="glyphs">The glyph indices.</param>
/// <returns></returns>
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) => PlatformImpl.GetGlyphAdvances(glyphs);
void IDisposable.Dispose()
{
PlatformImpl?.Dispose();
}
}
}

68
src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs → src/Avalonia.Base/Media/IGlyphTypeface.cs

@ -1,55 +1,23 @@
using System; using System;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Platform namespace Avalonia.Media
{ {
[Unstable] [Unstable]
public interface IGlyphTypefaceImpl : IDisposable public interface IGlyphTypeface : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the font design units per em. /// Gets the number of glyphs held by this glyph typeface.
/// </summary> /// </summary>
short DesignEmHeight { get; } int GlyphCount { get; }
/// <summary> /// <summary>
/// Gets the recommended distance above the baseline in design em size. /// Gets the font metrics.
/// </summary> /// </summary>
int Ascent { get; } /// <returns>
/// The font metrics.
/// <summary> /// </returns>
/// Gets the recommended distance under the baseline in design em size. FontMetrics Metrics { get; }
/// </summary>
int Descent { get; }
/// <summary>
/// Gets the recommended additional space between two lines of text in design em size.
/// </summary>
int LineGap { get; }
/// <summary>
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
/// </summary>
int UnderlinePosition { get; }
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
int UnderlineThickness { get; }
/// <summary>
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
/// </summary>
int StrikethroughPosition { get; }
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
int StrikethroughThickness { get; }
/// <summary>
/// A <see cref="bool"/> value indicating whether all glyphs in the font have the same advancement.
/// </summary>
bool IsFixedPitch { get; }
/// <summary> /// <summary>
/// Returns an glyph index for the specified codepoint. /// Returns an glyph index for the specified codepoint.
@ -63,6 +31,16 @@ namespace Avalonia.Platform
/// </returns> /// </returns>
ushort GetGlyph(uint codepoint); ushort GetGlyph(uint codepoint);
/// <summary>
/// Tries to get an glyph index for specified codepoint.
/// </summary>
/// <param name="codepoint">The codepoint.</param>
/// <param name="glyph">A glyph index.</param>
/// <returns>
/// <c>true</c> if an glyph index was found, <c>false</c> otherwise.
/// </returns>
bool TryGetGlyph(uint codepoint, out ushort glyph);
/// <summary> /// <summary>
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>. /// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
/// </summary> /// </summary>
@ -89,5 +67,13 @@ namespace Avalonia.Platform
/// An array of glyph advances. /// An array of glyph advances.
/// </returns> /// </returns>
int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs); int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs);
/// <summary>
/// Returns the contents of the table data for the specified tag.
/// </summary>
/// <param name="tag">The table tag to get the data for.</param>
/// <param name="table">The contents of the table data for the specified tag.</param>
/// <returns>Returns <c>true</c> if the content exists, otherwise <c>false</c>.</returns>
bool TryGetTable(uint tag, out byte[] table);
} }
} }

16
src/Avalonia.Base/Media/TextDecoration.cs

@ -155,9 +155,9 @@ namespace Avalonia.Media
/// </summary> /// </summary>
/// <param name="drawingContext">The drawing context.</param> /// <param name="drawingContext">The drawing context.</param>
/// <param name="glyphRun">The decorated run.</param> /// <param name="glyphRun">The decorated run.</param>
/// <param name="fontMetrics">The font metrics of the decorated run.</param> /// <param name="textMetrics">The font metrics of the decorated run.</param>
/// <param name="defaultBrush">The default brush that is used to draw the decoration.</param> /// <param name="defaultBrush">The default brush that is used to draw the decoration.</param>
internal void Draw(DrawingContext drawingContext, GlyphRun glyphRun, FontMetrics fontMetrics, IBrush defaultBrush) internal void Draw(DrawingContext drawingContext, GlyphRun glyphRun, TextMetrics textMetrics, IBrush defaultBrush)
{ {
var baselineOrigin = glyphRun.BaselineOrigin; var baselineOrigin = glyphRun.BaselineOrigin;
var thickness = StrokeThickness; var thickness = StrokeThickness;
@ -168,16 +168,16 @@ namespace Avalonia.Media
switch (Location) switch (Location)
{ {
case TextDecorationLocation.Underline: case TextDecorationLocation.Underline:
thickness = fontMetrics.UnderlineThickness; thickness = textMetrics.UnderlineThickness;
break; break;
case TextDecorationLocation.Strikethrough: case TextDecorationLocation.Strikethrough:
thickness = fontMetrics.StrikethroughThickness; thickness = textMetrics.StrikethroughThickness;
break; break;
} }
break; break;
case TextDecorationUnit.FontRenderingEmSize: case TextDecorationUnit.FontRenderingEmSize:
thickness = fontMetrics.FontRenderingEmSize * thickness; thickness = textMetrics.FontRenderingEmSize * thickness;
break; break;
} }
@ -189,17 +189,17 @@ namespace Avalonia.Media
origin += glyphRun.BaselineOrigin; origin += glyphRun.BaselineOrigin;
break; break;
case TextDecorationLocation.Strikethrough: case TextDecorationLocation.Strikethrough:
origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.StrikethroughPosition); origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.StrikethroughPosition);
break; break;
case TextDecorationLocation.Underline: case TextDecorationLocation.Underline:
origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.UnderlinePosition); origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.UnderlinePosition);
break; break;
} }
switch (StrokeOffsetUnit) switch (StrokeOffsetUnit)
{ {
case TextDecorationUnit.FontRenderingEmSize: case TextDecorationUnit.FontRenderingEmSize:
origin += new Point(0, StrokeOffset * fontMetrics.FontRenderingEmSize); origin += new Point(0, StrokeOffset * textMetrics.FontRenderingEmSize);
break; break;
case TextDecorationUnit.Pixel: case TextDecorationUnit.Pixel:
origin += new Point(0, StrokeOffset); origin += new Point(0, StrokeOffset);

6
src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs

@ -8,13 +8,13 @@ namespace Avalonia.Media.TextFormatting
{ {
private static readonly IComparer<GlyphInfo> s_clusterComparer = new CompareClusters(); private static readonly IComparer<GlyphInfo> s_clusterComparer = new CompareClusters();
public ShapedBuffer(ReadOnlySlice<char> text, int length, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) public ShapedBuffer(ReadOnlySlice<char> text, int length, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
: this(text, new GlyphInfo[length], glyphTypeface, fontRenderingEmSize, bidiLevel) : this(text, new GlyphInfo[length], glyphTypeface, fontRenderingEmSize, bidiLevel)
{ {
} }
internal ShapedBuffer(ReadOnlySlice<char> text, ArraySlice<GlyphInfo> glyphInfos, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) internal ShapedBuffer(ReadOnlySlice<char> text, ArraySlice<GlyphInfo> glyphInfos, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
{ {
Text = text; Text = text;
GlyphInfos = glyphInfos; GlyphInfos = glyphInfos;
@ -29,7 +29,7 @@ namespace Avalonia.Media.TextFormatting
public int Length => GlyphInfos.Length; public int Length => GlyphInfos.Length;
public GlyphTypeface GlyphTypeface { get; } public IGlyphTypeface GlyphTypeface { get; }
public double FontRenderingEmSize { get; } public double FontRenderingEmSize { get; }

9
src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs

@ -1,5 +1,4 @@
using System; using System;
using System.Diagnostics;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -18,7 +17,7 @@ namespace Avalonia.Media.TextFormatting
Text = shapedBuffer.Text; Text = shapedBuffer.Text;
Properties = properties; Properties = properties;
TextSourceLength = Text.Length; TextSourceLength = Text.Length;
FontMetrics = new FontMetrics(properties.Typeface, properties.FontRenderingEmSize); TextMetrics = new TextMetrics(properties.Typeface, properties.FontRenderingEmSize);
} }
public bool IsReversed { get; private set; } public bool IsReversed { get; private set; }
@ -36,9 +35,9 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/> /// <inheritdoc/>
public override int TextSourceLength { get; } public override int TextSourceLength { get; }
public FontMetrics FontMetrics { get; } public TextMetrics TextMetrics { get; }
public override double Baseline => -FontMetrics.Ascent; public override double Baseline => -TextMetrics.Ascent;
public override Size Size => GlyphRun.Size; public override Size Size => GlyphRun.Size;
@ -89,7 +88,7 @@ namespace Avalonia.Media.TextFormatting
foreach (var textDecoration in Properties.TextDecorations) foreach (var textDecoration in Properties.TextDecorations)
{ {
textDecoration.Draw(drawingContext, GlyphRun, FontMetrics, Properties.ForegroundBrush); textDecoration.Draw(drawingContext, GlyphRun, TextMetrics, Properties.ForegroundBrush);
} }
} }
} }

26
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -1378,17 +1378,17 @@ namespace Avalonia.Media.TextFormatting
private TextLineMetrics CreateLineMetrics() private TextLineMetrics CreateLineMetrics()
{ {
var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface; var fontMetrics = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface.Metrics;
var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize; var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize;
var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight; var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight;
var width = 0d; var width = 0d;
var widthIncludingWhitespace = 0d; var widthIncludingWhitespace = 0d;
var trailingWhitespaceLength = 0; var trailingWhitespaceLength = 0;
var newLineLength = 0; var newLineLength = 0;
var ascent = glyphTypeface.Ascent * scale; var ascent = fontMetrics.Ascent * scale;
var descent = glyphTypeface.Descent * scale; var descent = fontMetrics.Descent * scale;
var lineGap = glyphTypeface.LineGap * scale; var lineGap = fontMetrics.LineGap * scale;
var height = descent - ascent + lineGap; var height = descent - ascent + lineGap;
@ -1400,26 +1400,26 @@ namespace Avalonia.Media.TextFormatting
{ {
case ShapedTextCharacters textRun: case ShapedTextCharacters textRun:
{ {
var fontMetrics = var textMetrics =
new FontMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize); new TextMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize);
if (fontRenderingEmSize < textRun.Properties.FontRenderingEmSize) if (fontRenderingEmSize < textRun.Properties.FontRenderingEmSize)
{ {
fontRenderingEmSize = textRun.Properties.FontRenderingEmSize; fontRenderingEmSize = textRun.Properties.FontRenderingEmSize;
if (ascent > fontMetrics.Ascent) if (ascent > textMetrics.Ascent)
{ {
ascent = fontMetrics.Ascent; ascent = textMetrics.Ascent;
} }
if (descent < fontMetrics.Descent) if (descent < textMetrics.Descent)
{ {
descent = fontMetrics.Descent; descent = textMetrics.Descent;
} }
if (lineGap < fontMetrics.LineGap) if (lineGap < textMetrics.LineGap)
{ {
lineGap = fontMetrics.LineGap; lineGap = textMetrics.LineGap;
} }
if (descent - ascent + lineGap > height) if (descent - ascent + lineGap > height)

24
src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs → src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs

@ -1,33 +1,33 @@
namespace Avalonia.Media.TextFormatting namespace Avalonia.Media.TextFormatting
{ {
/// <summary> /// <summary>
/// A metric that holds information about font specific measurements. /// A metric that holds information about text specific measurements.
/// </summary> /// </summary>
public readonly struct FontMetrics public readonly struct TextMetrics
{ {
public FontMetrics(Typeface typeface, double fontRenderingEmSize) public TextMetrics(Typeface typeface, double fontRenderingEmSize)
{ {
var glyphTypeface = typeface.GlyphTypeface; var fontMetrics = typeface.GlyphTypeface.Metrics;
var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight; var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight;
FontRenderingEmSize = fontRenderingEmSize; FontRenderingEmSize = fontRenderingEmSize;
Ascent = glyphTypeface.Ascent * scale; Ascent = fontMetrics.Ascent * scale;
Descent = glyphTypeface.Descent * scale; Descent = fontMetrics.Descent * scale;
LineGap = glyphTypeface.LineGap * scale; LineGap = fontMetrics.LineGap * scale;
LineHeight = Descent - Ascent + LineGap; LineHeight = Descent - Ascent + LineGap;
UnderlineThickness = glyphTypeface.UnderlineThickness * scale; UnderlineThickness = fontMetrics.UnderlineThickness * scale;
UnderlinePosition = glyphTypeface.UnderlinePosition * scale; UnderlinePosition = fontMetrics.UnderlinePosition * scale;
StrikethroughThickness = glyphTypeface.StrikethroughThickness * scale; StrikethroughThickness = fontMetrics.StrikethroughThickness * scale;
StrikethroughPosition = glyphTypeface.StrikethroughPosition * scale; StrikethroughPosition = fontMetrics.StrikethroughPosition * scale;
} }
/// <summary> /// <summary>

4
src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs

@ -8,7 +8,7 @@ namespace Avalonia.Media.TextFormatting
public readonly struct TextShaperOptions public readonly struct TextShaperOptions
{ {
public TextShaperOptions( public TextShaperOptions(
GlyphTypeface typeface, IGlyphTypeface typeface,
double fontRenderingEmSize = 12, double fontRenderingEmSize = 12,
sbyte bidiLevel = 0, sbyte bidiLevel = 0,
CultureInfo? culture = null, CultureInfo? culture = null,
@ -24,7 +24,7 @@ namespace Avalonia.Media.TextFormatting
/// <summary> /// <summary>
/// Get the typeface. /// Get the typeface.
/// </summary> /// </summary>
public GlyphTypeface Typeface { get; } public IGlyphTypeface Typeface { get; }
/// <summary> /// <summary>
/// Get the font rendering em size. /// Get the font rendering em size.
/// </summary> /// </summary>

5
src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs

@ -1,4 +1,5 @@
using System.Runtime.CompilerServices; using System;
using System.Runtime.CompilerServices;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode namespace Avalonia.Media.TextFormatting.Unicode
@ -165,7 +166,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
/// <param name="index">The index to read at.</param> /// <param name="index">The index to read at.</param>
/// <param name="count">The count of character that were read.</param> /// <param name="count">The count of character that were read.</param>
/// <returns></returns> /// <returns></returns>
public static Codepoint ReadAt(ReadOnlySlice<char> text, int index, out int count) public static Codepoint ReadAt(ReadOnlySpan<char> text, int index, out int count)
{ {
count = 1; count = 1;

2
src/Avalonia.Base/Media/Typeface.cs

@ -81,7 +81,7 @@ namespace Avalonia.Media
/// <value> /// <value>
/// The glyph typeface. /// The glyph typeface.
/// </value> /// </value>
public GlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); public IGlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this);
public static bool operator !=(Typeface a, Typeface b) public static bool operator !=(Typeface a, Typeface b)
{ {

2
src/Avalonia.Base/Platform/IFontManagerImpl.cs

@ -43,6 +43,6 @@ namespace Avalonia.Platform
/// <returns>0 /// <returns>0
/// The created glyph typeface. Can be <c>Null</c> if it was not possible to create a glyph typeface. /// The created glyph typeface. Can be <c>Null</c> if it was not possible to create a glyph typeface.
/// </returns> /// </returns>
IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface); IGlyphTypeface CreateGlyphTypeface(Typeface typeface);
} }
} }

22
src/Avalonia.Base/Platform/IGlyphRunBuffer.cs

@ -0,0 +1,22 @@
using System;
using System.Drawing;
namespace Avalonia.Platform
{
public interface IGlyphRunBuffer
{
Span<ushort> GlyphIndices { get; }
IGlyphRunImpl Build();
}
public interface IHorizontalGlyphRunBuffer : IGlyphRunBuffer
{
Span<float> GlyphPositions { get; }
}
public interface IPositionedGlyphRunBuffer : IGlyphRunBuffer
{
Span<PointF> GlyphPositions { get; }
}
}

37
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@ -171,11 +171,40 @@ namespace Avalonia.Platform
IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride); IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride);
/// <summary> /// <summary>
/// Creates a platform implementation of a glyph run. /// Allocates a platform glyph run buffer.
/// </summary> /// </summary>
/// <param name="glyphRun">The glyph run.</param> /// <param name="glyphTypeface">The glyph typeface.</param>
/// <returns></returns> /// <param name="fontRenderingEmSize">The font rendering em size.</param>
IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun); /// <param name="length">The length.</param>
/// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
/// <remarks>
/// This buffer only holds glyph indices.
/// </remarks>
IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary>
/// Allocates a horizontal platform glyph run buffer.
/// </summary>
/// <param name="glyphTypeface">The glyph typeface.</param>
/// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="length">The length.</param>
/// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
/// <remarks>
/// This buffer holds glyph indices and glyph advances.
/// </remarks>
IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary>
/// Allocates a positioned platform glyph run buffer.
/// </summary>
/// <param name="glyphTypeface">The glyph typeface.</param>
/// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="length">The length.</param>
/// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
/// <remarks>
/// This buffer holds glyph indices, glyph advances and glyph positions.
/// </remarks>
IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary> /// <summary>
/// Gets a value indicating whether the platform directly supports rectangles with rounded corners. /// Gets a value indicating whether the platform directly supports rectangles with rounded corners.

2
src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs

@ -25,7 +25,7 @@ internal class FpsCounter
// ASCII chars // ASCII chars
private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1]; private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1];
public FpsCounter(GlyphTypeface typeface) public FpsCounter(IGlyphTypeface typeface)
{ {
for (var c = FirstChar; c <= LastChar; c++) for (var c = FirstChar; c <= LastChar; c++)
{ {

2
src/Avalonia.Base/Utilities/MathUtilities.cs

@ -11,7 +11,7 @@ namespace Avalonia.Utilities
static class MathUtilities static class MathUtilities
{ {
// smallest such that 1.0+DoubleEpsilon != 1.0 // smallest such that 1.0+DoubleEpsilon != 1.0
internal static readonly double DoubleEpsilon = 2.2204460492503131e-016; internal const double DoubleEpsilon = 2.2204460492503131e-016;
private const float FloatEpsilon = 1.192092896e-07F; private const float FloatEpsilon = 1.192092896e-07F;

2
src/Avalonia.Base/Utilities/ReadOnlySlice.cs

@ -214,6 +214,8 @@ namespace Avalonia.Utilities
return new ReadOnlySlice<T>(memory); return new ReadOnlySlice<T>(memory);
} }
public static implicit operator ReadOnlySpan<T>(ReadOnlySlice<T> slice) => slice.Span;
internal class ReadOnlySliceDebugView internal class ReadOnlySliceDebugView
{ {
private readonly ReadOnlySlice<T> _readOnlySlice; private readonly ReadOnlySlice<T> _readOnlySlice;

781
src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs

@ -17,268 +17,603 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
public class FlatColorPalette : IColorPalette public class FlatColorPalette : IColorPalette
{ {
// The full Flat UI color chart has 10 rows and 20 columns /// <summary>
// See: https://htmlcolorcodes.com/assets/downloads/flat-design-colors/flat-design-color-chart.png /// Defines all colors in the <see cref="FlatColorPalette"/>.
// This is a reduced palette for usability /// </summary>
private static Color[,] colorChart = new Color[,] /// <remarks>
/// This is done in an enum to ensure it is compiled into the assembly improving
/// startup performance.
/// </remarks>
public enum FlatColor : uint
{ {
// Pomegranate // Pomegranate
{ Pomegranate1 = 0xFFF9EBEA,
Color.FromArgb(0xFF, 0xF9, 0xEB, 0xEA), Pomegranate2 = 0xFFF2D7D5,
Color.FromArgb(0xFF, 0xE6, 0xB0, 0xAA), Pomegranate3 = 0xFFE6B0AA,
Color.FromArgb(0xFF, 0xCD, 0x61, 0x55), Pomegranate4 = 0xFFD98880,
Color.FromArgb(0xFF, 0xA9, 0x32, 0x26), Pomegranate5 = 0xFFCD6155,
Color.FromArgb(0xFF, 0x7B, 0x24, 0x1C), Pomegranate6 = 0xFFC0392B,
}, Pomegranate7 = 0xFFA93226,
Pomegranate8 = 0xFF922B21,
Pomegranate9 = 0xFF7B241C,
Pomegranate10 = 0xFF641E16,
// Alizarin
Alizarin1 = 0xFFFDEDEC,
Alizarin2 = 0xFFFADBD8,
Alizarin3 = 0xFFF5B7B1,
Alizarin4 = 0xFFF1948A,
Alizarin5 = 0xFFEC7063,
Alizarin6 = 0xFFE74C3C,
Alizarin7 = 0xFFCB4335,
Alizarin8 = 0xFFB03A2E,
Alizarin9 = 0xFF943126,
Alizarin10 = 0xFF78281F,
// Amethyst // Amethyst
{ Amethyst1 = 0xFFF5EEF8,
Color.FromArgb(0xFF, 0xF5, 0xEE, 0xF8), Amethyst2 = 0xFFEBDEF0,
Color.FromArgb(0xFF, 0xD7, 0xBD, 0xE2), Amethyst3 = 0xFFD7BDE2,
Color.FromArgb(0xFF, 0xAF, 0x7A, 0xC5), Amethyst4 = 0xFFC39BD3,
Color.FromArgb(0xFF, 0x88, 0x4E, 0xA0), Amethyst5 = 0xFFAF7AC5,
Color.FromArgb(0xFF, 0x63, 0x39, 0x74), Amethyst6 = 0xFF9B59B6,
}, Amethyst7 = 0xFF884EA0,
Amethyst8 = 0xFF76448A,
Amethyst9 = 0xFF633974,
Amethyst10 = 0xFF512E5F,
// Wisteria
Wisteria1 = 0xFFF4ECF7,
Wisteria2 = 0xFFE8DAEF,
Wisteria3 = 0xFFD2B4DE,
Wisteria4 = 0xFFBB8FCE,
Wisteria5 = 0xFFA569BD,
Wisteria6 = 0xFF8E44AD,
Wisteria7 = 0xFF7D3C98,
Wisteria8 = 0xFF6C3483,
Wisteria9 = 0xFF5B2C6F,
Wisteria10 = 0xFF4A235A,
// Belize Hole // Belize Hole
{ BelizeHole1 = 0xFFEAF2F8,
Color.FromArgb(0xFF, 0xEA, 0xF2, 0xF8), BelizeHole2 = 0xFFD4E6F1,
Color.FromArgb(0xFF, 0xA9, 0xCC, 0xE3), BelizeHole3 = 0xFFA9CCE3,
Color.FromArgb(0xFF, 0x54, 0x99, 0xC7), BelizeHole4 = 0xFF7FB3D5,
Color.FromArgb(0xFF, 0x24, 0x71, 0xA3), BelizeHole5 = 0xFF5499C7,
Color.FromArgb(0xFF, 0x1A, 0x52, 0x76), BelizeHole6 = 0xFF2980B9,
}, BelizeHole7 = 0xFF2471A3,
BelizeHole8 = 0xFF1F618D,
BelizeHole9 = 0xFF1A5276,
BelizeHole10 = 0xFF154360,
// Peter River
PeterRiver1 = 0xFFEBF5FB,
PeterRiver2 = 0xFFD6EAF8,
PeterRiver3 = 0xFFAED6F1,
PeterRiver4 = 0xFF85C1E9,
PeterRiver5 = 0xFF5DADE2,
PeterRiver6 = 0xFF3498DB,
PeterRiver7 = 0xFF2E86C1,
PeterRiver8 = 0xFF2874A6,
PeterRiver9 = 0xFF21618C,
PeterRiver10 = 0xFF1B4F72,
// Turquoise // Turquoise
{ Turquoise1 = 0xFFE8F8F5,
Color.FromArgb(0xFF, 0xE8, 0xF8, 0xF5), Turquoise2 = 0xFFD1F2EB,
Color.FromArgb(0xFF, 0xA3, 0xE4, 0xD7), Turquoise3 = 0xFFA3E4D7,
Color.FromArgb(0xFF, 0x48, 0xC9, 0xB0), Turquoise4 = 0xFF76D7C4,
Color.FromArgb(0xFF, 0x17, 0xA5, 0x89), Turquoise5 = 0xFF48C9B0,
Color.FromArgb(0xFF, 0x11, 0x78, 0x64), Turquoise6 = 0xFF1ABC9C,
}, Turquoise7 = 0xFF17A589,
Turquoise8 = 0xFF148F77,
Turquoise9 = 0xFF117864,
Turquoise10 = 0xFF0E6251,
// Green Sea
GreenSea1 = 0xFFE8F6F3,
GreenSea2 = 0xFFD0ECE7,
GreenSea3 = 0xFFA2D9CE,
GreenSea4 = 0xFF73C6B6,
GreenSea5 = 0xFF45B39D,
GreenSea6 = 0xFF16A085,
GreenSea7 = 0xFF138D75,
GreenSea8 = 0xFF117A65,
GreenSea9 = 0xFF0E6655,
GreenSea10 = 0xFF0B5345,
// Nephritis // Nephritis
{ Nephritis1 = 0xFFE9F7EF,
Color.FromArgb(0xFF, 0xE9, 0xF7, 0xEF), Nephritis2 = 0xFFD4EFDF,
Color.FromArgb(0xFF, 0xA9, 0xDF, 0xBF), Nephritis3 = 0xFFA9DFBF,
Color.FromArgb(0xFF, 0x52, 0xBE, 0x80), Nephritis4 = 0xFF7DCEA0,
Color.FromArgb(0xFF, 0x22, 0x99, 0x54), Nephritis5 = 0xFF52BE80,
Color.FromArgb(0xFF, 0x19, 0x6F, 0x3D), Nephritis6 = 0xFF27AE60,
}, Nephritis7 = 0xFF229954,
Nephritis8 = 0xFF1E8449,
Nephritis9 = 0xFF196F3D,
Nephritis10 = 0xFF145A32,
// Emerald
Emerald1 = 0xFFEAFAF1,
Emerald2 = 0xFFD5F5E3,
Emerald3 = 0xFFABEBC6,
Emerald4 = 0xFF82E0AA,
Emerald5 = 0xFF58D68D,
Emerald6 = 0xFF2ECC71,
Emerald7 = 0xFF28B463,
Emerald8 = 0xFF239B56,
Emerald9 = 0xFF1D8348,
Emerald10 = 0xFF186A3B,
// Sunflower // Sunflower
{ Sunflower1 = 0xFFFEF9E7,
Color.FromArgb(0xFF, 0xFE, 0xF9, 0xE7), Sunflower2 = 0xFFFCF3CF,
Color.FromArgb(0xFF, 0xF9, 0xE7, 0x9F), Sunflower3 = 0xFFF9E79F,
Color.FromArgb(0xFF, 0xF4, 0xD0, 0x3F), Sunflower4 = 0xFFF7DC6F,
Color.FromArgb(0xFF, 0xD4, 0xAC, 0x0D), Sunflower5 = 0xFFF4D03F,
Color.FromArgb(0xFF, 0x9A, 0x7D, 0x0A), Sunflower6 = 0xFFF1C40F,
}, Sunflower7 = 0xFFD4AC0D,
Sunflower8 = 0xFFB7950B,
Sunflower9 = 0xFF9A7D0A,
Sunflower10 = 0xFF7D6608,
// Orange
Orange1 = 0xFFFEF5E7,
Orange2 = 0xFFFDEBD0,
Orange3 = 0xFFFAD7A0,
Orange4 = 0xFFF8C471,
Orange5 = 0xFFF5B041,
Orange6 = 0xFFF39C12,
Orange7 = 0xFFD68910,
Orange8 = 0xFFB9770E,
Orange9 = 0xFF9C640C,
Orange10 = 0xFF7E5109,
// Carrot // Carrot
{ Carrot1 = 0xFFFDF2E9,
Color.FromArgb(0xFF, 0xFD, 0xF2, 0xE9), Carrot2 = 0xFFFAE5D3,
Color.FromArgb(0xFF, 0xF5, 0xCB, 0xA7), Carrot3 = 0xFFF5CBA7,
Color.FromArgb(0xFF, 0xEB, 0x98, 0x4E), Carrot4 = 0xFFF0B27A,
Color.FromArgb(0xFF, 0xCA, 0x6F, 0x1E), Carrot5 = 0xFFEB984E,
Color.FromArgb(0xFF, 0x93, 0x51, 0x16), Carrot6 = 0xFFE67E22,
}, Carrot7 = 0xFFCA6F1E,
Carrot8 = 0xFFAF601A,
Carrot9 = 0xFF935116,
Carrot10 = 0xFF784212,
// Pumpkin
Pumpkin1 = 0xFFFBEEE6,
Pumpkin2 = 0xFFF6DDCC,
Pumpkin3 = 0xFFEDBB99,
Pumpkin4 = 0xFFE59866,
Pumpkin5 = 0xFFDC7633,
Pumpkin6 = 0xFFD35400,
Pumpkin7 = 0xFFBA4A00,
Pumpkin8 = 0xFFA04000,
Pumpkin9 = 0xFF873600,
Pumpkin10 = 0xFF6E2C00,
// Clouds // Clouds
{ Clouds1 = 0xFFFDFEFE,
Color.FromArgb(0xFF, 0xFD, 0xFE, 0xFE), Clouds2 = 0xFFFBFCFC,
Color.FromArgb(0xFF, 0xF7, 0xF9, 0xF9), Clouds3 = 0xFFF7F9F9,
Color.FromArgb(0xFF, 0xF0, 0xF3, 0xF4), Clouds4 = 0xFFF4F6F7,
Color.FromArgb(0xFF, 0xD0, 0xD3, 0xD4), Clouds5 = 0xFFF0F3F4,
Color.FromArgb(0xFF, 0x97, 0x9A, 0x9A), Clouds6 = 0xFFECF0F1,
}, Clouds7 = 0xFFD0D3D4,
Clouds8 = 0xFFB3B6B7,
Clouds9 = 0xFF979A9A,
Clouds10 = 0xFF7B7D7D,
// Silver
Silver1 = 0xFFF8F9F9,
Silver2 = 0xFFF2F3F4,
Silver3 = 0xFFE5E7E9,
Silver4 = 0xFFD7DBDD,
Silver5 = 0xFFCACFD2,
Silver6 = 0xFFBDC3C7,
Silver7 = 0xFFA6ACAF,
Silver8 = 0xFF909497,
Silver9 = 0xFF797D7F,
Silver10 = 0xFF626567,
// Concrete // Concrete
{ Concrete1 = 0xFFF4F6F6,
Color.FromArgb(0xFF, 0xF4, 0xF6, 0xF6), Concrete2 = 0xFFEAEDED,
Color.FromArgb(0xFF, 0xD5, 0xDB, 0xDB), Concrete3 = 0xFFD5DBDB,
Color.FromArgb(0xFF, 0xAA, 0xB7, 0xB8), Concrete4 = 0xFFBFC9CA,
Color.FromArgb(0xFF, 0x83, 0x91, 0x92), Concrete5 = 0xFFAAB7B8,
Color.FromArgb(0xFF, 0x5F, 0x6A, 0x6A), Concrete6 = 0xFF95A5A6,
}, Concrete7 = 0xFF839192,
Concrete8 = 0xFF717D7E,
Concrete9 = 0xFF5F6A6A,
Concrete10 = 0xFF4D5656,
// Asbestos
Asbestos1 = 0xFFF2F4F4,
Asbestos2 = 0xFFE5E8E8,
Asbestos3 = 0xFFCCD1D1,
Asbestos4 = 0xFFB2BABB,
Asbestos5 = 0xFF99A3A4,
Asbestos6 = 0xFF7F8C8D,
Asbestos7 = 0xFF707B7C,
Asbestos8 = 0xFF616A6B,
Asbestos9 = 0xFF515A5A,
Asbestos10 = 0xFF424949,
// Wet Asphalt // Wet Asphalt
{ WetAsphalt1 = 0xFFEBEDEF,
Color.FromArgb(0xFF, 0xEB, 0xED, 0xEF), WetAsphalt2 = 0xFFD6DBDF,
Color.FromArgb(0xFF, 0xAE, 0xB6, 0xBF), WetAsphalt3 = 0xFFAEB6BF,
Color.FromArgb(0xFF, 0x5D, 0x6D, 0x7E), WetAsphalt4 = 0xFF85929E,
Color.FromArgb(0xFF, 0x2E, 0x40, 0x53), WetAsphalt5 = 0xFF5D6D7E,
Color.FromArgb(0xFF, 0x21, 0x2F, 0x3C), WetAsphalt6 = 0xFF34495E,
}, WetAsphalt7 = 0xFF2E4053,
WetAsphalt8 = 0xFF283747,
WetAsphalt9 = 0xFF212F3C,
WetAsphalt10 = 0xFF1B2631,
// Midnight Blue
MidnightBlue1 = 0xFFEAECEE,
MidnightBlue2 = 0xFFD5D8DC,
MidnightBlue3 = 0xFFABB2B9,
MidnightBlue4 = 0xFF808B96,
MidnightBlue5 = 0xFF566573,
MidnightBlue6 = 0xFF2C3E50,
MidnightBlue7 = 0xFF273746,
MidnightBlue8 = 0xFF212F3D,
MidnightBlue9 = 0xFF1C2833,
MidnightBlue10 = 0xFF17202A,
Pomegranate = Pomegranate3,
Alizarin = Alizarin3,
Amethyst = Amethyst3,
Wisteria = Wisteria3,
BelizeHole = BelizeHole3,
PeterRiver = PeterRiver3,
Turquoise = Turquoise3,
GreenSea = GreenSea3,
Nephritis = Nephritis3,
Emerald = Emerald3,
Sunflower = Sunflower3,
Orange = Orange3,
Carrot = Carrot3,
Pumpkin = Pumpkin3,
Clouds = Clouds3,
Silver = Silver3,
Concrete = Concrete3,
Asbestos = Asbestos3,
WetAsphalt = WetAsphalt3,
MidnightBlue = MidnightBlue3,
}; };
/// <summary> // See: https://htmlcolorcodes.com/assets/downloads/flat-design-colors/flat-design-color-chart.png
/// Gets the index of the default shade of colors in this palette. protected static Color[,]? _colorChart = null;
/// </summary> protected static object _colorChartMutex = new object();
public const int DefaultShadeIndex = 2;
/// <summary>
/// The index in the color palette of the 'Pomegranate' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int PomegranateIndex = 0;
/// <summary>
/// The index in the color palette of the 'Amethyst' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int AmethystIndex = 1;
/// <summary>
/// The index in the color palette of the 'BelizeHole' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int BelizeHoleIndex = 2;
/// <summary>
/// The index in the color palette of the 'Turquoise' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int TurquoiseIndex = 3;
/// <summary>
/// The index in the color palette of the 'Nephritis' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int NephritisIndex = 4;
/// <summary>
/// The index in the color palette of the 'Sunflower' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int SunflowerIndex = 5;
/// <summary>
/// The index in the color palette of the 'Carrot' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int CarrotIndex = 6;
/// <summary>
/// The index in the color palette of the 'Clouds' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int CloudsIndex = 7;
/// <summary>
/// The index in the color palette of the 'Concrete' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int ConcreteIndex = 8;
/// <summary> /// <summary>
/// The index in the color palette of the 'WetAsphalt' color. /// Initializes all color chart colors.
/// This index can correspond to multiple color shades.
/// </summary> /// </summary>
public const int WetAsphaltIndex = 9; protected void InitColorChart()
{
lock (_colorChartMutex)
{
if (_colorChart != null)
{
return;
}
_colorChart = new Color[,]
{
// Pomegranate
{
Color.FromUInt32((uint)FlatColor.Pomegranate1),
Color.FromUInt32((uint)FlatColor.Pomegranate2),
Color.FromUInt32((uint)FlatColor.Pomegranate3),
Color.FromUInt32((uint)FlatColor.Pomegranate4),
Color.FromUInt32((uint)FlatColor.Pomegranate5),
Color.FromUInt32((uint)FlatColor.Pomegranate6),
Color.FromUInt32((uint)FlatColor.Pomegranate7),
Color.FromUInt32((uint)FlatColor.Pomegranate8),
Color.FromUInt32((uint)FlatColor.Pomegranate9),
Color.FromUInt32((uint)FlatColor.Pomegranate10),
},
// Alizarin
{
Color.FromUInt32((uint)FlatColor.Alizarin1),
Color.FromUInt32((uint)FlatColor.Alizarin2),
Color.FromUInt32((uint)FlatColor.Alizarin3),
Color.FromUInt32((uint)FlatColor.Alizarin4),
Color.FromUInt32((uint)FlatColor.Alizarin5),
Color.FromUInt32((uint)FlatColor.Alizarin6),
Color.FromUInt32((uint)FlatColor.Alizarin7),
Color.FromUInt32((uint)FlatColor.Alizarin8),
Color.FromUInt32((uint)FlatColor.Alizarin9),
Color.FromUInt32((uint)FlatColor.Alizarin10),
},
// Amethyst
{
Color.FromUInt32((uint)FlatColor.Amethyst1),
Color.FromUInt32((uint)FlatColor.Amethyst2),
Color.FromUInt32((uint)FlatColor.Amethyst3),
Color.FromUInt32((uint)FlatColor.Amethyst4),
Color.FromUInt32((uint)FlatColor.Amethyst5),
Color.FromUInt32((uint)FlatColor.Amethyst6),
Color.FromUInt32((uint)FlatColor.Amethyst7),
Color.FromUInt32((uint)FlatColor.Amethyst8),
Color.FromUInt32((uint)FlatColor.Amethyst9),
Color.FromUInt32((uint)FlatColor.Amethyst10),
},
// Wisteria
{
Color.FromUInt32((uint)FlatColor.Wisteria1),
Color.FromUInt32((uint)FlatColor.Wisteria2),
Color.FromUInt32((uint)FlatColor.Wisteria3),
Color.FromUInt32((uint)FlatColor.Wisteria4),
Color.FromUInt32((uint)FlatColor.Wisteria5),
Color.FromUInt32((uint)FlatColor.Wisteria6),
Color.FromUInt32((uint)FlatColor.Wisteria7),
Color.FromUInt32((uint)FlatColor.Wisteria8),
Color.FromUInt32((uint)FlatColor.Wisteria9),
Color.FromUInt32((uint)FlatColor.Wisteria10),
},
// Belize Hole
{
Color.FromUInt32((uint)FlatColor.BelizeHole1),
Color.FromUInt32((uint)FlatColor.BelizeHole2),
Color.FromUInt32((uint)FlatColor.BelizeHole3),
Color.FromUInt32((uint)FlatColor.BelizeHole4),
Color.FromUInt32((uint)FlatColor.BelizeHole5),
Color.FromUInt32((uint)FlatColor.BelizeHole6),
Color.FromUInt32((uint)FlatColor.BelizeHole7),
Color.FromUInt32((uint)FlatColor.BelizeHole8),
Color.FromUInt32((uint)FlatColor.BelizeHole9),
Color.FromUInt32((uint)FlatColor.BelizeHole10),
},
// Peter River
{
Color.FromUInt32((uint)FlatColor.PeterRiver1),
Color.FromUInt32((uint)FlatColor.PeterRiver2),
Color.FromUInt32((uint)FlatColor.PeterRiver3),
Color.FromUInt32((uint)FlatColor.PeterRiver4),
Color.FromUInt32((uint)FlatColor.PeterRiver5),
Color.FromUInt32((uint)FlatColor.PeterRiver6),
Color.FromUInt32((uint)FlatColor.PeterRiver7),
Color.FromUInt32((uint)FlatColor.PeterRiver8),
Color.FromUInt32((uint)FlatColor.PeterRiver9),
Color.FromUInt32((uint)FlatColor.PeterRiver10),
},
// Turquoise
{
Color.FromUInt32((uint)FlatColor.Turquoise1),
Color.FromUInt32((uint)FlatColor.Turquoise2),
Color.FromUInt32((uint)FlatColor.Turquoise3),
Color.FromUInt32((uint)FlatColor.Turquoise4),
Color.FromUInt32((uint)FlatColor.Turquoise5),
Color.FromUInt32((uint)FlatColor.Turquoise6),
Color.FromUInt32((uint)FlatColor.Turquoise7),
Color.FromUInt32((uint)FlatColor.Turquoise8),
Color.FromUInt32((uint)FlatColor.Turquoise9),
Color.FromUInt32((uint)FlatColor.Turquoise10),
},
// Green Sea
{
Color.FromUInt32((uint)FlatColor.GreenSea1),
Color.FromUInt32((uint)FlatColor.GreenSea2),
Color.FromUInt32((uint)FlatColor.GreenSea3),
Color.FromUInt32((uint)FlatColor.GreenSea4),
Color.FromUInt32((uint)FlatColor.GreenSea5),
Color.FromUInt32((uint)FlatColor.GreenSea6),
Color.FromUInt32((uint)FlatColor.GreenSea7),
Color.FromUInt32((uint)FlatColor.GreenSea8),
Color.FromUInt32((uint)FlatColor.GreenSea9),
Color.FromUInt32((uint)FlatColor.GreenSea10),
},
// Nephritis
{
Color.FromUInt32((uint)FlatColor.Nephritis1),
Color.FromUInt32((uint)FlatColor.Nephritis2),
Color.FromUInt32((uint)FlatColor.Nephritis3),
Color.FromUInt32((uint)FlatColor.Nephritis4),
Color.FromUInt32((uint)FlatColor.Nephritis5),
Color.FromUInt32((uint)FlatColor.Nephritis6),
Color.FromUInt32((uint)FlatColor.Nephritis7),
Color.FromUInt32((uint)FlatColor.Nephritis8),
Color.FromUInt32((uint)FlatColor.Nephritis9),
Color.FromUInt32((uint)FlatColor.Nephritis10),
},
// Emerald
{
Color.FromUInt32((uint)FlatColor.Emerald1),
Color.FromUInt32((uint)FlatColor.Emerald2),
Color.FromUInt32((uint)FlatColor.Emerald3),
Color.FromUInt32((uint)FlatColor.Emerald4),
Color.FromUInt32((uint)FlatColor.Emerald5),
Color.FromUInt32((uint)FlatColor.Emerald6),
Color.FromUInt32((uint)FlatColor.Emerald7),
Color.FromUInt32((uint)FlatColor.Emerald8),
Color.FromUInt32((uint)FlatColor.Emerald9),
Color.FromUInt32((uint)FlatColor.Emerald10),
},
// Sunflower
{
Color.FromUInt32((uint)FlatColor.Sunflower1),
Color.FromUInt32((uint)FlatColor.Sunflower2),
Color.FromUInt32((uint)FlatColor.Sunflower3),
Color.FromUInt32((uint)FlatColor.Sunflower4),
Color.FromUInt32((uint)FlatColor.Sunflower5),
Color.FromUInt32((uint)FlatColor.Sunflower6),
Color.FromUInt32((uint)FlatColor.Sunflower7),
Color.FromUInt32((uint)FlatColor.Sunflower8),
Color.FromUInt32((uint)FlatColor.Sunflower9),
Color.FromUInt32((uint)FlatColor.Sunflower10),
},
// Orange
{
Color.FromUInt32((uint)FlatColor.Orange1),
Color.FromUInt32((uint)FlatColor.Orange2),
Color.FromUInt32((uint)FlatColor.Orange3),
Color.FromUInt32((uint)FlatColor.Orange4),
Color.FromUInt32((uint)FlatColor.Orange5),
Color.FromUInt32((uint)FlatColor.Orange6),
Color.FromUInt32((uint)FlatColor.Orange7),
Color.FromUInt32((uint)FlatColor.Orange8),
Color.FromUInt32((uint)FlatColor.Orange9),
Color.FromUInt32((uint)FlatColor.Orange10),
},
// Carrot
{
Color.FromUInt32((uint)FlatColor.Carrot1),
Color.FromUInt32((uint)FlatColor.Carrot2),
Color.FromUInt32((uint)FlatColor.Carrot3),
Color.FromUInt32((uint)FlatColor.Carrot4),
Color.FromUInt32((uint)FlatColor.Carrot5),
Color.FromUInt32((uint)FlatColor.Carrot6),
Color.FromUInt32((uint)FlatColor.Carrot7),
Color.FromUInt32((uint)FlatColor.Carrot8),
Color.FromUInt32((uint)FlatColor.Carrot9),
Color.FromUInt32((uint)FlatColor.Carrot10),
},
// Pumpkin
{
Color.FromUInt32((uint)FlatColor.Pumpkin1),
Color.FromUInt32((uint)FlatColor.Pumpkin2),
Color.FromUInt32((uint)FlatColor.Pumpkin3),
Color.FromUInt32((uint)FlatColor.Pumpkin4),
Color.FromUInt32((uint)FlatColor.Pumpkin5),
Color.FromUInt32((uint)FlatColor.Pumpkin6),
Color.FromUInt32((uint)FlatColor.Pumpkin7),
Color.FromUInt32((uint)FlatColor.Pumpkin8),
Color.FromUInt32((uint)FlatColor.Pumpkin9),
Color.FromUInt32((uint)FlatColor.Pumpkin10),
},
// Clouds
{
Color.FromUInt32((uint)FlatColor.Clouds1),
Color.FromUInt32((uint)FlatColor.Clouds2),
Color.FromUInt32((uint)FlatColor.Clouds3),
Color.FromUInt32((uint)FlatColor.Clouds4),
Color.FromUInt32((uint)FlatColor.Clouds5),
Color.FromUInt32((uint)FlatColor.Clouds6),
Color.FromUInt32((uint)FlatColor.Clouds7),
Color.FromUInt32((uint)FlatColor.Clouds8),
Color.FromUInt32((uint)FlatColor.Clouds9),
Color.FromUInt32((uint)FlatColor.Clouds10),
},
// Silver
{
Color.FromUInt32((uint)FlatColor.Silver1),
Color.FromUInt32((uint)FlatColor.Silver2),
Color.FromUInt32((uint)FlatColor.Silver3),
Color.FromUInt32((uint)FlatColor.Silver4),
Color.FromUInt32((uint)FlatColor.Silver5),
Color.FromUInt32((uint)FlatColor.Silver6),
Color.FromUInt32((uint)FlatColor.Silver7),
Color.FromUInt32((uint)FlatColor.Silver8),
Color.FromUInt32((uint)FlatColor.Silver9),
Color.FromUInt32((uint)FlatColor.Silver10),
},
// Concrete
{
Color.FromUInt32((uint)FlatColor.Concrete1),
Color.FromUInt32((uint)FlatColor.Concrete2),
Color.FromUInt32((uint)FlatColor.Concrete3),
Color.FromUInt32((uint)FlatColor.Concrete4),
Color.FromUInt32((uint)FlatColor.Concrete5),
Color.FromUInt32((uint)FlatColor.Concrete6),
Color.FromUInt32((uint)FlatColor.Concrete7),
Color.FromUInt32((uint)FlatColor.Concrete8),
Color.FromUInt32((uint)FlatColor.Concrete9),
Color.FromUInt32((uint)FlatColor.Concrete10),
},
// Asbestos
{
Color.FromUInt32((uint)FlatColor.Asbestos1),
Color.FromUInt32((uint)FlatColor.Asbestos2),
Color.FromUInt32((uint)FlatColor.Asbestos3),
Color.FromUInt32((uint)FlatColor.Asbestos4),
Color.FromUInt32((uint)FlatColor.Asbestos5),
Color.FromUInt32((uint)FlatColor.Asbestos6),
Color.FromUInt32((uint)FlatColor.Asbestos7),
Color.FromUInt32((uint)FlatColor.Asbestos8),
Color.FromUInt32((uint)FlatColor.Asbestos9),
Color.FromUInt32((uint)FlatColor.Asbestos10),
},
// Wet Asphalt
{
Color.FromUInt32((uint)FlatColor.WetAsphalt1),
Color.FromUInt32((uint)FlatColor.WetAsphalt2),
Color.FromUInt32((uint)FlatColor.WetAsphalt3),
Color.FromUInt32((uint)FlatColor.WetAsphalt4),
Color.FromUInt32((uint)FlatColor.WetAsphalt5),
Color.FromUInt32((uint)FlatColor.WetAsphalt6),
Color.FromUInt32((uint)FlatColor.WetAsphalt7),
Color.FromUInt32((uint)FlatColor.WetAsphalt8),
Color.FromUInt32((uint)FlatColor.WetAsphalt9),
Color.FromUInt32((uint)FlatColor.WetAsphalt10),
},
// Midnight Blue
{
Color.FromUInt32((uint)FlatColor.MidnightBlue1),
Color.FromUInt32((uint)FlatColor.MidnightBlue2),
Color.FromUInt32((uint)FlatColor.MidnightBlue3),
Color.FromUInt32((uint)FlatColor.MidnightBlue4),
Color.FromUInt32((uint)FlatColor.MidnightBlue5),
Color.FromUInt32((uint)FlatColor.MidnightBlue6),
Color.FromUInt32((uint)FlatColor.MidnightBlue7),
Color.FromUInt32((uint)FlatColor.MidnightBlue8),
Color.FromUInt32((uint)FlatColor.MidnightBlue9),
Color.FromUInt32((uint)FlatColor.MidnightBlue10),
},
};
}
return;
}
/// <inheritdoc/> /// <inheritdoc/>
public int ColorCount public int ColorCount
{ {
// Table is transposed compared to the reference chart get => 20;
get => colorChart.GetLength(0);
} }
/// <inheritdoc/> /// <inheritdoc/>
public int ShadeCount public int ShadeCount
{ {
// Table is transposed compared to the reference chart get => 10;
get => colorChart.GetLength(1);
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFC0392B.
/// </summary>
public static Color Pomegranate
{
get => colorChart[PomegranateIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF9B59B6.
/// </summary>
public static Color Amethyst
{
get => colorChart[AmethystIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF2980B9.
/// </summary>
public static Color BelizeHole
{
get => colorChart[BelizeHoleIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF1ABC9C.
/// </summary>
public static Color Turquoise
{
get => colorChart[TurquoiseIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF27AE60.
/// </summary>
public static Color Nephritis
{
get => colorChart[NephritisIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFF1C40F.
/// </summary>
public static Color Sunflower
{
get => colorChart[SunflowerIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFE67E22.
/// </summary>
public static Color Carrot
{
get => colorChart[CarrotIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFECF0F1.
/// </summary>
public static Color Clouds
{
get => colorChart[CloudsIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF95A5A6.
/// </summary>
public static Color Concrete
{
get => colorChart[ConcreteIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF34495E.
/// </summary>
public static Color WetAsphalt
{
get => colorChart[WetAsphaltIndex, DefaultShadeIndex];
} }
/// <inheritdoc/> /// <inheritdoc/>
public Color GetColor(int colorIndex, int shadeIndex) public Color GetColor(int colorIndex, int shadeIndex)
{ {
// Table is transposed compared to the reference chart if (_colorChart == null)
return colorChart[ {
MathUtilities.Clamp(colorIndex, 0, colorChart.GetLength(0) - 1), InitColorChart();
MathUtilities.Clamp(shadeIndex, 0, colorChart.GetLength(1) - 1)]; }
return _colorChart![
MathUtilities.Clamp(colorIndex, 0, ColorCount - 1),
MathUtilities.Clamp(shadeIndex, 0, ShadeCount - 1)];
} }
} }
} }

150
src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs

@ -0,0 +1,150 @@
using Avalonia.Media;
using Avalonia.Utilities;
using FlatColor = Avalonia.Controls.FlatColorPalette.FlatColor;
namespace Avalonia.Controls
{
/// <summary>
/// Implements half of the <see cref="FlatColorPalette"/> for improved usability.
/// </summary>
/// <inheritdoc cref="FlatColorPalette"/>
public class FlatHalfColorPalette : IColorPalette
{
protected static Color[,]? _colorChart = null;
protected static object _colorChartMutex = new object();
/// <summary>
/// Initializes all color chart colors.
/// </summary>
protected void InitColorChart()
{
lock (_colorChartMutex)
{
if (_colorChart != null)
{
return;
}
_colorChart = new Color[,]
{
// Pomegranate
{
Color.FromUInt32((uint)FlatColor.Pomegranate1),
Color.FromUInt32((uint)FlatColor.Pomegranate3),
Color.FromUInt32((uint)FlatColor.Pomegranate5),
Color.FromUInt32((uint)FlatColor.Pomegranate7),
Color.FromUInt32((uint)FlatColor.Pomegranate9),
},
// Amethyst
{
Color.FromUInt32((uint)FlatColor.Amethyst1),
Color.FromUInt32((uint)FlatColor.Amethyst3),
Color.FromUInt32((uint)FlatColor.Amethyst5),
Color.FromUInt32((uint)FlatColor.Amethyst7),
Color.FromUInt32((uint)FlatColor.Amethyst9),
},
// Belize Hole
{
Color.FromUInt32((uint)FlatColor.BelizeHole1),
Color.FromUInt32((uint)FlatColor.BelizeHole3),
Color.FromUInt32((uint)FlatColor.BelizeHole5),
Color.FromUInt32((uint)FlatColor.BelizeHole7),
Color.FromUInt32((uint)FlatColor.BelizeHole9),
},
// Turquoise
{
Color.FromUInt32((uint)FlatColor.Turquoise1),
Color.FromUInt32((uint)FlatColor.Turquoise3),
Color.FromUInt32((uint)FlatColor.Turquoise5),
Color.FromUInt32((uint)FlatColor.Turquoise7),
Color.FromUInt32((uint)FlatColor.Turquoise9),
},
// Nephritis
{
Color.FromUInt32((uint)FlatColor.Nephritis1),
Color.FromUInt32((uint)FlatColor.Nephritis3),
Color.FromUInt32((uint)FlatColor.Nephritis5),
Color.FromUInt32((uint)FlatColor.Nephritis7),
Color.FromUInt32((uint)FlatColor.Nephritis9),
},
// Sunflower
{
Color.FromUInt32((uint)FlatColor.Sunflower1),
Color.FromUInt32((uint)FlatColor.Sunflower3),
Color.FromUInt32((uint)FlatColor.Sunflower5),
Color.FromUInt32((uint)FlatColor.Sunflower7),
Color.FromUInt32((uint)FlatColor.Sunflower9),
},
// Carrot
{
Color.FromUInt32((uint)FlatColor.Carrot1),
Color.FromUInt32((uint)FlatColor.Carrot3),
Color.FromUInt32((uint)FlatColor.Carrot5),
Color.FromUInt32((uint)FlatColor.Carrot7),
Color.FromUInt32((uint)FlatColor.Carrot9),
},
// Clouds
{
Color.FromUInt32((uint)FlatColor.Clouds1),
Color.FromUInt32((uint)FlatColor.Clouds3),
Color.FromUInt32((uint)FlatColor.Clouds5),
Color.FromUInt32((uint)FlatColor.Clouds7),
Color.FromUInt32((uint)FlatColor.Clouds9),
},
// Concrete
{
Color.FromUInt32((uint)FlatColor.Concrete1),
Color.FromUInt32((uint)FlatColor.Concrete3),
Color.FromUInt32((uint)FlatColor.Concrete5),
Color.FromUInt32((uint)FlatColor.Concrete7),
Color.FromUInt32((uint)FlatColor.Concrete9),
},
// Wet Asphalt
{
Color.FromUInt32((uint)FlatColor.WetAsphalt1),
Color.FromUInt32((uint)FlatColor.WetAsphalt3),
Color.FromUInt32((uint)FlatColor.WetAsphalt5),
Color.FromUInt32((uint)FlatColor.WetAsphalt7),
Color.FromUInt32((uint)FlatColor.WetAsphalt9),
},
};
}
return;
}
/// <inheritdoc/>
public int ColorCount
{
get => 10;
}
/// <inheritdoc/>
public int ShadeCount
{
get => 5;
}
/// <inheritdoc/>
public Color GetColor(int colorIndex, int shadeIndex)
{
if (_colorChart == null)
{
InitColorChart();
}
return _colorChart![
MathUtilities.Clamp(colorIndex, 0, ColorCount - 1),
MathUtilities.Clamp(shadeIndex, 0, ShadeCount - 1)];
}
}
}

662
src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs

@ -21,295 +21,529 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
public class MaterialColorPalette : IColorPalette public class MaterialColorPalette : IColorPalette
{ {
/// <summary>
/// Defines all colors in the <see cref="MaterialColorPalette"/>.
/// </summary>
/// <remarks>
/// This is done in an enum to ensure it is compiled into the assembly improving
/// startup performance.
/// </remarks>
public enum MaterialColor : uint
{
// Red
Red50 = 0xFFFFEBEE,
Red100 = 0xFFFFCDD2,
Red200 = 0xFFEF9A9A,
Red300 = 0xFFE57373,
Red400 = 0xFFEF5350,
Red500 = 0xFFF44336,
Red600 = 0xFFE53935,
Red700 = 0xFFD32F2F,
Red800 = 0xFFC62828,
Red900 = 0xFFB71C1C,
// Pink
Pink50 = 0xFFFCE4EC,
Pink100 = 0xFFF8BBD0,
Pink200 = 0xFFF48FB1,
Pink300 = 0xFFF06292,
Pink400 = 0xFFEC407A,
Pink500 = 0xFFE91E63,
Pink600 = 0xFFD81B60,
Pink700 = 0xFFC2185B,
Pink800 = 0xFFAD1457,
Pink900 = 0xFF880E4F,
// Purple
Purple50 = 0xFFF3E5F5,
Purple100 = 0xFFE1BEE7,
Purple200 = 0xFFCE93D8,
Purple300 = 0xFFBA68C8,
Purple400 = 0xFFAB47BC,
Purple500 = 0xFF9C27B0,
Purple600 = 0xFF8E24AA,
Purple700 = 0xFF7B1FA2,
Purple800 = 0xFF6A1B9A,
Purple900 = 0xFF4A148C,
// Deep Purple
DeepPurple50 = 0xFFEDE7F6,
DeepPurple100 = 0xFFD1C4E9,
DeepPurple200 = 0xFFB39DDB,
DeepPurple300 = 0xFF9575CD,
DeepPurple400 = 0xFF7E57C2,
DeepPurple500 = 0xFF673AB7,
DeepPurple600 = 0xFF5E35B1,
DeepPurple700 = 0xFF512DA8,
DeepPurple800 = 0xFF4527A0,
DeepPurple900 = 0xFF311B92,
// Indigo
Indigo50 = 0xFFE8EAF6,
Indigo100 = 0xFFC5CAE9,
Indigo200 = 0xFF9FA8DA,
Indigo300 = 0xFF7986CB,
Indigo400 = 0xFF5C6BC0,
Indigo500 = 0xFF3F51B5,
Indigo600 = 0xFF3949AB,
Indigo700 = 0xFF303F9F,
Indigo800 = 0xFF283593,
Indigo900 = 0xFF1A237E,
// Blue
Blue50 = 0xFFE3F2FD,
Blue100 = 0xFFBBDEFB,
Blue200 = 0xFF90CAF9,
Blue300 = 0xFF64B5F6,
Blue400 = 0xFF42A5F5,
Blue500 = 0xFF2196F3,
Blue600 = 0xFF1E88E5,
Blue700 = 0xFF1976D2,
Blue800 = 0xFF1565C0,
Blue900 = 0xFF0D47A1,
// Light Blue
LightBlue50 = 0xFFE1F5FE,
LightBlue100 = 0xFFB3E5FC,
LightBlue200 = 0xFF81D4FA,
LightBlue300 = 0xFF4FC3F7,
LightBlue400 = 0xFF29B6F6,
LightBlue500 = 0xFF03A9F4,
LightBlue600 = 0xFF039BE5,
LightBlue700 = 0xFF0288D1,
LightBlue800 = 0xFF0277BD,
LightBlue900 = 0xFF01579B,
// Cyan
Cyan50 = 0xFFE0F7FA,
Cyan100 = 0xFFB2EBF2,
Cyan200 = 0xFF80DEEA,
Cyan300 = 0xFF4DD0E1,
Cyan400 = 0xFF26C6DA,
Cyan500 = 0xFF00BCD4,
Cyan600 = 0xFF00ACC1,
Cyan700 = 0xFF0097A7,
Cyan800 = 0xFF00838F,
Cyan900 = 0xFF006064,
// Teal
Teal50 = 0xFFE0F2F1,
Teal100 = 0xFFB2DFDB,
Teal200 = 0xFF80CBC4,
Teal300 = 0xFF4DB6AC,
Teal400 = 0xFF26A69A,
Teal500 = 0xFF009688,
Teal600 = 0xFF00897B,
Teal700 = 0xFF00796B,
Teal800 = 0xFF00695C,
Teal900 = 0xFF004D40,
// Green
Green50 = 0xFFE8F5E9,
Green100 = 0xFFC8E6C9,
Green200 = 0xFFA5D6A7,
Green300 = 0xFF81C784,
Green400 = 0xFF66BB6A,
Green500 = 0xFF4CAF50,
Green600 = 0xFF43A047,
Green700 = 0xFF388E3C,
Green800 = 0xFF2E7D32,
Green900 = 0xFF1B5E20,
// Light Green
LightGreen50 = 0xFFF1F8E9,
LightGreen100 = 0xFFDCEDC8,
LightGreen200 = 0xFFC5E1A5,
LightGreen300 = 0xFFAED581,
LightGreen400 = 0xFF9CCC65,
LightGreen500 = 0xFF8BC34A,
LightGreen600 = 0xFF7CB342,
LightGreen700 = 0xFF689F38,
LightGreen800 = 0xFF558B2F,
LightGreen900 = 0xFF33691E,
// Lime
Lime50 = 0xFFF9FBE7,
Lime100 = 0xFFF0F4C3,
Lime200 = 0xFFE6EE9C,
Lime300 = 0xFFDCE775,
Lime400 = 0xFFD4E157,
Lime500 = 0xFFCDDC39,
Lime600 = 0xFFC0CA33,
Lime700 = 0xFFAFB42B,
Lime800 = 0xFF9E9D24,
Lime900 = 0xFF827717,
// Yellow
Yellow50 = 0xFFFFFDE7,
Yellow100 = 0xFFFFF9C4,
Yellow200 = 0xFFFFF59D,
Yellow300 = 0xFFFFF176,
Yellow400 = 0xFFFFEE58,
Yellow500 = 0xFFFFEB3B,
Yellow600 = 0xFFFDD835,
Yellow700 = 0xFFFBC02D,
Yellow800 = 0xFFF9A825,
Yellow900 = 0xFFF57F17,
// Amber
Amber50 = 0xFFFFF8E1,
Amber100 = 0xFFFFECB3,
Amber200 = 0xFFFFE082,
Amber300 = 0xFFFFD54F,
Amber400 = 0xFFFFCA28,
Amber500 = 0xFFFFC107,
Amber600 = 0xFFFFB300,
Amber700 = 0xFFFFA000,
Amber800 = 0xFFFF8F00,
Amber900 = 0xFFFF6F00,
// Orange
Orange50 = 0xFFFFF3E0,
Orange100 = 0xFFFFE0B2,
Orange200 = 0xFFFFCC80,
Orange300 = 0xFFFFB74D,
Orange400 = 0xFFFFA726,
Orange500 = 0xFFFF9800,
Orange600 = 0xFFFB8C00,
Orange700 = 0xFFF57C00,
Orange800 = 0xFFEF6C00,
Orange900 = 0xFFE65100,
// Deep Orange
DeepOrange50 = 0xFFFBE9E7,
DeepOrange100 = 0xFFFFCCBC,
DeepOrange200 = 0xFFFFAB91,
DeepOrange300 = 0xFFFF8A65,
DeepOrange400 = 0xFFFF7043,
DeepOrange500 = 0xFFFF5722,
DeepOrange600 = 0xFFF4511E,
DeepOrange700 = 0xFFE64A19,
DeepOrange800 = 0xFFD84315,
DeepOrange900 = 0xFFBF360C,
// Brown
Brown50 = 0xFFEFEBE9,
Brown100 = 0xFFD7CCC8,
Brown200 = 0xFFBCAAA4,
Brown300 = 0xFFA1887F,
Brown400 = 0xFF8D6E63,
Brown500 = 0xFF795548,
Brown600 = 0xFF6D4C41,
Brown700 = 0xFF5D4037,
Brown800 = 0xFF4E342E,
Brown900 = 0xFF3E2723,
// Gray
Gray50 = 0xFFFAFAFA,
Gray100 = 0xFFF5F5F5,
Gray200 = 0xFFEEEEEE,
Gray300 = 0xFFE0E0E0,
Gray400 = 0xFFBDBDBD,
Gray500 = 0xFF9E9E9E,
Gray600 = 0xFF757575,
Gray700 = 0xFF616161,
Gray800 = 0xFF424242,
Gray900 = 0xFF212121,
// Blue Gray
BlueGray50 = 0xFFECEFF1,
BlueGray100 = 0xFFCFD8DC,
BlueGray200 = 0xFFB0BEC5,
BlueGray300 = 0xFF90A4AE,
BlueGray400 = 0xFF78909C,
BlueGray500 = 0xFF607D8B,
BlueGray600 = 0xFF546E7A,
BlueGray700 = 0xFF455A64,
BlueGray800 = 0xFF37474F,
BlueGray900 = 0xFF263238,
}
// See: https://material.io/design/color/the-color-system.html#tools-for-picking-colors // See: https://material.io/design/color/the-color-system.html#tools-for-picking-colors
// This is a reduced palette for uniformity // This is a reduced palette for uniformity
private static Color[,]? _colorChart = null; protected static Color[,]? _colorChart = null;
private static int _colorChartColorCount = 0; protected static object _colorChartMutex = new object();
private static int _colorChartShadeCount = 0;
private static object _colorChartMutex = new object();
/// <summary> /// <summary>
/// Initializes all color chart colors. /// Initializes all color chart colors.
/// </summary> /// </summary>
/// <remarks> protected void InitColorChart()
/// This is pulled out separately to lazy load for performance.
/// If no material color palette is ever used, no colors will be created.
/// </remarks>
private void InitColorChart()
{ {
lock (_colorChartMutex) lock (_colorChartMutex)
{ {
if (_colorChart != null)
{
return;
}
_colorChart = new Color[,] _colorChart = new Color[,]
{ {
// Red // Red
{ {
Color.FromArgb(0xFF, 0xFF, 0xEB, 0xEE), Color.FromUInt32((uint)MaterialColor.Red50),
Color.FromArgb(0xFF, 0xFF, 0xCD, 0xD2), Color.FromUInt32((uint)MaterialColor.Red100),
Color.FromArgb(0xFF, 0xEF, 0x9A, 0x9A), Color.FromUInt32((uint)MaterialColor.Red200),
Color.FromArgb(0xFF, 0xE5, 0x73, 0x73), Color.FromUInt32((uint)MaterialColor.Red300),
Color.FromArgb(0xFF, 0xEF, 0x53, 0x50), Color.FromUInt32((uint)MaterialColor.Red400),
Color.FromArgb(0xFF, 0xF4, 0x43, 0x36), Color.FromUInt32((uint)MaterialColor.Red500),
Color.FromArgb(0xFF, 0xE5, 0x39, 0x35), Color.FromUInt32((uint)MaterialColor.Red600),
Color.FromArgb(0xFF, 0xD3, 0x2F, 0x2F), Color.FromUInt32((uint)MaterialColor.Red700),
Color.FromArgb(0xFF, 0xC6, 0x28, 0x28), Color.FromUInt32((uint)MaterialColor.Red800),
Color.FromArgb(0xFF, 0xB7, 0x1C, 0x1C), Color.FromUInt32((uint)MaterialColor.Red900),
}, },
// Pink // Pink
{ {
Color.FromArgb(0xFF, 0xFC, 0xE4, 0xEC), Color.FromUInt32((uint)MaterialColor.Pink50),
Color.FromArgb(0xFF, 0xF8, 0xBB, 0xD0), Color.FromUInt32((uint)MaterialColor.Pink100),
Color.FromArgb(0xFF, 0xF4, 0x8F, 0xB1), Color.FromUInt32((uint)MaterialColor.Pink200),
Color.FromArgb(0xFF, 0xF0, 0x62, 0x92), Color.FromUInt32((uint)MaterialColor.Pink300),
Color.FromArgb(0xFF, 0xEC, 0x40, 0x7A), Color.FromUInt32((uint)MaterialColor.Pink400),
Color.FromArgb(0xFF, 0xE9, 0x1E, 0x63), Color.FromUInt32((uint)MaterialColor.Pink500),
Color.FromArgb(0xFF, 0xD8, 0x1B, 0x60), Color.FromUInt32((uint)MaterialColor.Pink600),
Color.FromArgb(0xFF, 0xC2, 0x18, 0x5B), Color.FromUInt32((uint)MaterialColor.Pink700),
Color.FromArgb(0xFF, 0xAD, 0x14, 0x57), Color.FromUInt32((uint)MaterialColor.Pink800),
Color.FromArgb(0xFF, 0x88, 0x0E, 0x4F), Color.FromUInt32((uint)MaterialColor.Pink900),
}, },
// Purple // Purple
{ {
Color.FromArgb(0xFF, 0xF3, 0xE5, 0xF5), Color.FromUInt32((uint)MaterialColor.Purple50),
Color.FromArgb(0xFF, 0xE1, 0xBE, 0xE7), Color.FromUInt32((uint)MaterialColor.Purple100),
Color.FromArgb(0xFF, 0xCE, 0x93, 0xD8), Color.FromUInt32((uint)MaterialColor.Purple200),
Color.FromArgb(0xFF, 0xBA, 0x68, 0xC8), Color.FromUInt32((uint)MaterialColor.Purple300),
Color.FromArgb(0xFF, 0xAB, 0x47, 0xBC), Color.FromUInt32((uint)MaterialColor.Purple400),
Color.FromArgb(0xFF, 0x9C, 0x27, 0xB0), Color.FromUInt32((uint)MaterialColor.Purple500),
Color.FromArgb(0xFF, 0x8E, 0x24, 0xAA), Color.FromUInt32((uint)MaterialColor.Purple600),
Color.FromArgb(0xFF, 0x7B, 0x1F, 0xA2), Color.FromUInt32((uint)MaterialColor.Purple700),
Color.FromArgb(0xFF, 0x6A, 0x1B, 0x9A), Color.FromUInt32((uint)MaterialColor.Purple800),
Color.FromArgb(0xFF, 0x4A, 0x14, 0x8C), Color.FromUInt32((uint)MaterialColor.Purple900),
}, },
// Deep Purple // Deep Purple
{ {
Color.FromArgb(0xFF, 0xED, 0xE7, 0xF6), Color.FromUInt32((uint)MaterialColor.DeepPurple50),
Color.FromArgb(0xFF, 0xD1, 0xC4, 0xE9), Color.FromUInt32((uint)MaterialColor.DeepPurple100),
Color.FromArgb(0xFF, 0xB3, 0x9D, 0xDB), Color.FromUInt32((uint)MaterialColor.DeepPurple200),
Color.FromArgb(0xFF, 0x95, 0x75, 0xCD), Color.FromUInt32((uint)MaterialColor.DeepPurple300),
Color.FromArgb(0xFF, 0x7E, 0x57, 0xC2), Color.FromUInt32((uint)MaterialColor.DeepPurple400),
Color.FromArgb(0xFF, 0x67, 0x3A, 0xB7), Color.FromUInt32((uint)MaterialColor.DeepPurple500),
Color.FromArgb(0xFF, 0x5E, 0x35, 0xB1), Color.FromUInt32((uint)MaterialColor.DeepPurple600),
Color.FromArgb(0xFF, 0x51, 0x2D, 0xA8), Color.FromUInt32((uint)MaterialColor.DeepPurple700),
Color.FromArgb(0xFF, 0x45, 0x27, 0xA0), Color.FromUInt32((uint)MaterialColor.DeepPurple800),
Color.FromArgb(0xFF, 0x31, 0x1B, 0x92), Color.FromUInt32((uint)MaterialColor.DeepPurple900),
}, },
// Indigo // Indigo
{ {
Color.FromArgb(0xFF, 0xE8, 0xEA, 0xF6), Color.FromUInt32((uint)MaterialColor.Indigo50),
Color.FromArgb(0xFF, 0xC5, 0xCA, 0xE9), Color.FromUInt32((uint)MaterialColor.Indigo100),
Color.FromArgb(0xFF, 0x9F, 0xA8, 0xDA), Color.FromUInt32((uint)MaterialColor.Indigo200),
Color.FromArgb(0xFF, 0x79, 0x86, 0xCB), Color.FromUInt32((uint)MaterialColor.Indigo300),
Color.FromArgb(0xFF, 0x5C, 0x6B, 0xC0), Color.FromUInt32((uint)MaterialColor.Indigo400),
Color.FromArgb(0xFF, 0x3F, 0x51, 0xB5), Color.FromUInt32((uint)MaterialColor.Indigo500),
Color.FromArgb(0xFF, 0x39, 0x49, 0xAB), Color.FromUInt32((uint)MaterialColor.Indigo600),
Color.FromArgb(0xFF, 0x30, 0x3F, 0x9F), Color.FromUInt32((uint)MaterialColor.Indigo700),
Color.FromArgb(0xFF, 0x28, 0x35, 0x93), Color.FromUInt32((uint)MaterialColor.Indigo800),
Color.FromArgb(0xFF, 0x1A, 0x23, 0x7E), Color.FromUInt32((uint)MaterialColor.Indigo900),
}, },
// Blue // Blue
{ {
Color.FromArgb(0xFF, 0xE3, 0xF2, 0xFD), Color.FromUInt32((uint)MaterialColor.Blue50),
Color.FromArgb(0xFF, 0xBB, 0xDE, 0xFB), Color.FromUInt32((uint)MaterialColor.Blue100),
Color.FromArgb(0xFF, 0x90, 0xCA, 0xF9), Color.FromUInt32((uint)MaterialColor.Blue200),
Color.FromArgb(0xFF, 0x64, 0xB5, 0xF6), Color.FromUInt32((uint)MaterialColor.Blue300),
Color.FromArgb(0xFF, 0x42, 0xA5, 0xF5), Color.FromUInt32((uint)MaterialColor.Blue400),
Color.FromArgb(0xFF, 0x21, 0x96, 0xF3), Color.FromUInt32((uint)MaterialColor.Blue500),
Color.FromArgb(0xFF, 0x1E, 0x88, 0xE5), Color.FromUInt32((uint)MaterialColor.Blue600),
Color.FromArgb(0xFF, 0x19, 0x76, 0xD2), Color.FromUInt32((uint)MaterialColor.Blue700),
Color.FromArgb(0xFF, 0x15, 0x65, 0xC0), Color.FromUInt32((uint)MaterialColor.Blue800),
Color.FromArgb(0xFF, 0x0D, 0x47, 0xA1), Color.FromUInt32((uint)MaterialColor.Blue900),
}, },
// Light Blue // Light Blue
{ {
Color.FromArgb(0xFF, 0xE1, 0xF5, 0xFE), Color.FromUInt32((uint)MaterialColor.LightBlue50),
Color.FromArgb(0xFF, 0xB3, 0xE5, 0xFC), Color.FromUInt32((uint)MaterialColor.LightBlue100),
Color.FromArgb(0xFF, 0x81, 0xD4, 0xFA), Color.FromUInt32((uint)MaterialColor.LightBlue200),
Color.FromArgb(0xFF, 0x4F, 0xC3, 0xF7), Color.FromUInt32((uint)MaterialColor.LightBlue300),
Color.FromArgb(0xFF, 0x29, 0xB6, 0xF6), Color.FromUInt32((uint)MaterialColor.LightBlue400),
Color.FromArgb(0xFF, 0x03, 0xA9, 0xF4), Color.FromUInt32((uint)MaterialColor.LightBlue500),
Color.FromArgb(0xFF, 0x03, 0x9B, 0xE5), Color.FromUInt32((uint)MaterialColor.LightBlue600),
Color.FromArgb(0xFF, 0x02, 0x88, 0xD1), Color.FromUInt32((uint)MaterialColor.LightBlue700),
Color.FromArgb(0xFF, 0x02, 0x77, 0xBD), Color.FromUInt32((uint)MaterialColor.LightBlue800),
Color.FromArgb(0xFF, 0x01, 0x57, 0x9B), Color.FromUInt32((uint)MaterialColor.LightBlue900),
}, },
// Cyan // Cyan
{ {
Color.FromArgb(0xFF, 0xE0, 0xF7, 0xFA), Color.FromUInt32((uint)MaterialColor.Cyan50),
Color.FromArgb(0xFF, 0xB2, 0xEB, 0xF2), Color.FromUInt32((uint)MaterialColor.Cyan100),
Color.FromArgb(0xFF, 0x80, 0xDE, 0xEA), Color.FromUInt32((uint)MaterialColor.Cyan200),
Color.FromArgb(0xFF, 0x4D, 0xD0, 0xE1), Color.FromUInt32((uint)MaterialColor.Cyan300),
Color.FromArgb(0xFF, 0x26, 0xC6, 0xDA), Color.FromUInt32((uint)MaterialColor.Cyan400),
Color.FromArgb(0xFF, 0x00, 0xBC, 0xD4), Color.FromUInt32((uint)MaterialColor.Cyan500),
Color.FromArgb(0xFF, 0x00, 0xAC, 0xC1), Color.FromUInt32((uint)MaterialColor.Cyan600),
Color.FromArgb(0xFF, 0x00, 0x97, 0xA7), Color.FromUInt32((uint)MaterialColor.Cyan700),
Color.FromArgb(0xFF, 0x00, 0x83, 0x8F), Color.FromUInt32((uint)MaterialColor.Cyan800),
Color.FromArgb(0xFF, 0x00, 0x60, 0x64), Color.FromUInt32((uint)MaterialColor.Cyan900),
}, },
// Teal // Teal
{ {
Color.FromArgb(0xFF, 0xE0, 0xF2, 0xF1), Color.FromUInt32((uint)MaterialColor.Teal50),
Color.FromArgb(0xFF, 0xB2, 0xDF, 0xDB), Color.FromUInt32((uint)MaterialColor.Teal100),
Color.FromArgb(0xFF, 0x80, 0xCB, 0xC4), Color.FromUInt32((uint)MaterialColor.Teal200),
Color.FromArgb(0xFF, 0x4D, 0xB6, 0xAC), Color.FromUInt32((uint)MaterialColor.Teal300),
Color.FromArgb(0xFF, 0x26, 0xA6, 0x9A), Color.FromUInt32((uint)MaterialColor.Teal400),
Color.FromArgb(0xFF, 0x00, 0x96, 0x88), Color.FromUInt32((uint)MaterialColor.Teal500),
Color.FromArgb(0xFF, 0x00, 0x89, 0x7B), Color.FromUInt32((uint)MaterialColor.Teal600),
Color.FromArgb(0xFF, 0x00, 0x79, 0x6B), Color.FromUInt32((uint)MaterialColor.Teal700),
Color.FromArgb(0xFF, 0x00, 0x69, 0x5C), Color.FromUInt32((uint)MaterialColor.Teal800),
Color.FromArgb(0xFF, 0x00, 0x4D, 0x40), Color.FromUInt32((uint)MaterialColor.Teal900),
}, },
// Green // Green
{ {
Color.FromArgb(0xFF, 0xE8, 0xF5, 0xE9), Color.FromUInt32((uint)MaterialColor.Green50),
Color.FromArgb(0xFF, 0xC8, 0xE6, 0xC9), Color.FromUInt32((uint)MaterialColor.Green100),
Color.FromArgb(0xFF, 0xA5, 0xD6, 0xA7), Color.FromUInt32((uint)MaterialColor.Green200),
Color.FromArgb(0xFF, 0x81, 0xC7, 0x84), Color.FromUInt32((uint)MaterialColor.Green300),
Color.FromArgb(0xFF, 0x66, 0xBB, 0x6A), Color.FromUInt32((uint)MaterialColor.Green400),
Color.FromArgb(0xFF, 0x4C, 0xAF, 0x50), Color.FromUInt32((uint)MaterialColor.Green500),
Color.FromArgb(0xFF, 0x43, 0xA0, 0x47), Color.FromUInt32((uint)MaterialColor.Green600),
Color.FromArgb(0xFF, 0x38, 0x8E, 0x3C), Color.FromUInt32((uint)MaterialColor.Green700),
Color.FromArgb(0xFF, 0x2E, 0x7D, 0x32), Color.FromUInt32((uint)MaterialColor.Green800),
Color.FromArgb(0xFF, 0x1B, 0x5E, 0x20), Color.FromUInt32((uint)MaterialColor.Green900),
}, },
// Light Green // Light Green
{ {
Color.FromArgb(0xFF, 0xF1, 0xF8, 0xE9), Color.FromUInt32((uint)MaterialColor.LightGreen50),
Color.FromArgb(0xFF, 0xDC, 0xED, 0xC8), Color.FromUInt32((uint)MaterialColor.LightGreen100),
Color.FromArgb(0xFF, 0xC5, 0xE1, 0xA5), Color.FromUInt32((uint)MaterialColor.LightGreen200),
Color.FromArgb(0xFF, 0xAE, 0xD5, 0x81), Color.FromUInt32((uint)MaterialColor.LightGreen300),
Color.FromArgb(0xFF, 0x9C, 0xCC, 0x65), Color.FromUInt32((uint)MaterialColor.LightGreen400),
Color.FromArgb(0xFF, 0x8B, 0xC3, 0x4A), Color.FromUInt32((uint)MaterialColor.LightGreen500),
Color.FromArgb(0xFF, 0x7C, 0xB3, 0x42), Color.FromUInt32((uint)MaterialColor.LightGreen600),
Color.FromArgb(0xFF, 0x68, 0x9F, 0x38), Color.FromUInt32((uint)MaterialColor.LightGreen700),
Color.FromArgb(0xFF, 0x55, 0x8B, 0x2F), Color.FromUInt32((uint)MaterialColor.LightGreen800),
Color.FromArgb(0xFF, 0x33, 0x69, 0x1E), Color.FromUInt32((uint)MaterialColor.LightGreen900),
}, },
// Lime // Lime
{ {
Color.FromArgb(0xFF, 0xF9, 0xFB, 0xE7), Color.FromUInt32((uint)MaterialColor.Lime50),
Color.FromArgb(0xFF, 0xF0, 0xF4, 0xC3), Color.FromUInt32((uint)MaterialColor.Lime100),
Color.FromArgb(0xFF, 0xE6, 0xEE, 0x9C), Color.FromUInt32((uint)MaterialColor.Lime200),
Color.FromArgb(0xFF, 0xDC, 0xE7, 0x75), Color.FromUInt32((uint)MaterialColor.Lime300),
Color.FromArgb(0xFF, 0xD4, 0xE1, 0x57), Color.FromUInt32((uint)MaterialColor.Lime400),
Color.FromArgb(0xFF, 0xCD, 0xDC, 0x39), Color.FromUInt32((uint)MaterialColor.Lime500),
Color.FromArgb(0xFF, 0xC0, 0xCA, 0x33), Color.FromUInt32((uint)MaterialColor.Lime600),
Color.FromArgb(0xFF, 0xAF, 0xB4, 0x2B), Color.FromUInt32((uint)MaterialColor.Lime700),
Color.FromArgb(0xFF, 0x9E, 0x9D, 0x24), Color.FromUInt32((uint)MaterialColor.Lime800),
Color.FromArgb(0xFF, 0x82, 0x77, 0x17), Color.FromUInt32((uint)MaterialColor.Lime900),
}, },
// Yellow // Yellow
{ {
Color.FromArgb(0xFF, 0xFF, 0xFD, 0xE7), Color.FromUInt32((uint)MaterialColor.Yellow50),
Color.FromArgb(0xFF, 0xFF, 0xF9, 0xC4), Color.FromUInt32((uint)MaterialColor.Yellow100),
Color.FromArgb(0xFF, 0xFF, 0xF5, 0x9D), Color.FromUInt32((uint)MaterialColor.Yellow200),
Color.FromArgb(0xFF, 0xFF, 0xF1, 0x76), Color.FromUInt32((uint)MaterialColor.Yellow300),
Color.FromArgb(0xFF, 0xFF, 0xEE, 0x58), Color.FromUInt32((uint)MaterialColor.Yellow400),
Color.FromArgb(0xFF, 0xFF, 0xEB, 0x3B), Color.FromUInt32((uint)MaterialColor.Yellow500),
Color.FromArgb(0xFF, 0xFD, 0xD8, 0x35), Color.FromUInt32((uint)MaterialColor.Yellow600),
Color.FromArgb(0xFF, 0xFB, 0xC0, 0x2D), Color.FromUInt32((uint)MaterialColor.Yellow700),
Color.FromArgb(0xFF, 0xF9, 0xA8, 0x25), Color.FromUInt32((uint)MaterialColor.Yellow800),
Color.FromArgb(0xFF, 0xF5, 0x7F, 0x17), Color.FromUInt32((uint)MaterialColor.Yellow900),
}, },
// Amber // Amber
{ {
Color.FromArgb(0xFF, 0xFF, 0xF8, 0xE1), Color.FromUInt32((uint)MaterialColor.Amber50),
Color.FromArgb(0xFF, 0xFF, 0xEC, 0xB3), Color.FromUInt32((uint)MaterialColor.Amber100),
Color.FromArgb(0xFF, 0xFF, 0xE0, 0x82), Color.FromUInt32((uint)MaterialColor.Amber200),
Color.FromArgb(0xFF, 0xFF, 0xD5, 0x4F), Color.FromUInt32((uint)MaterialColor.Amber300),
Color.FromArgb(0xFF, 0xFF, 0xCA, 0x28), Color.FromUInt32((uint)MaterialColor.Amber400),
Color.FromArgb(0xFF, 0xFF, 0xC1, 0x07), Color.FromUInt32((uint)MaterialColor.Amber500),
Color.FromArgb(0xFF, 0xFF, 0xB3, 0x00), Color.FromUInt32((uint)MaterialColor.Amber600),
Color.FromArgb(0xFF, 0xFF, 0xA0, 0x00), Color.FromUInt32((uint)MaterialColor.Amber700),
Color.FromArgb(0xFF, 0xFF, 0x8F, 0x00), Color.FromUInt32((uint)MaterialColor.Amber800),
Color.FromArgb(0xFF, 0xFF, 0x6F, 0x00), Color.FromUInt32((uint)MaterialColor.Amber900),
}, },
// Orange // Orange
{ {
Color.FromArgb(0xFF, 0xFF, 0xF3, 0xE0), Color.FromUInt32((uint)MaterialColor.Orange50),
Color.FromArgb(0xFF, 0xFF, 0xE0, 0xB2), Color.FromUInt32((uint)MaterialColor.Orange100),
Color.FromArgb(0xFF, 0xFF, 0xCC, 0x80), Color.FromUInt32((uint)MaterialColor.Orange200),
Color.FromArgb(0xFF, 0xFF, 0xB7, 0x4D), Color.FromUInt32((uint)MaterialColor.Orange300),
Color.FromArgb(0xFF, 0xFF, 0xA7, 0x26), Color.FromUInt32((uint)MaterialColor.Orange400),
Color.FromArgb(0xFF, 0xFF, 0x98, 0x00), Color.FromUInt32((uint)MaterialColor.Orange500),
Color.FromArgb(0xFF, 0xFB, 0x8C, 0x00), Color.FromUInt32((uint)MaterialColor.Orange600),
Color.FromArgb(0xFF, 0xF5, 0x7C, 0x00), Color.FromUInt32((uint)MaterialColor.Orange700),
Color.FromArgb(0xFF, 0xEF, 0x6C, 0x00), Color.FromUInt32((uint)MaterialColor.Orange800),
Color.FromArgb(0xFF, 0xE6, 0x51, 0x00), Color.FromUInt32((uint)MaterialColor.Orange900),
}, },
// Deep Orange // Deep Orange
{ {
Color.FromArgb(0xFF, 0xFB, 0xE9, 0xE7), Color.FromUInt32((uint)MaterialColor.DeepOrange50),
Color.FromArgb(0xFF, 0xFF, 0xCC, 0xBC), Color.FromUInt32((uint)MaterialColor.DeepOrange100),
Color.FromArgb(0xFF, 0xFF, 0xAB, 0x91), Color.FromUInt32((uint)MaterialColor.DeepOrange200),
Color.FromArgb(0xFF, 0xFF, 0x8A, 0x65), Color.FromUInt32((uint)MaterialColor.DeepOrange300),
Color.FromArgb(0xFF, 0xFF, 0x70, 0x43), Color.FromUInt32((uint)MaterialColor.DeepOrange400),
Color.FromArgb(0xFF, 0xFF, 0x57, 0x22), Color.FromUInt32((uint)MaterialColor.DeepOrange500),
Color.FromArgb(0xFF, 0xF4, 0x51, 0x1E), Color.FromUInt32((uint)MaterialColor.DeepOrange600),
Color.FromArgb(0xFF, 0xE6, 0x4A, 0x19), Color.FromUInt32((uint)MaterialColor.DeepOrange700),
Color.FromArgb(0xFF, 0xD8, 0x43, 0x15), Color.FromUInt32((uint)MaterialColor.DeepOrange800),
Color.FromArgb(0xFF, 0xBF, 0x36, 0x0C), Color.FromUInt32((uint)MaterialColor.DeepOrange900),
}, },
// Brown // Brown
{ {
Color.FromArgb(0xFF, 0xEF, 0xEB, 0xE9), Color.FromUInt32((uint)MaterialColor.Brown50),
Color.FromArgb(0xFF, 0xD7, 0xCC, 0xC8), Color.FromUInt32((uint)MaterialColor.Brown100),
Color.FromArgb(0xFF, 0xBC, 0xAA, 0xA4), Color.FromUInt32((uint)MaterialColor.Brown200),
Color.FromArgb(0xFF, 0xA1, 0x88, 0x7F), Color.FromUInt32((uint)MaterialColor.Brown300),
Color.FromArgb(0xFF, 0x8D, 0x6E, 0x63), Color.FromUInt32((uint)MaterialColor.Brown400),
Color.FromArgb(0xFF, 0x79, 0x55, 0x48), Color.FromUInt32((uint)MaterialColor.Brown500),
Color.FromArgb(0xFF, 0x6D, 0x4C, 0x41), Color.FromUInt32((uint)MaterialColor.Brown600),
Color.FromArgb(0xFF, 0x5D, 0x40, 0x37), Color.FromUInt32((uint)MaterialColor.Brown700),
Color.FromArgb(0xFF, 0x4E, 0x34, 0x2E), Color.FromUInt32((uint)MaterialColor.Brown800),
Color.FromArgb(0xFF, 0x3E, 0x27, 0x23), Color.FromUInt32((uint)MaterialColor.Brown900),
}, },
// Gray // Gray
{ {
Color.FromArgb(0xFF, 0xFA, 0xFA, 0xFA), Color.FromUInt32((uint)MaterialColor.Gray50),
Color.FromArgb(0xFF, 0xF5, 0xF5, 0xF5), Color.FromUInt32((uint)MaterialColor.Gray100),
Color.FromArgb(0xFF, 0xEE, 0xEE, 0xEE), Color.FromUInt32((uint)MaterialColor.Gray200),
Color.FromArgb(0xFF, 0xE0, 0xE0, 0xE0), Color.FromUInt32((uint)MaterialColor.Gray300),
Color.FromArgb(0xFF, 0xBD, 0xBD, 0xBD), Color.FromUInt32((uint)MaterialColor.Gray400),
Color.FromArgb(0xFF, 0x9E, 0x9E, 0x9E), Color.FromUInt32((uint)MaterialColor.Gray500),
Color.FromArgb(0xFF, 0x75, 0x75, 0x75), Color.FromUInt32((uint)MaterialColor.Gray600),
Color.FromArgb(0xFF, 0x61, 0x61, 0x61), Color.FromUInt32((uint)MaterialColor.Gray700),
Color.FromArgb(0xFF, 0x42, 0x42, 0x42), Color.FromUInt32((uint)MaterialColor.Gray800),
Color.FromArgb(0xFF, 0x21, 0x21, 0x21), Color.FromUInt32((uint)MaterialColor.Gray900),
}, },
// Blue Gray // Blue Gray
{ {
Color.FromArgb(0xFF, 0xEC, 0xEF, 0xF1), Color.FromUInt32((uint)MaterialColor.BlueGray50),
Color.FromArgb(0xFF, 0xCF, 0xD8, 0xDC), Color.FromUInt32((uint)MaterialColor.BlueGray100),
Color.FromArgb(0xFF, 0xB0, 0xBE, 0xC5), Color.FromUInt32((uint)MaterialColor.BlueGray200),
Color.FromArgb(0xFF, 0x90, 0xA4, 0xAE), Color.FromUInt32((uint)MaterialColor.BlueGray300),
Color.FromArgb(0xFF, 0x78, 0x90, 0x9C), Color.FromUInt32((uint)MaterialColor.BlueGray400),
Color.FromArgb(0xFF, 0x60, 0x7D, 0x8B), Color.FromUInt32((uint)MaterialColor.BlueGray500),
Color.FromArgb(0xFF, 0x54, 0x6E, 0x7A), Color.FromUInt32((uint)MaterialColor.BlueGray600),
Color.FromArgb(0xFF, 0x45, 0x5A, 0x64), Color.FromUInt32((uint)MaterialColor.BlueGray700),
Color.FromArgb(0xFF, 0x37, 0x47, 0x4F), Color.FromUInt32((uint)MaterialColor.BlueGray800),
Color.FromArgb(0xFF, 0x26, 0x32, 0x38), Color.FromUInt32((uint)MaterialColor.BlueGray900),
}, },
}; };
_colorChartColorCount = _colorChart.GetLength(0);
_colorChartShadeCount = _colorChart.GetLength(1);
} }
return; return;
@ -318,29 +552,13 @@ namespace Avalonia.Controls
/// <inheritdoc/> /// <inheritdoc/>
public int ColorCount public int ColorCount
{ {
get get => 19;
{
if (_colorChart == null)
{
InitColorChart();
}
return _colorChartColorCount;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public int ShadeCount public int ShadeCount
{ {
get get => 10;
{
if (_colorChart == null)
{
InitColorChart();
}
return _colorChartShadeCount;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -352,8 +570,8 @@ namespace Avalonia.Controls
} }
return _colorChart![ return _colorChart![
MathUtilities.Clamp(colorIndex, 0, _colorChartColorCount - 1), MathUtilities.Clamp(colorIndex, 0, ColorCount - 1),
MathUtilities.Clamp(shadeIndex, 0, _colorChartShadeCount - 1)]; MathUtilities.Clamp(shadeIndex, 0, ShadeCount - 1)];
} }
} }
} }

150
src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs

@ -0,0 +1,150 @@
using Avalonia.Media;
using Avalonia.Utilities;
using MaterialColor = Avalonia.Controls.MaterialColorPalette.MaterialColor;
namespace Avalonia.Controls
{
/// <summary>
/// Implements half of the <see cref="MaterialColorPalette"/> for improved usability.
/// </summary>
/// <inheritdoc cref="MaterialColorPalette"/>
public class MaterialHalfColorPalette : IColorPalette
{
protected static Color[,]? _colorChart = null;
protected static object _colorChartMutex = new object();
/// <summary>
/// Initializes all color chart colors.
/// </summary>
protected void InitColorChart()
{
lock (_colorChartMutex)
{
if (_colorChart != null)
{
return;
}
_colorChart = new Color[,]
{
// Red
{
Color.FromUInt32((uint)MaterialColor.Red50),
Color.FromUInt32((uint)MaterialColor.Red200),
Color.FromUInt32((uint)MaterialColor.Red400),
Color.FromUInt32((uint)MaterialColor.Red600),
Color.FromUInt32((uint)MaterialColor.Red800),
},
// Purple
{
Color.FromUInt32((uint)MaterialColor.Purple50),
Color.FromUInt32((uint)MaterialColor.Purple200),
Color.FromUInt32((uint)MaterialColor.Purple400),
Color.FromUInt32((uint)MaterialColor.Purple600),
Color.FromUInt32((uint)MaterialColor.Purple800),
},
// Indigo
{
Color.FromUInt32((uint)MaterialColor.Indigo50),
Color.FromUInt32((uint)MaterialColor.Indigo200),
Color.FromUInt32((uint)MaterialColor.Indigo400),
Color.FromUInt32((uint)MaterialColor.Indigo600),
Color.FromUInt32((uint)MaterialColor.Indigo800),
},
// Light Blue
{
Color.FromUInt32((uint)MaterialColor.LightBlue50),
Color.FromUInt32((uint)MaterialColor.LightBlue200),
Color.FromUInt32((uint)MaterialColor.LightBlue400),
Color.FromUInt32((uint)MaterialColor.LightBlue600),
Color.FromUInt32((uint)MaterialColor.LightBlue800),
},
// Teal
{
Color.FromUInt32((uint)MaterialColor.Teal50),
Color.FromUInt32((uint)MaterialColor.Teal200),
Color.FromUInt32((uint)MaterialColor.Teal400),
Color.FromUInt32((uint)MaterialColor.Teal600),
Color.FromUInt32((uint)MaterialColor.Teal800),
},
// Light Green
{
Color.FromUInt32((uint)MaterialColor.LightGreen50),
Color.FromUInt32((uint)MaterialColor.LightGreen200),
Color.FromUInt32((uint)MaterialColor.LightGreen400),
Color.FromUInt32((uint)MaterialColor.LightGreen600),
Color.FromUInt32((uint)MaterialColor.LightGreen800),
},
// Yellow
{
Color.FromUInt32((uint)MaterialColor.Yellow50),
Color.FromUInt32((uint)MaterialColor.Yellow200),
Color.FromUInt32((uint)MaterialColor.Yellow400),
Color.FromUInt32((uint)MaterialColor.Yellow600),
Color.FromUInt32((uint)MaterialColor.Yellow800),
},
// Orange
{
Color.FromUInt32((uint)MaterialColor.Orange50),
Color.FromUInt32((uint)MaterialColor.Orange200),
Color.FromUInt32((uint)MaterialColor.Orange400),
Color.FromUInt32((uint)MaterialColor.Orange600),
Color.FromUInt32((uint)MaterialColor.Orange800),
},
// Brown
{
Color.FromUInt32((uint)MaterialColor.Brown50),
Color.FromUInt32((uint)MaterialColor.Brown200),
Color.FromUInt32((uint)MaterialColor.Brown400),
Color.FromUInt32((uint)MaterialColor.Brown600),
Color.FromUInt32((uint)MaterialColor.Brown800),
},
// Blue Gray
{
Color.FromUInt32((uint)MaterialColor.BlueGray50),
Color.FromUInt32((uint)MaterialColor.BlueGray200),
Color.FromUInt32((uint)MaterialColor.BlueGray400),
Color.FromUInt32((uint)MaterialColor.BlueGray600),
Color.FromUInt32((uint)MaterialColor.BlueGray800),
},
};
}
return;
}
/// <inheritdoc/>
public int ColorCount
{
get => 10;
}
/// <inheritdoc/>
public int ShadeCount
{
get => 5;
}
/// <inheritdoc/>
public Color GetColor(int colorIndex, int shadeIndex)
{
if (_colorChart == null)
{
InitColorChart();
}
return _colorChart![
MathUtilities.Clamp(colorIndex, 0, ColorCount - 1),
MathUtilities.Clamp(shadeIndex, 0, ShadeCount - 1)];
}
}
}

229
src/Avalonia.Controls.ColorPicker/ColorPalettes/SixteenColorPalette.cs

@ -50,107 +50,6 @@ namespace Avalonia.Controls
} }
}; };
/// <summary>
/// Gets the index of the default shade of colors in this palette.
/// </summary>
public const int DefaultShadeIndex = 0;
/// <summary>
/// The index in the color palette of the 'White' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int WhiteIndex = 0;
/// <summary>
/// The index in the color palette of the 'Silver' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int SilverIndex = 1;
/// <summary>
/// The index in the color palette of the 'Gray' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int GrayIndex = 2;
/// <summary>
/// The index in the color palette of the 'Black' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int BlackIndex = 3;
/// <summary>
/// The index in the color palette of the 'Red' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int RedIndex = 4;
/// <summary>
/// The index in the color palette of the 'Maroon' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int MaroonIndex = 5;
/// <summary>
/// The index in the color palette of the 'Yellow' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int YellowIndex = 6;
/// <summary>
/// The index in the color palette of the 'Olive' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int OliveIndex = 7;
/// <summary>
/// The index in the color palette of the 'Lime' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int LimeIndex = 8;
/// <summary>
/// The index in the color palette of the 'Green' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int GreenIndex = 9;
/// <summary>
/// The index in the color palette of the 'Aqua' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int AquaIndex = 10;
/// <summary>
/// The index in the color palette of the 'Teal' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int TealIndex = 11;
/// <summary>
/// The index in the color palette of the 'Blue' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int BlueIndex = 12;
/// <summary>
/// The index in the color palette of the 'Navy' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int NavyIndex = 13;
/// <summary>
/// The index in the color palette of the 'Fuchsia' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int FuchsiaIndex = 14;
/// <summary>
/// The index in the color palette of the 'Purple' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int PurpleIndex = 15;
/// <inheritdoc/> /// <inheritdoc/>
public int ColorCount public int ColorCount
{ {
@ -163,134 +62,6 @@ namespace Avalonia.Controls
get => colorChart.GetLength(1); get => colorChart.GetLength(1);
} }
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFFFFFFF.
/// </summary>
public static Color White
{
get => colorChart[WhiteIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFC0C0C0.
/// </summary>
public static Color Silver
{
get => colorChart[SilverIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF808080.
/// </summary>
public static Color Gray
{
get => colorChart[GrayIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF000000.
/// </summary>
public static Color Black
{
get => colorChart[BlackIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFFF0000.
/// </summary>
public static Color Red
{
get => colorChart[RedIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF800000.
/// </summary>
public static Color Maroon
{
get => colorChart[MaroonIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFFFFF00.
/// </summary>
public static Color Yellow
{
get => colorChart[YellowIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF808000.
/// </summary>
public static Color Olive
{
get => colorChart[OliveIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF00FF00.
/// </summary>
public static Color Lime
{
get => colorChart[LimeIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF008000.
/// </summary>
public static Color Green
{
get => colorChart[GreenIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF00FFFF.
/// </summary>
public static Color Aqua
{
get => colorChart[AquaIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF008080.
/// </summary>
public static Color Teal
{
get => colorChart[TealIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF0000FF.
/// </summary>
public static Color Blue
{
get => colorChart[BlueIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF000080.
/// </summary>
public static Color Navy
{
get => colorChart[NavyIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFFF00FF.
/// </summary>
public static Color Fuchsia
{
get => colorChart[FuchsiaIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF800080.
/// </summary>
public static Color Purple
{
get => colorChart[PurpleIndex, DefaultShadeIndex];
}
/// <inheritdoc/> /// <inheritdoc/>
public Color GetColor(int colorIndex, int shadeIndex) public Color GetColor(int colorIndex, int shadeIndex)
{ {

29
src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs

@ -3412,23 +3412,30 @@ namespace Avalonia.Collections
RefreshOrDefer(); RefreshOrDefer();
return; return;
} }
object addedItem = args.NewItems?[0];
object removedItem = args.OldItems?[0];
// fire notifications for removes // fire notifications for removes
if (args.Action == NotifyCollectionChangedAction.Remove || if (args.OldItems != null &&
args.Action == NotifyCollectionChangedAction.Replace) (args.Action == NotifyCollectionChangedAction.Remove ||
args.Action == NotifyCollectionChangedAction.Replace))
{ {
ProcessRemoveEvent(removedItem, args.Action == NotifyCollectionChangedAction.Replace); foreach (var removedItem in args.OldItems)
{
ProcessRemoveEvent(removedItem, args.Action == NotifyCollectionChangedAction.Replace);
}
} }
// fire notifications for adds // fire notifications for adds
if ((args.Action == NotifyCollectionChangedAction.Add || if (args.NewItems != null &&
args.Action == NotifyCollectionChangedAction.Replace) && (args.Action == NotifyCollectionChangedAction.Add ||
(Filter == null || PassesFilter(addedItem))) args.Action == NotifyCollectionChangedAction.Replace))
{ {
ProcessAddEvent(addedItem, args.NewStartingIndex); for (var i = 0; i < args.NewItems.Count; i++)
{
if (Filter == null || PassesFilter(args.NewItems[i]))
{
ProcessAddEvent(args.NewItems[i], args.NewStartingIndex + i);
}
}
} }
if (args.Action != NotifyCollectionChangedAction.Replace) if (args.Action != NotifyCollectionChangedAction.Replace)
{ {

463
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs

@ -0,0 +1,463 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls.Templates;
using Avalonia.Data;
namespace Avalonia.Controls
{
public partial class AutoCompleteBox
{
public static readonly StyledProperty<string?> WatermarkProperty =
TextBox.WatermarkProperty.AddOwner<AutoCompleteBox>();
/// <summary>
/// Identifies the <see cref="MinimumPrefixLength" /> property.
/// </summary>
/// <value>The identifier for the <see cref="MinimumPrefixLength" /> property.</value>
public static readonly StyledProperty<int> MinimumPrefixLengthProperty =
AvaloniaProperty.Register<AutoCompleteBox, int>(
nameof(MinimumPrefixLength), 1,
validate: IsValidMinimumPrefixLength);
/// <summary>
/// Identifies the <see cref="MinimumPopulateDelay" /> property.
/// </summary>
/// <value>The identifier for the <see cref="MinimumPopulateDelay" /> property.</value>
public static readonly StyledProperty<TimeSpan> MinimumPopulateDelayProperty =
AvaloniaProperty.Register<AutoCompleteBox, TimeSpan>(
nameof(MinimumPopulateDelay),
TimeSpan.Zero,
validate: IsValidMinimumPopulateDelay);
/// <summary>
/// Identifies the <see cref="MaxDropDownHeight" /> property.
/// </summary>
/// <value>The identifier for the <see cref="MaxDropDownHeight" /> property.</value>
public static readonly StyledProperty<double> MaxDropDownHeightProperty =
AvaloniaProperty.Register<AutoCompleteBox, double>(
nameof(MaxDropDownHeight),
double.PositiveInfinity,
validate: IsValidMaxDropDownHeight);
/// <summary>
/// Identifies the <see cref="IsTextCompletionEnabled" /> property.
/// </summary>
/// <value>The identifier for the <see cref="IsTextCompletionEnabled" /> property.</value>
public static readonly StyledProperty<bool> IsTextCompletionEnabledProperty =
AvaloniaProperty.Register<AutoCompleteBox, bool>(nameof(IsTextCompletionEnabled));
/// <summary>
/// Identifies the <see cref="ItemTemplate" /> property.
/// </summary>
/// <value>The identifier for the <see cref="ItemTemplate" /> property.</value>
public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty =
AvaloniaProperty.Register<AutoCompleteBox, IDataTemplate>(nameof(ItemTemplate));
/// <summary>
/// Identifies the <see cref="IsDropDownOpen" /> property.
/// </summary>
/// <value>The identifier for the <see cref="IsDropDownOpen" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, bool> IsDropDownOpenProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, bool>(
nameof(IsDropDownOpen),
o => o.IsDropDownOpen,
(o, v) => o.IsDropDownOpen = v);
/// <summary>
/// Identifies the <see cref="SelectedItem" /> property.
/// </summary>
/// <value>The identifier the <see cref="SelectedItem" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, object?> SelectedItemProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, object?>(
nameof(SelectedItem),
o => o.SelectedItem,
(o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
/// <summary>
/// Identifies the <see cref="Text" /> property.
/// </summary>
/// <value>The identifier for the <see cref="Text" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, string?> TextProperty =
TextBlock.TextProperty.AddOwnerWithDataValidation<AutoCompleteBox>(
o => o.Text,
(o, v) => o.Text = v,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
/// <summary>
/// Identifies the <see cref="SearchText" /> property.
/// </summary>
/// <value>The identifier for the <see cref="SearchText" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, string?> SearchTextProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, string?>(
nameof(SearchText),
o => o.SearchText,
unsetValue: string.Empty);
/// <summary>
/// Gets the identifier for the <see cref="FilterMode" /> property.
/// </summary>
public static readonly StyledProperty<AutoCompleteFilterMode> FilterModeProperty =
AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteFilterMode>(
nameof(FilterMode),
defaultValue: AutoCompleteFilterMode.StartsWith,
validate: IsValidFilterMode);
/// <summary>
/// Identifies the <see cref="ItemFilter" /> property.
/// </summary>
/// <value>The identifier for the <see cref="ItemFilter" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<object?>?> ItemFilterProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<object?>?>(
nameof(ItemFilter),
o => o.ItemFilter,
(o, v) => o.ItemFilter = v);
/// <summary>
/// Identifies the <see cref="TextFilter" /> property.
/// </summary>
/// <value>The identifier for the <see cref="TextFilter" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<string?>?> TextFilterProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<string?>?>(
nameof(TextFilter),
o => o.TextFilter,
(o, v) => o.TextFilter = v,
unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
/// <summary>
/// Identifies the <see cref="ItemSelector" /> property.
/// </summary>
/// <value>The identifier for the <see cref="ItemSelector" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteSelector<object>?> ItemSelectorProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteSelector<object>?>(
nameof(ItemSelector),
o => o.ItemSelector,
(o, v) => o.ItemSelector = v);
/// <summary>
/// Identifies the <see cref="TextSelector" /> property.
/// </summary>
/// <value>The identifier for the <see cref="TextSelector" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteSelector<string?>?> TextSelectorProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteSelector<string?>?>(
nameof(TextSelector),
o => o.TextSelector,
(o, v) => o.TextSelector = v);
/// <summary>
/// Identifies the <see cref="Items" /> property.
/// </summary>
/// <value>The identifier for the <see cref="Items" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, IEnumerable?> ItemsProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, IEnumerable?>(
nameof(Items),
o => o.Items,
(o, v) => o.Items = v);
public static readonly DirectProperty<AutoCompleteBox, Func<string?, CancellationToken, Task<IEnumerable<object>>>?> AsyncPopulatorProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, Func<string?, CancellationToken, Task<IEnumerable<object>>>?>(
nameof(AsyncPopulator),
o => o.AsyncPopulator,
(o, v) => o.AsyncPopulator = v);
/// <summary>
/// Gets or sets the minimum number of characters required to be entered
/// in the text box before the <see cref="AutoCompleteBox" /> displays possible matches.
/// </summary>
/// <value>
/// The minimum number of characters to be entered in the text box
/// before the <see cref="AutoCompleteBox" />
/// displays possible matches. The default is 1.
/// </value>
/// <remarks>
/// If you set MinimumPrefixLength to -1, the AutoCompleteBox will
/// not provide possible matches. There is no maximum value, but
/// setting MinimumPrefixLength to value that is too large will
/// prevent the AutoCompleteBox from providing possible matches as well.
/// </remarks>
public int MinimumPrefixLength
{
get => GetValue(MinimumPrefixLengthProperty);
set => SetValue(MinimumPrefixLengthProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the first possible match
/// found during the filtering process will be displayed automatically
/// in the text box.
/// </summary>
/// <value>
/// True if the first possible match found will be displayed
/// automatically in the text box; otherwise, false. The default is
/// false.
/// </value>
public bool IsTextCompletionEnabled
{
get => GetValue(IsTextCompletionEnabledProperty);
set => SetValue(IsTextCompletionEnabledProperty, value);
}
/// <summary>
/// Gets or sets the <see cref="T:Avalonia.DataTemplate" /> used
/// to display each item in the drop-down portion of the control.
/// </summary>
/// <value>The <see cref="T:Avalonia.DataTemplate" /> used to
/// display each item in the drop-down. The default is null.</value>
/// <remarks>
/// You use the ItemTemplate property to specify the visualization
/// of the data objects in the drop-down portion of the AutoCompleteBox
/// control. If your AutoCompleteBox is bound to a collection and you
/// do not provide specific display instructions by using a
/// DataTemplate, the resulting UI of each item is a string
/// representation of each object in the underlying collection.
/// </remarks>
public IDataTemplate ItemTemplate
{
get => GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
/// <summary>
/// Gets or sets the minimum delay, after text is typed
/// in the text box before the
/// <see cref="AutoCompleteBox" /> control
/// populates the list of possible matches in the drop-down.
/// </summary>
/// <value>The minimum delay, after text is typed in
/// the text box, but before the
/// <see cref="AutoCompleteBox" /> populates
/// the list of possible matches in the drop-down. The default is 0.</value>
public TimeSpan MinimumPopulateDelay
{
get => GetValue(MinimumPopulateDelayProperty);
set => SetValue(MinimumPopulateDelayProperty, value);
}
/// <summary>
/// Gets or sets the maximum height of the drop-down portion of the
/// <see cref="AutoCompleteBox" /> control.
/// </summary>
/// <value>The maximum height of the drop-down portion of the
/// <see cref="AutoCompleteBox" /> control.
/// The default is <see cref="F:System.Double.PositiveInfinity" />.</value>
/// <exception cref="T:System.ArgumentException">The specified value is less than 0.</exception>
public double MaxDropDownHeight
{
get => GetValue(MaxDropDownHeightProperty);
set => SetValue(MaxDropDownHeightProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the drop-down portion of
/// the control is open.
/// </summary>
/// <value>
/// True if the drop-down is open; otherwise, false. The default is
/// false.
/// </value>
public bool IsDropDownOpen
{
get => _isDropDownOpen;
set => SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value);
}
/// <summary>
/// Gets or sets the <see cref="T:Avalonia.Data.Binding" /> that
/// is used to get the values for display in the text portion of
/// the <see cref="AutoCompleteBox" />
/// control.
/// </summary>
/// <value>The <see cref="T:Avalonia.Data.IBinding" /> object used
/// when binding to a collection property.</value>
[AssignBinding]
public IBinding? ValueMemberBinding
{
get => _valueBindingEvaluator?.ValueBinding;
set
{
if (ValueMemberBinding != value)
{
_valueBindingEvaluator = new BindingEvaluator<string>(value);
OnValueMemberBindingChanged(value);
}
}
}
/// <summary>
/// Gets or sets the selected item in the drop-down.
/// </summary>
/// <value>The selected item in the drop-down.</value>
/// <remarks>
/// If the IsTextCompletionEnabled property is true and text typed by
/// the user matches an item in the ItemsSource collection, which is
/// then displayed in the text box, the SelectedItem property will be
/// a null reference.
/// </remarks>
public object? SelectedItem
{
get => _selectedItem;
set => SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
}
/// <summary>
/// Gets or sets the text in the text box portion of the
/// <see cref="AutoCompleteBox" /> control.
/// </summary>
/// <value>The text in the text box portion of the
/// <see cref="AutoCompleteBox" /> control.</value>
public string? Text
{
get => _text;
set => SetAndRaise(TextProperty, ref _text, value);
}
/// <summary>
/// Gets the text that is used to filter items in the
/// <see cref="Items" /> item collection.
/// </summary>
/// <value>The text that is used to filter items in the
/// <see cref="Items" /> item collection.</value>
/// <remarks>
/// The SearchText value is typically the same as the
/// Text property, but is set after the TextChanged event occurs
/// and before the Populating event.
/// </remarks>
public string? SearchText
{
get => _searchText;
private set
{
try
{
_allowWrite = true;
SetAndRaise(SearchTextProperty, ref _searchText, value);
}
finally
{
_allowWrite = false;
}
}
}
/// <summary>
/// Gets or sets how the text in the text box is used to filter items
/// specified by the <see cref="Items" />
/// property for display in the drop-down.
/// </summary>
/// <value>One of the <see cref="AutoCompleteFilterMode" />
/// values The default is <see cref="AutoCompleteFilterMode.StartsWith" />.</value>
/// <exception cref="T:System.ArgumentException">The specified value is not a valid
/// <see cref="AutoCompleteFilterMode" />.</exception>
/// <remarks>
/// Use the FilterMode property to specify how possible matches are
/// filtered. For example, possible matches can be filtered in a
/// predefined or custom way. The search mode is automatically set to
/// Custom if you set the ItemFilter property.
/// </remarks>
public AutoCompleteFilterMode FilterMode
{
get => GetValue(FilterModeProperty);
set => SetValue(FilterModeProperty, value);
}
public string? Watermark
{
get => GetValue(WatermarkProperty);
set => SetValue(WatermarkProperty, value);
}
/// <summary>
/// Gets or sets the custom method that uses user-entered text to filter
/// the items specified by the <see cref="Items" />
/// property for display in the drop-down.
/// </summary>
/// <value>The custom method that uses the user-entered text to filter
/// the items specified by the <see cref="Items" />
/// property. The default is null.</value>
/// <remarks>
/// The filter mode is automatically set to Custom if you set the
/// ItemFilter property.
/// </remarks>
public AutoCompleteFilterPredicate<object?>? ItemFilter
{
get => _itemFilter;
set => SetAndRaise(ItemFilterProperty, ref _itemFilter, value);
}
/// <summary>
/// Gets or sets the custom method that uses the user-entered text to
/// filter items specified by the <see cref="Items" />
/// property in a text-based way for display in the drop-down.
/// </summary>
/// <value>The custom method that uses the user-entered text to filter
/// items specified by the <see cref="Items" />
/// property in a text-based way for display in the drop-down.</value>
/// <remarks>
/// The search mode is automatically set to Custom if you set the
/// TextFilter property.
/// </remarks>
public AutoCompleteFilterPredicate<string?>? TextFilter
{
get => _textFilter;
set => SetAndRaise(TextFilterProperty, ref _textFilter, value);
}
/// <summary>
/// Gets or sets the custom method that combines the user-entered
/// text and one of the items specified by the <see cref="Items" />.
/// </summary>
/// <value>
/// The custom method that combines the user-entered
/// text and one of the items specified by the <see cref="Items" />.
/// </value>
public AutoCompleteSelector<object>? ItemSelector
{
get => _itemSelector;
set => SetAndRaise(ItemSelectorProperty, ref _itemSelector, value);
}
/// <summary>
/// Gets or sets the custom method that combines the user-entered
/// text and one of the items specified by the
/// <see cref="Items" /> in a text-based way.
/// </summary>
/// <value>
/// The custom method that combines the user-entered
/// text and one of the items specified by the <see cref="Items" />
/// in a text-based way.
/// </value>
public AutoCompleteSelector<string?>? TextSelector
{
get => _textSelector;
set => SetAndRaise(TextSelectorProperty, ref _textSelector, value);
}
public Func<string?, CancellationToken, Task<IEnumerable<object>>>? AsyncPopulator
{
get => _asyncPopulator;
set => SetAndRaise(AsyncPopulatorProperty, ref _asyncPopulator, value);
}
/// <summary>
/// Gets or sets a collection that is used to generate the items for the
/// drop-down portion of the <see cref="AutoCompleteBox" /> control.
/// </summary>
/// <value>The collection that is used to generate the items of the
/// drop-down portion of the <see cref="AutoCompleteBox" /> control.</value>
public IEnumerable? Items
{
get => _itemsEnumerable;
set => SetAndRaise(ItemsProperty, ref _itemsEnumerable, value);
}
}
}

754
src/Avalonia.Controls/AutoCompleteBox.cs → src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@ -26,65 +26,6 @@ using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary>
/// Provides data for the
/// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populated" />
/// event.
/// </summary>
public class PopulatedEventArgs : EventArgs
{
/// <summary>
/// Gets the list of possible matches added to the drop-down portion of
/// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
/// control.
/// </summary>
/// <value>The list of possible matches added to the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" />.</value>
public IEnumerable Data { get; private set; }
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.PopulatedEventArgs" />.
/// </summary>
/// <param name="data">The list of possible matches added to the
/// drop-down portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</param>
public PopulatedEventArgs(IEnumerable data)
{
Data = data;
}
}
/// <summary>
/// Provides data for the
/// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populating" />
/// event.
/// </summary>
public class PopulatingEventArgs : CancelEventArgs
{
/// <summary>
/// Gets the text that is used to determine which items to display in
/// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
/// control.
/// </summary>
/// <value>The text that is used to determine which items to display in
/// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />.</value>
public string? Parameter { get; private set; }
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.PopulatingEventArgs" />.
/// </summary>
/// <param name="parameter">The value of the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
/// property, which is used to filter items for the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</param>
public PopulatingEventArgs(string? parameter)
{
Parameter = parameter;
}
}
/// <summary> /// <summary>
/// Represents the filter used by the /// Represents the filter used by the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control to /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control to
@ -100,132 +41,6 @@ namespace Avalonia.Controls
/// be either a string or an object.</typeparam> /// be either a string or an object.</typeparam>
public delegate bool AutoCompleteFilterPredicate<T>(string? search, T item); public delegate bool AutoCompleteFilterPredicate<T>(string? search, T item);
/// <summary>
/// Specifies how text in the text box portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control is used
/// to filter items specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// property for display in the drop-down.
/// </summary>
public enum AutoCompleteFilterMode
{
/// <summary>
/// Specifies that no filter is used. All items are returned.
/// </summary>
None = 0,
/// <summary>
/// Specifies a culture-sensitive, case-insensitive filter where the
/// returned items start with the specified text. The filter uses the
/// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
/// method, specifying
/// <see cref="P:System.StringComparer.CurrentCultureIgnoreCase" /> as
/// the string comparison criteria.
/// </summary>
StartsWith = 1,
/// <summary>
/// Specifies a culture-sensitive, case-sensitive filter where the
/// returned items start with the specified text. The filter uses the
/// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
/// method, specifying
/// <see cref="P:System.StringComparer.CurrentCulture" /> as the string
/// comparison criteria.
/// </summary>
StartsWithCaseSensitive = 2,
/// <summary>
/// Specifies an ordinal, case-insensitive filter where the returned
/// items start with the specified text. The filter uses the
/// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
/// method, specifying
/// <see cref="P:System.StringComparer.OrdinalIgnoreCase" /> as the
/// string comparison criteria.
/// </summary>
StartsWithOrdinal = 3,
/// <summary>
/// Specifies an ordinal, case-sensitive filter where the returned items
/// start with the specified text. The filter uses the
/// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
/// method, specifying <see cref="P:System.StringComparer.Ordinal" /> as
/// the string comparison criteria.
/// </summary>
StartsWithOrdinalCaseSensitive = 4,
/// <summary>
/// Specifies a culture-sensitive, case-insensitive filter where the
/// returned items contain the specified text.
/// </summary>
Contains = 5,
/// <summary>
/// Specifies a culture-sensitive, case-sensitive filter where the
/// returned items contain the specified text.
/// </summary>
ContainsCaseSensitive = 6,
/// <summary>
/// Specifies an ordinal, case-insensitive filter where the returned
/// items contain the specified text.
/// </summary>
ContainsOrdinal = 7,
/// <summary>
/// Specifies an ordinal, case-sensitive filter where the returned items
/// contain the specified text.
/// </summary>
ContainsOrdinalCaseSensitive = 8,
/// <summary>
/// Specifies a culture-sensitive, case-insensitive filter where the
/// returned items equal the specified text. The filter uses the
/// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
/// method, specifying
/// <see cref="P:System.StringComparer.CurrentCultureIgnoreCase" /> as
/// the search comparison criteria.
/// </summary>
Equals = 9,
/// <summary>
/// Specifies a culture-sensitive, case-sensitive filter where the
/// returned items equal the specified text. The filter uses the
/// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
/// method, specifying
/// <see cref="P:System.StringComparer.CurrentCulture" /> as the string
/// comparison criteria.
/// </summary>
EqualsCaseSensitive = 10,
/// <summary>
/// Specifies an ordinal, case-insensitive filter where the returned
/// items equal the specified text. The filter uses the
/// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
/// method, specifying
/// <see cref="P:System.StringComparer.OrdinalIgnoreCase" /> as the
/// string comparison criteria.
/// </summary>
EqualsOrdinal = 11,
/// <summary>
/// Specifies an ordinal, case-sensitive filter where the returned items
/// equal the specified text. The filter uses the
/// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
/// method, specifying <see cref="P:System.StringComparer.Ordinal" /> as
/// the string comparison criteria.
/// </summary>
EqualsOrdinalCaseSensitive = 12,
/// <summary>
/// Specifies that a custom filter is used. This mode is used when the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
/// or
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
/// properties are set.
/// </summary>
Custom = 13,
}
/// <summary> /// <summary>
/// Represents the selector used by the /// Represents the selector used by the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control to /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control to
@ -257,7 +72,7 @@ namespace Avalonia.Controls
[TemplatePart(ElementSelectionAdapter, typeof(ISelectionAdapter))] [TemplatePart(ElementSelectionAdapter, typeof(ISelectionAdapter))]
[TemplatePart(ElementTextBox, typeof(TextBox))] [TemplatePart(ElementTextBox, typeof(TextBox))]
[PseudoClasses(":dropdownopen")] [PseudoClasses(":dropdownopen")]
public class AutoCompleteBox : TemplatedControl public partial class AutoCompleteBox : TemplatedControl
{ {
/// <summary> /// <summary>
/// Specifies the name of the selection adapter TemplatePart. /// Specifies the name of the selection adapter TemplatePart.
@ -394,221 +209,22 @@ namespace Avalonia.Controls
private readonly EventHandler _populateDropDownHandler; private readonly EventHandler _populateDropDownHandler;
public static readonly RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent =
RoutedEvent.Register<SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox));
public static readonly StyledProperty<string?> WatermarkProperty =
TextBox.WatermarkProperty.AddOwner<AutoCompleteBox>();
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPrefixLength" />
/// dependency property.
/// </summary>
/// <value>The identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPrefixLength" />
/// dependency property.</value>
public static readonly StyledProperty<int> MinimumPrefixLengthProperty =
AvaloniaProperty.Register<AutoCompleteBox, int>(
nameof(MinimumPrefixLength), 1,
validate: IsValidMinimumPrefixLength);
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPopulateDelay" />
/// dependency property.
/// </summary>
/// <value>The identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPopulateDelay" />
/// dependency property.</value>
public static readonly StyledProperty<TimeSpan> MinimumPopulateDelayProperty =
AvaloniaProperty.Register<AutoCompleteBox, TimeSpan>(
nameof(MinimumPopulateDelay),
TimeSpan.Zero,
validate: IsValidMinimumPopulateDelay);
/// <summary> /// <summary>
/// Identifies the ///
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.MaxDropDownHeight" />
/// dependency property.
/// </summary> /// </summary>
/// <value>The identifier for the public static readonly RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent =
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.MaxDropDownHeight" /> RoutedEvent.Register<SelectionChangedEventArgs>(
/// dependency property.</value> nameof(SelectionChanged),
public static readonly StyledProperty<double> MaxDropDownHeightProperty = RoutingStrategies.Bubble,
AvaloniaProperty.Register<AutoCompleteBox, double>( typeof(AutoCompleteBox));
nameof(MaxDropDownHeight),
double.PositiveInfinity,
validate: IsValidMaxDropDownHeight);
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsTextCompletionEnabled" />
/// dependency property.
/// </summary>
/// <value>The identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsTextCompletionEnabled" />
/// dependency property.</value>
public static readonly StyledProperty<bool> IsTextCompletionEnabledProperty =
AvaloniaProperty.Register<AutoCompleteBox, bool>(nameof(IsTextCompletionEnabled));
/// <summary> /// <summary>
/// Identifies the /// Defines the <see cref="TextChanged"/> event.
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemTemplate" />
/// dependency property.
/// </summary> /// </summary>
/// <value>The identifier for the public static readonly RoutedEvent<TextChangedEventArgs> TextChangedEvent =
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemTemplate" /> RoutedEvent.Register<AutoCompleteBox, TextChangedEventArgs>(
/// dependency property.</value> nameof(TextChanged),
public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty = RoutingStrategies.Bubble);
AvaloniaProperty.Register<AutoCompleteBox, IDataTemplate>(nameof(ItemTemplate));
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
/// dependency property.
/// </summary>
/// <value>The identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
/// dependency property.</value>
public static readonly DirectProperty<AutoCompleteBox, bool> IsDropDownOpenProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, bool>(
nameof(IsDropDownOpen),
o => o.IsDropDownOpen,
(o, v) => o.IsDropDownOpen = v);
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.SelectedItem" />
/// dependency property.
/// </summary>
/// <value>The identifier the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.SelectedItem" />
/// dependency property.</value>
public static readonly DirectProperty<AutoCompleteBox, object?> SelectedItemProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, object?>(
nameof(SelectedItem),
o => o.SelectedItem,
(o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
/// dependency property.
/// </summary>
/// <value>The identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
/// dependency property.</value>
public static readonly DirectProperty<AutoCompleteBox, string?> TextProperty =
TextBlock.TextProperty.AddOwnerWithDataValidation<AutoCompleteBox>(
o => o.Text,
(o, v) => o.Text = v,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
/// dependency property.
/// </summary>
/// <value>The identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
/// dependency property.</value>
public static readonly DirectProperty<AutoCompleteBox, string?> SearchTextProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, string?>(
nameof(SearchText),
o => o.SearchText,
unsetValue: string.Empty);
/// <summary>
/// Gets the identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.FilterMode" />
/// dependency property.
/// </summary>
public static readonly StyledProperty<AutoCompleteFilterMode> FilterModeProperty =
AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteFilterMode>(
nameof(FilterMode),
defaultValue: AutoCompleteFilterMode.StartsWith,
validate: IsValidFilterMode);
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
/// dependency property.
/// </summary>
/// <value>The identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
/// dependency property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<object?>?> ItemFilterProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<object?>?>(
nameof(ItemFilter),
o => o.ItemFilter,
(o, v) => o.ItemFilter = v);
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
/// dependency property.
/// </summary>
/// <value>The identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
/// dependency property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<string?>?> TextFilterProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<string?>?>(
nameof(TextFilter),
o => o.TextFilter,
(o, v) => o.TextFilter = v,
unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemSelector" />
/// dependency property.
/// </summary>
/// <value>The identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemSelector" />
/// dependency property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteSelector<object>?> ItemSelectorProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteSelector<object>?>(
nameof(ItemSelector),
o => o.ItemSelector,
(o, v) => o.ItemSelector = v);
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextSelector" />
/// dependency property.
/// </summary>
/// <value>The identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextSelector" />
/// dependency property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteSelector<string?>?> TextSelectorProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteSelector<string?>?>(
nameof(TextSelector),
o => o.TextSelector,
(o, v) => o.TextSelector = v);
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// dependency property.
/// </summary>
/// <value>The identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// dependency property.</value>
public static readonly DirectProperty<AutoCompleteBox, IEnumerable?> ItemsProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, IEnumerable?>(
nameof(Items),
o => o.Items,
(o, v) => o.Items = v);
public static readonly DirectProperty<AutoCompleteBox, Func<string?, CancellationToken, Task<IEnumerable<object>>>?> AsyncPopulatorProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, Func<string?, CancellationToken, Task<IEnumerable<object>>>?>(
nameof(AsyncPopulator),
o => o.AsyncPopulator,
(o, v) => o.AsyncPopulator = v);
private static bool IsValidMinimumPrefixLength(int value) => value >= -1; private static bool IsValidMinimumPrefixLength(int value) => value >= -1;
@ -871,315 +487,6 @@ namespace Avalonia.Controls
ClearView(); ClearView();
} }
/// <summary>
/// Gets or sets the minimum number of characters required to be entered
/// in the text box before the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> displays
/// possible matches.
/// matches.
/// </summary>
/// <value>
/// The minimum number of characters to be entered in the text box
/// before the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
/// displays possible matches. The default is 1.
/// </value>
/// <remarks>
/// If you set MinimumPrefixLength to -1, the AutoCompleteBox will
/// not provide possible matches. There is no maximum value, but
/// setting MinimumPrefixLength to value that is too large will
/// prevent the AutoCompleteBox from providing possible matches as well.
/// </remarks>
public int MinimumPrefixLength
{
get { return GetValue(MinimumPrefixLengthProperty); }
set { SetValue(MinimumPrefixLengthProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the first possible match
/// found during the filtering process will be displayed automatically
/// in the text box.
/// </summary>
/// <value>
/// True if the first possible match found will be displayed
/// automatically in the text box; otherwise, false. The default is
/// false.
/// </value>
public bool IsTextCompletionEnabled
{
get { return GetValue(IsTextCompletionEnabledProperty); }
set { SetValue(IsTextCompletionEnabledProperty, value); }
}
/// <summary>
/// Gets or sets the <see cref="T:Avalonia.DataTemplate" /> used
/// to display each item in the drop-down portion of the control.
/// </summary>
/// <value>The <see cref="T:Avalonia.DataTemplate" /> used to
/// display each item in the drop-down. The default is null.</value>
/// <remarks>
/// You use the ItemTemplate property to specify the visualization
/// of the data objects in the drop-down portion of the AutoCompleteBox
/// control. If your AutoCompleteBox is bound to a collection and you
/// do not provide specific display instructions by using a
/// DataTemplate, the resulting UI of each item is a string
/// representation of each object in the underlying collection.
/// </remarks>
public IDataTemplate ItemTemplate
{
get { return GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
/// <summary>
/// Gets or sets the minimum delay, after text is typed
/// in the text box before the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control
/// populates the list of possible matches in the drop-down.
/// </summary>
/// <value>The minimum delay, after text is typed in
/// the text box, but before the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> populates
/// the list of possible matches in the drop-down. The default is 0.</value>
public TimeSpan MinimumPopulateDelay
{
get { return GetValue(MinimumPopulateDelayProperty); }
set { SetValue(MinimumPopulateDelayProperty, value); }
}
/// <summary>
/// Gets or sets the maximum height of the drop-down portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
/// </summary>
/// <value>The maximum height of the drop-down portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
/// The default is <see cref="F:System.Double.PositiveInfinity" />.</value>
/// <exception cref="T:System.ArgumentException">The specified value is less than 0.</exception>
public double MaxDropDownHeight
{
get { return GetValue(MaxDropDownHeightProperty); }
set { SetValue(MaxDropDownHeightProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the drop-down portion of
/// the control is open.
/// </summary>
/// <value>
/// True if the drop-down is open; otherwise, false. The default is
/// false.
/// </value>
public bool IsDropDownOpen
{
get { return _isDropDownOpen; }
set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
}
/// <summary>
/// Gets or sets the <see cref="T:Avalonia.Data.Binding" /> that
/// is used to get the values for display in the text portion of
/// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
/// control.
/// </summary>
/// <value>The <see cref="T:Avalonia.Data.IBinding" /> object used
/// when binding to a collection property.</value>
[AssignBinding]
public IBinding? ValueMemberBinding
{
get { return _valueBindingEvaluator?.ValueBinding; }
set
{
if (ValueMemberBinding != value)
{
_valueBindingEvaluator = new BindingEvaluator<string>(value);
OnValueMemberBindingChanged(value);
}
}
}
/// <summary>
/// Gets or sets the selected item in the drop-down.
/// </summary>
/// <value>The selected item in the drop-down.</value>
/// <remarks>
/// If the IsTextCompletionEnabled property is true and text typed by
/// the user matches an item in the ItemsSource collection, which is
/// then displayed in the text box, the SelectedItem property will be
/// a null reference.
/// </remarks>
public object? SelectedItem
{
get { return _selectedItem; }
set { SetAndRaise(SelectedItemProperty, ref _selectedItem, value); }
}
/// <summary>
/// Gets or sets the text in the text box portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
/// </summary>
/// <value>The text in the text box portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</value>
public string? Text
{
get { return _text; }
set { SetAndRaise(TextProperty, ref _text, value); }
}
/// <summary>
/// Gets the text that is used to filter items in the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// item collection.
/// </summary>
/// <value>The text that is used to filter items in the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// item collection.</value>
/// <remarks>
/// The SearchText value is typically the same as the
/// Text property, but is set after the TextChanged event occurs
/// and before the Populating event.
/// </remarks>
public string? SearchText
{
get { return _searchText; }
private set
{
try
{
_allowWrite = true;
SetAndRaise(SearchTextProperty, ref _searchText, value);
}
finally
{
_allowWrite = false;
}
}
}
/// <summary>
/// Gets or sets how the text in the text box is used to filter items
/// specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// property for display in the drop-down.
/// </summary>
/// <value>One of the
/// <see cref="T:Avalonia.Controls.AutoCompleteFilterMode" />
/// values The default is
/// <see cref="F:Avalonia.Controls.AutoCompleteFilterMode.StartsWith" />.</value>
/// <exception cref="T:System.ArgumentException">The specified value is
/// not a valid
/// <see cref="T:Avalonia.Controls.AutoCompleteFilterMode" />.</exception>
/// <remarks>
/// Use the FilterMode property to specify how possible matches are
/// filtered. For example, possible matches can be filtered in a
/// predefined or custom way. The search mode is automatically set to
/// Custom if you set the ItemFilter property.
/// </remarks>
public AutoCompleteFilterMode FilterMode
{
get { return GetValue(FilterModeProperty); }
set { SetValue(FilterModeProperty, value); }
}
public string? Watermark
{
get { return GetValue(WatermarkProperty); }
set { SetValue(WatermarkProperty, value); }
}
/// <summary>
/// Gets or sets the custom method that uses user-entered text to filter
/// the items specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// property for display in the drop-down.
/// </summary>
/// <value>The custom method that uses the user-entered text to filter
/// the items specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// property. The default is null.</value>
/// <remarks>
/// The filter mode is automatically set to Custom if you set the
/// ItemFilter property.
/// </remarks>
public AutoCompleteFilterPredicate<object?>? ItemFilter
{
get { return _itemFilter; }
set { SetAndRaise(ItemFilterProperty, ref _itemFilter, value); }
}
/// <summary>
/// Gets or sets the custom method that uses the user-entered text to
/// filter items specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// property in a text-based way for display in the drop-down.
/// </summary>
/// <value>The custom method that uses the user-entered text to filter
/// items specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// property in a text-based way for display in the drop-down.</value>
/// <remarks>
/// The search mode is automatically set to Custom if you set the
/// TextFilter property.
/// </remarks>
public AutoCompleteFilterPredicate<string?>? TextFilter
{
get { return _textFilter; }
set { SetAndRaise(TextFilterProperty, ref _textFilter, value); }
}
/// <summary>
/// Gets or sets the custom method that combines the user-entered
/// text and one of the items specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />.
/// </summary>
/// <value>
/// The custom method that combines the user-entered
/// text and one of the items specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />.
/// </value>
public AutoCompleteSelector<object>? ItemSelector
{
get { return _itemSelector; }
set { SetAndRaise(ItemSelectorProperty, ref _itemSelector, value); }
}
/// <summary>
/// Gets or sets the custom method that combines the user-entered
/// text and one of the items specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// in a text-based way.
/// </summary>
/// <value>
/// The custom method that combines the user-entered
/// text and one of the items specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// in a text-based way.
/// </value>
public AutoCompleteSelector<string?>? TextSelector
{
get { return _textSelector; }
set { SetAndRaise(TextSelectorProperty, ref _textSelector, value); }
}
public Func<string?, CancellationToken, Task<IEnumerable<object>>>? AsyncPopulator
{
get { return _asyncPopulator; }
set { SetAndRaise(AsyncPopulatorProperty, ref _asyncPopulator, value); }
}
/// <summary>
/// Gets or sets a collection that is used to generate the items for the
/// drop-down portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
/// </summary>
/// <value>The collection that is used to generate the items of the
/// drop-down portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</value>
public IEnumerable? Items
{
get { return _itemsEnumerable; }
set { SetAndRaise(ItemsProperty, ref _itemsEnumerable, value); }
}
/// <summary> /// <summary>
/// Gets or sets the drop down popup control. /// Gets or sets the drop down popup control.
/// </summary> /// </summary>
@ -1190,7 +497,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private TextBox? TextBox private TextBox? TextBox
{ {
get { return _textBox; } get => _textBox;
set set
{ {
_textBoxSubscriptions?.Dispose(); _textBoxSubscriptions?.Dispose();
@ -1254,7 +561,7 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
protected ISelectionAdapter? SelectionAdapter protected ISelectionAdapter? SelectionAdapter
{ {
get { return _adapter; } get => _adapter;
set set
{ {
if (_adapter != null) if (_adapter != null)
@ -1529,10 +836,14 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Occurs when the text in the text box portion of the /// Occurs asynchronously when the text in the <see cref="TextBox"/> portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> changes. /// <see cref="AutoCompleteBox" /> changes.
/// </summary> /// </summary>
public event EventHandler? TextChanged; public event EventHandler<TextChangedEventArgs>? TextChanged
{
add => AddHandler(TextChangedEvent, value);
remove => RemoveHandler(TextChangedEvent, value);
}
/// <summary> /// <summary>
/// Occurs when the /// Occurs when the
@ -1690,15 +1001,12 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Raises the /// Raises the <see cref="TextChanged" /> event.
/// <see cref="E:Avalonia.Controls.AutoCompleteBox.TextChanged" />
/// event.
/// </summary> /// </summary>
/// <param name="e">A <see cref="T:Avalonia.RoutedEventArgs" /> /// <param name="e">A <see cref="TextChangedEventArgs" /> that contains the event data.</param>
/// that contains the event data.</param> protected virtual void OnTextChanged(TextChangedEventArgs e)
protected virtual void OnTextChanged(RoutedEventArgs e)
{ {
TextChanged?.Invoke(this, e); RaiseEvent(e);
} }
/// <summary> /// <summary>
@ -1985,7 +1293,7 @@ namespace Avalonia.Controls
if (callTextChanged) if (callTextChanged)
{ {
OnTextChanged(new RoutedEventArgs()); OnTextChanged(new TextChangedEventArgs(TextChangedEvent));
} }
} }
@ -2740,8 +2048,6 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private IBinding? _binding; private IBinding? _binding;
#region public T Value
/// <summary> /// <summary>
/// Identifies the Value dependency property. /// Identifies the Value dependency property.
/// </summary> /// </summary>
@ -2753,18 +2059,16 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public T Value public T Value
{ {
get { return GetValue(ValueProperty); } get => GetValue(ValueProperty);
set { SetValue(ValueProperty, value); } set => SetValue(ValueProperty, value);
} }
#endregion public string Value
/// <summary> /// <summary>
/// Gets or sets the value binding. /// Gets or sets the value binding.
/// </summary> /// </summary>
public IBinding? ValueBinding public IBinding? ValueBinding
{ {
get { return _binding; } get => _binding;
set set
{ {
_binding = value; _binding = value;

131
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteFilterMode.cs

@ -0,0 +1,131 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Specifies how text in the text box portion of the <see cref="AutoCompleteBox" />
/// control is used to filter items specified by the <see cref="AutoCompleteBox.Items" />
/// property for display in the drop-down.
/// </summary>
public enum AutoCompleteFilterMode
{
/// <summary>
/// Specifies that no filter is used. All items are returned.
/// </summary>
None = 0,
/// <summary>
/// Specifies a culture-sensitive, case-insensitive filter where the
/// returned items start with the specified text. The filter uses the
/// <see cref="String.StartsWith(String,StringComparison)" />
/// method, specifying
/// <see cref="StringComparer.CurrentCultureIgnoreCase" /> as
/// the string comparison criteria.
/// </summary>
StartsWith = 1,
/// <summary>
/// Specifies a culture-sensitive, case-sensitive filter where the
/// returned items start with the specified text. The filter uses the
/// <see cref="String.StartsWith(String,StringComparison)" />
/// method, specifying
/// <see cref="StringComparer.CurrentCulture" /> as the string
/// comparison criteria.
/// </summary>
StartsWithCaseSensitive = 2,
/// <summary>
/// Specifies an ordinal, case-insensitive filter where the returned
/// items start with the specified text. The filter uses the
/// <see cref="String.StartsWith(String,StringComparison)" />
/// method, specifying
/// <see cref="StringComparer.OrdinalIgnoreCase" /> as the
/// string comparison criteria.
/// </summary>
StartsWithOrdinal = 3,
/// <summary>
/// Specifies an ordinal, case-sensitive filter where the returned items
/// start with the specified text. The filter uses the
/// <see cref="String.StartsWith(String,StringComparison)" />
/// method, specifying <see cref="StringComparer.Ordinal" /> as
/// the string comparison criteria.
/// </summary>
StartsWithOrdinalCaseSensitive = 4,
/// <summary>
/// Specifies a culture-sensitive, case-insensitive filter where the
/// returned items contain the specified text.
/// </summary>
Contains = 5,
/// <summary>
/// Specifies a culture-sensitive, case-sensitive filter where the
/// returned items contain the specified text.
/// </summary>
ContainsCaseSensitive = 6,
/// <summary>
/// Specifies an ordinal, case-insensitive filter where the returned
/// items contain the specified text.
/// </summary>
ContainsOrdinal = 7,
/// <summary>
/// Specifies an ordinal, case-sensitive filter where the returned items
/// contain the specified text.
/// </summary>
ContainsOrdinalCaseSensitive = 8,
/// <summary>
/// Specifies a culture-sensitive, case-insensitive filter where the
/// returned items equal the specified text. The filter uses the
/// <see cref="String.Equals(String,StringComparison)" />
/// method, specifying
/// <see cref="StringComparer.CurrentCultureIgnoreCase" /> as
/// the search comparison criteria.
/// </summary>
Equals = 9,
/// <summary>
/// Specifies a culture-sensitive, case-sensitive filter where the
/// returned items equal the specified text. The filter uses the
/// <see cref="String.Equals(String,StringComparison)" />
/// method, specifying
/// <see cref="StringComparer.CurrentCulture" /> as the string
/// comparison criteria.
/// </summary>
EqualsCaseSensitive = 10,
/// <summary>
/// Specifies an ordinal, case-insensitive filter where the returned
/// items equal the specified text. The filter uses the
/// <see cref="String.Equals(String,StringComparison)" />
/// method, specifying
/// <see cref="StringComparer.OrdinalIgnoreCase" /> as the
/// string comparison criteria.
/// </summary>
EqualsOrdinal = 11,
/// <summary>
/// Specifies an ordinal, case-sensitive filter where the returned items
/// equal the specified text. The filter uses the
/// <see cref="String.Equals(String,StringComparison)" />
/// method, specifying <see cref="StringComparer.Ordinal" /> as
/// the string comparison criteria.
/// </summary>
EqualsOrdinalCaseSensitive = 12,
/// <summary>
/// Specifies that a custom filter is used. This mode is used when the
/// <see cref="AutoCompleteBox.TextFilter" /> or <see cref="AutoCompleteBox.ItemFilter" />
/// properties are set.
/// </summary>
Custom = 13,
}
}

39
src/Avalonia.Controls/AutoCompleteBox/PopulatedEventArgs.cs

@ -0,0 +1,39 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System;
using System.Collections;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data for the
/// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populated" />
/// event.
/// </summary>
public class PopulatedEventArgs : EventArgs
{
/// <summary>
/// Gets the list of possible matches added to the drop-down portion of
/// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
/// control.
/// </summary>
/// <value>The list of possible matches added to the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" />.</value>
public IEnumerable Data { get; private set; }
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.PopulatedEventArgs" />.
/// </summary>
/// <param name="data">The list of possible matches added to the
/// drop-down portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</param>
public PopulatedEventArgs(IEnumerable data)
{
Data = data;
}
}
}

39
src/Avalonia.Controls/AutoCompleteBox/PopulatingEventArgs.cs

@ -0,0 +1,39 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System.ComponentModel;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data for the
/// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populating" />
/// event.
/// </summary>
public class PopulatingEventArgs : CancelEventArgs
{
/// <summary>
/// Gets the text that is used to determine which items to display in
/// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
/// control.
/// </summary>
/// <value>The text that is used to determine which items to display in
/// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />.</value>
public string? Parameter { get; private set; }
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.PopulatingEventArgs" />.
/// </summary>
/// <param name="parameter">The value of the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
/// property, which is used to filter items for the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</param>
public PopulatingEventArgs(string? parameter)
{
Parameter = parameter;
}
}
}

30
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@ -40,18 +40,6 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(DayVisible), AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(DayVisible),
x => x.DayVisible, (x, v) => x.DayVisible = v); x => x.DayVisible, (x, v) => x.DayVisible = v);
/// <summary>
/// Defines the <see cref="Header"/> Property
/// </summary>
public static readonly StyledProperty<object> HeaderProperty =
AvaloniaProperty.Register<DatePicker, object>(nameof(Header));
/// <summary>
/// Defines the <see cref="HeaderTemplate"/> Property
/// </summary>
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate));
/// <summary> /// <summary>
/// Defines the <see cref="MaxYear"/> Property /// Defines the <see cref="MaxYear"/> Property
/// </summary> /// </summary>
@ -152,24 +140,6 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Gets or sets the DatePicker header
/// </summary>
public object Header
{
get => GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
/// <summary>
/// Gets or sets the header template
/// </summary>
public IDataTemplate HeaderTemplate
{
get => GetValue(HeaderTemplateProperty);
set => SetValue(HeaderTemplateProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets the maximum year for the picker /// Gets or sets the maximum year for the picker
/// </summary> /// </summary>

30
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -34,18 +34,6 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement), AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement),
x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v); x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v);
/// <summary>
/// Defines the <see cref="Header"/> property
/// </summary>
public static readonly StyledProperty<object> HeaderProperty =
AvaloniaProperty.Register<TimePicker, object>(nameof(Header));
/// <summary>
/// Defines the <see cref="HeaderTemplate"/> property
/// </summary>
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
AvaloniaProperty.Register<TimePicker, IDataTemplate>(nameof(HeaderTemplate));
/// <summary> /// <summary>
/// Defines the <see cref="ClockIdentifier"/> property /// Defines the <see cref="ClockIdentifier"/> property
/// </summary> /// </summary>
@ -103,24 +91,6 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Gets or sets the header
/// </summary>
public object Header
{
get => GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
/// <summary>
/// Gets or sets the header template
/// </summary>
public IDataTemplate HeaderTemplate
{
get => GetValue(HeaderTemplateProperty);
set => SetValue(HeaderTemplateProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets the clock identifier, either 12HourClock or 24HourClock /// Gets or sets the clock identifier, either 12HourClock or 24HourClock
/// </summary> /// </summary>

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

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

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

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

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

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

2
src/Avalonia.Controls/Primitives/RangeBase.cs

@ -175,7 +175,7 @@ namespace Avalonia.Controls.Primitives
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
private static bool ValidateDouble(double value) private static bool ValidateDouble(double value)
{ {
return !double.IsInfinity(value) || !double.IsNaN(value); return !double.IsInfinity(value) && !double.IsNaN(value);
} }
/// <summary> /// <summary>

88
src/Avalonia.Controls/RichTextBlock.cs

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

2
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -191,7 +191,7 @@ namespace Avalonia.DesignerSupport.Remote
public Task ClearAsync() => Task.CompletedTask; public Task ClearAsync() => Task.CompletedTask;
public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask; public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask;
public Task<string[]> GetFormatsAsync() => Task.FromResult(new string[0]); public Task<string[]> GetFormatsAsync() => Task.FromResult(Array.Empty<string>());
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null); public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
} }

38
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -110,14 +110,24 @@ namespace Avalonia.Headless
return new HeadlessBitmapStub(destinationSize, new Vector(96, 96)); return new HeadlessBitmapStub(destinationSize, new Vector(96, 96));
} }
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{ {
return new HeadlessGlyphRunStub(); return new HeadlessGeometryStub(new Rect(glyphRun.Size));
} }
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{ {
return new HeadlessGeometryStub(new Rect(glyphRun.Size)); return new HeadlessGlyphRunBufferStub();
}
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return new HeadlessHorizontalGlyphRunBufferStub();
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return new HeadlessPositionedGlyphRunBufferStub();
} }
class HeadlessGeometryStub : IGeometryImpl class HeadlessGeometryStub : IGeometryImpl
@ -203,6 +213,26 @@ namespace Avalonia.Headless
public Matrix Transform { get; } public Matrix Transform { get; }
} }
class HeadlessGlyphRunBufferStub : IGlyphRunBuffer
{
public Span<ushort> GlyphIndices => Span<ushort>.Empty;
public IGlyphRunImpl Build()
{
return new HeadlessGlyphRunStub();
}
}
class HeadlessHorizontalGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IHorizontalGlyphRunBuffer
{
public Span<float> GlyphPositions => Span<float>.Empty;
}
class HeadlessPositionedGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IPositionedGlyphRunBuffer
{
public Span<System.Drawing.PointF> GlyphPositions => Span<System.Drawing.PointF>.Empty;
}
class HeadlessGlyphRunStub : IGlyphRunImpl class HeadlessGlyphRunStub : IGlyphRunImpl
{ {
public void Dispose() public void Dispose()

24
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -75,8 +75,13 @@ namespace Avalonia.Headless
public TimeSpan TouchDoubleClickTime => DoubleClickTime; public TimeSpan TouchDoubleClickTime => DoubleClickTime;
} }
class HeadlessGlyphTypefaceImpl : IGlyphTypefaceImpl class HeadlessGlyphTypefaceImpl : IGlyphTypeface
{ {
public FontMetrics Metrics => new FontMetrics
{
};
public short DesignEmHeight => 10; public short DesignEmHeight => 10;
public int Ascent => 5; public int Ascent => 5;
@ -95,6 +100,8 @@ namespace Avalonia.Headless
public bool IsFixedPitch => true; public bool IsFixedPitch => true;
public int GlyphCount => 1337;
public void Dispose() public void Dispose()
{ {
} }
@ -104,6 +111,13 @@ namespace Avalonia.Headless
return 1; return 1;
} }
public bool TryGetGlyph(uint codepoint, out ushort glyph)
{
glyph = 1;
return true;
}
public int GetGlyphAdvance(ushort glyph) public int GetGlyphAdvance(ushort glyph)
{ {
return 1; return 1;
@ -118,6 +132,12 @@ namespace Avalonia.Headless
{ {
return codepoints.ToArray().Select(x => (ushort)x).ToArray(); return codepoints.ToArray().Select(x => (ushort)x).ToArray();
} }
public bool TryGetTable(uint tag, out byte[] table)
{
table = null;
return false;
}
} }
class HeadlessTextShaperStub : ITextShaperImpl class HeadlessTextShaperStub : ITextShaperImpl
@ -134,7 +154,7 @@ namespace Avalonia.Headless
class HeadlessFontManagerStub : IFontManagerImpl class HeadlessFontManagerStub : IFontManagerImpl
{ {
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{ {
return new HeadlessGlyphTypefaceImpl(); return new HeadlessGlyphTypefaceImpl();
} }

7
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml

@ -358,7 +358,6 @@
<x:Double x:Key="SliderOutsideTickBarThemeHeight">4</x:Double> <x:Double x:Key="SliderOutsideTickBarThemeHeight">4</x:Double>
<x:Double x:Key="SliderTrackThemeHeight">2</x:Double> <x:Double x:Key="SliderTrackThemeHeight">2</x:Double>
<Thickness x:Key="SliderBorderThemeThickness">0</Thickness> <Thickness x:Key="SliderBorderThemeThickness">0</Thickness>
<FontWeight x:Key="SliderHeaderThemeFontWeight">Normal</FontWeight>
<StaticResource x:Key="SliderContainerBackground" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="SliderContainerBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPointerOver" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="SliderContainerBackgroundPointerOver" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" />
@ -375,8 +374,6 @@
<StaticResource x:Key="SliderTrackValueFillPointerOver" ResourceKey="SystemControlHighlightAccentBrush" /> <StaticResource x:Key="SliderTrackValueFillPointerOver" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillPressed" ResourceKey="SystemControlHighlightAccentBrush" /> <StaticResource x:Key="SliderTrackValueFillPressed" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" /> <StaticResource x:Key="SliderTrackValueFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="SliderHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" /> <StaticResource x:Key="SliderTickBarFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFillDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" /> <StaticResource x:Key="SliderTickBarFillDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderInlineTickBarFill" ResourceKey="SystemControlBackgroundAltHighBrush" /> <StaticResource x:Key="SliderInlineTickBarFill" ResourceKey="SystemControlBackgroundAltHighBrush" />
@ -424,7 +421,6 @@
<!-- Resources for DatePicker.xaml--> <!-- Resources for DatePicker.xaml-->
<StaticResource x:Key="DatePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" /> <StaticResource x:Key="DatePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="DatePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" /> <StaticResource x:Key="DatePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="DatePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="DatePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" /> <StaticResource x:Key="DatePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" /> <StaticResource x:Key="DatePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
@ -455,8 +451,6 @@
<!-- Resources for TimePicker.xaml --> <!-- Resources for TimePicker.xaml -->
<StaticResource x:Key="TimePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" /> <StaticResource x:Key="TimePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="TimePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" /> <StaticResource x:Key="TimePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="TimePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="TimePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="TimePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" /> <StaticResource x:Key="TimePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" /> <StaticResource x:Key="TimePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
@ -595,7 +589,6 @@
<StaticResource x:Key="CalendarDatePickerTextForeground" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="CalendarDatePickerTextForeground" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="CalendarDatePickerTextForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" /> <StaticResource x:Key="CalendarDatePickerTextForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarDatePickerTextForegroundSelected" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="CalendarDatePickerTextForegroundSelected" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CalendarDatePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarDatePickerBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" /> <StaticResource x:Key="CalendarDatePickerBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" />
<StaticResource x:Key="CalendarDatePickerBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" /> <StaticResource x:Key="CalendarDatePickerBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" />
<StaticResource x:Key="CalendarDatePickerBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" /> <StaticResource x:Key="CalendarDatePickerBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" />

7
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml

@ -354,7 +354,6 @@
<x:Double x:Key="SliderOutsideTickBarThemeHeight">4</x:Double> <x:Double x:Key="SliderOutsideTickBarThemeHeight">4</x:Double>
<x:Double x:Key="SliderTrackThemeHeight">2</x:Double> <x:Double x:Key="SliderTrackThemeHeight">2</x:Double>
<Thickness x:Key="SliderBorderThemeThickness">0</Thickness> <Thickness x:Key="SliderBorderThemeThickness">0</Thickness>
<FontWeight x:Key="SliderHeaderThemeFontWeight">Normal</FontWeight>
<StaticResource x:Key="SliderContainerBackground" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="SliderContainerBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPointerOver" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="SliderContainerBackgroundPointerOver" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" />
@ -371,8 +370,6 @@
<StaticResource x:Key="SliderTrackValueFillPointerOver" ResourceKey="SystemControlHighlightAccentBrush" /> <StaticResource x:Key="SliderTrackValueFillPointerOver" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillPressed" ResourceKey="SystemControlHighlightAccentBrush" /> <StaticResource x:Key="SliderTrackValueFillPressed" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" /> <StaticResource x:Key="SliderTrackValueFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="SliderHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" /> <StaticResource x:Key="SliderTickBarFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFillDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" /> <StaticResource x:Key="SliderTickBarFillDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderInlineTickBarFill" ResourceKey="SystemControlBackgroundAltHighBrush" /> <StaticResource x:Key="SliderInlineTickBarFill" ResourceKey="SystemControlBackgroundAltHighBrush" />
@ -420,7 +417,6 @@
<!-- Resources for DatePicker.xaml--> <!-- Resources for DatePicker.xaml-->
<StaticResource x:Key="DatePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" /> <StaticResource x:Key="DatePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="DatePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" /> <StaticResource x:Key="DatePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="DatePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="DatePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" /> <StaticResource x:Key="DatePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" /> <StaticResource x:Key="DatePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
@ -450,8 +446,6 @@
<!-- Resources for TimePicker.xaml --> <!-- Resources for TimePicker.xaml -->
<StaticResource x:Key="TimePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" /> <StaticResource x:Key="TimePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="TimePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" /> <StaticResource x:Key="TimePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="TimePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="TimePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="TimePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" /> <StaticResource x:Key="TimePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" /> <StaticResource x:Key="TimePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
@ -589,7 +583,6 @@
<StaticResource x:Key="CalendarDatePickerTextForeground" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="CalendarDatePickerTextForeground" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="CalendarDatePickerTextForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" /> <StaticResource x:Key="CalendarDatePickerTextForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarDatePickerTextForegroundSelected" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="CalendarDatePickerTextForegroundSelected" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CalendarDatePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarDatePickerBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" /> <StaticResource x:Key="CalendarDatePickerBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" />
<StaticResource x:Key="CalendarDatePickerBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" /> <StaticResource x:Key="CalendarDatePickerBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" />
<StaticResource x:Key="CalendarDatePickerBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" /> <StaticResource x:Key="CalendarDatePickerBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" />

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

@ -28,7 +28,6 @@
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Thickness x:Key="DatePickerTopHeaderMargin">0,0,0,4</Thickness>
<x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double> <x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double>
<x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double> <x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double>
<x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double> <x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double>
@ -84,18 +83,8 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<DataValidationErrors> <DataValidationErrors>
<Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}" RowDefinitions="Auto,*"> <Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}">
<ContentPresenter Name="HeaderContentPresenter" Grid.Row="0"
Content="{TemplateBinding Header}"
Foreground="{DynamicResource DatePickerHeaderForeground}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Margin="{DynamicResource DatePickerTopHeaderMargin}"
MaxWidth="{DynamicResource DatePickerThemeMaxWidth}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"/>
<Button Name="PART_FlyoutButton" <Button Name="PART_FlyoutButton"
Grid.Row="1"
Theme="{StaticResource FluentDatePickerFlyoutButton}" Theme="{StaticResource FluentDatePickerFlyoutButton}"
Foreground="{TemplateBinding Foreground}" Foreground="{TemplateBinding Foreground}"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"

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

@ -99,18 +99,8 @@
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<Grid
Name="grid"
Margin="{TemplateBinding Padding}"
RowDefinitions="Auto, *">
<ContentPresenter
x:Name="HeaderContentPresenter"
Grid.Row="0"
Margin="{DynamicResource SliderTopHeaderMargin}"
TextElement.FontWeight="{DynamicResource SliderHeaderThemeFontWeight}"
TextElement.Foreground="{DynamicResource SliderHeaderForeground}" />
<Grid x:Name="SliderContainer" <Grid x:Name="SliderContainer"
Grid.Row="1" Margin="{TemplateBinding Padding}"
Background="{DynamicResource SliderContainerBackground}"> Background="{DynamicResource SliderContainerBackground}">
<Grid.Styles> <Grid.Styles>
<Style Selector="TickBar"> <Style Selector="TickBar">
@ -191,7 +181,6 @@
</Track> </Track>
</Grid> </Grid>
</Grid> </Grid>
</Grid>
</Border> </Border>
</DataValidationErrors> </DataValidationErrors>
</ControlTemplate> </ControlTemplate>
@ -205,19 +194,10 @@
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<Grid
Name="grid"
Margin="{TemplateBinding Padding}"
RowDefinitions="Auto, *">
<ContentPresenter
x:Name="HeaderContentPresenter"
Grid.Row="0"
Margin="{DynamicResource SliderTopHeaderMargin}"
TextElement.FontWeight="{DynamicResource SliderHeaderThemeFontWeight}"
TextElement.Foreground="{DynamicResource SliderHeaderForeground}" />
<Grid x:Name="SliderContainer" <Grid x:Name="SliderContainer"
Grid.Row="1" Grid.Row="1"
Background="{DynamicResource SliderContainerBackground}"> Background="{DynamicResource SliderContainerBackground}"
Margin="{TemplateBinding Padding}">
<Grid.Styles> <Grid.Styles>
<Style Selector="TickBar"> <Style Selector="TickBar">
<Setter Property="ReservedSpace" Value="{Binding #PART_Track.Thumb.Bounds}" /> <Setter Property="ReservedSpace" Value="{Binding #PART_Track.Thumb.Bounds}" />
@ -298,7 +278,6 @@
</Track> </Track>
</Grid> </Grid>
</Grid> </Grid>
</Grid>
</Border> </Border>
</DataValidationErrors> </DataValidationErrors>
</ControlTemplate> </ControlTemplate>
@ -326,9 +305,6 @@
<!-- Disabled State --> <!-- Disabled State -->
<Style Selector="^:disabled"> <Style Selector="^:disabled">
<Style Selector="^ /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource SliderHeaderForegroundDisabled}" />
</Style>
<Style Selector="^ /template/ RepeatButton#PART_DecreaseButton"> <Style Selector="^ /template/ RepeatButton#PART_DecreaseButton">
<Setter Property="Background" Value="{DynamicResource SliderTrackValueFillDisabled}" /> <Setter Property="Background" Value="{DynamicResource SliderTrackValueFillDisabled}" />
</Style> </Style>

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

@ -31,7 +31,6 @@
<x:Double x:Key="TimePickerFlyoutPresenterItemHeight">40</x:Double> <x:Double x:Key="TimePickerFlyoutPresenterItemHeight">40</x:Double>
<x:Double x:Key="TimePickerSpacerThemeWidth">1</x:Double> <x:Double x:Key="TimePickerSpacerThemeWidth">1</x:Double>
<Thickness x:Key="TimePickerBorderThemeThickness">1</Thickness> <Thickness x:Key="TimePickerBorderThemeThickness">1</Thickness>
<Thickness x:Key="TimePickerTopHeaderMargin">0,0,0,4</Thickness>
<x:Double x:Key="TimePickerFlyoutPresenterHighlightHeight">40</x:Double> <x:Double x:Key="TimePickerFlyoutPresenterHighlightHeight">40</x:Double>
<x:Double x:Key="TimePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double> <x:Double x:Key="TimePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double>
<x:Double x:Key="TimePickerThemeMinWidth">242</x:Double> <x:Double x:Key="TimePickerThemeMinWidth">242</x:Double>
@ -83,19 +82,8 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<DataValidationErrors> <DataValidationErrors>
<Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}" RowDefinitions="Auto,*"> <Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}">
<ContentPresenter x:Name="HeaderContentPresenter"
Grid.Row="0"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Margin="{DynamicResource TimePickerTopHeaderMargin}"
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}"
Foreground="{DynamicResource TimePickerHeaderForeground}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top" />
<Button x:Name="PART_FlyoutButton" <Button x:Name="PART_FlyoutButton"
Grid.Row="1"
Theme="{StaticResource FluentTimePickerFlyoutButton}" Theme="{StaticResource FluentTimePickerFlyoutButton}"
Foreground="{TemplateBinding Foreground}" Foreground="{TemplateBinding Foreground}"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
@ -172,11 +160,7 @@
</DataValidationErrors> </DataValidationErrors>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
<Style Selector="^:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource TimePickerHeaderForegroundDisabled}"/>
</Style>
<Style Selector="^:disabled /template/ Rectangle"> <Style Selector="^:disabled /template/ Rectangle">
<Setter Property="Fill" Value="{DynamicResource TimePickerSpacerFillDisabled}"/> <Setter Property="Fill" Value="{DynamicResource TimePickerSpacerFillDisabled}"/>
</Style> </Style>

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

@ -135,7 +135,7 @@
</Style> </Style>
<!-- NormalState --> <!-- NormalState -->
<Style Selector="^:not(:dragging) /template/ Grid#MovingKnobs"> <Style Selector="^:not(:dragging) /template/ Grid#PART_MovingKnobs">
<Setter Property="Transitions"> <Setter Property="Transitions">
<Transitions> <Transitions>
<DoubleTransition <DoubleTransition

13
src/Avalonia.Themes.Simple/Controls/DatePicker.xaml

@ -30,7 +30,6 @@
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Thickness x:Key="DatePickerTopHeaderMargin">0,0,0,4</Thickness>
<x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double> <x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double>
<x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double> <x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double>
<x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double> <x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double>
@ -91,19 +90,9 @@
<ControlTemplate> <ControlTemplate>
<DataValidationErrors> <DataValidationErrors>
<Grid Name="LayoutRoot" <Grid Name="LayoutRoot"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}">
RowDefinitions="Auto,*">
<ContentPresenter Name="HeaderContentPresenter"
Grid.Row="0"
MaxWidth="{DynamicResource DatePickerThemeMaxWidth}"
Margin="{DynamicResource DatePickerTopHeaderMargin}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}" />
<Button Name="PART_FlyoutButton" <Button Name="PART_FlyoutButton"
Grid.Row="1"
MinWidth="{DynamicResource DatePickerThemeMinWidth}" MinWidth="{DynamicResource DatePickerThemeMinWidth}"
MaxWidth="{DynamicResource DatePickerThemeMaxWidth}" MaxWidth="{DynamicResource DatePickerThemeMaxWidth}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"

19
src/Avalonia.Themes.Simple/Controls/TimePicker.xaml

@ -33,7 +33,6 @@
<x:Double x:Key="TimePickerFlyoutPresenterItemHeight">40</x:Double> <x:Double x:Key="TimePickerFlyoutPresenterItemHeight">40</x:Double>
<x:Double x:Key="TimePickerSpacerThemeWidth">1</x:Double> <x:Double x:Key="TimePickerSpacerThemeWidth">1</x:Double>
<Thickness x:Key="TimePickerBorderThemeThickness">1</Thickness> <Thickness x:Key="TimePickerBorderThemeThickness">1</Thickness>
<Thickness x:Key="TimePickerTopHeaderMargin">0,0,0,4</Thickness>
<x:Double x:Key="TimePickerFlyoutPresenterHighlightHeight">40</x:Double> <x:Double x:Key="TimePickerFlyoutPresenterHighlightHeight">40</x:Double>
<x:Double x:Key="TimePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double> <x:Double x:Key="TimePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double>
<x:Double x:Key="TimePickerThemeMinWidth">242</x:Double> <x:Double x:Key="TimePickerThemeMinWidth">242</x:Double>
@ -90,20 +89,8 @@
<ControlTemplate> <ControlTemplate>
<DataValidationErrors> <DataValidationErrors>
<Grid Name="LayoutRoot" <Grid Name="LayoutRoot"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}">
RowDefinitions="Auto,*">
<ContentPresenter x:Name="HeaderContentPresenter"
Grid.Row="0"
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}"
Margin="{DynamicResource TimePickerTopHeaderMargin}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Foreground="{DynamicResource ThemeForegroundColor}" />
<Button x:Name="PART_FlyoutButton" <Button x:Name="PART_FlyoutButton"
Grid.Row="1"
MinWidth="{DynamicResource TimePickerThemeMinWidth}" MinWidth="{DynamicResource TimePickerThemeMinWidth}"
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}" MaxWidth="{DynamicResource TimePickerThemeMaxWidth}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
@ -181,10 +168,6 @@
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
<Style Selector="^:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
</Style>
<Style Selector="^:disabled /template/ Rectangle"> <Style Selector="^:disabled /template/ Rectangle">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" /> <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
</Style> </Style>

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

@ -5,6 +5,11 @@
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<PackageId>Avalonia.Markup.Xaml.Loader</PackageId> <PackageId>Avalonia.Markup.Xaml.Loader</PackageId>
<DefineConstants>$(DefineConstants);XAMLX_INTERNAL</DefineConstants> <DefineConstants>$(DefineConstants);XAMLX_INTERNAL</DefineConstants>
<LangVersion>10</LangVersion>
</PropertyGroup>
<!--Disable Net Perf. analyzer for submodule to avoid commit issue -->
<PropertyGroup>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
</PropertyGroup> </PropertyGroup>
<Import Project="IncludeXamlIlSre.props" /> <Import Project="IncludeXamlIlSre.props" />
<ItemGroup> <ItemGroup>

2
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -102,7 +102,7 @@ namespace Avalonia.Skia
return false; return false;
} }
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{ {
SKTypeface skTypeface = null; SKTypeface skTypeface = null;

90
src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs

@ -1,14 +1,14 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Platform;
using HarfBuzzSharp; using HarfBuzzSharp;
using SkiaSharp; using SkiaSharp;
namespace Avalonia.Skia namespace Avalonia.Skia
{ {
[Unstable] [Unstable]
public class GlyphTypefaceImpl : IGlyphTypefaceImpl public class GlyphTypefaceImpl : IGlyphTypeface
{ {
private bool _isDisposed; private bool _isDisposed;
@ -25,35 +25,32 @@ namespace Avalonia.Skia
Font.SetFunctionsOpenType(); Font.SetFunctionsOpenType();
DesignEmHeight = (short)Typeface.UnitsPerEm;
var metrics = Typeface.ToFont().Metrics; var metrics = Typeface.ToFont().Metrics;
const double defaultFontRenderingEmSize = 12.0; const double defaultFontRenderingEmSize = 12.0;
Ascent = (int)(metrics.Ascent / defaultFontRenderingEmSize * Typeface.UnitsPerEm); Metrics = new FontMetrics
{
Descent = (int)(metrics.Descent / defaultFontRenderingEmSize * Typeface.UnitsPerEm); DesignEmHeight = (short)Typeface.UnitsPerEm,
Ascent = (int)(metrics.Ascent / defaultFontRenderingEmSize * Typeface.UnitsPerEm),
LineGap = (int)(metrics.Leading / defaultFontRenderingEmSize * Typeface.UnitsPerEm); Descent = (int)(metrics.Descent / defaultFontRenderingEmSize * Typeface.UnitsPerEm),
LineGap = (int)(metrics.Leading / defaultFontRenderingEmSize * Typeface.UnitsPerEm),
UnderlinePosition = metrics.UnderlinePosition != null ? UnderlinePosition = metrics.UnderlinePosition != null ?
(int)(metrics.UnderlinePosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : (int)(metrics.UnderlinePosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
0; 0,
UnderlineThickness = metrics.UnderlineThickness != null ?
UnderlineThickness = metrics.UnderlineThickness != null ?
(int)(metrics.UnderlineThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : (int)(metrics.UnderlineThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
0; 0,
StrikethroughPosition = metrics.StrikeoutPosition != null ?
StrikethroughPosition = metrics.StrikeoutPosition != null ?
(int)(metrics.StrikeoutPosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : (int)(metrics.StrikeoutPosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
0; 0,
StrikethroughThickness = metrics.StrikeoutThickness != null ?
StrikethroughThickness = metrics.StrikeoutThickness != null ?
(int)(metrics.StrikeoutThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : (int)(metrics.StrikeoutThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
0; 0,
IsFixedPitch = Typeface.IsFixedPitch
};
IsFixedPitch = Typeface.IsFixedPitch; GlyphCount = Typeface.GlyphCount;
IsFakeBold = isFakeBold; IsFakeBold = isFakeBold;
@ -67,39 +64,16 @@ namespace Avalonia.Skia
public SKTypeface Typeface { get; } public SKTypeface Typeface { get; }
public int ReplacementCodepoint { get; } public int ReplacementCodepoint { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public short DesignEmHeight { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Ascent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Descent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> public FontMetrics Metrics { get; }
public int LineGap { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> public int GlyphCount { get; }
public int UnderlinePosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int UnderlineThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughPosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public bool IsFixedPitch { get; }
public bool IsFakeBold { get; } public bool IsFakeBold { get; }
public bool IsFakeItalic { get; } public bool IsFakeItalic { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> /// <inheritdoc cref="IGlyphTypeface"/>
public ushort GetGlyph(uint codepoint) public ushort GetGlyph(uint codepoint)
{ {
if (Font.TryGetGlyph(codepoint, out var glyph)) if (Font.TryGetGlyph(codepoint, out var glyph))
@ -110,7 +84,14 @@ namespace Avalonia.Skia
return 0; return 0;
} }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> public bool TryGetGlyph(uint codepoint, out ushort glyph)
{
glyph = GetGlyph(codepoint);
return glyph != 0;
}
/// <inheritdoc cref="IGlyphTypeface"/>
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints)
{ {
var glyphs = new ushort[codepoints.Length]; var glyphs = new ushort[codepoints.Length];
@ -126,13 +107,13 @@ namespace Avalonia.Skia
return glyphs; return glyphs;
} }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> /// <inheritdoc cref="IGlyphTypeface"/>
public int GetGlyphAdvance(ushort glyph) public int GetGlyphAdvance(ushort glyph)
{ {
return Font.GetHorizontalGlyphAdvance(glyph); return Font.GetHorizontalGlyphAdvance(glyph);
} }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> /// <inheritdoc cref="IGlyphTypeface"/>
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
{ {
var glyphIndices = new uint[glyphs.Length]; var glyphIndices = new uint[glyphs.Length];
@ -180,5 +161,10 @@ namespace Avalonia.Skia
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
public bool TryGetTable(uint tag, out byte[] table)
{
return Typeface.TryGetTableData(tag, out table);
}
} }
} }

184
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -1,7 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -12,6 +12,8 @@ using Avalonia.OpenGL.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using SkiaSharp; using SkiaSharp;
using System.Runtime.InteropServices;
using System.Drawing;
namespace Avalonia.Skia namespace Avalonia.Skia
{ {
@ -33,13 +35,17 @@ namespace Avalonia.Skia
} }
var gl = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>(); var gl = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
if (gl != null) if (gl != null)
_skiaGpu = new GlSkiaGpu(gl, maxResourceBytes); _skiaGpu = new GlSkiaGpu(gl, maxResourceBytes);
//TODO: SKFont crashes when disposed in finalizer so we keep it alive
GC.SuppressFinalize(s_font);
} }
public bool SupportsIndividualRoundRects => true;
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
public PixelFormat DefaultPixelFormat { get; }
public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect); public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2); public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);
@ -64,7 +70,7 @@ namespace Avalonia.Skia
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{ {
if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface) if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface)
{ {
throw new InvalidOperationException("PlatformImpl can't be null."); throw new InvalidOperationException("PlatformImpl can't be null.");
} }
@ -228,133 +234,95 @@ namespace Avalonia.Skia
return new WriteableBitmapImpl(size, dpi, format, alphaFormat); return new WriteableBitmapImpl(size, dpi, format, alphaFormat);
} }
private static readonly SKFont s_font = new SKFont public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi)
{
Subpixel = true,
Edging = SKFontEdging.SubpixelAntialias,
Hinting = SKFontHinting.Full,
LinearMetrics = true
};
private static readonly ThreadLocal<SKTextBlobBuilder> s_textBlobBuilderThreadLocal = new ThreadLocal<SKTextBlobBuilder>(() => new SKTextBlobBuilder());
/// <inheritdoc />
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun)
{ {
var count = glyphRun.GlyphIndices.Count; if (_skiaGpu is IOpenGlAwareSkiaGpu glAware)
var textBlobBuilder = s_textBlobBuilderThreadLocal.Value; return glAware.CreateOpenGlBitmap(size, dpi);
if (_skiaGpu == null)
var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl; throw new PlatformNotSupportedException("GPU acceleration is not available");
throw new PlatformNotSupportedException(
"Current GPU acceleration backend does not support OpenGL integration");
}
var typeface = glyphTypeface.Typeface; public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
=> new SKGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
s_font.Size = (float)glyphRun.FontRenderingEmSize; public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
s_font.Typeface = typeface; => new SKHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
s_font.Embolden = glyphTypeface.IsFakeBold;
s_font.SkewX = glyphTypeface.IsFakeItalic ? -0.2f : 0;
SKTextBlob textBlob; public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
=> new SKPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight); private abstract class SKGlyphRunBufferBase : IGlyphRunBuffer
{
protected readonly SKTextBlobBuilder _builder;
protected readonly SKFont _font;
if (glyphRun.GlyphOffsets == null) public SKGlyphRunBufferBase(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{ {
if (glyphTypeface.IsFixedPitch) _builder = new SKTextBlobBuilder();
{
var buffer = textBlobBuilder.AllocateRun(s_font, glyphRun.GlyphIndices.Count, 0, 0);
var glyphs = buffer.GetGlyphSpan();
for (int i = 0; i < glyphs.Length; i++) var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
{
glyphs[i] = glyphRun.GlyphIndices[i];
}
textBlob = textBlobBuilder.Build(); _font = new SKFont
}
else
{ {
var buffer = textBlobBuilder.AllocateHorizontalRun(s_font, count, 0); Subpixel = true,
Edging = SKFontEdging.SubpixelAntialias,
var positions = buffer.GetPositionSpan(); Hinting = SKFontHinting.Full,
LinearMetrics = true,
var width = 0d; Size = fontRenderingEmSize,
Typeface = glyphTypefaceImpl.Typeface,
for (var i = 0; i < count; i++) Embolden = glyphTypefaceImpl.IsFakeBold,
{ SkewX = glyphTypefaceImpl.IsFakeItalic ? -0.2f : 0
positions[i] = (float)width; };
}
if (glyphRun.GlyphAdvances == null)
{
width += glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale;
}
else
{
width += glyphRun.GlyphAdvances[i];
}
}
var glyphs = buffer.GetGlyphSpan();
for (int i = 0; i < glyphs.Length; i++) public abstract Span<ushort> GlyphIndices { get; }
{
glyphs[i] = glyphRun.GlyphIndices[i];
}
textBlob = textBlobBuilder.Build(); public IGlyphRunImpl Build()
}
}
else
{ {
var buffer = textBlobBuilder.AllocatePositionedRun(s_font, count); return new GlyphRunImpl(_builder.Build());
}
var glyphPositions = buffer.GetPositionSpan(); }
var currentX = 0.0; private sealed class SKGlyphRunBuffer : SKGlyphRunBufferBase
{
private readonly SKRunBuffer _buffer;
for (var i = 0; i < count; i++) public SKGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
{ {
var glyphOffset = glyphRun.GlyphOffsets[i]; _buffer = _builder.AllocateRun(_font, length, 0, 0);
}
glyphPositions[i] = new SKPoint((float)(currentX + glyphOffset.X), (float)glyphOffset.Y);
if (glyphRun.GlyphAdvances == null)
{
currentX += glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale;
}
else
{
currentX += glyphRun.GlyphAdvances[i];
}
}
var glyphs = buffer.GetGlyphSpan(); public override Span<ushort> GlyphIndices => _buffer.GetGlyphSpan();
}
for (int i = 0; i < glyphs.Length; i++) private sealed class SKHorizontalGlyphRunBuffer : SKGlyphRunBufferBase, IHorizontalGlyphRunBuffer
{ {
glyphs[i] = glyphRun.GlyphIndices[i]; private readonly SKHorizontalRunBuffer _buffer;
}
textBlob = textBlobBuilder.Build(); public SKHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
{
_buffer = _builder.AllocateHorizontalRun(_font, length, 0);
} }
return new GlyphRunImpl(textBlob); public override Span<ushort> GlyphIndices => _buffer.GetGlyphSpan();
public Span<float> GlyphPositions => _buffer.GetPositionSpan();
} }
public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) private sealed class SKPositionedGlyphRunBuffer : SKGlyphRunBufferBase, IPositionedGlyphRunBuffer
{ {
if (_skiaGpu is IOpenGlAwareSkiaGpu glAware) private readonly SKPositionedRunBuffer _buffer;
return glAware.CreateOpenGlBitmap(size, dpi);
if (_skiaGpu == null)
throw new PlatformNotSupportedException("GPU acceleration is not available");
throw new PlatformNotSupportedException(
"Current GPU acceleration backend does not support OpenGL integration");
}
public bool SupportsIndividualRoundRects => true; public SKPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
{
_buffer = _builder.AllocatePositionedRun(_font, length);
}
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; public override Span<ushort> GlyphIndices => _buffer.GetGlyphSpan();
public PixelFormat DefaultPixelFormat { get; } public Span<PointF> GlyphPositions => MemoryMarshal.Cast<SKPoint, PointF>(_buffer.GetPositionSpan());
}
} }
} }

2
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -31,7 +31,7 @@ namespace Avalonia.Skia
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
var font = ((GlyphTypefaceImpl)typeface.PlatformImpl).Font; var font = ((GlyphTypefaceImpl)typeface).Font;
font.Shape(buffer); font.Shape(buffer);

91
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -12,6 +12,8 @@ using SharpDX.DirectWrite;
using GlyphRun = Avalonia.Media.GlyphRun; using GlyphRun = Avalonia.Media.GlyphRun;
using TextAlignment = Avalonia.Media.TextAlignment; using TextAlignment = Avalonia.Media.TextAlignment;
using SharpDX.Mathematics.Interop; using SharpDX.Mathematics.Interop;
using System.Runtime.InteropServices;
using System.Drawing;
namespace Avalonia namespace Avalonia
{ {
@ -160,7 +162,7 @@ namespace Avalonia.Direct2D1
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{ {
if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface) if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface)
{ {
throw new InvalidOperationException("PlatformImpl can't be null."); throw new InvalidOperationException("PlatformImpl can't be null.");
} }
@ -258,69 +260,66 @@ namespace Avalonia.Direct2D1
return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride); return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride);
} }
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) private class DWGlyphRunBuffer : IGlyphRunBuffer
{ {
var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl; protected readonly SharpDX.DirectWrite.GlyphRun _dwRun;
var glyphCount = glyphRun.GlyphIndices.Count; public DWGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
var run = new SharpDX.DirectWrite.GlyphRun
{ {
FontFace = glyphTypeface.FontFace, var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
FontSize = (float)glyphRun.FontRenderingEmSize
};
var indices = new short[glyphCount];
for (var i = 0; i < glyphCount; i++) _dwRun = new SharpDX.DirectWrite.GlyphRun
{ {
indices[i] = (short)glyphRun.GlyphIndices[i]; FontFace = glyphTypefaceImpl.FontFace,
FontSize = fontRenderingEmSize,
Indices = new short[length]
};
} }
run.Indices = indices; public Span<ushort> GlyphIndices => MemoryMarshal.Cast<short, ushort>(_dwRun.Indices.AsSpan());
run.Advances = new float[glyphCount];
var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight); public IGlyphRunImpl Build()
if (glyphRun.GlyphAdvances == null)
{ {
for (var i = 0; i < glyphCount; i++) return new GlyphRunImpl(_dwRun);
{
var advance = glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale;
run.Advances[i] = advance;
}
} }
else }
{
for (var i = 0; i < glyphCount; i++)
{
var advance = (float)glyphRun.GlyphAdvances[i];
run.Advances[i] = advance; private class DWHorizontalGlyphRunBuffer : DWGlyphRunBuffer, IHorizontalGlyphRunBuffer
} {
public DWHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
: base(glyphTypeface, fontRenderingEmSize, length)
{
_dwRun.Advances = new float[length];
} }
if (glyphRun.GlyphOffsets == null) public Span<float> GlyphPositions => _dwRun.Advances.AsSpan();
}
private class DWPositionedGlyphRunBuffer : DWGlyphRunBuffer, IPositionedGlyphRunBuffer
{
public DWPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
: base(glyphTypeface, fontRenderingEmSize, length)
{ {
return new GlyphRunImpl(run); _dwRun.Advances = new float[length];
_dwRun.Offsets = new GlyphOffset[length];
} }
run.Offsets = new GlyphOffset[glyphCount]; public Span<PointF> GlyphPositions => MemoryMarshal.Cast<GlyphOffset, PointF>(_dwRun.Offsets.AsSpan());
}
for (var i = 0; i < glyphCount; i++) public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{ {
var (x, y) = glyphRun.GlyphOffsets[i]; return new DWGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
}
run.Offsets[i] = new GlyphOffset public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{ {
AdvanceOffset = (float)x, return new DWHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
AscenderOffset = (float)y }
};
}
return new GlyphRunImpl(run); public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return new DWPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
} }
public bool SupportsIndividualRoundRects => false; public bool SupportsIndividualRoundRects => false;

2
src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs

@ -62,7 +62,7 @@ namespace Avalonia.Direct2D1.Media
return false; return false;
} }
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{ {
return new GlyphTypefaceImpl(typeface); return new GlyphTypefaceImpl(typeface);
} }

105
src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs

@ -1,14 +1,15 @@
using System; using System;
using System.Drawing.Drawing2D;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Platform;
using HarfBuzzSharp; using HarfBuzzSharp;
using SharpDX.DirectWrite; using SharpDX.DirectWrite;
using FontMetrics = Avalonia.Media.FontMetrics;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
[Unstable] [Unstable]
public class GlyphTypefaceImpl : IGlyphTypefaceImpl public class GlyphTypefaceImpl : IGlyphTypeface
{ {
private bool _isDisposed; private bool _isDisposed;
@ -26,40 +27,28 @@ namespace Avalonia.Direct2D1.Media
Font.GetScale(out var xScale, out _); Font.GetScale(out var xScale, out _);
DesignEmHeight = (short)xScale;
if (!Font.TryGetHorizontalFontExtents(out var fontExtents)) if (!Font.TryGetHorizontalFontExtents(out var fontExtents))
{ {
Font.TryGetVerticalFontExtents(out fontExtents); Font.TryGetVerticalFontExtents(out fontExtents);
} }
Ascent = -fontExtents.Ascender; Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition);
Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness);
Descent = -fontExtents.Descender; Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition);
Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness);
LineGap = fontExtents.LineGap;
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition))
{
UnderlinePosition = underlinePosition;
}
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness))
{
UnderlineThickness = underlineThickness;
}
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition)) Metrics = new FontMetrics
{ {
StrikethroughPosition = strikethroughPosition; DesignEmHeight = (short)xScale,
} Ascent = -fontExtents.Ascender,
Descent = -fontExtents.Descender,
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness)) LineGap = fontExtents.LineGap,
{ UnderlinePosition = underlinePosition,
StrikethroughThickness = strikethroughThickness; UnderlineThickness = underlineThickness,
} StrikethroughPosition = strikethroughPosition,
StrikethroughThickness = strikethroughThickness,
IsFixedPitch = FontFace.IsMonospacedFont; IsFixedPitch = FontFace.IsMonospacedFont
};
} }
private Blob GetTable(Face face, Tag tag) private Blob GetTable(Face face, Tag tag)
@ -89,35 +78,11 @@ namespace Avalonia.Direct2D1.Media
public HarfBuzzSharp.Font Font { get; } public HarfBuzzSharp.Font Font { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> public FontMetrics Metrics { get; }
public short DesignEmHeight { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Ascent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Descent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int LineGap { get; }
//ToDo: Read font table for these values public int GlyphCount { get; set; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int UnderlinePosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> /// <inheritdoc cref="IGlyphTypeface"/>
public int UnderlineThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughPosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public bool IsFixedPitch { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public ushort GetGlyph(uint codepoint) public ushort GetGlyph(uint codepoint)
{ {
if (Font.TryGetGlyph(codepoint, out var glyph)) if (Font.TryGetGlyph(codepoint, out var glyph))
@ -128,7 +93,14 @@ namespace Avalonia.Direct2D1.Media
return 0; return 0;
} }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> public bool TryGetGlyph(uint codepoint, out ushort glyph)
{
glyph = GetGlyph(codepoint);
return glyph != 0;
}
/// <inheritdoc cref="IGlyphTypeface"/>
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints)
{ {
var glyphs = new ushort[codepoints.Length]; var glyphs = new ushort[codepoints.Length];
@ -144,13 +116,13 @@ namespace Avalonia.Direct2D1.Media
return glyphs; return glyphs;
} }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> /// <inheritdoc cref="IGlyphTypeface"/>
public int GetGlyphAdvance(ushort glyph) public int GetGlyphAdvance(ushort glyph)
{ {
return Font.GetHorizontalGlyphAdvance(glyph); return Font.GetHorizontalGlyphAdvance(glyph);
} }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> /// <inheritdoc cref="IGlyphTypeface"/>
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
{ {
var glyphIndices = new uint[glyphs.Length]; var glyphIndices = new uint[glyphs.Length];
@ -187,6 +159,21 @@ namespace Avalonia.Direct2D1.Media
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
public bool TryGetTable(uint tag, out byte[] table)
{
table = null;
var blob = Face.ReferenceTable(tag);
if (blob.Length > 0)
{
table = blob.AsSpan().ToArray();
return true;
}
return false;
}
} }
} }

2
src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs

@ -31,7 +31,7 @@ namespace Avalonia.Direct2D1.Media
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
var font = ((GlyphTypefaceImpl)typeface.PlatformImpl).Font; var font = ((GlyphTypefaceImpl)typeface).Font;
font.Shape(buffer); font.Shape(buffer);

2
src/iOS/Avalonia.iOS/TextInputResponder.cs

@ -405,7 +405,7 @@ partial class AvaloniaView
// TODO: Query from the input client // TODO: Query from the input client
Logger.TryGet(LogEventLevel.Debug, ImeLog)? Logger.TryGet(LogEventLevel.Debug, ImeLog)?
.Log(null, "IUITextInput:GetSelectionRect"); .Log(null, "IUITextInput:GetSelectionRect");
return new UITextSelectionRect[0]; return Array.Empty<UITextSelectionRect>();
} }
[Export("textStylingAtPosition:inDirection:")] [Export("textStylingAtPosition:inDirection:")]

4
src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj

@ -3,6 +3,10 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks> <TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<!--Disable Net Perf. analyzer for submodule to avoid commit issue -->
<PropertyGroup>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" /> <ProjectReference Include="..\..\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
<ProjectReference Include="..\..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" /> <ProjectReference Include="..\..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />

2
tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs

@ -185,7 +185,7 @@ namespace Avalonia.Base.UnitTests.Media
var characters = new ReadOnlySlice<char>(Enumerable.Repeat('a', count).ToArray(), start, count); var characters = new ReadOnlySlice<char>(Enumerable.Repeat('a', count).ToArray(), start, count);
return new GlyphRun(new GlyphTypeface(new MockGlyphTypeface()), 10, characters, glyphIndices, glyphAdvances, return new GlyphRun(new MockGlyphTypeface(), 10, characters, glyphIndices, glyphAdvances,
glyphClusters: glyphClusters, biDiLevel: bidiLevel); glyphClusters: glyphClusters, biDiLevel: bidiLevel);
} }
} }

15
tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs

@ -126,6 +126,21 @@ namespace Avalonia.Base.UnitTests.VisualTree
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
class MockStreamGeometry : IStreamGeometryImpl class MockStreamGeometry : IStreamGeometryImpl
{ {
private MockStreamGeometryContext _impl = new MockStreamGeometryContext(); private MockStreamGeometryContext _impl = new MockStreamGeometryContext();

16
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -112,12 +112,22 @@ namespace Avalonia.Benchmarks
return new MockFontManagerImpl(); return new MockFontManagerImpl();
} }
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{ {
return new NullGlyphRun(); return new MockStreamGeometryImpl();
} }
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

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

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

2
tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs

@ -64,7 +64,7 @@ namespace Avalonia.Skia.UnitTests.Media
return true; return true;
} }
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{ {
SKTypeface skTypeface; SKTypeface skTypeface;

2
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@ -237,7 +237,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var glyph = typeface.GlyphTypeface.GetGlyph('a'); var glyph = typeface.GlyphTypeface.GetGlyph('a');
var advance = typeface.GlyphTypeface.GetGlyphAdvance(glyph) * var advance = typeface.GlyphTypeface.GetGlyphAdvance(glyph) *
(12.0 / typeface.GlyphTypeface.DesignEmHeight); (12.0 / typeface.GlyphTypeface.Metrics.DesignEmHeight);
var paragraphWidth = advance * numberOfCharactersPerLine; var paragraphWidth = advance * numberOfCharactersPerLine;

4
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@ -578,9 +578,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{ {
var glyphTypeface = Typeface.Default.GlyphTypeface; var glyphTypeface = Typeface.Default.GlyphTypeface;
var emHeight = glyphTypeface.DesignEmHeight; var emHeight = glyphTypeface.Metrics.DesignEmHeight;
var lineHeight = (glyphTypeface.Descent - glyphTypeface.Ascent) * (12.0 / emHeight); var lineHeight = glyphTypeface.Metrics.LineSpacing * (12.0 / emHeight);
var layout = new TextLayout( var layout = new TextLayout(
text, text,

2
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -12,7 +12,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{ {
public class TextLineTests public class TextLineTests
{ {
private static readonly string s_multiLineText = "012345678\r\r0123456789"; private const string s_multiLineText = "012345678\r\r0123456789";
[Fact] [Fact]
public void Should_Get_First_CharacterHit() public void Should_Get_First_CharacterHit()

2
tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs

@ -58,7 +58,7 @@ namespace Avalonia.UnitTests
return false; return false;
} }
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{ {
var fontFamily = typeface.FontFamily; var fontFamily = typeface.FontFamily;

96
tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs

@ -1,11 +1,11 @@
using System; using System;
using System.IO; using System.IO;
using Avalonia.Platform; using Avalonia.Media;
using HarfBuzzSharp; using HarfBuzzSharp;
namespace Avalonia.UnitTests namespace Avalonia.UnitTests
{ {
public class HarfBuzzGlyphTypefaceImpl : IGlyphTypefaceImpl public class HarfBuzzGlyphTypefaceImpl : IGlyphTypeface
{ {
private bool _isDisposed; private bool _isDisposed;
private Blob _blob; private Blob _blob;
@ -21,70 +21,49 @@ namespace Avalonia.UnitTests
Font.SetFunctionsOpenType(); Font.SetFunctionsOpenType();
Font.GetScale(out var scale, out _); Font.GetScale(out var scale, out _);
DesignEmHeight = (short)scale;
var metrics = Font.OpenTypeMetrics;
const double defaultFontRenderingEmSize = 12.0; const double defaultFontRenderingEmSize = 12.0;
Ascent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalAscender) / defaultFontRenderingEmSize * DesignEmHeight); var metrics = Font.OpenTypeMetrics;
Descent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalDescender) / defaultFontRenderingEmSize * DesignEmHeight); Metrics = new FontMetrics
{
DesignEmHeight = (short)scale,
Ascent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalAscender) / defaultFontRenderingEmSize * scale),
Descent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalDescender) / defaultFontRenderingEmSize * scale),
LineGap = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalLineGap) / defaultFontRenderingEmSize * scale),
LineGap = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalLineGap) / defaultFontRenderingEmSize * DesignEmHeight); UnderlinePosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineOffset) / defaultFontRenderingEmSize * scale),
UnderlinePosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineOffset) / defaultFontRenderingEmSize * DesignEmHeight); UnderlineThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineSize) / defaultFontRenderingEmSize * scale),
UnderlineThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineSize) / defaultFontRenderingEmSize * DesignEmHeight); StrikethroughPosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutOffset) / defaultFontRenderingEmSize * scale),
StrikethroughPosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutOffset) / defaultFontRenderingEmSize * DesignEmHeight); StrikethroughThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutSize) / defaultFontRenderingEmSize * scale),
StrikethroughThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutSize) / defaultFontRenderingEmSize * DesignEmHeight); IsFixedPitch = GetGlyphAdvance(GetGlyph('a')) == GetGlyphAdvance(GetGlyph('b'))
};
IsFixedPitch = GetGlyphAdvance(GetGlyph('a')) == GetGlyphAdvance(GetGlyph('b')); GlyphCount = Face.GlyphCount;
IsFakeBold = isFakeBold; IsFakeBold = isFakeBold;
IsFakeItalic = isFakeItalic; IsFakeItalic = isFakeItalic;
} }
public FontMetrics Metrics { get; }
public Face Face { get; } public Face Face { get; }
public Font Font { get; } public Font Font { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> public int GlyphCount { get; set; }
public short DesignEmHeight { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Ascent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Descent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int LineGap { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int UnderlinePosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int UnderlineThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughPosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public bool IsFixedPitch { get; }
public bool IsFakeBold { get; } public bool IsFakeBold { get; }
public bool IsFakeItalic { get; } public bool IsFakeItalic { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> /// <inheritdoc cref="IGlyphTypeface"/>
public ushort GetGlyph(uint codepoint) public ushort GetGlyph(uint codepoint)
{ {
if (Font.TryGetGlyph(codepoint, out var glyph)) if (Font.TryGetGlyph(codepoint, out var glyph))
@ -95,7 +74,21 @@ namespace Avalonia.UnitTests
return 0; return 0;
} }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> public bool TryGetGlyph(uint codepoint,out ushort glyph)
{
glyph = 0;
if (Font.TryGetGlyph(codepoint, out var glyphId))
{
glyph = (ushort)glyphId;
return true;
}
return false;
}
/// <inheritdoc cref="IGlyphTypeface"/>
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints)
{ {
var glyphs = new ushort[codepoints.Length]; var glyphs = new ushort[codepoints.Length];
@ -111,13 +104,13 @@ namespace Avalonia.UnitTests
return glyphs; return glyphs;
} }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> /// <inheritdoc cref="IGlyphTypeface"/>
public int GetGlyphAdvance(ushort glyph) public int GetGlyphAdvance(ushort glyph)
{ {
return Font.GetHorizontalGlyphAdvance(glyph); return Font.GetHorizontalGlyphAdvance(glyph);
} }
/// <inheritdoc cref="IGlyphTypefaceImpl"/> /// <inheritdoc cref="IGlyphTypeface"/>
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
{ {
var glyphIndices = new uint[glyphs.Length]; var glyphIndices = new uint[glyphs.Length];
@ -130,6 +123,21 @@ namespace Avalonia.UnitTests
return Font.GetHorizontalGlyphAdvances(glyphIndices); return Font.GetHorizontalGlyphAdvances(glyphIndices);
} }
public bool TryGetTable(uint tag, out byte[] table)
{
table = null;
var blob = Face.ReferenceTable(tag);
if (blob.Length > 0)
{
table = blob.AsSpan().ToArray();
return true;
}
return false;
}
private void Dispose(bool disposing) private void Dispose(bool disposing)
{ {
if (_isDisposed) if (_isDisposed)

2
tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs

@ -30,7 +30,7 @@ namespace Avalonia.UnitTests
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
var font = ((HarfBuzzGlyphTypefaceImpl)typeface.PlatformImpl).Font; var font = ((HarfBuzzGlyphTypefaceImpl)typeface).Font;
font.Shape(buffer); font.Shape(buffer);

2
tests/Avalonia.UnitTests/MockFontManagerImpl.cs

@ -33,7 +33,7 @@ namespace Avalonia.UnitTests
return false; return false;
} }
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{ {
return new MockGlyphTypeface(); return new MockGlyphTypeface();
} }

35
tests/Avalonia.UnitTests/MockGlyphTypeface.cs

@ -1,19 +1,19 @@
using System; using System;
using Avalonia.Platform; using Avalonia.Media;
namespace Avalonia.UnitTests namespace Avalonia.UnitTests
{ {
public class MockGlyphTypeface : IGlyphTypefaceImpl public class MockGlyphTypeface : IGlyphTypeface
{ {
public short DesignEmHeight => 10; public FontMetrics Metrics => new FontMetrics
public int Ascent => 2; {
public int Descent => 10; DesignEmHeight = 10,
public int LineGap { get; } Ascent = 2,
public int UnderlinePosition { get; } Descent = 10,
public int UnderlineThickness { get; } IsFixedPitch = true
public int StrikethroughPosition { get; } };
public int StrikethroughThickness { get; }
public bool IsFixedPitch { get; } public int GlyphCount => 1337;
public ushort GetGlyph(uint codepoint) public ushort GetGlyph(uint codepoint)
{ {
@ -30,6 +30,13 @@ namespace Avalonia.UnitTests
return 8; return 8;
} }
public bool TryGetGlyph(uint codepoint, out ushort glyph)
{
glyph = 8;
return true;
}
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
{ {
var advances = new int[glyphs.Length]; var advances = new int[glyphs.Length];
@ -43,5 +50,11 @@ namespace Avalonia.UnitTests
} }
public void Dispose() { } public void Dispose() { }
public bool TryGetTable(uint tag, out byte[] table)
{
table = null;
return false;
}
} }
} }

15
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -152,6 +152,21 @@ namespace Avalonia.UnitTests
return Mock.Of<IGeometryImpl>(); return Mock.Of<IGeometryImpl>();
} }
public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return Mock.Of<IGlyphRunBuffer>();
}
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return Mock.Of<IHorizontalGlyphRunBuffer>();
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return Mock.Of<IPositionedGlyphRunBuffer>();
}
public bool SupportsIndividualRoundRects { get; set; } public bool SupportsIndividualRoundRects { get; set; }
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;

Loading…
Cancel
Save