442 lines
16 KiB
C#
442 lines
16 KiB
C#
// Adapted from https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Primitives/Track.cs
|
|
|
|
using System;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Controls.Primitives;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using HandyControl.Data;
|
|
|
|
namespace HandyControl.Controls
|
|
{
|
|
public class RangeTrack : FrameworkElement
|
|
{
|
|
private RepeatButton _increaseButton;
|
|
|
|
private RepeatButton _centerButton;
|
|
|
|
private RepeatButton _decreaseButton;
|
|
|
|
private RangeThumb _thumbStart;
|
|
|
|
private RangeThumb _thumbEnd;
|
|
|
|
private Visual[] _visualChildren;
|
|
|
|
private double Density { get; set; } = double.NaN;
|
|
|
|
private double ThumbStartCenterOffset { get; set; } = double.NaN;
|
|
|
|
public RepeatButton DecreaseRepeatButton
|
|
{
|
|
get => _decreaseButton;
|
|
set
|
|
{
|
|
if (Equals(_increaseButton, value) || Equals(_centerButton, value))
|
|
{
|
|
throw new NotSupportedException("SameButtons");
|
|
}
|
|
UpdateComponent(_decreaseButton, value);
|
|
_decreaseButton = value;
|
|
|
|
if (_decreaseButton != null)
|
|
{
|
|
CommandManager.InvalidateRequerySuggested(); // Should post an idle queue item to update IsEnabled on button
|
|
}
|
|
}
|
|
}
|
|
|
|
public RepeatButton CenterRepeatButton
|
|
{
|
|
get => _centerButton;
|
|
set
|
|
{
|
|
if (Equals(_increaseButton, value) || Equals(_decreaseButton, value))
|
|
{
|
|
throw new NotSupportedException("SameButtons");
|
|
}
|
|
UpdateComponent(_centerButton, value);
|
|
_centerButton = value;
|
|
|
|
if (_centerButton != null)
|
|
{
|
|
CommandManager.InvalidateRequerySuggested(); // Should post an idle queue item to update IsEnabled on button
|
|
}
|
|
}
|
|
}
|
|
|
|
public RepeatButton IncreaseRepeatButton
|
|
{
|
|
get => _increaseButton;
|
|
set
|
|
{
|
|
if (Equals(_decreaseButton, value) || Equals(_centerButton, value))
|
|
{
|
|
throw new NotSupportedException("SameButtons");
|
|
}
|
|
UpdateComponent(_increaseButton, value);
|
|
_increaseButton = value;
|
|
|
|
if (_increaseButton != null)
|
|
{
|
|
CommandManager.InvalidateRequerySuggested(); // Should post an idle queue item to update IsEnabled on button
|
|
}
|
|
}
|
|
}
|
|
|
|
public RangeThumb ThumbStart
|
|
{
|
|
get => _thumbStart;
|
|
set
|
|
{
|
|
UpdateComponent(_thumbStart, value);
|
|
_thumbStart = value;
|
|
}
|
|
}
|
|
|
|
public RangeThumb ThumbEnd
|
|
{
|
|
get => _thumbEnd;
|
|
set
|
|
{
|
|
UpdateComponent(_thumbEnd, value);
|
|
_thumbEnd = value;
|
|
}
|
|
}
|
|
|
|
static RangeTrack()
|
|
{
|
|
IsEnabledProperty.OverrideMetadata(typeof(RangeTrack), new UIPropertyMetadata(OnIsEnabledChanged));
|
|
}
|
|
|
|
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
|
|
"Orientation", typeof(Orientation), typeof(RangeTrack),
|
|
new FrameworkPropertyMetadata(default(Orientation), FrameworkPropertyMetadataOptions.AffectsMeasure));
|
|
|
|
public Orientation Orientation
|
|
{
|
|
get => (Orientation) GetValue(OrientationProperty);
|
|
set => SetValue(OrientationProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register(
|
|
"Minimum", typeof(double), typeof(RangeTrack),
|
|
new FrameworkPropertyMetadata(ValueBoxes.Double0Box, FrameworkPropertyMetadataOptions.AffectsArrange));
|
|
|
|
public double Minimum
|
|
{
|
|
get => (double) GetValue(MinimumProperty);
|
|
set => SetValue(MinimumProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register(
|
|
"Maximum", typeof(double), typeof(RangeTrack),
|
|
new FrameworkPropertyMetadata(ValueBoxes.Double1Box, FrameworkPropertyMetadataOptions.AffectsArrange));
|
|
|
|
public double Maximum
|
|
{
|
|
get => (double) GetValue(MaximumProperty);
|
|
set => SetValue(MaximumProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty ValueStartProperty = DependencyProperty.Register(
|
|
"ValueStart", typeof(double), typeof(RangeTrack),
|
|
new FrameworkPropertyMetadata(ValueBoxes.Double0Box,
|
|
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
|
|
FrameworkPropertyMetadataOptions.AffectsArrange));
|
|
|
|
public double ValueStart
|
|
{
|
|
get => (double) GetValue(ValueStartProperty);
|
|
set => SetValue(ValueStartProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty ValueEndProperty = DependencyProperty.Register(
|
|
"ValueEnd", typeof(double), typeof(RangeTrack),
|
|
new FrameworkPropertyMetadata(ValueBoxes.Double0Box,
|
|
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
|
|
FrameworkPropertyMetadataOptions.AffectsArrange));
|
|
|
|
public double ValueEnd
|
|
{
|
|
get => (double) GetValue(ValueEndProperty);
|
|
set => SetValue(ValueEndProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty IsDirectionReversedProperty = DependencyProperty.Register(
|
|
"IsDirectionReversed", typeof(bool), typeof(RangeTrack), new PropertyMetadata(ValueBoxes.FalseBox));
|
|
|
|
public bool IsDirectionReversed
|
|
{
|
|
get => (bool) GetValue(IsDirectionReversedProperty);
|
|
set => SetValue(IsDirectionReversedProperty, ValueBoxes.BooleanBox(value));
|
|
}
|
|
|
|
protected override Visual GetVisualChild(int index)
|
|
{
|
|
if (_visualChildren?[index] == null)
|
|
{
|
|
// ReSharper disable once UseNameofExpression
|
|
// ReSharper disable once LocalizableElement
|
|
throw new ArgumentOutOfRangeException("index", index, "ArgumentOutOfRange");
|
|
}
|
|
return _visualChildren[index];
|
|
}
|
|
|
|
protected override Size MeasureOverride(Size availableSize)
|
|
{
|
|
var desiredSize = new Size();
|
|
|
|
// Only measure thumb
|
|
// Repeat buttons will be sized based on thumb
|
|
if (_thumbStart != null)
|
|
{
|
|
_thumbStart.Measure(availableSize);
|
|
desiredSize = _thumbStart.DesiredSize;
|
|
}
|
|
|
|
if (_thumbEnd != null)
|
|
{
|
|
_thumbEnd.Measure(availableSize);
|
|
desiredSize = new Size(Math.Max(_thumbEnd.DesiredSize.Width, desiredSize.Width),
|
|
Math.Max(_thumbEnd.DesiredSize.Height, desiredSize.Height));
|
|
}
|
|
|
|
return desiredSize;
|
|
}
|
|
|
|
private static void CoerceLength(ref double componentLength, double trackLength)
|
|
{
|
|
if (componentLength < 0)
|
|
{
|
|
componentLength = 0;
|
|
}
|
|
else if (componentLength > trackLength || double.IsNaN(componentLength))
|
|
{
|
|
componentLength = trackLength;
|
|
}
|
|
}
|
|
|
|
protected override Size ArrangeOverride(Size arrangeSize)
|
|
{
|
|
var isVertical = Orientation == Orientation.Vertical;
|
|
|
|
ComputeLengths(arrangeSize, isVertical, out var decreaseButtonLength, out var centerButtonLength,
|
|
out var increaseButtonLength, out var thumbStartLength, out var thumbEndLength);
|
|
|
|
var offset = new Point();
|
|
var pieceSize = arrangeSize;
|
|
var isDirectionReversed = IsDirectionReversed;
|
|
|
|
if (isVertical)
|
|
{
|
|
CoerceLength(ref decreaseButtonLength, arrangeSize.Height);
|
|
CoerceLength(ref centerButtonLength, arrangeSize.Height);
|
|
CoerceLength(ref increaseButtonLength, arrangeSize.Height);
|
|
CoerceLength(ref thumbStartLength, arrangeSize.Height);
|
|
CoerceLength(ref thumbEndLength, arrangeSize.Height);
|
|
|
|
offset.Y = isDirectionReversed ? decreaseButtonLength + thumbEndLength + centerButtonLength + thumbStartLength : 0;
|
|
pieceSize.Height = increaseButtonLength;
|
|
|
|
IncreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
|
|
|
|
offset.Y = isDirectionReversed ? decreaseButtonLength + thumbEndLength : increaseButtonLength + thumbStartLength;
|
|
pieceSize.Height = centerButtonLength;
|
|
|
|
CenterRepeatButton?.Arrange(new Rect(offset, pieceSize));
|
|
|
|
offset.Y = isDirectionReversed ? 0 : increaseButtonLength + thumbStartLength + centerButtonLength + thumbEndLength;
|
|
pieceSize.Height = decreaseButtonLength;
|
|
|
|
DecreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
|
|
|
|
offset.Y = isDirectionReversed
|
|
? decreaseButtonLength + thumbEndLength + centerButtonLength
|
|
: increaseButtonLength + thumbStartLength + centerButtonLength;
|
|
pieceSize.Height = thumbStartLength;
|
|
|
|
ThumbStart?.Arrange(new Rect(offset, pieceSize));
|
|
ThumbStartCenterOffset = offset.Y + thumbStartLength * 0.5;
|
|
|
|
offset.Y = isDirectionReversed ? decreaseButtonLength : increaseButtonLength;
|
|
pieceSize.Height = thumbEndLength;
|
|
|
|
ThumbEnd?.Arrange(new Rect(offset, pieceSize));
|
|
}
|
|
else
|
|
{
|
|
CoerceLength(ref decreaseButtonLength, arrangeSize.Width);
|
|
CoerceLength(ref centerButtonLength, arrangeSize.Width);
|
|
CoerceLength(ref increaseButtonLength, arrangeSize.Width);
|
|
CoerceLength(ref thumbStartLength, arrangeSize.Width);
|
|
CoerceLength(ref thumbEndLength, arrangeSize.Width);
|
|
|
|
offset.X = isDirectionReversed ? 0 : decreaseButtonLength + thumbEndLength + centerButtonLength + thumbStartLength;
|
|
pieceSize.Width = increaseButtonLength;
|
|
|
|
IncreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
|
|
|
|
offset.X = isDirectionReversed ? increaseButtonLength + thumbStartLength : decreaseButtonLength + thumbEndLength;
|
|
pieceSize.Width = centerButtonLength;
|
|
|
|
CenterRepeatButton?.Arrange(new Rect(offset, pieceSize));
|
|
|
|
offset.X = isDirectionReversed ? increaseButtonLength + thumbStartLength + centerButtonLength + thumbEndLength : 0;
|
|
pieceSize.Width = decreaseButtonLength;
|
|
|
|
DecreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
|
|
|
|
offset.X = isDirectionReversed ? increaseButtonLength : decreaseButtonLength;
|
|
pieceSize.Width = thumbStartLength;
|
|
|
|
ThumbStart?.Arrange(new Rect(offset, pieceSize));
|
|
ThumbStartCenterOffset = offset.X + thumbStartLength * 0.5;
|
|
|
|
offset.X = isDirectionReversed
|
|
? increaseButtonLength + thumbStartLength + centerButtonLength
|
|
: decreaseButtonLength + thumbEndLength + centerButtonLength;
|
|
pieceSize.Width = thumbEndLength;
|
|
|
|
ThumbEnd?.Arrange(new Rect(offset, pieceSize));
|
|
}
|
|
|
|
return arrangeSize;
|
|
}
|
|
|
|
private void ComputeLengths(Size arrangeSize, bool isVertical, out double decreaseButtonLength,
|
|
out double centerButtonLength, out double increaseButtonLength, out double thumbStartLength,
|
|
out double thumbEndLength)
|
|
{
|
|
var min = Minimum;
|
|
var range = Math.Max(0.0, Maximum - min);
|
|
var offsetStart = Math.Min(range, ValueStart - min);
|
|
var offsetEnd = Math.Min(range, ValueEnd - min);
|
|
|
|
double trackLength;
|
|
|
|
// Compute thumb size
|
|
if (isVertical)
|
|
{
|
|
trackLength = arrangeSize.Height;
|
|
thumbStartLength = _thumbStart?.DesiredSize.Height ?? 0;
|
|
thumbEndLength = _thumbEnd?.DesiredSize.Height ?? 0;
|
|
}
|
|
else
|
|
{
|
|
trackLength = arrangeSize.Width;
|
|
thumbStartLength = _thumbStart?.DesiredSize.Width ?? 0;
|
|
thumbEndLength = _thumbEnd?.DesiredSize.Width ?? 0;
|
|
}
|
|
|
|
CoerceLength(ref thumbStartLength, trackLength);
|
|
CoerceLength(ref thumbEndLength, trackLength);
|
|
|
|
var remainingTrackLength = trackLength - thumbStartLength - thumbEndLength;
|
|
|
|
decreaseButtonLength = remainingTrackLength * offsetStart / range;
|
|
CoerceLength(ref decreaseButtonLength, remainingTrackLength);
|
|
|
|
centerButtonLength = remainingTrackLength * offsetEnd / range - decreaseButtonLength;
|
|
CoerceLength(ref centerButtonLength, remainingTrackLength);
|
|
|
|
increaseButtonLength = remainingTrackLength - decreaseButtonLength - centerButtonLength;
|
|
CoerceLength(ref increaseButtonLength, remainingTrackLength);
|
|
|
|
Density = range / remainingTrackLength;
|
|
}
|
|
|
|
protected override int VisualChildrenCount
|
|
{
|
|
get
|
|
{
|
|
if (_visualChildren == null) return 0;
|
|
|
|
for (var i = 0; i < _visualChildren.Length; i++)
|
|
{
|
|
if (_visualChildren[i] == null) return i;
|
|
}
|
|
|
|
return _visualChildren.Length;
|
|
}
|
|
}
|
|
|
|
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
if ((bool) e.NewValue)
|
|
{
|
|
Mouse.Synchronize();
|
|
}
|
|
}
|
|
|
|
public virtual double ValueFromPoint(Point pt)
|
|
{
|
|
double val;
|
|
// Find distance from center of thumb to given point.
|
|
if (Orientation == Orientation.Horizontal)
|
|
{
|
|
val = ValueStart + ValueFromDistance(pt.X - ThumbStartCenterOffset, pt.Y - RenderSize.Height * 0.5);
|
|
}
|
|
else
|
|
{
|
|
val = ValueStart + ValueFromDistance(pt.X - RenderSize.Width * 0.5, pt.Y - ThumbStartCenterOffset);
|
|
}
|
|
return Math.Max(Minimum, Math.Min(Maximum, val));
|
|
}
|
|
|
|
public virtual double ValueFromDistance(double horizontal, double vertical)
|
|
{
|
|
double scale = IsDirectionReversed ? -1 : 1;
|
|
return Orientation == Orientation.Horizontal
|
|
? scale * horizontal * Density
|
|
: -1 * scale * vertical * Density;
|
|
}
|
|
|
|
private void UpdateComponent(Control oldValue, Control newValue)
|
|
{
|
|
if (oldValue != newValue)
|
|
{
|
|
_visualChildren ??= new Visual[5];
|
|
|
|
if (oldValue != null)
|
|
{
|
|
// notify the visual layer that the old component has been removed.
|
|
RemoveVisualChild(oldValue);
|
|
}
|
|
|
|
// Remove the old value from our z index list and add new value to end
|
|
int i = 0;
|
|
while (i < 5)
|
|
{
|
|
// Array isn't full, break
|
|
if (_visualChildren[i] == null)
|
|
break;
|
|
|
|
// found the old value
|
|
if (_visualChildren[i] == oldValue)
|
|
{
|
|
// Move values down until end of array or a null element
|
|
while (i < 4 && _visualChildren[i + 1] != null)
|
|
{
|
|
_visualChildren[i] = _visualChildren[i + 1];
|
|
i++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
// Add newValue at end of z-order
|
|
_visualChildren[i] = newValue;
|
|
|
|
AddVisualChild(newValue);
|
|
|
|
InvalidateMeasure();
|
|
InvalidateArrange();
|
|
}
|
|
}
|
|
}
|
|
}
|