459 lines
14 KiB
C#
459 lines
14 KiB
C#
|
|
using System;
|
|||
|
|
using System.Collections;
|
|||
|
|
using System.Diagnostics;
|
|||
|
|
using System.Windows;
|
|||
|
|
using System.Windows.Controls;
|
|||
|
|
using System.Windows.Controls.Primitives;
|
|||
|
|
using System.Windows.Data;
|
|||
|
|
using System.Windows.Input;
|
|||
|
|
using System.Windows.Media;
|
|||
|
|
using System.Windows.Media.Effects;
|
|||
|
|
using System.Windows.Threading;
|
|||
|
|
using WPFDark.Internals;
|
|||
|
|
|
|||
|
|
namespace WPFDark.Controls
|
|||
|
|
{
|
|||
|
|
public class BiaColorSelector : FrameworkElement
|
|||
|
|
{
|
|||
|
|
#region Value
|
|||
|
|
|
|||
|
|
public DoubleColor Value
|
|||
|
|
{
|
|||
|
|
get => _Value;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (value.Equals(_Value) == false)
|
|||
|
|
SetValue(ValueProperty, value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private DoubleColor _Value = DoubleColor.Zero;
|
|||
|
|
|
|||
|
|
public static readonly DependencyProperty ValueProperty =
|
|||
|
|
DependencyProperty.Register(nameof(Value), typeof(DoubleColor), typeof(BiaColorSelector),
|
|||
|
|
new FrameworkPropertyMetadata(
|
|||
|
|
DoubleColor.Zero,
|
|||
|
|
FrameworkPropertyMetadataOptions.AffectsRender |
|
|||
|
|
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
|
|||
|
|
(s, e) =>
|
|||
|
|
{
|
|||
|
|
var self = (BiaColorSelector) s;
|
|||
|
|
self._Value = (DoubleColor) e.NewValue;
|
|||
|
|
|
|||
|
|
self._background = Caches.GetSolidColorBrush(self._Value.ByteColor);
|
|||
|
|
self._background.Freeze();
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region BorderColor
|
|||
|
|
|
|||
|
|
public ByteColor BorderColor
|
|||
|
|
{
|
|||
|
|
get => _BorderColor;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (value != _BorderColor)
|
|||
|
|
SetValue(BorderColorProperty, value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private ByteColor _BorderColor = ByteColor.Transparent;
|
|||
|
|
|
|||
|
|
public static readonly DependencyProperty BorderColorProperty =
|
|||
|
|
DependencyProperty.Register(nameof(BorderColor), typeof(ByteColor), typeof(BiaColorSelector),
|
|||
|
|
new FrameworkPropertyMetadata(
|
|||
|
|
Boxes.ByteColorTransparent,
|
|||
|
|
FrameworkPropertyMetadataOptions.AffectsRender |
|
|||
|
|
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
|
|||
|
|
(s, e) =>
|
|||
|
|
{
|
|||
|
|
var self = (BiaColorSelector) s;
|
|||
|
|
self._BorderColor = (ByteColor) e.NewValue;
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region CornerRadius
|
|||
|
|
|
|||
|
|
public double CornerRadius
|
|||
|
|
{
|
|||
|
|
get => _CornerRadius;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (NumberHelper.AreClose(value, _CornerRadius) == false)
|
|||
|
|
SetValue(CornerRadiusProperty, value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private double _CornerRadius;
|
|||
|
|
|
|||
|
|
public static readonly DependencyProperty CornerRadiusProperty =
|
|||
|
|
DependencyProperty.Register(nameof(CornerRadius), typeof(double), typeof(BiaColorSelector),
|
|||
|
|
new FrameworkPropertyMetadata(
|
|||
|
|
0.0,
|
|||
|
|
FrameworkPropertyMetadataOptions.AffectsRender |
|
|||
|
|
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
|
|||
|
|
(s, e) =>
|
|||
|
|
{
|
|||
|
|
var self = (BiaColorSelector) s;
|
|||
|
|
self._CornerRadius = (double) e.NewValue;
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region Choices
|
|||
|
|
|
|||
|
|
public IEnumerable? Choices
|
|||
|
|
{
|
|||
|
|
get => _Choices;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (!Equals(value, _Choices))
|
|||
|
|
SetValue(ChoicesProperty, value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private IEnumerable? _Choices;
|
|||
|
|
|
|||
|
|
public static readonly DependencyProperty ChoicesProperty =
|
|||
|
|
DependencyProperty.Register(
|
|||
|
|
nameof(Choices),
|
|||
|
|
typeof(IEnumerable),
|
|||
|
|
typeof(BiaColorSelector),
|
|||
|
|
new PropertyMetadata(
|
|||
|
|
default,
|
|||
|
|
(s, e) =>
|
|||
|
|
{
|
|||
|
|
var self = (BiaColorSelector) s;
|
|||
|
|
self._Choices = (IEnumerable) e.NewValue;
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region Columns
|
|||
|
|
|
|||
|
|
public int Columns
|
|||
|
|
{
|
|||
|
|
get => _Columns;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (value != _Columns)
|
|||
|
|
SetValue(ColumnsProperty, value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private int _Columns = 4;
|
|||
|
|
|
|||
|
|
public static readonly DependencyProperty ColumnsProperty =
|
|||
|
|
DependencyProperty.Register(
|
|||
|
|
nameof(Columns),
|
|||
|
|
typeof(int),
|
|||
|
|
typeof(BiaColorSelector),
|
|||
|
|
new PropertyMetadata(
|
|||
|
|
4,
|
|||
|
|
(s, e) =>
|
|||
|
|
{
|
|||
|
|
var self = (BiaColorSelector) s;
|
|||
|
|
self._Columns = (int) 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(BiaColorSelector),
|
|||
|
|
new PropertyMetadata(
|
|||
|
|
default(ICommand),
|
|||
|
|
(s, e) =>
|
|||
|
|
{
|
|||
|
|
var self = (BiaColorSelector) 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(BiaColorSelector),
|
|||
|
|
new PropertyMetadata(
|
|||
|
|
default(ICommand),
|
|||
|
|
(s, e) =>
|
|||
|
|
{
|
|||
|
|
var self = (BiaColorSelector) s;
|
|||
|
|
self._EndContinuousEditingCommand = (ICommand) e.NewValue;
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
static BiaColorSelector()
|
|||
|
|
{
|
|||
|
|
DefaultStyleKeyProperty.OverrideMetadata(typeof(BiaColorSelector),
|
|||
|
|
new FrameworkPropertyMetadata(typeof(BiaColorSelector)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private SolidColorBrush _background = Brushes.Transparent;
|
|||
|
|
|
|||
|
|
protected override void OnRender(DrawingContext dc)
|
|||
|
|
{
|
|||
|
|
if (ActualWidth <= 1 ||
|
|||
|
|
ActualHeight <= 1)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
var rounder = new LayoutRounder(this);
|
|||
|
|
|
|||
|
|
var rect = rounder.RoundRenderRectangle(true);
|
|||
|
|
|
|||
|
|
var borderPen = rounder.GetBorderPen(BorderColor);
|
|||
|
|
|
|||
|
|
if (IsEnabled)
|
|||
|
|
{
|
|||
|
|
if (NumberHelper.AreCloseZero(CornerRadius))
|
|||
|
|
{
|
|||
|
|
if (_background != null && _background.Color.A != 0xFF)
|
|||
|
|
dc.DrawRectangle(Constants.CheckerBrush, null, rect);
|
|||
|
|
|
|||
|
|
dc.DrawRectangle(_background, borderPen, rect);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
if (_background != null && _background.Color.A != 0xFF)
|
|||
|
|
dc.DrawRoundedRectangle(Constants.CheckerBrush, null, rect, CornerRadius, CornerRadius);
|
|||
|
|
|
|||
|
|
dc.DrawRoundedRectangle(_background, borderPen, rect, CornerRadius, CornerRadius);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
if (NumberHelper.AreCloseZero(CornerRadius))
|
|||
|
|
dc.DrawRectangle(null, borderPen, rect);
|
|||
|
|
else
|
|||
|
|
dc.DrawRoundedRectangle(null, borderPen, rect, CornerRadius, CornerRadius);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private ListBox? _items;
|
|||
|
|
private Popup? _popup;
|
|||
|
|
private ScaleTransform? _scale;
|
|||
|
|
|
|||
|
|
// ReSharper disable once ConvertToNullCoalescingCompoundAssignment
|
|||
|
|
private Action FocusThis => _FocusThis ?? (_FocusThis = () => Focus());
|
|||
|
|
private Action? _FocusThis;
|
|||
|
|
|
|||
|
|
private bool IsOpen => _popup is { IsOpen: true };
|
|||
|
|
|
|||
|
|
private void ShowPopup()
|
|||
|
|
{
|
|||
|
|
if (_popup is null)
|
|||
|
|
{
|
|||
|
|
_items = new ListBox
|
|||
|
|
{
|
|||
|
|
IsTabStop = false,
|
|||
|
|
FocusVisualStyle = null,
|
|||
|
|
Margin = new Thickness(0, 0, 3, 3),
|
|||
|
|
Effect = new DropShadowEffect
|
|||
|
|
{
|
|||
|
|
ShadowDepth = 2,
|
|||
|
|
Color = Colors.Black
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_scale = new ScaleTransform();
|
|||
|
|
|
|||
|
|
_popup = new Popup
|
|||
|
|
{
|
|||
|
|
Child = _items,
|
|||
|
|
AllowsTransparency = true,
|
|||
|
|
StaysOpen = false,
|
|||
|
|
Focusable = false,
|
|||
|
|
RenderTransform = _scale,
|
|||
|
|
PlacementTarget = this,
|
|||
|
|
Placement = PlacementMode.Left,
|
|||
|
|
HorizontalOffset = 2,
|
|||
|
|
VerticalOffset = -4
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_items.SetBinding(ItemsControl.ItemsSourceProperty,
|
|||
|
|
new Binding(nameof(Choices))
|
|||
|
|
{
|
|||
|
|
Source = this,
|
|||
|
|
Mode = BindingMode.TwoWay
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
_items.SetBinding(Selector.SelectedItemProperty,
|
|||
|
|
new Binding(nameof(Value))
|
|||
|
|
{
|
|||
|
|
Source = this,
|
|||
|
|
Mode = BindingMode.TwoWay
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
_items.PreviewKeyDown += ListBoxOnPreviewKeyDown;
|
|||
|
|
_items.PreviewMouseLeftButtonDown += ListBoxOnPreviewMouseLeftButtonDown;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Debug.Assert(_scale != null);
|
|||
|
|
Debug.Assert(_items != null);
|
|||
|
|
|
|||
|
|
Mouse.Capture(this, CaptureMode.SubTree);
|
|||
|
|
|
|||
|
|
var s = this.CalcCompositeRenderScale();
|
|||
|
|
_scale.ScaleX = s;
|
|||
|
|
_scale.ScaleY = s;
|
|||
|
|
|
|||
|
|
_popup.IsOpen = true;
|
|||
|
|
|
|||
|
|
if (_items.SelectedItem is null)
|
|||
|
|
{
|
|||
|
|
_items.Focus();
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
var item = _items.ItemContainerGenerator.ContainerFromItem(_items.SelectedItem) as ListBoxItem;
|
|||
|
|
item?.Focus();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
StartedContinuousEditingCommand?.ExecuteIfCan(null);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
|
|||
|
|
{
|
|||
|
|
base.OnMouseLeftButtonDown(e);
|
|||
|
|
|
|||
|
|
if (IsEnabled == false)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
if (IsOpen)
|
|||
|
|
{
|
|||
|
|
Discard();
|
|||
|
|
Dispatcher?.BeginInvoke(DispatcherPriority.Input, FocusThis);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
Focus();
|
|||
|
|
|
|||
|
|
_ContinuousEditingStartValue = Value;
|
|||
|
|
ShowPopup();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
e.Handled = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ListBoxOnPreviewKeyDown(object sender, KeyEventArgs e)
|
|||
|
|
{
|
|||
|
|
if (IsEnabled == false)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
if (IsOpen == false)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
if (e.Key == Key.Escape)
|
|||
|
|
{
|
|||
|
|
Discard();
|
|||
|
|
Focus();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ListBoxOnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|||
|
|
{
|
|||
|
|
Debug.Assert(_popup != null);
|
|||
|
|
|
|||
|
|
var lb = (ListBox) sender;
|
|||
|
|
|
|||
|
|
var pos = Mouse.GetPosition(lb);
|
|||
|
|
var hit = VisualTreeHelper.HitTest(lb, pos);
|
|||
|
|
|
|||
|
|
if (!(hit?.VisualHit is Border))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
SetValue();
|
|||
|
|
|
|||
|
|
_popup.IsOpen = false;
|
|||
|
|
Dispatcher?.BeginInvoke(DispatcherPriority.Input, FocusThis);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private DoubleColor _ContinuousEditingStartValue;
|
|||
|
|
|
|||
|
|
private void SetValue()
|
|||
|
|
{
|
|||
|
|
if (EndContinuousEditingCommand != null)
|
|||
|
|
{
|
|||
|
|
if (EndContinuousEditingCommand.CanExecute(null))
|
|||
|
|
{
|
|||
|
|
var changedValue = Value;
|
|||
|
|
Value = _ContinuousEditingStartValue;
|
|||
|
|
|
|||
|
|
EndContinuousEditingCommand.Execute(null);
|
|||
|
|
|
|||
|
|
Value = changedValue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void Discard()
|
|||
|
|
{
|
|||
|
|
Debug.Assert(_popup != null);
|
|||
|
|
|
|||
|
|
var done = false;
|
|||
|
|
|
|||
|
|
if (EndContinuousEditingCommand != null)
|
|||
|
|
{
|
|||
|
|
if (EndContinuousEditingCommand.CanExecute(null))
|
|||
|
|
{
|
|||
|
|
Value = _ContinuousEditingStartValue;
|
|||
|
|
|
|||
|
|
EndContinuousEditingCommand.Execute(null);
|
|||
|
|
|
|||
|
|
done = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (done == false)
|
|||
|
|
Value = _ContinuousEditingStartValue;
|
|||
|
|
|
|||
|
|
Mouse.Capture(null);
|
|||
|
|
_popup.IsOpen = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected override Size MeasureOverride(Size constraint)
|
|||
|
|
{
|
|||
|
|
// todo:DPI変更時に再描画が行われないため明示的に指示している。要調査。
|
|||
|
|
InvalidateVisual();
|
|||
|
|
|
|||
|
|
return new Size(ActualWidth, ActualHeight);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|