diff --git a/.editorconfig b/.editorconfig index 09f0d3e6ac..9ae52b8bbd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -139,10 +139,14 @@ dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomme # CA1802: Use literals where appropriate dotnet_diagnostic.CA1802.severity = warning -# CA1825: Avoid zero-length array allocations -dotnet_diagnostic.CA1825.severity = warning # CA1820: Test for empty strings using string length dotnet_diagnostic.CA1820.severity = warning +# CA1821: Remove empty finalizers +dotnet_diagnostic.CA1821.severity = warning +# CA1825: Avoid zero-length array allocations +dotnet_diagnostic.CA1825.severity = warning +#CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters +dotnet_diagnostic.CA1847.severity = warning # Wrapping preferences csharp_wrap_before_ternary_opsigns = false diff --git a/dirs.proj b/dirs.proj index a2544ef951..f1eaae8a4a 100644 --- a/dirs.proj +++ b/dirs.proj @@ -9,21 +9,18 @@ - - - - - - - + + + + - + diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml b/samples/ControlCatalog/Pages/ColorPickerPage.xaml index ad54eb95fc..69ceaea328 100644 --- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml @@ -11,16 +11,17 @@ x:Class="ControlCatalog.Pages.ColorPickerPage"> - - @@ -56,8 +57,5 @@ IsAccentColorsVisible="False" HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" /> - - diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs b/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs index 6e017e381f..4671bbdb7c 100644 --- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs @@ -1,6 +1,8 @@ using Avalonia; using Avalonia.Controls; +using Avalonia.Layout; using Avalonia.Markup.Xaml; +using Avalonia.Media; namespace ControlCatalog.Pages { @@ -9,6 +11,20 @@ namespace ControlCatalog.Pages public ColorPickerPage() { InitializeComponent(); + + var layoutRoot = this.GetControl("LayoutRoot"); + + // ColorPicker added from code-behind + var colorPicker = new ColorPicker() + { + Color = Colors.Blue, + Margin = new Thickness(0, 50, 0, 0), + HorizontalAlignment = HorizontalAlignment.Center, + }; + Grid.SetColumn(colorPicker, 2); + Grid.SetRow(colorPicker, 1); + + layoutRoot.Children.Add(colorPicker); } private void InitializeComponent() diff --git a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml index 29cd939520..6217d39b21 100644 --- a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml +++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml @@ -13,17 +13,17 @@ Margin="16" HorizontalAlignment="Stretch" Spacing="16"> - A simple DatePicker with a header + A simple DatePicker - + - <DatePicker Header="Pick a date" /> + <DatePicker/> @@ -33,7 +33,7 @@ - + @@ -79,24 +79,24 @@ - + - A TimePicker with a header and minute increments specified. + A TimePicker with minute increments specified. - + - <TimePicker Header="Arrival time" MinuteIncrement="15" /> + <TimePicker MinuteIncrement="15" /> @@ -107,13 +107,13 @@ - + - <TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" /> + <TimePicker ClockIdentifier="12HourClock" /> @@ -124,13 +124,13 @@ - + - <TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" /> + <TimePicker ClockIdentifier="24HourClock" /> diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs index 674ed8e61f..5c31e138e0 100644 --- a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs +++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs @@ -22,7 +22,7 @@ namespace RenderDemo.Pages public class GlyphRunControl : Control { - private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; + private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; private readonly Random _rand = new Random(); private ushort[] _glyphIndices = new ushort[1]; private char[] _characters = new char[1]; @@ -81,7 +81,7 @@ namespace RenderDemo.Pages public class GlyphRunGeometryControl : Control { - private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; + private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; private readonly Random _rand = new Random(); private ushort[] _glyphIndices = new ushort[1]; private char[] _characters = new char[1]; diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs index 705fa3c59d..de8d02f188 100644 --- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs @@ -9,11 +9,11 @@ using AndroidX.Lifecycle; namespace Avalonia.Android { - public abstract class AvaloniaMainActivity : AppCompatActivity + public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler { internal static object ViewContent; - internal Action ActivityResult; + public Action ActivityResult { get; set; } internal AvaloniaView View; protected override void OnCreate(Bundle savedInstanceState) @@ -24,8 +24,6 @@ namespace Avalonia.Android View.Content = ViewContent; } - View.Prepare(); - if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime) { lifetime.View = View; diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index 94e863210b..5267843bfc 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -21,10 +21,7 @@ namespace Avalonia.Android { _view = new ViewImpl(this); AddView(_view.View); - } - internal void Prepare () - { _root = new EmbeddableControlRoot(_view); _root.Prepare(); } diff --git a/src/Android/Avalonia.Android/IActivityResultHandler.cs b/src/Android/Avalonia.Android/IActivityResultHandler.cs new file mode 100644 index 0000000000..14094ee185 --- /dev/null +++ b/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 ActivityResult { get; set; } + } +} diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 7ce72aaca5..984eb775b5 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; - +using Android.App; using Android.Content; using Android.Graphics; using Android.Runtime; @@ -52,7 +52,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling); NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView); - StorageProvider = new AndroidStorageProvider((AvaloniaMainActivity)avaloniaView.Context); + StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context); } public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs index ce385ebe34..6d0e6be0ad 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs @@ -15,7 +15,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers internal class AndroidMotionEventsHelper : IDisposable { private static readonly PooledList 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 MouseDevice _mouseDevice; 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, 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, Pressure = Math.Min(e.GetHistoricalPressure(index, pos), 1), - Twist = e.GetHistoricalOrientation(index, pos) * s_radiansToDegree + Twist = e.GetHistoricalOrientation(index, pos) * RadiansToDegree }; } diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs index 3a1a9e76ea..62e43ff2ef 100644 --- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs +++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs @@ -14,10 +14,10 @@ namespace Avalonia.Android.Platform.Storage; internal class AndroidStorageProvider : IStorageProvider { - private readonly AvaloniaMainActivity _activity; + private readonly Activity _activity; private int _lastRequestCode = 20000; - public AndroidStorageProvider(AvaloniaMainActivity activity) + public AndroidStorageProvider(Activity activity) { _activity = activity; } @@ -119,7 +119,10 @@ internal class AndroidStorageProvider : IStorageProvider var tcs = new TaskCompletionSource(); var currentRequestCode = _lastRequestCode++; - _activity.ActivityResult += OnActivityResult; + if (_activity is IActivityResultHandler mainActivity) + { + mainActivity.ActivityResult += OnActivityResult; + } _activity.StartActivityForResult(pickerIntent, currentRequestCode); var result = await tcs.Task; @@ -158,7 +161,11 @@ internal class AndroidStorageProvider : IStorageProvider return; } - _activity.ActivityResult -= OnActivityResult; + + if (_activity is IActivityResultHandler mainActivity) + { + mainActivity.ActivityResult -= OnActivityResult; + } _ = tcs.TrySetResult(resultCode == Result.Ok ? data : null); } diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 1cb29e4e37..402bc3a099 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -21,6 +21,9 @@ + + + @@ -38,7 +41,7 @@ - + diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index fd43ced196..46ba4082fb 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -41,7 +41,7 @@ namespace Avalonia { _ = name ?? throw new ArgumentNullException(nameof(name)); - if (name.Contains(".")) + if (name.Contains('.')) { throw new ArgumentException("'name' may not contain periods."); } diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index d86e723b38..62265d3c59 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -228,7 +228,7 @@ namespace Avalonia _ = type ?? throw new ArgumentNullException(nameof(type)); _ = name ?? throw new ArgumentNullException(nameof(name)); - if (name.Contains(".")) + if (name.Contains('.')) { throw new InvalidOperationException("Attached properties not supported."); } diff --git a/src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs b/src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs new file mode 100644 index 0000000000..45e41b44d6 --- /dev/null +++ b/src/Avalonia.Base/Compatibility/StringCompatibilityExtensions.cs @@ -0,0 +1,12 @@ +using System.Runtime.CompilerServices; + +namespace System; + +#if !NET6_0_OR_GREATER +public static class StringCompatibilityExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Contains(this string str, char search) => + str.Contains(search.ToString()); +} +#endif diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index 37091b82e3..d92d003c2a 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/src/Avalonia.Base/Media/FontManager.cs @@ -13,8 +13,8 @@ namespace Avalonia.Media /// public sealed class FontManager { - private readonly ConcurrentDictionary _glyphTypefaceCache = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _glyphTypefaceCache = + new ConcurrentDictionary(); private readonly FontFamily _defaultFontFamily; private readonly IReadOnlyList? _fontFallbacks; @@ -81,13 +81,13 @@ namespace Avalonia.Media PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates); /// - /// Returns a new , or an existing one if a matching exists. + /// Returns a new , or an existing one if a matching exists. /// /// The typeface. /// - /// The . + /// The . /// - public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface) + public IGlyphTypeface GetOrAddGlyphTypeface(Typeface typeface) { while (true) { @@ -96,7 +96,7 @@ namespace Avalonia.Media return glyphTypeface; } - glyphTypeface = new GlyphTypeface(typeface); + glyphTypeface = PlatformImpl.CreateGlyphTypeface(typeface); if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface)) { diff --git a/src/Avalonia.Base/Media/FontMetrics.cs b/src/Avalonia.Base/Media/FontMetrics.cs new file mode 100644 index 0000000000..1cd01675db --- /dev/null +++ b/src/Avalonia.Base/Media/FontMetrics.cs @@ -0,0 +1,58 @@ +namespace Avalonia.Media +{ + /// + /// The font metrics is holding information about a font's ascent, descent, etc. in design em units. + /// + public readonly struct FontMetrics + { + /// + /// Gets the font design units per em. + /// + public short DesignEmHeight { get; init; } + + /// + /// A value indicating whether all glyphs in the font have the same advancement. + /// + public bool IsFixedPitch { get; init; } + + /// + /// Gets the recommended distance above the baseline in design em size. + /// + public int Ascent { get; init; } + + /// + /// Gets the recommended distance under the baseline in design em size. + /// + public int Descent { get; init; } + + /// + /// Gets the recommended additional space between two lines of text in design em size. + /// + public int LineGap { get; init; } + + /// + /// Gets the recommended line spacing of a formed text line. + /// + public int LineSpacing => Descent - Ascent + LineGap; + + /// + /// Gets a value that indicates the distance of the underline from the baseline in design em size. + /// + public int UnderlinePosition { get; init; } + + /// + /// Gets a value that indicates the thickness of the underline in design em size. + /// + public int UnderlineThickness { get; init; } + + /// + /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size. + /// + public int StrikethroughPosition { get; init; } + + /// + /// Gets a value that indicates the thickness of the underline in design em size. + /// + public int StrikethroughThickness { get; init; } + } +} diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index 2289f98228..a1cb00e209 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Platform; using Avalonia.Utilities; @@ -15,7 +16,7 @@ namespace Avalonia.Media private static readonly IComparer s_descendingComparer = new ReverseComparer(); private IGlyphRunImpl? _glyphRunImpl; - private GlyphTypeface _glyphTypeface; + private IGlyphTypeface _glyphTypeface; private double _fontRenderingEmSize; private int _biDiLevel; private Point? _baselineOrigin; @@ -42,7 +43,7 @@ namespace Avalonia.Media /// The glyph clusters. /// The bidi level. public GlyphRun( - GlyphTypeface glyphTypeface, + IGlyphTypeface glyphTypeface, double fontRenderingEmSize, ReadOnlySlice characters, IReadOnlyList glyphIndices, @@ -69,9 +70,9 @@ namespace Avalonia.Media } /// - /// Gets the for the . + /// Gets the for the . /// - public GlyphTypeface GlyphTypeface => _glyphTypeface; + public IGlyphTypeface GlyphTypeface => _glyphTypeface; /// /// Gets or sets the em size used for rendering the . @@ -171,7 +172,7 @@ namespace Avalonia.Media /// /// Gets the scale of the current /// - internal double Scale => FontRenderingEmSize / GlyphTypeface.DesignEmHeight; + internal double Scale => FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight; /// /// Returns true if the text direction is left-to-right. Otherwise, returns false. @@ -612,7 +613,7 @@ namespace Avalonia.Media /// The baseline origin. private Point CalculateBaselineOrigin() { - return new Point(0, -GlyphTypeface.Ascent * Scale); + return new Point(0, -GlyphTypeface.Metrics.Ascent * Scale); } private GlyphRunMetrics CreateGlyphRunMetrics() @@ -636,7 +637,7 @@ namespace Avalonia.Media } var isReversed = firstCluster > lastCluster; - var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; + var height = GlyphTypeface.Metrics.LineSpacing * Scale; var widthIncludingTrailingWhitespace = 0d; var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount); @@ -854,9 +855,87 @@ namespace Avalonia.Media throw new InvalidOperationException(); } + _glyphRunImpl = CreateGlyphRunImpl(); + } + + private IGlyphRunImpl CreateGlyphRunImpl() + { + IGlyphRunImpl glyphRunImpl; + var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService(); + 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() diff --git a/src/Avalonia.Base/Media/GlyphTypeface.cs b/src/Avalonia.Base/Media/GlyphTypeface.cs deleted file mode 100644 index 45ef04e77f..0000000000 --- a/src/Avalonia.Base/Media/GlyphTypeface.cs +++ /dev/null @@ -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; } - - /// - /// Gets the font design units per em. - /// - public short DesignEmHeight => PlatformImpl.DesignEmHeight; - - /// - /// Gets the recommended distance above the baseline in design em size. - /// - public int Ascent => PlatformImpl.Ascent; - - /// - /// Gets the recommended distance under the baseline in design em size. - /// - public int Descent => PlatformImpl.Descent; - - /// - /// Gets the recommended additional space between two lines of text in design em size. - /// - public int LineGap => PlatformImpl.LineGap; - - /// - /// Gets the recommended line height. - /// - public int LineHeight => Descent - Ascent + LineGap; - - /// - /// Gets a value that indicates the distance of the underline from the baseline in design em size. - /// - public int UnderlinePosition => PlatformImpl.UnderlinePosition; - - /// - /// Gets a value that indicates the thickness of the underline in design em size. - /// - public int UnderlineThickness => PlatformImpl.UnderlineThickness; - - /// - /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size. - /// - public int StrikethroughPosition => PlatformImpl.StrikethroughPosition; - - /// - /// Gets a value that indicates the thickness of the underline in design em size. - /// - public int StrikethroughThickness => PlatformImpl.StrikethroughThickness; - - /// - /// A value indicating whether all glyphs in the font have the same advancement. - /// - public bool IsFixedPitch => PlatformImpl.IsFixedPitch; - - /// - /// Returns an glyph index for the specified codepoint. - /// - /// - /// Returns a replacement glyph if a glyph isn't found. - /// - /// The codepoint. - /// - /// A glyph index. - /// - public ushort GetGlyph(uint codepoint) => PlatformImpl.GetGlyph(codepoint); - - /// - /// Tries to get an glyph index for specified codepoint. - /// - /// The codepoint. - /// A glyph index. - /// - /// true if an glyph index was found, false otherwise. - /// - public bool TryGetGlyph(uint codepoint, out ushort glyph) - { - glyph = PlatformImpl.GetGlyph(codepoint); - - return glyph != 0; - } - - /// - /// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as 0. - /// - /// The codepoints to map. - /// - public ushort[] GetGlyphs(ReadOnlySpan codepoints) => PlatformImpl.GetGlyphs(codepoints); - - /// - /// Returns the glyph advance for the specified glyph. - /// - /// The glyph. - /// - /// The advance. - /// - public int GetGlyphAdvance(ushort glyph) => PlatformImpl.GetGlyphAdvance(glyph); - - /// - /// Returns an array of glyph advances in design em size. - /// - /// The glyph indices. - /// - public int[] GetGlyphAdvances(ReadOnlySpan glyphs) => PlatformImpl.GetGlyphAdvances(glyphs); - - void IDisposable.Dispose() - { - PlatformImpl?.Dispose(); - } - } -} diff --git a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs b/src/Avalonia.Base/Media/IGlyphTypeface.cs similarity index 52% rename from src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs rename to src/Avalonia.Base/Media/IGlyphTypeface.cs index 415f34fb29..de2a2309ee 100644 --- a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs +++ b/src/Avalonia.Base/Media/IGlyphTypeface.cs @@ -1,55 +1,23 @@ using System; using Avalonia.Metadata; -namespace Avalonia.Platform +namespace Avalonia.Media { [Unstable] - public interface IGlyphTypefaceImpl : IDisposable + public interface IGlyphTypeface : IDisposable { /// - /// Gets the font design units per em. + /// Gets the number of glyphs held by this glyph typeface. /// - short DesignEmHeight { get; } + int GlyphCount { get; } /// - /// Gets the recommended distance above the baseline in design em size. + /// Gets the font metrics. /// - int Ascent { get; } - - /// - /// Gets the recommended distance under the baseline in design em size. - /// - int Descent { get; } - - /// - /// Gets the recommended additional space between two lines of text in design em size. - /// - int LineGap { get; } - - /// - /// Gets a value that indicates the distance of the underline from the baseline in design em size. - /// - int UnderlinePosition { get; } - - /// - /// Gets a value that indicates the thickness of the underline in design em size. - /// - int UnderlineThickness { get; } - - /// - /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size. - /// - int StrikethroughPosition { get; } - - /// - /// Gets a value that indicates the thickness of the underline in design em size. - /// - int StrikethroughThickness { get; } - - /// - /// A value indicating whether all glyphs in the font have the same advancement. - /// - bool IsFixedPitch { get; } + /// + /// The font metrics. + /// + FontMetrics Metrics { get; } /// /// Returns an glyph index for the specified codepoint. @@ -63,6 +31,16 @@ namespace Avalonia.Platform /// ushort GetGlyph(uint codepoint); + /// + /// Tries to get an glyph index for specified codepoint. + /// + /// The codepoint. + /// A glyph index. + /// + /// true if an glyph index was found, false otherwise. + /// + bool TryGetGlyph(uint codepoint, out ushort glyph); + /// /// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as 0. /// @@ -89,5 +67,13 @@ namespace Avalonia.Platform /// An array of glyph advances. /// int[] GetGlyphAdvances(ReadOnlySpan glyphs); + + /// + /// Returns the contents of the table data for the specified tag. + /// + /// The table tag to get the data for. + /// The contents of the table data for the specified tag. + /// Returns true if the content exists, otherwise false. + bool TryGetTable(uint tag, out byte[] table); } } diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs index 4c9764af96..0a7328125a 100644 --- a/src/Avalonia.Base/Media/TextDecoration.cs +++ b/src/Avalonia.Base/Media/TextDecoration.cs @@ -155,9 +155,9 @@ namespace Avalonia.Media /// /// The drawing context. /// The decorated run. - /// The font metrics of the decorated run. + /// The font metrics of the decorated run. /// The default brush that is used to draw the decoration. - 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 thickness = StrokeThickness; @@ -168,16 +168,16 @@ namespace Avalonia.Media switch (Location) { case TextDecorationLocation.Underline: - thickness = fontMetrics.UnderlineThickness; + thickness = textMetrics.UnderlineThickness; break; case TextDecorationLocation.Strikethrough: - thickness = fontMetrics.StrikethroughThickness; + thickness = textMetrics.StrikethroughThickness; break; } break; case TextDecorationUnit.FontRenderingEmSize: - thickness = fontMetrics.FontRenderingEmSize * thickness; + thickness = textMetrics.FontRenderingEmSize * thickness; break; } @@ -189,17 +189,17 @@ namespace Avalonia.Media origin += glyphRun.BaselineOrigin; break; case TextDecorationLocation.Strikethrough: - origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.StrikethroughPosition); + origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.StrikethroughPosition); break; case TextDecorationLocation.Underline: - origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.UnderlinePosition); + origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.UnderlinePosition); break; } switch (StrokeOffsetUnit) { case TextDecorationUnit.FontRenderingEmSize: - origin += new Point(0, StrokeOffset * fontMetrics.FontRenderingEmSize); + origin += new Point(0, StrokeOffset * textMetrics.FontRenderingEmSize); break; case TextDecorationUnit.Pixel: origin += new Point(0, StrokeOffset); diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs index 47a6334e39..85924a3d32 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs @@ -8,13 +8,13 @@ namespace Avalonia.Media.TextFormatting { private static readonly IComparer s_clusterComparer = new CompareClusters(); - public ShapedBuffer(ReadOnlySlice text, int length, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) + public ShapedBuffer(ReadOnlySlice text, int length, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) : this(text, new GlyphInfo[length], glyphTypeface, fontRenderingEmSize, bidiLevel) { } - internal ShapedBuffer(ReadOnlySlice text, ArraySlice glyphInfos, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) + internal ShapedBuffer(ReadOnlySlice text, ArraySlice glyphInfos, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) { Text = text; GlyphInfos = glyphInfos; @@ -29,7 +29,7 @@ namespace Avalonia.Media.TextFormatting public int Length => GlyphInfos.Length; - public GlyphTypeface GlyphTypeface { get; } + public IGlyphTypeface GlyphTypeface { get; } public double FontRenderingEmSize { get; } diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs index 53287a264d..21101f462c 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Utilities; @@ -18,7 +17,7 @@ namespace Avalonia.Media.TextFormatting Text = shapedBuffer.Text; Properties = properties; TextSourceLength = Text.Length; - FontMetrics = new FontMetrics(properties.Typeface, properties.FontRenderingEmSize); + TextMetrics = new TextMetrics(properties.Typeface, properties.FontRenderingEmSize); } public bool IsReversed { get; private set; } @@ -36,9 +35,9 @@ namespace Avalonia.Media.TextFormatting /// 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; @@ -89,7 +88,7 @@ namespace Avalonia.Media.TextFormatting foreach (var textDecoration in Properties.TextDecorations) { - textDecoration.Draw(drawingContext, GlyphRun, FontMetrics, Properties.ForegroundBrush); + textDecoration.Draw(drawingContext, GlyphRun, TextMetrics, Properties.ForegroundBrush); } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index aba8008fb9..96f88d1f44 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -1378,17 +1378,17 @@ namespace Avalonia.Media.TextFormatting private TextLineMetrics CreateLineMetrics() { - var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface; + var fontMetrics = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface.Metrics; var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize; - var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight; + var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight; var width = 0d; var widthIncludingWhitespace = 0d; var trailingWhitespaceLength = 0; var newLineLength = 0; - var ascent = glyphTypeface.Ascent * scale; - var descent = glyphTypeface.Descent * scale; - var lineGap = glyphTypeface.LineGap * scale; + var ascent = fontMetrics.Ascent * scale; + var descent = fontMetrics.Descent * scale; + var lineGap = fontMetrics.LineGap * scale; var height = descent - ascent + lineGap; @@ -1400,26 +1400,26 @@ namespace Avalonia.Media.TextFormatting { case ShapedTextCharacters textRun: { - var fontMetrics = - new FontMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize); + var textMetrics = + new TextMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize); if (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) diff --git a/src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs b/src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs similarity index 69% rename from src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs rename to src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs index e01bba00a4..0382e66b5a 100644 --- a/src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs @@ -1,33 +1,33 @@ namespace Avalonia.Media.TextFormatting { /// - /// A metric that holds information about font specific measurements. + /// A metric that holds information about text specific measurements. /// - 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; - 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; - 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; } /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs index 4e75bb921e..0d00bed51e 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs @@ -8,7 +8,7 @@ namespace Avalonia.Media.TextFormatting public readonly struct TextShaperOptions { public TextShaperOptions( - GlyphTypeface typeface, + IGlyphTypeface typeface, double fontRenderingEmSize = 12, sbyte bidiLevel = 0, CultureInfo? culture = null, @@ -24,7 +24,7 @@ namespace Avalonia.Media.TextFormatting /// /// Get the typeface. /// - public GlyphTypeface Typeface { get; } + public IGlyphTypeface Typeface { get; } /// /// Get the font rendering em size. /// diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs index ce9cdde044..de40839853 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs +++ b/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; namespace Avalonia.Media.TextFormatting.Unicode @@ -165,7 +166,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// The index to read at. /// The count of character that were read. /// - public static Codepoint ReadAt(ReadOnlySlice text, int index, out int count) + public static Codepoint ReadAt(ReadOnlySpan text, int index, out int count) { count = 1; diff --git a/src/Avalonia.Base/Media/Typeface.cs b/src/Avalonia.Base/Media/Typeface.cs index f0daa841d9..e6047bf96c 100644 --- a/src/Avalonia.Base/Media/Typeface.cs +++ b/src/Avalonia.Base/Media/Typeface.cs @@ -81,7 +81,7 @@ namespace Avalonia.Media /// /// The glyph typeface. /// - public GlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); + public IGlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); public static bool operator !=(Typeface a, Typeface b) { diff --git a/src/Avalonia.Base/Media/UnicodeRange.cs b/src/Avalonia.Base/Media/UnicodeRange.cs index e2338b9b26..344b85bae9 100644 --- a/src/Avalonia.Base/Media/UnicodeRange.cs +++ b/src/Avalonia.Base/Media/UnicodeRange.cs @@ -163,7 +163,7 @@ namespace Avalonia.Media throw new FormatException("Could not parse specified Unicode range segment."); } - if (!single.Value.Contains("?")) + if (!single.Value.Contains('?')) { start = int.Parse(single.Groups[1].Value, System.Globalization.NumberStyles.HexNumber); end = start; diff --git a/src/Avalonia.Base/Platform/IFontManagerImpl.cs b/src/Avalonia.Base/Platform/IFontManagerImpl.cs index 932249bd52..cd6e64abaf 100644 --- a/src/Avalonia.Base/Platform/IFontManagerImpl.cs +++ b/src/Avalonia.Base/Platform/IFontManagerImpl.cs @@ -43,6 +43,6 @@ namespace Avalonia.Platform /// 0 /// The created glyph typeface. Can be Null if it was not possible to create a glyph typeface. /// - IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface); + IGlyphTypeface CreateGlyphTypeface(Typeface typeface); } } diff --git a/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs b/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs new file mode 100644 index 0000000000..c1fc7a5967 --- /dev/null +++ b/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs @@ -0,0 +1,22 @@ +using System; +using System.Drawing; + +namespace Avalonia.Platform +{ + public interface IGlyphRunBuffer + { + Span GlyphIndices { get; } + + IGlyphRunImpl Build(); + } + + public interface IHorizontalGlyphRunBuffer : IGlyphRunBuffer + { + Span GlyphPositions { get; } + } + + public interface IPositionedGlyphRunBuffer : IGlyphRunBuffer + { + Span GlyphPositions { get; } + } +} diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index e39a4e23df..9d0d7974b4 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/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); /// - /// Creates a platform implementation of a glyph run. + /// Allocates a platform glyph run buffer. /// - /// The glyph run. - /// - IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun); + /// The glyph typeface. + /// The font rendering em size. + /// The length. + /// An . + /// + /// This buffer only holds glyph indices. + /// + IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + + /// + /// Allocates a horizontal platform glyph run buffer. + /// + /// The glyph typeface. + /// The font rendering em size. + /// The length. + /// An . + /// + /// This buffer holds glyph indices and glyph advances. + /// + IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + + /// + /// Allocates a positioned platform glyph run buffer. + /// + /// The glyph typeface. + /// The font rendering em size. + /// The length. + /// An . + /// + /// This buffer holds glyph indices, glyph advances and glyph positions. + /// + IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); /// /// Gets a value indicating whether the platform directly supports rectangles with rounded corners. diff --git a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs index b0b3982ed5..06fb526736 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs @@ -25,7 +25,7 @@ internal class FpsCounter // ASCII chars private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1]; - public FpsCounter(GlyphTypeface typeface) + public FpsCounter(IGlyphTypeface typeface) { for (var c = FirstChar; c <= LastChar; c++) { diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 3c48c3469e..feb1097b5f 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -11,7 +11,7 @@ namespace Avalonia.Utilities static class MathUtilities { // 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; diff --git a/src/Avalonia.Base/Utilities/ReadOnlySlice.cs b/src/Avalonia.Base/Utilities/ReadOnlySlice.cs index ad545b2923..583a3139b9 100644 --- a/src/Avalonia.Base/Utilities/ReadOnlySlice.cs +++ b/src/Avalonia.Base/Utilities/ReadOnlySlice.cs @@ -214,6 +214,8 @@ namespace Avalonia.Utilities return new ReadOnlySlice(memory); } + public static implicit operator ReadOnlySpan(ReadOnlySlice slice) => slice.Span; + internal class ReadOnlySliceDebugView { private readonly ReadOnlySlice _readOnlySlice; diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs index 130d7e0edd..2cc5b99b2e 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs @@ -17,268 +17,603 @@ namespace Avalonia.Controls /// public class FlatColorPalette : IColorPalette { - // The full Flat UI color chart has 10 rows and 20 columns - // See: https://htmlcolorcodes.com/assets/downloads/flat-design-colors/flat-design-color-chart.png - // This is a reduced palette for usability - private static Color[,] colorChart = new Color[,] + /// + /// Defines all colors in the . + /// + /// + /// This is done in an enum to ensure it is compiled into the assembly improving + /// startup performance. + /// + public enum FlatColor : uint { // Pomegranate - { - Color.FromArgb(0xFF, 0xF9, 0xEB, 0xEA), - Color.FromArgb(0xFF, 0xE6, 0xB0, 0xAA), - Color.FromArgb(0xFF, 0xCD, 0x61, 0x55), - Color.FromArgb(0xFF, 0xA9, 0x32, 0x26), - Color.FromArgb(0xFF, 0x7B, 0x24, 0x1C), - }, + Pomegranate1 = 0xFFF9EBEA, + Pomegranate2 = 0xFFF2D7D5, + Pomegranate3 = 0xFFE6B0AA, + Pomegranate4 = 0xFFD98880, + Pomegranate5 = 0xFFCD6155, + 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 - { - Color.FromArgb(0xFF, 0xF5, 0xEE, 0xF8), - Color.FromArgb(0xFF, 0xD7, 0xBD, 0xE2), - Color.FromArgb(0xFF, 0xAF, 0x7A, 0xC5), - Color.FromArgb(0xFF, 0x88, 0x4E, 0xA0), - Color.FromArgb(0xFF, 0x63, 0x39, 0x74), - }, + Amethyst1 = 0xFFF5EEF8, + Amethyst2 = 0xFFEBDEF0, + Amethyst3 = 0xFFD7BDE2, + Amethyst4 = 0xFFC39BD3, + Amethyst5 = 0xFFAF7AC5, + 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 - { - Color.FromArgb(0xFF, 0xEA, 0xF2, 0xF8), - Color.FromArgb(0xFF, 0xA9, 0xCC, 0xE3), - Color.FromArgb(0xFF, 0x54, 0x99, 0xC7), - Color.FromArgb(0xFF, 0x24, 0x71, 0xA3), - Color.FromArgb(0xFF, 0x1A, 0x52, 0x76), - }, + BelizeHole1 = 0xFFEAF2F8, + BelizeHole2 = 0xFFD4E6F1, + BelizeHole3 = 0xFFA9CCE3, + BelizeHole4 = 0xFF7FB3D5, + BelizeHole5 = 0xFF5499C7, + 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 - { - Color.FromArgb(0xFF, 0xE8, 0xF8, 0xF5), - Color.FromArgb(0xFF, 0xA3, 0xE4, 0xD7), - Color.FromArgb(0xFF, 0x48, 0xC9, 0xB0), - Color.FromArgb(0xFF, 0x17, 0xA5, 0x89), - Color.FromArgb(0xFF, 0x11, 0x78, 0x64), - }, + Turquoise1 = 0xFFE8F8F5, + Turquoise2 = 0xFFD1F2EB, + Turquoise3 = 0xFFA3E4D7, + Turquoise4 = 0xFF76D7C4, + Turquoise5 = 0xFF48C9B0, + 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 - { - Color.FromArgb(0xFF, 0xE9, 0xF7, 0xEF), - Color.FromArgb(0xFF, 0xA9, 0xDF, 0xBF), - Color.FromArgb(0xFF, 0x52, 0xBE, 0x80), - Color.FromArgb(0xFF, 0x22, 0x99, 0x54), - Color.FromArgb(0xFF, 0x19, 0x6F, 0x3D), - }, + Nephritis1 = 0xFFE9F7EF, + Nephritis2 = 0xFFD4EFDF, + Nephritis3 = 0xFFA9DFBF, + Nephritis4 = 0xFF7DCEA0, + Nephritis5 = 0xFF52BE80, + 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 - { - Color.FromArgb(0xFF, 0xFE, 0xF9, 0xE7), - Color.FromArgb(0xFF, 0xF9, 0xE7, 0x9F), - Color.FromArgb(0xFF, 0xF4, 0xD0, 0x3F), - Color.FromArgb(0xFF, 0xD4, 0xAC, 0x0D), - Color.FromArgb(0xFF, 0x9A, 0x7D, 0x0A), - }, + Sunflower1 = 0xFFFEF9E7, + Sunflower2 = 0xFFFCF3CF, + Sunflower3 = 0xFFF9E79F, + Sunflower4 = 0xFFF7DC6F, + Sunflower5 = 0xFFF4D03F, + 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 - { - Color.FromArgb(0xFF, 0xFD, 0xF2, 0xE9), - Color.FromArgb(0xFF, 0xF5, 0xCB, 0xA7), - Color.FromArgb(0xFF, 0xEB, 0x98, 0x4E), - Color.FromArgb(0xFF, 0xCA, 0x6F, 0x1E), - Color.FromArgb(0xFF, 0x93, 0x51, 0x16), - }, + Carrot1 = 0xFFFDF2E9, + Carrot2 = 0xFFFAE5D3, + Carrot3 = 0xFFF5CBA7, + Carrot4 = 0xFFF0B27A, + Carrot5 = 0xFFEB984E, + 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 - { - Color.FromArgb(0xFF, 0xFD, 0xFE, 0xFE), - Color.FromArgb(0xFF, 0xF7, 0xF9, 0xF9), - Color.FromArgb(0xFF, 0xF0, 0xF3, 0xF4), - Color.FromArgb(0xFF, 0xD0, 0xD3, 0xD4), - Color.FromArgb(0xFF, 0x97, 0x9A, 0x9A), - }, + Clouds1 = 0xFFFDFEFE, + Clouds2 = 0xFFFBFCFC, + Clouds3 = 0xFFF7F9F9, + Clouds4 = 0xFFF4F6F7, + Clouds5 = 0xFFF0F3F4, + 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 - { - Color.FromArgb(0xFF, 0xF4, 0xF6, 0xF6), - Color.FromArgb(0xFF, 0xD5, 0xDB, 0xDB), - Color.FromArgb(0xFF, 0xAA, 0xB7, 0xB8), - Color.FromArgb(0xFF, 0x83, 0x91, 0x92), - Color.FromArgb(0xFF, 0x5F, 0x6A, 0x6A), - }, + Concrete1 = 0xFFF4F6F6, + Concrete2 = 0xFFEAEDED, + Concrete3 = 0xFFD5DBDB, + Concrete4 = 0xFFBFC9CA, + Concrete5 = 0xFFAAB7B8, + 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 - { - Color.FromArgb(0xFF, 0xEB, 0xED, 0xEF), - Color.FromArgb(0xFF, 0xAE, 0xB6, 0xBF), - Color.FromArgb(0xFF, 0x5D, 0x6D, 0x7E), - Color.FromArgb(0xFF, 0x2E, 0x40, 0x53), - Color.FromArgb(0xFF, 0x21, 0x2F, 0x3C), - }, + WetAsphalt1 = 0xFFEBEDEF, + WetAsphalt2 = 0xFFD6DBDF, + WetAsphalt3 = 0xFFAEB6BF, + WetAsphalt4 = 0xFF85929E, + WetAsphalt5 = 0xFF5D6D7E, + 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, }; - /// - /// Gets the index of the default shade of colors in this palette. - /// - public const int DefaultShadeIndex = 2; - - /// - /// The index in the color palette of the 'Pomegranate' color. - /// This index can correspond to multiple color shades. - /// - public const int PomegranateIndex = 0; - - /// - /// The index in the color palette of the 'Amethyst' color. - /// This index can correspond to multiple color shades. - /// - public const int AmethystIndex = 1; - - /// - /// The index in the color palette of the 'BelizeHole' color. - /// This index can correspond to multiple color shades. - /// - public const int BelizeHoleIndex = 2; - - /// - /// The index in the color palette of the 'Turquoise' color. - /// This index can correspond to multiple color shades. - /// - public const int TurquoiseIndex = 3; - - /// - /// The index in the color palette of the 'Nephritis' color. - /// This index can correspond to multiple color shades. - /// - public const int NephritisIndex = 4; - - /// - /// The index in the color palette of the 'Sunflower' color. - /// This index can correspond to multiple color shades. - /// - public const int SunflowerIndex = 5; - - /// - /// The index in the color palette of the 'Carrot' color. - /// This index can correspond to multiple color shades. - /// - public const int CarrotIndex = 6; - - /// - /// The index in the color palette of the 'Clouds' color. - /// This index can correspond to multiple color shades. - /// - public const int CloudsIndex = 7; - - /// - /// The index in the color palette of the 'Concrete' color. - /// This index can correspond to multiple color shades. - /// - public const int ConcreteIndex = 8; + // See: https://htmlcolorcodes.com/assets/downloads/flat-design-colors/flat-design-color-chart.png + protected static Color[,]? _colorChart = null; + protected static object _colorChartMutex = new object(); /// - /// The index in the color palette of the 'WetAsphalt' color. - /// This index can correspond to multiple color shades. + /// Initializes all color chart colors. /// - 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; + } /// public int ColorCount { - // Table is transposed compared to the reference chart - get => colorChart.GetLength(0); + get => 20; } /// public int ShadeCount { - // Table is transposed compared to the reference chart - get => colorChart.GetLength(1); - } - - /// - /// Gets the palette defined color that has an ARGB value of #FFC0392B. - /// - public static Color Pomegranate - { - get => colorChart[PomegranateIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF9B59B6. - /// - public static Color Amethyst - { - get => colorChart[AmethystIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF2980B9. - /// - public static Color BelizeHole - { - get => colorChart[BelizeHoleIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF1ABC9C. - /// - public static Color Turquoise - { - get => colorChart[TurquoiseIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF27AE60. - /// - public static Color Nephritis - { - get => colorChart[NephritisIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FFF1C40F. - /// - public static Color Sunflower - { - get => colorChart[SunflowerIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FFE67E22. - /// - public static Color Carrot - { - get => colorChart[CarrotIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FFECF0F1. - /// - public static Color Clouds - { - get => colorChart[CloudsIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF95A5A6. - /// - public static Color Concrete - { - get => colorChart[ConcreteIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF34495E. - /// - public static Color WetAsphalt - { - get => colorChart[WetAsphaltIndex, DefaultShadeIndex]; + get => 10; } /// public Color GetColor(int colorIndex, int shadeIndex) { - // Table is transposed compared to the reference chart - return colorChart[ - MathUtilities.Clamp(colorIndex, 0, colorChart.GetLength(0) - 1), - MathUtilities.Clamp(shadeIndex, 0, colorChart.GetLength(1) - 1)]; + if (_colorChart == null) + { + InitColorChart(); + } + + return _colorChart![ + MathUtilities.Clamp(colorIndex, 0, ColorCount - 1), + MathUtilities.Clamp(shadeIndex, 0, ShadeCount - 1)]; } } } diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs new file mode 100644 index 0000000000..2758124fae --- /dev/null +++ b/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 +{ + /// + /// Implements half of the for improved usability. + /// + /// + public class FlatHalfColorPalette : IColorPalette + { + protected static Color[,]? _colorChart = null; + protected static object _colorChartMutex = new object(); + + /// + /// Initializes all color chart colors. + /// + 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; + } + + /// + public int ColorCount + { + get => 10; + } + + /// + public int ShadeCount + { + get => 5; + } + + /// + 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)]; + } + } +} diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs index 5cf5662ede..8fa7ede77e 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs @@ -21,295 +21,529 @@ namespace Avalonia.Controls /// public class MaterialColorPalette : IColorPalette { + /// + /// Defines all colors in the . + /// + /// + /// This is done in an enum to ensure it is compiled into the assembly improving + /// startup performance. + /// + 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 // This is a reduced palette for uniformity - private static Color[,]? _colorChart = null; - private static int _colorChartColorCount = 0; - private static int _colorChartShadeCount = 0; - private static object _colorChartMutex = new object(); + protected static Color[,]? _colorChart = null; + protected static object _colorChartMutex = new object(); /// /// Initializes all color chart colors. /// - /// - /// This is pulled out separately to lazy load for performance. - /// If no material color palette is ever used, no colors will be created. - /// - private void InitColorChart() + protected void InitColorChart() { lock (_colorChartMutex) { + if (_colorChart != null) + { + return; + } + _colorChart = new Color[,] { // Red { - Color.FromArgb(0xFF, 0xFF, 0xEB, 0xEE), - Color.FromArgb(0xFF, 0xFF, 0xCD, 0xD2), - Color.FromArgb(0xFF, 0xEF, 0x9A, 0x9A), - Color.FromArgb(0xFF, 0xE5, 0x73, 0x73), - Color.FromArgb(0xFF, 0xEF, 0x53, 0x50), - Color.FromArgb(0xFF, 0xF4, 0x43, 0x36), - Color.FromArgb(0xFF, 0xE5, 0x39, 0x35), - Color.FromArgb(0xFF, 0xD3, 0x2F, 0x2F), - Color.FromArgb(0xFF, 0xC6, 0x28, 0x28), - Color.FromArgb(0xFF, 0xB7, 0x1C, 0x1C), + Color.FromUInt32((uint)MaterialColor.Red50), + Color.FromUInt32((uint)MaterialColor.Red100), + Color.FromUInt32((uint)MaterialColor.Red200), + Color.FromUInt32((uint)MaterialColor.Red300), + Color.FromUInt32((uint)MaterialColor.Red400), + Color.FromUInt32((uint)MaterialColor.Red500), + Color.FromUInt32((uint)MaterialColor.Red600), + Color.FromUInt32((uint)MaterialColor.Red700), + Color.FromUInt32((uint)MaterialColor.Red800), + Color.FromUInt32((uint)MaterialColor.Red900), }, // Pink { - Color.FromArgb(0xFF, 0xFC, 0xE4, 0xEC), - Color.FromArgb(0xFF, 0xF8, 0xBB, 0xD0), - Color.FromArgb(0xFF, 0xF4, 0x8F, 0xB1), - Color.FromArgb(0xFF, 0xF0, 0x62, 0x92), - Color.FromArgb(0xFF, 0xEC, 0x40, 0x7A), - Color.FromArgb(0xFF, 0xE9, 0x1E, 0x63), - Color.FromArgb(0xFF, 0xD8, 0x1B, 0x60), - Color.FromArgb(0xFF, 0xC2, 0x18, 0x5B), - Color.FromArgb(0xFF, 0xAD, 0x14, 0x57), - Color.FromArgb(0xFF, 0x88, 0x0E, 0x4F), + Color.FromUInt32((uint)MaterialColor.Pink50), + Color.FromUInt32((uint)MaterialColor.Pink100), + Color.FromUInt32((uint)MaterialColor.Pink200), + Color.FromUInt32((uint)MaterialColor.Pink300), + Color.FromUInt32((uint)MaterialColor.Pink400), + Color.FromUInt32((uint)MaterialColor.Pink500), + Color.FromUInt32((uint)MaterialColor.Pink600), + Color.FromUInt32((uint)MaterialColor.Pink700), + Color.FromUInt32((uint)MaterialColor.Pink800), + Color.FromUInt32((uint)MaterialColor.Pink900), }, // Purple { - Color.FromArgb(0xFF, 0xF3, 0xE5, 0xF5), - Color.FromArgb(0xFF, 0xE1, 0xBE, 0xE7), - Color.FromArgb(0xFF, 0xCE, 0x93, 0xD8), - Color.FromArgb(0xFF, 0xBA, 0x68, 0xC8), - Color.FromArgb(0xFF, 0xAB, 0x47, 0xBC), - Color.FromArgb(0xFF, 0x9C, 0x27, 0xB0), - Color.FromArgb(0xFF, 0x8E, 0x24, 0xAA), - Color.FromArgb(0xFF, 0x7B, 0x1F, 0xA2), - Color.FromArgb(0xFF, 0x6A, 0x1B, 0x9A), - Color.FromArgb(0xFF, 0x4A, 0x14, 0x8C), + Color.FromUInt32((uint)MaterialColor.Purple50), + Color.FromUInt32((uint)MaterialColor.Purple100), + Color.FromUInt32((uint)MaterialColor.Purple200), + Color.FromUInt32((uint)MaterialColor.Purple300), + Color.FromUInt32((uint)MaterialColor.Purple400), + Color.FromUInt32((uint)MaterialColor.Purple500), + Color.FromUInt32((uint)MaterialColor.Purple600), + Color.FromUInt32((uint)MaterialColor.Purple700), + Color.FromUInt32((uint)MaterialColor.Purple800), + Color.FromUInt32((uint)MaterialColor.Purple900), }, // Deep Purple { - Color.FromArgb(0xFF, 0xED, 0xE7, 0xF6), - Color.FromArgb(0xFF, 0xD1, 0xC4, 0xE9), - Color.FromArgb(0xFF, 0xB3, 0x9D, 0xDB), - Color.FromArgb(0xFF, 0x95, 0x75, 0xCD), - Color.FromArgb(0xFF, 0x7E, 0x57, 0xC2), - Color.FromArgb(0xFF, 0x67, 0x3A, 0xB7), - Color.FromArgb(0xFF, 0x5E, 0x35, 0xB1), - Color.FromArgb(0xFF, 0x51, 0x2D, 0xA8), - Color.FromArgb(0xFF, 0x45, 0x27, 0xA0), - Color.FromArgb(0xFF, 0x31, 0x1B, 0x92), + Color.FromUInt32((uint)MaterialColor.DeepPurple50), + Color.FromUInt32((uint)MaterialColor.DeepPurple100), + Color.FromUInt32((uint)MaterialColor.DeepPurple200), + Color.FromUInt32((uint)MaterialColor.DeepPurple300), + Color.FromUInt32((uint)MaterialColor.DeepPurple400), + Color.FromUInt32((uint)MaterialColor.DeepPurple500), + Color.FromUInt32((uint)MaterialColor.DeepPurple600), + Color.FromUInt32((uint)MaterialColor.DeepPurple700), + Color.FromUInt32((uint)MaterialColor.DeepPurple800), + Color.FromUInt32((uint)MaterialColor.DeepPurple900), }, // Indigo { - Color.FromArgb(0xFF, 0xE8, 0xEA, 0xF6), - Color.FromArgb(0xFF, 0xC5, 0xCA, 0xE9), - Color.FromArgb(0xFF, 0x9F, 0xA8, 0xDA), - Color.FromArgb(0xFF, 0x79, 0x86, 0xCB), - Color.FromArgb(0xFF, 0x5C, 0x6B, 0xC0), - Color.FromArgb(0xFF, 0x3F, 0x51, 0xB5), - Color.FromArgb(0xFF, 0x39, 0x49, 0xAB), - Color.FromArgb(0xFF, 0x30, 0x3F, 0x9F), - Color.FromArgb(0xFF, 0x28, 0x35, 0x93), - Color.FromArgb(0xFF, 0x1A, 0x23, 0x7E), + Color.FromUInt32((uint)MaterialColor.Indigo50), + Color.FromUInt32((uint)MaterialColor.Indigo100), + Color.FromUInt32((uint)MaterialColor.Indigo200), + Color.FromUInt32((uint)MaterialColor.Indigo300), + Color.FromUInt32((uint)MaterialColor.Indigo400), + Color.FromUInt32((uint)MaterialColor.Indigo500), + Color.FromUInt32((uint)MaterialColor.Indigo600), + Color.FromUInt32((uint)MaterialColor.Indigo700), + Color.FromUInt32((uint)MaterialColor.Indigo800), + Color.FromUInt32((uint)MaterialColor.Indigo900), }, // Blue { - Color.FromArgb(0xFF, 0xE3, 0xF2, 0xFD), - Color.FromArgb(0xFF, 0xBB, 0xDE, 0xFB), - Color.FromArgb(0xFF, 0x90, 0xCA, 0xF9), - Color.FromArgb(0xFF, 0x64, 0xB5, 0xF6), - Color.FromArgb(0xFF, 0x42, 0xA5, 0xF5), - Color.FromArgb(0xFF, 0x21, 0x96, 0xF3), - Color.FromArgb(0xFF, 0x1E, 0x88, 0xE5), - Color.FromArgb(0xFF, 0x19, 0x76, 0xD2), - Color.FromArgb(0xFF, 0x15, 0x65, 0xC0), - Color.FromArgb(0xFF, 0x0D, 0x47, 0xA1), + Color.FromUInt32((uint)MaterialColor.Blue50), + Color.FromUInt32((uint)MaterialColor.Blue100), + Color.FromUInt32((uint)MaterialColor.Blue200), + Color.FromUInt32((uint)MaterialColor.Blue300), + Color.FromUInt32((uint)MaterialColor.Blue400), + Color.FromUInt32((uint)MaterialColor.Blue500), + Color.FromUInt32((uint)MaterialColor.Blue600), + Color.FromUInt32((uint)MaterialColor.Blue700), + Color.FromUInt32((uint)MaterialColor.Blue800), + Color.FromUInt32((uint)MaterialColor.Blue900), }, // Light Blue { - Color.FromArgb(0xFF, 0xE1, 0xF5, 0xFE), - Color.FromArgb(0xFF, 0xB3, 0xE5, 0xFC), - Color.FromArgb(0xFF, 0x81, 0xD4, 0xFA), - Color.FromArgb(0xFF, 0x4F, 0xC3, 0xF7), - Color.FromArgb(0xFF, 0x29, 0xB6, 0xF6), - Color.FromArgb(0xFF, 0x03, 0xA9, 0xF4), - Color.FromArgb(0xFF, 0x03, 0x9B, 0xE5), - Color.FromArgb(0xFF, 0x02, 0x88, 0xD1), - Color.FromArgb(0xFF, 0x02, 0x77, 0xBD), - Color.FromArgb(0xFF, 0x01, 0x57, 0x9B), + Color.FromUInt32((uint)MaterialColor.LightBlue50), + Color.FromUInt32((uint)MaterialColor.LightBlue100), + Color.FromUInt32((uint)MaterialColor.LightBlue200), + Color.FromUInt32((uint)MaterialColor.LightBlue300), + Color.FromUInt32((uint)MaterialColor.LightBlue400), + Color.FromUInt32((uint)MaterialColor.LightBlue500), + Color.FromUInt32((uint)MaterialColor.LightBlue600), + Color.FromUInt32((uint)MaterialColor.LightBlue700), + Color.FromUInt32((uint)MaterialColor.LightBlue800), + Color.FromUInt32((uint)MaterialColor.LightBlue900), }, // Cyan { - Color.FromArgb(0xFF, 0xE0, 0xF7, 0xFA), - Color.FromArgb(0xFF, 0xB2, 0xEB, 0xF2), - Color.FromArgb(0xFF, 0x80, 0xDE, 0xEA), - Color.FromArgb(0xFF, 0x4D, 0xD0, 0xE1), - Color.FromArgb(0xFF, 0x26, 0xC6, 0xDA), - Color.FromArgb(0xFF, 0x00, 0xBC, 0xD4), - Color.FromArgb(0xFF, 0x00, 0xAC, 0xC1), - Color.FromArgb(0xFF, 0x00, 0x97, 0xA7), - Color.FromArgb(0xFF, 0x00, 0x83, 0x8F), - Color.FromArgb(0xFF, 0x00, 0x60, 0x64), + Color.FromUInt32((uint)MaterialColor.Cyan50), + Color.FromUInt32((uint)MaterialColor.Cyan100), + Color.FromUInt32((uint)MaterialColor.Cyan200), + Color.FromUInt32((uint)MaterialColor.Cyan300), + Color.FromUInt32((uint)MaterialColor.Cyan400), + Color.FromUInt32((uint)MaterialColor.Cyan500), + Color.FromUInt32((uint)MaterialColor.Cyan600), + Color.FromUInt32((uint)MaterialColor.Cyan700), + Color.FromUInt32((uint)MaterialColor.Cyan800), + Color.FromUInt32((uint)MaterialColor.Cyan900), }, // Teal { - Color.FromArgb(0xFF, 0xE0, 0xF2, 0xF1), - Color.FromArgb(0xFF, 0xB2, 0xDF, 0xDB), - Color.FromArgb(0xFF, 0x80, 0xCB, 0xC4), - Color.FromArgb(0xFF, 0x4D, 0xB6, 0xAC), - Color.FromArgb(0xFF, 0x26, 0xA6, 0x9A), - Color.FromArgb(0xFF, 0x00, 0x96, 0x88), - Color.FromArgb(0xFF, 0x00, 0x89, 0x7B), - Color.FromArgb(0xFF, 0x00, 0x79, 0x6B), - Color.FromArgb(0xFF, 0x00, 0x69, 0x5C), - Color.FromArgb(0xFF, 0x00, 0x4D, 0x40), + Color.FromUInt32((uint)MaterialColor.Teal50), + Color.FromUInt32((uint)MaterialColor.Teal100), + Color.FromUInt32((uint)MaterialColor.Teal200), + Color.FromUInt32((uint)MaterialColor.Teal300), + Color.FromUInt32((uint)MaterialColor.Teal400), + Color.FromUInt32((uint)MaterialColor.Teal500), + Color.FromUInt32((uint)MaterialColor.Teal600), + Color.FromUInt32((uint)MaterialColor.Teal700), + Color.FromUInt32((uint)MaterialColor.Teal800), + Color.FromUInt32((uint)MaterialColor.Teal900), }, // Green { - Color.FromArgb(0xFF, 0xE8, 0xF5, 0xE9), - Color.FromArgb(0xFF, 0xC8, 0xE6, 0xC9), - Color.FromArgb(0xFF, 0xA5, 0xD6, 0xA7), - Color.FromArgb(0xFF, 0x81, 0xC7, 0x84), - Color.FromArgb(0xFF, 0x66, 0xBB, 0x6A), - Color.FromArgb(0xFF, 0x4C, 0xAF, 0x50), - Color.FromArgb(0xFF, 0x43, 0xA0, 0x47), - Color.FromArgb(0xFF, 0x38, 0x8E, 0x3C), - Color.FromArgb(0xFF, 0x2E, 0x7D, 0x32), - Color.FromArgb(0xFF, 0x1B, 0x5E, 0x20), + Color.FromUInt32((uint)MaterialColor.Green50), + Color.FromUInt32((uint)MaterialColor.Green100), + Color.FromUInt32((uint)MaterialColor.Green200), + Color.FromUInt32((uint)MaterialColor.Green300), + Color.FromUInt32((uint)MaterialColor.Green400), + Color.FromUInt32((uint)MaterialColor.Green500), + Color.FromUInt32((uint)MaterialColor.Green600), + Color.FromUInt32((uint)MaterialColor.Green700), + Color.FromUInt32((uint)MaterialColor.Green800), + Color.FromUInt32((uint)MaterialColor.Green900), }, // Light Green { - Color.FromArgb(0xFF, 0xF1, 0xF8, 0xE9), - Color.FromArgb(0xFF, 0xDC, 0xED, 0xC8), - Color.FromArgb(0xFF, 0xC5, 0xE1, 0xA5), - Color.FromArgb(0xFF, 0xAE, 0xD5, 0x81), - Color.FromArgb(0xFF, 0x9C, 0xCC, 0x65), - Color.FromArgb(0xFF, 0x8B, 0xC3, 0x4A), - Color.FromArgb(0xFF, 0x7C, 0xB3, 0x42), - Color.FromArgb(0xFF, 0x68, 0x9F, 0x38), - Color.FromArgb(0xFF, 0x55, 0x8B, 0x2F), - Color.FromArgb(0xFF, 0x33, 0x69, 0x1E), + Color.FromUInt32((uint)MaterialColor.LightGreen50), + Color.FromUInt32((uint)MaterialColor.LightGreen100), + Color.FromUInt32((uint)MaterialColor.LightGreen200), + Color.FromUInt32((uint)MaterialColor.LightGreen300), + Color.FromUInt32((uint)MaterialColor.LightGreen400), + Color.FromUInt32((uint)MaterialColor.LightGreen500), + Color.FromUInt32((uint)MaterialColor.LightGreen600), + Color.FromUInt32((uint)MaterialColor.LightGreen700), + Color.FromUInt32((uint)MaterialColor.LightGreen800), + Color.FromUInt32((uint)MaterialColor.LightGreen900), }, // Lime { - Color.FromArgb(0xFF, 0xF9, 0xFB, 0xE7), - Color.FromArgb(0xFF, 0xF0, 0xF4, 0xC3), - Color.FromArgb(0xFF, 0xE6, 0xEE, 0x9C), - Color.FromArgb(0xFF, 0xDC, 0xE7, 0x75), - Color.FromArgb(0xFF, 0xD4, 0xE1, 0x57), - Color.FromArgb(0xFF, 0xCD, 0xDC, 0x39), - Color.FromArgb(0xFF, 0xC0, 0xCA, 0x33), - Color.FromArgb(0xFF, 0xAF, 0xB4, 0x2B), - Color.FromArgb(0xFF, 0x9E, 0x9D, 0x24), - Color.FromArgb(0xFF, 0x82, 0x77, 0x17), + Color.FromUInt32((uint)MaterialColor.Lime50), + Color.FromUInt32((uint)MaterialColor.Lime100), + Color.FromUInt32((uint)MaterialColor.Lime200), + Color.FromUInt32((uint)MaterialColor.Lime300), + Color.FromUInt32((uint)MaterialColor.Lime400), + Color.FromUInt32((uint)MaterialColor.Lime500), + Color.FromUInt32((uint)MaterialColor.Lime600), + Color.FromUInt32((uint)MaterialColor.Lime700), + Color.FromUInt32((uint)MaterialColor.Lime800), + Color.FromUInt32((uint)MaterialColor.Lime900), }, // Yellow { - Color.FromArgb(0xFF, 0xFF, 0xFD, 0xE7), - Color.FromArgb(0xFF, 0xFF, 0xF9, 0xC4), - Color.FromArgb(0xFF, 0xFF, 0xF5, 0x9D), - Color.FromArgb(0xFF, 0xFF, 0xF1, 0x76), - Color.FromArgb(0xFF, 0xFF, 0xEE, 0x58), - Color.FromArgb(0xFF, 0xFF, 0xEB, 0x3B), - Color.FromArgb(0xFF, 0xFD, 0xD8, 0x35), - Color.FromArgb(0xFF, 0xFB, 0xC0, 0x2D), - Color.FromArgb(0xFF, 0xF9, 0xA8, 0x25), - Color.FromArgb(0xFF, 0xF5, 0x7F, 0x17), + Color.FromUInt32((uint)MaterialColor.Yellow50), + Color.FromUInt32((uint)MaterialColor.Yellow100), + Color.FromUInt32((uint)MaterialColor.Yellow200), + Color.FromUInt32((uint)MaterialColor.Yellow300), + Color.FromUInt32((uint)MaterialColor.Yellow400), + Color.FromUInt32((uint)MaterialColor.Yellow500), + Color.FromUInt32((uint)MaterialColor.Yellow600), + Color.FromUInt32((uint)MaterialColor.Yellow700), + Color.FromUInt32((uint)MaterialColor.Yellow800), + Color.FromUInt32((uint)MaterialColor.Yellow900), }, // Amber { - Color.FromArgb(0xFF, 0xFF, 0xF8, 0xE1), - Color.FromArgb(0xFF, 0xFF, 0xEC, 0xB3), - Color.FromArgb(0xFF, 0xFF, 0xE0, 0x82), - Color.FromArgb(0xFF, 0xFF, 0xD5, 0x4F), - Color.FromArgb(0xFF, 0xFF, 0xCA, 0x28), - Color.FromArgb(0xFF, 0xFF, 0xC1, 0x07), - Color.FromArgb(0xFF, 0xFF, 0xB3, 0x00), - Color.FromArgb(0xFF, 0xFF, 0xA0, 0x00), - Color.FromArgb(0xFF, 0xFF, 0x8F, 0x00), - Color.FromArgb(0xFF, 0xFF, 0x6F, 0x00), + Color.FromUInt32((uint)MaterialColor.Amber50), + Color.FromUInt32((uint)MaterialColor.Amber100), + Color.FromUInt32((uint)MaterialColor.Amber200), + Color.FromUInt32((uint)MaterialColor.Amber300), + Color.FromUInt32((uint)MaterialColor.Amber400), + Color.FromUInt32((uint)MaterialColor.Amber500), + Color.FromUInt32((uint)MaterialColor.Amber600), + Color.FromUInt32((uint)MaterialColor.Amber700), + Color.FromUInt32((uint)MaterialColor.Amber800), + Color.FromUInt32((uint)MaterialColor.Amber900), }, // Orange { - Color.FromArgb(0xFF, 0xFF, 0xF3, 0xE0), - Color.FromArgb(0xFF, 0xFF, 0xE0, 0xB2), - Color.FromArgb(0xFF, 0xFF, 0xCC, 0x80), - Color.FromArgb(0xFF, 0xFF, 0xB7, 0x4D), - Color.FromArgb(0xFF, 0xFF, 0xA7, 0x26), - Color.FromArgb(0xFF, 0xFF, 0x98, 0x00), - Color.FromArgb(0xFF, 0xFB, 0x8C, 0x00), - Color.FromArgb(0xFF, 0xF5, 0x7C, 0x00), - Color.FromArgb(0xFF, 0xEF, 0x6C, 0x00), - Color.FromArgb(0xFF, 0xE6, 0x51, 0x00), + Color.FromUInt32((uint)MaterialColor.Orange50), + Color.FromUInt32((uint)MaterialColor.Orange100), + Color.FromUInt32((uint)MaterialColor.Orange200), + Color.FromUInt32((uint)MaterialColor.Orange300), + Color.FromUInt32((uint)MaterialColor.Orange400), + Color.FromUInt32((uint)MaterialColor.Orange500), + Color.FromUInt32((uint)MaterialColor.Orange600), + Color.FromUInt32((uint)MaterialColor.Orange700), + Color.FromUInt32((uint)MaterialColor.Orange800), + Color.FromUInt32((uint)MaterialColor.Orange900), }, // Deep Orange { - Color.FromArgb(0xFF, 0xFB, 0xE9, 0xE7), - Color.FromArgb(0xFF, 0xFF, 0xCC, 0xBC), - Color.FromArgb(0xFF, 0xFF, 0xAB, 0x91), - Color.FromArgb(0xFF, 0xFF, 0x8A, 0x65), - Color.FromArgb(0xFF, 0xFF, 0x70, 0x43), - Color.FromArgb(0xFF, 0xFF, 0x57, 0x22), - Color.FromArgb(0xFF, 0xF4, 0x51, 0x1E), - Color.FromArgb(0xFF, 0xE6, 0x4A, 0x19), - Color.FromArgb(0xFF, 0xD8, 0x43, 0x15), - Color.FromArgb(0xFF, 0xBF, 0x36, 0x0C), + Color.FromUInt32((uint)MaterialColor.DeepOrange50), + Color.FromUInt32((uint)MaterialColor.DeepOrange100), + Color.FromUInt32((uint)MaterialColor.DeepOrange200), + Color.FromUInt32((uint)MaterialColor.DeepOrange300), + Color.FromUInt32((uint)MaterialColor.DeepOrange400), + Color.FromUInt32((uint)MaterialColor.DeepOrange500), + Color.FromUInt32((uint)MaterialColor.DeepOrange600), + Color.FromUInt32((uint)MaterialColor.DeepOrange700), + Color.FromUInt32((uint)MaterialColor.DeepOrange800), + Color.FromUInt32((uint)MaterialColor.DeepOrange900), }, // Brown { - Color.FromArgb(0xFF, 0xEF, 0xEB, 0xE9), - Color.FromArgb(0xFF, 0xD7, 0xCC, 0xC8), - Color.FromArgb(0xFF, 0xBC, 0xAA, 0xA4), - Color.FromArgb(0xFF, 0xA1, 0x88, 0x7F), - Color.FromArgb(0xFF, 0x8D, 0x6E, 0x63), - Color.FromArgb(0xFF, 0x79, 0x55, 0x48), - Color.FromArgb(0xFF, 0x6D, 0x4C, 0x41), - Color.FromArgb(0xFF, 0x5D, 0x40, 0x37), - Color.FromArgb(0xFF, 0x4E, 0x34, 0x2E), - Color.FromArgb(0xFF, 0x3E, 0x27, 0x23), + Color.FromUInt32((uint)MaterialColor.Brown50), + Color.FromUInt32((uint)MaterialColor.Brown100), + Color.FromUInt32((uint)MaterialColor.Brown200), + Color.FromUInt32((uint)MaterialColor.Brown300), + Color.FromUInt32((uint)MaterialColor.Brown400), + Color.FromUInt32((uint)MaterialColor.Brown500), + Color.FromUInt32((uint)MaterialColor.Brown600), + Color.FromUInt32((uint)MaterialColor.Brown700), + Color.FromUInt32((uint)MaterialColor.Brown800), + Color.FromUInt32((uint)MaterialColor.Brown900), }, // Gray { - Color.FromArgb(0xFF, 0xFA, 0xFA, 0xFA), - Color.FromArgb(0xFF, 0xF5, 0xF5, 0xF5), - Color.FromArgb(0xFF, 0xEE, 0xEE, 0xEE), - Color.FromArgb(0xFF, 0xE0, 0xE0, 0xE0), - Color.FromArgb(0xFF, 0xBD, 0xBD, 0xBD), - Color.FromArgb(0xFF, 0x9E, 0x9E, 0x9E), - Color.FromArgb(0xFF, 0x75, 0x75, 0x75), - Color.FromArgb(0xFF, 0x61, 0x61, 0x61), - Color.FromArgb(0xFF, 0x42, 0x42, 0x42), - Color.FromArgb(0xFF, 0x21, 0x21, 0x21), + Color.FromUInt32((uint)MaterialColor.Gray50), + Color.FromUInt32((uint)MaterialColor.Gray100), + Color.FromUInt32((uint)MaterialColor.Gray200), + Color.FromUInt32((uint)MaterialColor.Gray300), + Color.FromUInt32((uint)MaterialColor.Gray400), + Color.FromUInt32((uint)MaterialColor.Gray500), + Color.FromUInt32((uint)MaterialColor.Gray600), + Color.FromUInt32((uint)MaterialColor.Gray700), + Color.FromUInt32((uint)MaterialColor.Gray800), + Color.FromUInt32((uint)MaterialColor.Gray900), }, // Blue Gray { - Color.FromArgb(0xFF, 0xEC, 0xEF, 0xF1), - Color.FromArgb(0xFF, 0xCF, 0xD8, 0xDC), - Color.FromArgb(0xFF, 0xB0, 0xBE, 0xC5), - Color.FromArgb(0xFF, 0x90, 0xA4, 0xAE), - Color.FromArgb(0xFF, 0x78, 0x90, 0x9C), - Color.FromArgb(0xFF, 0x60, 0x7D, 0x8B), - Color.FromArgb(0xFF, 0x54, 0x6E, 0x7A), - Color.FromArgb(0xFF, 0x45, 0x5A, 0x64), - Color.FromArgb(0xFF, 0x37, 0x47, 0x4F), - Color.FromArgb(0xFF, 0x26, 0x32, 0x38), + Color.FromUInt32((uint)MaterialColor.BlueGray50), + Color.FromUInt32((uint)MaterialColor.BlueGray100), + Color.FromUInt32((uint)MaterialColor.BlueGray200), + Color.FromUInt32((uint)MaterialColor.BlueGray300), + Color.FromUInt32((uint)MaterialColor.BlueGray400), + Color.FromUInt32((uint)MaterialColor.BlueGray500), + Color.FromUInt32((uint)MaterialColor.BlueGray600), + Color.FromUInt32((uint)MaterialColor.BlueGray700), + Color.FromUInt32((uint)MaterialColor.BlueGray800), + Color.FromUInt32((uint)MaterialColor.BlueGray900), }, }; - - _colorChartColorCount = _colorChart.GetLength(0); - _colorChartShadeCount = _colorChart.GetLength(1); } return; @@ -318,29 +552,13 @@ namespace Avalonia.Controls /// public int ColorCount { - get - { - if (_colorChart == null) - { - InitColorChart(); - } - - return _colorChartColorCount; - } + get => 19; } /// public int ShadeCount { - get - { - if (_colorChart == null) - { - InitColorChart(); - } - - return _colorChartShadeCount; - } + get => 10; } /// @@ -352,8 +570,8 @@ namespace Avalonia.Controls } return _colorChart![ - MathUtilities.Clamp(colorIndex, 0, _colorChartColorCount - 1), - MathUtilities.Clamp(shadeIndex, 0, _colorChartShadeCount - 1)]; + MathUtilities.Clamp(colorIndex, 0, ColorCount - 1), + MathUtilities.Clamp(shadeIndex, 0, ShadeCount - 1)]; } } } diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs new file mode 100644 index 0000000000..01d44aa65d --- /dev/null +++ b/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 +{ + /// + /// Implements half of the for improved usability. + /// + /// + public class MaterialHalfColorPalette : IColorPalette + { + protected static Color[,]? _colorChart = null; + protected static object _colorChartMutex = new object(); + + /// + /// Initializes all color chart colors. + /// + 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; + } + + /// + public int ColorCount + { + get => 10; + } + + /// + public int ShadeCount + { + get => 5; + } + + /// + 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)]; + } + } +} diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/SixteenColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/SixteenColorPalette.cs index f3abfdfd7f..e8e82eac91 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/SixteenColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/SixteenColorPalette.cs @@ -50,107 +50,6 @@ namespace Avalonia.Controls } }; - /// - /// Gets the index of the default shade of colors in this palette. - /// - public const int DefaultShadeIndex = 0; - - /// - /// The index in the color palette of the 'White' color. - /// This index can correspond to multiple color shades. - /// - public const int WhiteIndex = 0; - - /// - /// The index in the color palette of the 'Silver' color. - /// This index can correspond to multiple color shades. - /// - public const int SilverIndex = 1; - - /// - /// The index in the color palette of the 'Gray' color. - /// This index can correspond to multiple color shades. - /// - public const int GrayIndex = 2; - - /// - /// The index in the color palette of the 'Black' color. - /// This index can correspond to multiple color shades. - /// - public const int BlackIndex = 3; - - /// - /// The index in the color palette of the 'Red' color. - /// This index can correspond to multiple color shades. - /// - public const int RedIndex = 4; - - /// - /// The index in the color palette of the 'Maroon' color. - /// This index can correspond to multiple color shades. - /// - public const int MaroonIndex = 5; - - /// - /// The index in the color palette of the 'Yellow' color. - /// This index can correspond to multiple color shades. - /// - public const int YellowIndex = 6; - - /// - /// The index in the color palette of the 'Olive' color. - /// This index can correspond to multiple color shades. - /// - public const int OliveIndex = 7; - - /// - /// The index in the color palette of the 'Lime' color. - /// This index can correspond to multiple color shades. - /// - public const int LimeIndex = 8; - - /// - /// The index in the color palette of the 'Green' color. - /// This index can correspond to multiple color shades. - /// - public const int GreenIndex = 9; - - /// - /// The index in the color palette of the 'Aqua' color. - /// This index can correspond to multiple color shades. - /// - public const int AquaIndex = 10; - - /// - /// The index in the color palette of the 'Teal' color. - /// This index can correspond to multiple color shades. - /// - public const int TealIndex = 11; - - /// - /// The index in the color palette of the 'Blue' color. - /// This index can correspond to multiple color shades. - /// - public const int BlueIndex = 12; - - /// - /// The index in the color palette of the 'Navy' color. - /// This index can correspond to multiple color shades. - /// - public const int NavyIndex = 13; - - /// - /// The index in the color palette of the 'Fuchsia' color. - /// This index can correspond to multiple color shades. - /// - public const int FuchsiaIndex = 14; - - /// - /// The index in the color palette of the 'Purple' color. - /// This index can correspond to multiple color shades. - /// - public const int PurpleIndex = 15; - /// public int ColorCount { @@ -163,134 +62,6 @@ namespace Avalonia.Controls get => colorChart.GetLength(1); } - /// - /// Gets the palette defined color that has an ARGB value of #FFFFFFFF. - /// - public static Color White - { - get => colorChart[WhiteIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FFC0C0C0. - /// - public static Color Silver - { - get => colorChart[SilverIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF808080. - /// - public static Color Gray - { - get => colorChart[GrayIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF000000. - /// - public static Color Black - { - get => colorChart[BlackIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FFFF0000. - /// - public static Color Red - { - get => colorChart[RedIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF800000. - /// - public static Color Maroon - { - get => colorChart[MaroonIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FFFFFF00. - /// - public static Color Yellow - { - get => colorChart[YellowIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF808000. - /// - public static Color Olive - { - get => colorChart[OliveIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF00FF00. - /// - public static Color Lime - { - get => colorChart[LimeIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF008000. - /// - public static Color Green - { - get => colorChart[GreenIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF00FFFF. - /// - public static Color Aqua - { - get => colorChart[AquaIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF008080. - /// - public static Color Teal - { - get => colorChart[TealIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF0000FF. - /// - public static Color Blue - { - get => colorChart[BlueIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF000080. - /// - public static Color Navy - { - get => colorChart[NavyIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FFFF00FF. - /// - public static Color Fuchsia - { - get => colorChart[FuchsiaIndex, DefaultShadeIndex]; - } - - /// - /// Gets the palette defined color that has an ARGB value of #FF800080. - /// - public static Color Purple - { - get => colorChart[PurpleIndex, DefaultShadeIndex]; - } - /// public Color GetColor(int colorIndex, int shadeIndex) { diff --git a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs index 39369bcbdb..29f9f3c571 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Controls +using Avalonia.Controls.Primitives; + +namespace Avalonia.Controls { /// /// Presents a color for user editing using a spectrum, palette and component sliders within a drop down. @@ -11,8 +13,33 @@ /// public ColorPicker() : base() { - // Completely ignore property changes here - // The ColorView in the control template is responsible to manage this + } + + /// + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + // Until this point the ColorPicker itself is responsible to process property updates. + // This, for example, syncs Color with HsvColor and updates primitive controls. + // + // However, when the template is created, hand-off this change processing to the + // ColorView within the control template itself. Remember ColorPicker derives from + // ColorView so we don't want two instances of the same logic fighting each other. + // It is best to hand-off to the ColorView in the control template because that is the + // primary point of user-interaction for the overall control. It also simplifies binding. + // + // Keep in mind this hand-off is not possible until the template controls are created + // which is done after the ColorPicker is instantiated. The ColorPicker must still + // process updates before the template is applied to ensure all property changes in + // XAML or object initializers are handled correctly. Otherwise, there can be bugs + // such as setting the Color property doesn't work because the HsvColor is never updated + // and then the Color value is lost once the template loads (and the template ColorView + // takes over). + // + // In order to complete this hand-off, completely ignore property changes here in the + // ColorPicker. This means the ColorView in the control template is now responsible to + // process property changes and handle primary calculations. base.ignorePropertyChanged = true; } } diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index b662d20223..ec08e96d87 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -2,6 +2,7 @@ using Avalonia.Controls.Metadata; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Media.Imaging; using Avalonia.Utilities; namespace Avalonia.Controls.Primitives @@ -31,6 +32,8 @@ namespace Avalonia.Controls.Primitives protected bool ignorePropertyChanged = false; + private WriteableBitmap? _backgroundBitmap; + /// /// Initializes a new instance of the class. /// @@ -38,6 +41,18 @@ namespace Avalonia.Controls.Primitives { } + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + } + + /// + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + } + /// /// Updates the visual state of the control by applying latest PseudoClasses. /// @@ -98,7 +113,7 @@ namespace Avalonia.Controls.Primitives if (pixelWidth != 0 && pixelHeight != 0) { - var bitmap = await ColorPickerHelpers.CreateComponentBitmapAsync( + ArrayList bgraPixelData = await ColorPickerHelpers.CreateComponentBitmapAsync( pixelWidth, pixelHeight, Orientation, @@ -108,9 +123,27 @@ namespace Avalonia.Controls.Primitives IsAlphaMaxForced, IsSaturationValueMaxForced); - if (bitmap != null) + if (bgraPixelData != null) { - Background = new ImageBrush(ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight)); + if (_backgroundBitmap != null) + { + // TODO: CURRENTLY DISABLED DUE TO INTERMITTENT CRASHES IN SKIA/RENDERER + // + // Re-use the existing WriteableBitmap + // This assumes the height, width and byte counts are the same and must be set to null + // elsewhere if that assumption is ever not true. + // ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData); + + // TODO: ALSO DISABLED DISPOSE DUE TO INTERMITTENT CRASHES + //_backgroundBitmap?.Dispose(); + _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight); + } + else + { + _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight); + } + + Background = new ImageBrush(_backgroundBitmap); } } } @@ -350,11 +383,11 @@ namespace Avalonia.Controls.Primitives return; } - // Always keep the two color properties in sync if (change.Property == ColorProperty) { ignorePropertyChanged = true; + // Always keep the two color properties in sync HsvColor = Color.ToHsv(); SetColorToSliderValues(); @@ -367,7 +400,10 @@ namespace Avalonia.Controls.Primitives ignorePropertyChanged = false; } - else if (change.Property == ColorModelProperty) + else if (change.Property == ColorComponentProperty || + change.Property == ColorModelProperty || + change.Property == IsAlphaMaxForcedProperty || + change.Property == IsSaturationValueMaxForcedProperty) { ignorePropertyChanged = true; @@ -381,6 +417,7 @@ namespace Avalonia.Controls.Primitives { ignorePropertyChanged = true; + // Always keep the two color properties in sync Color = HsvColor.ToRgb(); SetColorToSliderValues(); @@ -399,7 +436,13 @@ namespace Avalonia.Controls.Primitives } else if (change.Property == BoundsProperty) { + // If the control's overall dimensions have changed the background bitmap size also needs to change. + // This means the existing bitmap must be released to be recreated correctly in UpdateBackground(). + _backgroundBitmap?.Dispose(); + _backgroundBitmap = null; + UpdateBackground(); + UpdatePseudoClasses(); } else if (change.Property == ValueProperty || change.Property == MinimumProperty || diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs index 00d84f5dd3..39b7b7f660 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs @@ -93,6 +93,14 @@ namespace Avalonia.Controls.Primitives nameof(Shape), ColorSpectrumShape.Box); + /// + /// Defines the property. + /// + public static readonly StyledProperty ThirdComponentProperty = + AvaloniaProperty.Register( + nameof(ThirdComponent), + ColorComponent.Component3); // Value + /// /// Gets or sets the currently selected color in the RGB color model. /// @@ -218,5 +226,21 @@ namespace Avalonia.Controls.Primitives get => GetValue(ShapeProperty); set => SetValue(ShapeProperty, value); } + + /// + /// Gets the third HSV color component that is NOT displayed by the spectrum. + /// This is automatically calculated from the property. + /// + /// + /// This property should be used for any external color slider that represents the + /// third component of the color. Note that this property uses the generic + /// type instead of the more accurate + /// to allow direct usage by the generalized color sliders. + /// + public ColorComponent ThirdComponent + { + get => GetValue(ThirdComponentProperty); + private set => SetValue(ThirdComponentProperty, value); + } } } diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index bd44161a42..f0ed89fb3a 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -73,6 +73,8 @@ namespace Avalonia.Controls.Primitives private WriteableBitmap? _saturationMaximumBitmap; private WriteableBitmap? _valueBitmap; + private WriteableBitmap? _minBitmap; + private WriteableBitmap? _maxBitmap; // Fields used by UpdateEllipse() to ensure that it's using the data // associated with the last call to CreateBitmapsAndColorMap(), @@ -95,7 +97,7 @@ namespace Avalonia.Controls.Primitives /// /// Initializes a new instance of the class. /// - public ColorSpectrum() + public ColorSpectrum() : base() { _shapeFromLastBitmapCreation = Shape; _componentsFromLastBitmapCreation = Components; @@ -171,6 +173,18 @@ namespace Avalonia.Controls.Primitives { base.OnAttachedToVisualTree(e); + // If the color was updated while this ColorSpectrum was not part of the visual tree, + // the selection ellipse may be in an incorrect position. This is because the spectrum + // renders based on layout scaling to avoid color banding; however, layout scale is only + // available when the control is attached to the visual tree. The ColorSpectrum's color + // may be updated from code-behind or from binding with another control when it's not + // part of the visual tree. + // + // See discussion: https://github.com/AvaloniaUI/Avalonia/discussions/9077 + // + // To work-around this issue the selection ellipse is refreshed here. + UpdateEllipse(); + // OnAttachedToVisualTree is called after OnApplyTemplate so events cannot be connected here } @@ -489,6 +503,23 @@ namespace Avalonia.Controls.Primitives } else if (change.Property == ComponentsProperty) { + // Calculate and update the ThirdComponent value + switch (Components) + { + case ColorSpectrumComponents.HueSaturation: + case ColorSpectrumComponents.SaturationHue: + ThirdComponent = (ColorComponent)HsvComponent.Value; + break; + case ColorSpectrumComponents.HueValue: + case ColorSpectrumComponents.ValueHue: + ThirdComponent = (ColorComponent)HsvComponent.Saturation; + break; + case ColorSpectrumComponents.SaturationValue: + case ColorSpectrumComponents.ValueSaturation: + ThirdComponent = (ColorComponent)HsvComponent.Hue; + break; + } + CreateBitmapsAndColorMap(); } @@ -588,6 +619,10 @@ namespace Avalonia.Controls.Primitives RaiseColorChanged(); } + /// + /// Updates the selected and based on a point within the color spectrum. + /// + /// The point on the spectrum representing the color. private void UpdateColorFromPoint(PointerPoint point) { // If we haven't initialized our HSV value array yet, then we should just ignore any user input - @@ -664,6 +699,9 @@ namespace Avalonia.Controls.Primitives UpdateColor(hsvAtPoint); } + /// + /// Updates the position of the selection ellipse on the spectrum which indicates the selected color. + /// private void UpdateEllipse() { if (_selectionEllipsePanel == null) @@ -832,6 +870,8 @@ namespace Avalonia.Controls.Primitives } // Remember the bitmap size follows physical device pixels + // Warning: LayoutHelper.GetLayoutScale() doesn't work unless the control is visible + // This will not be true in all cases if the color is updated from another control or code-behind var scale = LayoutHelper.GetLayoutScale(this); Canvas.SetLeft(_selectionEllipsePanel, (xPosition / scale) - (_selectionEllipsePanel.Width / 2)); Canvas.SetTop(_selectionEllipsePanel, (yPosition / scale) - (_selectionEllipsePanel.Height / 2)); @@ -973,13 +1013,13 @@ namespace Avalonia.Controls.Primitives // The middle 4 are only needed and used in the case of hue as the third dimension. // Saturation and luminosity need only a min and max. - List bgraMinPixelData = new List(); - List bgraMiddle1PixelData = new List(); - List bgraMiddle2PixelData = new List(); - List bgraMiddle3PixelData = new List(); - List bgraMiddle4PixelData = new List(); - List bgraMaxPixelData = new List(); - List newHsvValues = new List(); + ArrayList bgraMinPixelData; + ArrayList bgraMiddle1PixelData; + ArrayList bgraMiddle2PixelData; + ArrayList bgraMiddle3PixelData; + ArrayList bgraMiddle4PixelData; + ArrayList bgraMaxPixelData; + List newHsvValues; // In Avalonia, Bounds returns the actual device-independent pixel size of a control. // However, this is not necessarily the size of the control rendered on a display. @@ -990,20 +1030,27 @@ namespace Avalonia.Controls.Primitives int pixelDimension = (int)Math.Round(minDimension * scale); var pixelCount = pixelDimension * pixelDimension; var pixelDataSize = pixelCount * 4; - bgraMinPixelData.Capacity = pixelDataSize; + + bgraMinPixelData = new ArrayList(pixelDataSize); + bgraMaxPixelData = new ArrayList(pixelDataSize); + newHsvValues = new List(pixelCount); // We'll only save pixel data for the middle bitmaps if our third dimension is hue. if (components == ColorSpectrumComponents.ValueSaturation || components == ColorSpectrumComponents.SaturationValue) { - bgraMiddle1PixelData.Capacity = pixelDataSize; - bgraMiddle2PixelData.Capacity = pixelDataSize; - bgraMiddle3PixelData.Capacity = pixelDataSize; - bgraMiddle4PixelData.Capacity = pixelDataSize; + bgraMiddle1PixelData = new ArrayList(pixelDataSize); + bgraMiddle2PixelData = new ArrayList(pixelDataSize); + bgraMiddle3PixelData = new ArrayList(pixelDataSize); + bgraMiddle4PixelData = new ArrayList(pixelDataSize); + } + else + { + bgraMiddle1PixelData = new ArrayList(0); + bgraMiddle2PixelData = new ArrayList(0); + bgraMiddle3PixelData = new ArrayList(0); + bgraMiddle4PixelData = new ArrayList(0); } - - bgraMaxPixelData.Capacity = pixelDataSize; - newHsvValues.Capacity = pixelCount; await Task.Run(() => { @@ -1056,28 +1103,28 @@ namespace Avalonia.Controls.Primitives ColorSpectrumComponents components2 = Components; - WriteableBitmap minBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMinPixelData, pixelWidth, pixelHeight); - WriteableBitmap maxBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMaxPixelData, pixelWidth, pixelHeight); + _minBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMinPixelData, pixelWidth, pixelHeight); + _maxBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMaxPixelData, pixelWidth, pixelHeight); switch (components2) { case ColorSpectrumComponents.HueValue: case ColorSpectrumComponents.ValueHue: - _saturationMinimumBitmap = minBitmap; - _saturationMaximumBitmap = maxBitmap; + _saturationMinimumBitmap = _minBitmap; + _saturationMaximumBitmap = _maxBitmap; break; case ColorSpectrumComponents.HueSaturation: case ColorSpectrumComponents.SaturationHue: - _valueBitmap = maxBitmap; + _valueBitmap = _maxBitmap; break; case ColorSpectrumComponents.ValueSaturation: case ColorSpectrumComponents.SaturationValue: - _hueRedBitmap = minBitmap; + _hueRedBitmap = _minBitmap; _hueYellowBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle1PixelData, pixelWidth, pixelHeight); _hueGreenBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle2PixelData, pixelWidth, pixelHeight); _hueCyanBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle3PixelData, pixelWidth, pixelHeight); _hueBlueBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle4PixelData, pixelWidth, pixelHeight); - _huePurpleBitmap = maxBitmap; + _huePurpleBitmap = _maxBitmap; break; } @@ -1111,12 +1158,12 @@ namespace Avalonia.Controls.Primitives double maxSaturation, double minValue, double maxValue, - List bgraMinPixelData, - List bgraMiddle1PixelData, - List bgraMiddle2PixelData, - List bgraMiddle3PixelData, - List bgraMiddle4PixelData, - List bgraMaxPixelData, + ArrayList bgraMinPixelData, + ArrayList bgraMiddle1PixelData, + ArrayList bgraMiddle2PixelData, + ArrayList bgraMiddle3PixelData, + ArrayList bgraMiddle4PixelData, + ArrayList bgraMaxPixelData, List newHsvValues) { double hMin = minHue; @@ -1271,12 +1318,12 @@ namespace Avalonia.Controls.Primitives double maxSaturation, double minValue, double maxValue, - List bgraMinPixelData, - List bgraMiddle1PixelData, - List bgraMiddle2PixelData, - List bgraMiddle3PixelData, - List bgraMiddle4PixelData, - List bgraMaxPixelData, + ArrayList bgraMinPixelData, + ArrayList bgraMiddle1PixelData, + ArrayList bgraMiddle2PixelData, + ArrayList bgraMiddle3PixelData, + ArrayList bgraMiddle4PixelData, + ArrayList bgraMaxPixelData, List newHsvValues) { double hMin = minHue; diff --git a/src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs b/src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs deleted file mode 100644 index 11e33c74f0..0000000000 --- a/src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Globalization; -using Avalonia.Data.Converters; - -namespace Avalonia.Controls.Primitives.Converters -{ - /// - /// Gets the third corresponding with a given - /// that represents the other two components. - /// - /// - /// This is a highly-specialized converter for the color picker. - /// - public class ThirdComponentConverter : IValueConverter - { - /// - public object? Convert( - object? value, - Type targetType, - object? parameter, - CultureInfo culture) - { - if (value is ColorSpectrumComponents components) - { - // Note: Alpha is not relevant here - switch (components) - { - case ColorSpectrumComponents.HueSaturation: - case ColorSpectrumComponents.SaturationHue: - return (ColorComponent)HsvComponent.Value; - case ColorSpectrumComponents.HueValue: - case ColorSpectrumComponents.ValueHue: - return (ColorComponent)HsvComponent.Saturation; - case ColorSpectrumComponents.SaturationValue: - case ColorSpectrumComponents.ValueSaturation: - return (ColorComponent)HsvComponent.Hue; - } - } - - return AvaloniaProperty.UnsetValue; - } - - /// - public object? ConvertBack( - object? value, - Type targetType, - object? parameter, - CultureInfo culture) - { - return AvaloniaProperty.UnsetValue; - } - } -} diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs new file mode 100644 index 0000000000..0b4c2f8579 --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs @@ -0,0 +1,71 @@ +namespace Avalonia.Controls.Primitives +{ + /// + /// A thin wrapper over an that allows some additional list-like functionality. + /// + /// + /// This is only for internal ColorPicker-related functionality and should not be used elsewhere. + /// It is added for performance to enjoy the simplicity of the IList.Add() method without requiring + /// an additional copy to turn a list into an array for bitmaps. + /// + /// The type of items in the array. + internal class ArrayList + { + private int _nextIndex = 0; + + /// + /// Initializes a new instance of the class. + /// + public ArrayList(int capacity) + { + Capacity = capacity; + Array = new T[capacity]; + } + + /// + /// Provides access to the underlying array by index. + /// This exists for simplification and the property + /// may also be used. + /// + /// The index of the item to get or set. + /// The item at the given index. + public T this[int i] + { + get => Array[i]; + set => Array[i] = value; + } + + /// + /// Gets the underlying array. + /// + public T[] Array { get; private set; } + + /// + /// Gets the fixed capacity/size of the array. + /// This must be set during construction. + /// + public int Capacity { get; private set; } + + /// + /// Adds the given item to the array at the next available index. + /// WARNING: This must be used carefully and only once, in sequence. + /// + /// The item to add. + public void Add(T item) + { + if (_nextIndex >= 0 && + _nextIndex < Capacity) + { + Array[_nextIndex] = item; + _nextIndex++; + } + else + { + // If necessary an exception could be thrown here + // throw new IndexOutOfRangeException(); + } + + return; + } + } +} diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index 381bc42aaa..819d745772 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -37,7 +37,7 @@ namespace Avalonia.Controls.Primitives /// during calculation with the HSVA color model. /// This will ensure colors are always discernible regardless of saturation/value. /// A new bitmap representing a gradient of color component values. - public static async Task CreateComponentBitmapAsync( + public static async Task> CreateComponentBitmapAsync( int width, int height, Orientation orientation, @@ -49,14 +49,14 @@ namespace Avalonia.Controls.Primitives { if (width == 0 || height == 0) { - return Array.Empty(); + return new ArrayList(0); } - var bitmap = await Task.Run(() => + var bitmap = await Task.Run>(() => { int pixelDataIndex = 0; double componentStep; - byte[] bgraPixelData; + ArrayList bgraPixelData; Color baseRgbColor = Colors.White; Color rgbColor; int bgraPixelDataHeight; @@ -64,7 +64,7 @@ namespace Avalonia.Controls.Primitives // Allocate the buffer // BGRA formatted color components 1 byte each (4 bytes in a pixel) - bgraPixelData = new byte[width * height * 4]; + bgraPixelData = new ArrayList(width * height * 4); bgraPixelDataHeight = height * 4; bgraPixelDataWidth = width * 4; @@ -604,7 +604,7 @@ namespace Avalonia.Controls.Primitives /// The pixel height of the bitmap. /// A new . public static WriteableBitmap CreateBitmapFromPixelData( - IList bgraPixelData, + ArrayList bgraPixelData, int pixelWidth, int pixelHeight) { @@ -617,13 +617,31 @@ namespace Avalonia.Controls.Primitives PixelFormat.Bgra8888, AlphaFormat.Premul); - // Warning: This is highly questionable using (var frameBuffer = bitmap.Lock()) { - Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); + Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length); } return bitmap; } + + /// + /// Updates the given with new, raw BGRA pre-multiplied alpha pixel data. + /// TODO: THIS METHOD IS CURRENTLY PROVIDED AS REFERENCE BUT CAUSES INTERMITTENT CRASHES IF USED. + /// WARNING: The bitmap's width, height and byte count MUST not have changed and MUST be enforced externally. + /// + /// The existing to update. + /// The bitmap (in raw BGRA pre-multiplied alpha pixels). + public static void UpdateBitmapFromPixelData( + WriteableBitmap bitmap, + ArrayList bgraPixelData) + { + using (var frameBuffer = bitmap.Lock()) + { + Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length); + } + + return; + } } } diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 16bc2acdd1..b3c6d1a430 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -8,7 +8,6 @@ x:CompileBindings="True"> - @@ -241,23 +240,21 @@ - + - + - @@ -215,23 +214,21 @@ - + - + WatermarkProperty = + TextBox.WatermarkProperty.AddOwner(); + + /// + /// Identifies the property. + /// + /// The identifier for the property. + public static readonly StyledProperty MinimumPrefixLengthProperty = + AvaloniaProperty.Register( + nameof(MinimumPrefixLength), 1, + validate: IsValidMinimumPrefixLength); + + /// + /// Identifies the property. + /// + /// The identifier for the property. + public static readonly StyledProperty MinimumPopulateDelayProperty = + AvaloniaProperty.Register( + nameof(MinimumPopulateDelay), + TimeSpan.Zero, + validate: IsValidMinimumPopulateDelay); + + /// + /// Identifies the property. + /// + /// The identifier for the property. + public static readonly StyledProperty MaxDropDownHeightProperty = + AvaloniaProperty.Register( + nameof(MaxDropDownHeight), + double.PositiveInfinity, + validate: IsValidMaxDropDownHeight); + + /// + /// Identifies the property. + /// + /// The identifier for the property. + public static readonly StyledProperty IsTextCompletionEnabledProperty = + AvaloniaProperty.Register(nameof(IsTextCompletionEnabled)); + + /// + /// Identifies the property. + /// + /// The identifier for the property. + public static readonly StyledProperty ItemTemplateProperty = + AvaloniaProperty.Register(nameof(ItemTemplate)); + + /// + /// Identifies the property. + /// + /// The identifier for the property. + public static readonly DirectProperty IsDropDownOpenProperty = + AvaloniaProperty.RegisterDirect( + nameof(IsDropDownOpen), + o => o.IsDropDownOpen, + (o, v) => o.IsDropDownOpen = v); + + /// + /// Identifies the property. + /// + /// The identifier the property. + public static readonly DirectProperty SelectedItemProperty = + AvaloniaProperty.RegisterDirect( + nameof(SelectedItem), + o => o.SelectedItem, + (o, v) => o.SelectedItem = v, + defaultBindingMode: BindingMode.TwoWay, + enableDataValidation: true); + + /// + /// Identifies the property. + /// + /// The identifier for the property. + public static readonly DirectProperty TextProperty = + TextBlock.TextProperty.AddOwnerWithDataValidation( + o => o.Text, + (o, v) => o.Text = v, + defaultBindingMode: BindingMode.TwoWay, + enableDataValidation: true); + + /// + /// Identifies the property. + /// + /// The identifier for the property. + public static readonly DirectProperty SearchTextProperty = + AvaloniaProperty.RegisterDirect( + nameof(SearchText), + o => o.SearchText, + unsetValue: string.Empty); + + /// + /// Gets the identifier for the property. + /// + public static readonly StyledProperty FilterModeProperty = + AvaloniaProperty.Register( + nameof(FilterMode), + defaultValue: AutoCompleteFilterMode.StartsWith, + validate: IsValidFilterMode); + + /// + /// Identifies the property. + /// + /// The identifier for the property. + public static readonly DirectProperty?> ItemFilterProperty = + AvaloniaProperty.RegisterDirect?>( + nameof(ItemFilter), + o => o.ItemFilter, + (o, v) => o.ItemFilter = v); + + /// + /// Identifies the property. + /// + /// The identifier for the property. + public static readonly DirectProperty?> TextFilterProperty = + AvaloniaProperty.RegisterDirect?>( + nameof(TextFilter), + o => o.TextFilter, + (o, v) => o.TextFilter = v, + unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith)); + + /// + /// Identifies the property. + /// + /// The identifier for the property. + public static readonly DirectProperty?> ItemSelectorProperty = + AvaloniaProperty.RegisterDirect?>( + nameof(ItemSelector), + o => o.ItemSelector, + (o, v) => o.ItemSelector = v); + + /// + /// Identifies the property. + /// + /// The identifier for the property. + public static readonly DirectProperty?> TextSelectorProperty = + AvaloniaProperty.RegisterDirect?>( + nameof(TextSelector), + o => o.TextSelector, + (o, v) => o.TextSelector = v); + + /// + /// Identifies the property. + /// + /// The identifier for the property. + public static readonly DirectProperty ItemsProperty = + AvaloniaProperty.RegisterDirect( + nameof(Items), + o => o.Items, + (o, v) => o.Items = v); + + public static readonly DirectProperty>>?> AsyncPopulatorProperty = + AvaloniaProperty.RegisterDirect>>?>( + nameof(AsyncPopulator), + o => o.AsyncPopulator, + (o, v) => o.AsyncPopulator = v); + + /// + /// Gets or sets the minimum number of characters required to be entered + /// in the text box before the displays possible matches. + /// + /// + /// The minimum number of characters to be entered in the text box + /// before the + /// displays possible matches. The default is 1. + /// + /// + /// 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. + /// + public int MinimumPrefixLength + { + get => GetValue(MinimumPrefixLengthProperty); + set => SetValue(MinimumPrefixLengthProperty, value); + } + + /// + /// Gets or sets a value indicating whether the first possible match + /// found during the filtering process will be displayed automatically + /// in the text box. + /// + /// + /// True if the first possible match found will be displayed + /// automatically in the text box; otherwise, false. The default is + /// false. + /// + public bool IsTextCompletionEnabled + { + get => GetValue(IsTextCompletionEnabledProperty); + set => SetValue(IsTextCompletionEnabledProperty, value); + } + + /// + /// Gets or sets the used + /// to display each item in the drop-down portion of the control. + /// + /// The used to + /// display each item in the drop-down. The default is null. + /// + /// 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. + /// + public IDataTemplate ItemTemplate + { + get => GetValue(ItemTemplateProperty); + set => SetValue(ItemTemplateProperty, value); + } + + /// + /// Gets or sets the minimum delay, after text is typed + /// in the text box before the + /// control + /// populates the list of possible matches in the drop-down. + /// + /// The minimum delay, after text is typed in + /// the text box, but before the + /// populates + /// the list of possible matches in the drop-down. The default is 0. + public TimeSpan MinimumPopulateDelay + { + get => GetValue(MinimumPopulateDelayProperty); + set => SetValue(MinimumPopulateDelayProperty, value); + } + + /// + /// Gets or sets the maximum height of the drop-down portion of the + /// control. + /// + /// The maximum height of the drop-down portion of the + /// control. + /// The default is . + /// The specified value is less than 0. + public double MaxDropDownHeight + { + get => GetValue(MaxDropDownHeightProperty); + set => SetValue(MaxDropDownHeightProperty, value); + } + + /// + /// Gets or sets a value indicating whether the drop-down portion of + /// the control is open. + /// + /// + /// True if the drop-down is open; otherwise, false. The default is + /// false. + /// + public bool IsDropDownOpen + { + get => _isDropDownOpen; + set => SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); + } + + /// + /// Gets or sets the that + /// is used to get the values for display in the text portion of + /// the + /// control. + /// + /// The object used + /// when binding to a collection property. + [AssignBinding] + public IBinding? ValueMemberBinding + { + get => _valueBindingEvaluator?.ValueBinding; + set + { + if (ValueMemberBinding != value) + { + _valueBindingEvaluator = new BindingEvaluator(value); + OnValueMemberBindingChanged(value); + } + } + } + + /// + /// Gets or sets the selected item in the drop-down. + /// + /// The selected item in the drop-down. + /// + /// 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. + /// + public object? SelectedItem + { + get => _selectedItem; + set => SetAndRaise(SelectedItemProperty, ref _selectedItem, value); + } + + /// + /// Gets or sets the text in the text box portion of the + /// control. + /// + /// The text in the text box portion of the + /// control. + public string? Text + { + get => _text; + set => SetAndRaise(TextProperty, ref _text, value); + } + + /// + /// Gets the text that is used to filter items in the + /// item collection. + /// + /// The text that is used to filter items in the + /// item collection. + /// + /// The SearchText value is typically the same as the + /// Text property, but is set after the TextChanged event occurs + /// and before the Populating event. + /// + public string? SearchText + { + get => _searchText; + private set + { + try + { + _allowWrite = true; + SetAndRaise(SearchTextProperty, ref _searchText, value); + } + finally + { + _allowWrite = false; + } + } + } + + /// + /// Gets or sets how the text in the text box is used to filter items + /// specified by the + /// property for display in the drop-down. + /// + /// One of the + /// values The default is . + /// The specified value is not a valid + /// . + /// + /// 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. + /// + public AutoCompleteFilterMode FilterMode + { + get => GetValue(FilterModeProperty); + set => SetValue(FilterModeProperty, value); + } + + public string? Watermark + { + get => GetValue(WatermarkProperty); + set => SetValue(WatermarkProperty, value); + } + + /// + /// Gets or sets the custom method that uses user-entered text to filter + /// the items specified by the + /// property for display in the drop-down. + /// + /// The custom method that uses the user-entered text to filter + /// the items specified by the + /// property. The default is null. + /// + /// The filter mode is automatically set to Custom if you set the + /// ItemFilter property. + /// + public AutoCompleteFilterPredicate? ItemFilter + { + get => _itemFilter; + set => SetAndRaise(ItemFilterProperty, ref _itemFilter, value); + } + + /// + /// Gets or sets the custom method that uses the user-entered text to + /// filter items specified by the + /// property in a text-based way for display in the drop-down. + /// + /// The custom method that uses the user-entered text to filter + /// items specified by the + /// property in a text-based way for display in the drop-down. + /// + /// The search mode is automatically set to Custom if you set the + /// TextFilter property. + /// + public AutoCompleteFilterPredicate? TextFilter + { + get => _textFilter; + set => SetAndRaise(TextFilterProperty, ref _textFilter, value); + } + + /// + /// Gets or sets the custom method that combines the user-entered + /// text and one of the items specified by the . + /// + /// + /// The custom method that combines the user-entered + /// text and one of the items specified by the . + /// + public AutoCompleteSelector? ItemSelector + { + get => _itemSelector; + set => SetAndRaise(ItemSelectorProperty, ref _itemSelector, value); + } + + /// + /// Gets or sets the custom method that combines the user-entered + /// text and one of the items specified by the + /// in a text-based way. + /// + /// + /// The custom method that combines the user-entered + /// text and one of the items specified by the + /// in a text-based way. + /// + public AutoCompleteSelector? TextSelector + { + get => _textSelector; + set => SetAndRaise(TextSelectorProperty, ref _textSelector, value); + } + + public Func>>? AsyncPopulator + { + get => _asyncPopulator; + set => SetAndRaise(AsyncPopulatorProperty, ref _asyncPopulator, value); + } + + /// + /// Gets or sets a collection that is used to generate the items for the + /// drop-down portion of the control. + /// + /// The collection that is used to generate the items of the + /// drop-down portion of the control. + public IEnumerable? Items + { + get => _itemsEnumerable; + set => SetAndRaise(ItemsProperty, ref _itemsEnumerable, value); + } + } +} diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs similarity index 71% rename from src/Avalonia.Controls/AutoCompleteBox.cs rename to src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index 8a8c4ead86..a027b8b650 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -26,65 +26,6 @@ using Avalonia.VisualTree; namespace Avalonia.Controls { - /// - /// Provides data for the - /// - /// event. - /// - public class PopulatedEventArgs : EventArgs - { - /// - /// Gets the list of possible matches added to the drop-down portion of - /// the - /// control. - /// - /// The list of possible matches added to the - /// . - public IEnumerable Data { get; private set; } - - /// - /// Initializes a new instance of the - /// . - /// - /// The list of possible matches added to the - /// drop-down portion of the - /// control. - public PopulatedEventArgs(IEnumerable data) - { - Data = data; - } - } - - /// - /// Provides data for the - /// - /// event. - /// - public class PopulatingEventArgs : CancelEventArgs - { - /// - /// Gets the text that is used to determine which items to display in - /// the - /// control. - /// - /// The text that is used to determine which items to display in - /// the . - public string? Parameter { get; private set; } - - /// - /// Initializes a new instance of the - /// . - /// - /// The value of the - /// - /// property, which is used to filter items for the - /// control. - public PopulatingEventArgs(string? parameter) - { - Parameter = parameter; - } - } - /// /// Represents the filter used by the /// control to @@ -100,132 +41,6 @@ namespace Avalonia.Controls /// be either a string or an object. public delegate bool AutoCompleteFilterPredicate(string? search, T item); - /// - /// Specifies how text in the text box portion of the - /// control is used - /// to filter items specified by the - /// - /// property for display in the drop-down. - /// - public enum AutoCompleteFilterMode - { - /// - /// Specifies that no filter is used. All items are returned. - /// - None = 0, - - /// - /// Specifies a culture-sensitive, case-insensitive filter where the - /// returned items start with the specified text. The filter uses the - /// - /// method, specifying - /// as - /// the string comparison criteria. - /// - StartsWith = 1, - - /// - /// Specifies a culture-sensitive, case-sensitive filter where the - /// returned items start with the specified text. The filter uses the - /// - /// method, specifying - /// as the string - /// comparison criteria. - /// - StartsWithCaseSensitive = 2, - - /// - /// Specifies an ordinal, case-insensitive filter where the returned - /// items start with the specified text. The filter uses the - /// - /// method, specifying - /// as the - /// string comparison criteria. - /// - StartsWithOrdinal = 3, - - /// - /// Specifies an ordinal, case-sensitive filter where the returned items - /// start with the specified text. The filter uses the - /// - /// method, specifying as - /// the string comparison criteria. - /// - StartsWithOrdinalCaseSensitive = 4, - - /// - /// Specifies a culture-sensitive, case-insensitive filter where the - /// returned items contain the specified text. - /// - Contains = 5, - - /// - /// Specifies a culture-sensitive, case-sensitive filter where the - /// returned items contain the specified text. - /// - ContainsCaseSensitive = 6, - - /// - /// Specifies an ordinal, case-insensitive filter where the returned - /// items contain the specified text. - /// - ContainsOrdinal = 7, - - /// - /// Specifies an ordinal, case-sensitive filter where the returned items - /// contain the specified text. - /// - ContainsOrdinalCaseSensitive = 8, - - /// - /// Specifies a culture-sensitive, case-insensitive filter where the - /// returned items equal the specified text. The filter uses the - /// - /// method, specifying - /// as - /// the search comparison criteria. - /// - Equals = 9, - - /// - /// Specifies a culture-sensitive, case-sensitive filter where the - /// returned items equal the specified text. The filter uses the - /// - /// method, specifying - /// as the string - /// comparison criteria. - /// - EqualsCaseSensitive = 10, - - /// - /// Specifies an ordinal, case-insensitive filter where the returned - /// items equal the specified text. The filter uses the - /// - /// method, specifying - /// as the - /// string comparison criteria. - /// - EqualsOrdinal = 11, - - /// - /// Specifies an ordinal, case-sensitive filter where the returned items - /// equal the specified text. The filter uses the - /// - /// method, specifying as - /// the string comparison criteria. - /// - EqualsOrdinalCaseSensitive = 12, - - /// - /// Specifies that a custom filter is used. This mode is used when the - /// - /// or - /// - /// properties are set. - /// - Custom = 13, - } - /// /// Represents the selector used by the /// control to @@ -257,7 +72,7 @@ namespace Avalonia.Controls [TemplatePart(ElementSelectionAdapter, typeof(ISelectionAdapter))] [TemplatePart(ElementTextBox, typeof(TextBox))] [PseudoClasses(":dropdownopen")] - public class AutoCompleteBox : TemplatedControl + public partial class AutoCompleteBox : TemplatedControl { /// /// Specifies the name of the selection adapter TemplatePart. @@ -394,221 +209,22 @@ namespace Avalonia.Controls private readonly EventHandler _populateDropDownHandler; - public static readonly RoutedEvent SelectionChangedEvent = - RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox)); - - public static readonly StyledProperty WatermarkProperty = - TextBox.WatermarkProperty.AddOwner(); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly StyledProperty MinimumPrefixLengthProperty = - AvaloniaProperty.Register( - nameof(MinimumPrefixLength), 1, - validate: IsValidMinimumPrefixLength); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly StyledProperty MinimumPopulateDelayProperty = - AvaloniaProperty.Register( - nameof(MinimumPopulateDelay), - TimeSpan.Zero, - validate: IsValidMinimumPopulateDelay); - /// - /// Identifies the - /// - /// dependency property. + /// /// - /// The identifier for the - /// - /// dependency property. - public static readonly StyledProperty MaxDropDownHeightProperty = - AvaloniaProperty.Register( - nameof(MaxDropDownHeight), - double.PositiveInfinity, - validate: IsValidMaxDropDownHeight); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly StyledProperty IsTextCompletionEnabledProperty = - AvaloniaProperty.Register(nameof(IsTextCompletionEnabled)); + public static readonly RoutedEvent SelectionChangedEvent = + RoutedEvent.Register( + nameof(SelectionChanged), + RoutingStrategies.Bubble, + typeof(AutoCompleteBox)); /// - /// Identifies the - /// - /// dependency property. + /// Defines the event. /// - /// The identifier for the - /// - /// dependency property. - public static readonly StyledProperty ItemTemplateProperty = - AvaloniaProperty.Register(nameof(ItemTemplate)); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty IsDropDownOpenProperty = - AvaloniaProperty.RegisterDirect( - nameof(IsDropDownOpen), - o => o.IsDropDownOpen, - (o, v) => o.IsDropDownOpen = v); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier the - /// - /// dependency property. - public static readonly DirectProperty SelectedItemProperty = - AvaloniaProperty.RegisterDirect( - nameof(SelectedItem), - o => o.SelectedItem, - (o, v) => o.SelectedItem = v, - defaultBindingMode: BindingMode.TwoWay, - enableDataValidation: true); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty TextProperty = - TextBlock.TextProperty.AddOwnerWithDataValidation( - o => o.Text, - (o, v) => o.Text = v, - defaultBindingMode: BindingMode.TwoWay, - enableDataValidation: true); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty SearchTextProperty = - AvaloniaProperty.RegisterDirect( - nameof(SearchText), - o => o.SearchText, - unsetValue: string.Empty); - - /// - /// Gets the identifier for the - /// - /// dependency property. - /// - public static readonly StyledProperty FilterModeProperty = - AvaloniaProperty.Register( - nameof(FilterMode), - defaultValue: AutoCompleteFilterMode.StartsWith, - validate: IsValidFilterMode); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty?> ItemFilterProperty = - AvaloniaProperty.RegisterDirect?>( - nameof(ItemFilter), - o => o.ItemFilter, - (o, v) => o.ItemFilter = v); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty?> TextFilterProperty = - AvaloniaProperty.RegisterDirect?>( - nameof(TextFilter), - o => o.TextFilter, - (o, v) => o.TextFilter = v, - unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith)); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty?> ItemSelectorProperty = - AvaloniaProperty.RegisterDirect?>( - nameof(ItemSelector), - o => o.ItemSelector, - (o, v) => o.ItemSelector = v); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty?> TextSelectorProperty = - AvaloniaProperty.RegisterDirect?>( - nameof(TextSelector), - o => o.TextSelector, - (o, v) => o.TextSelector = v); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty ItemsProperty = - AvaloniaProperty.RegisterDirect( - nameof(Items), - o => o.Items, - (o, v) => o.Items = v); - - public static readonly DirectProperty>>?> AsyncPopulatorProperty = - AvaloniaProperty.RegisterDirect>>?>( - nameof(AsyncPopulator), - o => o.AsyncPopulator, - (o, v) => o.AsyncPopulator = v); + public static readonly RoutedEvent TextChangedEvent = + RoutedEvent.Register( + nameof(TextChanged), + RoutingStrategies.Bubble); private static bool IsValidMinimumPrefixLength(int value) => value >= -1; @@ -871,315 +487,6 @@ namespace Avalonia.Controls ClearView(); } - /// - /// Gets or sets the minimum number of characters required to be entered - /// in the text box before the - /// displays - /// possible matches. - /// matches. - /// - /// - /// The minimum number of characters to be entered in the text box - /// before the - /// displays possible matches. The default is 1. - /// - /// - /// 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. - /// - public int MinimumPrefixLength - { - get { return GetValue(MinimumPrefixLengthProperty); } - set { SetValue(MinimumPrefixLengthProperty, value); } - } - - /// - /// Gets or sets a value indicating whether the first possible match - /// found during the filtering process will be displayed automatically - /// in the text box. - /// - /// - /// True if the first possible match found will be displayed - /// automatically in the text box; otherwise, false. The default is - /// false. - /// - public bool IsTextCompletionEnabled - { - get { return GetValue(IsTextCompletionEnabledProperty); } - set { SetValue(IsTextCompletionEnabledProperty, value); } - } - - /// - /// Gets or sets the used - /// to display each item in the drop-down portion of the control. - /// - /// The used to - /// display each item in the drop-down. The default is null. - /// - /// 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. - /// - public IDataTemplate ItemTemplate - { - get { return GetValue(ItemTemplateProperty); } - set { SetValue(ItemTemplateProperty, value); } - } - - /// - /// Gets or sets the minimum delay, after text is typed - /// in the text box before the - /// control - /// populates the list of possible matches in the drop-down. - /// - /// The minimum delay, after text is typed in - /// the text box, but before the - /// populates - /// the list of possible matches in the drop-down. The default is 0. - public TimeSpan MinimumPopulateDelay - { - get { return GetValue(MinimumPopulateDelayProperty); } - set { SetValue(MinimumPopulateDelayProperty, value); } - } - - /// - /// Gets or sets the maximum height of the drop-down portion of the - /// control. - /// - /// The maximum height of the drop-down portion of the - /// control. - /// The default is . - /// The specified value is less than 0. - public double MaxDropDownHeight - { - get { return GetValue(MaxDropDownHeightProperty); } - set { SetValue(MaxDropDownHeightProperty, value); } - } - - /// - /// Gets or sets a value indicating whether the drop-down portion of - /// the control is open. - /// - /// - /// True if the drop-down is open; otherwise, false. The default is - /// false. - /// - public bool IsDropDownOpen - { - get { return _isDropDownOpen; } - set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); } - } - - /// - /// Gets or sets the that - /// is used to get the values for display in the text portion of - /// the - /// control. - /// - /// The object used - /// when binding to a collection property. - [AssignBinding] - public IBinding? ValueMemberBinding - { - get { return _valueBindingEvaluator?.ValueBinding; } - set - { - if (ValueMemberBinding != value) - { - _valueBindingEvaluator = new BindingEvaluator(value); - OnValueMemberBindingChanged(value); - } - } - } - - /// - /// Gets or sets the selected item in the drop-down. - /// - /// The selected item in the drop-down. - /// - /// 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. - /// - public object? SelectedItem - { - get { return _selectedItem; } - set { SetAndRaise(SelectedItemProperty, ref _selectedItem, value); } - } - - /// - /// Gets or sets the text in the text box portion of the - /// control. - /// - /// The text in the text box portion of the - /// control. - public string? Text - { - get { return _text; } - set { SetAndRaise(TextProperty, ref _text, value); } - } - - /// - /// Gets the text that is used to filter items in the - /// - /// item collection. - /// - /// The text that is used to filter items in the - /// - /// item collection. - /// - /// The SearchText value is typically the same as the - /// Text property, but is set after the TextChanged event occurs - /// and before the Populating event. - /// - public string? SearchText - { - get { return _searchText; } - private set - { - try - { - _allowWrite = true; - SetAndRaise(SearchTextProperty, ref _searchText, value); - } - finally - { - _allowWrite = false; - } - } - } - - /// - /// Gets or sets how the text in the text box is used to filter items - /// specified by the - /// - /// property for display in the drop-down. - /// - /// One of the - /// - /// values The default is - /// . - /// The specified value is - /// not a valid - /// . - /// - /// 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. - /// - public AutoCompleteFilterMode FilterMode - { - get { return GetValue(FilterModeProperty); } - set { SetValue(FilterModeProperty, value); } - } - - public string? Watermark - { - get { return GetValue(WatermarkProperty); } - set { SetValue(WatermarkProperty, value); } - } - - /// - /// Gets or sets the custom method that uses user-entered text to filter - /// the items specified by the - /// - /// property for display in the drop-down. - /// - /// The custom method that uses the user-entered text to filter - /// the items specified by the - /// - /// property. The default is null. - /// - /// The filter mode is automatically set to Custom if you set the - /// ItemFilter property. - /// - public AutoCompleteFilterPredicate? ItemFilter - { - get { return _itemFilter; } - set { SetAndRaise(ItemFilterProperty, ref _itemFilter, value); } - } - - /// - /// Gets or sets the custom method that uses the user-entered text to - /// filter items specified by the - /// - /// property in a text-based way for display in the drop-down. - /// - /// The custom method that uses the user-entered text to filter - /// items specified by the - /// - /// property in a text-based way for display in the drop-down. - /// - /// The search mode is automatically set to Custom if you set the - /// TextFilter property. - /// - public AutoCompleteFilterPredicate? TextFilter - { - get { return _textFilter; } - set { SetAndRaise(TextFilterProperty, ref _textFilter, value); } - } - - /// - /// Gets or sets the custom method that combines the user-entered - /// text and one of the items specified by the - /// . - /// - /// - /// The custom method that combines the user-entered - /// text and one of the items specified by the - /// . - /// - public AutoCompleteSelector? ItemSelector - { - get { return _itemSelector; } - set { SetAndRaise(ItemSelectorProperty, ref _itemSelector, value); } - } - - /// - /// Gets or sets the custom method that combines the user-entered - /// text and one of the items specified by the - /// - /// in a text-based way. - /// - /// - /// The custom method that combines the user-entered - /// text and one of the items specified by the - /// - /// in a text-based way. - /// - public AutoCompleteSelector? TextSelector - { - get { return _textSelector; } - set { SetAndRaise(TextSelectorProperty, ref _textSelector, value); } - } - - public Func>>? AsyncPopulator - { - get { return _asyncPopulator; } - set { SetAndRaise(AsyncPopulatorProperty, ref _asyncPopulator, value); } - } - - /// - /// Gets or sets a collection that is used to generate the items for the - /// drop-down portion of the - /// control. - /// - /// The collection that is used to generate the items of the - /// drop-down portion of the - /// control. - public IEnumerable? Items - { - get { return _itemsEnumerable; } - set { SetAndRaise(ItemsProperty, ref _itemsEnumerable, value); } - } - /// /// Gets or sets the drop down popup control. /// @@ -1190,7 +497,7 @@ namespace Avalonia.Controls /// private TextBox? TextBox { - get { return _textBox; } + get => _textBox; set { _textBoxSubscriptions?.Dispose(); @@ -1254,7 +561,7 @@ namespace Avalonia.Controls /// protected ISelectionAdapter? SelectionAdapter { - get { return _adapter; } + get => _adapter; set { if (_adapter != null) @@ -1529,10 +836,14 @@ namespace Avalonia.Controls } /// - /// Occurs when the text in the text box portion of the - /// changes. + /// Occurs asynchronously when the text in the portion of the + /// changes. /// - public event EventHandler? TextChanged; + public event EventHandler? TextChanged + { + add => AddHandler(TextChangedEvent, value); + remove => RemoveHandler(TextChangedEvent, value); + } /// /// Occurs when the @@ -1690,15 +1001,12 @@ namespace Avalonia.Controls } /// - /// Raises the - /// - /// event. + /// Raises the event. /// - /// A - /// that contains the event data. - protected virtual void OnTextChanged(RoutedEventArgs e) + /// A that contains the event data. + protected virtual void OnTextChanged(TextChangedEventArgs e) { - TextChanged?.Invoke(this, e); + RaiseEvent(e); } /// @@ -1985,7 +1293,7 @@ namespace Avalonia.Controls if (callTextChanged) { - OnTextChanged(new RoutedEventArgs()); + OnTextChanged(new TextChangedEventArgs(TextChangedEvent)); } } @@ -2740,8 +2048,6 @@ namespace Avalonia.Controls /// private IBinding? _binding; - #region public T Value - /// /// Identifies the Value dependency property. /// @@ -2753,18 +2059,16 @@ namespace Avalonia.Controls /// public T Value { - get { return GetValue(ValueProperty); } - set { SetValue(ValueProperty, value); } + get => GetValue(ValueProperty); + set => SetValue(ValueProperty, value); } - #endregion public string Value - /// /// Gets or sets the value binding. /// public IBinding? ValueBinding { - get { return _binding; } + get => _binding; set { _binding = value; diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteFilterMode.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteFilterMode.cs new file mode 100644 index 0000000000..c17f5a19ab --- /dev/null +++ b/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 +{ + /// + /// Specifies how text in the text box portion of the + /// control is used to filter items specified by the + /// property for display in the drop-down. + /// + public enum AutoCompleteFilterMode + { + /// + /// Specifies that no filter is used. All items are returned. + /// + None = 0, + + /// + /// Specifies a culture-sensitive, case-insensitive filter where the + /// returned items start with the specified text. The filter uses the + /// + /// method, specifying + /// as + /// the string comparison criteria. + /// + StartsWith = 1, + + /// + /// Specifies a culture-sensitive, case-sensitive filter where the + /// returned items start with the specified text. The filter uses the + /// + /// method, specifying + /// as the string + /// comparison criteria. + /// + StartsWithCaseSensitive = 2, + + /// + /// Specifies an ordinal, case-insensitive filter where the returned + /// items start with the specified text. The filter uses the + /// + /// method, specifying + /// as the + /// string comparison criteria. + /// + StartsWithOrdinal = 3, + + /// + /// Specifies an ordinal, case-sensitive filter where the returned items + /// start with the specified text. The filter uses the + /// + /// method, specifying as + /// the string comparison criteria. + /// + StartsWithOrdinalCaseSensitive = 4, + + /// + /// Specifies a culture-sensitive, case-insensitive filter where the + /// returned items contain the specified text. + /// + Contains = 5, + + /// + /// Specifies a culture-sensitive, case-sensitive filter where the + /// returned items contain the specified text. + /// + ContainsCaseSensitive = 6, + + /// + /// Specifies an ordinal, case-insensitive filter where the returned + /// items contain the specified text. + /// + ContainsOrdinal = 7, + + /// + /// Specifies an ordinal, case-sensitive filter where the returned items + /// contain the specified text. + /// + ContainsOrdinalCaseSensitive = 8, + + /// + /// Specifies a culture-sensitive, case-insensitive filter where the + /// returned items equal the specified text. The filter uses the + /// + /// method, specifying + /// as + /// the search comparison criteria. + /// + Equals = 9, + + /// + /// Specifies a culture-sensitive, case-sensitive filter where the + /// returned items equal the specified text. The filter uses the + /// + /// method, specifying + /// as the string + /// comparison criteria. + /// + EqualsCaseSensitive = 10, + + /// + /// Specifies an ordinal, case-insensitive filter where the returned + /// items equal the specified text. The filter uses the + /// + /// method, specifying + /// as the + /// string comparison criteria. + /// + EqualsOrdinal = 11, + + /// + /// Specifies an ordinal, case-sensitive filter where the returned items + /// equal the specified text. The filter uses the + /// + /// method, specifying as + /// the string comparison criteria. + /// + EqualsOrdinalCaseSensitive = 12, + + /// + /// Specifies that a custom filter is used. This mode is used when the + /// or + /// properties are set. + /// + Custom = 13, + } +} diff --git a/src/Avalonia.Controls/AutoCompleteBox/PopulatedEventArgs.cs b/src/Avalonia.Controls/AutoCompleteBox/PopulatedEventArgs.cs new file mode 100644 index 0000000000..22bc1d3cab --- /dev/null +++ b/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 +{ + /// + /// Provides data for the + /// + /// event. + /// + public class PopulatedEventArgs : EventArgs + { + /// + /// Gets the list of possible matches added to the drop-down portion of + /// the + /// control. + /// + /// The list of possible matches added to the + /// . + public IEnumerable Data { get; private set; } + + /// + /// Initializes a new instance of the + /// . + /// + /// The list of possible matches added to the + /// drop-down portion of the + /// control. + public PopulatedEventArgs(IEnumerable data) + { + Data = data; + } + } +} diff --git a/src/Avalonia.Controls/AutoCompleteBox/PopulatingEventArgs.cs b/src/Avalonia.Controls/AutoCompleteBox/PopulatingEventArgs.cs new file mode 100644 index 0000000000..c4941ad6fe --- /dev/null +++ b/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 +{ + /// + /// Provides data for the + /// + /// event. + /// + public class PopulatingEventArgs : CancelEventArgs + { + /// + /// Gets the text that is used to determine which items to display in + /// the + /// control. + /// + /// The text that is used to determine which items to display in + /// the . + public string? Parameter { get; private set; } + + /// + /// Initializes a new instance of the + /// . + /// + /// The value of the + /// + /// property, which is used to filter items for the + /// control. + public PopulatingEventArgs(string? parameter) + { + Parameter = parameter; + } + } +} diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index fb27feb521..bb05cd1b1f 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -40,18 +40,6 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(DayVisible), x => x.DayVisible, (x, v) => x.DayVisible = v); - /// - /// Defines the Property - /// - public static readonly StyledProperty HeaderProperty = - AvaloniaProperty.Register(nameof(Header)); - - /// - /// Defines the Property - /// - public static readonly StyledProperty HeaderTemplateProperty = - AvaloniaProperty.Register(nameof(HeaderTemplate)); - /// /// Defines the Property /// @@ -152,24 +140,6 @@ namespace Avalonia.Controls } } - /// - /// Gets or sets the DatePicker header - /// - public object Header - { - get => GetValue(HeaderProperty); - set => SetValue(HeaderProperty, value); - } - - /// - /// Gets or sets the header template - /// - public IDataTemplate HeaderTemplate - { - get => GetValue(HeaderTemplateProperty); - set => SetValue(HeaderTemplateProperty, value); - } - /// /// Gets or sets the maximum year for the picker /// diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index c3baa6f17f..a7a6881fe5 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -34,18 +34,6 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(MinuteIncrement), x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v); - /// - /// Defines the property - /// - public static readonly StyledProperty HeaderProperty = - AvaloniaProperty.Register(nameof(Header)); - - /// - /// Defines the property - /// - public static readonly StyledProperty HeaderTemplateProperty = - AvaloniaProperty.Register(nameof(HeaderTemplate)); - /// /// Defines the property /// @@ -103,24 +91,6 @@ namespace Avalonia.Controls } } - /// - /// Gets or sets the header - /// - public object Header - { - get => GetValue(HeaderProperty); - set => SetValue(HeaderProperty, value); - } - - /// - /// Gets or sets the header template - /// - public IDataTemplate HeaderTemplate - { - get => GetValue(HeaderTemplateProperty); - set => SetValue(HeaderTemplateProperty, value); - } - /// /// Gets or sets the clock identifier, either 12HourClock or 24HourClock /// diff --git a/src/Avalonia.Controls/Documents/IInlineHost.cs b/src/Avalonia.Controls/Documents/IInlineHost.cs index da72c207be..5d142952ab 100644 --- a/src/Avalonia.Controls/Documents/IInlineHost.cs +++ b/src/Avalonia.Controls/Documents/IInlineHost.cs @@ -4,8 +4,6 @@ namespace Avalonia.Controls.Documents { internal interface IInlineHost : ILogical { - void AddVisualChild(IControl child); - void Invalidate(); } } diff --git a/src/Avalonia.Controls/Documents/InlineRun.cs b/src/Avalonia.Controls/Documents/InlineRun.cs new file mode 100644 index 0000000000..68c61ca3fa --- /dev/null +++ b/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(TextBlock.BaselineOffsetProperty); + + if (!MathUtilities.IsZero(baselineOffsetValue)) + { + baseline = baselineOffsetValue; + } + + return -baseline; + } + } + + public override void Draw(DrawingContext drawingContext, Point origin) + { + //noop + } + } +} diff --git a/src/Avalonia.Controls/Documents/InlineUIContainer.cs b/src/Avalonia.Controls/Documents/InlineUIContainer.cs index d632e5fea7..7107fb7fed 100644 --- a/src/Avalonia.Controls/Documents/InlineUIContainer.cs +++ b/src/Avalonia.Controls/Documents/InlineUIContainer.cs @@ -3,7 +3,6 @@ using System.Text; using Avalonia.Media; using Avalonia.Media.TextFormatting; using Avalonia.Metadata; -using Avalonia.Utilities; namespace Avalonia.Controls.Documents { @@ -59,56 +58,11 @@ namespace Avalonia.Controls.Documents internal override void BuildTextRun(IList textRuns) { - if(InlineHost == null) - { - return; - } - - ((ISetLogicalParent)Child).SetParent(InlineHost); - - InlineHost.AddVisualChild(Child); - - textRuns.Add(new InlineRun(Child, CreateTextRunProperties())); + textRuns.Add(new EmbeddedControlRun(Child, CreateTextRunProperties())); } internal override void AppendText(StringBuilder stringBuilder) { } - - private class InlineRun : DrawableTextRun - { - public InlineRun(IControl control, TextRunProperties properties) - { - Control = control; - Properties = properties; - } - - public IControl Control { get; } - - public override TextRunProperties? Properties { get; } - - public override Size Size => Control.DesiredSize; - - public override double Baseline - { - get - { - double baseline = Size.Height; - double baselineOffsetValue = Control.GetValue(TextBlock.BaselineOffsetProperty); - - if (!MathUtilities.IsZero(baselineOffsetValue)) - { - baseline = baselineOffsetValue; - } - - return -baseline; - } - } - - public override void Draw(DrawingContext drawingContext, Point origin) - { - //noop - } - } } } diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 00ebcab70e..65ec4cc54c 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -597,7 +597,7 @@ namespace Avalonia.Controls.Primitives for (int i = presenter.Classes.Count - 1; i >= 0; i--) { if (!classes.Contains(presenter.Classes[i]) && - !presenter.Classes[i].Contains(":")) + !presenter.Classes[i].Contains(':')) { presenter.Classes.RemoveAt(i); } diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index b607ced10a..21373c6b76 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -1151,8 +1151,8 @@ namespace Avalonia.Controls if (PIndex >= 0) { //stringToTest contains a "P" between 2 "'", it's considered as text, not percent - var isText = stringToTest.Substring(0, PIndex).Contains("'") - && stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains("'"); + var isText = stringToTest.Substring(0, PIndex).Contains('\'') + && stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains('\''); return !isText; } diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs index acb8e0f006..38d848d69b 100644 --- a/src/Avalonia.Controls/Primitives/RangeBase.cs +++ b/src/Avalonia.Controls/Primitives/RangeBase.cs @@ -175,7 +175,7 @@ namespace Avalonia.Controls.Primitives /// The value. private static bool ValidateDouble(double value) { - return !double.IsInfinity(value) || !double.IsNaN(value); + return !double.IsInfinity(value) && !double.IsNaN(value); } /// diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs index 906a038ec3..d0b713ba56 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/RichTextBlock.cs @@ -61,6 +61,7 @@ namespace Avalonia.Controls private int _selectionStart; private int _selectionEnd; private int _wordSelectionStart = -1; + private IReadOnlyList? _textRuns; static RichTextBlock() { @@ -277,8 +278,8 @@ namespace Avalonia.Controls protected override void SetText(string? text) { var oldValue = GetText(); - - AddText(text); + + AddText(text); RaisePropertyChanged(TextProperty, oldValue, text); } @@ -301,18 +302,9 @@ namespace Avalonia.Controls ITextSource textSource; - if (HasComplexContent) + if (_textRuns != null) { - var inlines = Inlines!; - - var textRuns = new List(); - - foreach (var inline in inlines) - { - inline.BuildTextRun(textRuns); - } - - textSource = new InlinesTextSource(textRuns); + textSource = new InlinesTextSource(_textRuns); } else { @@ -546,27 +538,73 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size availableSize) { - foreach (var child in VisualChildren) + if(_textRuns != null) { - if (child is Control control) + LogicalChildren.Clear(); + + VisualChildren.Clear(); + + _textRuns = null; + } + + if (Inlines != null && Inlines.Count > 0) + { + var inlines = Inlines; + + var textRuns = new List(); + + foreach (var inline in inlines) + { + inline.BuildTextRun(textRuns); + } + + foreach (var textRun in textRuns) { - control.Measure(Size.Infinity); + if (textRun is EmbeddedControlRun controlRun && + controlRun.Control is Control control) + { + LogicalChildren.Add(control); + + VisualChildren.Add(control); + + control.Measure(Size.Infinity); + } } + + _textRuns = textRuns; } - + return base.MeasureOverride(availableSize); } protected override Size ArrangeOverride(Size finalSize) { - foreach (var child in VisualChildren) + if (HasComplexContent) { - if (child is Control control) + var currentY = 0.0; + + foreach (var textLine in TextLayout.TextLines) { - control.Arrange(new Rect(control.DesiredSize)); + var currentX = textLine.Start; + + foreach (var run in textLine.TextRuns) + { + if (run is DrawableTextRun drawable) + { + if (drawable is EmbeddedControlRun controlRun + && controlRun.Control is Control control) + { + control.Arrange(new Rect(new Point(currentX, currentY), control.DesiredSize)); + } + + currentX += drawable.Size.Width; + } + } + + currentY += textLine.Height; } } - + return base.ArrangeOverride(finalSize); } @@ -618,14 +656,6 @@ namespace Avalonia.Controls } } - void IInlineHost.AddVisualChild(IControl child) - { - if (child.VisualParent == null) - { - VisualChildren.Add(child); - } - } - void IInlineHost.Invalidate() { InvalidateTextLayout(); diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index e6677194b0..7d4a6518ef 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -191,7 +191,7 @@ namespace Avalonia.DesignerSupport.Remote public Task ClearAsync() => Task.CompletedTask; public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask; - public Task GetFormatsAsync() => Task.FromResult(new string[0]); + public Task GetFormatsAsync() => Task.FromResult(Array.Empty()); public Task GetDataAsync(string format) => Task.FromResult((object)null); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml index 1270dbaa62..d2b31fdb20 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml @@ -3,8 +3,7 @@ xmlns:views="clr-namespace:Avalonia.Diagnostics.Views" xmlns:diag="clr-namespace:Avalonia.Diagnostics" Title="Avalonia DevTools" - x:Class="Avalonia.Diagnostics.Views.MainWindow" - Theme="{StaticResource {x:Type Window}}"> + x:Class="Avalonia.Diagnostics.Views.MainWindow"> diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index c81997f2cb..e6e630112b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -28,6 +28,11 @@ namespace Avalonia.Diagnostics.Views { InitializeComponent(); + // Apply the SimpleTheme.Window theme; this must be done after the XAML is parsed as + // the theme is included in the MainWindow's XAML. + if (Theme is null && this.FindResource(typeof(Window)) is ControlTheme windowTheme) + Theme = windowTheme; + _keySubscription = InputManager.Instance?.Process .OfType() .Where(x => x.Type == RawKeyEventType.KeyDown) diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index cb23c6c336..fcd7f1e31f 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -110,14 +110,24 @@ namespace Avalonia.Headless 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 @@ -203,6 +213,26 @@ namespace Avalonia.Headless public Matrix Transform { get; } } + class HeadlessGlyphRunBufferStub : IGlyphRunBuffer + { + public Span GlyphIndices => Span.Empty; + + public IGlyphRunImpl Build() + { + return new HeadlessGlyphRunStub(); + } + } + + class HeadlessHorizontalGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IHorizontalGlyphRunBuffer + { + public Span GlyphPositions => Span.Empty; + } + + class HeadlessPositionedGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IPositionedGlyphRunBuffer + { + public Span GlyphPositions => Span.Empty; + } + class HeadlessGlyphRunStub : IGlyphRunImpl { public void Dispose() diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index ed51529a96..c8ac947c16 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -75,8 +75,13 @@ namespace Avalonia.Headless public TimeSpan TouchDoubleClickTime => DoubleClickTime; } - class HeadlessGlyphTypefaceImpl : IGlyphTypefaceImpl + class HeadlessGlyphTypefaceImpl : IGlyphTypeface { + public FontMetrics Metrics => new FontMetrics + { + + }; + public short DesignEmHeight => 10; public int Ascent => 5; @@ -95,6 +100,8 @@ namespace Avalonia.Headless public bool IsFixedPitch => true; + public int GlyphCount => 1337; + public void Dispose() { } @@ -104,6 +111,13 @@ namespace Avalonia.Headless return 1; } + public bool TryGetGlyph(uint codepoint, out ushort glyph) + { + glyph = 1; + + return true; + } + public int GetGlyphAdvance(ushort glyph) { return 1; @@ -118,6 +132,12 @@ namespace Avalonia.Headless { return codepoints.ToArray().Select(x => (ushort)x).ToArray(); } + + public bool TryGetTable(uint tag, out byte[] table) + { + table = null; + return false; + } } class HeadlessTextShaperStub : ITextShaperImpl @@ -134,7 +154,7 @@ namespace Avalonia.Headless class HeadlessFontManagerStub : IFontManagerImpl { - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { return new HeadlessGlyphTypefaceImpl(); } diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs index 524ec09ca9..6abece6bf3 100644 --- a/src/Avalonia.Remote.Protocol/MetsysBson.cs +++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs @@ -1364,13 +1364,13 @@ namespace Metsys.Bson var optionsString = ReadName(); var options = RegexOptions.None; - if (optionsString.Contains("e")) options = options | RegexOptions.ECMAScript; - if (optionsString.Contains("i")) options = options | RegexOptions.IgnoreCase; - if (optionsString.Contains("l")) options = options | RegexOptions.CultureInvariant; - if (optionsString.Contains("m")) options = options | RegexOptions.Multiline; - if (optionsString.Contains("s")) options = options | RegexOptions.Singleline; - if (optionsString.Contains("w")) options = options | RegexOptions.IgnorePatternWhitespace; - if (optionsString.Contains("x")) options = options | RegexOptions.ExplicitCapture; + if (optionsString.Contains('e')) options = options | RegexOptions.ECMAScript; + if (optionsString.Contains('i')) options = options | RegexOptions.IgnoreCase; + if (optionsString.Contains('l')) options = options | RegexOptions.CultureInvariant; + if (optionsString.Contains('m')) options = options | RegexOptions.Multiline; + if (optionsString.Contains('s')) options = options | RegexOptions.Singleline; + if (optionsString.Contains('w')) options = options | RegexOptions.IgnorePatternWhitespace; + if (optionsString.Contains('x')) options = options | RegexOptions.ExplicitCapture; return new Regex(pattern, options); } diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index 88b9f18eee..e480fb1670 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -358,7 +358,6 @@ 4 2 0 - Normal @@ -375,8 +374,6 @@ - - @@ -424,7 +421,6 @@ - @@ -455,8 +451,6 @@ - - @@ -595,7 +589,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index b51a0bf259..4f421f64a7 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -354,7 +354,6 @@ 4 2 0 - Normal @@ -371,8 +370,6 @@ - - @@ -420,7 +417,6 @@ - @@ -450,8 +446,6 @@ - - @@ -589,7 +583,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml b/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml index c91e008e02..71ae012289 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml @@ -83,14 +83,14 @@ - - - diff --git a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml index 6f1e4fccfe..1585dada90 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml @@ -28,7 +28,6 @@ - 0,0,0,4 40 40 41 @@ -84,18 +83,8 @@ - - - +