diff --git a/demo/Ursa.Demo/Pages/RangeSliderDemo.axaml b/demo/Ursa.Demo/Pages/RangeSliderDemo.axaml index 5a0576c..da47e62 100644 --- a/demo/Ursa.Demo/Pages/RangeSliderDemo.axaml +++ b/demo/Ursa.Demo/Pages/RangeSliderDemo.axaml @@ -12,7 +12,7 @@ - + diff --git a/src/Ursa.Themes.Semi/Controls/RangeSlider.axaml b/src/Ursa.Themes.Semi/Controls/RangeSlider.axaml index f71b008..a2647c1 100644 --- a/src/Ursa.Themes.Semi/Controls/RangeSlider.axaml +++ b/src/Ursa.Themes.Semi/Controls/RangeSlider.axaml @@ -1,42 +1,48 @@ - - + + + - - - - + Maximum="{Binding Maximum, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" + Minimum="{Binding Minimum, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" + UpperValue="{Binding UpperValue, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"> + + + - - - - - - - + - - - + + + - - - - - - - + - - - + + + diff --git a/src/Ursa/Controls/RangeSlider/RangeSlider.cs b/src/Ursa/Controls/RangeSlider/RangeSlider.cs index d989c4f..e6fe313 100644 --- a/src/Ursa/Controls/RangeSlider/RangeSlider.cs +++ b/src/Ursa/Controls/RangeSlider/RangeSlider.cs @@ -1,18 +1,25 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; +using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Utilities; namespace Ursa.Controls; -[TemplatePart(PART_DecreaseButton, typeof(Button))] -[TemplatePart(PART_IncreaseButton, typeof(Button))] -[TemplatePart(PART_Track, typeof(Track))] +[TemplatePart(PART_Track, typeof(RangeTrack))] public class RangeSlider: TemplatedControl { - public const string PART_DecreaseButton = "PART_DecreaseButton"; - public const string PART_IncreaseButton = "PART_IncreaseButton"; - public const string PART_Track = "PART_Track"; + public const string PART_Track = "PART_Track"; + + private RangeTrack? _track; + private bool _isDragging; + private IDisposable? _pointerPressedDisposable; + private IDisposable? _pointerMoveDisposable; + private IDisposable? _pointerReleasedDisposable; public static readonly StyledProperty MinimumProperty = RangeTrack.MinimumProperty.AddOwner(); public double Minimum @@ -42,12 +49,106 @@ public class RangeSlider: TemplatedControl set => SetValue(UpperValueProperty, value); } + public static readonly StyledProperty TrackWidthProperty = AvaloniaProperty.Register( + nameof(TrackWidth)); + + public double TrackWidth + { + get => GetValue(TrackWidthProperty); + set => SetValue(TrackWidthProperty, value); + } + + public static readonly StyledProperty OrientationProperty = RangeTrack.OrientationProperty.AddOwner(); + + public Orientation Orientation + { + get => GetValue(OrientationProperty); + set => SetValue(OrientationProperty, value); + } + + public static readonly StyledProperty IsDirectionReversedProperty = + RangeTrack.IsDirectionReversedProperty.AddOwner(); + + public bool IsDirectionReversed + { + get => GetValue(IsDirectionReversedProperty); + set => SetValue(IsDirectionReversedProperty, value); + } + static RangeSlider() { + PressedMixin.Attach(); + FocusableProperty.OverrideDefaultValue(true); + IsHitTestVisibleProperty.OverrideDefaultValue(true); + OrientationProperty.OverrideDefaultValue(Orientation.Horizontal); MinimumProperty.OverrideDefaultValue(0); MaximumProperty.OverrideDefaultValue(100); LowerValueProperty.OverrideDefaultValue(0); UpperValueProperty.OverrideDefaultValue(100); } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + _pointerMoveDisposable?.Dispose(); + _pointerPressedDisposable?.Dispose(); + _pointerReleasedDisposable?.Dispose(); + _track = e.NameScope.Find(PART_Track); + _pointerMoveDisposable = this.AddDisposableHandler(PointerMovedEvent, PointerMove, RoutingStrategies.Tunnel); + _pointerPressedDisposable = this.AddDisposableHandler(PointerPressedEvent, PointerPress, RoutingStrategies.Tunnel); + _pointerReleasedDisposable = this.AddDisposableHandler(PointerReleasedEvent, PointerRelease, RoutingStrategies.Tunnel); + + } + + private void PointerPress(object sender, PointerPressedEventArgs e) + { + if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) + { + var point = e.GetCurrentPoint(_track); + MoveToPoint(point); + _isDragging = true; + } + } + private void PointerMove(object sender, PointerEventArgs args) + { + if (!IsEnabled) + { + _isDragging = false; + return; + } + if (_isDragging) + { + MoveToPoint(args.GetCurrentPoint(_track)); + } + } + + private void PointerRelease(object sender, PointerReleasedEventArgs e) + { + _isDragging = false; + } + + private void MoveToPoint(PointerPoint posOnTrack) + { + if (_track is null) return; + var isHorizontal = Orientation == Orientation.Horizontal; + var thumbLength = _track.GetThumbLength(); + var trackLength = _track.GetTrackLength() - thumbLength; + var pos = isHorizontal ? posOnTrack.Position.X : posOnTrack.Position.Y; + var lowerPosition = isHorizontal? _track.LowerThumb.Bounds.X : _track.LowerThumb.Bounds.Y; + var upperPosition = isHorizontal? _track.UpperThumb.Bounds.X : _track.UpperThumb.Bounds.Y; + bool lower = Math.Abs(pos - lowerPosition) < Math.Abs(pos - upperPosition); + var logicalPosition = MathUtilities.Clamp((pos - thumbLength*0.5) / trackLength, 0.0, 1.0); + var invert = isHorizontal ? IsDirectionReversed ? 1.0 : 0 : + IsDirectionReversed ? 0 : 1.0; + var calValue = Math.Abs(invert - logicalPosition); + var range = Maximum - Minimum; + var finalValue = calValue * range + Minimum; + SetCurrentValue(lower? LowerValueProperty: UpperValueProperty, finalValue); + } + + private double SnapToTick(double value) + { + return value; + } } \ No newline at end of file diff --git a/src/Ursa/Controls/RangeSlider/RangeTrack.cs b/src/Ursa/Controls/RangeSlider/RangeTrack.cs index c37afe1..12fc381 100644 --- a/src/Ursa/Controls/RangeSlider/RangeTrack.cs +++ b/src/Ursa/Controls/RangeSlider/RangeTrack.cs @@ -68,31 +68,31 @@ public class RangeTrack: Control set => SetValue(OrientationProperty, value); } - public static readonly StyledProperty UpperButtonProperty = AvaloniaProperty.Register( - nameof(UpperButton)); + public static readonly StyledProperty UpperSectionProperty = AvaloniaProperty.Register( + nameof(UpperSection)); - public Button? UpperButton + public Control? UpperSection { - get => GetValue(UpperButtonProperty); - set => SetValue(UpperButtonProperty, value); + get => GetValue(UpperSectionProperty); + set => SetValue(UpperSectionProperty, value); } - public static readonly StyledProperty LowerButtonProperty = AvaloniaProperty.Register( - nameof(LowerButton)); + public static readonly StyledProperty LowerSectionProperty = AvaloniaProperty.Register( + nameof(LowerSection)); - public Button? LowerButton + public Control? LowerSection { - get => GetValue(LowerButtonProperty); - set => SetValue(LowerButtonProperty, value); + get => GetValue(LowerSectionProperty); + set => SetValue(LowerSectionProperty, value); } - public static readonly StyledProperty InnerButtonProperty = AvaloniaProperty.Register( - nameof(InnerButton)); + public static readonly StyledProperty InnerSectionProperty = AvaloniaProperty.Register( + nameof(InnerSection)); - public Button? InnerButton + public Control? InnerSection { - get => GetValue(InnerButtonProperty); - set => SetValue(InnerButtonProperty, value); + get => GetValue(InnerSectionProperty); + set => SetValue(InnerSectionProperty, value); } public static readonly StyledProperty UpperThumbProperty = AvaloniaProperty.Register( @@ -136,9 +136,9 @@ public class RangeTrack: Control OrientationProperty.Changed.AddClassHandler((o, e) => o.OnOrientationChanged(e)); LowerThumbProperty.Changed.AddClassHandler((o, e) => o.OnThumbChanged(e)); UpperThumbProperty.Changed.AddClassHandler((o, e) => o.OnThumbChanged(e)); - LowerButtonProperty.Changed.AddClassHandler((o, e) => o.OnButtonChanged(e)); - UpperButtonProperty.Changed.AddClassHandler((o, e) => o.OnButtonChanged(e)); - InnerButtonProperty.Changed.AddClassHandler((o, e) => o.OnButtonChanged(e)); + LowerSectionProperty.Changed.AddClassHandler((o, e) => o.OnSectionChanged(e)); + UpperSectionProperty.Changed.AddClassHandler((o, e) => o.OnSectionChanged(e)); + InnerSectionProperty.Changed.AddClassHandler((o, e) => o.OnSectionChanged(e)); MinimumProperty.Changed.AddClassHandler((o, e) => o.OnMinimumChanged(e)); MaximumProperty.Changed.AddClassHandler((o, e) => o.OnMaximumChanged(e)); LowerValueProperty.Changed.AddClassHandler((o, e) => o.OnValueChanged(e, true)); @@ -181,19 +181,19 @@ public class RangeTrack: Control } } - private void OnButtonChanged(AvaloniaPropertyChangedEventArgs args) + private void OnSectionChanged(AvaloniaPropertyChangedEventArgs args) { - var oldButton = args.OldValue.Value; - var newButton = args.NewValue.Value; - if (oldButton is not null) + var oldSection = args.OldValue.Value; + var newSection = args.NewValue.Value; + if (oldSection is not null) { - LogicalChildren.Remove(oldButton); - VisualChildren.Remove(oldButton); + LogicalChildren.Remove(oldSection); + VisualChildren.Remove(oldSection); } - if (newButton is not null) + if (newSection is not null) { - LogicalChildren.Add(newButton); - VisualChildren.Add(newButton); + LogicalChildren.Add(newSection); + VisualChildren.Add(newSection); } } @@ -216,7 +216,8 @@ public class RangeTrack: Control } private void OnThumbDragDelta(object sender, VectorEventArgs e) - { + { + return; if(sender is not Thumb thumb) return; bool lower = thumb == LowerThumb; double scale = IsDirectionReversed ? -1 : 1; @@ -324,36 +325,36 @@ public class RangeTrack: Control if (IsDirectionReversed) { pieceSize = pieceSize.WithHeight(upperButtonLength); - UpperButton?.Arrange(new Rect(offset, pieceSize)); + UpperSection?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithY(offset.Y + upperButtonLength); pieceSize = pieceSize.WithHeight(upperThumbLength); UpperThumb?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithY(offset.Y + upperThumbLength); pieceSize = pieceSize.WithHeight(innerButtonLength); - InnerButton?.Arrange(new Rect(offset, pieceSize)); + InnerSection?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithY(offset.Y + innerButtonLength); pieceSize = pieceSize.WithHeight(lowerThumbLength); LowerThumb?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithY(offset.Y + lowerThumbLength); pieceSize = pieceSize.WithHeight(lowerButtonLength); - LowerButton?.Arrange(new Rect(offset, pieceSize)); + LowerSection?.Arrange(new Rect(offset, pieceSize)); } else { pieceSize = pieceSize.WithHeight(lowerButtonLength); - LowerButton?.Arrange(new Rect(offset, pieceSize)); + LowerSection?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithY(offset.Y + lowerButtonLength); pieceSize = pieceSize.WithHeight(lowerThumbLength); LowerThumb?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithY(offset.Y + lowerThumbLength); pieceSize = pieceSize.WithHeight(innerButtonLength); - InnerButton?.Arrange(new Rect(offset, pieceSize)); + InnerSection?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithY(offset.Y + innerButtonLength); pieceSize = pieceSize.WithHeight(upperThumbLength); UpperThumb?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithY(offset.Y + upperThumbLength); pieceSize = pieceSize.WithHeight(upperButtonLength); - UpperButton?.Arrange(new Rect(offset, pieceSize)); + UpperSection?.Arrange(new Rect(offset, pieceSize)); } } else @@ -366,36 +367,36 @@ public class RangeTrack: Control if (IsDirectionReversed) { pieceSize = pieceSize.WithWidth(upperButtonLength); - UpperButton?.Arrange(new Rect(offset, pieceSize)); + UpperSection?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithX(offset.X + upperButtonLength); pieceSize = pieceSize.WithWidth(upperThumbLength); UpperThumb?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithX(offset.X + upperThumbLength); pieceSize = pieceSize.WithWidth(innerButtonLength); - InnerButton?.Arrange(new Rect(offset, pieceSize)); + InnerSection?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithX(offset.X + innerButtonLength); pieceSize = pieceSize.WithWidth(lowerThumbLength); LowerThumb?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithX(offset.X + lowerThumbLength); pieceSize = pieceSize.WithWidth(lowerButtonLength); - LowerButton?.Arrange(new Rect(offset, pieceSize)); + LowerSection?.Arrange(new Rect(offset, pieceSize)); } else { pieceSize = pieceSize.WithWidth(lowerButtonLength); - LowerButton?.Arrange(new Rect(offset, pieceSize)); + LowerSection?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithX(offset.X + lowerButtonLength); pieceSize = pieceSize.WithWidth(lowerThumbLength); LowerThumb?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithX(offset.X + lowerThumbLength); pieceSize = pieceSize.WithWidth(innerButtonLength); - InnerButton?.Arrange(new Rect(offset, pieceSize)); + InnerSection?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithX(offset.X + innerButtonLength); pieceSize = pieceSize.WithWidth(upperThumbLength); UpperThumb?.Arrange(new Rect(offset, pieceSize)); offset = offset.WithX(offset.X + upperThumbLength); pieceSize = pieceSize.WithWidth(upperButtonLength); - UpperButton?.Arrange(new Rect(offset, pieceSize)); + UpperSection?.Arrange(new Rect(offset, pieceSize)); } } @@ -457,4 +458,18 @@ public class RangeTrack: Control { return !double.IsInfinity(value) && !double.IsNaN(value); } + + internal double GetThumbLength() + { + return Orientation == Orientation.Horizontal + ? LowerThumb?.Bounds.Width ?? 0 + UpperThumb?.Bounds.Width ?? 0 + : LowerThumb?.Bounds.Height ?? 0 + UpperThumb?.Bounds.Height ?? 0; + } + + internal double GetTrackLength() + { + return Orientation == Orientation.Horizontal + ? Bounds.Width + : Bounds.Height; + } } \ No newline at end of file