diff --git a/src/Ursa/Controls/RangeSlider/RangeSlider.cs b/src/Ursa/Controls/RangeSlider/RangeSlider.cs index 371e662..7da7cfa 100644 --- a/src/Ursa/Controls/RangeSlider/RangeSlider.cs +++ b/src/Ursa/Controls/RangeSlider/RangeSlider.cs @@ -1,5 +1,6 @@ using System.Runtime.CompilerServices; using Avalonia; +using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; @@ -12,9 +13,12 @@ using Avalonia.Utilities; namespace Ursa.Controls; [TemplatePart(PART_Track, typeof(RangeTrack))] +[PseudoClasses(PC_Horizontal, PC_Vertical)] public class RangeSlider: TemplatedControl { public const string PART_Track = "PART_Track"; + private const string PC_Horizontal= "horizontal"; + private const string PC_Vertical = "vertical"; private RangeTrack? _track; private bool _isDragging; @@ -75,6 +79,42 @@ public class RangeSlider: TemplatedControl get => GetValue(IsDirectionReversedProperty); set => SetValue(IsDirectionReversedProperty, value); } + + public static readonly StyledProperty TickFrequencyProperty = AvaloniaProperty.Register( + nameof(TickFrequency)); + + public double TickFrequency + { + get => GetValue(TickFrequencyProperty); + set => SetValue(TickFrequencyProperty, value); + } + + public static readonly StyledProperty?> TicksProperty = + TickBar.TicksProperty.AddOwner(); + + public AvaloniaList? Ticks + { + get => GetValue(TicksProperty); + set => SetValue(TicksProperty, value); + } + + public static readonly StyledProperty TickPlacementProperty = + Slider.TickPlacementProperty.AddOwner(); + + public TickPlacement TickPlacement + { + get => GetValue(TickPlacementProperty); + set => SetValue(TickPlacementProperty, value); + } + + public static readonly StyledProperty IsSnapToTickProperty = AvaloniaProperty.Register( + nameof(IsSnapToTick)); + + public bool IsSnapToTick + { + get => GetValue(IsSnapToTickProperty); + set => SetValue(IsSnapToTickProperty, value); + } static RangeSlider() { @@ -82,12 +122,20 @@ public class RangeSlider: TemplatedControl FocusableProperty.OverrideDefaultValue(true); IsHitTestVisibleProperty.OverrideDefaultValue(true); OrientationProperty.OverrideDefaultValue(Orientation.Horizontal); + OrientationProperty.Changed.AddClassHandler((o,e)=>o.OnOrientationChanged(e)); MinimumProperty.OverrideDefaultValue(0); MaximumProperty.OverrideDefaultValue(100); LowerValueProperty.OverrideDefaultValue(0); UpperValueProperty.OverrideDefaultValue(100); } + private void OnOrientationChanged(AvaloniaPropertyChangedEventArgs args) + { + var value = args.NewValue.Value; + PseudoClasses.Set(PC_Horizontal, value == Orientation.Horizontal); + PseudoClasses.Set(PC_Vertical, value == Orientation.Vertical); + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -141,16 +189,50 @@ public class RangeSlider: TemplatedControl if (thumb is null) return; if (thumb == _track.LowerThumb) { - SetCurrentValue(LowerValueProperty, value); + SetCurrentValue(LowerValueProperty, IsSnapToTick ? SnapToTick(value) : value); } else { - SetCurrentValue(UpperValueProperty, value); + SetCurrentValue(UpperValueProperty, IsSnapToTick ? SnapToTick(value) : value); } } private double SnapToTick(double value) { + if (IsSnapToTick) + { + var previous = Minimum; + var next = Maximum; + + var ticks = Ticks; + + if (ticks != null && ticks.Count > 0) + { + foreach (var tick in ticks) + { + if (MathUtilities.AreClose(tick, value)) + { + return value; + } + + if (MathUtilities.LessThan(tick, value) && MathUtilities.GreaterThan(tick, previous)) + { + previous = tick; + } + else if (MathUtilities.GreaterThan(tick, value) && MathUtilities.LessThan(tick, next)) + { + next = tick; + } + } + } + else if (MathUtilities.GreaterThan(TickFrequency, 0.0)) + { + previous = Minimum + Math.Round((value - Minimum) / TickFrequency) * TickFrequency; + next = Math.Min(Maximum, previous + TickFrequency); + } + value = MathUtilities.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous; + } + return value; }