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