committed by
GitHub
48 changed files with 3048 additions and 621 deletions
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -1,414 +0,0 @@ |
|||
// This source file is adapted from the WinUI project.
|
|||
// (https://github.com/microsoft/microsoft-ui-xaml)
|
|||
//
|
|||
// Licensed to The Avalonia Project under the MIT License.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Imaging; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
internal static class ColorHelpers |
|||
{ |
|||
public const int CheckerSize = 4; |
|||
|
|||
public static bool ToDisplayNameExists |
|||
{ |
|||
get => false; |
|||
} |
|||
|
|||
public static string ToDisplayName(Color color) |
|||
{ |
|||
return string.Empty; |
|||
} |
|||
|
|||
public static Hsv IncrementColorComponent( |
|||
Hsv originalHsv, |
|||
HsvComponent component, |
|||
IncrementDirection direction, |
|||
IncrementAmount amount, |
|||
bool shouldWrap, |
|||
double minBound, |
|||
double maxBound) |
|||
{ |
|||
Hsv newHsv = originalHsv; |
|||
|
|||
if (amount == IncrementAmount.Small || !ToDisplayNameExists) |
|||
{ |
|||
// In order to avoid working with small values that can incur rounding issues,
|
|||
// we'll multiple saturation and value by 100 to put them in the range of 0-100 instead of 0-1.
|
|||
newHsv.S *= 100; |
|||
newHsv.V *= 100; |
|||
|
|||
// Note: *valueToIncrement replaced with ref local variable for C#, must be initialized
|
|||
ref double valueToIncrement = ref newHsv.H; |
|||
double incrementAmount = 0.0; |
|||
|
|||
// If we're adding a small increment, then we'll just add or subtract 1.
|
|||
// If we're adding a large increment, then we want to snap to the next
|
|||
// or previous major value - for hue, this is every increment of 30;
|
|||
// for saturation and value, this is every increment of 10.
|
|||
switch (component) |
|||
{ |
|||
case HsvComponent.Hue: |
|||
valueToIncrement = ref newHsv.H; |
|||
incrementAmount = amount == IncrementAmount.Small ? 1 : 30; |
|||
break; |
|||
|
|||
case HsvComponent.Saturation: |
|||
valueToIncrement = ref newHsv.S; |
|||
incrementAmount = amount == IncrementAmount.Small ? 1 : 10; |
|||
break; |
|||
|
|||
case HsvComponent.Value: |
|||
valueToIncrement = ref newHsv.V; |
|||
incrementAmount = amount == IncrementAmount.Small ? 1 : 10; |
|||
break; |
|||
|
|||
default: |
|||
throw new InvalidOperationException("Invalid HsvComponent."); |
|||
} |
|||
|
|||
double previousValue = valueToIncrement; |
|||
|
|||
valueToIncrement += (direction == IncrementDirection.Lower ? -incrementAmount : incrementAmount); |
|||
|
|||
// If the value has reached outside the bounds, we were previous at the boundary, and we should wrap,
|
|||
// then we'll place the selection on the other side of the spectrum.
|
|||
// Otherwise, we'll place it on the boundary that was exceeded.
|
|||
if (valueToIncrement < minBound) |
|||
{ |
|||
valueToIncrement = (shouldWrap && previousValue == minBound) ? maxBound : minBound; |
|||
} |
|||
|
|||
if (valueToIncrement > maxBound) |
|||
{ |
|||
valueToIncrement = (shouldWrap && previousValue == maxBound) ? minBound : maxBound; |
|||
} |
|||
|
|||
// We multiplied saturation and value by 100 previously, so now we want to put them back in the 0-1 range.
|
|||
newHsv.S /= 100; |
|||
newHsv.V /= 100; |
|||
} |
|||
else |
|||
{ |
|||
// While working with named colors, we're going to need to be working in actual HSV units,
|
|||
// so we'll divide the min bound and max bound by 100 in the case of saturation or value,
|
|||
// since we'll have received units between 0-100 and we need them within 0-1.
|
|||
if (component == HsvComponent.Saturation || |
|||
component == HsvComponent.Value) |
|||
{ |
|||
minBound /= 100; |
|||
maxBound /= 100; |
|||
} |
|||
|
|||
newHsv = FindNextNamedColor(originalHsv, component, direction, shouldWrap, minBound, maxBound); |
|||
} |
|||
|
|||
return newHsv; |
|||
} |
|||
|
|||
public static Hsv FindNextNamedColor( |
|||
Hsv originalHsv, |
|||
HsvComponent component, |
|||
IncrementDirection direction, |
|||
bool shouldWrap, |
|||
double minBound, |
|||
double maxBound) |
|||
{ |
|||
// There's no easy way to directly get the next named color, so what we'll do
|
|||
// is just iterate in the direction that we want to find it until we find a color
|
|||
// in that direction that has a color name different than our current color name.
|
|||
// Once we find a new color name, then we'll iterate across that color name until
|
|||
// we find its bounds on the other side, and then select the color that is exactly
|
|||
// in the middle of that color's bounds.
|
|||
Hsv newHsv = originalHsv; |
|||
|
|||
string originalColorName = ColorHelpers.ToDisplayName(originalHsv.ToRgb().ToColor()); |
|||
string newColorName = originalColorName; |
|||
|
|||
// Note: *newValue replaced with ref local variable for C#, must be initialized
|
|||
double originalValue = 0.0; |
|||
ref double newValue = ref newHsv.H; |
|||
double incrementAmount = 0.0; |
|||
|
|||
switch (component) |
|||
{ |
|||
case HsvComponent.Hue: |
|||
originalValue = originalHsv.H; |
|||
newValue = ref newHsv.H; |
|||
incrementAmount = 1; |
|||
break; |
|||
|
|||
case HsvComponent.Saturation: |
|||
originalValue = originalHsv.S; |
|||
newValue = ref newHsv.S; |
|||
incrementAmount = 0.01; |
|||
break; |
|||
|
|||
case HsvComponent.Value: |
|||
originalValue = originalHsv.V; |
|||
newValue = ref newHsv.V; |
|||
incrementAmount = 0.01; |
|||
break; |
|||
|
|||
default: |
|||
throw new InvalidOperationException("Invalid HsvComponent."); |
|||
} |
|||
|
|||
bool shouldFindMidPoint = true; |
|||
|
|||
while (newColorName == originalColorName) |
|||
{ |
|||
double previousValue = newValue; |
|||
newValue += (direction == IncrementDirection.Lower ? -1 : 1) * incrementAmount; |
|||
|
|||
bool justWrapped = false; |
|||
|
|||
// If we've hit a boundary, then either we should wrap or we shouldn't.
|
|||
// If we should, then we'll perform that wrapping if we were previously up against
|
|||
// the boundary that we've now hit. Otherwise, we'll stop at that boundary.
|
|||
if (newValue > maxBound) |
|||
{ |
|||
if (shouldWrap) |
|||
{ |
|||
newValue = minBound; |
|||
justWrapped = true; |
|||
} |
|||
else |
|||
{ |
|||
newValue = maxBound; |
|||
shouldFindMidPoint = false; |
|||
newColorName = ColorHelpers.ToDisplayName(newHsv.ToRgb().ToColor()); |
|||
break; |
|||
} |
|||
} |
|||
else if (newValue < minBound) |
|||
{ |
|||
if (shouldWrap) |
|||
{ |
|||
newValue = maxBound; |
|||
justWrapped = true; |
|||
} |
|||
else |
|||
{ |
|||
newValue = minBound; |
|||
shouldFindMidPoint = false; |
|||
newColorName = ColorHelpers.ToDisplayName(newHsv.ToRgb().ToColor()); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (!justWrapped && |
|||
previousValue != originalValue && |
|||
Math.Sign(newValue - originalValue) != Math.Sign(previousValue - originalValue)) |
|||
{ |
|||
// If we've wrapped all the way back to the start and have failed to find a new color name,
|
|||
// then we'll just quit - there isn't a new color name that we're going to find.
|
|||
shouldFindMidPoint = false; |
|||
break; |
|||
} |
|||
|
|||
newColorName = ColorHelpers.ToDisplayName(newHsv.ToRgb().ToColor()); |
|||
} |
|||
|
|||
if (shouldFindMidPoint) |
|||
{ |
|||
Hsv startHsv = newHsv; |
|||
Hsv currentHsv = startHsv; |
|||
double startEndOffset = 0; |
|||
string currentColorName = newColorName; |
|||
|
|||
// Note: *startValue/*currentValue replaced with ref local variables for C#, must be initialized
|
|||
ref double startValue = ref startHsv.H; |
|||
ref double currentValue = ref currentHsv.H; |
|||
double wrapIncrement = 0; |
|||
|
|||
switch (component) |
|||
{ |
|||
case HsvComponent.Hue: |
|||
startValue = ref startHsv.H; |
|||
currentValue = ref currentHsv.H; |
|||
wrapIncrement = 360.0; |
|||
break; |
|||
|
|||
case HsvComponent.Saturation: |
|||
startValue = ref startHsv.S; |
|||
currentValue = ref currentHsv.S; |
|||
wrapIncrement = 1.0; |
|||
break; |
|||
|
|||
case HsvComponent.Value: |
|||
startValue = ref startHsv.V; |
|||
currentValue = ref currentHsv.V; |
|||
wrapIncrement = 1.0; |
|||
break; |
|||
|
|||
default: |
|||
throw new InvalidOperationException("Invalid HsvComponent."); |
|||
} |
|||
|
|||
while (newColorName == currentColorName) |
|||
{ |
|||
currentValue += (direction == IncrementDirection.Lower ? -1 : 1) * incrementAmount; |
|||
|
|||
// If we've hit a boundary, then either we should wrap or we shouldn't.
|
|||
// If we should, then we'll perform that wrapping if we were previously up against
|
|||
// the boundary that we've now hit. Otherwise, we'll stop at that boundary.
|
|||
if (currentValue > maxBound) |
|||
{ |
|||
if (shouldWrap) |
|||
{ |
|||
currentValue = minBound; |
|||
startEndOffset = maxBound - minBound; |
|||
} |
|||
else |
|||
{ |
|||
currentValue = maxBound; |
|||
break; |
|||
} |
|||
} |
|||
else if (currentValue < minBound) |
|||
{ |
|||
if (shouldWrap) |
|||
{ |
|||
currentValue = maxBound; |
|||
startEndOffset = minBound - maxBound; |
|||
} |
|||
else |
|||
{ |
|||
currentValue = minBound; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
currentColorName = ColorHelpers.ToDisplayName(currentHsv.ToRgb().ToColor()); |
|||
} |
|||
|
|||
newValue = (startValue + currentValue + startEndOffset) / 2; |
|||
|
|||
// Dividing by 2 may have gotten us halfway through a single step, so we'll
|
|||
// remove that half-step if it exists.
|
|||
double leftoverValue = Math.Abs(newValue); |
|||
|
|||
while (leftoverValue > incrementAmount) |
|||
{ |
|||
leftoverValue -= incrementAmount; |
|||
} |
|||
|
|||
newValue -= leftoverValue; |
|||
|
|||
while (newValue < minBound) |
|||
{ |
|||
newValue += wrapIncrement; |
|||
} |
|||
|
|||
while (newValue > maxBound) |
|||
{ |
|||
newValue -= wrapIncrement; |
|||
} |
|||
} |
|||
|
|||
return newHsv; |
|||
} |
|||
|
|||
public static double IncrementAlphaComponent( |
|||
double originalAlpha, |
|||
IncrementDirection direction, |
|||
IncrementAmount amount, |
|||
bool shouldWrap, |
|||
double minBound, |
|||
double maxBound) |
|||
{ |
|||
// In order to avoid working with small values that can incur rounding issues,
|
|||
// we'll multiple alpha by 100 to put it in the range of 0-100 instead of 0-1.
|
|||
originalAlpha *= 100; |
|||
|
|||
const double smallIncrementAmount = 1; |
|||
const double largeIncrementAmount = 10; |
|||
|
|||
if (amount == IncrementAmount.Small) |
|||
{ |
|||
originalAlpha += (direction == IncrementDirection.Lower ? -1 : 1) * smallIncrementAmount; |
|||
} |
|||
else |
|||
{ |
|||
if (direction == IncrementDirection.Lower) |
|||
{ |
|||
originalAlpha = Math.Ceiling((originalAlpha - largeIncrementAmount) / largeIncrementAmount) * largeIncrementAmount; |
|||
} |
|||
else |
|||
{ |
|||
originalAlpha = Math.Floor((originalAlpha + largeIncrementAmount) / largeIncrementAmount) * largeIncrementAmount; |
|||
} |
|||
} |
|||
|
|||
// If the value has reached outside the bounds and we should wrap, then we'll place the selection
|
|||
// on the other side of the spectrum. Otherwise, we'll place it on the boundary that was exceeded.
|
|||
if (originalAlpha < minBound) |
|||
{ |
|||
originalAlpha = shouldWrap ? maxBound : minBound; |
|||
} |
|||
|
|||
if (originalAlpha > maxBound) |
|||
{ |
|||
originalAlpha = shouldWrap ? minBound : maxBound; |
|||
} |
|||
|
|||
// We multiplied alpha by 100 previously, so now we want to put it back in the 0-1 range.
|
|||
return originalAlpha / 100; |
|||
} |
|||
|
|||
public static WriteableBitmap CreateBitmapFromPixelData( |
|||
int pixelWidth, |
|||
int pixelHeight, |
|||
List<byte> bgraPixelData) |
|||
{ |
|||
Vector dpi = new Vector(96, 96); // Standard may need to change on some devices
|
|||
|
|||
WriteableBitmap bitmap = new WriteableBitmap( |
|||
new PixelSize(pixelWidth, pixelHeight), |
|||
dpi, |
|||
PixelFormat.Bgra8888, |
|||
AlphaFormat.Premul); |
|||
|
|||
// Warning: This is highly questionable
|
|||
using (var frameBuffer = bitmap.Lock()) |
|||
{ |
|||
Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); |
|||
} |
|||
|
|||
return bitmap; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the relative (perceptual) luminance/brightness of the given color.
|
|||
/// 1 is closer to white while 0 is closer to black.
|
|||
/// </summary>
|
|||
/// <param name="color">The color to calculate relative luminance for.</param>
|
|||
/// <returns>The relative (perceptual) luminance/brightness of the given color.</returns>
|
|||
public static double GetRelativeLuminance(Color color) |
|||
{ |
|||
// The equation for relative luminance is given by
|
|||
//
|
|||
// L = 0.2126 * Rg + 0.7152 * Gg + 0.0722 * Bg
|
|||
//
|
|||
// where Xg = { X/3294 if X <= 10, (R/269 + 0.0513)^2.4 otherwise }
|
|||
//
|
|||
// If L is closer to 1, then the color is closer to white; if it is closer to 0,
|
|||
// then the color is closer to black. This is based on the fact that the human
|
|||
// eye perceives green to be much brighter than red, which in turn is perceived to be
|
|||
// brighter than blue.
|
|||
|
|||
double rg = color.R <= 10 ? color.R / 3294.0 : Math.Pow(color.R / 269.0 + 0.0513, 2.4); |
|||
double gg = color.G <= 10 ? color.G / 3294.0 : Math.Pow(color.G / 269.0 + 0.0513, 2.4); |
|||
double bg = color.B <= 10 ? color.B / 3294.0 : Math.Pow(color.B / 269.0 + 0.0513, 2.4); |
|||
|
|||
return (0.2126 * rg + 0.7152 * gg + 0.0722 * bg); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,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,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,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,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,28 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Data.Converters; |
|||
using Avalonia.Media.Imaging; |
|||
|
|||
namespace Avalonia.Themes.Default |
|||
{ |
|||
internal class IBitmapToImageConverter : IValueConverter |
|||
{ |
|||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) |
|||
{ |
|||
if (value != null && value is IBitmap bm) |
|||
return new Image { Source=bm }; |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Data.Converters; |
|||
using Avalonia.Media.Imaging; |
|||
|
|||
namespace Avalonia.Themes.Fluent |
|||
{ |
|||
internal class IBitmapToImageConverter : IValueConverter |
|||
{ |
|||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) |
|||
{ |
|||
if (value != null && value is IBitmap bm) |
|||
return new Image { Source=bm }; |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue