Browse Source

Add initial ColorSlider primitive

pull/8050/head
robloo 4 years ago
parent
commit
ad5249992d
  1. 28
      src/Avalonia.Controls.ColorPicker/ColorComponent.cs
  2. 344
      src/Avalonia.Controls.ColorPicker/ColorHelpers.cs
  3. 18
      src/Avalonia.Controls.ColorPicker/ColorModel.cs
  4. 143
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs
  5. 221
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
  6. 1
      src/Avalonia.Controls.ColorPicker/Themes/Fluent.xaml
  7. 172
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml

28
src/Avalonia.Controls.ColorPicker/ColorComponent.cs

@ -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,
/// <summary>
/// Represents the first color component which is Red when RGB or Hue when HSV.
/// </summary>
Component1,
/// <summary>
/// Represents the second color component which is Green when RGB or Saturation when HSV.
/// </summary>
Component2,
/// <summary>
/// Represents the third color component which is Blue when RGB or Value when HSV.
/// </summary>
Component3
}
}

344
src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorHelpers.cs → src/Avalonia.Controls.ColorPicker/ColorHelpers.cs

@ -6,9 +6,12 @@
using System;
using System.Collections.Generic;
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
{
@ -26,6 +29,291 @@ namespace Avalonia.Controls.Primitives
return string.Empty;
}
/// <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>
internal 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,
@ -363,14 +651,22 @@ namespace Avalonia.Controls.Primitives
return originalAlpha / 100;
}
/// <summary>
///
/// </summary>
/// <param name="pixelWidth">The pixel width of the bitmap.</param>
/// <param name="pixelHeight">The pixel height of the bitmap.</param>
/// <param name="bgraPixelData"></param>
/// <returns></returns>
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
// Standard may need to change on some devices
Vector dpi = new Vector(96, 96);
WriteableBitmap bitmap = new WriteableBitmap(
var bitmap = new WriteableBitmap(
new PixelSize(pixelWidth, pixelHeight),
dpi,
PixelFormat.Bgra8888,
@ -385,6 +681,50 @@ namespace Avalonia.Controls.Primitives
return bitmap;
}
/// <summary>
/// Converts the given bitmap (in raw BGRA pre-multiplied alpha pixels) into an image brush
/// that can be used in the UI.
/// </summary>
/// <param name="bgraPixelData">The bitmap (in raw BGRA pre-multiplied alpha pixels)
/// to convert to a brush.</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="ImageBrush"/>.</returns>
public static IBrush? BitmapToBrushAsync(
byte[] bgraPixelData,
int pixelWidth,
int pixelHeight)
{
if (bgraPixelData.Length == 0 ||
(pixelWidth == 0 &&
pixelHeight == 0))
{
return null;
}
// 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, 0, frameBuffer.Address, bgraPixelData.Length);
}
var brush = new ImageBrush(bitmap)
{
Stretch = Stretch.Fill
};
return brush;
}
/// <summary>
/// Gets the relative (perceptual) luminance/brightness of the given color.
/// 1 is closer to white while 0 is closer to black.

18
src/Avalonia.Controls.ColorPicker/ColorModel.cs

@ -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
}
}

143
src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs

@ -0,0 +1,143 @@
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);
/// <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>
/// Defines the <see cref="ColorComponent"/> property.
/// </summary>
public static readonly StyledProperty<ColorComponent> ColorComponentProperty =
AvaloniaProperty.Register<ColorSlider, ColorComponent>(
nameof(ColorComponent),
ColorComponent.Component1);
/// <summary>
/// Gets or sets the color component represented by the slider.
/// </summary>
public ColorComponent ColorComponent
{
get => GetValue(ColorComponentProperty);
set => SetValue(ColorComponentProperty, value);
}
/// <summary>
/// Defines the <see cref="ColorModel"/> property.
/// </summary>
public static readonly StyledProperty<ColorModel> ColorModelProperty =
AvaloniaProperty.Register<ColorSlider, ColorModel>(
nameof(ColorModel),
ColorModel.Rgba);
/// <summary>
/// Gets or sets the active color model used by the slider.
/// </summary>
public ColorModel ColorModel
{
get => GetValue(ColorModelProperty);
set => SetValue(ColorModelProperty, value);
}
/// <summary>
/// Defines the <see cref="HsvColor"/> property.
/// </summary>
public static readonly StyledProperty<HsvColor> HsvColorProperty =
AvaloniaProperty.Register<ColorSlider, HsvColor>(
nameof(HsvColor),
Colors.White.ToHsv());
/// <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>
/// Defines the <see cref="IsAlphaMaxForced"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsAlphaMaxForcedProperty =
AvaloniaProperty.Register<ColorSlider, bool>(
nameof(IsAlphaMaxForced),
true);
/// <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>
/// Defines the <see cref="IsAutoUpdatingEnabled"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsAutoUpdatingEnabledProperty =
AvaloniaProperty.Register<ColorSlider, bool>(
nameof(IsAutoUpdatingEnabled),
true);
/// <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>
/// Defines the <see cref="IsSaturationValueMaxForced"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsSaturationValueMaxForcedProperty =
AvaloniaProperty.Register<ColorSlider, bool>(
nameof(IsSaturationValueMaxForced),
true);
/// <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);
}
}
}

221
src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs

@ -0,0 +1,221 @@
using System;
using Avalonia.Media;
using Avalonia.Utilities;
namespace Avalonia.Controls.Primitives
{
/// <summary>
/// A slider with a background that represents a single color component.
/// </summary>
public partial class ColorSlider : Slider
{
private Size cachedSize = Size.Empty;
/// <summary>
/// Initializes a new instance of the <see cref="ColorSlider"/> class.
/// </summary>
public ColorSlider() : base()
{
}
/// <summary>
/// Update the slider's Foreground and Background brushes based on the current slider state and color.
/// </summary>
/// <remarks>
/// Manually refreshes the background gradient of the slider.
/// This is callable separately for performance reasons.
/// </remarks>
public void UpdateColors()
{
HsvColor hsvColor = HsvColor;
// Calculate and set the background
UpdateBackground(hsvColor);
// Calculate and set the foreground ensuring contrast with the background
Color rgbColor = hsvColor.ToRgb();
Color selectedRgbColor;
double sliderPercent = Value / (Maximum - Minimum);
var component = ColorComponent;
if (ColorModel == ColorModel.Hsva)
{
if (IsAlphaMaxForced &&
component != ColorComponent.Alpha)
{
hsvColor = new HsvColor(1.0, hsvColor.H, hsvColor.S, hsvColor.V);
}
switch (component)
{
case ColorComponent.Component1:
{
var componentValue = MathUtilities.Clamp(sliderPercent * 360.0, 0.0, 360.0);
hsvColor = new HsvColor(
hsvColor.A,
componentValue,
IsSaturationValueMaxForced ? 1.0 : hsvColor.S,
IsSaturationValueMaxForced ? 1.0 : hsvColor.V);
break;
}
case ColorComponent.Component2:
{
var componentValue = MathUtilities.Clamp(sliderPercent * 1.0, 0.0, 1.0);
hsvColor = new HsvColor(
hsvColor.A,
hsvColor.H,
componentValue,
IsSaturationValueMaxForced ? 1.0 : hsvColor.V);
break;
}
case ColorComponent.Component3:
{
var componentValue = MathUtilities.Clamp(sliderPercent * 1.0, 0.0, 1.0);
hsvColor = new HsvColor(
hsvColor.A,
hsvColor.H,
IsSaturationValueMaxForced ? 1.0 : hsvColor.S,
componentValue);
break;
}
}
selectedRgbColor = hsvColor.ToRgb();
}
else
{
if (IsAlphaMaxForced &&
component != ColorComponent.Alpha)
{
rgbColor = new Color(255, rgbColor.R, rgbColor.G, rgbColor.B);
}
byte componentValue = Convert.ToByte(MathUtilities.Clamp(sliderPercent * 255, 0, 255));
switch (component)
{
case ColorComponent.Component1:
rgbColor = new Color(rgbColor.A, componentValue, rgbColor.G, rgbColor.B);
break;
case ColorComponent.Component2:
rgbColor = new Color(rgbColor.A, rgbColor.R, componentValue, rgbColor.B);
break;
case ColorComponent.Component3:
rgbColor = new Color(rgbColor.A, rgbColor.R, rgbColor.G, componentValue);
break;
}
selectedRgbColor = rgbColor;
}
//var converter = new ContrastBrushConverter();
//this.Foreground = converter.Convert(selectedRgbColor, typeof(Brush), this.DefaultForeground, null) as Brush;
return;
}
/// <summary>
/// Generates a new background image for the color slider and applies it.
/// </summary>
private async void UpdateBackground(HsvColor color)
{
// Updates may be requested when sliders are not in the visual tree.
// For first-time load this is handled by the Loaded event.
// However, after that problems may arise, consider the following case:
//
// (1) Backgrounds are drawn normally the first time on Loaded.
// Actual height/width are available.
// (2) The palette tab is selected which has no sliders
// (3) The picker flyout is closed
// (4) Externally the color is changed
// The color change will trigger slider background updates but
// with the flyout closed, actual height/width are zero.
// No zero size bitmap can be generated.
// (5) The picker flyout is re-opened by the user and the default
// last-opened tab will be viewed: palette.
// No loaded events will be fired for sliders. The color change
// event was already handled in (4). The sliders will never
// be updated.
//
// In this case the sliders become out of sync with the Color because there is no way
// to tell when they actually come into view. To work around this, force a re-render of
// the background with the last size of the slider. This last size will be when it was
// last loaded or updated.
//
// In the future additional consideration may be required for SizeChanged of the control.
// This work-around will also cause issues if display scaling changes in the special
// case where cached sizes are required.
var width = Convert.ToInt32(Bounds.Width);
var height = Convert.ToInt32(Bounds.Height);
if (width == 0 || height == 0)
{
// Attempt to use the last size if it was available
if (cachedSize.IsDefault == false)
{
width = Convert.ToInt32(cachedSize.Width);
height = Convert.ToInt32(cachedSize.Height);
}
}
else
{
cachedSize = new Size(width, height);
}
var bitmap = await ColorHelpers.CreateComponentBitmapAsync(
width,
height,
Orientation,
ColorModel,
ColorComponent,
color,
IsAlphaMaxForced,
IsSaturationValueMaxForced);
if (bitmap != null)
{
Background = ColorHelpers.BitmapToBrushAsync(bitmap, width, height);
}
return;
}
/// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
bool update = false;
if (change.Property == ColorProperty)
{
// Sync with HSV (which is primary)
HsvColor = Color.ToHsv();
update = true;
}
else if (change.Property == HsvColorProperty)
{
update = true;
}
else if (change.Property == BoundsProperty)
{
update = true;
}
if (update && IsAutoUpdatingEnabled)
{
UpdateColors();
}
base.OnPropertyChanged(change);
}
}
}

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

@ -22,6 +22,7 @@
<!-- 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>

172
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml

@ -0,0 +1,172 @@
<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="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="SliderThumbStyle"
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="SliderThumbStyle"
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.SliderThumbStyle">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource SliderThumbBackground}" />
<Setter Property="BorderThickness" Value="3" />
</Style>
<!-- PointerOver State -->
<Style Selector="ColorSlider:pointerover /template/ Thumb.SliderThumbStyle">
<Setter Property="BorderBrush" Value="{DynamicResource SliderThumbBackgroundPointerOver}" />
</Style>
<!-- Pressed State -->
<Style Selector="ColorSlider:pressed /template/ Thumb.SliderThumbStyle">
<Setter Property="BorderBrush" Value="{DynamicResource SliderThumbBackgroundPressed}" />
</Style>
</Styles>
Loading…
Cancel
Save