|
|
@@ -11,7 +11,6 @@ using Avalonia.Utilities;
|
|
|
|
|
|
namespace Avalonia.Controls
|
|
|
{
|
|
|
-
|
|
|
/// <summary>
|
|
|
/// Enum which describes how to position the ticks in a <see cref="Slider"/>.
|
|
|
/// </summary>
|
|
|
@@ -84,6 +83,9 @@ namespace Avalonia.Controls
|
|
|
private IDisposable _increaseButtonSubscription;
|
|
|
private IDisposable _increaseButtonReleaseDispose;
|
|
|
private IDisposable _pointerMovedDispose;
|
|
|
+ private IDisposable _trackOnKeyDownDispose;
|
|
|
+
|
|
|
+ private const double Tolerance = 0.0001;
|
|
|
|
|
|
/// <summary>
|
|
|
/// Initializes static members of the <see cref="Slider"/> class.
|
|
|
@@ -95,7 +97,7 @@ namespace Avalonia.Controls
|
|
|
Thumb.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble);
|
|
|
Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e),
|
|
|
RoutingStrategies.Bubble);
|
|
|
-
|
|
|
+
|
|
|
ValueProperty.OverrideMetadata<Slider>(new DirectPropertyMetadata<double>(enableDataValidation: true));
|
|
|
}
|
|
|
|
|
|
@@ -157,13 +159,14 @@ namespace Avalonia.Controls
|
|
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
|
|
{
|
|
|
base.OnApplyTemplate(e);
|
|
|
-
|
|
|
+
|
|
|
_decreaseButtonPressDispose?.Dispose();
|
|
|
_decreaseButtonReleaseDispose?.Dispose();
|
|
|
_increaseButtonSubscription?.Dispose();
|
|
|
_increaseButtonReleaseDispose?.Dispose();
|
|
|
_pointerMovedDispose?.Dispose();
|
|
|
-
|
|
|
+ _trackOnKeyDownDispose?.Dispose();
|
|
|
+
|
|
|
_decreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton");
|
|
|
_track = e.NameScope.Find<Track>("PART_Track");
|
|
|
_increaseButton = e.NameScope.Find<Button>("PART_IncreaseButton");
|
|
|
@@ -171,6 +174,7 @@ namespace Avalonia.Controls
|
|
|
if (_track != null)
|
|
|
{
|
|
|
_track.IsThumbDragHandled = true;
|
|
|
+ _trackOnKeyDownDispose = _track.AddDisposableHandler(KeyDownEvent, TrackOnKeyDown);
|
|
|
}
|
|
|
|
|
|
if (_decreaseButton != null)
|
|
|
@@ -188,6 +192,94 @@ namespace Avalonia.Controls
|
|
|
_pointerMovedDispose = this.AddDisposableHandler(PointerMovedEvent, TrackMoved, RoutingStrategies.Tunnel);
|
|
|
}
|
|
|
|
|
|
+ private void TrackOnKeyDown(object sender, KeyEventArgs e)
|
|
|
+ {
|
|
|
+ if (e.KeyModifiers != KeyModifiers.None) return;
|
|
|
+
|
|
|
+ switch (e.Key)
|
|
|
+ {
|
|
|
+ case Key.Left:
|
|
|
+ MoveToNextTick(-SmallChange);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case Key.Right:
|
|
|
+ MoveToNextTick(SmallChange);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case Key.PageUp:
|
|
|
+ MoveToNextTick(-LargeChange);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case Key.PageDown:
|
|
|
+ MoveToNextTick(LargeChange);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case Key.Home:
|
|
|
+ Value = Minimum;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case Key.End:
|
|
|
+ Value = Maximum;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void MoveToNextTick(double direction)
|
|
|
+ {
|
|
|
+ if (direction == 0.0) return;
|
|
|
+
|
|
|
+ var value = Value;
|
|
|
+
|
|
|
+ // Find the next value by snapping
|
|
|
+ var next = SnapToTick(Math.Max(Minimum, Math.Min(Maximum, value + direction)));
|
|
|
+
|
|
|
+ var greaterThan = direction > 0; //search for the next tick greater than value?
|
|
|
+
|
|
|
+ // If the snapping brought us back to value, find the next tick point
|
|
|
+ if (Math.Abs(next - value) < Tolerance
|
|
|
+ && !(greaterThan && Math.Abs(value - Maximum) < Tolerance) // Stop if searching up if already at Max
|
|
|
+ && !(!greaterThan && Math.Abs(value - Minimum) < Tolerance)) // Stop if searching down if already at Min
|
|
|
+ {
|
|
|
+ var ticks = Ticks;
|
|
|
+
|
|
|
+ // If ticks collection is available, use it.
|
|
|
+ // Note that ticks may be unsorted.
|
|
|
+ if (ticks != null && ticks.Count > 0)
|
|
|
+ {
|
|
|
+ foreach (var tick in ticks)
|
|
|
+ {
|
|
|
+ // Find the smallest tick greater than value or the largest tick less than value
|
|
|
+ if (greaterThan && MathUtilities.GreaterThan(tick, value) &&
|
|
|
+ (MathUtilities.LessThan(tick, next) || Math.Abs(next - value) < Tolerance)
|
|
|
+ || !greaterThan && MathUtilities.LessThan(tick, value) &&
|
|
|
+ (MathUtilities.GreaterThan(tick, next) || Math.Abs(next - value) < Tolerance))
|
|
|
+ {
|
|
|
+ next = tick;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (MathUtilities.GreaterThan(TickFrequency, 0.0))
|
|
|
+ {
|
|
|
+ // Find the current tick we are at
|
|
|
+ var tickNumber = Math.Round((value - Minimum) / TickFrequency);
|
|
|
+
|
|
|
+ if (greaterThan)
|
|
|
+ tickNumber += 1.0;
|
|
|
+ else
|
|
|
+ tickNumber -= 1.0;
|
|
|
+
|
|
|
+ next = Minimum + tickNumber * TickFrequency;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // Update if we've found a better value
|
|
|
+ if (Math.Abs(next - value) > Tolerance)
|
|
|
+ {
|
|
|
+ Value = next;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private void TrackMoved(object sender, PointerEventArgs e)
|
|
|
{
|
|
|
if (_isDragging)
|
|
|
@@ -272,19 +364,18 @@ namespace Avalonia.Controls
|
|
|
{
|
|
|
if (IsSnapToTickEnabled)
|
|
|
{
|
|
|
- double previous = Minimum;
|
|
|
- double next = Maximum;
|
|
|
+ var previous = Minimum;
|
|
|
+ var next = Maximum;
|
|
|
|
|
|
// This property is rarely set so let's try to avoid the GetValue
|
|
|
var ticks = Ticks;
|
|
|
|
|
|
// If ticks collection is available, use it.
|
|
|
// Note that ticks may be unsorted.
|
|
|
- if ((ticks != null) && (ticks.Count > 0))
|
|
|
+ if (ticks != null && ticks.Count > 0)
|
|
|
{
|
|
|
- for (int i = 0; i < ticks.Count; i++)
|
|
|
+ foreach (var tick in ticks)
|
|
|
{
|
|
|
- double tick = ticks[i];
|
|
|
if (MathUtilities.AreClose(tick, value))
|
|
|
{
|
|
|
return value;
|
|
|
@@ -302,7 +393,7 @@ namespace Avalonia.Controls
|
|
|
}
|
|
|
else if (MathUtilities.GreaterThan(TickFrequency, 0.0))
|
|
|
{
|
|
|
- previous = Minimum + (Math.Round(((value - Minimum) / TickFrequency)) * TickFrequency);
|
|
|
+ previous = Minimum + Math.Round((value - Minimum) / TickFrequency) * TickFrequency;
|
|
|
next = Math.Min(Maximum, previous + TickFrequency);
|
|
|
}
|
|
|
|