27 changed files with 873 additions and 87 deletions
@ -0,0 +1,45 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:pages="clr-namespace:ControlCatalog.Pages" |
|||
x:Class="ControlCatalog.Pages.CompositionPage"> |
|||
<StackPanel> |
|||
<TextBlock Classes="h1">Implicit animations</TextBlock> |
|||
<Grid ColumnDefinitions="*,10,40" Margin="0 0 40 0"> |
|||
<ItemsControl x:Name="Items"> |
|||
<ItemsControl.ItemsPanel> |
|||
<ItemsPanelTemplate> |
|||
<WrapPanel/> |
|||
</ItemsPanelTemplate> |
|||
</ItemsControl.ItemsPanel> |
|||
<ItemsControl.DataTemplates> |
|||
<DataTemplate> |
|||
<Border |
|||
pages:CompositionPage.EnableAnimations="True" |
|||
Padding="10" BorderBrush="Gray" BorderThickness="2" |
|||
Background="{Binding ColorBrush}" Width="100" Height="100" Margin="10"> |
|||
<TextBlock Text="{Binding ColorHexValue}"/> |
|||
</Border> |
|||
</DataTemplate> |
|||
</ItemsControl.DataTemplates> |
|||
</ItemsControl> |
|||
<GridSplitter Margin="2" BorderThickness="1" BorderBrush="Gray" |
|||
Background="#e0e0e0" Grid.Column="1" |
|||
ResizeDirection="Columns" ResizeBehavior="PreviousAndNext" |
|||
/> |
|||
<Border Grid.Column="2"> |
|||
<LayoutTransformControl |
|||
HorizontalAlignment="Center" |
|||
|
|||
MinWidth="30"> |
|||
<LayoutTransformControl.LayoutTransform> |
|||
<RotateTransform Angle="90"/> |
|||
</LayoutTransformControl.LayoutTransform> |
|||
|
|||
<TextBlock>Resize me</TextBlock> |
|||
</LayoutTransformControl> |
|||
</Border> |
|||
</Grid> |
|||
|
|||
|
|||
</StackPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,157 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Numerics; |
|||
using System.Threading.Tasks; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Markup.Xaml; |
|||
using Avalonia.Markup.Xaml.Templates; |
|||
using Avalonia.Media; |
|||
using Avalonia.Rendering.Composition; |
|||
using Avalonia.Rendering.Composition.Animations; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace ControlCatalog.Pages; |
|||
|
|||
public partial class CompositionPage : UserControl |
|||
{ |
|||
private ImplicitAnimationCollection _implicitAnimations; |
|||
|
|||
public CompositionPage() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
|
|||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnAttachedToVisualTree(e); |
|||
this.FindControl<ItemsControl>("Items").Items = CreateColorItems(); |
|||
} |
|||
|
|||
private List<ColorItem> CreateColorItems() |
|||
{ |
|||
var list = new List<ColorItem>(); |
|||
|
|||
list.Add(new ColorItem(Color.FromArgb(255, 255, 185, 0))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 231, 72, 86))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 0, 120, 215))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 0, 153, 188))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 122, 117, 116))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 118, 118, 118))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 255, 141, 0))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 232, 17, 35))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 0, 99, 177))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 45, 125, 154))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 93, 90, 88))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 76, 74, 72))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 247, 99, 12))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 234, 0, 94))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 142, 140, 216))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 0, 183, 195))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 104, 118, 138))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 105, 121, 126))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 202, 80, 16))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 195, 0, 82))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 107, 105, 214))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 3, 131, 135))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 81, 92, 107))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 74, 84, 89))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 218, 59, 1))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 227, 0, 140))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 135, 100, 184))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 0, 178, 148))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 86, 124, 115))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 100, 124, 100))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 239, 105, 80))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 191, 0, 119))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 116, 77, 169))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 1, 133, 116))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 72, 104, 96))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 82, 94, 84))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 209, 52, 56))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 194, 57, 179))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 177, 70, 194))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 0, 204, 106))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 73, 130, 5))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 132, 117, 69))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 255, 67, 67))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 154, 0, 137))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 136, 23, 152))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 16, 137, 62))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 16, 124, 16))); |
|||
list.Add(new ColorItem(Color.FromArgb(255, 126, 115, 95))); |
|||
|
|||
return list; |
|||
} |
|||
|
|||
public class ColorItem |
|||
{ |
|||
public Color Color { get; private set; } |
|||
|
|||
public SolidColorBrush ColorBrush |
|||
{ |
|||
get { return new SolidColorBrush(Color); } |
|||
} |
|||
|
|||
public String ColorHexValue |
|||
{ |
|||
get { return Color.ToString().Substring(3).ToUpperInvariant(); } |
|||
} |
|||
|
|||
public ColorItem(Color color) |
|||
{ |
|||
Color = color; |
|||
} |
|||
} |
|||
|
|||
private void EnsureImplicitAnimations() |
|||
{ |
|||
if (_implicitAnimations == null) |
|||
{ |
|||
var compositor = ElementCompositionPreview.GetElementVisual(this)!.Compositor; |
|||
|
|||
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation(); |
|||
offsetAnimation.Target = "Offset"; |
|||
offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue"); |
|||
offsetAnimation.Duration = TimeSpan.FromMilliseconds(400); |
|||
|
|||
var rotationAnimation = compositor.CreateScalarKeyFrameAnimation(); |
|||
rotationAnimation.Target = "RotationAngle"; |
|||
rotationAnimation.InsertKeyFrame(.5f, 0.160f); |
|||
rotationAnimation.InsertKeyFrame(1f, 0f); |
|||
rotationAnimation.Duration = TimeSpan.FromMilliseconds(400); |
|||
|
|||
var animationGroup = compositor.CreateAnimationGroup(); |
|||
animationGroup.Add(offsetAnimation); |
|||
animationGroup.Add(rotationAnimation); |
|||
|
|||
_implicitAnimations = compositor.CreateImplicitAnimationCollection(); |
|||
_implicitAnimations["Offset"] = animationGroup; |
|||
} |
|||
} |
|||
|
|||
public static void SetEnableAnimations(Border border, bool value) |
|||
{ |
|||
|
|||
var page = border.FindAncestorOfType<CompositionPage>(); |
|||
if (page == null) |
|||
{ |
|||
border.AttachedToVisualTree += delegate { SetEnableAnimations(border, true); }; |
|||
return; |
|||
} |
|||
|
|||
if (ElementCompositionPreview.GetElementVisual(page) == null) |
|||
return; |
|||
|
|||
page.EnsureImplicitAnimations(); |
|||
ElementCompositionPreview.GetElementVisual((Visual)border.GetVisualParent()).ImplicitAnimations = |
|||
page._implicitAnimations; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Rendering.Composition.Expressions; |
|||
using Avalonia.Rendering.Composition.Server; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Animations; |
|||
|
|||
internal abstract class AnimationInstanceBase : IAnimationInstance |
|||
{ |
|||
private List<(ServerObject obj, int member)>? _trackedObjects; |
|||
protected PropertySetSnapshot Parameters { get; } |
|||
public ServerObject TargetObject { get; } |
|||
protected int StoreOffset { get; private set; } |
|||
private bool _invalidated; |
|||
|
|||
public AnimationInstanceBase(ServerObject target, PropertySetSnapshot parameters) |
|||
{ |
|||
Parameters = parameters; |
|||
TargetObject = target; |
|||
} |
|||
|
|||
protected void Initialize(int storeOffset, HashSet<(string name, string member)> trackedObjects) |
|||
{ |
|||
if (trackedObjects.Count > 0) |
|||
{ |
|||
_trackedObjects = new (); |
|||
foreach (var t in trackedObjects) |
|||
{ |
|||
var obj = Parameters.GetObjectParameter(t.name); |
|||
if (obj is ServerObject tracked) |
|||
{ |
|||
var off = tracked.GetFieldOffset(t.member); |
|||
if (off == null) |
|||
#if DEBUG
|
|||
throw new InvalidCastException("Attempting to subscribe to unknown field"); |
|||
#else
|
|||
continue; |
|||
#endif
|
|||
_trackedObjects.Add((tracked, off.Value)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
StoreOffset = storeOffset; |
|||
} |
|||
|
|||
public abstract void Initialize(TimeSpan startedAt, ExpressionVariant startingValue, int storeOffset); |
|||
protected abstract ExpressionVariant EvaluateCore(TimeSpan now, ExpressionVariant currentValue); |
|||
|
|||
public ExpressionVariant Evaluate(TimeSpan now, ExpressionVariant currentValue) |
|||
{ |
|||
_invalidated = false; |
|||
return EvaluateCore(now, currentValue); |
|||
} |
|||
|
|||
public virtual void Activate() |
|||
{ |
|||
if (_trackedObjects != null) |
|||
foreach (var tracked in _trackedObjects) |
|||
tracked.obj.SubscribeToInvalidation(tracked.member, this); |
|||
} |
|||
|
|||
public virtual void Deactivate() |
|||
{ |
|||
if (_trackedObjects != null) |
|||
foreach (var tracked in _trackedObjects) |
|||
tracked.obj.UnsubscribeFromInvalidation(tracked.member, this); |
|||
} |
|||
|
|||
public void Invalidate() |
|||
{ |
|||
if (_invalidated) |
|||
return; |
|||
_invalidated = true; |
|||
TargetObject.NotifyAnimatedValueChanged(StoreOffset); |
|||
} |
|||
} |
|||
@ -1,11 +1,16 @@ |
|||
using System; |
|||
using Avalonia.Rendering.Composition.Expressions; |
|||
using Avalonia.Rendering.Composition.Server; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Animations |
|||
{ |
|||
internal interface IAnimationInstance |
|||
{ |
|||
ServerObject TargetObject { get; } |
|||
ExpressionVariant Evaluate(TimeSpan now, ExpressionVariant currentValue); |
|||
void Start(TimeSpan startedAt, ExpressionVariant startingValue); |
|||
void Initialize(TimeSpan startedAt, ExpressionVariant startingValue, int storeOffset); |
|||
void Activate(); |
|||
void Deactivate(); |
|||
void Invalidate(); |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Avalonia.Rendering.Composition; |
|||
|
|||
public static class ElementCompositionPreview |
|||
{ |
|||
public static CompositionVisual? GetElementVisual(Visual visual) => visual.CompositionVisual; |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Rendering.Composition.Server; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Expressions; |
|||
|
|||
internal class ExpressionTrackedObjects : IEnumerable<IExpressionObject> |
|||
{ |
|||
private List<IExpressionObject> _list = new(); |
|||
private HashSet<IExpressionObject> _hashSet = new(); |
|||
|
|||
public void Add(IExpressionObject obj, string member) |
|||
{ |
|||
if (_hashSet.Add(obj)) |
|||
_list.Add(obj); |
|||
} |
|||
|
|||
public void Clear() |
|||
{ |
|||
_list.Clear(); |
|||
_hashSet.Clear(); |
|||
} |
|||
|
|||
IEnumerator<IExpressionObject> IEnumerable<IExpressionObject>.GetEnumerator() |
|||
{ |
|||
return _list.GetEnumerator(); |
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return ((IEnumerable)_list).GetEnumerator(); |
|||
} |
|||
|
|||
public List<IExpressionObject>.Enumerator GetEnumerator() => _list.GetEnumerator(); |
|||
|
|||
public struct Pool |
|||
{ |
|||
private Stack<ExpressionTrackedObjects> _stack = new(); |
|||
|
|||
public Pool() |
|||
{ |
|||
} |
|||
|
|||
public ExpressionTrackedObjects Get() |
|||
{ |
|||
if (_stack.Count > 0) |
|||
return _stack.Pop(); |
|||
return new ExpressionTrackedObjects(); |
|||
} |
|||
|
|||
public void Return(ExpressionTrackedObjects obj) |
|||
{ |
|||
_stack.Clear(); |
|||
_stack.Push(obj); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace Avalonia.Utilities; |
|||
|
|||
internal class RefTrackingDictionary<TKey> : Dictionary<TKey, int> where TKey : class |
|||
{ |
|||
/// <summary>
|
|||
/// Increase reference count for a key by 1.
|
|||
/// </summary>
|
|||
/// <returns>true if key was added to the dictionary, false otherwise</returns>
|
|||
public bool AddRef(TKey key) |
|||
{ |
|||
#if NET5_0_OR_GREATER
|
|||
ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(this, key, out var _); |
|||
count++; |
|||
#else
|
|||
TryGetValue(key, out var count); |
|||
count++; |
|||
this[key] = count; |
|||
#endif
|
|||
return count == 1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decrease reference count for a key by 1.
|
|||
/// </summary>
|
|||
/// <returns>true if key was removed to the dictionary, false otherwise</returns>
|
|||
public bool ReleaseRef(TKey key) |
|||
{ |
|||
#if NET5_0_OR_GREATER
|
|||
ref var count = ref CollectionsMarshal.GetValueRefOrNullRef(this, key); |
|||
if (Unsafe.IsNullRef(ref count)) |
|||
#if DEBUG
|
|||
throw new InvalidOperationException("Attempting to release a non-referenced object"); |
|||
#else
|
|||
return false; |
|||
#endif // DEBUG
|
|||
count--; |
|||
if (count == 0) |
|||
{ |
|||
Remove(key); |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
#else
|
|||
if (!TryGetValue(key, out var count)) |
|||
#if DEBUG
|
|||
throw new InvalidOperationException("Attempting to release a non-referenced object"); |
|||
#else
|
|||
return false; |
|||
#endif // DEBUG
|
|||
count--; |
|||
if (count == 0) |
|||
{ |
|||
Remove(key); |
|||
return true; |
|||
} |
|||
|
|||
this[key] = count; |
|||
return false; |
|||
#endif
|
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Avalonia.SourceGenerator.CompositionGenerator; |
|||
|
|||
public interface ICompositionGeneratorSink |
|||
{ |
|||
void AddSource(string name, string code); |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using Microsoft.CodeAnalysis; |
|||
|
|||
namespace Avalonia.SourceGenerator.CompositionGenerator; |
|||
|
|||
class RoslynCompositionGeneratorSink : ICompositionGeneratorSink |
|||
{ |
|||
private readonly SourceProductionContext _ctx; |
|||
|
|||
public RoslynCompositionGeneratorSink(SourceProductionContext ctx) |
|||
{ |
|||
_ctx = ctx; |
|||
} |
|||
|
|||
public void AddSource(string name, string code) => _ctx.AddSource(name, code); |
|||
} |
|||
Loading…
Reference in new issue