Files
Shrlalgo.RvKits/WPFDark/Controls/BiaHsvWheelCursor.cs
ShrlAlgo 4d35cadb56 更新
2025-07-11 09:20:23 +08:00

155 lines
5.2 KiB
C#

using System;
using System.Windows;
using System.Windows.Media;
using WPFDark.Internals;
namespace WPFDark.Controls
{
public class BiaHsvWheelCursor : FrameworkElement
{
#region Hue
public double Hue
{
get => _Hue;
set
{
if (NumberHelper.AreClose(value, _Hue) == false)
SetValue(HueProperty, value);
}
}
private double _Hue;
public static readonly DependencyProperty HueProperty =
DependencyProperty.Register(nameof(Hue), typeof(double), typeof(BiaHsvWheelCursor),
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaHsvWheelCursor) s;
self._Hue = (double) e.NewValue;
}));
#endregion
#region Saturation
public double Saturation
{
get => _Saturation;
set
{
if (NumberHelper.AreClose(value, _Saturation) == false)
SetValue(SaturationProperty, value);
}
}
private double _Saturation;
public static readonly DependencyProperty SaturationProperty =
DependencyProperty.Register(nameof(Saturation), typeof(double), typeof(BiaHsvWheelCursor),
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaHsvWheelCursor) s;
self._Saturation = (double) e.NewValue;
}));
#endregion
#region IsReadOnly
public bool IsReadOnly
{
get => _IsReadOnly;
set
{
if (value != _IsReadOnly)
SetValue(IsReadOnlyProperty, value);
}
}
private bool _IsReadOnly;
public static readonly DependencyProperty IsReadOnlyProperty =
DependencyProperty.Register(nameof(IsReadOnly), typeof(bool), typeof(BiaHsvWheelCursor),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaHsvWheelCursor) s;
self._IsReadOnly = (bool) e.NewValue;
}));
#endregion
static BiaHsvWheelCursor()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BiaHsvWheelCursor),
new FrameworkPropertyMetadata(typeof(BiaHsvWheelCursor)));
}
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly PropertyChangeNotifier _isEnabledChangeNotifier;
public BiaHsvWheelCursor()
{
IsHitTestVisible = false;
_isEnabledChangeNotifier = new PropertyChangeNotifier(this, IsEnabledProperty);
_isEnabledChangeNotifier.ValueChanged += (_, __) => InvalidateVisual();
}
protected override void OnRender(DrawingContext dc)
{
if (ActualWidth <= 1 ||
ActualHeight <= 1)
return;
var rounder = new LayoutRounder(this);
// Cursor
this.DrawPointCursor(rounder, dc, MakeCursorRenderPos(rounder), IsEnabled, IsReadOnly);
}
private ImmutableVec2_double MakeCursorRenderPos(in LayoutRounder rounder)
=> MakeCursorRenderPos(rounder, ActualWidth, ActualHeight, Hue, Saturation);
internal static ImmutableVec2_double MakeCursorRenderPos(
in LayoutRounder rounder,
double actualWidth,
double actualHeight,
double hue,
double saturation)
{
hue = NumberHelper.Clamp01(hue);
saturation = NumberHelper.Clamp01(saturation);
var bw = rounder.RoundLayoutValue(FrameworkElementExtensions.BorderWidth);
var w = rounder.RoundLayoutValue(actualWidth - bw * 2);
var h = rounder.RoundLayoutValue(actualHeight - bw * 2);
var r = hue * 2.0 * Math.PI;
var (cx, cy) = MakeAspectRatioCorrection(actualWidth, actualHeight);
var x = bw + Math.Cos(r) * saturation * (w * 0.5) / cx + w * 0.5;
var y = bw + Math.Sin(r) * saturation * (h * 0.5) / cy + h * 0.5;
return new ImmutableVec2_double(rounder.RoundLayoutValue(x), rounder.RoundLayoutValue(y));
}
internal static (double X, double Y) MakeAspectRatioCorrection(double actualWidth, double actualHeight)
=> actualWidth > actualHeight
? (actualWidth / actualHeight, 1.0)
: (1.0, actualHeight / actualWidth);
}
}