Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions PCL.Core/UI/Animation/Animatable/EmptyAnimatable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace PCL.Core.UI.Animation.Animatable;

public sealed class EmptyAnimatable : IAnimatable
{
public static EmptyAnimatable Instance { get; } = new();

private EmptyAnimatable() { }

public object? GetValue()
{
return null;
}

public void SetValue(object value)
{
// 空
}
}
24 changes: 23 additions & 1 deletion PCL.Core/UI/Animation/Animatable/WpfAnimatable.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Windows;
using System.Windows.Media;
using PCL.Core.UI.Animation.ValueProcessor;

namespace PCL.Core.UI.Animation.Animatable;
Expand Down Expand Up @@ -28,13 +29,34 @@ public sealed class WpfAnimatable(DependencyObject owner, DependencyProperty? pr

ArgumentNullException.ThrowIfNull(actualProperty);

return Owner.GetValue(actualProperty);
var value = Owner.GetValue(actualProperty);
return value switch
{
SolidColorBrush brush => (NColor)brush,
Color color => (NColor)color,
ScaleTransform scaleTransform => (NScaleTransform)scaleTransform,
RotateTransform rotateTransform => (NRotateTransform)rotateTransform,
_ => value
};
}

public void SetValue(object value)
{
value = ValueProcessorManager.Filter(value);
ArgumentNullException.ThrowIfNull(Property);

value = value switch
{
NColor color => Property.Name switch
{
"Color" => (Color)color,
_ => (SolidColorBrush)color
},
NScaleTransform st => (ScaleTransform)st,
NRotateTransform rt => (RotateTransform)rt,
_ => value
};

Owner.SetValue(Property, value);
}
}
90 changes: 90 additions & 0 deletions PCL.Core/UI/Animation/Core/ActionAnimation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using PCL.Core.UI.Animation.Animatable;

namespace PCL.Core.UI.Animation.Core;

/// <summary>
/// 用于在动画系统中执行 Action。
/// </summary>
public class ActionAnimation : AnimationBase
{
public ActionAnimation() { }

public ActionAnimation(Action action) => Action = _ => action();

public ActionAnimation(Action<CancellationToken> action) => Action = action;

public Action<CancellationToken> Action { get; set; } = null!;
public override int CurrentFrame { get; set; }
public TimeSpan Delay { get; set; }

private CancellationTokenSource? _cts = new();
private TaskCompletionSource? _tcs;
private int _called = 0;

public override async Task<IAnimation> RunAsync(IAnimatable target)
{
ArgumentNullException.ThrowIfNull(Action);

_tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
_cts = new CancellationTokenSource();

var clone = (ActionAnimation)MemberwiseClone();
clone.Status = AnimationStatus.Running;

// 延迟
await Task.Delay(Delay);

Interlocked.Exchange(ref _called, 0);
_ = AnimationService.PushAnimationAsync(clone, target);
return await _tcs.Task.ContinueWith<IAnimation>(_ => clone);
}

public override IAnimation RunFireAndForget(IAnimatable target)
{
ArgumentNullException.ThrowIfNull(Action);

_cts = new CancellationTokenSource();

var clone = (ActionAnimation)MemberwiseClone();
clone.Status = AnimationStatus.Running;

_ = Task.Run(async () =>
{
// 延迟
await Task.Delay(Delay);

Interlocked.Exchange(ref _called, 0);
AnimationService.PushAnimationFireAndForget(clone, target);
});

return clone;
}

public override void Cancel()
{
Status = AnimationStatus.Canceled;
_cts?.Cancel();
_tcs?.TrySetCanceled();
}

public override IAnimationFrame? ComputeNextFrame(IAnimatable target)
{
if (Status is AnimationStatus.Canceled or AnimationStatus.Completed) return null;

if (Interlocked.CompareExchange(ref _called, 1, 0) == 0)
{
return new ActionAnimationFrame(() =>
{
Action(_cts!.Token);
Status = AnimationStatus.Completed;
_tcs?.TrySetResult();
});
}

return null;
}
}
10 changes: 10 additions & 0 deletions PCL.Core/UI/Animation/Core/ActionAnimationFrame.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace PCL.Core.UI.Animation.Core;

public struct ActionAnimationFrame(Action action) : IAnimationFrame
{
public Action Action { get; set; } = action;

public Action GetAction() => Action;
}
14 changes: 10 additions & 4 deletions PCL.Core/UI/Animation/Core/AnimationBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using PCL.Core.UI.Animation.Animatable;
Expand All @@ -7,13 +8,18 @@ namespace PCL.Core.UI.Animation.Core;

public abstract class AnimationBase : DependencyObject, IAnimation
{
public abstract bool IsCompleted { get; }
public string Name { get; set; } = string.Empty;
private volatile int _status = (int)AnimationStatus.NotStarted;
public AnimationStatus Status
{
get => (AnimationStatus)_status;
internal set => Interlocked.Exchange(ref _status, (int)value);
}
public abstract int CurrentFrame { get; set; }

public abstract Task RunAsync(IAnimatable target);
public abstract void RunFireAndForget(IAnimatable target);
public abstract Task<IAnimation> RunAsync(IAnimatable target);
public abstract IAnimation RunFireAndForget(IAnimatable target);
public abstract void Cancel();

public abstract IAnimationFrame? ComputeNextFrame(IAnimatable target);

public void RaiseStarted() => Started?.Invoke(this, EventArgs.Empty);
Expand Down
19 changes: 10 additions & 9 deletions PCL.Core/UI/Animation/Core/AnimationFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@

namespace PCL.Core.UI.Animation.Core;

public readonly struct AnimationFrame<T> : IAnimationFrame where T : struct
{
public IAnimatable Target { get; init; }
public T Value { get; init; }
public T StartValue { get; init; }
public T GetAbsoluteValue() => ValueProcessorManager.Add(StartValue, Value);

object IAnimationFrame.GetAbsoluteValue() => GetAbsoluteValue();
}
// public readonly struct AnimationFrame<T>(IAnimatable target, T value, T startValue) : IAnimationFrame
// {
// public IAnimatable Target { get; init; } = target;
// public T Value { get; init; } = value;
// public T StartValue { get; init; } = startValue;
// public T GetAbsoluteValue() => ValueProcessorManager.Add(StartValue, Value);
// object IAnimationFrame.StartValue => StartValue!;
// object IAnimationFrame.Value => Value!;
// object IAnimationFrame.GetAbsoluteValue() => GetAbsoluteValue()!;
// }
91 changes: 72 additions & 19 deletions PCL.Core/UI/Animation/Core/AnimationGroup.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using System.Linq;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
Expand All @@ -14,34 +15,55 @@ namespace PCL.Core.UI.Animation.Core;
[ContentProperty(nameof(Children))]
public abstract class AnimationGroup : AnimationBase
{
public static readonly DependencyProperty ChildrenProperty = DependencyProperty.Register(
nameof(Children), typeof(ObservableCollection<IAnimation>), typeof(SequentialAnimationGroup));
public static readonly DependencyProperty ChildrenProperty =
DependencyProperty.Register(
nameof(Children),
typeof(ObservableCollection<IAnimation>),
typeof(AnimationGroup),
new PropertyMetadata(null)); // 移除 OnChildrenChanged 回调,避免运行时冲突

public ObservableCollection<IAnimation> Children
{
get => (ObservableCollection<IAnimation>)GetValue(ChildrenProperty);
set => SetValue(ChildrenProperty, value);
}


/// <summary>
/// 存储当前正在运行的动画实例。
/// </summary>
protected List<IAnimation> ChildrenCore { get; } = [];

protected AnimationGroup()
{
// 确保集合初始化,但不进行自动同步
SetCurrentValue(ChildrenProperty, new ObservableCollection<IAnimation>());
}

public override bool IsCompleted => Children.All(child => child.IsCompleted);

public override int CurrentFrame { get; set; }

public override void Cancel()
{
// 重置值
Status = AnimationStatus.Canceled;

CurrentFrame = 0;

// 取消所有子动画
foreach (var child in Children)
lock (ChildrenCore)
{
child.Cancel();
foreach (var child in ChildrenCore)
{
child.Cancel();
}
// 清理运行实例,断开引用
ChildrenCore.Clear();
}
}

public void CancelAndClear()
{
Cancel();
// 如果需要清空定义的 Children 集合,应在 UI 线程操作
AnimationService.UIAccessProvider.Invoke(() => Children.Clear());
}

public override IAnimationFrame? ComputeNextFrame(IAnimatable target)
{
Expand All @@ -53,29 +75,60 @@ protected static IAnimatable ResolveTarget(IAnimation animation, IAnimatable def
if (animation is not DependencyObject aniDependencyObject)
return defaultTarget;

DependencyObject? targetObject;
DependencyProperty? targetProperty;
DependencyObject? targetObject = null;
DependencyProperty? targetProperty = null;

// Target
// Target check
if (WpfUtils.IsDependencyPropertySet(aniDependencyObject, AnimationExtensions.TargetProperty))
{
targetObject = (DependencyObject)aniDependencyObject.GetValue(AnimationExtensions.TargetProperty);
}
else
else if (defaultTarget is WpfAnimatable animatable)
{
targetObject = ((WpfAnimatable)defaultTarget).Owner; // 默认用父级
targetObject = animatable.Owner;
}

// TargetProperty
// TargetProperty check
if (WpfUtils.IsDependencyPropertySet(aniDependencyObject, AnimationExtensions.TargetPropertyProperty))
{
targetProperty = (DependencyProperty)aniDependencyObject.GetValue(AnimationExtensions.TargetPropertyProperty);
}
else
else if (defaultTarget is WpfAnimatable animatable)
{
targetProperty = ((WpfAnimatable)defaultTarget).Property; // 默认用父级
targetProperty = animatable.Property;
}

// 如果都未解析出特定值,直接返回默认目标
if (targetObject == null || targetProperty == null)
return defaultTarget;

return new WpfAnimatable(targetObject, targetProperty);
}

protected static Task CreateChildAwaiter(IAnimation animation)
{
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

if (animation.Status == AnimationStatus.Completed)
{
tcs.TrySetResult();
return tcs.Task;
}

EventHandler? handler = null;
handler = (_, _) =>
{
animation.Completed -= handler;
tcs.TrySetResult();
};

animation.Completed += handler;

if (animation.Status != AnimationStatus.Completed) return tcs.Task;

animation.Completed -= handler;
tcs.TrySetResult();

return tcs.Task;
}
}
Loading
Loading