using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using WPFDark.Controls.Effects; using WPFDark.Internals; namespace WPFDark.Controls { internal class BiaHsvBoxBackground : Canvas { #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(BiaHsvBoxBackground), new FrameworkPropertyMetadata( 0.0, (s, e) => { var self = (BiaHsvBoxBackground) 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(BiaHsvBoxBackground), new FrameworkPropertyMetadata( 0.0, (s, e) => { var self = (BiaHsvBoxBackground) s; self.saturation = (double) e.NewValue; })); #endregion #region Value public double Value { get => value; set { if (NumberHelper.AreClose(value, this.value) == false) SetValue(ValueProperty, value); } } private double value; public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(double), typeof(BiaHsvBoxBackground), new FrameworkPropertyMetadata( 0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender, (s, e) => { var self = (BiaHsvBoxBackground) s; self.value = (double) e.NewValue; self.effect.Value = NumberHelper.Clamp01(self.value); })); #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(BiaHsvBoxBackground), new FrameworkPropertyMetadata( false, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender, (s, e) => { var self = (BiaHsvBoxBackground) s; self.isReadOnly = (bool) e.NewValue; })); #endregion #region StartedContinuousEditingCommand public ICommand? StartedContinuousEditingCommand { get => startedContinuousEditingCommand; set { if (value != startedContinuousEditingCommand) SetValue(StartedContinuousEditingCommandProperty, value); } } private ICommand? startedContinuousEditingCommand; public static readonly DependencyProperty StartedContinuousEditingCommandProperty = DependencyProperty.Register( nameof(StartedContinuousEditingCommand), typeof(ICommand), typeof(BiaHsvBoxBackground), new PropertyMetadata( default(ICommand), (s, e) => { var self = (BiaHsvBoxBackground) s; self.startedContinuousEditingCommand = (ICommand) e.NewValue; })); #endregion #region EndContinuousEditingCommand public ICommand? EndContinuousEditingCommand { get => endContinuousEditingCommand; set { if (value != endContinuousEditingCommand) SetValue(EndContinuousEditingCommandProperty, value); } } private ICommand? endContinuousEditingCommand; public static readonly DependencyProperty EndContinuousEditingCommandProperty = DependencyProperty.Register( nameof(EndContinuousEditingCommand), typeof(ICommand), typeof(BiaHsvBoxBackground), new PropertyMetadata( default(ICommand), (s, e) => { var self = (BiaHsvBoxBackground) s; self.endContinuousEditingCommand = (ICommand) e.NewValue; })); #endregion #region StartedBatchEditingCommand public ICommand? StartedBatchEditingCommand { get => startedBatchEditingCommand; set { if (value != startedBatchEditingCommand) SetValue(StartedBatchEditingCommandProperty, value); } } private ICommand? startedBatchEditingCommand; public static readonly DependencyProperty StartedBatchEditingCommandProperty = DependencyProperty.Register( nameof(StartedBatchEditingCommand), typeof(ICommand), typeof(BiaHsvBoxBackground), new PropertyMetadata( default(ICommand), (s, e) => { var self = (BiaHsvBoxBackground) s; self.startedBatchEditingCommand = (ICommand) e.NewValue; })); #endregion #region EndBatchEditingCommand public ICommand? EndBatchEditingCommand { get => endBatchEditingCommand; set { if (value != endBatchEditingCommand) SetValue(EndBatchEditingCommandProperty, value); } } private ICommand? endBatchEditingCommand; public static readonly DependencyProperty EndBatchEditingCommandProperty = DependencyProperty.Register( nameof(EndBatchEditingCommand), typeof(ICommand), typeof(BiaHsvBoxBackground), new PropertyMetadata( default(ICommand), (s, e) => { var self = (BiaHsvBoxBackground) s; self.endBatchEditingCommand = (ICommand) e.NewValue; })); #endregion private readonly HsvBoxBackgroundEffect effect = new (); // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable private readonly PropertyChangeNotifier isEnabledChangeNotifier; static BiaHsvBoxBackground() { DefaultStyleKeyProperty.OverrideMetadata(typeof(BiaHsvBoxBackground), new FrameworkPropertyMetadata(typeof(BiaHsvBoxBackground))); } public BiaHsvBoxBackground() { Effect = effect; RenderOptions.SetEdgeMode(this, EdgeMode.Aliased); effect.DisableColor = ((ByteColor)ThemeManager.Current.TryFindResource("InactiveColorPickerColorKey")).ToPoint3D(); isEnabledChangeNotifier = new PropertyChangeNotifier(this, IsEnabledProperty); isEnabledChangeNotifier.ValueChanged += (_, _) => { effect.IsEnabled = IsEnabled ? 1.0f : 0.0f; InvalidateVisual(); }; } protected override void OnRender(DrawingContext dc) { base.OnRender(dc); if (ActualWidth <= 1 || ActualHeight <= 1) return; var rounder = new LayoutRounder(this); var rect = rounder.RoundRenderRectangle(true); dc.DrawRectangle(Brushes.Transparent, null, rect); } private void UpdateParams(in LayoutRounder rounder, MouseEventArgs e) { var pos = e.GetPosition(this); var s = rounder.RoundLayoutValue(1); var x = (pos.X - s) / (ActualWidth - s * 2); var y = (pos.Y - s) / (ActualHeight - s * 2); x = NumberHelper.Clamp01(x); y = NumberHelper.Clamp01(y); Hue = x; Saturation = 1 - y; } private bool isMouseDown; private bool isContinuousEdited; private (double, double) continuousEditingStartValue; protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { base.OnMouseLeftButtonDown(e); if (IsReadOnly) return; isMouseDown = true; GuiHelper.HideCursor(); var rounder = new LayoutRounder(this); continuousEditingStartValue = (Hue, Saturation); isContinuousEdited = true; StartedContinuousEditingCommand?.ExecuteIfCan(null); UpdateParams(rounder, e); CaptureMouse(); this.SetMouseClipping(); e.Handled = true; } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (IsReadOnly) return; if (isMouseDown == false) return; var rounder = new LayoutRounder(this); UpdateParams(rounder, e); e.Handled = true; } protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { base.OnMouseLeftButtonUp(e); if (IsReadOnly) return; if (isMouseDown == false) return; // マウス位置を補正する { var rounder = new LayoutRounder(this); var pos = BiaHsvBoxCursor.MakeCursorRenderPos(rounder, ActualWidth, ActualHeight, Hue, Saturation); var mousePos = PointToScreen(Unsafe.As(ref pos)); Win32Helper.SetCursorPos((int) mousePos.X, (int) mousePos.Y); } isMouseDown = false; GuiHelper.ShowCursor(); this.ResetMouseClipping(); ReleaseMouseCapture(); if (isContinuousEdited) { if (EndContinuousEditingCommand != null) { if (EndContinuousEditingCommand.CanExecute(null)) { var changedValue = (Hue, Saturation); (Hue, Saturation) = continuousEditingStartValue; EndContinuousEditingCommand.Execute(null); StartedBatchEditingCommand?.ExecuteIfCan(null); (Hue, Saturation) = changedValue; EndBatchEditingCommand?.ExecuteIfCan(null); } } isContinuousEdited = false; } e.Handled = true; } protected override void OnMouseLeave(MouseEventArgs e) { base.OnMouseLeave(e); if (isMouseDown) { isMouseDown = false; ReleaseMouseCapture(); GuiHelper.ShowCursor(); this.ResetMouseClipping(); } e.Handled = true; } } }