Media system refactoring
- animation/layout/render cycle is now managed from a central location - animations are now throttled if animation/layout/render pass takes longer than a frame which previously caused a soft-freeze with input not being processed - the public API is trimmed to make sure that we can make other planned changes during the 11.x support cycle "Changelog": - IClock is hidden and is planned to be replaced later - Animator classes are hidden and are planned to be refactored later - IAnimation members are hidden, it's supposed to be a marker interface for Style.Animations collection now, to start animations manually use Animation.RunAsync - Sealed several classes in Avalonia.Animation namespace - Spring class is removed from the public API (it wasn't possible to use it directly in a meaningful way anyway) - Sealed brushes, transforms, effects and drawings - Removed separate dispatcher priorities for Layout and Composition, everything now happens from a central place with Render priority (same as WPF) - - some private "hook" priorities are added for now, those will be removed later - IRenderLoop is hidden and removed from locator - IRenderer is hidden (the plan is to remove that concept later) - - Renderer.Start/Stop exposed as StartRendering/StopRendering on the toplevel (will be on a CompositionTarget/PresentationSource-like type later) - - Renderer.Diagnistics exposed as RendererDiagnostics (same) - - Renderer is no longer created by the platform code and is created by TopLevel itself - - From the user-code hit-testing should be done by VisualExtensions.GetVisual(s)At, which has the same features - - For unit tests a separate IHitTester interface is added which can be changed for a particular toplevel - ILayoutManager is hidden - - LayoutManager.ExecuteLayoutPass() exposed as TopLevel.UpdateLayout() - Custom animators now have a separate base class that only deals with interpolation Minor improvements: - Compositor has a mode that doesn't use DispatcherTimers, useful for unit tests - Introduced ScopedTestBase that auto-resets the locator when test is finished
This commit is contained in:
@@ -71,10 +71,10 @@ public class RefAssemblyGenerator
|
||||
foreach (var nested in type.NestedTypes)
|
||||
ProcessType(nested, obsoleteCtor);
|
||||
|
||||
var hideMethods = (type.IsInterface && type.Name.EndsWith("Impl"))
|
||||
var hideMembers = (type.IsInterface && type.Name.EndsWith("Impl"))
|
||||
|| HasPrivateApi(type.CustomAttributes);
|
||||
|
||||
var injectMethod = hideMethods
|
||||
var injectMethod = hideMembers
|
||||
|| type.CustomAttributes.Any(a =>
|
||||
a.AttributeType.FullName == "Avalonia.Metadata.NotClientImplementableAttribute");
|
||||
|
||||
@@ -95,21 +95,47 @@ public class RefAssemblyGenerator
|
||||
|
||||
foreach (var m in type.Methods)
|
||||
{
|
||||
if (hideMethods || HasPrivateApi(m.CustomAttributes))
|
||||
if (hideMembers || HasPrivateApi(m.CustomAttributes))
|
||||
{
|
||||
var dflags = MethodAttributes.Public | MethodAttributes.Family | MethodAttributes.FamORAssem |
|
||||
MethodAttributes.FamANDAssem | MethodAttributes.Assembly;
|
||||
m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly;
|
||||
HideMethod(m);
|
||||
}
|
||||
MarkAsUnstable(m, obsoleteCtor, forceUnstable);
|
||||
}
|
||||
|
||||
foreach (var p in type.Properties)
|
||||
{
|
||||
if (HasPrivateApi(p.CustomAttributes))
|
||||
{
|
||||
if (p.SetMethod != null)
|
||||
HideMethod(p.SetMethod);
|
||||
if (p.GetMethod != null)
|
||||
HideMethod(p.GetMethod);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var f in type.Fields)
|
||||
{
|
||||
if (hideMembers || HasPrivateApi(f.CustomAttributes))
|
||||
{
|
||||
var dflags = FieldAttributes.Public | FieldAttributes.Family | FieldAttributes.FamORAssem |
|
||||
FieldAttributes.FamANDAssem | FieldAttributes.Assembly;
|
||||
f.Attributes = ((f.Attributes | dflags) ^ dflags) | FieldAttributes.Assembly;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var m in type.Properties)
|
||||
MarkAsUnstable(m, obsoleteCtor, forceUnstable);
|
||||
foreach (var m in type.Events)
|
||||
MarkAsUnstable(m, obsoleteCtor, forceUnstable);
|
||||
}
|
||||
|
||||
static void HideMethod(MethodDefinition m)
|
||||
{
|
||||
var dflags = MethodAttributes.Public | MethodAttributes.Family | MethodAttributes.FamORAssem |
|
||||
MethodAttributes.FamANDAssem | MethodAttributes.Assembly;
|
||||
m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly;
|
||||
}
|
||||
|
||||
static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, ICustomAttribute unstableAttribute)
|
||||
{
|
||||
if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute"))
|
||||
|
||||
@@ -117,9 +117,8 @@ namespace ControlCatalog.Pages
|
||||
private void ScrollTo(int index)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("Scroll to " + index);
|
||||
var layoutManager = ((TopLevel)VisualRoot!).LayoutManager;
|
||||
var element = _repeater.GetOrCreateElement(index);
|
||||
layoutManager.ExecuteLayoutPass();
|
||||
((TopLevel)VisualRoot!).UpdateLayout();
|
||||
element.BringIntoView();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace GpuInterop
|
||||
{
|
||||
InitializeComponent();
|
||||
this.AttachDevTools();
|
||||
Renderer.Diagnostics.DebugOverlays = RendererDebugOverlays.Fps;
|
||||
RendererDiagnostics.DebugOverlays = RendererDebugOverlays.Fps;
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace RenderDemo
|
||||
void BindOverlay(Expression<Func<MainWindowViewModel, bool>> expr, RendererDebugOverlays overlay)
|
||||
=> vm.WhenAnyValue(expr).Subscribe(x =>
|
||||
{
|
||||
var diagnostics = Renderer.Diagnostics;
|
||||
var diagnostics = RendererDiagnostics;
|
||||
diagnostics.DebugOverlays = x ?
|
||||
diagnostics.DebugOverlays | overlay :
|
||||
diagnostics.DebugOverlays & ~overlay;
|
||||
|
||||
@@ -347,12 +347,8 @@
|
||||
</UserControl.Styles>
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" ClipToBounds="False">
|
||||
<StackPanel.Clock>
|
||||
<Clock />
|
||||
</StackPanel.Clock>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="20">
|
||||
<TextBlock VerticalAlignment="Center">Hover to activate Keyframe Animations.</TextBlock>
|
||||
<Button Content="{Binding PlayStateText}" Command="{Binding TogglePlayState}" Click="ToggleClock" />
|
||||
</StackPanel>
|
||||
<WrapPanel ClipToBounds="False">
|
||||
<Border Classes="Test Rect1" Background="DarkRed"/>
|
||||
|
||||
@@ -24,20 +24,5 @@ namespace RenderDemo.Pages
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void ToggleClock(object sender, RoutedEventArgs args)
|
||||
{
|
||||
var button = sender as Button;
|
||||
var clock = button.Clock;
|
||||
|
||||
if (clock.PlayState == PlayState.Run)
|
||||
{
|
||||
clock.PlayState = PlayState.Pause;
|
||||
}
|
||||
else if (clock.PlayState == PlayState.Pause)
|
||||
{
|
||||
clock.PlayState = PlayState.Run;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,18 @@
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:1" IterationCount="Infinite">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Text" Value="" Animation.Animator="{x:Type pages:CustomStringAnimator}"/>
|
||||
<Setter Property="Text" Value="">
|
||||
<Animation.Animator>
|
||||
<pages:CustomStringAnimator/>
|
||||
</Animation.Animator>
|
||||
</Setter>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Text" Value="0123456789" Animation.Animator="{x:Type pages:CustomStringAnimator}"/>
|
||||
<Setter Property="Text" Value="0123456789" >
|
||||
<Animation.Animator>
|
||||
<pages:CustomStringAnimator/>
|
||||
</Animation.Animator>
|
||||
</Setter>
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Avalonia.Animation.Animators;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Animation.Animators;
|
||||
|
||||
namespace RenderDemo.Pages
|
||||
{
|
||||
public class CustomStringAnimator : Animator<string>
|
||||
public class CustomStringAnimator : CustomAnimatorBase<string>
|
||||
{
|
||||
public override string Interpolate(double progress, string oldValue, string newValue)
|
||||
{
|
||||
|
||||
@@ -134,9 +134,6 @@
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,*" RowDefinitions="*, Auto, Auto, Auto, Auto, Auto, Auto, Auto">
|
||||
<Grid.Clock>
|
||||
<Clock />
|
||||
</Grid.Clock>
|
||||
<Border Name="B1" Background="DarkRed" Classes="Test">
|
||||
<Border.RenderTransform>
|
||||
<Rotate3DTransform CenterZ="-100"
|
||||
|
||||
@@ -248,12 +248,8 @@
|
||||
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" ClipToBounds="False">
|
||||
<StackPanel.Clock>
|
||||
<Clock />
|
||||
</StackPanel.Clock>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="20">
|
||||
<TextBlock VerticalAlignment="Center">Hover to activate Transitions.</TextBlock>
|
||||
<Button Content="{Binding PlayStateText}" Command="{Binding TogglePlayState}" Click="ToggleClock" />
|
||||
</StackPanel>
|
||||
<WrapPanel ClipToBounds="False">
|
||||
<Border Classes="Test Rect1" Background="DarkRed"/>
|
||||
|
||||
@@ -18,20 +18,5 @@ namespace RenderDemo.Pages
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void ToggleClock(object sender, RoutedEventArgs args)
|
||||
{
|
||||
var button = sender as Button;
|
||||
var clock = button.Clock;
|
||||
|
||||
if (clock.PlayState == PlayState.Run)
|
||||
{
|
||||
clock.PlayState = PlayState.Pause;
|
||||
}
|
||||
else if (clock.PlayState == PlayState.Pause)
|
||||
{
|
||||
clock.PlayState = PlayState.Run;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ namespace Avalonia.Android
|
||||
.Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
|
||||
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoaderStub>()
|
||||
.Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())
|
||||
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
|
||||
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
|
||||
|
||||
if (Options.UseGpu)
|
||||
|
||||
@@ -103,9 +103,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
|
||||
|
||||
public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer, Handle };
|
||||
|
||||
public IRenderer CreateRenderer(IRenderRoot root) =>
|
||||
new CompositingRenderer(root, AndroidPlatform.Compositor, () => Surfaces);
|
||||
|
||||
public Compositor Compositor => AndroidPlatform.Compositor;
|
||||
|
||||
public virtual void Hide()
|
||||
{
|
||||
_view.Visibility = ViewStates.Invisible;
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Clock"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<IClock> ClockProperty =
|
||||
internal static readonly StyledProperty<IClock> ClockProperty =
|
||||
AvaloniaProperty.Register<Animatable, IClock>(nameof(Clock), inherits: true);
|
||||
|
||||
/// <summary>
|
||||
@@ -36,7 +36,7 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Gets or sets the clock which controls the animations on the control.
|
||||
/// </summary>
|
||||
public IClock Clock
|
||||
internal IClock Clock
|
||||
{
|
||||
get => GetValue(ClockProperty);
|
||||
set => SetValue(ClockProperty, value);
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Tracks the progress of an animation.
|
||||
/// </summary>
|
||||
public class Animation : AvaloniaObject, IAnimation
|
||||
public sealed class Animation : AvaloniaObject, IAnimation
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Duration"/> property.
|
||||
@@ -186,7 +186,7 @@ namespace Avalonia.Animation
|
||||
/// </summary>
|
||||
/// <param name="setter">The animation setter.</param>
|
||||
/// <returns>The property animator type.</returns>
|
||||
public static (Type Type, Func<IAnimator> Factory)? GetAnimator(IAnimationSetter setter)
|
||||
internal static (Type Type, Func<IAnimator> Factory)? GetAnimator(IAnimationSetter setter)
|
||||
{
|
||||
if (s_animators.TryGetValue(setter, out var type))
|
||||
{
|
||||
@@ -200,11 +200,9 @@ namespace Avalonia.Animation
|
||||
/// </summary>
|
||||
/// <param name="setter">The animation setter.</param>
|
||||
/// <param name="value">The property animator value.</param>
|
||||
public static void SetAnimator(IAnimationSetter setter,
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicMethods)]
|
||||
Type value)
|
||||
public static void SetAnimator(IAnimationSetter setter, CustomAnimatorBase value)
|
||||
{
|
||||
s_animators[setter] = (value, () => (IAnimator)Activator.CreateInstance(value)!);
|
||||
s_animators[setter] = (value.WrapperType, value.CreateWrapper);
|
||||
}
|
||||
|
||||
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator, Func<IAnimator> Factory)> Animators = new()
|
||||
@@ -233,7 +231,7 @@ namespace Avalonia.Animation
|
||||
/// <typeparam name="TAnimator">
|
||||
/// The type of the animator to instantiate.
|
||||
/// </typeparam>
|
||||
public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
|
||||
internal static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
|
||||
where TAnimator : IAnimator, new()
|
||||
{
|
||||
Animators.Insert(0, (condition, typeof(TAnimator), () => new TAnimator()));
|
||||
@@ -312,8 +310,11 @@ namespace Avalonia.Animation
|
||||
return (newAnimatorInstances, subscriptions);
|
||||
}
|
||||
|
||||
IDisposable IAnimation.Apply(Animatable control, IClock? clock, IObservable<bool> match, Action? onComplete)
|
||||
=> Apply(control, clock, match, onComplete);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDisposable Apply(Animatable control, IClock? clock, IObservable<bool> match, Action? onComplete)
|
||||
internal IDisposable Apply(Animatable control, IClock? clock, IObservable<bool> match, Action? onComplete)
|
||||
{
|
||||
var (animators, subscriptions) = InterpretKeyframes(control);
|
||||
if (animators.Count == 1)
|
||||
@@ -358,14 +359,19 @@ namespace Avalonia.Animation
|
||||
return new CompositeDisposable(subscriptions);
|
||||
}
|
||||
|
||||
public Task RunAsync(Animatable control) => RunAsync(control, null);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task RunAsync(Animatable control, IClock? clock = null)
|
||||
internal Task RunAsync(Animatable control, IClock? clock)
|
||||
{
|
||||
return RunAsync(control, clock, default);
|
||||
}
|
||||
|
||||
Task IAnimation.RunAsync(Animatable control, IClock? clock, CancellationToken cancellationToken)
|
||||
=> RunAsync(control, clock, cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task RunAsync(Animatable control, IClock? clock = null, CancellationToken cancellationToken = default)
|
||||
internal Task RunAsync(Animatable control, IClock? clock, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using Avalonia.Animation.Animators;
|
||||
using Avalonia.Animation.Easings;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
{
|
||||
@@ -8,13 +9,11 @@ namespace Avalonia.Animation
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the transitioned value.</typeparam>
|
||||
/// <typeparam name="TAnimator">Type of the animator.</typeparam>
|
||||
public abstract class AnimatorDrivenTransition<T, TAnimator> : Transition<T> where TAnimator : Animator<T>, new()
|
||||
internal static class AnimatorDrivenTransition<T, TAnimator> where TAnimator : Animator<T>, new()
|
||||
{
|
||||
private static readonly TAnimator s_animator = new TAnimator();
|
||||
|
||||
public override IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue)
|
||||
{
|
||||
return new AnimatorTransitionObservable<T, TAnimator>(s_animator, progress, Easing, oldValue, newValue);
|
||||
}
|
||||
public static IObservable<T> Transition(IEasing easing, IObservable<double> progress, T oldValue, T newValue) =>
|
||||
new AnimatorTransitionObservable<T, TAnimator>(s_animator, progress, easing, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Avalonia.Animation
|
||||
/// Defines a KeyFrame that is used for
|
||||
/// <see cref="Animator{T}"/> objects.
|
||||
/// </summary>
|
||||
public class AnimatorKeyFrame : AvaloniaObject
|
||||
internal class AnimatorKeyFrame : AvaloniaObject
|
||||
{
|
||||
public static readonly DirectProperty<AnimatorKeyFrame, object?> ValueProperty =
|
||||
AvaloniaProperty.RegisterDirect<AnimatorKeyFrame, object?>(nameof(Value), k => k.Value, (k, v) => k.Value = v);
|
||||
|
||||
@@ -9,17 +9,15 @@ namespace Avalonia.Animation
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the transitioned value.</typeparam>
|
||||
/// <typeparam name="TAnimator">Type of the animator.</typeparam>
|
||||
public class AnimatorTransitionObservable<T, TAnimator> : TransitionObservableBase<T> where TAnimator : Animator<T>
|
||||
internal class AnimatorTransitionObservable<T, TAnimator> : TransitionObservableBase<T> where TAnimator : Animator<T>
|
||||
{
|
||||
private readonly TAnimator _animator;
|
||||
private readonly Easing _easing;
|
||||
private readonly T _oldValue;
|
||||
private readonly T _newValue;
|
||||
|
||||
public AnimatorTransitionObservable(TAnimator animator, IObservable<double> progress, Easing easing, T oldValue, T newValue) : base(progress, easing)
|
||||
public AnimatorTransitionObservable(TAnimator animator, IObservable<double> progress, IEasing easing, T oldValue, T newValue) : base(progress, easing)
|
||||
{
|
||||
_animator = animator;
|
||||
_easing = easing;
|
||||
_oldValue = oldValue;
|
||||
_newValue = newValue;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Base class for <see cref="Animator{T}"/> objects
|
||||
/// </summary>
|
||||
public abstract class Animator<T> : AvaloniaList<AnimatorKeyFrame>, IAnimator
|
||||
internal abstract class Animator<T> : AvaloniaList<AnimatorKeyFrame>, IAnimator
|
||||
{
|
||||
/// <summary>
|
||||
/// List of type-converted keyframes.
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// redirect them to the properly registered
|
||||
/// animators in this class.
|
||||
/// </summary>
|
||||
public class BaseBrushAnimator : Animator<IBrush?>
|
||||
internal class BaseBrushAnimator : Animator<IBrush?>
|
||||
{
|
||||
private static readonly List<(Func<Type, bool> Match, Type AnimatorType, Func<IAnimator> AnimatorFactory)> _brushAnimators = new();
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="bool"/> properties.
|
||||
/// </summary>
|
||||
public class BoolAnimator : Animator<bool>
|
||||
internal class BoolAnimator : Animator<bool>
|
||||
{
|
||||
/// <inheritdocs/>
|
||||
public override bool Interpolate(double progress, bool oldValue, bool newValue)
|
||||
|
||||
@@ -2,7 +2,7 @@ using Avalonia.Media;
|
||||
|
||||
namespace Avalonia.Animation.Animators
|
||||
{
|
||||
public class BoxShadowAnimator : Animator<BoxShadow>
|
||||
internal class BoxShadowAnimator : Animator<BoxShadow>
|
||||
{
|
||||
static ColorAnimator s_colorAnimator = new ColorAnimator();
|
||||
static DoubleAnimator s_doubleAnimator = new DoubleAnimator();
|
||||
|
||||
@@ -2,7 +2,7 @@ using Avalonia.Media;
|
||||
|
||||
namespace Avalonia.Animation.Animators
|
||||
{
|
||||
public class BoxShadowsAnimator : Animator<BoxShadows>
|
||||
internal class BoxShadowsAnimator : Animator<BoxShadows>
|
||||
{
|
||||
private static readonly BoxShadowAnimator s_boxShadowAnimator = new BoxShadowAnimator();
|
||||
public override BoxShadows Interpolate(double progress, BoxShadows oldValue, BoxShadows newValue)
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="byte"/> properties.
|
||||
/// </summary>
|
||||
public class ByteAnimator : Animator<byte>
|
||||
internal class ByteAnimator : Animator<byte>
|
||||
{
|
||||
const double maxVal = (double)byte.MaxValue;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// Animator that interpolates <see cref="Color"/> through
|
||||
/// gamma sRGB color space for better visual result.
|
||||
/// </summary>
|
||||
public class ColorAnimator : Animator<Color>
|
||||
internal class ColorAnimator : Animator<Color>
|
||||
{
|
||||
/// <summary>
|
||||
/// Opto-electronic conversion function for the sRGB color space.
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="CornerRadius"/> properties.
|
||||
/// </summary>
|
||||
public class CornerRadiusAnimator : Animator<CornerRadius>
|
||||
internal class CornerRadiusAnimator : Animator<CornerRadius>
|
||||
{
|
||||
public override CornerRadius Interpolate(double progress, CornerRadius oldValue, CornerRadius newValue)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="decimal"/> properties.
|
||||
/// </summary>
|
||||
public class DecimalAnimator : Animator<decimal>
|
||||
internal class DecimalAnimator : Animator<decimal>
|
||||
{
|
||||
/// <inheritdocs/>
|
||||
public override decimal Interpolate(double progress, decimal oldValue, decimal newValue)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="double"/> properties.
|
||||
/// </summary>
|
||||
public class DoubleAnimator : Animator<double>
|
||||
internal class DoubleAnimator : Animator<double>
|
||||
{
|
||||
/// <inheritdocs/>
|
||||
public override double Interpolate(double progress, double oldValue, double newValue)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="float"/> properties.
|
||||
/// </summary>
|
||||
public class FloatAnimator : Animator<float>
|
||||
internal class FloatAnimator : Animator<float>
|
||||
{
|
||||
/// <inheritdocs/>
|
||||
public override float Interpolate(double progress, float oldValue, float newValue)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="SolidColorBrush"/> values.
|
||||
/// </summary>
|
||||
public class GradientBrushAnimator : Animator<IGradientBrush?>
|
||||
internal class GradientBrushAnimator : Animator<IGradientBrush?>
|
||||
{
|
||||
private static readonly RelativePointAnimator s_relativePointAnimator = new RelativePointAnimator();
|
||||
private static readonly DoubleAnimator s_doubleAnimator = new DoubleAnimator();
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="Int16"/> properties.
|
||||
/// </summary>
|
||||
public class Int16Animator : Animator<Int16>
|
||||
internal class Int16Animator : Animator<Int16>
|
||||
{
|
||||
const double maxVal = (double)Int16.MaxValue;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="Int32"/> properties.
|
||||
/// </summary>
|
||||
public class Int32Animator : Animator<Int32>
|
||||
internal class Int32Animator : Animator<Int32>
|
||||
{
|
||||
const double maxVal = (double)Int32.MaxValue;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="Int64"/> properties.
|
||||
/// </summary>
|
||||
public class Int64Animator : Animator<Int64>
|
||||
internal class Int64Animator : Animator<Int64>
|
||||
{
|
||||
const double maxVal = (double)Int64.MaxValue;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="Point"/> properties.
|
||||
/// </summary>
|
||||
public class PointAnimator : Animator<Point>
|
||||
internal class PointAnimator : Animator<Point>
|
||||
{
|
||||
public override Point Interpolate(double progress, Point oldValue, Point newValue)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="Rect"/> properties.
|
||||
/// </summary>
|
||||
public class RectAnimator : Animator<Rect>
|
||||
internal class RectAnimator : Animator<Rect>
|
||||
{
|
||||
public override Rect Interpolate(double progress, Rect oldValue, Rect newValue)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="RelativePoint"/> properties.
|
||||
/// </summary>
|
||||
public class RelativePointAnimator : Animator<RelativePoint>
|
||||
internal class RelativePointAnimator : Animator<RelativePoint>
|
||||
{
|
||||
private static readonly PointAnimator s_pointAnimator = new PointAnimator();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="Size"/> properties.
|
||||
/// </summary>
|
||||
public class SizeAnimator : Animator<Size>
|
||||
internal class SizeAnimator : Animator<Size>
|
||||
{
|
||||
public override Size Interpolate(double progress, Size oldValue, Size newValue)
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="SolidColorBrush"/> values.
|
||||
/// </summary>
|
||||
public class ISolidColorBrushAnimator : Animator<ISolidColorBrush?>
|
||||
internal class ISolidColorBrushAnimator : Animator<ISolidColorBrush?>
|
||||
{
|
||||
private static readonly DoubleAnimator s_doubleAnimator = new DoubleAnimator();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="Thickness"/> properties.
|
||||
/// </summary>
|
||||
public class ThicknessAnimator : Animator<Thickness>
|
||||
internal class ThicknessAnimator : Animator<Thickness>
|
||||
{
|
||||
public override Thickness Interpolate(double progress, Thickness oldValue, Thickness newValue)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="Transform"/> properties.
|
||||
/// </summary>
|
||||
public class TransformAnimator : Animator<double>
|
||||
internal class TransformAnimator : Animator<double>
|
||||
{
|
||||
DoubleAnimator? _doubleAnimator;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ using Avalonia.Media.Transformation;
|
||||
|
||||
namespace Avalonia.Animation.Animators
|
||||
{
|
||||
public class TransformOperationsAnimator : Animator<TransformOperations>
|
||||
internal class TransformOperationsAnimator : Animator<TransformOperations>
|
||||
{
|
||||
public TransformOperationsAnimator()
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="UInt16"/> properties.
|
||||
/// </summary>
|
||||
public class UInt16Animator : Animator<UInt16>
|
||||
internal class UInt16Animator : Animator<UInt16>
|
||||
{
|
||||
const double maxVal = (double)UInt16.MaxValue;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="UInt32"/> properties.
|
||||
/// </summary>
|
||||
public class UInt32Animator : Animator<UInt32>
|
||||
internal class UInt32Animator : Animator<UInt32>
|
||||
{
|
||||
const double maxVal = (double)UInt32.MaxValue;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="UInt64"/> properties.
|
||||
/// </summary>
|
||||
public class UInt64Animator : Animator<UInt64>
|
||||
internal class UInt64Animator : Animator<UInt64>
|
||||
{
|
||||
const double maxVal = (double)UInt64.MaxValue;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Avalonia.Animation.Animators
|
||||
/// <summary>
|
||||
/// Animator that handles <see cref="Vector"/> properties.
|
||||
/// </summary>
|
||||
public class VectorAnimator : Animator<Vector>
|
||||
internal class VectorAnimator : Animator<Vector>
|
||||
{
|
||||
public override Vector Interpolate(double progress, Vector oldValue, Vector newValue)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,10 @@ using Avalonia.Reactive;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
{
|
||||
public class Clock : ClockBase
|
||||
// Note: this class was always broken: it subscribes to the global clock immediately even it if
|
||||
// doesn't actually have subscriptions
|
||||
|
||||
internal class Clock : ClockBase
|
||||
{
|
||||
public static IClock GlobalClock => AvaloniaLocator.Current.GetRequiredService<IGlobalClock>();
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using Avalonia.Reactive;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
{
|
||||
public class ClockBase : IClock
|
||||
internal class ClockBase : IClock
|
||||
{
|
||||
private readonly ClockObservable _observable;
|
||||
|
||||
|
||||
@@ -64,11 +64,6 @@ public class SpringEasing : Easing
|
||||
Damping = damping;
|
||||
InitialVelocity = initialVelocity;
|
||||
}
|
||||
|
||||
public SpringEasing(Spring keySpline)
|
||||
{
|
||||
_internalSpring = keySpline;
|
||||
}
|
||||
|
||||
public SpringEasing()
|
||||
{
|
||||
|
||||
@@ -14,11 +14,11 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Apply the animation to the specified control and run it when <paramref name="match" /> produces <c>true</c>.
|
||||
/// </summary>
|
||||
IDisposable Apply(Animatable control, IClock? clock, IObservable<bool> match, Action? onComplete = null);
|
||||
internal IDisposable Apply(Animatable control, IClock? clock, IObservable<bool> match, Action? onComplete = null);
|
||||
|
||||
/// <summary>
|
||||
/// Run the animation on the specified control.
|
||||
/// </summary>
|
||||
Task RunAsync(Animatable control, IClock clock, CancellationToken cancellationToken = default);
|
||||
internal Task RunAsync(Animatable control, IClock clock, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ using Avalonia.Metadata;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
{
|
||||
[NotClientImplementable]
|
||||
[NotClientImplementable, PrivateApi]
|
||||
public interface IAnimationSetter
|
||||
{
|
||||
AvaloniaProperty? Property { get; set; }
|
||||
|
||||
@@ -7,8 +7,7 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Interface for Animator objects
|
||||
/// </summary>
|
||||
[NotClientImplementable]
|
||||
public interface IAnimator : IList<AnimatorKeyFrame>
|
||||
internal interface IAnimator : IList<AnimatorKeyFrame>
|
||||
{
|
||||
/// <summary>
|
||||
/// The target property.
|
||||
|
||||
@@ -3,8 +3,7 @@ using Avalonia.Metadata;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
{
|
||||
[NotClientImplementable]
|
||||
public interface IClock : IObservable<TimeSpan>
|
||||
internal interface IClock : IObservable<TimeSpan>
|
||||
{
|
||||
PlayState PlayState { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using Avalonia.Animation.Animators;
|
||||
|
||||
namespace Avalonia.Animation;
|
||||
|
||||
public abstract class CustomAnimatorBase
|
||||
{
|
||||
internal abstract IAnimator CreateWrapper();
|
||||
internal abstract Type WrapperType { get; }
|
||||
}
|
||||
|
||||
public abstract class CustomAnimatorBase<T> : CustomAnimatorBase
|
||||
{
|
||||
public abstract T Interpolate(double progress, T oldValue, T newValue);
|
||||
|
||||
internal override Type WrapperType => typeof(AnimatorWrapper);
|
||||
internal override IAnimator CreateWrapper() => new AnimatorWrapper(this);
|
||||
|
||||
internal class AnimatorWrapper : Animator<T>
|
||||
{
|
||||
private readonly CustomAnimatorBase<T> _parent;
|
||||
|
||||
public AnimatorWrapper(CustomAnimatorBase<T> parent)
|
||||
{
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,7 @@ using Avalonia.Metadata;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
{
|
||||
[NotClientImplementable]
|
||||
public interface IGlobalClock : IClock
|
||||
internal interface IGlobalClock : IClock
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Interface for Transition objects.
|
||||
/// </summary>
|
||||
[NotClientImplementable]
|
||||
[NotClientImplementable, PrivateApi]
|
||||
public interface ITransition
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies the transition to the specified <see cref="Animatable"/>.
|
||||
/// </summary>
|
||||
IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue);
|
||||
internal IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property to be animated.
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Avalonia.Animation
|
||||
/// Stores data regarding a specific key
|
||||
/// point and value in an animation.
|
||||
/// </summary>
|
||||
public class KeyFrame : AvaloniaObject
|
||||
public sealed class KeyFrame : AvaloniaObject
|
||||
{
|
||||
private TimeSpan _ktimeSpan;
|
||||
private Cue _kCue;
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// A collection of <see cref="KeyFrame"/>s.
|
||||
/// </summary>
|
||||
public class KeyFrames : AvaloniaList<KeyFrame>
|
||||
public sealed class KeyFrames : AvaloniaList<KeyFrame>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="KeyFrames"/> class.
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Avalonia.Animation
|
||||
/// See https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.animation.keyspline
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(KeySplineTypeConverter))]
|
||||
public class KeySpline : AvaloniaObject
|
||||
public sealed class KeySpline : AvaloniaObject
|
||||
{
|
||||
// Control points
|
||||
private double _controlPointX1;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Avalonia.Rendering;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
{
|
||||
public class RenderLoopClock : ClockBase, IRenderLoopTask, IGlobalClock
|
||||
{
|
||||
protected override void Stop()
|
||||
{
|
||||
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>().Remove(this);
|
||||
}
|
||||
|
||||
bool IRenderLoopTask.NeedsUpdate => HasSubscriptions;
|
||||
|
||||
void IRenderLoopTask.Render()
|
||||
{
|
||||
}
|
||||
|
||||
void IRenderLoopTask.Update(TimeSpan time)
|
||||
{
|
||||
Pulse(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace Avalonia.Animation;
|
||||
/// Determines how an animation is used based on spring formula.
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(SpringTypeConverter))]
|
||||
public class Spring
|
||||
internal class Spring
|
||||
{
|
||||
private SpringSolver _springSolver;
|
||||
private double _mass;
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Avalonia.Animation;
|
||||
/// <summary>
|
||||
/// Converts string values to <see cref="Spring"/> values.
|
||||
/// </summary>
|
||||
public class SpringTypeConverter : TypeConverter
|
||||
internal class SpringTypeConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
{
|
||||
|
||||
@@ -53,10 +53,13 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Apply interpolation to the property.
|
||||
/// </summary>
|
||||
public abstract IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue);
|
||||
internal abstract IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue);
|
||||
|
||||
/// <inheritdocs/>
|
||||
public virtual IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue)
|
||||
IDisposable ITransition.Apply(Animatable control, IClock clock, object? oldValue, object? newValue)
|
||||
=> Apply(control, clock, oldValue, newValue);
|
||||
|
||||
internal virtual IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue)
|
||||
{
|
||||
if (Property is null)
|
||||
throw new InvalidOperationException("Transition has no property specified.");
|
||||
|
||||
@@ -12,11 +12,11 @@ namespace Avalonia.Animation
|
||||
/// <typeparam name="T">Type of the transitioned value.</typeparam>
|
||||
public abstract class TransitionObservableBase<T> : SingleSubscriberObservableBase<T>, IObserver<double>
|
||||
{
|
||||
private readonly Easing _easing;
|
||||
private readonly IEasing _easing;
|
||||
private readonly IObservable<double> _progress;
|
||||
private IDisposable? _progressSubscription;
|
||||
|
||||
protected TransitionObservableBase(IObservable<double> progress, Easing easing)
|
||||
protected TransitionObservableBase(IObservable<double> progress, IEasing easing)
|
||||
{
|
||||
_progress = progress;
|
||||
_easing = easing;
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// A collection of <see cref="ITransition"/> definitions.
|
||||
/// </summary>
|
||||
public class Transitions : AvaloniaList<ITransition>
|
||||
public sealed class Transitions : AvaloniaList<ITransition>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Transitions"/> class.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.Animation.Animators;
|
||||
using Avalonia.Media;
|
||||
|
||||
@@ -6,7 +7,10 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="BoxShadows"/> type.
|
||||
/// </summary>
|
||||
public class BoxShadowsTransition : AnimatorDrivenTransition<BoxShadows, BoxShadowsAnimator>
|
||||
public class BoxShadowsTransition : Transition<BoxShadows>
|
||||
{
|
||||
internal override IObservable<BoxShadows> DoTransition(IObservable<double> progress, BoxShadows oldValue,
|
||||
BoxShadows newValue) =>
|
||||
AnimatorDrivenTransition<BoxShadows, BoxShadowsAnimator>.Transition(Easing, progress, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Avalonia.Animation
|
||||
private static readonly GradientBrushAnimator s_gradientAnimator = new GradientBrushAnimator();
|
||||
private static readonly ISolidColorBrushAnimator s_solidColorBrushAnimator = new ISolidColorBrushAnimator();
|
||||
|
||||
public override IObservable<IBrush?> DoTransition(IObservable<double> progress, IBrush? oldValue, IBrush? newValue)
|
||||
internal override IObservable<IBrush?> DoTransition(IObservable<double> progress, IBrush? oldValue, IBrush? newValue)
|
||||
{
|
||||
if (oldValue is null || newValue is null)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Avalonia.Animation.Animators;
|
||||
using System;
|
||||
using Avalonia.Animation.Animators;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
@@ -6,7 +7,9 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Color"/> type.
|
||||
/// </summary>
|
||||
public class ColorTransition : AnimatorDrivenTransition<Color, ColorAnimator>
|
||||
public class ColorTransition : Transition<Color>
|
||||
{
|
||||
internal override IObservable<Color> DoTransition(IObservable<double> progress, Color oldValue, Color newValue)
|
||||
=> AnimatorDrivenTransition<Color, ColorAnimator>.Transition(Easing, progress, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.Animation.Animators;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
@@ -5,7 +6,11 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="CornerRadius"/> type.
|
||||
/// </summary>
|
||||
public class CornerRadiusTransition : AnimatorDrivenTransition<CornerRadius, CornerRadiusAnimator>
|
||||
public class CornerRadiusTransition : Transition<CornerRadius>
|
||||
{
|
||||
internal override IObservable<CornerRadius> DoTransition(IObservable<double> progress, CornerRadius oldValue,
|
||||
CornerRadius newValue) =>
|
||||
AnimatorDrivenTransition<CornerRadius, CornerRadiusAnimator>.Transition(Easing, progress, oldValue,
|
||||
newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.Animation.Animators;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
@@ -5,7 +6,9 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="double"/> types.
|
||||
/// </summary>
|
||||
public class DoubleTransition : AnimatorDrivenTransition<double, DoubleAnimator>
|
||||
public class DoubleTransition : Transition<double>
|
||||
{
|
||||
internal override IObservable<double> DoTransition(IObservable<double> progress, double oldValue, double newValue) =>
|
||||
AnimatorDrivenTransition<double, DoubleAnimator>.Transition(Easing, progress, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.Animation.Animators;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
@@ -5,7 +6,9 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="float"/> types.
|
||||
/// </summary>
|
||||
public class FloatTransition : AnimatorDrivenTransition<float, FloatAnimator>
|
||||
public class FloatTransition : Transition<float>
|
||||
{
|
||||
internal override IObservable<float> DoTransition(IObservable<double> progress, float oldValue, float newValue) =>
|
||||
AnimatorDrivenTransition<float, FloatAnimator>.Transition(Easing, progress, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.Animation.Animators;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
@@ -5,7 +6,9 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="int"/> types.
|
||||
/// </summary>
|
||||
public class IntegerTransition : AnimatorDrivenTransition<int, Int32Animator>
|
||||
public class IntegerTransition : Transition<int>
|
||||
{
|
||||
internal override IObservable<int> DoTransition(IObservable<double> progress, int oldValue, int newValue) =>
|
||||
AnimatorDrivenTransition<int, Int32Animator>.Transition(Easing, progress, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.Animation.Animators;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
@@ -5,7 +6,9 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Point"/> type.
|
||||
/// </summary>
|
||||
public class PointTransition : AnimatorDrivenTransition<Point, PointAnimator>
|
||||
public class PointTransition : Transition<Point>
|
||||
{
|
||||
internal override IObservable<Point> DoTransition(IObservable<double> progress, Point oldValue, Point newValue) =>
|
||||
AnimatorDrivenTransition<Point, PointAnimator>.Transition(Easing, progress, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.Animation.Animators;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
@@ -5,7 +6,9 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="RelativePoint"/> type.
|
||||
/// </summary>
|
||||
public class RelativePointTransition : AnimatorDrivenTransition<RelativePoint, RelativePointAnimator>
|
||||
public class RelativePointTransition : Transition<RelativePoint>
|
||||
{
|
||||
internal override IObservable<RelativePoint> DoTransition(IObservable<double> progress, RelativePoint oldValue, RelativePoint newValue) =>
|
||||
AnimatorDrivenTransition<RelativePoint, RelativePointAnimator>.Transition(Easing, progress, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.Animation.Animators;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
@@ -5,7 +6,9 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Size"/> type.
|
||||
/// </summary>
|
||||
public class SizeTransition : AnimatorDrivenTransition<Size, SizeAnimator>
|
||||
public class SizeTransition : Transition<Size>
|
||||
{
|
||||
internal override IObservable<Size> DoTransition(IObservable<double> progress, Size oldValue, Size newValue) =>
|
||||
AnimatorDrivenTransition<Size, SizeAnimator>.Transition(Easing, progress, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.Animation.Animators;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
@@ -5,7 +6,9 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Thickness"/> type.
|
||||
/// </summary>
|
||||
public class ThicknessTransition : AnimatorDrivenTransition<Thickness, ThicknessAnimator>
|
||||
public class ThicknessTransition : Transition<Thickness>
|
||||
{
|
||||
internal override IObservable<Thickness> DoTransition(IObservable<double> progress, Thickness oldValue, Thickness newValue) =>
|
||||
AnimatorDrivenTransition<Thickness, ThicknessAnimator>.Transition(Easing, progress, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Avalonia.Animation
|
||||
{
|
||||
private static readonly TransformOperationsAnimator s_operationsAnimator = new TransformOperationsAnimator();
|
||||
|
||||
public override IObservable<ITransform> DoTransition(
|
||||
internal override IObservable<ITransform> DoTransition(
|
||||
IObservable<double> progress,
|
||||
ITransform oldValue,
|
||||
ITransform newValue)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.Animation.Animators;
|
||||
|
||||
namespace Avalonia.Animation
|
||||
@@ -5,7 +6,9 @@ namespace Avalonia.Animation
|
||||
/// <summary>
|
||||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Vector"/> type.
|
||||
/// </summary>
|
||||
public class VectorTransition : AnimatorDrivenTransition<Vector, VectorAnimator>
|
||||
public class VectorTransition : Transition<Vector>
|
||||
{
|
||||
internal override IObservable<Vector> DoTransition(IObservable<double> progress, Vector oldValue, Vector newValue) =>
|
||||
AnimatorDrivenTransition<Vector, VectorAnimator>.Transition(Easing, progress, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace Avalonia.Input.TextInput
|
||||
if(_queuedForUpdate)
|
||||
return;
|
||||
_queuedForUpdate = true;
|
||||
Dispatcher.UIThread.Post(UpdateMatrix, DispatcherPriority.Render);
|
||||
Dispatcher.UIThread.Post(UpdateMatrix, DispatcherPriority.AfterRender);
|
||||
}
|
||||
|
||||
private void PropertyChangedHandler(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Avalonia.Layout
|
||||
/// Manages measuring and arranging of controls.
|
||||
/// </summary>
|
||||
[NotClientImplementable]
|
||||
public interface ILayoutManager : IDisposable
|
||||
internal interface ILayoutManager : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Raised when the layout manager completes a layout pass.
|
||||
|
||||
@@ -21,6 +21,6 @@ namespace Avalonia.Layout
|
||||
/// <summary>
|
||||
/// Associated instance of layout manager
|
||||
/// </summary>
|
||||
ILayoutManager LayoutManager { get; }
|
||||
internal ILayoutManager LayoutManager { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Avalonia.Logging;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Rendering;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.Utilities;
|
||||
@@ -15,14 +16,13 @@ namespace Avalonia.Layout
|
||||
/// <summary>
|
||||
/// Manages measuring and arranging of controls.
|
||||
/// </summary>
|
||||
public class LayoutManager : ILayoutManager, IDisposable
|
||||
internal class LayoutManager : ILayoutManager, IDisposable
|
||||
{
|
||||
private const int MaxPasses = 10;
|
||||
private readonly Layoutable _owner;
|
||||
private readonly LayoutQueue<Layoutable> _toMeasure = new LayoutQueue<Layoutable>(v => !v.IsMeasureValid);
|
||||
private readonly LayoutQueue<Layoutable> _toArrange = new LayoutQueue<Layoutable>(v => !v.IsArrangeValid);
|
||||
private readonly List<Layoutable> _toArrangeAfterMeasure = new();
|
||||
private readonly Action _executeLayoutPass;
|
||||
private List<EffectiveViewportChangedListener>? _effectiveViewportChangedListeners;
|
||||
private bool _disposed;
|
||||
private bool _queued;
|
||||
@@ -32,7 +32,6 @@ namespace Avalonia.Layout
|
||||
public LayoutManager(ILayoutRoot owner)
|
||||
{
|
||||
_owner = owner as Layoutable ?? throw new ArgumentNullException(nameof(owner));
|
||||
_executeLayoutPass = ExecuteQueuedLayoutPass;
|
||||
}
|
||||
|
||||
public virtual event EventHandler? LayoutUpdated;
|
||||
@@ -99,7 +98,7 @@ namespace Avalonia.Layout
|
||||
QueueLayoutPass();
|
||||
}
|
||||
|
||||
private void ExecuteQueuedLayoutPass()
|
||||
internal void ExecuteQueuedLayoutPass()
|
||||
{
|
||||
if (!_queued)
|
||||
{
|
||||
@@ -346,7 +345,7 @@ namespace Avalonia.Layout
|
||||
if (!_queued && !_running)
|
||||
{
|
||||
_queued = true;
|
||||
Dispatcher.UIThread.Post(_executeLayoutPass, DispatcherPriority.Layout);
|
||||
MediaContext.Instance.QueueLayoutPass(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Avalonia.Media
|
||||
/// <summary>
|
||||
/// Represents the sequence of dashes and gaps that will be applied by a <see cref="Pen"/>.
|
||||
/// </summary>
|
||||
public class DashStyle : Animatable, IDashStyle
|
||||
public sealed class DashStyle : Animatable, IDashStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Dashes"/> property.
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
/// </summary>
|
||||
public abstract class Drawing : AvaloniaObject
|
||||
{
|
||||
internal Drawing()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws this drawing to the given <see cref="DrawingContext"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Avalonia.Media
|
||||
/// <summary>
|
||||
/// Paints an area with an <see cref="Drawing"/>.
|
||||
/// </summary>
|
||||
public class DrawingBrush : TileBrush, ISceneBrush
|
||||
public sealed class DrawingBrush : TileBrush, ISceneBrush
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Drawing"/> property.
|
||||
|
||||
@@ -8,7 +8,7 @@ using Avalonia.Utilities;
|
||||
|
||||
namespace Avalonia.Media
|
||||
{
|
||||
public class DrawingGroup : Drawing
|
||||
public sealed class DrawingGroup : Drawing
|
||||
{
|
||||
public static readonly StyledProperty<double> OpacityProperty =
|
||||
AvaloniaProperty.Register<DrawingGroup, double>(nameof(Opacity), 1);
|
||||
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
// ReSharper disable CheckNamespace
|
||||
namespace Avalonia.Media;
|
||||
|
||||
public class BlurEffect : Effect, IBlurEffect, IMutableEffect
|
||||
public sealed class BlurEffect : Effect, IBlurEffect, IMutableEffect
|
||||
{
|
||||
public static readonly StyledProperty<double> RadiusProperty = AvaloniaProperty.Register<BlurEffect, double>(
|
||||
nameof(Radius), 5);
|
||||
|
||||
@@ -42,7 +42,7 @@ public abstract class DropShadowEffectBase : Effect
|
||||
}
|
||||
}
|
||||
|
||||
public class DropShadowEffect : DropShadowEffectBase, IDropShadowEffect, IMutableEffect
|
||||
public sealed class DropShadowEffect : DropShadowEffectBase, IDropShadowEffect, IMutableEffect
|
||||
{
|
||||
public static readonly StyledProperty<double> OffsetXProperty = AvaloniaProperty.Register<DropShadowEffect, double>(
|
||||
nameof(OffsetX), 3.5355);
|
||||
@@ -76,7 +76,7 @@ public class DropShadowEffect : DropShadowEffectBase, IDropShadowEffect, IMutabl
|
||||
/// <summary>
|
||||
/// This class is compatible with WPF's DropShadowEffect and provides Direction and ShadowDepth properties instead of OffsetX/OffsetY
|
||||
/// </summary>
|
||||
public class DropShadowDirectionEffect : DropShadowEffectBase, IDirectionDropShadowEffect, IMutableEffect
|
||||
public sealed class DropShadowDirectionEffect : DropShadowEffectBase, IDirectionDropShadowEffect, IMutableEffect
|
||||
{
|
||||
public static readonly StyledProperty<double> ShadowDepthProperty =
|
||||
AvaloniaProperty.Register<DropShadowDirectionEffect, double>(
|
||||
|
||||
@@ -90,4 +90,9 @@ public class Effect : Animatable, IAffectsRender
|
||||
{
|
||||
EffectAnimator.EnsureRegistered();
|
||||
}
|
||||
|
||||
internal Effect()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using Avalonia.Media;
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Avalonia.Animation.Animators;
|
||||
|
||||
public class EffectAnimator : Animator<IEffect?>
|
||||
internal class EffectAnimator : Animator<IEffect?>
|
||||
{
|
||||
public override IDisposable? Apply(Animation animation, Animatable control, IClock? clock,
|
||||
IObservable<bool> match, Action? onComplete)
|
||||
@@ -68,7 +68,7 @@ public class EffectAnimator : Animator<IEffect?>
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class EffectAnimatorBase<T> : Animator<IEffect?> where T : class, IEffect?
|
||||
internal abstract class EffectAnimatorBase<T> : Animator<IEffect?> where T : class, IEffect?
|
||||
{
|
||||
public override IDisposable BindAnimation(Animatable control, IObservable<IEffect?> instance)
|
||||
{
|
||||
@@ -91,7 +91,7 @@ public abstract class EffectAnimatorBase<T> : Animator<IEffect?> where T : class
|
||||
}
|
||||
}
|
||||
|
||||
public class BlurEffectAnimator : EffectAnimatorBase<IBlurEffect>
|
||||
internal class BlurEffectAnimator : EffectAnimatorBase<IBlurEffect>
|
||||
{
|
||||
private static readonly DoubleAnimator s_doubleAnimator = new DoubleAnimator();
|
||||
|
||||
@@ -102,7 +102,7 @@ public class BlurEffectAnimator : EffectAnimatorBase<IBlurEffect>
|
||||
}
|
||||
}
|
||||
|
||||
public class DropShadowEffectAnimator : EffectAnimatorBase<IDropShadowEffect>
|
||||
internal class DropShadowEffectAnimator : EffectAnimatorBase<IDropShadowEffect>
|
||||
{
|
||||
private static readonly DoubleAnimator s_doubleAnimator = new DoubleAnimator();
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ public class EffectTransition : Transition<IEffect?>
|
||||
|
||||
}
|
||||
|
||||
public override IObservable<IEffect?> DoTransition(IObservable<double> progress, IEffect? oldValue, IEffect? newValue)
|
||||
internal override IObservable<IEffect?> DoTransition(IObservable<double> progress, IEffect? oldValue, IEffect? newValue)
|
||||
{
|
||||
if ((oldValue != null || newValue != null)
|
||||
&& (
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Avalonia.Media
|
||||
/// Represents a drawing operation that combines
|
||||
/// a geometry with and brush and/or pen to produce rendered content.
|
||||
/// </summary>
|
||||
public class GeometryDrawing : Drawing
|
||||
public sealed class GeometryDrawing : Drawing
|
||||
{
|
||||
// Adding the Pen's stroke thickness here could yield wrong results due to transforms.
|
||||
private static readonly IPen s_boundsPen = new ImmutablePen(Colors.Black.ToUInt32(), 0);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Avalonia.Media
|
||||
{
|
||||
public class GlyphRunDrawing : Drawing
|
||||
public sealed class GlyphRunDrawing : Drawing
|
||||
{
|
||||
public static readonly StyledProperty<IBrush?> ForegroundProperty =
|
||||
AvaloniaProperty.Register<GlyphRunDrawing, IBrush?>(nameof(Foreground));
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Avalonia.Media
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012",
|
||||
Justification = "Collection properties shouldn't be set with SetCurrentValue.")]
|
||||
public GradientBrush()
|
||||
internal GradientBrush()
|
||||
{
|
||||
this.GradientStops = new GradientStops();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Avalonia.Media
|
||||
/// <summary>
|
||||
/// Paints an area with an <see cref="IBitmap"/>.
|
||||
/// </summary>
|
||||
public class ImageBrush : TileBrush, IImageBrush, IMutableBrush
|
||||
public sealed class ImageBrush : TileBrush, IImageBrush, IMutableBrush
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Visual"/> property.
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Avalonia.Media
|
||||
/// <summary>
|
||||
/// Draws an image within a region defined by a <see cref="Rect"/>.
|
||||
/// </summary>
|
||||
public class ImageDrawing : Drawing
|
||||
public sealed class ImageDrawing : Drawing
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ImageSource"/> property.
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Avalonia.Media
|
||||
/// <summary>
|
||||
/// Transforms an <see cref="Visual"/> according to a <see cref="Matrix"/>.
|
||||
/// </summary>
|
||||
public class MatrixTransform : Transform
|
||||
public sealed class MatrixTransform : Transform
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Matrix"/> property.
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Reactive;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace Avalonia.Media;
|
||||
|
||||
internal partial class MediaContext
|
||||
{
|
||||
private readonly MediaContextClock _clock;
|
||||
public IGlobalClock Clock => _clock;
|
||||
private readonly Stopwatch _time = Stopwatch.StartNew();
|
||||
|
||||
class MediaContextClock : IGlobalClock
|
||||
{
|
||||
private readonly MediaContext _parent;
|
||||
private List<IObserver<TimeSpan>> _observers = new();
|
||||
public bool HasNewSubscriptions { get; set; }
|
||||
public bool HasSubscriptions => _observers.Count > 0;
|
||||
|
||||
public MediaContextClock(MediaContext parent)
|
||||
{
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<TimeSpan> observer)
|
||||
{
|
||||
_parent.ScheduleRender(false);
|
||||
Dispatcher.UIThread.VerifyAccess();
|
||||
HasNewSubscriptions = true;
|
||||
_observers.Add(observer);
|
||||
return Disposable.Create(() =>
|
||||
{
|
||||
Dispatcher.UIThread.VerifyAccess();
|
||||
_observers.Remove(observer);
|
||||
});
|
||||
}
|
||||
|
||||
public void Pulse(TimeSpan now)
|
||||
{
|
||||
foreach (var observer in _observers.ToArray())
|
||||
observer.OnNext(now);
|
||||
}
|
||||
|
||||
public PlayState PlayState
|
||||
{
|
||||
get => PlayState.Run;
|
||||
set => throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Rendering.Composition;
|
||||
using Avalonia.Rendering.Composition.Transport;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace Avalonia.Media;
|
||||
|
||||
partial class MediaContext
|
||||
{
|
||||
private bool _scheduleCommitOnLastCompositionBatchCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Actually sends the current batch to the compositor and does the required housekeeping
|
||||
/// This is the only place that should be allowed to call Commit
|
||||
/// </summary>
|
||||
private Batch CommitCompositor(Compositor compositor)
|
||||
{
|
||||
var commit = compositor.Commit();
|
||||
_requestedCommits.Remove(compositor);
|
||||
_pendingCompositionBatches[compositor] = commit;
|
||||
commit.Processed.ContinueWith(_ =>
|
||||
Dispatcher.UIThread.Post(() => CompositionBatchFinished(compositor, commit), DispatcherPriority.Send));
|
||||
return commit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles batch completion, required to re-schedule a render pass if one was skipped due to compositor throttling
|
||||
/// </summary>
|
||||
private void CompositionBatchFinished(Compositor compositor, Batch batch)
|
||||
{
|
||||
// Check if it was the last commited batch, since sometimes we are forced to send a new
|
||||
// one without waiting for the previous one to complete
|
||||
if (_pendingCompositionBatches.TryGetValue(compositor, out var waitingForBatch) && waitingForBatch == batch)
|
||||
_pendingCompositionBatches.Remove(compositor);
|
||||
|
||||
if (_pendingCompositionBatches.Count == 0)
|
||||
{
|
||||
_animationsAreWaitingForComposition = false;
|
||||
|
||||
// Check if we have uncommited changes
|
||||
if (_scheduleCommitOnLastCompositionBatchCompletion && _pendingCompositionBatches.Count > 0)
|
||||
{
|
||||
CommitCompositorsWithThrottling();
|
||||
_scheduleCommitOnLastCompositionBatchCompletion = false;
|
||||
}
|
||||
// Check if there are active animations and schedule the next render
|
||||
else if(_clock.HasSubscriptions)
|
||||
ScheduleRender(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private bool CommitCompositorsWithThrottling()
|
||||
{
|
||||
// Check if we are still waiting for previous composition batches
|
||||
if (_pendingCompositionBatches.Count > 0)
|
||||
{
|
||||
_scheduleCommitOnLastCompositionBatchCompletion = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_requestedCommits.Count == 0)
|
||||
return false;
|
||||
|
||||
foreach (var c in _requestedCommits)
|
||||
CommitCompositor(c);
|
||||
|
||||
_requestedCommits.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a synchronous commit when we need to wait for composition jobs to be done
|
||||
/// Is used in resize and TopLevel destruction scenarios
|
||||
/// </summary>
|
||||
private void SyncCommit(Compositor compositor, bool waitFullRender)
|
||||
{
|
||||
// Unit tests are assuming that they can call any API without setting up platforms
|
||||
if (AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() == null)
|
||||
return;
|
||||
|
||||
if (compositor is
|
||||
{
|
||||
UseUiThreadForSynchronousCommits: false,
|
||||
Loop.RunsInBackground: true
|
||||
})
|
||||
{
|
||||
var batch = CommitCompositor(compositor);
|
||||
(waitFullRender ? batch.Rendered : batch.Processed).Wait();
|
||||
}
|
||||
else
|
||||
{
|
||||
CommitCompositor(compositor);
|
||||
compositor.Server.Render();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method handles synchronous rendering of a surface when requested by the OS (typically during the resize)
|
||||
/// </summary>
|
||||
// TODO: do we need to execute a render pass here too?
|
||||
// We've previously tried that and it made the resize experience worse
|
||||
public void ImmediateRenderRequested(CompositionTarget target)
|
||||
{
|
||||
SyncCommit(target.Compositor, true);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This method handles synchronous destruction of the composition target, so we are guaranteed
|
||||
/// to release all resources when a TopLevel is being destroyed
|
||||
/// </summary>
|
||||
public void SyncDisposeCompositionTarget(CompositionTarget compositionTarget)
|
||||
{
|
||||
compositionTarget.Dispose();
|
||||
|
||||
// TODO: introduce a way to skip any actual rendering for other targets and only do a dispose?
|
||||
SyncCommit(compositionTarget.Compositor, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method schedules a render when something has called RequestCommitAsync
|
||||
/// This can be triggered by user code outside of our normal layout and rendering
|
||||
/// </summary>
|
||||
void ICompositorScheduler.CommitRequested(Compositor compositor)
|
||||
{
|
||||
if(!_requestedCommits.Add(compositor))
|
||||
return;
|
||||
|
||||
// TODO: maybe skip the full render here?
|
||||
ScheduleRender(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Rendering;
|
||||
using Avalonia.Rendering.Composition;
|
||||
using Avalonia.Rendering.Composition.Transport;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace Avalonia.Media;
|
||||
|
||||
internal partial class MediaContext : ICompositorScheduler
|
||||
{
|
||||
private DispatcherOperation? _nextRenderOp;
|
||||
private DispatcherOperation? _inputMarkerOp;
|
||||
private TimeSpan _inputMarkerAddedAt;
|
||||
private bool _animationsAreWaitingForComposition;
|
||||
private const double MaxSecondsWithoutInput = 1;
|
||||
private readonly Action _render;
|
||||
private readonly Action _inputMarkerHandler;
|
||||
private readonly HashSet<Compositor> _requestedCommits = new();
|
||||
private readonly Dictionary<Compositor, Batch> _pendingCompositionBatches = new();
|
||||
private record TopLevelInfo(Compositor Compositor, CompositingRenderer Renderer, ILayoutManager LayoutManager);
|
||||
private readonly HashSet<LayoutManager> _queuedLayoutManagers = new();
|
||||
public static bool InputMarkerEnabled = true;
|
||||
|
||||
private Dictionary<object, TopLevelInfo> _topLevels = new();
|
||||
|
||||
private MediaContext()
|
||||
{
|
||||
_render = Render;
|
||||
_inputMarkerHandler = InputMarkerHandler;
|
||||
_clock = new(this);
|
||||
}
|
||||
|
||||
public static MediaContext Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
// Technically it's supposed to be a thread-static singleton, but we don't have multiple threads
|
||||
// and need to do a full reset for unit tests
|
||||
var context = AvaloniaLocator.Current.GetService<MediaContext>();
|
||||
if (context == null)
|
||||
AvaloniaLocator.CurrentMutable.Bind<MediaContext>().ToConstant(context = new());
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules the next render operation, handles render throttling for input processing
|
||||
/// </summary>
|
||||
private void ScheduleRender(bool now)
|
||||
{
|
||||
// Already scheduled, nothing to do
|
||||
if (_nextRenderOp != null)
|
||||
{
|
||||
if (now)
|
||||
_nextRenderOp.Priority = DispatcherPriority.Render;
|
||||
return;
|
||||
}
|
||||
// Sometimes our animation, layout and render passes might be taking more than a frame to complete
|
||||
// which can cause a "freeze"-like state when UI is being updated, but input is never being processed
|
||||
// So here we inject an operation with Input priority to check if Input wasn't being processed
|
||||
// for a long time. If that's the case the next rendering operation will be scheduled to happen after all pending input
|
||||
|
||||
var priority = DispatcherPriority.Render;
|
||||
|
||||
if (InputMarkerEnabled)
|
||||
{
|
||||
if (_inputMarkerOp == null)
|
||||
{
|
||||
_inputMarkerOp = Dispatcher.UIThread.InvokeAsync(_inputMarkerHandler, DispatcherPriority.Input);
|
||||
_inputMarkerAddedAt = _time.Elapsed;
|
||||
}
|
||||
else if (!now && (_time.Elapsed - _inputMarkerAddedAt).TotalSeconds > MaxSecondsWithoutInput)
|
||||
{
|
||||
priority = DispatcherPriority.Input;
|
||||
}
|
||||
}
|
||||
|
||||
_nextRenderOp = Dispatcher.UIThread.InvokeAsync(_render, priority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This handles the _inputMarkerOp message. We're using
|
||||
/// _inputMarkerOp to determine if input priority dispatcher ops
|
||||
/// have been processes.
|
||||
/// </summary>
|
||||
private void InputMarkerHandler()
|
||||
{
|
||||
//set the marker to null so we know that input priority has been processed
|
||||
_inputMarkerOp = null;
|
||||
}
|
||||
|
||||
private void Render()
|
||||
{
|
||||
try
|
||||
{
|
||||
RenderCore();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_nextRenderOp = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderCore()
|
||||
{
|
||||
var now = _time.Elapsed;
|
||||
if (!_animationsAreWaitingForComposition)
|
||||
_clock.Pulse(now);
|
||||
|
||||
// Since new animations could be started during the layout and can affect layout/render
|
||||
// We are doing several iterations when it happens
|
||||
for (var c = 0; c < 10; c++)
|
||||
{
|
||||
_clock.HasNewSubscriptions = false;
|
||||
//TODO: Integrate LayoutManager's attempt limit here
|
||||
foreach (var layout in _queuedLayoutManagers.ToArray())
|
||||
layout.ExecuteQueuedLayoutPass();
|
||||
_queuedLayoutManagers.Clear();
|
||||
|
||||
if (_clock.HasNewSubscriptions)
|
||||
{
|
||||
_clock.Pulse(now);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// We are currently using compositor commit callbacks to drive animations
|
||||
// Later we should use WPF's approach that asks the animation system for the next tick time
|
||||
// and use some timer if the next animation frame is not needed to be sent to the compositor immediately
|
||||
if (_requestedCommits.Count > 0 || _clock.HasSubscriptions)
|
||||
{
|
||||
_animationsAreWaitingForComposition = true;
|
||||
CommitCompositorsWithThrottling();
|
||||
}
|
||||
}
|
||||
|
||||
// Used for unit tests
|
||||
public bool IsTopLevelActive(object key) => _topLevels.ContainsKey(key);
|
||||
|
||||
public void AddTopLevel(object key, ILayoutManager layoutManager, IRenderer renderer)
|
||||
{
|
||||
if(_topLevels.ContainsKey(key))
|
||||
return;
|
||||
var render = (CompositingRenderer)renderer;
|
||||
_topLevels.Add(key, new TopLevelInfo(render.Compositor, render, layoutManager));
|
||||
render.Start();
|
||||
ScheduleRender(true);
|
||||
}
|
||||
|
||||
public void RemoveTopLevel(object key)
|
||||
{
|
||||
if (_topLevels.TryGetValue(key, out var info))
|
||||
{
|
||||
_topLevels.Remove(key);
|
||||
info.Renderer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void QueueLayoutPass(LayoutManager layoutManager)
|
||||
{
|
||||
_queuedLayoutManagers.Add(layoutManager);
|
||||
ScheduleRender(true);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace Avalonia.Media
|
||||
/// <summary>
|
||||
/// Rotates an <see cref="Visual"/>.
|
||||
/// </summary>
|
||||
public class RotateTransform : Transform
|
||||
public sealed class RotateTransform : Transform
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Angle"/> property.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user