using System; using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; using Point = Avalonia.Point; namespace Splitter_UI.Controls { public sealed class PreviewSlider : Control { public static readonly StyledProperty MinimumProperty = AvaloniaProperty.Register(nameof(Minimum), 0d); public static readonly StyledProperty MaximumProperty = AvaloniaProperty.Register(nameof(Maximum), 100d); public static readonly StyledProperty ValueProperty = AvaloniaProperty.Register( nameof(Value), 0d, coerce: (o, v) => { var slider = (PreviewSlider)o; if (v < slider.Minimum) return slider.Minimum; if (v > slider.Maximum) return slider.Maximum; return v; }); public static readonly StyledProperty SegmentDurationProperty = AvaloniaProperty.Register(nameof(SegmentDuration), 1d); public static readonly StyledProperty TrackThicknessProperty = AvaloniaProperty.Register(nameof(TrackThickness), 4d); public static readonly StyledProperty ThumbRadiusProperty = AvaloniaProperty.Register(nameof(ThumbRadius), 8d); public static readonly StyledProperty TrackBrushProperty = AvaloniaProperty.Register(nameof(TrackBrush), Brushes.Gray); public static readonly StyledProperty TrackFillBrushProperty = AvaloniaProperty.Register(nameof(TrackFillBrush), Brushes.DodgerBlue); public static readonly StyledProperty ThumbBrushProperty = AvaloniaProperty.Register(nameof(ThumbBrush), Brushes.White); public static readonly StyledProperty ThumbBorderBrushProperty = AvaloniaProperty.Register(nameof(ThumbBorderBrush), Brushes.DodgerBlue); public static readonly StyledProperty ThumbBorderThicknessProperty = AvaloniaProperty.Register(nameof(ThumbBorderThickness), 1d); public static readonly StyledProperty SegmentLineBrushProperty = AvaloniaProperty.Register(nameof(SegmentLineBrush), Brushes.LightSalmon); public static readonly StyledProperty SegmentLineThicknessProperty = AvaloniaProperty.Register(nameof(SegmentLineThickness), 1d); private bool _isDragging; public double Minimum { get => GetValue(MinimumProperty); set => SetValue(MinimumProperty, value); } public double Maximum { get => GetValue(MaximumProperty); set => SetValue(MaximumProperty, value); } public double Value { get => GetValue(ValueProperty); set => SetValue(ValueProperty, value); } public double SegmentDuration { get => GetValue(SegmentDurationProperty); set => SetValue(SegmentDurationProperty, value); } public double TrackThickness { get => GetValue(TrackThicknessProperty); set => SetValue(TrackThicknessProperty, value); } public double ThumbRadius { get => GetValue(ThumbRadiusProperty); set => SetValue(ThumbRadiusProperty, value); } public IBrush TrackBrush { get => GetValue(TrackBrushProperty); set => SetValue(TrackBrushProperty, value); } public IBrush TrackFillBrush { get => GetValue(TrackFillBrushProperty); set => SetValue(TrackFillBrushProperty, value); } public IBrush ThumbBrush { get => GetValue(ThumbBrushProperty); set => SetValue(ThumbBrushProperty, value); } public IBrush ThumbBorderBrush { get => GetValue(ThumbBorderBrushProperty); set => SetValue(ThumbBorderBrushProperty, value); } public double ThumbBorderThickness { get => GetValue(ThumbBorderThicknessProperty); set => SetValue(ThumbBorderThicknessProperty, value); } public IBrush SegmentLineBrush { get => GetValue(SegmentLineBrushProperty); set => SetValue(SegmentLineBrushProperty, value); } public double SegmentLineThickness { get => GetValue(SegmentLineThicknessProperty); set => SetValue(SegmentLineThicknessProperty, value); } static PreviewSlider() { FocusableProperty.OverrideDefaultValue(true); ValueProperty.Changed.AddClassHandler((s, _) => s.InvalidateVisual()); MinimumProperty.Changed.AddClassHandler((s, _) => s.InvalidateVisual()); MaximumProperty.Changed.AddClassHandler((s, _) => s.InvalidateVisual()); SegmentDurationProperty.Changed.AddClassHandler((s, _) => s.InvalidateVisual()); } public PreviewSlider() { ClipToBounds = true; AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel); AddHandler(PointerMovedEvent, OnPointerMoved, RoutingStrategies.Tunnel); AddHandler(PointerReleasedEvent, OnPointerReleased, RoutingStrategies.Tunnel); AddHandler(PointerCaptureLostEvent, OnPointerCaptureLost, RoutingStrategies.Tunnel); } public override void Render(DrawingContext context) { base.Render(context); var bounds = Bounds; if (bounds.Width <= 0 || bounds.Height <= 0) return; var centerY = bounds.Height / 2.0; var left = ThumbRadius; var right = bounds.Width - ThumbRadius; var trackThickness = TrackThickness; var trackRect = new Rect(left, centerY - trackThickness / 2.0, right - left, trackThickness); context.FillRectangle(TrackBrush, trackRect); var range = Maximum - Minimum; if (SegmentDuration > 0 && range > 0 && SegmentLineBrush != null && SegmentLineThickness > 0) { var pen = new Pen(SegmentLineBrush, SegmentLineThickness); var totalSegments = (int)Math.Floor(range / SegmentDuration); for (var i = 1; i <= totalSegments; i++) { var segmentValue = Minimum + i * SegmentDuration; var tSeg = (segmentValue - Minimum) / range; var xSeg = left + tSeg * (right - left); var p1 = new Point(xSeg, centerY - trackThickness); var p2 = new Point(xSeg, centerY + trackThickness); context.DrawLine(pen, p1, p2); } } var t = (range <= 0) ? 0.0 : (Value - Minimum) / range; t = Math.Clamp(t, 0.0, 1.0); var thumbX = left + t * (right - left); var fillRect = new Rect(left, centerY - trackThickness / 2.0, thumbX - left, trackThickness); context.FillRectangle(TrackFillBrush, fillRect); var thumbRadius = ThumbRadius; var thumbCenter = new Point(thumbX, centerY); var ellipse = new EllipseGeometry(new Rect( thumbCenter.X - thumbRadius, thumbCenter.Y - thumbRadius, thumbRadius * 2, thumbRadius * 2)); context.DrawGeometry(ThumbBrush, null, ellipse); if (ThumbBorderThickness > 0 && ThumbBorderBrush != null) { var pen = new Pen(ThumbBorderBrush, ThumbBorderThickness); context.DrawGeometry(null, pen, ellipse); } } protected override void OnPointerWheelChanged(PointerWheelEventArgs e) { base.OnPointerWheelChanged(e); var delta = e.Delta.Y; if (delta == 0) return; var step = (Maximum - Minimum) / 100.0; if (step <= 0) step = 1.0; if (delta > 0) Value = Math.Clamp(Value - step, Minimum, Maximum); else Value = Math.Clamp(Value + step, Minimum, Maximum); e.Handled = true; } private void OnPointerPressed(object? sender, PointerPressedEventArgs e) { if (!IsEnabled) return; if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return; e.Pointer.Capture(this); UpdateValueFromPoint(e.GetPosition(this)); _isDragging = true; e.Handled = true; } private void OnPointerMoved(object? sender, PointerEventArgs e) { if (!_isDragging) return; UpdateValueFromPoint(e.GetPosition(this)); e.Handled = true; } private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) { if (!_isDragging) return; _isDragging = false; e.Pointer.Capture(null); e.Handled = true; } private void OnPointerCaptureLost(object? sender, PointerCaptureLostEventArgs e) { _isDragging = false; } private void UpdateValueFromPoint(Point point) { var bounds = Bounds; var left = ThumbRadius; var right = bounds.Width - ThumbRadius; if (right <= left) return; var x = Math.Clamp(point.X, left, right); var t = (x - left) / (right - left); var newValue = Minimum + t * (Maximum - Minimum); Value = newValue; InvalidateVisual(); } } }