using System; using System.Runtime.ExceptionServices; using Avalonia.Reactive; using Avalonia.Utilities; namespace Avalonia.Animation { /// /// Handles the timing and lifetime of a . /// internal class TransitionInstance : SingleSubscriberObservableBase, IObserver { private IDisposable? _timerSubscription; private TimeSpan _delay; private TimeSpan _duration; private readonly IClock _baseClock; private TransitionClock? _clock; public TransitionInstance(IClock clock, TimeSpan delay, TimeSpan duration) { clock = clock ?? throw new ArgumentNullException(nameof(clock)); _delay = delay; _duration = duration; _baseClock = clock; } private void TimerTick(TimeSpan t) { // [<------------- normalizedTotalDur ------------------>] // [<---- Delay ---->][<---------- Duration ------------>] // ^- normalizedDelayEnd // [<---- normalizedInterpVal --->] double normalizedInterpVal; if (t < _delay) { normalizedInterpVal = 0d; } else if (MathUtilities.AreClose(_duration.TotalSeconds, 0d)) { normalizedInterpVal = 1d; } else { var normalizedTotalDur = _delay + _duration; var normalizedDelayEnd = _delay.TotalSeconds / normalizedTotalDur.TotalSeconds; var normalizedPresentationTime = t.TotalSeconds / normalizedTotalDur.TotalSeconds; if (normalizedPresentationTime < normalizedDelayEnd || MathUtilities.AreClose(normalizedPresentationTime, normalizedDelayEnd)) { normalizedInterpVal = 0d; } else { normalizedInterpVal = (t.TotalSeconds - _delay.TotalSeconds) / _duration.TotalSeconds; } } // Clamp interpolation value. if (normalizedInterpVal >= 1d || normalizedInterpVal < 0d) { PublishNext(1d); PublishCompleted(); } else { PublishNext(normalizedInterpVal); } } protected override void Unsubscribed() { _timerSubscription?.Dispose(); _clock!.PlayState = PlayState.Stop; } protected override void Subscribed() { _clock = new TransitionClock(_baseClock); _timerSubscription = _clock.Subscribe(this); PublishNext(0.0d); } void IObserver.OnCompleted() { PublishCompleted(); } void IObserver.OnError(Exception error) { PublishError(error); } void IObserver.OnNext(TimeSpan value) { TimerTick(value); } /// /// TODO: This clock is still fairly expensive due to implementation. /// private sealed class TransitionClock : ClockBase, IObserver { private readonly IDisposable _parentSubscription; public TransitionClock(IClock parent) { _parentSubscription = parent.Subscribe(this); } protected override void Stop() { _parentSubscription.Dispose(); } void IObserver.OnNext(TimeSpan value) { Pulse(value); } void IObserver.OnCompleted() { } void IObserver.OnError(Exception error) { ExceptionDispatchInfo.Capture(error).Throw(); } } } }