committed by
GitHub
85 changed files with 6262 additions and 409 deletions
@ -0,0 +1,79 @@ |
|||
<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" |
|||
xmlns:pc="clr-namespace:Avalonia.Controls.Primitives.Converters;assembly=Avalonia.Controls.ColorPicker" |
|||
mc:Ignorable="d" |
|||
d:DesignWidth="800" |
|||
d:DesignHeight="450" |
|||
x:Class="ControlCatalog.Pages.ColorPickerPage"> |
|||
|
|||
<UserControl.Resources> |
|||
<pc:ThirdComponentConverter x:Key="ThirdComponent" /> |
|||
</UserControl.Resources> |
|||
|
|||
<Grid ColumnDefinitions="Auto,10,Auto"> |
|||
<Grid Grid.Column="0" |
|||
Grid.Row="0" |
|||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto"> |
|||
<ColorSpectrum x:Name="ColorSpectrum1" |
|||
Grid.Row="0" |
|||
Color="Red" |
|||
CornerRadius="10" |
|||
Height="256" |
|||
Width="256" /> |
|||
<ColorSlider Grid.Row="1" |
|||
Margin="0,10,0,0" |
|||
ColorComponent="Component1" |
|||
ColorModel="Hsva" |
|||
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" /> |
|||
<ColorSlider Grid.Row="2" |
|||
ColorComponent="Component2" |
|||
ColorModel="Hsva" |
|||
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" /> |
|||
<ColorSlider Grid.Row="3" |
|||
ColorComponent="Component3" |
|||
ColorModel="Hsva" |
|||
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" /> |
|||
<ColorSlider Grid.Row="4" |
|||
ColorComponent="Alpha" |
|||
ColorModel="Hsva" |
|||
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" /> |
|||
<ColorPreviewer Grid.Row="5" |
|||
ShowAccentColors="True" |
|||
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" /> |
|||
</Grid> |
|||
<Grid Grid.Column="2" |
|||
Grid.Row="0" |
|||
ColumnDefinitions="Auto,Auto,Auto" |
|||
RowDefinitions="Auto,Auto"> |
|||
<ColorSlider Grid.Column="0" |
|||
Grid.Row="0" |
|||
IsAlphaMaxForced="True" |
|||
IsSaturationValueMaxForced="False" |
|||
ColorComponent="{Binding Components, ElementName=ColorSpectrum2, Converter={StaticResource ThirdComponent}}" |
|||
ColorModel="Hsva" |
|||
Orientation="Vertical" |
|||
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum2}" /> |
|||
<ColorSpectrum x:Name="ColorSpectrum2" |
|||
Grid.Column="1" |
|||
Grid.Row="0" |
|||
Color="Green" |
|||
Shape="Ring" |
|||
Height="256" |
|||
Width="256" /> |
|||
<ColorSlider Grid.Column="2" |
|||
Grid.Row="0" |
|||
ColorComponent="Alpha" |
|||
ColorModel="Hsva" |
|||
Orientation="Vertical" |
|||
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum2}" /> |
|||
<ColorPreviewer Grid.Column="0" |
|||
Grid.ColumnSpan="3" |
|||
Grid.Row="1" |
|||
ShowAccentColors="True" |
|||
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum2}" /> |
|||
</Grid> |
|||
</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,28 @@ |
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a specific component within a color model.
|
|||
/// </summary>
|
|||
public enum ColorComponent |
|||
{ |
|||
/// <summary>
|
|||
/// Represents the alpha component.
|
|||
/// </summary>
|
|||
Alpha = 0, |
|||
|
|||
/// <summary>
|
|||
/// Represents the first color component which is Red when RGB or Hue when HSV.
|
|||
/// </summary>
|
|||
Component1 = 1, |
|||
|
|||
/// <summary>
|
|||
/// Represents the second color component which is Green when RGB or Saturation when HSV.
|
|||
/// </summary>
|
|||
Component2 = 2, |
|||
|
|||
/// <summary>
|
|||
/// Represents the third color component which is Blue when RGB or Value when HSV.
|
|||
/// </summary>
|
|||
Component3 = 3 |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the model used to represent colors.
|
|||
/// </summary>
|
|||
public enum ColorModel |
|||
{ |
|||
/// <summary>
|
|||
/// Color is represented by hue, saturation, value and alpha components.
|
|||
/// </summary>
|
|||
Hsva, |
|||
|
|||
/// <summary>
|
|||
/// Color is represented by red, green, blue and alpha components.
|
|||
/// </summary>
|
|||
Rgba |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using Avalonia.Data; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public partial class ColorPreviewer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="HsvColor"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<HsvColor> HsvColorProperty = |
|||
AvaloniaProperty.Register<ColorPreviewer, HsvColor>( |
|||
nameof(HsvColor), |
|||
Colors.Transparent.ToHsv(), |
|||
defaultBindingMode: BindingMode.TwoWay); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ShowAccentColors"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> ShowAccentColorsProperty = |
|||
AvaloniaProperty.Register<ColorPreviewer, bool>( |
|||
nameof(ShowAccentColors), |
|||
true); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the currently previewed color in the HSV color model.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Only an HSV color is supported in this control to ensure there is never any
|
|||
/// loss of precision or color information. Accent colors, like the color spectrum,
|
|||
/// only operate with the HSV color model.
|
|||
/// </remarks>
|
|||
public HsvColor HsvColor |
|||
{ |
|||
get => GetValue(HsvColorProperty); |
|||
set => SetValue(HsvColorProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether accent colors are shown along
|
|||
/// with the preview color.
|
|||
/// </summary>
|
|||
public bool ShowAccentColors |
|||
{ |
|||
get => GetValue(ShowAccentColorsProperty); |
|||
set => SetValue(ShowAccentColorsProperty, value); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,130 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using Avalonia.Controls.Metadata; |
|||
using Avalonia.Controls.Primitives.Converters; |
|||
using Avalonia.Input; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Presents a preview color with optional accent colors.
|
|||
/// </summary>
|
|||
[TemplatePart(Name = nameof(AccentDec1Border), Type = typeof(Border))] |
|||
[TemplatePart(Name = nameof(AccentDec2Border), Type = typeof(Border))] |
|||
[TemplatePart(Name = nameof(AccentInc1Border), Type = typeof(Border))] |
|||
[TemplatePart(Name = nameof(AccentInc2Border), Type = typeof(Border))] |
|||
public partial class ColorPreviewer : TemplatedControl |
|||
{ |
|||
/// <summary>
|
|||
/// Event for when the selected color changes within the previewer.
|
|||
/// This occurs when an accent color is pressed.
|
|||
/// </summary>
|
|||
public event EventHandler<ColorChangedEventArgs>? ColorChanged; |
|||
|
|||
private bool eventsConnected = false; |
|||
|
|||
private Border? AccentDec1Border; |
|||
private Border? AccentDec2Border; |
|||
private Border? AccentInc1Border; |
|||
private Border? AccentInc2Border; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ColorPreviewer"/> class.
|
|||
/// </summary>
|
|||
public ColorPreviewer() : base() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Connects or disconnects all control event handlers.
|
|||
/// </summary>
|
|||
/// <param name="connected">True to connect event handlers, otherwise false.</param>
|
|||
private void ConnectEvents(bool connected) |
|||
{ |
|||
if (connected == true && eventsConnected == false) |
|||
{ |
|||
// Add all events
|
|||
if (AccentDec1Border != null) { AccentDec1Border.PointerPressed += AccentBorder_PointerPressed; } |
|||
if (AccentDec2Border != null) { AccentDec2Border.PointerPressed += AccentBorder_PointerPressed; } |
|||
if (AccentInc1Border != null) { AccentInc1Border.PointerPressed += AccentBorder_PointerPressed; } |
|||
if (AccentInc2Border != null) { AccentInc2Border.PointerPressed += AccentBorder_PointerPressed; } |
|||
|
|||
eventsConnected = true; |
|||
} |
|||
else if (connected == false && eventsConnected == true) |
|||
{ |
|||
// Remove all events
|
|||
if (AccentDec1Border != null) { AccentDec1Border.PointerPressed -= AccentBorder_PointerPressed; } |
|||
if (AccentDec2Border != null) { AccentDec2Border.PointerPressed -= AccentBorder_PointerPressed; } |
|||
if (AccentInc1Border != null) { AccentInc1Border.PointerPressed -= AccentBorder_PointerPressed; } |
|||
if (AccentInc2Border != null) { AccentInc2Border.PointerPressed -= AccentBorder_PointerPressed; } |
|||
|
|||
eventsConnected = false; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
// Remove any existing events present if the control was previously loaded then unloaded
|
|||
ConnectEvents(false); |
|||
|
|||
AccentDec1Border = e.NameScope.Find<Border>(nameof(AccentDec1Border)); |
|||
AccentDec2Border = e.NameScope.Find<Border>(nameof(AccentDec2Border)); |
|||
AccentInc1Border = e.NameScope.Find<Border>(nameof(AccentInc1Border)); |
|||
AccentInc2Border = e.NameScope.Find<Border>(nameof(AccentInc2Border)); |
|||
|
|||
// Must connect after controls are found
|
|||
ConnectEvents(true); |
|||
|
|||
base.OnApplyTemplate(e); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
|||
{ |
|||
if (change.Property == HsvColorProperty) |
|||
{ |
|||
OnColorChanged(new ColorChangedEventArgs( |
|||
change.GetOldValue<HsvColor>().ToRgb(), |
|||
change.GetNewValue<HsvColor>().ToRgb())); |
|||
} |
|||
|
|||
base.OnPropertyChanged(change); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called before the <see cref="ColorChanged"/> event occurs.
|
|||
/// </summary>
|
|||
/// <param name="e">The <see cref="ColorChangedEventArgs"/> defining old/new colors.</param>
|
|||
protected virtual void OnColorChanged(ColorChangedEventArgs e) |
|||
{ |
|||
ColorChanged?.Invoke(this, e); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Event handler for when an accent color border is pressed.
|
|||
/// This will update the color to the background of the pressed panel.
|
|||
/// </summary>
|
|||
private void AccentBorder_PointerPressed(object? sender, PointerPressedEventArgs e) |
|||
{ |
|||
Border? border = sender as Border; |
|||
int accentStep = 0; |
|||
HsvColor hsvColor = HsvColor; |
|||
|
|||
// Get the value component delta
|
|||
try |
|||
{ |
|||
accentStep = int.Parse(border?.Tag?.ToString() ?? "", CultureInfo.InvariantCulture); |
|||
} |
|||
catch { } |
|||
|
|||
HsvColor newHsvColor = AccentColorConverter.GetAccent(hsvColor, accentStep); |
|||
HsvColor oldHsvColor = HsvColor; |
|||
|
|||
HsvColor = newHsvColor; |
|||
OnColorChanged(new ColorChangedEventArgs(oldHsvColor.ToRgb(), newHsvColor.ToRgb())); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,146 @@ |
|||
using Avalonia.Data; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public partial class ColorSlider |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Color"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<Color> ColorProperty = |
|||
AvaloniaProperty.Register<ColorSlider, Color>( |
|||
nameof(Color), |
|||
Colors.White, |
|||
defaultBindingMode: BindingMode.TwoWay); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ColorComponent"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<ColorComponent> ColorComponentProperty = |
|||
AvaloniaProperty.Register<ColorSlider, ColorComponent>( |
|||
nameof(ColorComponent), |
|||
ColorComponent.Component1); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ColorModel"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<ColorModel> ColorModelProperty = |
|||
AvaloniaProperty.Register<ColorSlider, ColorModel>( |
|||
nameof(ColorModel), |
|||
ColorModel.Rgba); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="HsvColor"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<HsvColor> HsvColorProperty = |
|||
AvaloniaProperty.Register<ColorSlider, HsvColor>( |
|||
nameof(HsvColor), |
|||
Colors.White.ToHsv(), |
|||
defaultBindingMode: BindingMode.TwoWay); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="IsAlphaMaxForced"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> IsAlphaMaxForcedProperty = |
|||
AvaloniaProperty.Register<ColorSlider, bool>( |
|||
nameof(IsAlphaMaxForced), |
|||
true); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="IsAutoUpdatingEnabled"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> IsAutoUpdatingEnabledProperty = |
|||
AvaloniaProperty.Register<ColorSlider, bool>( |
|||
nameof(IsAutoUpdatingEnabled), |
|||
true); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="IsSaturationValueMaxForced"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> IsSaturationValueMaxForcedProperty = |
|||
AvaloniaProperty.Register<ColorSlider, bool>( |
|||
nameof(IsSaturationValueMaxForced), |
|||
true); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the currently selected color in the RGB color model.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Use this property instead of <see cref="HsvColor"/> when in <see cref="ColorModel.Rgba"/>
|
|||
/// to avoid loss of precision and color drifting.
|
|||
/// </remarks>
|
|||
public Color Color |
|||
{ |
|||
get => GetValue(ColorProperty); |
|||
set => SetValue(ColorProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the color component represented by the slider.
|
|||
/// </summary>
|
|||
public ColorComponent ColorComponent |
|||
{ |
|||
get => GetValue(ColorComponentProperty); |
|||
set => SetValue(ColorComponentProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the active color model used by the slider.
|
|||
/// </summary>
|
|||
public ColorModel ColorModel |
|||
{ |
|||
get => GetValue(ColorModelProperty); |
|||
set => SetValue(ColorModelProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the currently selected color in the HSV color model.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Use this property instead of <see cref="Color"/> when in <see cref="ColorModel.Hsva"/>
|
|||
/// to avoid loss of precision and color drifting.
|
|||
/// </remarks>
|
|||
public HsvColor HsvColor |
|||
{ |
|||
get => GetValue(HsvColorProperty); |
|||
set => SetValue(HsvColorProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the alpha component is always forced to maximum for components
|
|||
/// other than <see cref="ColorComponent"/>.
|
|||
/// This ensures that the background is always visible and never transparent regardless of the actual color.
|
|||
/// </summary>
|
|||
public bool IsAlphaMaxForced |
|||
{ |
|||
get => GetValue(IsAlphaMaxForcedProperty); |
|||
set => SetValue(IsAlphaMaxForcedProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether automatic background and foreground updates will be
|
|||
/// calculated when the set color changes.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This can be disabled for performance reasons when working with multiple sliders.
|
|||
/// </remarks>
|
|||
public bool IsAutoUpdatingEnabled |
|||
{ |
|||
get => GetValue(IsAutoUpdatingEnabledProperty); |
|||
set => SetValue(IsAutoUpdatingEnabledProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the saturation and value components are always forced to maximum values
|
|||
/// when using the HSVA color model. Only component values other than <see cref="ColorComponent"/> will be changed.
|
|||
/// This ensures, for example, that the Hue background is always visible and never washed out regardless of the actual color.
|
|||
/// </summary>
|
|||
public bool IsSaturationValueMaxForced |
|||
{ |
|||
get => GetValue(IsSaturationValueMaxForcedProperty); |
|||
set => SetValue(IsSaturationValueMaxForcedProperty, value); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,399 @@ |
|||
using System; |
|||
using Avalonia.Controls.Metadata; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Media; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// A slider with a background that represents a single color component.
|
|||
/// </summary>
|
|||
[PseudoClasses(pcDarkSelector, pcLightSelector)] |
|||
public partial class ColorSlider : Slider |
|||
{ |
|||
protected const string pcDarkSelector = ":dark-selector"; |
|||
protected const string pcLightSelector = ":light-selector"; |
|||
|
|||
/// <summary>
|
|||
/// Event for when the selected color changes within the slider.
|
|||
/// </summary>
|
|||
public event EventHandler<ColorChangedEventArgs>? ColorChanged; |
|||
|
|||
private const double MaxHue = 359.99999999999999999; // 17 decimal places
|
|||
private bool disableUpdates = false; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ColorSlider"/> class.
|
|||
/// </summary>
|
|||
public ColorSlider() : base() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Updates the visual state of the control by applying latest PseudoClasses.
|
|||
/// </summary>
|
|||
private void UpdatePseudoClasses() |
|||
{ |
|||
// The slider itself can be transparent for certain color values.
|
|||
// This causes an issue where a white selector thumb over a light window background or
|
|||
// a black selector thumb over a dark window background is not visible.
|
|||
// This means under a certain alpha threshold, neither a white or black selector thumb
|
|||
// should be shown and instead the default slider thumb color should be used instead.
|
|||
if (Color.A < 128 && |
|||
(IsAlphaMaxForced == false || |
|||
ColorComponent == ColorComponent.Alpha)) |
|||
{ |
|||
PseudoClasses.Set(pcDarkSelector, false); |
|||
PseudoClasses.Set(pcLightSelector, false); |
|||
} |
|||
else |
|||
{ |
|||
Color perceivedColor; |
|||
|
|||
if (ColorModel == ColorModel.Hsva) |
|||
{ |
|||
perceivedColor = GetEquivalentBackgroundColor(HsvColor).ToRgb(); |
|||
} |
|||
else |
|||
{ |
|||
perceivedColor = GetEquivalentBackgroundColor(Color); |
|||
} |
|||
|
|||
if (ColorHelper.GetRelativeLuminance(perceivedColor) <= 0.5) |
|||
{ |
|||
PseudoClasses.Set(pcDarkSelector, false); |
|||
PseudoClasses.Set(pcLightSelector, true); |
|||
} |
|||
else |
|||
{ |
|||
PseudoClasses.Set(pcDarkSelector, true); |
|||
PseudoClasses.Set(pcLightSelector, false); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Generates a new background image for the color slider and applies it.
|
|||
/// </summary>
|
|||
private async void UpdateBackground() |
|||
{ |
|||
// In Avalonia, Bounds returns the actual device-independent pixel size of a control.
|
|||
// However, this is not necessarily the size of the control rendered on a display.
|
|||
// A desktop or application scaling factor may be applied which must be accounted for here.
|
|||
// Remember bitmaps in Avalonia are rendered mapping to actual device pixels, not the device-
|
|||
// independent pixels of controls.
|
|||
|
|||
var scale = LayoutHelper.GetLayoutScale(this); |
|||
var pixelWidth = Convert.ToInt32(Bounds.Width * scale); |
|||
var pixelHeight = Convert.ToInt32(Bounds.Height * scale); |
|||
|
|||
if (pixelWidth != 0 && pixelHeight != 0) |
|||
{ |
|||
var bitmap = await ColorPickerHelpers.CreateComponentBitmapAsync( |
|||
pixelWidth, |
|||
pixelHeight, |
|||
Orientation, |
|||
ColorModel, |
|||
ColorComponent, |
|||
HsvColor, |
|||
IsAlphaMaxForced, |
|||
IsSaturationValueMaxForced); |
|||
|
|||
if (bitmap != null) |
|||
{ |
|||
Background = new ImageBrush(ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Updates the slider property values by applying the current color.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Warning: This will trigger property changed updates.
|
|||
/// Consider using <see cref="disableUpdates"/> externally.
|
|||
/// </remarks>
|
|||
private void SetColorToSliderValues() |
|||
{ |
|||
var hsvColor = HsvColor; |
|||
var rgbColor = Color; |
|||
var component = ColorComponent; |
|||
|
|||
if (ColorModel == ColorModel.Hsva) |
|||
{ |
|||
// Note: Components converted into a usable range for the user
|
|||
switch (component) |
|||
{ |
|||
case ColorComponent.Alpha: |
|||
Minimum = 0; |
|||
Maximum = 100; |
|||
Value = hsvColor.A * 100; |
|||
break; |
|||
case ColorComponent.Component1: // Hue
|
|||
Minimum = 0; |
|||
Maximum = MaxHue; |
|||
Value = hsvColor.H; |
|||
break; |
|||
case ColorComponent.Component2: // Saturation
|
|||
Minimum = 0; |
|||
Maximum = 100; |
|||
Value = hsvColor.S * 100; |
|||
break; |
|||
case ColorComponent.Component3: // Value
|
|||
Minimum = 0; |
|||
Maximum = 100; |
|||
Value = hsvColor.V * 100; |
|||
break; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
switch (component) |
|||
{ |
|||
case ColorComponent.Alpha: |
|||
Minimum = 0; |
|||
Maximum = 255; |
|||
Value = Convert.ToDouble(rgbColor.A); |
|||
break; |
|||
case ColorComponent.Component1: // Red
|
|||
Minimum = 0; |
|||
Maximum = 255; |
|||
Value = Convert.ToDouble(rgbColor.R); |
|||
break; |
|||
case ColorComponent.Component2: // Green
|
|||
Minimum = 0; |
|||
Maximum = 255; |
|||
Value = Convert.ToDouble(rgbColor.G); |
|||
break; |
|||
case ColorComponent.Component3: // Blue
|
|||
Minimum = 0; |
|||
Maximum = 255; |
|||
Value = Convert.ToDouble(rgbColor.B); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the current color determined by the slider values.
|
|||
/// </summary>
|
|||
private (Color, HsvColor) GetColorFromSliderValues() |
|||
{ |
|||
HsvColor hsvColor = new HsvColor(); |
|||
Color rgbColor = new Color(); |
|||
double sliderPercent = Value / (Maximum - Minimum); |
|||
|
|||
var baseHsvColor = HsvColor; |
|||
var baseRgbColor = Color; |
|||
var component = ColorComponent; |
|||
|
|||
if (ColorModel == ColorModel.Hsva) |
|||
{ |
|||
switch (component) |
|||
{ |
|||
case ColorComponent.Alpha: |
|||
{ |
|||
hsvColor = new HsvColor(sliderPercent, baseHsvColor.H, baseHsvColor.S, baseHsvColor.V); |
|||
break; |
|||
} |
|||
case ColorComponent.Component1: |
|||
{ |
|||
hsvColor = new HsvColor(baseHsvColor.A, sliderPercent * MaxHue, baseHsvColor.S, baseHsvColor.V); |
|||
break; |
|||
} |
|||
case ColorComponent.Component2: |
|||
{ |
|||
hsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, sliderPercent, baseHsvColor.V); |
|||
break; |
|||
} |
|||
case ColorComponent.Component3: |
|||
{ |
|||
hsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, baseHsvColor.S, sliderPercent); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return (hsvColor.ToRgb(), hsvColor); |
|||
} |
|||
else |
|||
{ |
|||
byte componentValue = Convert.ToByte(MathUtilities.Clamp(sliderPercent * 255, 0, 255)); |
|||
|
|||
switch (component) |
|||
{ |
|||
case ColorComponent.Alpha: |
|||
rgbColor = new Color(componentValue, baseRgbColor.R, baseRgbColor.G, baseRgbColor.B); |
|||
break; |
|||
case ColorComponent.Component1: |
|||
rgbColor = new Color(baseRgbColor.A, componentValue, baseRgbColor.G, baseRgbColor.B); |
|||
break; |
|||
case ColorComponent.Component2: |
|||
rgbColor = new Color(baseRgbColor.A, baseRgbColor.R, componentValue, baseRgbColor.B); |
|||
break; |
|||
case ColorComponent.Component3: |
|||
rgbColor = new Color(baseRgbColor.A, baseRgbColor.R, baseRgbColor.G, componentValue); |
|||
break; |
|||
} |
|||
|
|||
return (rgbColor, rgbColor.ToHsv()); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the actual background color displayed for the given HSV color.
|
|||
/// This can differ due to the effects of certain properties intended to improve perception.
|
|||
/// </summary>
|
|||
/// <param name="hsvColor">The actual color to get the equivalent background color for.</param>
|
|||
/// <returns>The equivalent, perceived background color.</returns>
|
|||
private HsvColor GetEquivalentBackgroundColor(HsvColor hsvColor) |
|||
{ |
|||
var component = ColorComponent; |
|||
var isAlphaMaxForced = IsAlphaMaxForced; |
|||
var isSaturationValueMaxForced = IsSaturationValueMaxForced; |
|||
|
|||
if (isAlphaMaxForced && |
|||
component != ColorComponent.Alpha) |
|||
{ |
|||
hsvColor = new HsvColor(1.0, hsvColor.H, hsvColor.S, hsvColor.V); |
|||
} |
|||
|
|||
switch (component) |
|||
{ |
|||
case ColorComponent.Component1: |
|||
return new HsvColor( |
|||
hsvColor.A, |
|||
hsvColor.H, |
|||
isSaturationValueMaxForced ? 1.0 : hsvColor.S, |
|||
isSaturationValueMaxForced ? 1.0 : hsvColor.V); |
|||
case ColorComponent.Component2: |
|||
return new HsvColor( |
|||
hsvColor.A, |
|||
hsvColor.H, |
|||
hsvColor.S, |
|||
isSaturationValueMaxForced ? 1.0 : hsvColor.V); |
|||
case ColorComponent.Component3: |
|||
return new HsvColor( |
|||
hsvColor.A, |
|||
hsvColor.H, |
|||
isSaturationValueMaxForced ? 1.0 : hsvColor.S, |
|||
hsvColor.V); |
|||
default: |
|||
return hsvColor; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the actual background color displayed for the given RGB color.
|
|||
/// This can differ due to the effects of certain properties intended to improve perception.
|
|||
/// </summary>
|
|||
/// <param name="rgbColor">The actual color to get the equivalent background color for.</param>
|
|||
/// <returns>The equivalent, perceived background color.</returns>
|
|||
private Color GetEquivalentBackgroundColor(Color rgbColor) |
|||
{ |
|||
var component = ColorComponent; |
|||
var isAlphaMaxForced = IsAlphaMaxForced; |
|||
|
|||
if (isAlphaMaxForced && |
|||
component != ColorComponent.Alpha) |
|||
{ |
|||
rgbColor = new Color(255, rgbColor.R, rgbColor.G, rgbColor.B); |
|||
} |
|||
|
|||
return rgbColor; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
|||
{ |
|||
if (disableUpdates) |
|||
{ |
|||
base.OnPropertyChanged(change); |
|||
return; |
|||
} |
|||
|
|||
// Always keep the two color properties in sync
|
|||
if (change.Property == ColorProperty) |
|||
{ |
|||
disableUpdates = true; |
|||
|
|||
HsvColor = Color.ToHsv(); |
|||
|
|||
if (IsAutoUpdatingEnabled) |
|||
{ |
|||
SetColorToSliderValues(); |
|||
UpdateBackground(); |
|||
} |
|||
|
|||
UpdatePseudoClasses(); |
|||
OnColorChanged(new ColorChangedEventArgs( |
|||
change.GetOldValue<Color>(), |
|||
change.GetNewValue<Color>())); |
|||
|
|||
disableUpdates = false; |
|||
} |
|||
else if (change.Property == HsvColorProperty) |
|||
{ |
|||
disableUpdates = true; |
|||
|
|||
Color = HsvColor.ToRgb(); |
|||
|
|||
if (IsAutoUpdatingEnabled) |
|||
{ |
|||
SetColorToSliderValues(); |
|||
UpdateBackground(); |
|||
} |
|||
|
|||
UpdatePseudoClasses(); |
|||
OnColorChanged(new ColorChangedEventArgs( |
|||
change.GetOldValue<HsvColor>().ToRgb(), |
|||
change.GetNewValue<HsvColor>().ToRgb())); |
|||
|
|||
disableUpdates = false; |
|||
} |
|||
else if (change.Property == BoundsProperty) |
|||
{ |
|||
if (IsAutoUpdatingEnabled) |
|||
{ |
|||
UpdateBackground(); |
|||
} |
|||
} |
|||
else if (change.Property == ValueProperty || |
|||
change.Property == MinimumProperty || |
|||
change.Property == MaximumProperty) |
|||
{ |
|||
disableUpdates = true; |
|||
|
|||
Color oldColor = Color; |
|||
(var color, var hsvColor) = GetColorFromSliderValues(); |
|||
|
|||
if (ColorModel == ColorModel.Hsva) |
|||
{ |
|||
HsvColor = hsvColor; |
|||
Color = hsvColor.ToRgb(); |
|||
} |
|||
else |
|||
{ |
|||
Color = color; |
|||
HsvColor = color.ToHsv(); |
|||
} |
|||
|
|||
UpdatePseudoClasses(); |
|||
OnColorChanged(new ColorChangedEventArgs(oldColor, Color)); |
|||
|
|||
disableUpdates = false; |
|||
} |
|||
|
|||
base.OnPropertyChanged(change); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called before the <see cref="ColorChanged"/> event occurs.
|
|||
/// </summary>
|
|||
/// <param name="e">The <see cref="ColorChangedEventArgs"/> defining old/new colors.</param>
|
|||
protected virtual void OnColorChanged(ColorChangedEventArgs e) |
|||
{ |
|||
ColorChanged?.Invoke(this, e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,222 @@ |
|||
// 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.Data; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public partial class ColorSpectrum |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Color"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<Color> ColorProperty = |
|||
AvaloniaProperty.Register<ColorSpectrum, Color>( |
|||
nameof(Color), |
|||
Colors.White, |
|||
defaultBindingMode: BindingMode.TwoWay); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Components"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<ColorSpectrumComponents> ComponentsProperty = |
|||
AvaloniaProperty.Register<ColorSpectrum, ColorSpectrumComponents>( |
|||
nameof(Components), |
|||
ColorSpectrumComponents.HueSaturation); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="HsvColor"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<HsvColor> HsvColorProperty = |
|||
AvaloniaProperty.Register<ColorSpectrum, HsvColor>( |
|||
nameof(HsvColor), |
|||
Colors.White.ToHsv(), |
|||
defaultBindingMode: BindingMode.TwoWay); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MaxHue"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<int> MaxHueProperty = |
|||
AvaloniaProperty.Register<ColorSpectrum, int>( |
|||
nameof(MaxHue), |
|||
359); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MaxSaturation"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<int> MaxSaturationProperty = |
|||
AvaloniaProperty.Register<ColorSpectrum, int>( |
|||
nameof(MaxSaturation), |
|||
100); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MaxValue"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<int> MaxValueProperty = |
|||
AvaloniaProperty.Register<ColorSpectrum, int>( |
|||
nameof(MaxValue), |
|||
100); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MinHue"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<int> MinHueProperty = |
|||
AvaloniaProperty.Register<ColorSpectrum, int>( |
|||
nameof(MinHue), |
|||
0); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MinSaturation"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<int> MinSaturationProperty = |
|||
AvaloniaProperty.Register<ColorSpectrum, int>( |
|||
nameof(MinSaturation), |
|||
0); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MinValue"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<int> MinValueProperty = |
|||
AvaloniaProperty.Register<ColorSpectrum, int>( |
|||
nameof(MinValue), |
|||
0); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Shape"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<ColorSpectrumShape> ShapeProperty = |
|||
AvaloniaProperty.Register<ColorSpectrum, ColorSpectrumShape>( |
|||
nameof(Shape), |
|||
ColorSpectrumShape.Box); |
|||
|
|||
/// <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>
|
|||
/// 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>
|
|||
/// 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>
|
|||
/// 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>
|
|||
/// 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>
|
|||
/// 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>
|
|||
/// 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>
|
|||
/// 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>
|
|||
/// 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>
|
|||
/// Gets or sets the displayed shape of the spectrum.
|
|||
/// </summary>
|
|||
public ColorSpectrumShape Shape |
|||
{ |
|||
get => GetValue(ShapeProperty); |
|||
set => SetValue(ShapeProperty, value); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -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,116 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using Avalonia.Data.Converters; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Controls.Primitives.Converters |
|||
{ |
|||
/// <summary>
|
|||
/// Creates an accent color for a given base color value and step parameter.
|
|||
/// This is a highly-specialized converter for the color picker.
|
|||
/// </summary>
|
|||
public class AccentColorConverter : IValueConverter |
|||
{ |
|||
/// <summary>
|
|||
/// The amount to change the Value component for each accent color step.
|
|||
/// </summary>
|
|||
public const double ValueDelta = 0.1; |
|||
|
|||
/// <inheritdoc/>
|
|||
public object? Convert( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
int accentStep; |
|||
Color? rgbColor = null; |
|||
HsvColor? hsvColor = null; |
|||
|
|||
if (value is Color valueColor) |
|||
{ |
|||
rgbColor = valueColor; |
|||
} |
|||
else if (value is HslColor valueHslColor) |
|||
{ |
|||
rgbColor = valueHslColor.ToRgb(); |
|||
} |
|||
else if (value is HsvColor valueHsvColor) |
|||
{ |
|||
hsvColor = valueHsvColor; |
|||
} |
|||
else if (value is SolidColorBrush valueBrush) |
|||
{ |
|||
rgbColor = valueBrush.Color; |
|||
} |
|||
else |
|||
{ |
|||
// Invalid color value provided
|
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
|
|||
// Get the value component delta
|
|||
try |
|||
{ |
|||
accentStep = int.Parse(parameter?.ToString() ?? "", CultureInfo.InvariantCulture); |
|||
} |
|||
catch |
|||
{ |
|||
// Invalid parameter provided, unable to convert to integer
|
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
|
|||
if (hsvColor == null && |
|||
rgbColor != null) |
|||
{ |
|||
hsvColor = rgbColor.Value.ToHsv(); |
|||
} |
|||
|
|||
if (hsvColor != null) |
|||
{ |
|||
return new SolidColorBrush(GetAccent(hsvColor.Value, accentStep).ToRgb()); |
|||
} |
|||
else |
|||
{ |
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public object? ConvertBack( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This does not account for perceptual differences and also does not match with
|
|||
/// system accent color calculation.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Use the HSV representation as it's more perceptual.
|
|||
/// In most cases only the value is changed by a fixed percentage so the algorithm is reproducible.
|
|||
/// </remarks>
|
|||
/// <param name="hsvColor">The base color to calculate the accent from.</param>
|
|||
/// <param name="accentStep">The number of accent color steps to move.</param>
|
|||
/// <returns>The new accent color.</returns>
|
|||
public static HsvColor GetAccent(HsvColor hsvColor, int accentStep) |
|||
{ |
|||
if (accentStep != 0) |
|||
{ |
|||
double colorValue = hsvColor.V; |
|||
colorValue += (accentStep * AccentColorConverter.ValueDelta); |
|||
colorValue = Math.Round(colorValue, 2); |
|||
|
|||
return new HsvColor(hsvColor.A, hsvColor.H, hsvColor.S, colorValue); |
|||
} |
|||
else |
|||
{ |
|||
return hsvColor; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Data.Converters; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Controls.Converters |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the approximated display name for the color.
|
|||
/// </summary>
|
|||
public class ColorToDisplayNameConverter : IValueConverter |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public object? Convert( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
Color color; |
|||
|
|||
if (value is Color valueColor) |
|||
{ |
|||
color = valueColor; |
|||
} |
|||
else if (value is HslColor valueHslColor) |
|||
{ |
|||
color = valueHslColor.ToRgb(); |
|||
} |
|||
else if (value is HsvColor valueHsvColor) |
|||
{ |
|||
color = valueHsvColor.ToRgb(); |
|||
} |
|||
else if (value is SolidColorBrush valueBrush) |
|||
{ |
|||
color = valueBrush.Color; |
|||
} |
|||
else |
|||
{ |
|||
// Invalid color value provided
|
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
|
|||
// ColorHelper.ToDisplayName ignores the alpha component
|
|||
// This means fully transparent colors will be named as a real color
|
|||
// That undesirable behavior is specially overridden here
|
|||
if (color.A == 0x00) |
|||
{ |
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
else |
|||
{ |
|||
return ColorHelper.ToDisplayName(color); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public object? ConvertBack( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using Avalonia.Data.Converters; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Controls.Converters |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a color to a hex string and vice versa.
|
|||
/// </summary>
|
|||
public class ColorToHexConverter : IValueConverter |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public object? Convert( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
Color color; |
|||
bool includeSymbol = parameter as bool? ?? false; |
|||
|
|||
if (value is Color valueColor) |
|||
{ |
|||
color = valueColor; |
|||
} |
|||
else if (value is HslColor valueHslColor) |
|||
{ |
|||
color = valueHslColor.ToRgb(); |
|||
} |
|||
else if (value is HsvColor valueHsvColor) |
|||
{ |
|||
color = valueHsvColor.ToRgb(); |
|||
} |
|||
else if (value is SolidColorBrush valueBrush) |
|||
{ |
|||
color = valueBrush.Color; |
|||
} |
|||
else |
|||
{ |
|||
// Invalid color value provided
|
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
|
|||
string hexColor = color.ToString(); |
|||
|
|||
if (includeSymbol == false) |
|||
{ |
|||
// TODO: When .net standard 2.0 is dropped, replace the below line
|
|||
//hexColor = hexColor.Replace("#", string.Empty, StringComparison.Ordinal);
|
|||
hexColor = hexColor.Replace("#", string.Empty); |
|||
} |
|||
|
|||
return hexColor; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public object? ConvertBack( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
string hexValue = value?.ToString() ?? string.Empty; |
|||
|
|||
if (Color.TryParse(hexValue, out Color color)) |
|||
{ |
|||
return color; |
|||
} |
|||
else if (hexValue.StartsWith("#", StringComparison.Ordinal) == false && |
|||
Color.TryParse("#" + hexValue, out Color color2)) |
|||
{ |
|||
return color2; |
|||
} |
|||
else |
|||
{ |
|||
// Invalid hex color value provided
|
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using Avalonia.Data.Converters; |
|||
|
|||
namespace Avalonia.Controls.Primitives.Converters |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the third <see cref="ColorComponent"/> corresponding with a given
|
|||
/// <see cref="ColorSpectrumComponents"/> that represents the other two components.
|
|||
/// This is a highly-specialized converter for the color picker.
|
|||
/// </summary>
|
|||
public class ThirdComponentConverter : IValueConverter |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public object? Convert( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
if (value is ColorSpectrumComponents components) |
|||
{ |
|||
// Note: Alpha is not relevant here
|
|||
switch (components) |
|||
{ |
|||
case ColorSpectrumComponents.HueSaturation: |
|||
case ColorSpectrumComponents.SaturationHue: |
|||
return (ColorComponent)HsvComponent.Value; |
|||
case ColorSpectrumComponents.HueValue: |
|||
case ColorSpectrumComponents.ValueHue: |
|||
return (ColorComponent)HsvComponent.Saturation; |
|||
case ColorSpectrumComponents.SaturationValue: |
|||
case ColorSpectrumComponents.ValueSaturation: |
|||
return (ColorComponent)HsvComponent.Hue; |
|||
} |
|||
} |
|||
|
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public object? ConvertBack( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using Avalonia.Data.Converters; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Controls.Converters |
|||
{ |
|||
/// <summary>
|
|||
/// Converts the given value into an <see cref="IBrush"/> when a conversion is possible.
|
|||
/// </summary>
|
|||
public class ToBrushConverter : IValueConverter |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public object? Convert( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
if (value is IBrush brush) |
|||
{ |
|||
return brush; |
|||
} |
|||
else if (value is Color valueColor) |
|||
{ |
|||
return new SolidColorBrush(valueColor); |
|||
} |
|||
else if (value is HslColor valueHslColor) |
|||
{ |
|||
return new SolidColorBrush(valueHslColor.ToRgb()); |
|||
} |
|||
else if (value is HsvColor valueHsvColor) |
|||
{ |
|||
return new SolidColorBrush(valueHsvColor.ToRgb()); |
|||
} |
|||
|
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public object? ConvertBack( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using Avalonia.Data.Converters; |
|||
using Avalonia.Media; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Controls.Converters |
|||
{ |
|||
/// <summary>
|
|||
/// Converts the given value into a <see cref="Color"/> when a conversion is possible.
|
|||
/// </summary>
|
|||
public class ToColorConverter : IValueConverter |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public object? Convert( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
if (value is Color valueColor) |
|||
{ |
|||
return valueColor; |
|||
} |
|||
else if (value is HslColor valueHslColor) |
|||
{ |
|||
return valueHslColor.ToRgb(); |
|||
} |
|||
else if (value is HsvColor valueHsvColor) |
|||
{ |
|||
return valueHsvColor.ToRgb(); |
|||
} |
|||
else if (value is SolidColorBrush valueBrush) |
|||
{ |
|||
// A brush may have an opacity set along with alpha transparency
|
|||
double alpha = valueBrush.Color.A * valueBrush.Opacity; |
|||
|
|||
return new Color( |
|||
(byte)MathUtilities.Clamp(alpha, 0x00, 0xFF), |
|||
valueBrush.Color.R, |
|||
valueBrush.Color.G, |
|||
valueBrush.Color.B); |
|||
} |
|||
|
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public object? ConvertBack( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using Avalonia.Data.Converters; |
|||
|
|||
namespace Avalonia.Controls.Primitives.Converters |
|||
{ |
|||
/// <summary>
|
|||
/// Converter to chain together multiple converters.
|
|||
/// </summary>
|
|||
public class ValueConverterGroup : List<IValueConverter>, IValueConverter |
|||
{ |
|||
/// <inheritdoc/>
|
|||
/// <inheritdoc/>
|
|||
public object? Convert( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
object? curValue; |
|||
|
|||
curValue = value; |
|||
for (int i = 0; i < Count; i++) |
|||
{ |
|||
curValue = this[i].Convert(curValue, targetType, parameter, culture); |
|||
} |
|||
|
|||
return curValue; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public object? ConvertBack( |
|||
object? value, |
|||
Type targetType, |
|||
object? parameter, |
|||
CultureInfo culture) |
|||
{ |
|||
object? curValue; |
|||
|
|||
curValue = value; |
|||
for (int i = (Count - 1); i >= 0; i--) |
|||
{ |
|||
curValue = this[i].ConvertBack(curValue, targetType, parameter, culture); |
|||
} |
|||
|
|||
return curValue; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,142 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Media; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Contains helpers useful when working with colors.
|
|||
/// </summary>
|
|||
public static class ColorHelper |
|||
{ |
|||
private static readonly Dictionary<Color, string> cachedDisplayNames = new Dictionary<Color, string>(); |
|||
private static readonly object cacheMutex = new object(); |
|||
|
|||
/// <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); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines if color display names are supported based on the current thread culture.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Only English names are currently supported following known color names.
|
|||
/// In the future known color names could be localized.
|
|||
/// </remarks>
|
|||
public static bool ToDisplayNameExists |
|||
{ |
|||
get => CultureInfo.CurrentUICulture.Name.StartsWith("EN", StringComparison.OrdinalIgnoreCase); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines an approximate display name for the given color.
|
|||
/// </summary>
|
|||
/// <param name="color">The color to get the display name for.</param>
|
|||
/// <returns>The approximate color display name.</returns>
|
|||
public static string ToDisplayName(Color color) |
|||
{ |
|||
// Without rounding, there are 16,777,216 possible RGB colors (without alpha).
|
|||
// This is too many to cache and search through for performance reasons.
|
|||
// It is also needlessly large as there are only ~140 known/named colors.
|
|||
// Therefore, rounding of the input color's component values is done to
|
|||
// reduce the color space into something more useful.
|
|||
double rounding = 5; |
|||
var roundedColor = new Color( |
|||
0xFF, |
|||
Convert.ToByte(Math.Round(color.R / rounding) * rounding), |
|||
Convert.ToByte(Math.Round(color.G / rounding) * rounding), |
|||
Convert.ToByte(Math.Round(color.B / rounding) * rounding)); |
|||
|
|||
// Attempt to use a previously cached display name
|
|||
lock (cacheMutex) |
|||
{ |
|||
if (cachedDisplayNames.TryGetValue(roundedColor, out var displayName)) |
|||
{ |
|||
return displayName; |
|||
} |
|||
} |
|||
|
|||
// Find the closest known color by measuring 3D Euclidean distance (ignore alpha)
|
|||
var closestKnownColor = KnownColor.None; |
|||
var closestKnownColorDistance = double.PositiveInfinity; |
|||
var knownColors = (KnownColor[])Enum.GetValues(typeof(KnownColor)); |
|||
|
|||
for (int i = 1; i < knownColors.Length; i++) // Skip 'None'
|
|||
{ |
|||
// Transparent is skipped since alpha is ignored making it equivalent to White
|
|||
if (knownColors[i] != KnownColor.Transparent) |
|||
{ |
|||
Color knownColor = KnownColors.ToColor(knownColors[i]); |
|||
|
|||
double distance = Math.Sqrt( |
|||
Math.Pow((double)(roundedColor.R - knownColor.R), 2.0) + |
|||
Math.Pow((double)(roundedColor.G - knownColor.G), 2.0) + |
|||
Math.Pow((double)(roundedColor.B - knownColor.B), 2.0)); |
|||
|
|||
if (distance < closestKnownColorDistance) |
|||
{ |
|||
closestKnownColor = knownColors[i]; |
|||
closestKnownColorDistance = distance; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Return the closest known color as the display name
|
|||
// Cache results for next time as well
|
|||
if (closestKnownColor != KnownColor.None) |
|||
{ |
|||
StringBuilder sb = new StringBuilder(); |
|||
string name = closestKnownColor.ToString(); |
|||
|
|||
// Add spaces converting PascalCase to human-readable names
|
|||
for (int i = 0; i < name.Length; i++) |
|||
{ |
|||
if (i != 0 && |
|||
char.IsUpper(name[i])) |
|||
{ |
|||
sb.Append(' '); |
|||
} |
|||
|
|||
sb.Append(name[i]); |
|||
} |
|||
|
|||
string displayName = sb.ToString(); |
|||
|
|||
lock (cacheMutex) |
|||
{ |
|||
cachedDisplayNames.Add(roundedColor, displayName); |
|||
} |
|||
|
|||
return displayName; |
|||
} |
|||
else |
|||
{ |
|||
return string.Empty; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,629 @@ |
|||
// 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.Linq; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Imaging; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Contains internal, special-purpose helpers used with the color picker.
|
|||
/// </summary>
|
|||
internal static class ColorPickerHelpers |
|||
{ |
|||
/// <summary>
|
|||
/// Generates a new bitmap of the specified size by changing a specific color component.
|
|||
/// This will produce a gradient representing a sweep of all possible values of the color component.
|
|||
/// </summary>
|
|||
/// <param name="width">The pixel width (X, horizontal) of the resulting bitmap.</param>
|
|||
/// <param name="height">The pixel height (Y, vertical) of the resulting bitmap.</param>
|
|||
/// <param name="orientation">The orientation of the resulting bitmap (gradient direction).</param>
|
|||
/// <param name="colorModel">The color model being used: RGBA or HSVA.</param>
|
|||
/// <param name="component">The specific color component to sweep.</param>
|
|||
/// <param name="baseHsvColor">The base HSV color used for components not being changed.</param>
|
|||
/// <param name="isAlphaMaxForced">Fix the alpha component value to maximum during calculation.
|
|||
/// This will remove any alpha/transparency from the other component backgrounds.</param>
|
|||
/// <param name="isSaturationValueMaxForced">Fix the saturation and value components to maximum
|
|||
/// during calculation with the HSVA color model.
|
|||
/// This will ensure colors are always discernible regardless of saturation/value.</param>
|
|||
/// <returns>A new bitmap representing a gradient of color component values.</returns>
|
|||
public static async Task<byte[]> CreateComponentBitmapAsync( |
|||
int width, |
|||
int height, |
|||
Orientation orientation, |
|||
ColorModel colorModel, |
|||
ColorComponent component, |
|||
HsvColor baseHsvColor, |
|||
bool isAlphaMaxForced, |
|||
bool isSaturationValueMaxForced) |
|||
{ |
|||
if (width == 0 || height == 0) |
|||
{ |
|||
return new byte[0]; |
|||
} |
|||
|
|||
var bitmap = await Task.Run<byte[]>(() => |
|||
{ |
|||
int pixelDataIndex = 0; |
|||
double componentStep; |
|||
byte[] bgraPixelData; |
|||
Color baseRgbColor = Colors.White; |
|||
Color rgbColor; |
|||
int bgraPixelDataHeight; |
|||
int bgraPixelDataWidth; |
|||
|
|||
// Allocate the buffer
|
|||
// BGRA formatted color components 1 byte each (4 bytes in a pixel)
|
|||
bgraPixelData = new byte[width * height * 4]; |
|||
bgraPixelDataHeight = height * 4; |
|||
bgraPixelDataWidth = width * 4; |
|||
|
|||
// Maximize alpha component value
|
|||
if (isAlphaMaxForced && |
|||
component != ColorComponent.Alpha) |
|||
{ |
|||
baseHsvColor = new HsvColor(1.0, baseHsvColor.H, baseHsvColor.S, baseHsvColor.V); |
|||
} |
|||
|
|||
// Convert HSV to RGB once
|
|||
if (colorModel == ColorModel.Rgba) |
|||
{ |
|||
baseRgbColor = baseHsvColor.ToRgb(); |
|||
} |
|||
|
|||
// Maximize Saturation and Value components when in HSVA mode
|
|||
if (isSaturationValueMaxForced && |
|||
colorModel == ColorModel.Hsva && |
|||
component != ColorComponent.Alpha) |
|||
{ |
|||
switch (component) |
|||
{ |
|||
case ColorComponent.Component1: |
|||
baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, 1.0); |
|||
break; |
|||
case ColorComponent.Component2: |
|||
baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, baseHsvColor.S, 1.0); |
|||
break; |
|||
case ColorComponent.Component3: |
|||
baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, baseHsvColor.V); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// Create the color component gradient
|
|||
if (orientation == Orientation.Horizontal) |
|||
{ |
|||
// Determine the numerical increment of the color steps within the component
|
|||
if (colorModel == ColorModel.Hsva) |
|||
{ |
|||
if (component == ColorComponent.Component1) |
|||
{ |
|||
componentStep = 360.0 / width; |
|||
} |
|||
else |
|||
{ |
|||
componentStep = 1.0 / width; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
componentStep = 255.0 / width; |
|||
} |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
if (y == 0) |
|||
{ |
|||
rgbColor = GetColor(x * componentStep); |
|||
|
|||
// Get a new color
|
|||
bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(rgbColor.B * rgbColor.A / 255); |
|||
bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(rgbColor.G * rgbColor.A / 255); |
|||
bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(rgbColor.R * rgbColor.A / 255); |
|||
bgraPixelData[pixelDataIndex + 3] = rgbColor.A; |
|||
} |
|||
else |
|||
{ |
|||
// Use the color in the row above
|
|||
// Remember the pixel data is 1 dimensional instead of 2
|
|||
bgraPixelData[pixelDataIndex + 0] = bgraPixelData[pixelDataIndex + 0 - bgraPixelDataWidth]; |
|||
bgraPixelData[pixelDataIndex + 1] = bgraPixelData[pixelDataIndex + 1 - bgraPixelDataWidth]; |
|||
bgraPixelData[pixelDataIndex + 2] = bgraPixelData[pixelDataIndex + 2 - bgraPixelDataWidth]; |
|||
bgraPixelData[pixelDataIndex + 3] = bgraPixelData[pixelDataIndex + 3 - bgraPixelDataWidth]; |
|||
} |
|||
|
|||
pixelDataIndex += 4; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Determine the numerical increment of the color steps within the component
|
|||
if (colorModel == ColorModel.Hsva) |
|||
{ |
|||
if (component == ColorComponent.Component1) |
|||
{ |
|||
componentStep = 360.0 / height; |
|||
} |
|||
else |
|||
{ |
|||
componentStep = 1.0 / height; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
componentStep = 255.0 / height; |
|||
} |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
if (x == 0) |
|||
{ |
|||
// The lowest component value should be at the 'bottom' of the bitmap
|
|||
rgbColor = GetColor((height - 1 - y) * componentStep); |
|||
|
|||
// Get a new color
|
|||
bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(rgbColor.B * rgbColor.A / 255); |
|||
bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(rgbColor.G * rgbColor.A / 255); |
|||
bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(rgbColor.R * rgbColor.A / 255); |
|||
bgraPixelData[pixelDataIndex + 3] = rgbColor.A; |
|||
} |
|||
else |
|||
{ |
|||
// Use the color in the column to the left
|
|||
// Remember the pixel data is 1 dimensional instead of 2
|
|||
bgraPixelData[pixelDataIndex + 0] = bgraPixelData[pixelDataIndex - 4]; |
|||
bgraPixelData[pixelDataIndex + 1] = bgraPixelData[pixelDataIndex - 3]; |
|||
bgraPixelData[pixelDataIndex + 2] = bgraPixelData[pixelDataIndex - 2]; |
|||
bgraPixelData[pixelDataIndex + 3] = bgraPixelData[pixelDataIndex - 1]; |
|||
} |
|||
|
|||
pixelDataIndex += 4; |
|||
} |
|||
} |
|||
} |
|||
|
|||
Color GetColor(double componentValue) |
|||
{ |
|||
Color newRgbColor = Colors.White; |
|||
|
|||
switch (component) |
|||
{ |
|||
case ColorComponent.Component1: |
|||
{ |
|||
if (colorModel == ColorModel.Hsva) |
|||
{ |
|||
// Sweep hue
|
|||
newRgbColor = HsvColor.ToRgb( |
|||
MathUtilities.Clamp(componentValue, 0.0, 360.0), |
|||
baseHsvColor.S, |
|||
baseHsvColor.V, |
|||
baseHsvColor.A); |
|||
} |
|||
else |
|||
{ |
|||
// Sweep red
|
|||
newRgbColor = new Color( |
|||
baseRgbColor.A, |
|||
Convert.ToByte(MathUtilities.Clamp(componentValue, 0.0, 255.0)), |
|||
baseRgbColor.G, |
|||
baseRgbColor.B); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
case ColorComponent.Component2: |
|||
{ |
|||
if (colorModel == ColorModel.Hsva) |
|||
{ |
|||
// Sweep saturation
|
|||
newRgbColor = HsvColor.ToRgb( |
|||
baseHsvColor.H, |
|||
MathUtilities.Clamp(componentValue, 0.0, 1.0), |
|||
baseHsvColor.V, |
|||
baseHsvColor.A); |
|||
} |
|||
else |
|||
{ |
|||
// Sweep green
|
|||
newRgbColor = new Color( |
|||
baseRgbColor.A, |
|||
baseRgbColor.R, |
|||
Convert.ToByte(MathUtilities.Clamp(componentValue, 0.0, 255.0)), |
|||
baseRgbColor.B); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
case ColorComponent.Component3: |
|||
{ |
|||
if (colorModel == ColorModel.Hsva) |
|||
{ |
|||
// Sweep value
|
|||
newRgbColor = HsvColor.ToRgb( |
|||
baseHsvColor.H, |
|||
baseHsvColor.S, |
|||
MathUtilities.Clamp(componentValue, 0.0, 1.0), |
|||
baseHsvColor.A); |
|||
} |
|||
else |
|||
{ |
|||
// Sweep blue
|
|||
newRgbColor = new Color( |
|||
baseRgbColor.A, |
|||
baseRgbColor.R, |
|||
baseRgbColor.G, |
|||
Convert.ToByte(MathUtilities.Clamp(componentValue, 0.0, 255.0))); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
case ColorComponent.Alpha: |
|||
{ |
|||
if (colorModel == ColorModel.Hsva) |
|||
{ |
|||
// Sweep alpha
|
|||
newRgbColor = HsvColor.ToRgb( |
|||
baseHsvColor.H, |
|||
baseHsvColor.S, |
|||
baseHsvColor.V, |
|||
MathUtilities.Clamp(componentValue, 0.0, 1.0)); |
|||
} |
|||
else |
|||
{ |
|||
// Sweep alpha
|
|||
newRgbColor = new Color( |
|||
Convert.ToByte(MathUtilities.Clamp(componentValue, 0.0, 255.0)), |
|||
baseRgbColor.R, |
|||
baseRgbColor.G, |
|||
baseRgbColor.B); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
return newRgbColor; |
|||
} |
|||
|
|||
return bgraPixelData; |
|||
}); |
|||
|
|||
return bitmap; |
|||
} |
|||
|
|||
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 || !ColorHelper.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 = ColorHelper.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 = ColorHelper.ToDisplayName(newHsv.ToRgb().ToColor()); |
|||
break; |
|||
} |
|||
} |
|||
else if (newValue < minBound) |
|||
{ |
|||
if (shouldWrap) |
|||
{ |
|||
newValue = maxBound; |
|||
justWrapped = true; |
|||
} |
|||
else |
|||
{ |
|||
newValue = minBound; |
|||
shouldFindMidPoint = false; |
|||
newColorName = ColorHelper.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 = ColorHelper.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 = ColorHelper.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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts the given raw BGRA pre-multiplied alpha pixel data into a bitmap.
|
|||
/// </summary>
|
|||
/// <param name="bgraPixelData">The bitmap (in raw BGRA pre-multiplied alpha pixels).</param>
|
|||
/// <param name="pixelWidth">The pixel width of the bitmap.</param>
|
|||
/// <param name="pixelHeight">The pixel height of the bitmap.</param>
|
|||
/// <returns>A new <see cref="WriteableBitmap"/>.</returns>
|
|||
public static WriteableBitmap CreateBitmapFromPixelData( |
|||
IList<byte> bgraPixelData, |
|||
int pixelWidth, |
|||
int pixelHeight) |
|||
{ |
|||
// Standard may need to change on some devices
|
|||
Vector dpi = new Vector(96, 96); |
|||
|
|||
var 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; |
|||
} |
|||
} |
|||
} |
|||
@ -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,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 Alpha component.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Also see: <see cref="HsvColor.A"/>
|
|||
/// </remarks>
|
|||
Alpha = 0, |
|||
|
|||
/// <summary>
|
|||
/// The Hue component.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Also see: <see cref="HsvColor.H"/>
|
|||
/// </remarks>
|
|||
Hue = 1, |
|||
|
|||
/// <summary>
|
|||
/// The Saturation component.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Also see: <see cref="HsvColor.S"/>
|
|||
/// </remarks>
|
|||
Saturation = 2, |
|||
|
|||
/// <summary>
|
|||
/// The Value component.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Also see: <see cref="HsvColor.V"/>
|
|||
/// </remarks>
|
|||
Value = 3 |
|||
}; |
|||
} |
|||
@ -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,42 @@ |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a specific component in the RGB color model.
|
|||
/// </summary>
|
|||
public enum RgbComponent |
|||
{ |
|||
/// <summary>
|
|||
/// The Alpha component.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Also see: <see cref="Color.A"/>
|
|||
/// </remarks>
|
|||
Alpha = 0, |
|||
|
|||
/// <summary>
|
|||
/// The Red component.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Also see: <see cref="Color.R"/>
|
|||
/// </remarks>
|
|||
Red = 1, |
|||
|
|||
/// <summary>
|
|||
/// The Green component.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Also see: <see cref="Color.G"/>
|
|||
/// </remarks>
|
|||
Green = 2, |
|||
|
|||
/// <summary>
|
|||
/// The Blue component.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Also see: <see cref="Color.B"/>
|
|||
/// </remarks>
|
|||
Blue = 3 |
|||
}; |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:converters="using:Avalonia.Controls.Converters" |
|||
xmlns:pc="using:Avalonia.Controls.Primitives.Converters" |
|||
x:CompileBindings="True"> |
|||
|
|||
<Styles.Resources> |
|||
<pc:AccentColorConverter x:Key="AccentColor" /> |
|||
<converters:ToBrushConverter x:Key="ToBrush" /> |
|||
<converters:CornerRadiusFilterConverter x:Key="RightCornerRadiusFilterConverter" Filter="TopRight, BottomRight"/> |
|||
<converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft"/> |
|||
</Styles.Resources> |
|||
|
|||
<Style Selector="ColorPreviewer"> |
|||
<Setter Property="Height" Value="70" /> |
|||
<Setter Property="CornerRadius" Value="0" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Grid ColumnDefinitions="Auto,*,Auto"> |
|||
<!-- Left accent colors --> |
|||
<Grid Grid.Column="0" |
|||
Height="40" |
|||
Width="80" |
|||
ColumnDefinitions="*,*" |
|||
Margin="0,0,-10,0" |
|||
VerticalAlignment="Center" |
|||
IsVisible="{TemplateBinding ShowAccentColors}"> |
|||
<Border Grid.Column="0" |
|||
Grid.ColumnSpan="2" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Background="{StaticResource CheckeredBackgroundBrush}" /> |
|||
<Border x:Name="AccentDec2Border" |
|||
Grid.Column="0" |
|||
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource LeftCornerRadiusFilterConverter}}" |
|||
Tag="-2" |
|||
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='-2'}" /> |
|||
<Border x:Name="AccentDec1Border" |
|||
Grid.Column="1" |
|||
Tag="-1" |
|||
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='-1'}" /> |
|||
</Grid> |
|||
<!-- Right accent colors --> |
|||
<Grid Grid.Column="2" |
|||
Height="40" |
|||
Width="80" |
|||
ColumnDefinitions="*,*" |
|||
Margin="-10,0,0,0" |
|||
VerticalAlignment="Center" |
|||
IsVisible="{TemplateBinding ShowAccentColors}"> |
|||
<Border Grid.Column="0" |
|||
Grid.ColumnSpan="2" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Background="{StaticResource CheckeredBackgroundBrush}" /> |
|||
<Border x:Name="AccentInc1Border" |
|||
Grid.Column="0" |
|||
Tag="1" |
|||
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='1'}" /> |
|||
<Border x:Name="AccentInc2Border" |
|||
Grid.Column="1" |
|||
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource RightCornerRadiusFilterConverter}}" |
|||
Tag="2" |
|||
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='2'}" /> |
|||
</Grid> |
|||
<!-- Must be last for drop shadow Z-index --> |
|||
<Border Grid.Column="1" |
|||
BoxShadow="0 0 10 2 #BF000000" |
|||
CornerRadius="{TemplateBinding CornerRadius}" |
|||
Margin="10"> |
|||
<Panel> |
|||
<Border Background="{StaticResource CheckeredBackgroundBrush}" |
|||
CornerRadius="{TemplateBinding CornerRadius}" /> |
|||
<Border x:Name="PreviewBorder" |
|||
CornerRadius="{TemplateBinding CornerRadius}" |
|||
Background="{TemplateBinding HsvColor, Converter={StaticResource ToBrush}}" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" /> |
|||
</Panel> |
|||
</Border> |
|||
</Grid> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
</Styles> |
|||
@ -0,0 +1,194 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:converters="using:Avalonia.Controls.Converters" |
|||
x:CompileBindings="True"> |
|||
|
|||
<Styles.Resources> |
|||
<converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadius" Corner="TopLeft" /> |
|||
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadius" Corner="BottomRight" /> |
|||
</Styles.Resources> |
|||
|
|||
<Style Selector="Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="BorderThickness" Value="0" /> |
|||
<Setter Property="Template"> |
|||
<Setter.Value> |
|||
<ControlTemplate> |
|||
<Border Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
CornerRadius="10" /> |
|||
</ControlTemplate> |
|||
</Setter.Value> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="ColorSlider:horizontal"> |
|||
<Setter Property="BorderThickness" Value="0" /> |
|||
<Setter Property="CornerRadius" Value="10" /> |
|||
<Setter Property="Height" Value="20" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Border BorderThickness="{TemplateBinding BorderThickness}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
CornerRadius="{TemplateBinding CornerRadius}"> |
|||
<Grid Margin="{TemplateBinding Padding}"> |
|||
<Rectangle HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Fill="{StaticResource CheckeredBackgroundBrush}" |
|||
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
|||
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
|||
<Rectangle HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Fill="{TemplateBinding Background}" |
|||
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
|||
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
|||
<Track Name="PART_Track" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Minimum="{TemplateBinding Minimum}" |
|||
Maximum="{TemplateBinding Maximum}" |
|||
Value="{TemplateBinding Value, Mode=TwoWay}" |
|||
IsDirectionReversed="{TemplateBinding IsDirectionReversed}" |
|||
Orientation="Horizontal"> |
|||
<Track.DecreaseButton> |
|||
<RepeatButton Name="PART_DecreaseButton" |
|||
Background="Transparent" |
|||
Focusable="False" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch"> |
|||
<RepeatButton.Template> |
|||
<ControlTemplate> |
|||
<Border Name="FocusTarget" |
|||
Background="Transparent" |
|||
Margin="0,-10" /> |
|||
</ControlTemplate> |
|||
</RepeatButton.Template> |
|||
</RepeatButton> |
|||
</Track.DecreaseButton> |
|||
<Track.IncreaseButton> |
|||
<RepeatButton Name="PART_IncreaseButton" |
|||
Background="Transparent" |
|||
Focusable="False" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch"> |
|||
<RepeatButton.Template> |
|||
<ControlTemplate> |
|||
<Border Name="FocusTarget" |
|||
Background="Transparent" |
|||
Margin="0,-10" /> |
|||
</ControlTemplate> |
|||
</RepeatButton.Template> |
|||
</RepeatButton> |
|||
</Track.IncreaseButton> |
|||
<Thumb Classes="ColorSliderThumbStyle" |
|||
Name="ColorSliderThumb" |
|||
Margin="0" |
|||
Padding="0" |
|||
DataContext="{TemplateBinding Value}" |
|||
Height="{TemplateBinding Height}" |
|||
Width="{TemplateBinding Height}" /> |
|||
</Track> |
|||
</Grid> |
|||
</Border> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="ColorSlider:vertical"> |
|||
<Setter Property="BorderThickness" Value="0" /> |
|||
<Setter Property="CornerRadius" Value="10" /> |
|||
<Setter Property="Width" Value="20" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Border BorderThickness="{TemplateBinding BorderThickness}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
CornerRadius="{TemplateBinding CornerRadius}"> |
|||
<Grid Margin="{TemplateBinding Padding}"> |
|||
<Rectangle HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Fill="{StaticResource CheckeredBackgroundBrush}" |
|||
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
|||
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
|||
<Rectangle HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Fill="{TemplateBinding Background}" |
|||
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
|||
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
|||
<Track Name="PART_Track" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Minimum="{TemplateBinding Minimum}" |
|||
Maximum="{TemplateBinding Maximum}" |
|||
Value="{TemplateBinding Value, Mode=TwoWay}" |
|||
IsDirectionReversed="{TemplateBinding IsDirectionReversed}" |
|||
Orientation="Vertical"> |
|||
<Track.DecreaseButton> |
|||
<RepeatButton Name="PART_DecreaseButton" |
|||
Background="Transparent" |
|||
Focusable="False" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch"> |
|||
<RepeatButton.Template> |
|||
<ControlTemplate> |
|||
<Border Name="FocusTarget" |
|||
Background="Transparent" |
|||
Margin="0,-10" /> |
|||
</ControlTemplate> |
|||
</RepeatButton.Template> |
|||
</RepeatButton> |
|||
</Track.DecreaseButton> |
|||
<Track.IncreaseButton> |
|||
<RepeatButton Name="PART_IncreaseButton" |
|||
Background="Transparent" |
|||
Focusable="False" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch"> |
|||
<RepeatButton.Template> |
|||
<ControlTemplate> |
|||
<Border Name="FocusTarget" |
|||
Background="Transparent" |
|||
Margin="0,-10" /> |
|||
</ControlTemplate> |
|||
</RepeatButton.Template> |
|||
</RepeatButton> |
|||
</Track.IncreaseButton> |
|||
<Thumb Classes="ColorSliderThumbStyle" |
|||
Name="ColorSliderThumb" |
|||
Margin="0" |
|||
Padding="0" |
|||
DataContext="{TemplateBinding Value}" |
|||
Height="{TemplateBinding Width}" |
|||
Width="{TemplateBinding Width}" /> |
|||
</Track> |
|||
</Grid> |
|||
</Border> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<!-- Normal State --> |
|||
<Style Selector="ColorSlider /template/ Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource ThemeForegroundBrush}" /> |
|||
<Setter Property="BorderThickness" Value="3" /> |
|||
</Style> |
|||
|
|||
<!-- Selector/Thumb Color --> |
|||
<Style Selector="ColorSlider:pointerover /template/ Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="Opacity" Value="0.75" /> |
|||
</Style> |
|||
<Style Selector="ColorSlider:pointerover:dark-selector /template/ Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="Opacity" Value="0.7" /> |
|||
</Style> |
|||
<Style Selector="ColorSlider:pointerover:light-selector /template/ Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="Opacity" Value="0.8" /> |
|||
</Style> |
|||
|
|||
<Style Selector="ColorSlider:dark-selector /template/ Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="BorderBrush" Value="Black" /> |
|||
</Style> |
|||
<Style Selector="ColorSlider:light-selector /template/ Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="BorderBrush" Value="White" /> |
|||
</Style> |
|||
|
|||
</Styles> |
|||
@ -0,0 +1,134 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:converters="using:Avalonia.Controls.Converters" |
|||
x:CompileBindings="True"> |
|||
|
|||
<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"> |
|||
<!-- Note: ToolTip.VerticalOffset is for touch devices to keep the tip above fingers --> |
|||
<Panel x:Name="PART_SelectionEllipsePanel" |
|||
ToolTip.VerticalOffset="-10" |
|||
ToolTip.Placement="Top"> |
|||
<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.Tip> |
|||
<!-- Set in code-behind --> |
|||
</ToolTip.Tip> |
|||
</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,28 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
|
|||
<Styles.Resources> |
|||
<VisualBrush x:Key="CheckeredBackgroundBrush" |
|||
TileMode="Tile" |
|||
Stretch="Uniform" |
|||
DestinationRect="0,0,8,8"> |
|||
<VisualBrush.Visual> |
|||
<DrawingPresenter Width="8" |
|||
Height="8"> |
|||
<DrawingGroup> |
|||
<GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z" |
|||
Brush="Transparent" /> |
|||
<GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z" |
|||
Brush="#19808080" /> |
|||
</DrawingGroup> |
|||
</DrawingPresenter> |
|||
</VisualBrush.Visual> |
|||
</VisualBrush> |
|||
</Styles.Resources> |
|||
|
|||
<!-- Primitives --> |
|||
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml" /> |
|||
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml" /> |
|||
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml" /> |
|||
|
|||
</Styles> |
|||
@ -0,0 +1,86 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:converters="using:Avalonia.Controls.Converters" |
|||
xmlns:pc="using:Avalonia.Controls.Primitives.Converters" |
|||
x:CompileBindings="True"> |
|||
|
|||
<Styles.Resources> |
|||
<pc:AccentColorConverter x:Key="AccentColor" /> |
|||
<converters:ToBrushConverter x:Key="ToBrush" /> |
|||
<converters:CornerRadiusFilterConverter x:Key="RightCornerRadiusFilterConverter" Filter="TopRight, BottomRight"/> |
|||
<converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft"/> |
|||
</Styles.Resources> |
|||
|
|||
<Style Selector="ColorPreviewer"> |
|||
<Setter Property="Height" Value="70" /> |
|||
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Grid ColumnDefinitions="Auto,*,Auto"> |
|||
<!-- Left accent colors --> |
|||
<Grid Grid.Column="0" |
|||
Height="40" |
|||
Width="80" |
|||
ColumnDefinitions="*,*" |
|||
Margin="0,0,-10,0" |
|||
VerticalAlignment="Center" |
|||
IsVisible="{TemplateBinding ShowAccentColors}"> |
|||
<Border Grid.Column="0" |
|||
Grid.ColumnSpan="2" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Background="{StaticResource CheckeredBackgroundBrush}" /> |
|||
<Border x:Name="AccentDec2Border" |
|||
Grid.Column="0" |
|||
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource LeftCornerRadiusFilterConverter}}" |
|||
Tag="-2" |
|||
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='-2'}" /> |
|||
<Border x:Name="AccentDec1Border" |
|||
Grid.Column="1" |
|||
Tag="-1" |
|||
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='-1'}" /> |
|||
</Grid> |
|||
<!-- Right accent colors --> |
|||
<Grid Grid.Column="2" |
|||
Height="40" |
|||
Width="80" |
|||
ColumnDefinitions="*,*" |
|||
Margin="-10,0,0,0" |
|||
VerticalAlignment="Center" |
|||
IsVisible="{TemplateBinding ShowAccentColors}"> |
|||
<Border Grid.Column="0" |
|||
Grid.ColumnSpan="2" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Background="{StaticResource CheckeredBackgroundBrush}" /> |
|||
<Border x:Name="AccentInc1Border" |
|||
Grid.Column="0" |
|||
Tag="1" |
|||
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='1'}" /> |
|||
<Border x:Name="AccentInc2Border" |
|||
Grid.Column="1" |
|||
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource RightCornerRadiusFilterConverter}}" |
|||
Tag="2" |
|||
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='2'}" /> |
|||
</Grid> |
|||
<!-- Must be last for drop shadow Z-index --> |
|||
<Border Grid.Column="1" |
|||
BoxShadow="0 0 10 2 #BF000000" |
|||
CornerRadius="{TemplateBinding CornerRadius}" |
|||
Margin="10"> |
|||
<Panel> |
|||
<Border Background="{StaticResource CheckeredBackgroundBrush}" |
|||
CornerRadius="{TemplateBinding CornerRadius}" /> |
|||
<Border x:Name="PreviewBorder" |
|||
CornerRadius="{TemplateBinding CornerRadius}" |
|||
Background="{TemplateBinding HsvColor, Converter={StaticResource ToBrush}}" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" /> |
|||
</Panel> |
|||
</Border> |
|||
</Grid> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
</Styles> |
|||
@ -0,0 +1,194 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:converters="using:Avalonia.Controls.Converters" |
|||
x:CompileBindings="True"> |
|||
|
|||
<Styles.Resources> |
|||
<converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadius" Corner="TopLeft" /> |
|||
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadius" Corner="BottomRight" /> |
|||
</Styles.Resources> |
|||
|
|||
<Style Selector="Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="BorderThickness" Value="0" /> |
|||
<Setter Property="Template"> |
|||
<Setter.Value> |
|||
<ControlTemplate> |
|||
<Border Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
CornerRadius="10" /> |
|||
</ControlTemplate> |
|||
</Setter.Value> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="ColorSlider:horizontal"> |
|||
<Setter Property="BorderThickness" Value="0" /> |
|||
<Setter Property="CornerRadius" Value="10" /> |
|||
<Setter Property="Height" Value="20" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Border BorderThickness="{TemplateBinding BorderThickness}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
CornerRadius="{TemplateBinding CornerRadius}"> |
|||
<Grid Margin="{TemplateBinding Padding}"> |
|||
<Rectangle HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Fill="{StaticResource CheckeredBackgroundBrush}" |
|||
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
|||
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
|||
<Rectangle HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Fill="{TemplateBinding Background}" |
|||
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
|||
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
|||
<Track Name="PART_Track" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Minimum="{TemplateBinding Minimum}" |
|||
Maximum="{TemplateBinding Maximum}" |
|||
Value="{TemplateBinding Value, Mode=TwoWay}" |
|||
IsDirectionReversed="{TemplateBinding IsDirectionReversed}" |
|||
Orientation="Horizontal"> |
|||
<Track.DecreaseButton> |
|||
<RepeatButton Name="PART_DecreaseButton" |
|||
Background="Transparent" |
|||
Focusable="False" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch"> |
|||
<RepeatButton.Template> |
|||
<ControlTemplate> |
|||
<Border Name="FocusTarget" |
|||
Background="Transparent" |
|||
Margin="0,-10" /> |
|||
</ControlTemplate> |
|||
</RepeatButton.Template> |
|||
</RepeatButton> |
|||
</Track.DecreaseButton> |
|||
<Track.IncreaseButton> |
|||
<RepeatButton Name="PART_IncreaseButton" |
|||
Background="Transparent" |
|||
Focusable="False" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch"> |
|||
<RepeatButton.Template> |
|||
<ControlTemplate> |
|||
<Border Name="FocusTarget" |
|||
Background="Transparent" |
|||
Margin="0,-10" /> |
|||
</ControlTemplate> |
|||
</RepeatButton.Template> |
|||
</RepeatButton> |
|||
</Track.IncreaseButton> |
|||
<Thumb Classes="ColorSliderThumbStyle" |
|||
Name="ColorSliderThumb" |
|||
Margin="0" |
|||
Padding="0" |
|||
DataContext="{TemplateBinding Value}" |
|||
Height="{TemplateBinding Height}" |
|||
Width="{TemplateBinding Height}" /> |
|||
</Track> |
|||
</Grid> |
|||
</Border> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="ColorSlider:vertical"> |
|||
<Setter Property="BorderThickness" Value="0" /> |
|||
<Setter Property="CornerRadius" Value="10" /> |
|||
<Setter Property="Width" Value="20" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Border BorderThickness="{TemplateBinding BorderThickness}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
CornerRadius="{TemplateBinding CornerRadius}"> |
|||
<Grid Margin="{TemplateBinding Padding}"> |
|||
<Rectangle HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Fill="{StaticResource CheckeredBackgroundBrush}" |
|||
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
|||
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
|||
<Rectangle HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Fill="{TemplateBinding Background}" |
|||
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" |
|||
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> |
|||
<Track Name="PART_Track" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
Minimum="{TemplateBinding Minimum}" |
|||
Maximum="{TemplateBinding Maximum}" |
|||
Value="{TemplateBinding Value, Mode=TwoWay}" |
|||
IsDirectionReversed="{TemplateBinding IsDirectionReversed}" |
|||
Orientation="Vertical"> |
|||
<Track.DecreaseButton> |
|||
<RepeatButton Name="PART_DecreaseButton" |
|||
Background="Transparent" |
|||
Focusable="False" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch"> |
|||
<RepeatButton.Template> |
|||
<ControlTemplate> |
|||
<Border Name="FocusTarget" |
|||
Background="Transparent" |
|||
Margin="0,-10" /> |
|||
</ControlTemplate> |
|||
</RepeatButton.Template> |
|||
</RepeatButton> |
|||
</Track.DecreaseButton> |
|||
<Track.IncreaseButton> |
|||
<RepeatButton Name="PART_IncreaseButton" |
|||
Background="Transparent" |
|||
Focusable="False" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch"> |
|||
<RepeatButton.Template> |
|||
<ControlTemplate> |
|||
<Border Name="FocusTarget" |
|||
Background="Transparent" |
|||
Margin="0,-10" /> |
|||
</ControlTemplate> |
|||
</RepeatButton.Template> |
|||
</RepeatButton> |
|||
</Track.IncreaseButton> |
|||
<Thumb Classes="ColorSliderThumbStyle" |
|||
Name="ColorSliderThumb" |
|||
Margin="0" |
|||
Padding="0" |
|||
DataContext="{TemplateBinding Value}" |
|||
Height="{TemplateBinding Width}" |
|||
Width="{TemplateBinding Width}" /> |
|||
</Track> |
|||
</Grid> |
|||
</Border> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<!-- Normal State --> |
|||
<Style Selector="ColorSlider /template/ Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
<Setter Property="BorderThickness" Value="3" /> |
|||
</Style> |
|||
|
|||
<!-- Selector/Thumb Color --> |
|||
<Style Selector="ColorSlider:pointerover /template/ Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="Opacity" Value="0.75" /> |
|||
</Style> |
|||
<Style Selector="ColorSlider:pointerover:dark-selector /template/ Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="Opacity" Value="0.7" /> |
|||
</Style> |
|||
<Style Selector="ColorSlider:pointerover:light-selector /template/ Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="Opacity" Value="0.8" /> |
|||
</Style> |
|||
|
|||
<Style Selector="ColorSlider:dark-selector /template/ Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" /> |
|||
</Style> |
|||
<Style Selector="ColorSlider:light-selector /template/ Thumb.ColorSliderThumbStyle"> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlBackgroundChromeWhiteBrush}" /> |
|||
</Style> |
|||
|
|||
</Styles> |
|||
@ -0,0 +1,137 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:converters="using:Avalonia.Controls.Converters" |
|||
x:CompileBindings="True"> |
|||
|
|||
<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"> |
|||
<!-- Note: ToolTip.VerticalOffset is for touch devices to keep the tip above fingers --> |
|||
<Panel x:Name="PART_SelectionEllipsePanel" |
|||
ToolTip.VerticalOffset="-10" |
|||
ToolTip.Placement="Top"> |
|||
<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.Tip> |
|||
<!-- Set in code-behind --> |
|||
</ToolTip.Tip> |
|||
</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.7" /> |
|||
</Style> |
|||
<Style Selector="ColorSpectrum:pointerover:light-selector /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,28 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
|
|||
<Styles.Resources> |
|||
<VisualBrush x:Key="CheckeredBackgroundBrush" |
|||
TileMode="Tile" |
|||
Stretch="Uniform" |
|||
DestinationRect="0,0,8,8"> |
|||
<VisualBrush.Visual> |
|||
<DrawingPresenter Width="8" |
|||
Height="8"> |
|||
<DrawingGroup> |
|||
<GeometryDrawing Geometry="M0,0 L2,0 2,2, 0,2Z" |
|||
Brush="Transparent" /> |
|||
<GeometryDrawing Geometry="M0,1 L2,1 2,2, 1,2 1,0 0,0Z" |
|||
Brush="#19808080" /> |
|||
</DrawingGroup> |
|||
</DrawingPresenter> |
|||
</VisualBrush.Visual> |
|||
</VisualBrush> |
|||
</Styles.Resources> |
|||
|
|||
<!-- Primitives --> |
|||
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml" /> |
|||
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml" /> |
|||
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml" /> |
|||
|
|||
</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(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using Avalonia.LogicalTree; |
|||
|
|||
namespace Avalonia.Controls.Documents |
|||
{ |
|||
internal interface IInlineHost : ILogical |
|||
{ |
|||
void AddVisualChild(IControl child); |
|||
|
|||
void Invalidate(); |
|||
} |
|||
} |
|||
@ -0,0 +1,125 @@ |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.TextFormatting; |
|||
using Avalonia.Metadata; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Controls.Documents |
|||
{ |
|||
/// <summary>
|
|||
/// InlineUIContainer - a wrapper for embedded UIElements in text
|
|||
/// flow content inline collections
|
|||
/// </summary>
|
|||
public class InlineUIContainer : Inline |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Child"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IControl> ChildProperty = |
|||
AvaloniaProperty.Register<InlineUIContainer, IControl>(nameof(Child)); |
|||
|
|||
static InlineUIContainer() |
|||
{ |
|||
BaselineAlignmentProperty.OverrideDefaultValue<InlineUIContainer>(BaselineAlignment.Top); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of InlineUIContainer element.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The purpose of this element is to be a wrapper for UIElements
|
|||
/// when they are embedded into text flow - as items of
|
|||
/// InlineCollections.
|
|||
/// </remarks>
|
|||
public InlineUIContainer() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes an InlineBox specifying its child UIElement
|
|||
/// </summary>
|
|||
/// <param name="child">
|
|||
/// UIElement set as a child of this inline item
|
|||
/// </param>
|
|||
public InlineUIContainer(IControl child) |
|||
{ |
|||
Child = child; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The content spanned by this TextElement.
|
|||
/// </summary>
|
|||
[Content] |
|||
public IControl Child |
|||
{ |
|||
get => GetValue(ChildProperty); |
|||
set => SetValue(ChildProperty, value); |
|||
} |
|||
|
|||
internal override void BuildTextRun(IList<TextRun> textRuns) |
|||
{ |
|||
if(InlineHost == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
((ISetLogicalParent)Child).SetParent(InlineHost); |
|||
|
|||
InlineHost.AddVisualChild(Child); |
|||
|
|||
textRuns.Add(new InlineRun(Child, CreateTextRunProperties())); |
|||
} |
|||
|
|||
internal override void AppendText(StringBuilder stringBuilder) |
|||
{ |
|||
} |
|||
|
|||
private class InlineRun : DrawableTextRun |
|||
{ |
|||
public InlineRun(IControl control, TextRunProperties properties) |
|||
{ |
|||
Control = control; |
|||
Properties = properties; |
|||
} |
|||
|
|||
public IControl Control { get; } |
|||
|
|||
public override TextRunProperties? Properties { get; } |
|||
|
|||
public override Size Size |
|||
{ |
|||
get |
|||
{ |
|||
if (!Control.IsMeasureValid) |
|||
{ |
|||
Control.Measure(Size.Infinity); |
|||
} |
|||
|
|||
return Control.DesiredSize; |
|||
} |
|||
} |
|||
|
|||
public override double Baseline |
|||
{ |
|||
get |
|||
{ |
|||
double baseline = Size.Height; |
|||
double baselineOffsetValue = Control.GetValue<double>(TextBlock.BaselineOffsetProperty); |
|||
|
|||
if (!MathUtilities.IsZero(baselineOffsetValue)) |
|||
{ |
|||
baseline = baselineOffsetValue; |
|||
} |
|||
|
|||
return -baseline; |
|||
} |
|||
} |
|||
|
|||
public override void Draw(DrawingContext drawingContext, Point origin) |
|||
{ |
|||
Control.Arrange(new Rect(origin, Size)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue