committed by
GitHub
33 changed files with 3139 additions and 23 deletions
@ -0,0 +1,29 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
|
xmlns:primitives="clr-namespace:Avalonia.Controls.Primitives;assembly=Avalonia.Controls" |
||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
|
x:Class="ControlCatalog.Pages.ColorPickerPage"> |
||||
|
|
||||
|
<Grid ColumnDefinitions="Auto,Auto" |
||||
|
RowDefinitions="Auto,Auto"> |
||||
|
<ColorSpectrum Grid.Column="0" |
||||
|
Grid.Row="0" |
||||
|
Color="Red" |
||||
|
Height="256" |
||||
|
Width="256" /> |
||||
|
<ColorSpectrum Grid.Column="1" |
||||
|
Grid.Row="0" |
||||
|
Color="Green" |
||||
|
Shape="Ring" |
||||
|
Height="256" |
||||
|
Width="256" /> |
||||
|
<ColorSpectrum Grid.Column="0" |
||||
|
Grid.Row="1" |
||||
|
CornerRadius="10" |
||||
|
Color="Blue" |
||||
|
Height="256" |
||||
|
Width="256" /> |
||||
|
</Grid> |
||||
|
</UserControl> |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public partial class ColorPickerPage : UserControl |
||||
|
{ |
||||
|
public ColorPickerPage() |
||||
|
{ |
||||
|
InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks> |
||||
|
<PackageId>Avalonia.Controls.ColorPicker</PackageId> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<Compile Include="..\Avalonia.Base\Metadata\NullableAttributes.cs" Link="NullableAttributes.cs" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" /> |
||||
|
<ProjectReference Include="..\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj" /> |
||||
|
<ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" /> |
||||
|
<ProjectReference Include="..\Markup\Avalonia.Markup\Avalonia.Markup.csproj" /> |
||||
|
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" /> |
||||
|
<!-- Compatibility with old apps --> |
||||
|
<EmbeddedResource Include="Themes\**\*.xaml" /> |
||||
|
</ItemGroup> |
||||
|
<Import Project="..\..\build\Rx.props" /> |
||||
|
<Import Project="..\..\build\EmbedXaml.props" /> |
||||
|
<Import Project="..\..\build\JetBrains.Annotations.props" /> |
||||
|
<Import Project="..\..\build\BuildTargets.targets" /> |
||||
|
<!--<Import Project="..\..\build\ApiDiff.props" />--> |
||||
|
<Import Project="..\..\build\NullableEnable.props" /> |
||||
|
<Import Project="..\..\build\DevAnalyzers.props" /> |
||||
|
</Project> |
||||
@ -0,0 +1,41 @@ |
|||||
|
// Portions of this source file are adapted from the WinUI project.
|
||||
|
// (https://github.com/microsoft/microsoft-ui-xaml)
|
||||
|
//
|
||||
|
// Licensed to The Avalonia Project under the MIT License.
|
||||
|
|
||||
|
using System; |
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Holds the details of a ColorChanged event.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// HSV color information is intentionally not provided.
|
||||
|
/// Use <see cref="Color.ToHsv()"/> to obtain it.
|
||||
|
/// </remarks>
|
||||
|
public class ColorChangedEventArgs : EventArgs |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ColorChangedEventArgs"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="oldColor">The old/original color from before the change event.</param>
|
||||
|
/// <param name="newColor">The new/updated color that triggered the change event.</param>
|
||||
|
public ColorChangedEventArgs(Color oldColor, Color newColor) |
||||
|
{ |
||||
|
OldColor = oldColor; |
||||
|
NewColor = newColor; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the old/original color from before the change event.
|
||||
|
/// </summary>
|
||||
|
public Color OldColor { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the new/updated color that triggered the change event.
|
||||
|
/// </summary>
|
||||
|
public Color NewColor { get; private set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,414 @@ |
|||||
|
// This source file is adapted from the WinUI project.
|
||||
|
// (https://github.com/microsoft/microsoft-ui-xaml)
|
||||
|
//
|
||||
|
// Licensed to The Avalonia Project under the MIT License.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Media.Imaging; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives |
||||
|
{ |
||||
|
internal static class ColorHelpers |
||||
|
{ |
||||
|
public const int CheckerSize = 4; |
||||
|
|
||||
|
public static bool ToDisplayNameExists |
||||
|
{ |
||||
|
get => false; |
||||
|
} |
||||
|
|
||||
|
public static string ToDisplayName(Color color) |
||||
|
{ |
||||
|
return string.Empty; |
||||
|
} |
||||
|
|
||||
|
public static Hsv IncrementColorComponent( |
||||
|
Hsv originalHsv, |
||||
|
HsvComponent component, |
||||
|
IncrementDirection direction, |
||||
|
IncrementAmount amount, |
||||
|
bool shouldWrap, |
||||
|
double minBound, |
||||
|
double maxBound) |
||||
|
{ |
||||
|
Hsv newHsv = originalHsv; |
||||
|
|
||||
|
if (amount == IncrementAmount.Small || !ToDisplayNameExists) |
||||
|
{ |
||||
|
// In order to avoid working with small values that can incur rounding issues,
|
||||
|
// we'll multiple saturation and value by 100 to put them in the range of 0-100 instead of 0-1.
|
||||
|
newHsv.S *= 100; |
||||
|
newHsv.V *= 100; |
||||
|
|
||||
|
// Note: *valueToIncrement replaced with ref local variable for C#, must be initialized
|
||||
|
ref double valueToIncrement = ref newHsv.H; |
||||
|
double incrementAmount = 0.0; |
||||
|
|
||||
|
// If we're adding a small increment, then we'll just add or subtract 1.
|
||||
|
// If we're adding a large increment, then we want to snap to the next
|
||||
|
// or previous major value - for hue, this is every increment of 30;
|
||||
|
// for saturation and value, this is every increment of 10.
|
||||
|
switch (component) |
||||
|
{ |
||||
|
case HsvComponent.Hue: |
||||
|
valueToIncrement = ref newHsv.H; |
||||
|
incrementAmount = amount == IncrementAmount.Small ? 1 : 30; |
||||
|
break; |
||||
|
|
||||
|
case HsvComponent.Saturation: |
||||
|
valueToIncrement = ref newHsv.S; |
||||
|
incrementAmount = amount == IncrementAmount.Small ? 1 : 10; |
||||
|
break; |
||||
|
|
||||
|
case HsvComponent.Value: |
||||
|
valueToIncrement = ref newHsv.V; |
||||
|
incrementAmount = amount == IncrementAmount.Small ? 1 : 10; |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
throw new InvalidOperationException("Invalid HsvComponent."); |
||||
|
} |
||||
|
|
||||
|
double previousValue = valueToIncrement; |
||||
|
|
||||
|
valueToIncrement += (direction == IncrementDirection.Lower ? -incrementAmount : incrementAmount); |
||||
|
|
||||
|
// If the value has reached outside the bounds, we were previous at the boundary, and we should wrap,
|
||||
|
// then we'll place the selection on the other side of the spectrum.
|
||||
|
// Otherwise, we'll place it on the boundary that was exceeded.
|
||||
|
if (valueToIncrement < minBound) |
||||
|
{ |
||||
|
valueToIncrement = (shouldWrap && previousValue == minBound) ? maxBound : minBound; |
||||
|
} |
||||
|
|
||||
|
if (valueToIncrement > maxBound) |
||||
|
{ |
||||
|
valueToIncrement = (shouldWrap && previousValue == maxBound) ? minBound : maxBound; |
||||
|
} |
||||
|
|
||||
|
// We multiplied saturation and value by 100 previously, so now we want to put them back in the 0-1 range.
|
||||
|
newHsv.S /= 100; |
||||
|
newHsv.V /= 100; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// While working with named colors, we're going to need to be working in actual HSV units,
|
||||
|
// so we'll divide the min bound and max bound by 100 in the case of saturation or value,
|
||||
|
// since we'll have received units between 0-100 and we need them within 0-1.
|
||||
|
if (component == HsvComponent.Saturation || |
||||
|
component == HsvComponent.Value) |
||||
|
{ |
||||
|
minBound /= 100; |
||||
|
maxBound /= 100; |
||||
|
} |
||||
|
|
||||
|
newHsv = FindNextNamedColor(originalHsv, component, direction, shouldWrap, minBound, maxBound); |
||||
|
} |
||||
|
|
||||
|
return newHsv; |
||||
|
} |
||||
|
|
||||
|
public static Hsv FindNextNamedColor( |
||||
|
Hsv originalHsv, |
||||
|
HsvComponent component, |
||||
|
IncrementDirection direction, |
||||
|
bool shouldWrap, |
||||
|
double minBound, |
||||
|
double maxBound) |
||||
|
{ |
||||
|
// There's no easy way to directly get the next named color, so what we'll do
|
||||
|
// is just iterate in the direction that we want to find it until we find a color
|
||||
|
// in that direction that has a color name different than our current color name.
|
||||
|
// Once we find a new color name, then we'll iterate across that color name until
|
||||
|
// we find its bounds on the other side, and then select the color that is exactly
|
||||
|
// in the middle of that color's bounds.
|
||||
|
Hsv newHsv = originalHsv; |
||||
|
|
||||
|
string originalColorName = ColorHelpers.ToDisplayName(originalHsv.ToRgb().ToColor()); |
||||
|
string newColorName = originalColorName; |
||||
|
|
||||
|
// Note: *newValue replaced with ref local variable for C#, must be initialized
|
||||
|
double originalValue = 0.0; |
||||
|
ref double newValue = ref newHsv.H; |
||||
|
double incrementAmount = 0.0; |
||||
|
|
||||
|
switch (component) |
||||
|
{ |
||||
|
case HsvComponent.Hue: |
||||
|
originalValue = originalHsv.H; |
||||
|
newValue = ref newHsv.H; |
||||
|
incrementAmount = 1; |
||||
|
break; |
||||
|
|
||||
|
case HsvComponent.Saturation: |
||||
|
originalValue = originalHsv.S; |
||||
|
newValue = ref newHsv.S; |
||||
|
incrementAmount = 0.01; |
||||
|
break; |
||||
|
|
||||
|
case HsvComponent.Value: |
||||
|
originalValue = originalHsv.V; |
||||
|
newValue = ref newHsv.V; |
||||
|
incrementAmount = 0.01; |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
throw new InvalidOperationException("Invalid HsvComponent."); |
||||
|
} |
||||
|
|
||||
|
bool shouldFindMidPoint = true; |
||||
|
|
||||
|
while (newColorName == originalColorName) |
||||
|
{ |
||||
|
double previousValue = newValue; |
||||
|
newValue += (direction == IncrementDirection.Lower ? -1 : 1) * incrementAmount; |
||||
|
|
||||
|
bool justWrapped = false; |
||||
|
|
||||
|
// If we've hit a boundary, then either we should wrap or we shouldn't.
|
||||
|
// If we should, then we'll perform that wrapping if we were previously up against
|
||||
|
// the boundary that we've now hit. Otherwise, we'll stop at that boundary.
|
||||
|
if (newValue > maxBound) |
||||
|
{ |
||||
|
if (shouldWrap) |
||||
|
{ |
||||
|
newValue = minBound; |
||||
|
justWrapped = true; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
newValue = maxBound; |
||||
|
shouldFindMidPoint = false; |
||||
|
newColorName = ColorHelpers.ToDisplayName(newHsv.ToRgb().ToColor()); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
else if (newValue < minBound) |
||||
|
{ |
||||
|
if (shouldWrap) |
||||
|
{ |
||||
|
newValue = maxBound; |
||||
|
justWrapped = true; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
newValue = minBound; |
||||
|
shouldFindMidPoint = false; |
||||
|
newColorName = ColorHelpers.ToDisplayName(newHsv.ToRgb().ToColor()); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!justWrapped && |
||||
|
previousValue != originalValue && |
||||
|
Math.Sign(newValue - originalValue) != Math.Sign(previousValue - originalValue)) |
||||
|
{ |
||||
|
// If we've wrapped all the way back to the start and have failed to find a new color name,
|
||||
|
// then we'll just quit - there isn't a new color name that we're going to find.
|
||||
|
shouldFindMidPoint = false; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
newColorName = ColorHelpers.ToDisplayName(newHsv.ToRgb().ToColor()); |
||||
|
} |
||||
|
|
||||
|
if (shouldFindMidPoint) |
||||
|
{ |
||||
|
Hsv startHsv = newHsv; |
||||
|
Hsv currentHsv = startHsv; |
||||
|
double startEndOffset = 0; |
||||
|
string currentColorName = newColorName; |
||||
|
|
||||
|
// Note: *startValue/*currentValue replaced with ref local variables for C#, must be initialized
|
||||
|
ref double startValue = ref startHsv.H; |
||||
|
ref double currentValue = ref currentHsv.H; |
||||
|
double wrapIncrement = 0; |
||||
|
|
||||
|
switch (component) |
||||
|
{ |
||||
|
case HsvComponent.Hue: |
||||
|
startValue = ref startHsv.H; |
||||
|
currentValue = ref currentHsv.H; |
||||
|
wrapIncrement = 360.0; |
||||
|
break; |
||||
|
|
||||
|
case HsvComponent.Saturation: |
||||
|
startValue = ref startHsv.S; |
||||
|
currentValue = ref currentHsv.S; |
||||
|
wrapIncrement = 1.0; |
||||
|
break; |
||||
|
|
||||
|
case HsvComponent.Value: |
||||
|
startValue = ref startHsv.V; |
||||
|
currentValue = ref currentHsv.V; |
||||
|
wrapIncrement = 1.0; |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
throw new InvalidOperationException("Invalid HsvComponent."); |
||||
|
} |
||||
|
|
||||
|
while (newColorName == currentColorName) |
||||
|
{ |
||||
|
currentValue += (direction == IncrementDirection.Lower ? -1 : 1) * incrementAmount; |
||||
|
|
||||
|
// If we've hit a boundary, then either we should wrap or we shouldn't.
|
||||
|
// If we should, then we'll perform that wrapping if we were previously up against
|
||||
|
// the boundary that we've now hit. Otherwise, we'll stop at that boundary.
|
||||
|
if (currentValue > maxBound) |
||||
|
{ |
||||
|
if (shouldWrap) |
||||
|
{ |
||||
|
currentValue = minBound; |
||||
|
startEndOffset = maxBound - minBound; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
currentValue = maxBound; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
else if (currentValue < minBound) |
||||
|
{ |
||||
|
if (shouldWrap) |
||||
|
{ |
||||
|
currentValue = maxBound; |
||||
|
startEndOffset = minBound - maxBound; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
currentValue = minBound; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
currentColorName = ColorHelpers.ToDisplayName(currentHsv.ToRgb().ToColor()); |
||||
|
} |
||||
|
|
||||
|
newValue = (startValue + currentValue + startEndOffset) / 2; |
||||
|
|
||||
|
// Dividing by 2 may have gotten us halfway through a single step, so we'll
|
||||
|
// remove that half-step if it exists.
|
||||
|
double leftoverValue = Math.Abs(newValue); |
||||
|
|
||||
|
while (leftoverValue > incrementAmount) |
||||
|
{ |
||||
|
leftoverValue -= incrementAmount; |
||||
|
} |
||||
|
|
||||
|
newValue -= leftoverValue; |
||||
|
|
||||
|
while (newValue < minBound) |
||||
|
{ |
||||
|
newValue += wrapIncrement; |
||||
|
} |
||||
|
|
||||
|
while (newValue > maxBound) |
||||
|
{ |
||||
|
newValue -= wrapIncrement; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return newHsv; |
||||
|
} |
||||
|
|
||||
|
public static double IncrementAlphaComponent( |
||||
|
double originalAlpha, |
||||
|
IncrementDirection direction, |
||||
|
IncrementAmount amount, |
||||
|
bool shouldWrap, |
||||
|
double minBound, |
||||
|
double maxBound) |
||||
|
{ |
||||
|
// In order to avoid working with small values that can incur rounding issues,
|
||||
|
// we'll multiple alpha by 100 to put it in the range of 0-100 instead of 0-1.
|
||||
|
originalAlpha *= 100; |
||||
|
|
||||
|
const double smallIncrementAmount = 1; |
||||
|
const double largeIncrementAmount = 10; |
||||
|
|
||||
|
if (amount == IncrementAmount.Small) |
||||
|
{ |
||||
|
originalAlpha += (direction == IncrementDirection.Lower ? -1 : 1) * smallIncrementAmount; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (direction == IncrementDirection.Lower) |
||||
|
{ |
||||
|
originalAlpha = Math.Ceiling((originalAlpha - largeIncrementAmount) / largeIncrementAmount) * largeIncrementAmount; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
originalAlpha = Math.Floor((originalAlpha + largeIncrementAmount) / largeIncrementAmount) * largeIncrementAmount; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// If the value has reached outside the bounds and we should wrap, then we'll place the selection
|
||||
|
// on the other side of the spectrum. Otherwise, we'll place it on the boundary that was exceeded.
|
||||
|
if (originalAlpha < minBound) |
||||
|
{ |
||||
|
originalAlpha = shouldWrap ? maxBound : minBound; |
||||
|
} |
||||
|
|
||||
|
if (originalAlpha > maxBound) |
||||
|
{ |
||||
|
originalAlpha = shouldWrap ? minBound : maxBound; |
||||
|
} |
||||
|
|
||||
|
// We multiplied alpha by 100 previously, so now we want to put it back in the 0-1 range.
|
||||
|
return originalAlpha / 100; |
||||
|
} |
||||
|
|
||||
|
public static WriteableBitmap CreateBitmapFromPixelData( |
||||
|
int pixelWidth, |
||||
|
int pixelHeight, |
||||
|
List<byte> bgraPixelData) |
||||
|
{ |
||||
|
Vector dpi = new Vector(96, 96); // Standard may need to change on some devices
|
||||
|
|
||||
|
WriteableBitmap bitmap = new WriteableBitmap( |
||||
|
new PixelSize(pixelWidth, pixelHeight), |
||||
|
dpi, |
||||
|
PixelFormat.Bgra8888, |
||||
|
AlphaFormat.Premul); |
||||
|
|
||||
|
// Warning: This is highly questionable
|
||||
|
using (var frameBuffer = bitmap.Lock()) |
||||
|
{ |
||||
|
Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); |
||||
|
} |
||||
|
|
||||
|
return bitmap; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the relative (perceptual) luminance/brightness of the given color.
|
||||
|
/// 1 is closer to white while 0 is closer to black.
|
||||
|
/// </summary>
|
||||
|
/// <param name="color">The color to calculate relative luminance for.</param>
|
||||
|
/// <returns>The relative (perceptual) luminance/brightness of the given color.</returns>
|
||||
|
public static double GetRelativeLuminance(Color color) |
||||
|
{ |
||||
|
// The equation for relative luminance is given by
|
||||
|
//
|
||||
|
// L = 0.2126 * Rg + 0.7152 * Gg + 0.0722 * Bg
|
||||
|
//
|
||||
|
// where Xg = { X/3294 if X <= 10, (R/269 + 0.0513)^2.4 otherwise }
|
||||
|
//
|
||||
|
// If L is closer to 1, then the color is closer to white; if it is closer to 0,
|
||||
|
// then the color is closer to black. This is based on the fact that the human
|
||||
|
// eye perceives green to be much brighter than red, which in turn is perceived to be
|
||||
|
// brighter than blue.
|
||||
|
|
||||
|
double rg = color.R <= 10 ? color.R / 3294.0 : Math.Pow(color.R / 269.0 + 0.0513, 2.4); |
||||
|
double gg = color.G <= 10 ? color.G / 3294.0 : Math.Pow(color.G / 269.0 + 0.0513, 2.4); |
||||
|
double bg = color.B <= 10 ? color.B / 3294.0 : Math.Pow(color.B / 269.0 + 0.0513, 2.4); |
||||
|
|
||||
|
return (0.2126 * rg + 0.7152 * gg + 0.0722 * bg); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,207 @@ |
|||||
|
// This source file is adapted from the WinUI project.
|
||||
|
// (https://github.com/microsoft/microsoft-ui-xaml)
|
||||
|
//
|
||||
|
// Licensed to The Avalonia Project under the MIT License.
|
||||
|
|
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public partial class ColorSpectrum |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets the currently selected color in the RGB color model.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// For control authors use <see cref="HsvColor"/> instead to avoid loss
|
||||
|
/// of precision and color drifting.
|
||||
|
/// </remarks>
|
||||
|
public Color Color |
||||
|
{ |
||||
|
get => GetValue(ColorProperty); |
||||
|
set => SetValue(ColorProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Color"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<Color> ColorProperty = |
||||
|
AvaloniaProperty.Register<ColorSpectrum, Color>( |
||||
|
nameof(Color), |
||||
|
Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the two HSV color components displayed by the spectrum.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Internally, the <see cref="ColorSpectrum"/> uses the HSV color model.
|
||||
|
/// </remarks>
|
||||
|
public ColorSpectrumComponents Components |
||||
|
{ |
||||
|
get => GetValue(ComponentsProperty); |
||||
|
set => SetValue(ComponentsProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Components"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<ColorSpectrumComponents> ComponentsProperty = |
||||
|
AvaloniaProperty.Register<ColorSpectrum, ColorSpectrumComponents>( |
||||
|
nameof(Components), |
||||
|
ColorSpectrumComponents.HueSaturation); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the currently selected color in the HSV color model.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// This should be used in all cases instead of the <see cref="Color"/> property.
|
||||
|
/// Internally, the <see cref="ColorSpectrum"/> uses the HSV color model and using
|
||||
|
/// this property will avoid loss of precision and color drifting.
|
||||
|
/// </remarks>
|
||||
|
public HsvColor HsvColor |
||||
|
{ |
||||
|
get => GetValue(HsvColorProperty); |
||||
|
set => SetValue(HsvColorProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="HsvColor"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<HsvColor> HsvColorProperty = |
||||
|
AvaloniaProperty.Register<ColorSpectrum, HsvColor>( |
||||
|
nameof(HsvColor), |
||||
|
new HsvColor(1, 0, 0, 1)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the maximum value of the Hue component in the range from 0..359.
|
||||
|
/// This property must be greater than <see cref="MinHue"/>.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Internally, the <see cref="ColorSpectrum"/> uses the HSV color model.
|
||||
|
/// </remarks>
|
||||
|
public int MaxHue |
||||
|
{ |
||||
|
get => GetValue(MaxHueProperty); |
||||
|
set => SetValue(MaxHueProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MaxHue"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<int> MaxHueProperty = |
||||
|
AvaloniaProperty.Register<ColorSpectrum, int>(nameof(MaxHue), 359); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the maximum value of the Saturation component in the range from 0..100.
|
||||
|
/// This property must be greater than <see cref="MinSaturation"/>.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Internally, the <see cref="ColorSpectrum"/> uses the HSV color model.
|
||||
|
/// </remarks>
|
||||
|
public int MaxSaturation |
||||
|
{ |
||||
|
get => GetValue(MaxSaturationProperty); |
||||
|
set => SetValue(MaxSaturationProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MaxSaturation"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<int> MaxSaturationProperty = |
||||
|
AvaloniaProperty.Register<ColorSpectrum, int>(nameof(MaxSaturation), 100); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the maximum value of the Value component in the range from 0..100.
|
||||
|
/// This property must be greater than <see cref="MinValue"/>.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Internally, the <see cref="ColorSpectrum"/> uses the HSV color model.
|
||||
|
/// </remarks>
|
||||
|
public int MaxValue |
||||
|
{ |
||||
|
get => GetValue(MaxValueProperty); |
||||
|
set => SetValue(MaxValueProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MaxValue"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<int> MaxValueProperty = |
||||
|
AvaloniaProperty.Register<ColorSpectrum, int>(nameof(MaxValue), 100); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the minimum value of the Hue component in the range from 0..359.
|
||||
|
/// This property must be less than <see cref="MaxHue"/>.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Internally, the <see cref="ColorSpectrum"/> uses the HSV color model.
|
||||
|
/// </remarks>
|
||||
|
public int MinHue |
||||
|
{ |
||||
|
get => GetValue(MinHueProperty); |
||||
|
set => SetValue(MinHueProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MinHue"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<int> MinHueProperty = |
||||
|
AvaloniaProperty.Register<ColorSpectrum, int>(nameof(MinHue), 0); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the minimum value of the Saturation component in the range from 0..100.
|
||||
|
/// This property must be less than <see cref="MaxSaturation"/>.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Internally, the <see cref="ColorSpectrum"/> uses the HSV color model.
|
||||
|
/// </remarks>
|
||||
|
public int MinSaturation |
||||
|
{ |
||||
|
get => GetValue(MinSaturationProperty); |
||||
|
set => SetValue(MinSaturationProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MinSaturation"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<int> MinSaturationProperty = |
||||
|
AvaloniaProperty.Register<ColorSpectrum, int>(nameof(MinSaturation), 0); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the minimum value of the Value component in the range from 0..100.
|
||||
|
/// This property must be less than <see cref="MaxValue"/>.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Internally, the <see cref="ColorSpectrum"/> uses the HSV color model.
|
||||
|
/// </remarks>
|
||||
|
public int MinValue |
||||
|
{ |
||||
|
get => GetValue(MinValueProperty); |
||||
|
set => SetValue(MinValueProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="MinValue"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<int> MinValueProperty = |
||||
|
AvaloniaProperty.Register<ColorSpectrum, int>(nameof(MinValue), 0); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the displayed shape of the spectrum.
|
||||
|
/// </summary>
|
||||
|
public ColorSpectrumShape Shape |
||||
|
{ |
||||
|
get => GetValue(ShapeProperty); |
||||
|
set => SetValue(ShapeProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Shape"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<ColorSpectrumShape> ShapeProperty = |
||||
|
AvaloniaProperty.Register<ColorSpectrum, ColorSpectrumShape>( |
||||
|
nameof(Shape), |
||||
|
ColorSpectrumShape.Box); |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,86 @@ |
|||||
|
// Portions of this source file are adapted from the WinUI project.
|
||||
|
// (https://github.com/microsoft/microsoft-ui-xaml)
|
||||
|
//
|
||||
|
// Licensed to The Avalonia Project under the MIT License.
|
||||
|
|
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Contains and allows modification of Hue, Saturation and Value components.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// The is a specialized struct optimized for permanence and memory:
|
||||
|
/// <list type="bullet">
|
||||
|
/// <item>This is not a read-only struct like <see cref="HsvColor"/> and allows editing the fields</item>
|
||||
|
/// <item>Removes the alpha component unnecessary in core calculations</item>
|
||||
|
/// <item>No component bounds checks or clamping is done.</item>
|
||||
|
/// </list>
|
||||
|
/// </remarks>
|
||||
|
internal struct Hsv |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The Hue component in the range from 0..359.
|
||||
|
/// </summary>
|
||||
|
public double H; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Saturation component in the range from 0..1.
|
||||
|
/// </summary>
|
||||
|
public double S; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Value component in the range from 0..1.
|
||||
|
/// </summary>
|
||||
|
public double V; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Hsv"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="h">The Hue component in the range from 0..360.</param>
|
||||
|
/// <param name="s">The Saturation component in the range from 0..1.</param>
|
||||
|
/// <param name="v">The Value component in the range from 0..1.</param>
|
||||
|
public Hsv(double h, double s, double v) |
||||
|
{ |
||||
|
H = h; |
||||
|
S = s; |
||||
|
V = v; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Hsv"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="hsvColor">An existing <see cref="HsvColor"/> to convert to <see cref="Hsv"/>.</param>
|
||||
|
public Hsv(HsvColor hsvColor) |
||||
|
{ |
||||
|
H = hsvColor.H; |
||||
|
S = hsvColor.S; |
||||
|
V = hsvColor.V; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts this <see cref="Hsv"/> struct into a standard <see cref="HsvColor"/>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="alpha">The Alpha component in the range from 0..1.</param>
|
||||
|
/// <returns>A new <see cref="HsvColor"/> representing this <see cref="Hsv"/> struct.</returns>
|
||||
|
public HsvColor ToHsvColor(double alpha = 1.0) |
||||
|
{ |
||||
|
// Clamping is done automatically in the constructor
|
||||
|
return HsvColor.FromAhsv(alpha, H, S, V); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the <see cref="Rgb"/> color model equivalent of this <see cref="Hsv"/> color.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The <see cref="Rgb"/> equivalent color.</returns>
|
||||
|
public Rgb ToRgb() |
||||
|
{ |
||||
|
// Instantiating a Color is unfortunately necessary to use existing conversions
|
||||
|
// Clamping is done internally in the conversion method
|
||||
|
Color color = HsvColor.ToRgb(H, S, V); |
||||
|
|
||||
|
return new Rgb(color); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// This source file is adapted from the WinUI project.
|
||||
|
// (https://github.com/microsoft/microsoft-ui-xaml)
|
||||
|
//
|
||||
|
// Licensed to The Avalonia Project under the MIT License.
|
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines a relative amount that a color component should be incremented.
|
||||
|
/// </summary>
|
||||
|
internal enum IncrementAmount |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A smaller change in value.
|
||||
|
/// </summary>
|
||||
|
Small, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A larger change in value.
|
||||
|
/// </summary>
|
||||
|
Large, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// This source file is adapted from the WinUI project.
|
||||
|
// (https://github.com/microsoft/microsoft-ui-xaml)
|
||||
|
//
|
||||
|
// Licensed to The Avalonia Project under the MIT License.
|
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the direction a color component should be incremented.
|
||||
|
/// </summary>
|
||||
|
internal enum IncrementDirection |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Decreasing in value towards zero.
|
||||
|
/// </summary>
|
||||
|
Lower, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Increasing in value towards positive infinity.
|
||||
|
/// </summary>
|
||||
|
Higher, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,94 @@ |
|||||
|
// Portions of this source file are adapted from the WinUI project.
|
||||
|
// (https://github.com/microsoft/microsoft-ui-xaml)
|
||||
|
//
|
||||
|
// Licensed to The Avalonia Project under the MIT License.
|
||||
|
|
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Contains and allows modification of Red, Green and Blue components.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// The is a specialized struct optimized for permanence and memory:
|
||||
|
/// <list type="bullet">
|
||||
|
/// <item>This is not a read-only struct like <see cref="Color"/> and allows editing the fields</item>
|
||||
|
/// <item>Removes the alpha component unnecessary in core calculations</item>
|
||||
|
/// <item>Normalizes RGB components in the range of 0..1 to simplify calculations.</item>
|
||||
|
/// <item>No component bounds checks or clamping is done.</item>
|
||||
|
/// </list>
|
||||
|
/// </remarks>
|
||||
|
internal struct Rgb |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The Red component in the range from 0..1.
|
||||
|
/// </summary>
|
||||
|
public double R; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Green component in the range from 0..1.
|
||||
|
/// </summary>
|
||||
|
public double G; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Blue component in the range from 0..1.
|
||||
|
/// </summary>
|
||||
|
public double B; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Rgb"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="r">The Red component in the range from 0..1.</param>
|
||||
|
/// <param name="g">The Green component in the range from 0..1.</param>
|
||||
|
/// <param name="b">The Blue component in the range from 0..1.</param>
|
||||
|
public Rgb(double r, double g, double b) |
||||
|
{ |
||||
|
R = r; |
||||
|
G = g; |
||||
|
B = b; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Rgb"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="color">An existing <see cref="Color"/> to convert to <see cref="Rgb"/>.</param>
|
||||
|
public Rgb(Color color) |
||||
|
{ |
||||
|
R = color.R / 255.0; |
||||
|
G = color.G / 255.0; |
||||
|
B = color.B / 255.0; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts this <see cref="Rgb"/> struct into a standard <see cref="Color"/>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="alpha">The Alpha component in the range from 0..1.</param>
|
||||
|
/// <returns>A new <see cref="Color"/> representing this <see cref="Rgb"/> struct.</returns>
|
||||
|
public Color ToColor(double alpha = 1.0) |
||||
|
{ |
||||
|
return Color.FromArgb( |
||||
|
(byte)MathUtilities.Clamp(alpha * 255.0, 0x00, 0xFF), |
||||
|
(byte)MathUtilities.Clamp(R * 255.0, 0x00, 0xFF), |
||||
|
(byte)MathUtilities.Clamp(G * 255.0, 0x00, 0xFF), |
||||
|
(byte)MathUtilities.Clamp(B * 255.0, 0x00, 0xFF)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the <see cref="Hsv"/> color model equivalent of this <see cref="Rgb"/> color.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The <see cref="Hsv"/> equivalent color.</returns>
|
||||
|
public Hsv ToHsv() |
||||
|
{ |
||||
|
// Instantiating an HsvColor is unfortunately necessary to use existing conversions
|
||||
|
// Clamping must be done here as it isn't done in the conversion method (internal-use only)
|
||||
|
HsvColor hsvColor = Color.ToHsv( |
||||
|
MathUtilities.Clamp(R, 0.0, 1.0), |
||||
|
MathUtilities.Clamp(G, 0.0, 1.0), |
||||
|
MathUtilities.Clamp(B, 0.0, 1.0)); |
||||
|
|
||||
|
return new Hsv(hsvColor); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
// This source file is adapted from the WinUI project.
|
||||
|
// (https://github.com/microsoft/microsoft-ui-xaml)
|
||||
|
//
|
||||
|
// Licensed to The Avalonia Project under the MIT License.
|
||||
|
|
||||
|
using Avalonia.Controls.Primitives; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the two HSV color components displayed by a <see cref="ColorSpectrum"/>.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Order of the color components is important and correspond with an X/Y axis in Box
|
||||
|
/// shape or a degree/radius in Ring shape.
|
||||
|
/// </remarks>
|
||||
|
public enum ColorSpectrumComponents |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The Hue and Value components.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// In Box shape, Hue is mapped to the X-axis and Value is mapped to the Y-axis.
|
||||
|
/// In Ring shape, Hue is mapped to degrees and Value is mapped to radius.
|
||||
|
/// </remarks>
|
||||
|
HueValue, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Value and Hue components.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// In Box shape, Value is mapped to the X-axis and Hue is mapped to the Y-axis.
|
||||
|
/// In Ring shape, Value is mapped to degrees and Hue is mapped to radius.
|
||||
|
/// </remarks>
|
||||
|
ValueHue, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Hue and Saturation components.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// In Box shape, Hue is mapped to the X-axis and Saturation is mapped to the Y-axis.
|
||||
|
/// In Ring shape, Hue is mapped to degrees and Saturation is mapped to radius.
|
||||
|
/// </remarks>
|
||||
|
HueSaturation, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Saturation and Hue components.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// In Box shape, Saturation is mapped to the X-axis and Hue is mapped to the Y-axis.
|
||||
|
/// In Ring shape, Saturation is mapped to degrees and Hue is mapped to radius.
|
||||
|
/// </remarks>
|
||||
|
SaturationHue, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Saturation and Value components.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// In Box shape, Saturation is mapped to the X-axis and Value is mapped to the Y-axis.
|
||||
|
/// In Ring shape, Saturation is mapped to degrees and Value is mapped to radius.
|
||||
|
/// </remarks>
|
||||
|
SaturationValue, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Value and Saturation components.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// In Box shape, Value is mapped to the X-axis and Saturation is mapped to the Y-axis.
|
||||
|
/// In Ring shape, Value is mapped to degrees and Saturation is mapped to radius.
|
||||
|
/// </remarks>
|
||||
|
ValueSaturation, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// This source file is adapted from the WinUI project.
|
||||
|
// (https://github.com/microsoft/microsoft-ui-xaml)
|
||||
|
//
|
||||
|
// Licensed to The Avalonia Project under the MIT License.
|
||||
|
|
||||
|
using Avalonia.Controls.Primitives; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the shape of a <see cref="ColorSpectrum"/>.
|
||||
|
/// </summary>
|
||||
|
public enum ColorSpectrumShape |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The spectrum is in the shape of a rectangular or square box.
|
||||
|
/// Note that more colors are visible to the user in Box shape.
|
||||
|
/// </summary>
|
||||
|
Box, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The spectrum is in the shape of an ellipse or circle.
|
||||
|
/// </summary>
|
||||
|
Ring, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// This source file is adapted from the WinUI project.
|
||||
|
// (https://github.com/microsoft/microsoft-ui-xaml)
|
||||
|
//
|
||||
|
// Licensed to The Avalonia Project under the MIT License.
|
||||
|
|
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines a specific component in the HSV color model.
|
||||
|
/// </summary>
|
||||
|
public enum HsvComponent |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The Hue component.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Also see: <see cref="HsvColor.H"/>
|
||||
|
/// </remarks>
|
||||
|
Hue, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Saturation component.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Also see: <see cref="HsvColor.S"/>
|
||||
|
/// </remarks>
|
||||
|
Saturation, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Value component.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Also see: <see cref="HsvColor.V"/>
|
||||
|
/// </remarks>
|
||||
|
Value, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Alpha component.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Also see: <see cref="HsvColor.A"/>
|
||||
|
/// </remarks>
|
||||
|
Alpha |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System.Runtime.CompilerServices; |
||||
|
using Avalonia.Metadata; |
||||
|
|
||||
|
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] |
||||
|
|
||||
|
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] |
||||
|
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Collections")] |
||||
|
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Primitives")] |
||||
@ -0,0 +1,134 @@ |
|||||
|
<Styles xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:CompileBindings="True" |
||||
|
xmlns:converters="using:Avalonia.Controls.Converters"> |
||||
|
|
||||
|
<Styles.Resources> |
||||
|
<converters:EnumValueEqualsConverter x:Key="EnumValueEquals" /> |
||||
|
<converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadius" Corner="TopLeft" /> |
||||
|
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadius" Corner="BottomRight" /> |
||||
|
</Styles.Resources> |
||||
|
|
||||
|
<Style Selector="ColorSpectrum"> |
||||
|
<Setter Property="Template"> |
||||
|
<Setter.Value> |
||||
|
<ControlTemplate> |
||||
|
<Panel x:Name="PART_LayoutRoot" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch"> |
||||
|
<Panel x:Name="PART_SizingPanel" |
||||
|
HorizontalAlignment="Center" |
||||
|
VerticalAlignment="Center" |
||||
|
ClipToBounds="True"> |
||||
|
<Rectangle x:Name="PART_SpectrumRectangle" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" |
||||
|
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
||||
|
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
||||
|
<Rectangle x:Name="PART_SpectrumOverlayRectangle" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" |
||||
|
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
||||
|
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
||||
|
<Ellipse x:Name="PART_SpectrumEllipse" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Ring'}" /> |
||||
|
<Ellipse x:Name="PART_SpectrumOverlayEllipse" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Ring'}" /> |
||||
|
<Canvas x:Name="PART_InputTarget" |
||||
|
Background="Transparent" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch"> |
||||
|
<Panel x:Name="PART_SelectionEllipsePanel"> |
||||
|
<Ellipse x:Name="FocusEllipse" |
||||
|
Margin="-2" |
||||
|
StrokeThickness="2" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" /> |
||||
|
<Ellipse x:Name="SelectionEllipse" |
||||
|
StrokeThickness="2" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
ToolTip.VerticalOffset="-20" |
||||
|
ToolTip.Placement="Top"> |
||||
|
<ToolTip.Tip> |
||||
|
<ToolTip x:Name="PART_ColorNameToolTip" /> |
||||
|
</ToolTip.Tip> |
||||
|
</Ellipse> |
||||
|
</Panel> |
||||
|
</Canvas> |
||||
|
<Rectangle x:Name="BorderRectangle" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" |
||||
|
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
||||
|
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
||||
|
<Ellipse x:Name="BorderEllipse" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Ring'}" /> |
||||
|
</Panel> |
||||
|
</Panel> |
||||
|
</ControlTemplate> |
||||
|
</Setter.Value> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Normal --> |
||||
|
<!-- Separating this allows easier customization in applications --> |
||||
|
<Style Selector="ColorSpectrum /template/ Ellipse#BorderEllipse, |
||||
|
ColorSpectrum /template/ Rectangle#BorderRectangle"> |
||||
|
<Setter Property="Stroke" Value="{DynamicResource ThemeBorderLowBrush}" /> |
||||
|
<Setter Property="StrokeThickness" Value="1" /> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Focus --> |
||||
|
<Style Selector="ColorSpectrum /template/ Ellipse#FocusEllipse"> |
||||
|
<Setter Property="IsVisible" Value="False" /> |
||||
|
</Style> |
||||
|
<Style Selector="ColorSpectrum:focus-visible /template/ Ellipse#FocusEllipse"> |
||||
|
<Setter Property="IsVisible" Value="True" /> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Selector Color --> |
||||
|
<Style Selector="ColorSpectrum /template/ Ellipse#FocusEllipse"> |
||||
|
<Setter Property="Stroke" Value="White" /> |
||||
|
</Style> |
||||
|
<Style Selector="ColorSpectrum /template/ Ellipse#SelectionEllipse"> |
||||
|
<Setter Property="Stroke" Value="Black" /> |
||||
|
</Style> |
||||
|
<Style Selector="ColorSpectrum:light-selector /template/ Ellipse#FocusEllipse"> |
||||
|
<Setter Property="Stroke" Value="Black" /> |
||||
|
</Style> |
||||
|
<Style Selector="ColorSpectrum:light-selector /template/ Ellipse#SelectionEllipse"> |
||||
|
<Setter Property="Stroke" Value="White" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="ColorSpectrum:pointerover /template/ Ellipse#SelectionEllipse"> |
||||
|
<Setter Property="Opacity" Value="0.8" /> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Selector Size --> |
||||
|
<Style Selector="ColorSpectrum /template/ Panel#PART_SelectionEllipsePanel"> |
||||
|
<Setter Property="Width" Value="16" /> |
||||
|
<Setter Property="Height" Value="16" /> |
||||
|
</Style> |
||||
|
<Style Selector="ColorSpectrum:large-selector /template/ Panel#PART_SelectionEllipsePanel"> |
||||
|
<Setter Property="Width" Value="48" /> |
||||
|
<Setter Property="Height" Value="48" /> |
||||
|
</Style> |
||||
|
|
||||
|
</Styles> |
||||
@ -0,0 +1,134 @@ |
|||||
|
<Styles xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:CompileBindings="True" |
||||
|
xmlns:converters="using:Avalonia.Controls.Converters"> |
||||
|
|
||||
|
<Styles.Resources> |
||||
|
<converters:EnumValueEqualsConverter x:Key="EnumValueEquals" /> |
||||
|
<converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadius" Corner="TopLeft" /> |
||||
|
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadius" Corner="BottomRight" /> |
||||
|
</Styles.Resources> |
||||
|
|
||||
|
<Style Selector="ColorSpectrum"> |
||||
|
<Setter Property="Template"> |
||||
|
<Setter.Value> |
||||
|
<ControlTemplate> |
||||
|
<Panel x:Name="PART_LayoutRoot" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch"> |
||||
|
<Panel x:Name="PART_SizingPanel" |
||||
|
HorizontalAlignment="Center" |
||||
|
VerticalAlignment="Center" |
||||
|
ClipToBounds="True"> |
||||
|
<Rectangle x:Name="PART_SpectrumRectangle" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" |
||||
|
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
||||
|
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
||||
|
<Rectangle x:Name="PART_SpectrumOverlayRectangle" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" |
||||
|
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
||||
|
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
||||
|
<Ellipse x:Name="PART_SpectrumEllipse" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Ring'}" /> |
||||
|
<Ellipse x:Name="PART_SpectrumOverlayEllipse" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Ring'}" /> |
||||
|
<Canvas x:Name="PART_InputTarget" |
||||
|
Background="Transparent" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch"> |
||||
|
<Panel x:Name="PART_SelectionEllipsePanel"> |
||||
|
<Ellipse x:Name="FocusEllipse" |
||||
|
Margin="-2" |
||||
|
StrokeThickness="2" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" /> |
||||
|
<Ellipse x:Name="SelectionEllipse" |
||||
|
StrokeThickness="2" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
ToolTip.VerticalOffset="-20" |
||||
|
ToolTip.Placement="Top"> |
||||
|
<ToolTip.Tip> |
||||
|
<ToolTip x:Name="PART_ColorNameToolTip" /> |
||||
|
</ToolTip.Tip> |
||||
|
</Ellipse> |
||||
|
</Panel> |
||||
|
</Canvas> |
||||
|
<Rectangle x:Name="BorderRectangle" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" |
||||
|
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
||||
|
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
||||
|
<Ellipse x:Name="BorderEllipse" |
||||
|
IsHitTestVisible="False" |
||||
|
HorizontalAlignment="Stretch" |
||||
|
VerticalAlignment="Stretch" |
||||
|
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Ring'}" /> |
||||
|
</Panel> |
||||
|
</Panel> |
||||
|
</ControlTemplate> |
||||
|
</Setter.Value> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Normal --> |
||||
|
<!-- Separating this allows easier customization in applications --> |
||||
|
<Style Selector="ColorSpectrum /template/ Ellipse#BorderEllipse, |
||||
|
ColorSpectrum /template/ Rectangle#BorderRectangle"> |
||||
|
<Setter Property="Stroke" Value="{DynamicResource SystemControlForegroundListLowBrush}" /> |
||||
|
<Setter Property="StrokeThickness" Value="1" /> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Focus --> |
||||
|
<Style Selector="ColorSpectrum /template/ Ellipse#FocusEllipse"> |
||||
|
<Setter Property="IsVisible" Value="False" /> |
||||
|
</Style> |
||||
|
<Style Selector="ColorSpectrum:focus-visible /template/ Ellipse#FocusEllipse"> |
||||
|
<Setter Property="IsVisible" Value="True" /> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Selector Color --> |
||||
|
<Style Selector="ColorSpectrum /template/ Ellipse#FocusEllipse"> |
||||
|
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" /> |
||||
|
</Style> |
||||
|
<Style Selector="ColorSpectrum /template/ Ellipse#SelectionEllipse"> |
||||
|
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" /> |
||||
|
</Style> |
||||
|
<Style Selector="ColorSpectrum:light-selector /template/ Ellipse#FocusEllipse"> |
||||
|
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" /> |
||||
|
</Style> |
||||
|
<Style Selector="ColorSpectrum:light-selector /template/ Ellipse#SelectionEllipse"> |
||||
|
<Setter Property="Stroke" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="ColorSpectrum:pointerover /template/ Ellipse#SelectionEllipse"> |
||||
|
<Setter Property="Opacity" Value="0.8" /> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Selector Size --> |
||||
|
<Style Selector="ColorSpectrum /template/ Panel#PART_SelectionEllipsePanel"> |
||||
|
<Setter Property="Width" Value="16" /> |
||||
|
<Setter Property="Height" Value="16" /> |
||||
|
</Style> |
||||
|
<Style Selector="ColorSpectrum:large-selector /template/ Panel#PART_SelectionEllipsePanel"> |
||||
|
<Setter Property="Width" Value="48" /> |
||||
|
<Setter Property="Height" Value="48" /> |
||||
|
</Style> |
||||
|
|
||||
|
</Styles> |
||||
@ -0,0 +1,46 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Data.Converters; |
||||
|
|
||||
|
namespace Avalonia.Controls.Converters |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Converts one corner of a <see cref="CornerRadius"/> to its double value.
|
||||
|
/// </summary>
|
||||
|
public class CornerRadiusToDoubleConverter : IValueConverter |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets the specific corner of the <see cref="CornerRadius"/> to convert to double.
|
||||
|
/// </summary>
|
||||
|
public Corners Corner { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||
|
{ |
||||
|
if (!(value is CornerRadius cornerRadius)) |
||||
|
{ |
||||
|
return AvaloniaProperty.UnsetValue; |
||||
|
} |
||||
|
|
||||
|
switch (Corner) |
||||
|
{ |
||||
|
case Corners.TopLeft: |
||||
|
return cornerRadius.TopLeft; |
||||
|
case Corners.TopRight: |
||||
|
return cornerRadius.TopRight; |
||||
|
case Corners.BottomRight: |
||||
|
return cornerRadius.BottomRight; |
||||
|
case Corners.BottomLeft: |
||||
|
return cornerRadius.BottomLeft; |
||||
|
default: |
||||
|
return 0.0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Data.Converters; |
||||
|
|
||||
|
namespace Avalonia.Controls.Converters |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Converter that checks if an enum value is equal to the given parameter enum value.
|
||||
|
/// </summary>
|
||||
|
public class EnumValueEqualsConverter : IValueConverter |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||
|
{ |
||||
|
// Note: Unlike string comparisons, null/empty is not supported
|
||||
|
// Both 'value' and 'parameter' must exist and if both are missing they are not considered equal
|
||||
|
if (value != null && |
||||
|
parameter != null) |
||||
|
{ |
||||
|
Type type = value.GetType(); |
||||
|
|
||||
|
if (type.IsEnum) |
||||
|
{ |
||||
|
var valueStr = value?.ToString(); |
||||
|
var paramStr = parameter?.ToString(); |
||||
|
|
||||
|
if (string.Equals(valueStr, paramStr, StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
// TODO: When .net Standard 2.0 is no longer supported the code can be changed to below
|
||||
|
// This is a little more type safe
|
||||
|
if (type.IsEnum && |
||||
|
Enum.TryParse(type, value?.ToString(), true, out object? valueEnum) && |
||||
|
Enum.TryParse(type, parameter?.ToString(), true, out object? paramEnum)) |
||||
|
{ |
||||
|
return valueEnum == paramEnum; |
||||
|
} |
||||
|
*/ |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||
|
{ |
||||
|
throw new System.NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue