Browse Source

Merge pull request #11637 from AvaloniaUI/feature/request-animation-frame

Added TopLevel.RequestAnimationFrame
pull/11649/head
Max Katz 3 years ago
committed by GitHub
parent
commit
8e63c12fee
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 37
      src/Avalonia.Base/Media/MediaContext.Clock.cs
  2. 3
      src/Avalonia.Base/Media/MediaContext.cs
  3. 12
      src/Avalonia.Controls/TopLevel.cs

37
src/Avalonia.Base/Media/MediaContext.Clock.cs

@ -4,6 +4,7 @@ using System.Diagnostics;
using Avalonia.Animation;
using Avalonia.Reactive;
using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Media;
@ -17,8 +18,12 @@ internal partial class MediaContext
{
private readonly MediaContext _parent;
private List<IObserver<TimeSpan>> _observers = new();
public bool HasNewSubscriptions { get; set; }
public bool HasSubscriptions => _observers.Count > 0;
private List<IObserver<TimeSpan>> _newObservers = new();
private Queue<Action<TimeSpan>> _queuedAnimationFrames = new();
private Queue<Action<TimeSpan>> _queuedAnimationFramesNext = new();
private TimeSpan _currentAnimationTimestamp;
public bool HasNewSubscriptions => _newObservers.Count > 0;
public bool HasSubscriptions => _observers.Count > 0 || _queuedAnimationFrames.Count > 0;
public MediaContextClock(MediaContext parent)
{
@ -29,19 +34,41 @@ internal partial class MediaContext
{
_parent.ScheduleRender(false);
Dispatcher.UIThread.VerifyAccess();
HasNewSubscriptions = true;
_observers.Add(observer);
_newObservers.Add(observer);
return Disposable.Create(() =>
{
Dispatcher.UIThread.VerifyAccess();
_observers.Remove(observer);
});
}
public void RequestAnimationFrame(Action<TimeSpan> action)
{
_parent.ScheduleRender(false);
_queuedAnimationFrames.Enqueue(action);
}
public void Pulse(TimeSpan now)
{
_newObservers.Clear();
_currentAnimationTimestamp = now;
// We are swapping the queues before enumeration
(_queuedAnimationFrames, _queuedAnimationFramesNext) = (_queuedAnimationFramesNext, _queuedAnimationFrames);
var animationFrames = _queuedAnimationFramesNext;
while (animationFrames.TryDequeue(out var callback))
callback(now);
foreach (var observer in _observers.ToArray())
observer.OnNext(now);
observer.OnNext(_currentAnimationTimestamp);
}
public void PulseNewSubscriptions()
{
foreach (var observer in _newObservers.ToArray())
observer.OnNext(_currentAnimationTimestamp);
_newObservers.Clear();
}
public PlayState PlayState
@ -50,4 +77,6 @@ internal partial class MediaContext
set => throw new InvalidOperationException();
}
}
public void RequestAnimationFrame(Action<TimeSpan> action) => _clock.RequestAnimationFrame(action);
}

3
src/Avalonia.Base/Media/MediaContext.cs

@ -131,12 +131,11 @@ internal partial class MediaContext : ICompositorScheduler
// We are doing several iterations when it happens
for (var c = 0; c < 10; c++)
{
_clock.HasNewSubscriptions = false;
FireInvokeOnRenderCallbacks();
if (_clock.HasNewSubscriptions)
{
_clock.Pulse(now);
_clock.PulseNewSubscriptions();
continue;
}

12
src/Avalonia.Controls/TopLevel.cs

@ -25,6 +25,7 @@ using System.Linq;
using System.Threading.Tasks;
using Avalonia.Metadata;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
namespace Avalonia.Controls
{
@ -535,7 +536,16 @@ namespace Avalonia.Controls
return Disposable.Create(() => { });
}
}
/// <summary>
/// Enqueues a callback to be called on the next animation tick
/// </summary>
public void RequestAnimationFrame(Action<TimeSpan> action)
{
Dispatcher.UIThread.VerifyAccess();
MediaContext.Instance.RequestAnimationFrame(action);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

Loading…
Cancel
Save