Back/Forward buttons fixed

This commit is contained in:
Alexander Shabarshov 2026-06-20 10:36:37 +01:00
parent f412db219f
commit 2058ae0f7e
2 changed files with 75 additions and 27 deletions

View File

@ -1,5 +1,6 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization; using System.Globalization;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
@ -68,14 +69,15 @@ public class TimelinePreviewSlider : Control, IDisposable
private readonly object _cacheLock = new(); private readonly object _cacheLock = new();
private IDisposable? _segmentsSubscription; private IDisposable? _segmentsSubscription;
private bool _isInternalSliderUpdate;
private JobViewModel? _currentVm;
// Interaction state // Interaction state
private bool _isPointerCaptured; private bool _isPointerCaptured;
private Point _lastPointerPoint; private Point _lastPointerPoint;
private double _lastPointerXForDrag; // used to compute delta for segment drag private DragMode _dragMode = DragMode.None;
private DragMode _dragMode = DragMode.None; private int _activeSegmentIndex = -1;
private int _activeSegmentIndex = -1; private bool _isSplitModifierActive;
private bool _isSplitModifierActive;
// Throttle invalidation during drag // Throttle invalidation during drag
private DateTime _lastInvalidate = DateTime.MinValue; private DateTime _lastInvalidate = DateTime.MinValue;
@ -83,17 +85,17 @@ public class TimelinePreviewSlider : Control, IDisposable
public TimelinePreviewSlider() public TimelinePreviewSlider()
{ {
Focusable = true; Focusable = true;
Height = _timelineHeight; Height = _timelineHeight;
ClipToBounds = true; ClipToBounds = true;
// Use property change override instead of GetObservable.Subscribe to avoid IObserver compile issues. // Use property change override instead of GetObservable.Subscribe to avoid IObserver compile issues.
PointerPressed += OnPointerPressed; PointerPressed += OnPointerPressed;
PointerMoved += OnPointerMoved; PointerMoved += OnPointerMoved;
PointerReleased += OnPointerReleased; PointerReleased += OnPointerReleased;
PointerCaptureLost += OnPointerCaptureLost; PointerCaptureLost += OnPointerCaptureLost;
KeyDown += OnKeyDown; KeyDown += OnKeyDown;
KeyUp += OnKeyUp; KeyUp += OnKeyUp;
} }
// Override to detect ViewModel property changes // Override to detect ViewModel property changes
@ -116,11 +118,24 @@ public class TimelinePreviewSlider : Control, IDisposable
if (vm != null) if (vm != null)
{ {
_segmentsSubscription = SubscribeToSegments(vm.Segments); _segmentsSubscription = SubscribeToSegments(vm.Segments);
vm.PropertyChanged += OnVmPropertyChanged;
} }
InvalidateVisual(); InvalidateVisual();
} }
private void OnVmPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(JobViewModel.SliderLiveValue))
{
if (_isInternalSliderUpdate)
return;
InvalidateVisual();
}
}
private IDisposable SubscribeToSegments(ObservableCollection<Segment> segments) private IDisposable SubscribeToSegments(ObservableCollection<Segment> segments)
{ {
NotifyCollectionChangedEventHandler handler = (s, e) => NotifyCollectionChangedEventHandler handler = (s, e) =>
@ -135,6 +150,11 @@ public class TimelinePreviewSlider : Control, IDisposable
private void UnsubscribeFromViewModel() private void UnsubscribeFromViewModel()
{ {
if (_currentVm != null)
_currentVm.PropertyChanged -= OnVmPropertyChanged;
_currentVm = null;
_segmentsSubscription?.Dispose(); _segmentsSubscription?.Dispose();
_segmentsSubscription = null; _segmentsSubscription = null;
} }
@ -519,7 +539,6 @@ public class TimelinePreviewSlider : Control, IDisposable
var p = e.GetPosition(this); var p = e.GetPosition(this);
_lastPointerPoint = p; _lastPointerPoint = p;
_lastPointerXForDrag = p.X;
_isSplitModifierActive = e.KeyModifiers.HasFlag(KeyModifiers.Control); _isSplitModifierActive = e.KeyModifiers.HasFlag(KeyModifiers.Control);
var hit = HitTestAtPoint(p); var hit = HitTestAtPoint(p);
@ -576,7 +595,9 @@ public class TimelinePreviewSlider : Control, IDisposable
break; break;
} }
_isInternalSliderUpdate = true;
vm.SliderLiveValue = sec; vm.SliderLiveValue = sec;
_isInternalSliderUpdate = false;
ThrottledInvalidate(); ThrottledInvalidate();
_lastPointerPoint = p; _lastPointerPoint = p;
@ -665,7 +686,6 @@ public class TimelinePreviewSlider : Control, IDisposable
_dragMode = mode; _dragMode = mode;
_activeSegmentIndex = segmentIndex; _activeSegmentIndex = segmentIndex;
_lastPointerPoint = e.GetPosition(this); _lastPointerPoint = e.GetPosition(this);
_lastPointerXForDrag = _lastPointerPoint.X;
} }
private void ThrottledInvalidate() private void ThrottledInvalidate()
@ -681,10 +701,16 @@ public class TimelinePreviewSlider : Control, IDisposable
private void SetPlayheadFromPoint(Point p) private void SetPlayheadFromPoint(Point p)
{ {
var vm = ViewModel; var vm = ViewModel;
if (vm == null) return; if (vm == null)
var sec = PixelToSeconds(p.X); return;
double sec = PixelToSeconds(p.X);
sec = Math.Max(0, Math.Min(vm.DurationSeconds, sec)); sec = Math.Max(0, Math.Min(vm.DurationSeconds, sec));
_isInternalSliderUpdate = true;
vm.SliderLiveValue = sec; vm.SliderLiveValue = sec;
_isInternalSliderUpdate = false;
InvalidateVisual(); InvalidateVisual();
} }

View File

@ -462,26 +462,48 @@ public partial class JobViewModel : ObservableObject
private void StepForward() private void StepForward()
{ {
if (DurationSeconds <= 0) if (Segments.Count <= 1)
return; return;
var duration = SegmentDuration; var current = GetCurrentSegment();
var segment = Math.Round(SliderLiveValue / duration, MidpointRounding.ToZero)+1; if ( current < 0 || current >= Segments.Count - 1 )
return;
SliderLiveValue = Math.Min(DurationSeconds - duration, segment * duration); SliderLiveValue = Segments[current + 1].Start;
// trigger seek in your playback pipeline here
} }
private void StepBackward() private void StepBackward()
{ {
if (DurationSeconds <= 0) if (Segments.Count <= 0)
return; return;
var duration = SegmentDuration; var current = GetCurrentSegment();
var segment = Math.Max(0, Math.Round(SliderLiveValue / duration, MidpointRounding.ToZero)-1); if (current <= 0)
{
SliderLiveValue = 0;
return;
}
SliderLiveValue = segment * duration; if (SliderLiveValue > Segments[current].Start)
// trigger seek in your playback pipeline here SliderLiveValue = Segments[current].Start;
else
SliderLiveValue = Segments[current - 1].Start;
}
private int GetCurrentSegment()
{
double pos = SliderLiveValue;
for (int i = 0; i < Segments.Count; i++)
{
var s = Segments[i];
if (pos < s.Start)
return i - 1;
if (pos == s.Start)
return i;
}
return -1;
} }
partial void OnSliderLiveValueChanged(double value) partial void OnSliderLiveValueChanged(double value)