Browse Source

Add initial ColorPreviewer primitive

pull/8050/head
robloo 5 years ago
parent
commit
21dd8392bf
  1. 112
      src/Avalonia.Controls.ColorPicker/ColorPreviewer/AccentColorConverter.cs
  2. 69
      src/Avalonia.Controls.ColorPicker/ColorPreviewer/ColorPreviewer.Properties.cs
  3. 138
      src/Avalonia.Controls.ColorPicker/ColorPreviewer/ColorPreviewer.cs
  4. 1
      src/Avalonia.Controls.ColorPicker/Themes/Fluent.xaml
  5. 90
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml

112
src/Avalonia.Controls.ColorPicker/ColorPreviewer/AccentColorConverter.cs

@ -0,0 +1,112 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
namespace Avalonia.Controls.Primitives
{
/// <summary>
/// Creates an accent color for a given base color value and step parameter.
/// </summary>
public class AccentColorConverter : IValueConverter
{
/// <summary>
/// The amount to change the Value channel 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;
// Get the current color in HSV
if (value is Color valueColor)
{
rgbColor = valueColor;
}
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;
}
}
}
}

69
src/Avalonia.Controls.ColorPicker/ColorPreviewer/ColorPreviewer.Properties.cs

@ -0,0 +1,69 @@
using Avalonia.Media;
namespace Avalonia.Controls.Primitives
{
/// <inheritdoc/>
public partial class ColorPreviewer
{
/// <summary>
/// Defines the <see cref="Color"/> property.
/// </summary>
public static readonly StyledProperty<Color> ColorProperty =
AvaloniaProperty.Register<ColorSpectrum, Color>(
nameof(Color),
Colors.White);
/// <summary>
/// Gets or sets the currently previewed color in the RGB color model.
/// </summary>
/// <remarks>
/// For control authors use <see cref="HsvColor"/> instead to avoid loss
/// of precision and color drifting.
/// </remarks>
public Color Color
{
get => GetValue(ColorProperty);
set => SetValue(ColorProperty, value);
}
/// <summary>
/// Defines the <see cref="HsvColor"/> property.
/// </summary>
public static readonly StyledProperty<HsvColor> HsvColorProperty =
AvaloniaProperty.Register<ColorPreviewer, HsvColor>(
nameof(HsvColor),
Colors.Transparent.ToHsv());
/// <summary>
/// Gets or sets the currently previewed 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="ColorPreviewer"/> uses the HSV color model and using
/// this property will avoid loss of precision and color drifting.
/// </remarks>
public HsvColor HsvColor
{
get => GetValue(HsvColorProperty);
set => SetValue(HsvColorProperty, value);
}
/// <summary>
/// Defines the <see cref="ShowAccentColors"/> property.
/// </summary>
public static readonly StyledProperty<bool> ShowAccentColorsProperty =
AvaloniaProperty.Register<ColorPreviewer, bool>(
nameof(ShowAccentColors),
true);
/// <summary>
/// Gets or sets a value indicating whether accent colors are shown along
/// with the preview color.
/// </summary>
public bool ShowAccentColors
{
get => (bool)this.GetValue(ShowAccentColorsProperty);
set => SetValue(ShowAccentColorsProperty, value);
}
}
}

138
src/Avalonia.Controls.ColorPicker/ColorPreviewer/ColorPreviewer.cs

@ -0,0 +1,138 @@
using System;
using System.Globalization;
using Avalonia.Controls.Metadata;
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 happens 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;
}
return;
}
/// <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)
{
// Always keep the two color properties in sync
if (change.Property == ColorProperty)
{
HsvColor = Color.ToHsv();
}
else if (change.Property == HsvColorProperty)
{
Color = HsvColor.ToRgb();
}
base.OnPropertyChanged(change);
}
/// <summary>
/// Called before the <see cref="ColorChanged"/> event occurs.
/// </summary>
/// <param name="newColor">The newly selected color.</param>
protected virtual void OnColorChanged(HsvColor newColor)
{
var oldColor = HsvColor;
HsvColor = newColor;
ColorChanged?.Invoke(this, new ColorChangedEventArgs(oldColor.ToRgb(), newColor.ToRgb()));
return;
}
/// <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);
OnColorChanged(newHsvColor);
return;
}
}
}

1
src/Avalonia.Controls.ColorPicker/Themes/Fluent.xaml

@ -21,6 +21,7 @@
</Styles.Resources>
<!-- Primitives -->
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml" />
</Styles>

90
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml

@ -0,0 +1,90 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:primitives="using:Avalonia.Controls.Primitives"
x:CompileBindings="True">
<Styles.Resources>
<primitives:AccentColorConverter x:Key="AccentColor" />
</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"
Margin="0,0,-10,0"
VerticalAlignment="Center"
IsVisible="{TemplateBinding ShowAccentColors}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{StaticResource CheckeredBackgroundBrush}" />
<Border x:Name="AccentDec2Border"
Grid.Column="0"
CornerRadius="4,0,0,4"
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"
Margin="-10,0,0,0"
VerticalAlignment="Center"
IsVisible="{TemplateBinding ShowAccentColors}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<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="0,4,4,0"
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">
<Grid>
<Border Background="{DynamicResource FlyoutPresenterBackground}"
CornerRadius="{TemplateBinding CornerRadius}" />
<Border Background="{StaticResource CheckeredBackgroundBrush}"
CornerRadius="{TemplateBinding CornerRadius}" />
<Border x:Name="PreviewBorder"
CornerRadius="{TemplateBinding CornerRadius}"
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='0'}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>
</Border>
</Grid>
</ControlTemplate>
</Setter>
</Style>
</Styles>
Loading…
Cancel
Save