diff --git a/PCL.Core/UI/Animation/Animatable/EmptyAnimatable.cs b/PCL.Core/UI/Animation/Animatable/EmptyAnimatable.cs
new file mode 100644
index 000000000..29c6115b8
--- /dev/null
+++ b/PCL.Core/UI/Animation/Animatable/EmptyAnimatable.cs
@@ -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)
+ {
+ // 空
+ }
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Animatable/WpfAnimatable.cs b/PCL.Core/UI/Animation/Animatable/WpfAnimatable.cs
index 9cd9e2e21..653b0f590 100644
--- a/PCL.Core/UI/Animation/Animatable/WpfAnimatable.cs
+++ b/PCL.Core/UI/Animation/Animatable/WpfAnimatable.cs
@@ -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;
@@ -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);
}
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Core/ActionAnimation.cs b/PCL.Core/UI/Animation/Core/ActionAnimation.cs
new file mode 100644
index 000000000..6433731bb
--- /dev/null
+++ b/PCL.Core/UI/Animation/Core/ActionAnimation.cs
@@ -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;
+
+///
+/// 用于在动画系统中执行 Action。
+///
+public class ActionAnimation : AnimationBase
+{
+ public ActionAnimation() { }
+
+ public ActionAnimation(Action action) => Action = _ => action();
+
+ public ActionAnimation(Action action) => Action = action;
+
+ public Action 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 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(_ => 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;
+ }
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Core/ActionAnimationFrame.cs b/PCL.Core/UI/Animation/Core/ActionAnimationFrame.cs
new file mode 100644
index 000000000..ceb29a36e
--- /dev/null
+++ b/PCL.Core/UI/Animation/Core/ActionAnimationFrame.cs
@@ -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;
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Core/AnimationBase.cs b/PCL.Core/UI/Animation/Core/AnimationBase.cs
index 57d55de15..cd0c6c6ed 100644
--- a/PCL.Core/UI/Animation/Core/AnimationBase.cs
+++ b/PCL.Core/UI/Animation/Core/AnimationBase.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using PCL.Core.UI.Animation.Animatable;
@@ -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 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);
diff --git a/PCL.Core/UI/Animation/Core/AnimationFrame.cs b/PCL.Core/UI/Animation/Core/AnimationFrame.cs
index 255a2c895..52bc275b7 100644
--- a/PCL.Core/UI/Animation/Core/AnimationFrame.cs
+++ b/PCL.Core/UI/Animation/Core/AnimationFrame.cs
@@ -5,12 +5,13 @@
namespace PCL.Core.UI.Animation.Core;
-public readonly struct AnimationFrame : 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();
-}
\ No newline at end of file
+// public readonly struct AnimationFrame(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()!;
+// }
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Core/AnimationGroup.cs b/PCL.Core/UI/Animation/Core/AnimationGroup.cs
index 172d11671..f2227462d 100644
--- a/PCL.Core/UI/Animation/Core/AnimationGroup.cs
+++ b/PCL.Core/UI/Animation/Core/AnimationGroup.cs
@@ -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;
@@ -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), typeof(SequentialAnimationGroup));
+ public static readonly DependencyProperty ChildrenProperty =
+ DependencyProperty.Register(
+ nameof(Children),
+ typeof(ObservableCollection),
+ typeof(AnimationGroup),
+ new PropertyMetadata(null)); // 移除 OnChildrenChanged 回调,避免运行时冲突
public ObservableCollection Children
{
get => (ObservableCollection)GetValue(ChildrenProperty);
set => SetValue(ChildrenProperty, value);
}
-
+
+ ///
+ /// 存储当前正在运行的动画实例。
+ ///
+ protected List ChildrenCore { get; } = [];
+
protected AnimationGroup()
{
+ // 确保集合初始化,但不进行自动同步
SetCurrentValue(ChildrenProperty, new ObservableCollection());
}
-
- 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)
{
@@ -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;
+ }
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Core/AnimationService.cs b/PCL.Core/UI/Animation/Core/AnimationService.cs
index 2340ca3af..10c366554 100644
--- a/PCL.Core/UI/Animation/Core/AnimationService.cs
+++ b/PCL.Core/UI/Animation/Core/AnimationService.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Channels;
@@ -17,12 +18,14 @@ namespace PCL.Core.UI.Animation.Core;
public sealed class AnimationService : GeneralService
{
#region Lifecycle
-
+
private static LifecycleContext? _context;
private static LifecycleContext Context => _context!;
-
- private AnimationService() : base("animation", "动画计算以及赋值") { _context = ServiceContext; }
-
+
+ private AnimationService() : base("animation", "动画")
+ {
+ _context = ServiceContext;
+ }
public override void Start()
{
@@ -35,34 +38,40 @@ public override void Stop()
}
#endregion
-
+
private static void _RegisterValueProcessors()
{
// 在这里注册所有的 ValueProcessor
ValueProcessorManager.Register(new DoubleValueProcessor());
ValueProcessorManager.Register(new MatrixValueProcessor());
ValueProcessorManager.Register(new NColorValueProcessor());
+ ValueProcessorManager.Register(new NRotateTransformValueProcessor());
+ ValueProcessorManager.Register(new NScaleTransformValueProcessor());
ValueProcessorManager.Register(new PointValueProcessor());
ValueProcessorManager.Register(new ThicknessValueProcessor());
}
-
- private static Channel<(IAnimation, IAnimatable)> _animationChannel = null!;
- private static Channel _frameChannel = null!;
+
+ private static Channel<(IAnimation Animation, IAnimatable Target)> _animationChannel = null!;
+ // private static Channel _frameChannel = null!;
+ private static Channel<(IAnimationFrame Frame, IAnimation Source)> _frameChannel = null!;
+ // private static ConcurrentDictionary _frameDictionary = null!;
+ private static ConcurrentDictionary _namedAnimations = new();
private static IClock _clock = null!;
private static AsyncCountResetEvent _resetEvent = null!;
private static int _taskCount;
private static CancellationTokenSource _cts = null!;
public static int Fps { get; set; } = 60;
- public static double Scale { get; set; } = 1.0d;
+ public static double Scale { get; set; } = 0.1d;
public static IUIAccessProvider UIAccessProvider { get; private set; } = null!;
private static void _Initialize()
{
- // 初始化 Channel
+ // 初始化 Channel 与 Dictionary
_animationChannel = Channel.CreateUnbounded<(IAnimation, IAnimatable)>();
- _frameChannel = Channel.CreateUnbounded();
+ // _frameChannel = Channel.CreateUnbounded();
+ _frameChannel = Channel.CreateUnbounded<(IAnimationFrame, IAnimation)>();
// 根据核心数量来确定动画计算 Task 数量
_taskCount = Environment.ProcessorCount;
@@ -80,13 +89,17 @@ private static void _Initialize()
_ = UIAccessProvider.InvokeAsync(async () =>
{
if (_cts.IsCancellationRequested) return;
-
- // 取出所有动画帧并赋值
while (await _frameChannel.Reader.WaitToReadAsync())
{
- while (_frameChannel.Reader.TryRead(out var frame))
+ // 读取数据
+ while (_frameChannel.Reader.TryRead(out var item))
{
- frame.Target.SetValue(frame.GetAbsoluteValue());
+ // 如果动画源已被标记取消,直接丢弃该帧,不进行处理
+ if (item.Source.Status == AnimationStatus.Canceled)
+ continue;
+
+ // 正常处理
+ item.Frame.GetAction()();
}
await Task.Yield();
@@ -117,6 +130,9 @@ private static void _Uninitialize()
// 将 ResetEvent 释放
_resetEvent.Dispose();
+
+ // 清理 Dictionary
+ _namedAnimations.Clear();
}
private static void ClockOnTick(object? sender, long e)
@@ -128,7 +144,7 @@ private static void ClockOnTick(object? sender, long e)
private static async Task _AnimationComputeTaskAsync()
{
// 本地动画列表,确保没有一直无法计算的动画
- var animationList = new List<(IAnimation, IAnimatable)>(8);
+ var animationList = new List<(IAnimation Animation, IAnimatable Target)>(8);
// 持续监听 Channel 中的动画
while (!_cts.IsCancellationRequested)
@@ -152,33 +168,59 @@ private static async Task _AnimationComputeTaskAsync()
// TODO: 支持缓存动画计算结果 (由 AnimationData 支持)
// 从列表中获取动画
- var animation = animationList[i];
+ var animationEntry = animationList[i];
- // 如果动画已经完成,则从列表中移除
- if (animation.Item1.IsCompleted)
+ // 如果动画已经完成或被取消,则从列表中移除
+ if (animationEntry.Animation.Status is AnimationStatus.Canceled or AnimationStatus.Completed)
{
- animation.Item1.RaiseCompleted();
+ animationEntry.Animation.RaiseCompleted();
+
+ if (!string.IsNullOrEmpty(animationEntry.Animation.Name))
+ {
+ // 使用显式接口
+ ((ICollection>)_namedAnimations)
+ .Remove(new KeyValuePair(animationEntry.Animation.Name, animationEntry.Animation));
+ }
+
animationList.RemoveAt(i);
continue;
}
-
+
// 计算动画的下一帧
- var frame = animation.Item1.ComputeNextFrame(animation.Item2);
+ var frame = animationEntry.Animation.ComputeNextFrame(animationEntry.Target);
// 如果没有计算帧(当动画为 SequentialAnimationGroup 或 ParallelAnimationGroup 这种动画集合时),跳过
if (frame is null) continue;
// 将动画帧写入 Channel
- _frameChannel.Writer.TryWrite(frame);
+ _frameChannel.Writer.TryWrite((frame, animationEntry.Animation));
// 增加当前帧计数
- animation.Item1.CurrentFrame++;
+ animationEntry.Animation.CurrentFrame++;
}
-
+
// 等待 Tick 事件的通知
await _resetEvent.WaitAsync();
}
}
+ private static void HandleNamedAnimationConflict(IAnimation animation)
+ {
+ if (string.IsNullOrEmpty(animation.Name)) return;
+
+ _namedAnimations.AddOrUpdate(
+ animation.Name,
+ animation, // 如果不存在,直接添加
+ (_, existingAnimation) =>
+ {
+ // 如果已存在同名动画,取消旧动画
+ existingAnimation.Cancel();
+ // 替换为新动画
+ return animation;
+ });
+ }
+
internal static Task PushAnimationAsync(IAnimation animation, IAnimatable target)
{
+ HandleNamedAnimationConflict(animation);
+
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
animation.Completed += (_, _) => tcs.SetResult();
@@ -189,6 +231,16 @@ internal static Task PushAnimationAsync(IAnimation animation, IAnimatable target
internal static void PushAnimationFireAndForget(IAnimation animation, IAnimatable target)
{
+ HandleNamedAnimationConflict(animation);
+
_animationChannel.Writer.TryWrite((animation, target));
}
+
+ public static void CancelAnimationByName(string name)
+ {
+ if (_namedAnimations.TryRemove(name, out var animation))
+ {
+ animation.Cancel();
+ }
+ }
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Core/AnimationStatus.cs b/PCL.Core/UI/Animation/Core/AnimationStatus.cs
new file mode 100644
index 000000000..ac2bae8ec
--- /dev/null
+++ b/PCL.Core/UI/Animation/Core/AnimationStatus.cs
@@ -0,0 +1,21 @@
+namespace PCL.Core.UI.Animation.Core;
+
+public enum AnimationStatus
+{
+ ///
+ /// 动画未开始。
+ ///
+ NotStarted,
+ ///
+ /// 动画正在运行。
+ ///
+ Running,
+ ///
+ /// 动画已完成。
+ ///
+ Completed,
+ ///
+ /// 动画已取消。
+ ///
+ Canceled,
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Core/FromToAnimationBase.cs b/PCL.Core/UI/Animation/Core/FromToAnimationBase.cs
index 88eda2f31..dd6c86b8f 100644
--- a/PCL.Core/UI/Animation/Core/FromToAnimationBase.cs
+++ b/PCL.Core/UI/Animation/Core/FromToAnimationBase.cs
@@ -1,5 +1,6 @@
using System;
using System.Numerics;
+using System.Threading;
using System.Threading.Tasks;
using PCL.Core.UI.Animation.Animatable;
using PCL.Core.UI.Animation.Easings;
@@ -7,12 +8,14 @@
namespace PCL.Core.UI.Animation.Core;
-public class FromToAnimationBase : AnimationBase, IFromToAnimation where T : struct
+public class FromToAnimationBase : AnimationBase, IFromToAnimation
{
public IEasing Easing { get; set; } = new LinearEasing();
- public T? From { get; set; }
- public T To { get; set; }
- public AnimationValueType ValueType { get; set; } = AnimationValueType.Relative;
+
+ public T From { get; set; } = default!;
+
+ public T? To { get; set; }
+ public AnimationValueType ValueType { get; set; } = AnimationValueType.Absolute;
public TimeSpan Duration { get; set; }
public TimeSpan Delay { get; set; }
public T? CurrentValue { get; internal set; }
@@ -28,26 +31,35 @@ public class FromToAnimationBase : AnimationBase, IFromToAnimation where T :
}
public int TotalFrames { get; private set; }
+
+ private int _currentFrame;
- public override bool IsCompleted => CurrentFrame >= TotalFrames;
- public override int CurrentFrame { get; set; }
+ public override int CurrentFrame
+ {
+ get => Interlocked.CompareExchange(ref _currentFrame, 0, 0);
+ set => Interlocked.Exchange(ref _currentFrame, value);
+ }
- private T _startValue;
+ private T? _startValue;
- public override async Task RunAsync(IAnimatable target)
+ public override async Task RunAsync(IAnimatable target)
{
_RunCore(target);
+ var clone = (FromToAnimationBase)MemberwiseClone();
// 延迟
await Task.Delay(Delay);
// 将该动画推送到动画服务
- await AnimationService.PushAnimationAsync((FromToAnimationBase)MemberwiseClone(), target);
+ await AnimationService.PushAnimationAsync(clone, target);
+
+ return clone;
}
- public override void RunFireAndForget(IAnimatable target)
+ public override IAnimation RunFireAndForget(IAnimatable target)
{
_RunCore(target);
+ var clone = (FromToAnimationBase)MemberwiseClone();
_ = Task.Run(async () =>
{
@@ -55,14 +67,16 @@ public override void RunFireAndForget(IAnimatable target)
await Task.Delay(Delay);
// 将该动画推送到动画服务
- AnimationService.PushAnimationFireAndForget((FromToAnimationBase)MemberwiseClone(), target);
+ AnimationService.PushAnimationFireAndForget(clone, target);
});
+
+ return clone;
}
private void _RunCore(IAnimatable target)
{
// 重置当前帧
- CurrentFrame = 0;
+ _currentFrame = 0;
// 空值检查
ArgumentNullException.ThrowIfNull(To);
@@ -71,31 +85,45 @@ private void _RunCore(IAnimatable target)
_startValue = (T)target.GetValue()!;
// 如果 From 为空,则根据动画值类型设置初始值
- From ??= ValueType == AnimationValueType.Relative ? default : _startValue;
+ if (!ValueProcessorManager.Equal(_startValue, From))
+ {
+ From = ValueType == AnimationValueType.Relative ? ValueProcessorManager.DefaultValue() : _startValue;
+ }
// 计算总帧数
TotalFrames = (int)Math.Round(Duration.TotalSeconds * AnimationService.Fps / AnimationService.Scale);
// 进行初始赋值
- target.SetValue(
- ValueType == AnimationValueType.Relative ? ValueProcessorManager.Add(From!.Value, _startValue) : From!);
+ // target.SetValue(
+ // ValueType == AnimationValueType.Relative ? ValueProcessorManager.Add(From, _startValue)! : From!);
+
+ // 设置状态
+ Status = AnimationStatus.Running;
}
public override void Cancel()
{
// 确保正常结束
- CurrentFrame = TotalFrames + 1;
+ Interlocked.Exchange(ref _currentFrame, TotalFrames);
+
+ Status = AnimationStatus.Canceled;
}
public override IAnimationFrame? ComputeNextFrame(IAnimatable target)
{
- return new AnimationFrame
+ if (_currentFrame >= TotalFrames)
+ {
+ Status = AnimationStatus.Completed;
+ return null;
+ }
+
+ return new FromToAnimationFrame
{
Target = target,
Value = ValueType == AnimationValueType.Relative
- ? CurrentValue!.Value
- : ValueProcessorManager.Subtract(CurrentValue!.Value, From!.Value),
- StartValue = ValueType == AnimationValueType.Relative ? _startValue : From!.Value
+ ? CurrentValue!
+ : ValueProcessorManager.Subtract(CurrentValue!, From!),
+ StartValue = ValueType == AnimationValueType.Relative ? _startValue! : From!
};
}
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Core/FromToAnimationFrame.cs b/PCL.Core/UI/Animation/Core/FromToAnimationFrame.cs
new file mode 100644
index 000000000..7068c82d1
--- /dev/null
+++ b/PCL.Core/UI/Animation/Core/FromToAnimationFrame.cs
@@ -0,0 +1,18 @@
+using System;
+using PCL.Core.UI.Animation.Animatable;
+using PCL.Core.UI.Animation.ValueProcessor;
+
+namespace PCL.Core.UI.Animation.Core;
+
+public readonly struct FromToAnimationFrame(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 Action GetAction()
+ {
+ var target = Target;
+ var absolute = ValueProcessorManager.Add(StartValue, Value);
+ return () => target.SetValue(absolute!);
+ }
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Core/IAnimation.cs b/PCL.Core/UI/Animation/Core/IAnimation.cs
index 1ce98c8a4..aee661a00 100644
--- a/PCL.Core/UI/Animation/Core/IAnimation.cs
+++ b/PCL.Core/UI/Animation/Core/IAnimation.cs
@@ -7,9 +7,13 @@ namespace PCL.Core.UI.Animation.Core;
public interface IAnimation
{
///
- /// 动画是否已完成。
+ /// 动画名。
///
- bool IsCompleted { get; }
+ string Name { get; set; }
+ ///
+ /// 当前动画状态。
+ ///
+ AnimationStatus Status { get; }
///
/// 当前动画帧索引。
///
@@ -19,18 +23,18 @@ public interface IAnimation
///
/// 被动画的对象。
/// 返回表示异步动画操作的任务。
- Task RunAsync(IAnimatable target);
+ Task RunAsync(IAnimatable target);
///
/// 一发即忘方式运行动画。
///
/// 被动画的对象。
- void RunFireAndForget(IAnimatable target);
+ IAnimation RunFireAndForget(IAnimatable target);
///
/// 取消动画。
///
void Cancel();
///
- /// 计算下一帧。。
+ /// 计算下一帧。
///
/// 被动画的对象。
/// 动画帧。
diff --git a/PCL.Core/UI/Animation/Core/IAnimationFrame.cs b/PCL.Core/UI/Animation/Core/IAnimationFrame.cs
index 7a93143db..ca044dda7 100644
--- a/PCL.Core/UI/Animation/Core/IAnimationFrame.cs
+++ b/PCL.Core/UI/Animation/Core/IAnimationFrame.cs
@@ -1,10 +1,10 @@
-using System.Windows;
+using System;
+using System.Windows;
using PCL.Core.UI.Animation.Animatable;
namespace PCL.Core.UI.Animation.Core;
public interface IAnimationFrame
{
- IAnimatable Target { get; }
- object GetAbsoluteValue();
+ Action GetAction();
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Core/ParallelAnimationGroup.cs b/PCL.Core/UI/Animation/Core/ParallelAnimationGroup.cs
index fb4a6d3fb..99f5454ab 100644
--- a/PCL.Core/UI/Animation/Core/ParallelAnimationGroup.cs
+++ b/PCL.Core/UI/Animation/Core/ParallelAnimationGroup.cs
@@ -1,4 +1,6 @@
-using System.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using PCL.Core.UI.Animation.Animatable;
@@ -9,24 +11,62 @@ namespace PCL.Core.UI.Animation.Core;
///
public sealed class ParallelAnimationGroup : AnimationGroup
{
- public override Task RunAsync(IAnimatable target)
+ private TaskCompletionSource? _cancelTcs;
+
+ public override async Task RunAsync(IAnimatable target)
{
- // 取出所有子动画的任务,并等待它们全部完成
- var tasks = Children.Select(child =>
- {
- var childTarget = ResolveTarget(child, target);
- return child.RunAsync(childTarget);
- });
+ Status = AnimationStatus.Running;
+ AnimationService.PushAnimationFireAndForget(this, target);
- return Task.WhenAll(tasks);
- }
+ _cancelTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- public override void RunFireAndForget(IAnimatable target)
- {
- foreach (var child in Children)
+ var childrenSnapshot = Children.ToList();
+ var childWaitTasks = new List();
+
+ lock (ChildrenCore)
{
- var childTarget = ResolveTarget(child, target);
- child.RunFireAndForget(childTarget);
+ if (Status == AnimationStatus.Canceled) return this;
+
+ // 立即启动所有子动画,并收集它们的完成 Task
+ foreach (var child in childrenSnapshot)
+ {
+ var childTarget = ResolveTarget(child, target);
+
+ // 立即拿到实例
+ var instance = child.RunFireAndForget(childTarget);
+ ChildrenCore.Add(instance);
+
+ // 收集 Task
+ childWaitTasks.Add(CreateChildAwaiter(instance));
+ }
}
+
+ try
+ {
+ // 等待到所有子动画完成或组被取消
+ await Task.WhenAny(Task.WhenAll(childWaitTasks), _cancelTcs.Task);
+ }
+ finally
+ {
+ if (Status != AnimationStatus.Canceled)
+ {
+ Status = AnimationStatus.Completed;
+ }
+ _cancelTcs = null;
+ }
+
+ return this;
+ }
+
+ public override void Cancel()
+ {
+ base.Cancel();
+ _cancelTcs?.TrySetResult();
+ }
+
+ public override IAnimation RunFireAndForget(IAnimatable target)
+ {
+ _ = RunAsync(target);
+ return this;
}
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Core/RunAnimation.cs b/PCL.Core/UI/Animation/Core/RunAnimation.cs
index 771d17229..a3e7043b5 100644
--- a/PCL.Core/UI/Animation/Core/RunAnimation.cs
+++ b/PCL.Core/UI/Animation/Core/RunAnimation.cs
@@ -1,7 +1,5 @@
using System;
-using System.ComponentModel;
using System.Windows;
-using System.Windows.Data;
using System.Windows.Markup;
using Microsoft.Xaml.Behaviors;
using PCL.Core.UI.Animation.Animatable;
@@ -10,12 +8,12 @@
namespace PCL.Core.UI.Animation.Core;
[ContentProperty(nameof(Animation))]
-public class RunAnimation : TriggerAction
+public class RunAnimationAction : TriggerAction
{
public static readonly DependencyProperty AnimationProperty = DependencyProperty.Register(
nameof(Animation),
typeof(IAnimation),
- typeof(RunAnimation),
+ typeof(RunAnimationAction),
new PropertyMetadata(default(IAnimation)));
public IAnimation Animation
@@ -27,7 +25,7 @@ public IAnimation Animation
public static readonly DependencyProperty TargetPropertyProperty = DependencyProperty.Register(
nameof(TargetProperty),
typeof(DependencyProperty),
- typeof(RunAnimation),
+ typeof(RunAnimationAction),
new PropertyMetadata(default(DependencyProperty)));
public DependencyProperty TargetProperty
diff --git a/PCL.Core/UI/Animation/Core/SequentialAnimationGroup.cs b/PCL.Core/UI/Animation/Core/SequentialAnimationGroup.cs
index 6a3792015..cd440b24e 100644
--- a/PCL.Core/UI/Animation/Core/SequentialAnimationGroup.cs
+++ b/PCL.Core/UI/Animation/Core/SequentialAnimationGroup.cs
@@ -1,4 +1,6 @@
-using System.Threading.Tasks;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
using PCL.Core.UI.Animation.Animatable;
namespace PCL.Core.UI.Animation.Core;
@@ -8,18 +10,68 @@ namespace PCL.Core.UI.Animation.Core;
///
public sealed class SequentialAnimationGroup : AnimationGroup
{
- public override async Task RunAsync(IAnimatable target)
+ private TaskCompletionSource? _cancelTcs;
+
+ public override async Task RunAsync(IAnimatable target)
{
- foreach (var child in Children)
+ Status = AnimationStatus.Running;
+ AnimationService.PushAnimationFireAndForget(this, target);
+
+ _cancelTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ var childrenSnapshot = Children.ToList();
+
+ lock (ChildrenCore)
{
- var childTarget = ResolveTarget(child, target);
- await child.RunAsync(childTarget);
+ if (Status == AnimationStatus.Canceled) return this;
}
+
+ try
+ {
+ foreach (var child in childrenSnapshot)
+ {
+ // 检查取消信号
+ if (Status == AnimationStatus.Canceled) break;
+
+ var childTarget = ResolveTarget(child, target);
+ Task childWaiter;
+
+ lock (ChildrenCore)
+ {
+ var runChild = child.RunFireAndForget(childTarget);
+ ChildrenCore.Add(runChild);
+
+ childWaiter = CreateChildAwaiter(runChild);
+ }
+
+ // 等待到当前子动画完成或组被取消
+ await Task.WhenAny(childWaiter, _cancelTcs.Task);
+
+ // 如果是取消触发的醒来,直接跳出循环
+ if (Status == AnimationStatus.Canceled) break;
+ }
+ }
+ finally
+ {
+ if (Status != AnimationStatus.Canceled)
+ {
+ Status = AnimationStatus.Completed;
+ }
+ _cancelTcs = null;
+ }
+
+ return this;
+ }
+
+ public override void Cancel()
+ {
+ base.Cancel();
+ _cancelTcs?.TrySetResult();
}
- public override void RunFireAndForget(IAnimatable target)
+ public override IAnimation RunFireAndForget(IAnimatable target)
{
- // 由于顺序执行的特性,这里直接调用异步方法并且不等待其完成,无法享受 FireAndForget 的好处。
_ = RunAsync(target);
+ return this;
}
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/DoubleFromToAnimation.cs b/PCL.Core/UI/Animation/DoubleFromToAnimation.cs
index b2331996e..c21cf34c2 100644
--- a/PCL.Core/UI/Animation/DoubleFromToAnimation.cs
+++ b/PCL.Core/UI/Animation/DoubleFromToAnimation.cs
@@ -1,4 +1,5 @@
-using PCL.Core.UI.Animation.Animatable;
+using System.Diagnostics;
+using PCL.Core.UI.Animation.Animatable;
using PCL.Core.UI.Animation.Core;
namespace PCL.Core.UI.Animation;
diff --git a/PCL.Core/UI/Animation/Easings/BackEaseIn.cs b/PCL.Core/UI/Animation/Easings/BackEaseIn.cs
index 928367b99..64def1a66 100644
--- a/PCL.Core/UI/Animation/Easings/BackEaseIn.cs
+++ b/PCL.Core/UI/Animation/Easings/BackEaseIn.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class BackEaseIn : Easing
{
+ public static BackEaseIn Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return progress * (progress * progress - Math.Sin(progress * Math.PI));
diff --git a/PCL.Core/UI/Animation/Easings/BackEaseInOut.cs b/PCL.Core/UI/Animation/Easings/BackEaseInOut.cs
index 07a79eced..e9b373ec8 100644
--- a/PCL.Core/UI/Animation/Easings/BackEaseInOut.cs
+++ b/PCL.Core/UI/Animation/Easings/BackEaseInOut.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class BackEaseInOut : Easing
{
+ public static BackEaseInOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
if (progress < 0.5)
diff --git a/PCL.Core/UI/Animation/Easings/BackEaseOut.cs b/PCL.Core/UI/Animation/Easings/BackEaseOut.cs
index 60578e15c..d59a978e6 100644
--- a/PCL.Core/UI/Animation/Easings/BackEaseOut.cs
+++ b/PCL.Core/UI/Animation/Easings/BackEaseOut.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class BackEaseOut : Easing
{
+ public static BackEaseOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
var p = 1 - progress;
diff --git a/PCL.Core/UI/Animation/Easings/BackEaseWithPowerIn.cs b/PCL.Core/UI/Animation/Easings/BackEaseWithPowerIn.cs
new file mode 100644
index 000000000..9974cd6f1
--- /dev/null
+++ b/PCL.Core/UI/Animation/Easings/BackEaseWithPowerIn.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace PCL.Core.UI.Animation.Easings;
+
+public class BackEaseWithPowerIn(EasePower power = EasePower.Middle) : Easing
+{
+ private readonly double _p = 3.0 - (double)power * 0.5;
+
+ protected override double EaseCore(double progress)
+ {
+ var t = Math.Clamp(progress, 0.0, 1.0);
+ return Math.Pow(t, _p) * Math.Cos(1.5 * Math.PI * (1.0 - t));
+ }
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Easings/BackEaseWithPowerInOut.cs b/PCL.Core/UI/Animation/Easings/BackEaseWithPowerInOut.cs
new file mode 100644
index 000000000..ac337ba9c
--- /dev/null
+++ b/PCL.Core/UI/Animation/Easings/BackEaseWithPowerInOut.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace PCL.Core.UI.Animation.Easings;
+
+public class BackEaseWithPowerInOut(EasePower power = EasePower.Middle) : Easing
+{
+ private readonly double _p = 3.0 - (double)power * 0.5;
+
+ protected override double EaseCore(double progress)
+ {
+ var t = Math.Clamp(progress, 0.0, 1.0);
+
+ if (t < 0.5)
+ {
+ var f = 2.0 * t;
+ return 0.5 * (Math.Pow(f, _p) * Math.Cos(1.5 * Math.PI * (1.0 - f)));
+ }
+ else
+ {
+ var f = 2.0 * (t - 0.5);
+ var inv = 1.0 - f;
+ return 0.5 * (1.0 - Math.Pow(inv, _p) * Math.Cos(1.5 * Math.PI * f)) + 0.5;
+ }
+ }
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Easings/BackEaseWithPowerOut.cs b/PCL.Core/UI/Animation/Easings/BackEaseWithPowerOut.cs
new file mode 100644
index 000000000..6343f6372
--- /dev/null
+++ b/PCL.Core/UI/Animation/Easings/BackEaseWithPowerOut.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace PCL.Core.UI.Animation.Easings;
+
+public class BackEaseWithPowerOut(EasePower power = EasePower.Middle) : Easing
+{
+ private readonly double _p = 3.0 - (double)power * 0.5;
+
+ protected override double EaseCore(double progress)
+ {
+ var t = Math.Clamp(progress, 0.0, 1.0);
+ var inv = 1.0 - t;
+ return 1.0 - Math.Pow(inv, _p) * Math.Cos(1.5 * Math.PI * t);
+ }
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Easings/BounceEaseIn.cs b/PCL.Core/UI/Animation/Easings/BounceEaseIn.cs
index e5491fdca..1558c6c7a 100644
--- a/PCL.Core/UI/Animation/Easings/BounceEaseIn.cs
+++ b/PCL.Core/UI/Animation/Easings/BounceEaseIn.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class BounceEaseIn : Easing
{
+ public static BounceEaseIn Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return 1 - EaseUtils.Bounce(1 - progress);
diff --git a/PCL.Core/UI/Animation/Easings/BounceEaseInOut.cs b/PCL.Core/UI/Animation/Easings/BounceEaseInOut.cs
index 261fa2d87..8a40dac82 100644
--- a/PCL.Core/UI/Animation/Easings/BounceEaseInOut.cs
+++ b/PCL.Core/UI/Animation/Easings/BounceEaseInOut.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class BounceEaseInOut : Easing
{
+ public static BounceEaseInOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
if (progress < 0.5)
diff --git a/PCL.Core/UI/Animation/Easings/BounceEaseOut.cs b/PCL.Core/UI/Animation/Easings/BounceEaseOut.cs
index 412351e6b..e1496db8d 100644
--- a/PCL.Core/UI/Animation/Easings/BounceEaseOut.cs
+++ b/PCL.Core/UI/Animation/Easings/BounceEaseOut.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class BounceEaseOut : Easing
{
+ public static BounceEaseOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return EaseUtils.Bounce(progress);
diff --git a/PCL.Core/UI/Animation/Easings/CircularEaseIn.cs b/PCL.Core/UI/Animation/Easings/CircularEaseIn.cs
index 8a29c2227..2728ed327 100644
--- a/PCL.Core/UI/Animation/Easings/CircularEaseIn.cs
+++ b/PCL.Core/UI/Animation/Easings/CircularEaseIn.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class CircularEaseIn : Easing
{
+ public static CircularEaseOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return 1 - Math.Sqrt(1d - progress * progress);
diff --git a/PCL.Core/UI/Animation/Easings/CircularEaseInOut.cs b/PCL.Core/UI/Animation/Easings/CircularEaseInOut.cs
index edcaed1a5..1b33af137 100644
--- a/PCL.Core/UI/Animation/Easings/CircularEaseInOut.cs
+++ b/PCL.Core/UI/Animation/Easings/CircularEaseInOut.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class CircularEaseInOut : Easing
{
+ public static CircularEaseInOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
if (progress < 0.5)
diff --git a/PCL.Core/UI/Animation/Easings/CircularEaseOut.cs b/PCL.Core/UI/Animation/Easings/CircularEaseOut.cs
index 799554933..6869c3f2e 100644
--- a/PCL.Core/UI/Animation/Easings/CircularEaseOut.cs
+++ b/PCL.Core/UI/Animation/Easings/CircularEaseOut.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class CircularEaseOut : Easing
{
+ public static CircularEaseOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return Math.Sqrt((2d - progress) * progress);
diff --git a/PCL.Core/UI/Animation/Easings/CombinedEasing.cs b/PCL.Core/UI/Animation/Easings/CombinedEasing.cs
new file mode 100644
index 000000000..29bdf8d65
--- /dev/null
+++ b/PCL.Core/UI/Animation/Easings/CombinedEasing.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace PCL.Core.UI.Animation.Easings;
+
+public class CombinedEasing(IEasing ease1, IEasing ease2, double split = 0.5) : Easing
+{
+ private readonly IEasing _ease1 = ease1 ?? throw new ArgumentNullException(nameof(ease1));
+ private readonly IEasing _ease2 = ease2 ?? throw new ArgumentNullException(nameof(ease2));
+
+ private readonly double _split = Math.Clamp(split, 0.00001, 0.99999);
+
+ protected override double EaseCore(double t)
+ {
+ if (t < _split)
+ {
+ return _split * _ease1.Ease(t / _split);
+ }
+
+ return (1.0 - _split) * _ease2.Ease((t - _split) / (1.0 - _split)) + _split;
+ }
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Easings/CompositeEasing.cs b/PCL.Core/UI/Animation/Easings/CompositeEasing.cs
new file mode 100644
index 000000000..28ad1a657
--- /dev/null
+++ b/PCL.Core/UI/Animation/Easings/CompositeEasing.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace PCL.Core.UI.Animation.Easings;
+
+///
+/// 复合缓动,支持多个缓动混合,每个缓动可独立设置时长,延迟和权重。
+///
+public class CompositeEasing : Easing
+{
+ private readonly List<(IEasing easing, TimeSpan duration, TimeSpan delay, double weight)> _easings;
+ private readonly TimeSpan _totalDuration;
+
+ ///
+ /// 初始化复合缓动。
+ ///
+ /// 参数元组:(缓动逻辑, 持续时长)
+ public CompositeEasing(params (IEasing easing, TimeSpan duration)[] easings)
+ : this(easings.Select(e => (e.easing, e.duration, TimeSpan.Zero, 1.0 / (easings.Length > 0 ? easings.Length : 1))).ToArray())
+ {
+ }
+
+ ///
+ /// 初始化复合缓动。
+ ///
+ /// 参数元组:(缓动逻辑, 持续时长, 延迟时间)
+ public CompositeEasing(params (IEasing easing, TimeSpan duration, TimeSpan delay)[] easings)
+ : this(easings.Select(e => (e.easing, e.duration, e.delay, 1.0 / (easings.Length > 0 ? easings.Length : 1))).ToArray())
+ {
+ }
+
+ ///
+ /// 初始化复合缓动。
+ ///
+ /// 参数元组:(缓动逻辑, 持续时长, 权重)
+ public CompositeEasing(params (IEasing easing, TimeSpan duration, double weight)[] easings)
+ : this(easings.Select(e => (e.easing, e.duration, TimeSpan.Zero, e.weight)).ToArray())
+ {
+ }
+
+ ///
+ /// 初始化复合缓动。
+ ///
+ /// 参数元组:(缓动逻辑, 持续时长, 延迟时间, 权重)
+ public CompositeEasing(params (IEasing easing, TimeSpan duration, TimeSpan delay, double weight)[] easings)
+ {
+ if (easings == null || easings.Length == 0)
+ throw new ArgumentException("至少需要一个缓动", nameof(easings));
+
+ _easings = new List<(IEasing, TimeSpan, TimeSpan, double)>(easings.Length);
+
+ long maxTicks = 0;
+
+ foreach (var (easing, duration, delay, weight) in easings)
+ {
+ if (easing is null) throw new ArgumentNullException(nameof(easing));
+ if (duration <= TimeSpan.Zero) throw new ArgumentException("duration 必须大于 zero");
+
+ _easings.Add((easing, duration, delay, weight));
+
+ long endTicks = (delay + duration).Ticks;
+ if (endTicks > maxTicks)
+ maxTicks = endTicks;
+ }
+
+ _totalDuration = TimeSpan.FromTicks(maxTicks);
+ }
+
+ public TimeSpan TotalDuration => _totalDuration;
+
+ protected override double EaseCore(double progress)
+ {
+ // 计算当前绝对时间
+ var elapsed = _totalDuration * progress;
+
+ var value = 0.0;
+
+ foreach (var (easing, duration, delay, weight) in _easings)
+ {
+ if (weight == 0) continue;
+
+ // 计算相对于该缓动的时间
+ var localElapsed = elapsed - delay;
+
+ double easingValue;
+
+ // 还没开始
+ if (localElapsed <= TimeSpan.Zero)
+ {
+ easingValue = 0.0;
+ }
+ // 已经结束
+ else if (localElapsed >= duration)
+ {
+ // 保持最终状态
+ easingValue = easing.Ease(1.0);
+ }
+ // 正在运行
+ else
+ {
+ // 避免除法浮点数计算问题
+ var localProgress = localElapsed.TotalSeconds / duration.TotalSeconds;
+ easingValue = easing.Ease(localProgress);
+ }
+
+ value += easingValue * weight;
+ }
+
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Easings/CubicEaseIn.cs b/PCL.Core/UI/Animation/Easings/CubicEaseIn.cs
index 6db012f1b..7d7b37a9c 100644
--- a/PCL.Core/UI/Animation/Easings/CubicEaseIn.cs
+++ b/PCL.Core/UI/Animation/Easings/CubicEaseIn.cs
@@ -2,6 +2,8 @@
public class CubicEaseIn : Easing
{
+ public static CubicEaseIn Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return progress * progress * progress;
diff --git a/PCL.Core/UI/Animation/Easings/CubicEaseInOut.cs b/PCL.Core/UI/Animation/Easings/CubicEaseInOut.cs
index 284be5a90..4ba98d1bf 100644
--- a/PCL.Core/UI/Animation/Easings/CubicEaseInOut.cs
+++ b/PCL.Core/UI/Animation/Easings/CubicEaseInOut.cs
@@ -2,6 +2,8 @@
public class CubicEaseInOut : Easing
{
+ public static CubicEaseInOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
if (progress < 0.5)
diff --git a/PCL.Core/UI/Animation/Easings/CubicEaseOut.cs b/PCL.Core/UI/Animation/Easings/CubicEaseOut.cs
index 35a607769..600cd3ad0 100644
--- a/PCL.Core/UI/Animation/Easings/CubicEaseOut.cs
+++ b/PCL.Core/UI/Animation/Easings/CubicEaseOut.cs
@@ -2,6 +2,8 @@
public class CubicEaseOut : Easing
{
+ public static CubicEaseOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
var f = progress - 1;
diff --git a/PCL.Core/UI/Animation/Easings/EasePower.cs b/PCL.Core/UI/Animation/Easings/EasePower.cs
new file mode 100644
index 000000000..b6eabfc6a
--- /dev/null
+++ b/PCL.Core/UI/Animation/Easings/EasePower.cs
@@ -0,0 +1,9 @@
+namespace PCL.Core.UI.Animation.Easings;
+
+public enum EasePower
+{
+ Weak = 2,
+ Middle = 3,
+ Strong = 4,
+ ExtraStrong = 5
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Easings/Easing.cs b/PCL.Core/UI/Animation/Easings/Easing.cs
index dfd7ee7ea..7246ca493 100644
--- a/PCL.Core/UI/Animation/Easings/Easing.cs
+++ b/PCL.Core/UI/Animation/Easings/Easing.cs
@@ -19,6 +19,6 @@ public double Ease(double progress)
public double Ease(int currentFrame, int totalFrames)
{
- return totalFrames <= 0 ? 0.0 : Ease((double)currentFrame / totalFrames);
+ return totalFrames <= 1 ? 1.0 : Ease((double)currentFrame / (totalFrames - 1));
}
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/Easings/ElasticEaseIn.cs b/PCL.Core/UI/Animation/Easings/ElasticEaseIn.cs
index c423fa224..054b073f4 100644
--- a/PCL.Core/UI/Animation/Easings/ElasticEaseIn.cs
+++ b/PCL.Core/UI/Animation/Easings/ElasticEaseIn.cs
@@ -5,6 +5,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class ElasticEaseIn : Easing
{
+ public static ElasticEaseIn Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return Math.Sin(EaseUtils.ElasticPiTimes6Point5 * progress) *
diff --git a/PCL.Core/UI/Animation/Easings/ElasticEaseInOut.cs b/PCL.Core/UI/Animation/Easings/ElasticEaseInOut.cs
index c7cc60fd0..333e01956 100644
--- a/PCL.Core/UI/Animation/Easings/ElasticEaseInOut.cs
+++ b/PCL.Core/UI/Animation/Easings/ElasticEaseInOut.cs
@@ -5,6 +5,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class ElasticEaseInOut : Easing
{
+ public static ElasticEaseInOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
if (progress < 0.5d)
diff --git a/PCL.Core/UI/Animation/Easings/ElasticEaseOut.cs b/PCL.Core/UI/Animation/Easings/ElasticEaseOut.cs
index efd0025a9..906c0490f 100644
--- a/PCL.Core/UI/Animation/Easings/ElasticEaseOut.cs
+++ b/PCL.Core/UI/Animation/Easings/ElasticEaseOut.cs
@@ -5,6 +5,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class ElasticEaseOut : Easing
{
+ public static ElasticEaseOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return Math.Sin(-EaseUtils.ElasticPiTimes6Point5 * (progress + 1d)) *
diff --git a/PCL.Core/UI/Animation/Easings/ExponentialEaseIn.cs b/PCL.Core/UI/Animation/Easings/ExponentialEaseIn.cs
index fc5061b12..a06f16dd3 100644
--- a/PCL.Core/UI/Animation/Easings/ExponentialEaseIn.cs
+++ b/PCL.Core/UI/Animation/Easings/ExponentialEaseIn.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class ExponentialEaseIn : Easing
{
+ public static ExponentialEaseIn Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return progress == 0 ? progress : Math.Pow(2, 10 * (progress - 1));
diff --git a/PCL.Core/UI/Animation/Easings/ExponentialEaseInOut.cs b/PCL.Core/UI/Animation/Easings/ExponentialEaseInOut.cs
index bb6528114..b1a71cee8 100644
--- a/PCL.Core/UI/Animation/Easings/ExponentialEaseInOut.cs
+++ b/PCL.Core/UI/Animation/Easings/ExponentialEaseInOut.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class ExponentialEaseInOut : Easing
{
+ public static ExponentialEaseInOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
if (progress < 0.5)
diff --git a/PCL.Core/UI/Animation/Easings/ExponentialEaseOut.cs b/PCL.Core/UI/Animation/Easings/ExponentialEaseOut.cs
index c5958041d..5109bbfe4 100644
--- a/PCL.Core/UI/Animation/Easings/ExponentialEaseOut.cs
+++ b/PCL.Core/UI/Animation/Easings/ExponentialEaseOut.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class ExponentialEaseOut : Easing
{
+ public static ExponentialEaseOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return Math.Abs(progress - 1.0) < 1e-4 ? progress : 1 - Math.Pow(2, -10 * progress);
diff --git a/PCL.Core/UI/Animation/Easings/LinearEasing.cs b/PCL.Core/UI/Animation/Easings/LinearEasing.cs
index ef3751526..24f7ffff3 100644
--- a/PCL.Core/UI/Animation/Easings/LinearEasing.cs
+++ b/PCL.Core/UI/Animation/Easings/LinearEasing.cs
@@ -2,6 +2,8 @@
public class LinearEasing : Easing
{
+ public static LinearEasing Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return progress;
diff --git a/PCL.Core/UI/Animation/Easings/QuadEaseIn.cs b/PCL.Core/UI/Animation/Easings/QuadEaseIn.cs
index 9baf5363b..bef1756aa 100644
--- a/PCL.Core/UI/Animation/Easings/QuadEaseIn.cs
+++ b/PCL.Core/UI/Animation/Easings/QuadEaseIn.cs
@@ -2,6 +2,8 @@
public class QuadEaseIn : Easing
{
+ public static QuadEaseIn Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return progress * progress;
diff --git a/PCL.Core/UI/Animation/Easings/QuadEaseInOut.cs b/PCL.Core/UI/Animation/Easings/QuadEaseInOut.cs
index 6f79dc8d9..f07d3db8d 100644
--- a/PCL.Core/UI/Animation/Easings/QuadEaseInOut.cs
+++ b/PCL.Core/UI/Animation/Easings/QuadEaseInOut.cs
@@ -2,6 +2,8 @@
public class QuadEaseInOut : Easing
{
+ public static QuadEaseInOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
if (progress < 0.5)
diff --git a/PCL.Core/UI/Animation/Easings/QuadEaseOut.cs b/PCL.Core/UI/Animation/Easings/QuadEaseOut.cs
index 85477f80d..c2cc52492 100644
--- a/PCL.Core/UI/Animation/Easings/QuadEaseOut.cs
+++ b/PCL.Core/UI/Animation/Easings/QuadEaseOut.cs
@@ -2,6 +2,8 @@
public class QuadEaseOut : Easing
{
+ public static QuadEaseOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return 1 - (1 - progress) * (1 - progress);
diff --git a/PCL.Core/UI/Animation/Easings/QuarticEaseIn.cs b/PCL.Core/UI/Animation/Easings/QuarticEaseIn.cs
index 23f180db2..3897d2974 100644
--- a/PCL.Core/UI/Animation/Easings/QuarticEaseIn.cs
+++ b/PCL.Core/UI/Animation/Easings/QuarticEaseIn.cs
@@ -2,6 +2,8 @@
public class QuarticEaseIn : Easing
{
+ public static QuarticEaseIn Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
var p2 = progress * progress;
diff --git a/PCL.Core/UI/Animation/Easings/QuarticEaseInOut.cs b/PCL.Core/UI/Animation/Easings/QuarticEaseInOut.cs
index 70a42bc7d..082780172 100644
--- a/PCL.Core/UI/Animation/Easings/QuarticEaseInOut.cs
+++ b/PCL.Core/UI/Animation/Easings/QuarticEaseInOut.cs
@@ -2,6 +2,8 @@
public class QuarticEaseInOut : Easing
{
+ public static QuarticEaseInOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
if (progress < 0.5d)
diff --git a/PCL.Core/UI/Animation/Easings/QuarticEaseOut.cs b/PCL.Core/UI/Animation/Easings/QuarticEaseOut.cs
index 454e70662..80323f890 100644
--- a/PCL.Core/UI/Animation/Easings/QuarticEaseOut.cs
+++ b/PCL.Core/UI/Animation/Easings/QuarticEaseOut.cs
@@ -2,6 +2,8 @@
public class QuarticEaseOut : Easing
{
+ public static QuarticEaseOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
var f = progress - 1;
diff --git a/PCL.Core/UI/Animation/Easings/QuinticEaseIn.cs b/PCL.Core/UI/Animation/Easings/QuinticEaseIn.cs
index 842fe0e16..67feef4d7 100644
--- a/PCL.Core/UI/Animation/Easings/QuinticEaseIn.cs
+++ b/PCL.Core/UI/Animation/Easings/QuinticEaseIn.cs
@@ -2,6 +2,8 @@
public class QuinticEaseIn : Easing
{
+ public static QuinticEaseIn Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
var p2 = progress * progress;
diff --git a/PCL.Core/UI/Animation/Easings/QuinticEaseInOut.cs b/PCL.Core/UI/Animation/Easings/QuinticEaseInOut.cs
index 1ef718985..06bf35f2c 100644
--- a/PCL.Core/UI/Animation/Easings/QuinticEaseInOut.cs
+++ b/PCL.Core/UI/Animation/Easings/QuinticEaseInOut.cs
@@ -2,6 +2,8 @@
public class QuinticEaseInOut : Easing
{
+ public static QuinticEaseInOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
if (progress < 0.5)
diff --git a/PCL.Core/UI/Animation/Easings/QuinticEaseOut.cs b/PCL.Core/UI/Animation/Easings/QuinticEaseOut.cs
index b0c7d7e0d..d36313966 100644
--- a/PCL.Core/UI/Animation/Easings/QuinticEaseOut.cs
+++ b/PCL.Core/UI/Animation/Easings/QuinticEaseOut.cs
@@ -2,6 +2,8 @@
public class QuinticEaseOut : Easing
{
+ public static QuinticEaseOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
var f = progress - 1d;
diff --git a/PCL.Core/UI/Animation/Easings/SineEaseIn.cs b/PCL.Core/UI/Animation/Easings/SineEaseIn.cs
index 2e9f96f6a..66d823c8f 100644
--- a/PCL.Core/UI/Animation/Easings/SineEaseIn.cs
+++ b/PCL.Core/UI/Animation/Easings/SineEaseIn.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class SineEaseIn : Easing
{
+ public static SineEaseIn Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return 1 - Math.Cos(progress * Math.PI / 2);
diff --git a/PCL.Core/UI/Animation/Easings/SineEaseInOut.cs b/PCL.Core/UI/Animation/Easings/SineEaseInOut.cs
index f6d2de93b..b4aeb6e5b 100644
--- a/PCL.Core/UI/Animation/Easings/SineEaseInOut.cs
+++ b/PCL.Core/UI/Animation/Easings/SineEaseInOut.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class SineEaseInOut : Easing
{
+ public static SineEaseInOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return -(Math.Cos(Math.PI * progress) - 1) / 2;
diff --git a/PCL.Core/UI/Animation/Easings/SineEaseOut.cs b/PCL.Core/UI/Animation/Easings/SineEaseOut.cs
index b4c114b03..7aa4116b1 100644
--- a/PCL.Core/UI/Animation/Easings/SineEaseOut.cs
+++ b/PCL.Core/UI/Animation/Easings/SineEaseOut.cs
@@ -4,6 +4,8 @@ namespace PCL.Core.UI.Animation.Easings;
public class SineEaseOut : Easing
{
+ public static SineEaseOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
{
return Math.Sin(progress * Math.PI / 2);
diff --git a/PCL.Core/UI/Animation/MatrixFromToAnimation.cs b/PCL.Core/UI/Animation/MatrixFromToAnimation.cs
index bf8d71990..8b58ad0bf 100644
--- a/PCL.Core/UI/Animation/MatrixFromToAnimation.cs
+++ b/PCL.Core/UI/Animation/MatrixFromToAnimation.cs
@@ -14,9 +14,9 @@ public class MatrixFromToAnimation : FromToAnimationBase
// 计算当前值
CurrentValue = ValueType == AnimationValueType.Relative
- ? ValueProcessorManager.Add(From!.Value, ValueProcessorManager.Scale(To, easedProgress))
- : ValueProcessorManager.Add(From!.Value,
- ValueProcessorManager.Scale(ValueProcessorManager.Subtract(To, From!.Value), easedProgress));
+ ? ValueProcessorManager.Add(From, ValueProcessorManager.Scale(To, easedProgress))
+ : ValueProcessorManager.Add(From,
+ ValueProcessorManager.Scale(ValueProcessorManager.Subtract(To, From), easedProgress));
return base.ComputeNextFrame(target);
}
diff --git a/PCL.Core/UI/Animation/NRotateTransformFromToAnimation.cs b/PCL.Core/UI/Animation/NRotateTransformFromToAnimation.cs
new file mode 100644
index 000000000..00d805910
--- /dev/null
+++ b/PCL.Core/UI/Animation/NRotateTransformFromToAnimation.cs
@@ -0,0 +1,23 @@
+using System.Windows.Media;
+using PCL.Core.UI.Animation.Animatable;
+using PCL.Core.UI.Animation.Core;
+using PCL.Core.UI.Animation.ValueProcessor;
+
+namespace PCL.Core.UI.Animation;
+
+public class NRotateTransformFromToAnimation : FromToAnimationBase
+{
+ public override IAnimationFrame? ComputeNextFrame(IAnimatable target)
+ {
+ // 应用缓动函数
+ var easedProgress = Easing.Ease(CurrentFrame, TotalFrames);
+
+ // 计算当前值
+ CurrentValue = ValueType == AnimationValueType.Relative
+ ? ValueProcessorManager.Add(From, ValueProcessorManager.Scale(To, easedProgress))
+ : ValueProcessorManager.Add(From,
+ ValueProcessorManager.Scale(ValueProcessorManager.Subtract(To, From), easedProgress));
+
+ return base.ComputeNextFrame(target);
+ }
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/NScaleTransformFromToAnimation.cs b/PCL.Core/UI/Animation/NScaleTransformFromToAnimation.cs
new file mode 100644
index 000000000..1763fb022
--- /dev/null
+++ b/PCL.Core/UI/Animation/NScaleTransformFromToAnimation.cs
@@ -0,0 +1,23 @@
+using System.Windows.Media;
+using PCL.Core.UI.Animation.Animatable;
+using PCL.Core.UI.Animation.Core;
+using PCL.Core.UI.Animation.ValueProcessor;
+
+namespace PCL.Core.UI.Animation;
+
+public class NScaleTransformFromToAnimation : FromToAnimationBase
+{
+ public override IAnimationFrame? ComputeNextFrame(IAnimatable target)
+ {
+ // 应用缓动函数
+ var easedProgress = Easing.Ease(CurrentFrame, TotalFrames);
+
+ // 计算当前值
+ CurrentValue = ValueType == AnimationValueType.Relative
+ ? ValueProcessorManager.Add(From, ValueProcessorManager.Scale(To, easedProgress))
+ : ValueProcessorManager.Add(From,
+ ValueProcessorManager.Scale(ValueProcessorManager.Subtract(To, From), easedProgress));
+
+ return base.ComputeNextFrame(target);
+ }
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/PointFromToAnimation.cs b/PCL.Core/UI/Animation/PointFromToAnimation.cs
index f44329550..bff0ec0ba 100644
--- a/PCL.Core/UI/Animation/PointFromToAnimation.cs
+++ b/PCL.Core/UI/Animation/PointFromToAnimation.cs
@@ -14,9 +14,9 @@ public class PointFromToAnimation : FromToAnimationBase
// 计算当前值
CurrentValue = ValueType == AnimationValueType.Relative
- ? ValueProcessorManager.Add(From!.Value, ValueProcessorManager.Scale(To, easedProgress))
- : ValueProcessorManager.Add(From!.Value,
- ValueProcessorManager.Scale(ValueProcessorManager.Subtract(To, From!.Value), easedProgress));
+ ? ValueProcessorManager.Add(From, ValueProcessorManager.Scale(To, easedProgress))
+ : ValueProcessorManager.Add(From,
+ ValueProcessorManager.Scale(ValueProcessorManager.Subtract(To, From), easedProgress));
return base.ComputeNextFrame(target);
}
diff --git a/PCL.Core/UI/Animation/ThicknessFromToAnimation.cs b/PCL.Core/UI/Animation/ThicknessFromToAnimation.cs
index a7a328c9e..cbdea1cdd 100644
--- a/PCL.Core/UI/Animation/ThicknessFromToAnimation.cs
+++ b/PCL.Core/UI/Animation/ThicknessFromToAnimation.cs
@@ -14,9 +14,9 @@ public class ThicknessFromToAnimation : FromToAnimationBase
// 计算当前值
CurrentValue = ValueType == AnimationValueType.Relative
- ? ValueProcessorManager.Add(From!.Value, ValueProcessorManager.Scale(To, easedProgress))
- : ValueProcessorManager.Add(From!.Value,
- ValueProcessorManager.Scale(ValueProcessorManager.Subtract(To, From!.Value), easedProgress));
+ ? ValueProcessorManager.Add(From, ValueProcessorManager.Scale(To, easedProgress))
+ : ValueProcessorManager.Add(From,
+ ValueProcessorManager.Scale(ValueProcessorManager.Subtract(To, From), easedProgress));
return base.ComputeNextFrame(target);
}
diff --git a/PCL.Core/UI/Animation/ValueProcessor/DoubleValueProcessor.cs b/PCL.Core/UI/Animation/ValueProcessor/DoubleValueProcessor.cs
index 20ea409e3..701335aa1 100644
--- a/PCL.Core/UI/Animation/ValueProcessor/DoubleValueProcessor.cs
+++ b/PCL.Core/UI/Animation/ValueProcessor/DoubleValueProcessor.cs
@@ -4,10 +4,15 @@ namespace PCL.Core.UI.Animation.ValueProcessor;
public class DoubleValueProcessor : IValueProcessor
{
- public double Filter(double value) => Math.Max(0, value);
-
+ public double Filter(double value) => value;
+
public double Add(double value1, double value2) => value1 + value2;
-
+
public double Subtract(double value1, double value2) => value1 - value2;
+
public double Scale(double value, double factor) => value * factor;
+
+ public double DefaultValue() => 0;
+
+ public bool Equal(double value1, double value2) => Math.Abs(value1 - value2) < 1e-6;
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/ValueProcessor/IValueProcessor.cs b/PCL.Core/UI/Animation/ValueProcessor/IValueProcessor.cs
index 6694c816f..9fe576a78 100644
--- a/PCL.Core/UI/Animation/ValueProcessor/IValueProcessor.cs
+++ b/PCL.Core/UI/Animation/ValueProcessor/IValueProcessor.cs
@@ -35,4 +35,18 @@ public interface IValueProcessor
/// 缩放因子。
/// 返回缩放后的值。
T Scale(T value, double factor);
+
+ ///
+ /// 获取某种类型的初始值。
+ ///
+ /// 初始值。
+ T DefaultValue();
+
+ ///
+ /// 比较两个值是否相等。
+ ///
+ /// 第一个值。
+ /// 第二个值。
+ ///
+ bool Equal(T value1, T value2);
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/ValueProcessor/MatrixValueProcessor.cs b/PCL.Core/UI/Animation/ValueProcessor/MatrixValueProcessor.cs
index 11edf954b..1d9092901 100644
--- a/PCL.Core/UI/Animation/ValueProcessor/MatrixValueProcessor.cs
+++ b/PCL.Core/UI/Animation/ValueProcessor/MatrixValueProcessor.cs
@@ -29,4 +29,8 @@ public Matrix Scale(Matrix value, double factor)
value.M21 * factor, value.M22 * factor,
value.OffsetX * factor, value.OffsetY * factor);
}
+
+ public Matrix DefaultValue() => new();
+
+ public bool Equal(Matrix value1, Matrix value2) => value1 == value2;
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/ValueProcessor/NColorValueProcessor.cs b/PCL.Core/UI/Animation/ValueProcessor/NColorValueProcessor.cs
index 132804392..7b0d791ff 100644
--- a/PCL.Core/UI/Animation/ValueProcessor/NColorValueProcessor.cs
+++ b/PCL.Core/UI/Animation/ValueProcessor/NColorValueProcessor.cs
@@ -11,9 +11,14 @@ public NColor Filter(NColor value)
return value;
}
-
+
public NColor Add(NColor value1, NColor value2) => value1 + value2;
-
+
public NColor Subtract(NColor value1, NColor value2) => value1 - value2;
+
public NColor Scale(NColor value, double factor) => value * (float)factor;
+
+ public NColor DefaultValue() => new();
+
+ public bool Equal(NColor value1, NColor value2) => value1 == value2;
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/ValueProcessor/NRotateTransformValueProcessor.cs b/PCL.Core/UI/Animation/ValueProcessor/NRotateTransformValueProcessor.cs
new file mode 100644
index 000000000..d3ad30b28
--- /dev/null
+++ b/PCL.Core/UI/Animation/ValueProcessor/NRotateTransformValueProcessor.cs
@@ -0,0 +1,16 @@
+namespace PCL.Core.UI.Animation.ValueProcessor;
+
+public class NRotateTransformValueProcessor : IValueProcessor
+{
+ public NRotateTransform Filter(NRotateTransform value) => value;
+
+ public NRotateTransform Add(NRotateTransform value1, NRotateTransform value2) => value1 + value2;
+
+ public NRotateTransform Subtract(NRotateTransform value1, NRotateTransform value2) => value1 - value2;
+
+ public NRotateTransform Scale(NRotateTransform value, double factor) => value * (float)factor;
+
+ public NRotateTransform DefaultValue() => new();
+
+ public bool Equal(NRotateTransform value1, NRotateTransform value2) => value1 == value2;
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/ValueProcessor/NScaleTransformValueProcessor.cs b/PCL.Core/UI/Animation/ValueProcessor/NScaleTransformValueProcessor.cs
new file mode 100644
index 000000000..535b3c5e0
--- /dev/null
+++ b/PCL.Core/UI/Animation/ValueProcessor/NScaleTransformValueProcessor.cs
@@ -0,0 +1,16 @@
+namespace PCL.Core.UI.Animation.ValueProcessor;
+
+public class NScaleTransformValueProcessor : IValueProcessor
+{
+ public NScaleTransform Filter(NScaleTransform value) => value;
+
+ public NScaleTransform Add(NScaleTransform value1, NScaleTransform value2) => value1 + value2;
+
+ public NScaleTransform Subtract(NScaleTransform value1, NScaleTransform value2) => value1 - value2;
+
+ public NScaleTransform Scale(NScaleTransform value, double factor) => value * (float)factor;
+
+ public NScaleTransform DefaultValue() => new();
+
+ public bool Equal(NScaleTransform value1, NScaleTransform value2) => value1 == value2;
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/ValueProcessor/PointValueProcessor.cs b/PCL.Core/UI/Animation/ValueProcessor/PointValueProcessor.cs
index 8e2995354..fb367f583 100644
--- a/PCL.Core/UI/Animation/ValueProcessor/PointValueProcessor.cs
+++ b/PCL.Core/UI/Animation/ValueProcessor/PointValueProcessor.cs
@@ -11,4 +11,8 @@ public class PointValueProcessor : IValueProcessor
public Point Subtract(Point value1, Point value2) => new(value1.X - value2.X, value1.Y - value2.Y);
public Point Scale(Point value, double factor) => new(value.X * factor, value.Y * factor);
+
+ public Point DefaultValue() => new();
+
+ public bool Equal(Point value1, Point value2) => value1 == value2;
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/ValueProcessor/ThicknessValueProcessor.cs b/PCL.Core/UI/Animation/ValueProcessor/ThicknessValueProcessor.cs
index 4f972420e..396a2e084 100644
--- a/PCL.Core/UI/Animation/ValueProcessor/ThicknessValueProcessor.cs
+++ b/PCL.Core/UI/Animation/ValueProcessor/ThicknessValueProcessor.cs
@@ -4,7 +4,6 @@ namespace PCL.Core.UI.Animation.ValueProcessor;
public class ThicknessValueProcessor : IValueProcessor
{
- // Thickness 不需要过滤
public Thickness Filter(Thickness value) => value;
public Thickness Add(Thickness value1, Thickness value2)
@@ -30,4 +29,8 @@ public Thickness Scale(Thickness value, double factor)
value.Right * factor,
value.Bottom * factor);
}
+
+ public Thickness DefaultValue() => new();
+
+ public bool Equal(Thickness value1, Thickness value2) => value1 == value2;
}
\ No newline at end of file
diff --git a/PCL.Core/UI/Animation/ValueProcessor/ValueProcessorManager.cs b/PCL.Core/UI/Animation/ValueProcessor/ValueProcessorManager.cs
index 964fffd2e..8eb213f76 100644
--- a/PCL.Core/UI/Animation/ValueProcessor/ValueProcessorManager.cs
+++ b/PCL.Core/UI/Animation/ValueProcessor/ValueProcessorManager.cs
@@ -5,7 +5,9 @@ namespace PCL.Core.UI.Animation.ValueProcessor;
public static class ValueProcessorManager
{
- private static readonly Dictionary> _Filters = new();
+ private static readonly Dictionary> Filters = new();
+ private static readonly Dictionary> Adders = new();
+ private static readonly Dictionary> Scalers = new();
private static class Cache
{
@@ -17,7 +19,9 @@ public static void Register(IValueProcessor processor)
ArgumentNullException.ThrowIfNull(processor);
Cache.Processor = processor;
- _Filters[typeof(T)] = o => processor.Filter((T)o)!;
+ Filters[typeof(T)] = o => processor.Filter((T)o)!;
+ Adders[typeof(T)] = (o1, o2) => processor.Add((T)o1, (T)o2)!;
+ Scalers[typeof(T)] = (o, f) => processor.Scale((T)o, f)!;
}
public static T Filter(T value)
@@ -29,7 +33,7 @@ public static T Filter(T value)
public static object Filter(object value)
{
var t = value.GetType();
- return _Filters.TryGetValue(t, out var func)
+ return Filters.TryGetValue(t, out var func)
? func(value)
: value;
}
@@ -41,6 +45,15 @@ public static T Add(T value1, T value2)
return p.Add(value1, value2);
}
+ public static object Add(object value1, object value2)
+ {
+ var t = value1.GetType();
+ if (t != value2.GetType())
+ throw new InvalidOperationException($"类型不一致:{t} vs {value2.GetType()}");
+
+ return Adders.TryGetValue(t, out var func) ? func(value1, value2) : value2;
+ }
+
public static T Subtract(T value1, T value2)
{
var p = Cache.Processor
@@ -54,4 +67,24 @@ public static T Scale(T value, double factor)
?? throw new InvalidOperationException($"类型未注册:{typeof(T)}");
return p.Scale(value, factor);
}
+
+ public static object Scale(object value, double factor)
+ {
+ var t = value.GetType();
+ return Scalers.TryGetValue(t, out var func) ? func(value, factor) : value;
+ }
+
+ public static T DefaultValue()
+ {
+ var p = Cache.Processor
+ ?? throw new InvalidOperationException($"类型未注册:{typeof(T)}");
+ return p.DefaultValue();
+ }
+
+ public static bool Equal(T value1, T value2)
+ {
+ var p = Cache.Processor
+ ?? throw new InvalidOperationException($"类型未注册:{typeof(T)}");
+ return p.Equal(value1, value2);
+ }
}
\ No newline at end of file
diff --git a/PCL.Core/UI/NColor.cs b/PCL.Core/UI/NColor.cs
index ccb73c33f..187533867 100644
--- a/PCL.Core/UI/NColor.cs
+++ b/PCL.Core/UI/NColor.cs
@@ -1,6 +1,8 @@
using System;
using System.Numerics;
+using System.Windows;
using System.Windows.Media;
+using PCL.Core.App;
namespace PCL.Core.UI;
@@ -49,11 +51,10 @@ public NColor()
public NColor(float r, float g, float b, float a = 255f)
{
- _color = new Vector4(Math.Clamp(r, 0, 255), Math.Clamp(g, 0, 255), Math.Clamp(b, 0, 255),
- Math.Clamp(a, 0, 255));
+ _color = new Vector4(r, g, b, a);
}
- public NColor(Color color) : this(color.R, color.G, color.B, a: color.A)
+ public NColor(Color color) : this(color.R, color.G, color.B, color.A)
{
}
@@ -61,14 +62,34 @@ public NColor(System.Drawing.Color color) : this(color.R, color.G, color.B, colo
{
}
- public NColor(string hex)
+ public NColor(string str)
{
- if (string.IsNullOrWhiteSpace(hex))
- throw new ArgumentException("颜色字符串不能为空。", nameof(hex));
+ try
+ {
+ var resource = Lifecycle.CurrentApplication.FindResource(str);
+ switch (resource)
+ {
+ case Color color:
+ _color = new Vector4(color.R, color.G, color.B, color.A);
+ return;
+ case SolidColorBrush brush:
+ var brushColor = brush.Color;
+ _color = new Vector4(brushColor.R, brushColor.G, brushColor.B, brushColor.A);
+ return;
+ }
+ }
+ catch
+ {
+ // 忽略
+ }
+
+
+ if (string.IsNullOrWhiteSpace(str))
+ throw new ArgumentException("颜色字符串不能为空。", nameof(str));
- var trimmedString = hex.Trim();
+ var trimmedString = str.Trim();
if (!trimmedString.StartsWith('#'))
- throw new ArgumentException("颜色字符串必须以 '#' 开头。", nameof(hex));
+ throw new ArgumentException("颜色字符串必须以 '#' 开头。", nameof(str));
trimmedString = trimmedString[1..];
@@ -104,7 +125,7 @@ public NColor(string hex)
break;
default:
- throw new ArgumentException($"无效的颜色字符串长度:{trimmedString.Length}。", nameof(hex));
+ throw new ArgumentException($"无效的颜色字符串长度:{trimmedString.Length}。", nameof(str));
}
_color = new Vector4(r, g, b, a);
@@ -126,40 +147,21 @@ public NColor(Brush brush) : this((SolidColorBrush)brush)
{
}
+ private NColor(Vector4 v) => _color = v;
+
#endregion
#region 运算符重载
- public static NColor operator +(NColor a, NColor b)
- {
- return new NColor(a.R + b.R, a.G + b.G, a.B + b.B, a.A + b.A);
- }
+ public static NColor operator +(NColor a, NColor b) => new(a._color + b._color);
+ public static NColor operator -(NColor a, NColor b) => new(a._color - b._color);
+ public static NColor operator *(NColor a, float b) => new(a._color * b);
- public static NColor operator -(NColor a, NColor b)
- {
- return new NColor(a.R - b.R, a.G - b.G, a.B - b.B, a.A - b.A);
- }
+ public static NColor operator /(NColor a, float b) =>
+ b == 0 ? throw new DivideByZeroException("除数不能为零。") : new NColor(a._color / b);
- public static NColor operator *(NColor a, float b)
- {
- return new NColor(a.R * b, a.G * b, a.B * b, a.A * b);
- }
-
- public static NColor operator /(NColor a, float b)
- {
- if (b == 0) throw new DivideByZeroException("除数不能为零。");
- return new NColor(a.R / b, a.G / b, a.B / b, a.A / b);
- }
-
- public static bool operator ==(NColor a, NColor b)
- {
- return a._color == b._color;
- }
-
- public static bool operator !=(NColor a, NColor b)
- {
- return a._color != b._color;
- }
+ public static bool operator ==(NColor a, NColor b) => a._color == b._color;
+ public static bool operator !=(NColor a, NColor b) => a._color != b._color;
#endregion
@@ -231,4 +233,21 @@ private static double _Hue(double v1, double v2, double vH)
}
#endregion
+
+ #region 隐式转换
+
+ public static implicit operator Color(NColor color) =>
+ Color.FromArgb(
+ (byte)Math.Clamp(color.A, 0, 255),
+ (byte)Math.Clamp(color.R, 0, 255),
+ (byte)Math.Clamp(color.G, 0, 255),
+ (byte)Math.Clamp(color.B, 0, 255));
+ public static implicit operator Brush(NColor color) => new SolidColorBrush(color);
+ public static implicit operator SolidColorBrush(NColor color) => new(color);
+
+ public static implicit operator NColor(Color color) => new(color);
+ public static implicit operator NColor(Brush brush) => new(brush);
+ public static implicit operator NColor(SolidColorBrush brush) => new(brush);
+
+ #endregion
}
\ No newline at end of file
diff --git a/PCL.Core/UI/NRotateTransform.cs b/PCL.Core/UI/NRotateTransform.cs
new file mode 100644
index 000000000..4fc45413c
--- /dev/null
+++ b/PCL.Core/UI/NRotateTransform.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Numerics;
+using System.Windows.Media;
+using PCL.Core.UI.Animation.Core;
+
+namespace PCL.Core.UI;
+
+public struct NRotateTransform :
+ IEquatable,
+ IAdditionOperators,
+ ISubtractionOperators,
+ IMultiplyOperators,
+ IDivisionOperators
+{
+ private Vector3 _rotate;
+
+ public float Angle
+ {
+ get => _rotate.X;
+ set => _rotate.X = value;
+ }
+
+ public float CenterX
+ {
+ get => _rotate.Y;
+ set => _rotate.Y = value;
+ }
+
+ public float CenterY
+ {
+ get => _rotate.Z;
+ set => _rotate.Z = value;
+ }
+
+ #region 构造函数
+
+ public NRotateTransform()
+ {
+ _rotate = new Vector3(0, 0, 0);
+ }
+
+ public NRotateTransform(float angle, float centerX = 0f, float centerY = 0f)
+ {
+ _rotate = new Vector3(angle, centerX, centerY);
+ }
+
+ public NRotateTransform(RotateTransform scaleTransform)
+ {
+ var uiAccessProvider = AnimationService.UIAccessProvider;
+ if (uiAccessProvider.CheckAccess())
+ {
+ _rotate = GetVector(scaleTransform);
+ }
+ else
+ {
+ Vector3 localScale = default;
+ uiAccessProvider.Invoke(() => localScale = GetVector(scaleTransform));
+ _rotate = localScale;
+ }
+
+ return;
+
+ Vector3 GetVector(RotateTransform rt)
+ {
+ return new Vector3((float)rt.Angle, (float)rt.CenterX, (float)rt.CenterY);
+ }
+ }
+
+ #endregion
+
+ #region 运算符重载
+
+ public static NRotateTransform operator +(NRotateTransform a, NRotateTransform b) => new(a.Angle + b.Angle);
+ public static NRotateTransform operator -(NRotateTransform a, NRotateTransform b) => new(a.Angle - b.Angle);
+ public static NRotateTransform operator *(NRotateTransform a, float b) => new(a.Angle * b);
+
+ public static NRotateTransform operator /(NRotateTransform a, float b) =>
+ b == 0 ? throw new DivideByZeroException("除数不能为零。") : new NRotateTransform(a.Angle / b);
+
+ public static bool operator ==(NRotateTransform a, NRotateTransform b) => a._rotate == b._rotate;
+ public static bool operator !=(NRotateTransform a, NRotateTransform b) => a._rotate != b._rotate;
+
+ #endregion
+
+ #region IEquatable
+
+ public bool Equals(NRotateTransform other)
+ {
+ return _rotate.Equals(other._rotate);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (obj is NRotateTransform color)
+ return Equals(color);
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Angle, CenterX, CenterY);
+ }
+
+ #endregion
+
+ #region 隐式转换
+
+ public static implicit operator RotateTransform(NRotateTransform rt) =>
+ new(rt.Angle, rt.CenterX, rt.CenterY);
+
+ public static implicit operator NRotateTransform(RotateTransform rt) => new(rt);
+
+ #endregion
+}
\ No newline at end of file
diff --git a/PCL.Core/UI/NScaleTransform.cs b/PCL.Core/UI/NScaleTransform.cs
new file mode 100644
index 000000000..efc5a8dbc
--- /dev/null
+++ b/PCL.Core/UI/NScaleTransform.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Numerics;
+using System.Windows.Media;
+using PCL.Core.UI.Animation.Core;
+
+namespace PCL.Core.UI;
+
+public struct NScaleTransform :
+ IEquatable,
+ IAdditionOperators,
+ ISubtractionOperators,
+ IMultiplyOperators,
+ IDivisionOperators
+{
+ private Vector4 _scale;
+
+ public float ScaleX
+ {
+ get => _scale.X;
+ set => _scale.X = value;
+ }
+
+ public float ScaleY
+ {
+ get => _scale.Y;
+ set => _scale.Y = value;
+ }
+
+ public float CenterX
+ {
+ get => _scale.Z;
+ set => _scale.Z = value;
+ }
+
+ public float CenterY
+ {
+ get => _scale.W;
+ set => _scale.W = value;
+ }
+
+ #region 构造函数
+
+ public NScaleTransform()
+ {
+ _scale = new Vector4(1, 1, 0, 0);
+ }
+
+ public NScaleTransform(float scaleX, float scaleY, float centerX = 0f, float centerY = 0f)
+ {
+ _scale = new Vector4(scaleX, scaleY, centerX, centerY);
+ }
+
+ public NScaleTransform(ScaleTransform scaleTransform)
+ {
+ var uiAccessProvider = AnimationService.UIAccessProvider;
+ if (uiAccessProvider.CheckAccess())
+ {
+ _scale = GetVector(scaleTransform);
+ }
+ else
+ {
+ Vector4 localScale = default;
+ uiAccessProvider.Invoke(() => localScale = GetVector(scaleTransform));
+ _scale = localScale;
+ }
+
+ return;
+
+ Vector4 GetVector(ScaleTransform st)
+ {
+ return new Vector4((float)st.ScaleX, (float)st.ScaleY, (float)st.CenterX, (float)st.CenterY);
+ }
+ }
+
+ #endregion
+
+ #region 运算符重载
+
+ public static NScaleTransform operator +(NScaleTransform a, NScaleTransform b) => new(a.ScaleX + b.ScaleX, a.ScaleY + b.ScaleY);
+ public static NScaleTransform operator -(NScaleTransform a, NScaleTransform b) => new(a.ScaleX - b.ScaleX, a.ScaleY - b.ScaleY);
+ public static NScaleTransform operator *(NScaleTransform a, float b) => new(a.ScaleX * b, a.ScaleY * b);
+
+ public static NScaleTransform operator /(NScaleTransform a, float b) =>
+ b == 0 ? throw new DivideByZeroException("除数不能为零。") : new NScaleTransform(a.ScaleX / b, a.ScaleY / b);
+
+ public static bool operator ==(NScaleTransform a, NScaleTransform b) => a._scale == b._scale;
+ public static bool operator !=(NScaleTransform a, NScaleTransform b) => a._scale != b._scale;
+
+ #endregion
+
+ #region IEquatable
+
+ public bool Equals(NScaleTransform other)
+ {
+ return _scale.Equals(other._scale);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (obj is NScaleTransform color)
+ return Equals(color);
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(ScaleX, ScaleY, CenterX, CenterY);
+ }
+
+ #endregion
+
+ #region 隐式转换
+
+ public static implicit operator ScaleTransform(NScaleTransform st) =>
+ new(st.ScaleX, st.ScaleY, st.CenterX, st.CenterY);
+
+ public static implicit operator NScaleTransform(ScaleTransform st) => new(st);
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Application.xaml b/Plain Craft Launcher 2/Application.xaml
index 10b720815..e5269dbb3 100644
--- a/Plain Craft Launcher 2/Application.xaml
+++ b/Plain Craft Launcher 2/Application.xaml
@@ -4,6 +4,8 @@
xmlns:local="clr-namespace:PCL"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:behaviors="clr-namespace:PCL.Controls.Behaviors"
+ xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
+ xmlns:ani="https://ce.pclc.cc/core/ui/animation"
xmlns:controls="clr-namespace:PCL.Core.UI.Controls;assembly=PCL.Core"
xmlns:cvt="clr-namespace:PCL.Core.UI.Converters;assembly=PCL.Core"
ShutdownMode="OnExplicitShutdown">
@@ -456,11 +458,33 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/Plain Craft Launcher 2/Controls/MyButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyButton.xaml.vb
index 94a0677d4..95648d039 100644
--- a/Plain Craft Launcher 2/Controls/MyButton.xaml.vb
+++ b/Plain Craft Launcher 2/Controls/MyButton.xaml.vb
@@ -1,4 +1,9 @@
Imports System.Windows.Markup
+Imports PCL.Core.UI
+Imports PCL.Core.UI.Animation
+Imports PCL.Core.UI.Animation.Animatable
+Imports PCL.Core.UI.Animation.Core
+Imports PCL.Core.UI.Animation.Easings
Public Class MyButton
@@ -69,10 +74,13 @@ Public Class MyButton
End Property
'自定义事件
- Private Const AnimationColorIn As Integer = 100
- Private Const AnimationColorOut As Integer = 200
+ Private ReadOnly _animationColorIn = TimeSpan.FromMilliseconds(100)
+ Private Readonly _animationColorOut = TimeSpan.FromMilliseconds(200)
Private Sub RefreshColor(Optional obj = Nothing, Optional e = Nothing) Handles Me.MouseEnter, Me.MouseLeave, Me.Loaded, Me.IsEnabledChanged
Try
+ Dim animation = New NColorFromToAnimation With {.Name = "MyButton Color " & Uuid}
+ Dim animatable = New WpfAnimatable(PanFore, BorderBrushProperty)
+
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
If IsEnabled Then
@@ -80,54 +88,75 @@ Public Class MyButton
Case ColorState.Normal
If IsMouseOver Then
'指向(Main 3)
- AniStart({AaColor(PanFore, Border.BorderBrushProperty, "ColorBrush3", AnimationColorIn)}, "MyButton Color " & Uuid)
+ 'AniStart({AaColor(PanFore, Border.BorderBrushProperty, "ColorBrush3", AnimationColorIn)}, "MyButton Color " & Uuid)
+ animation.To = New NColor("ColorBrush3")
+ animation.Duration = _animationColorIn
+ animation.RunFireAndForget(animatable)
Else
'普通(Main 1)
- AniStart({AaColor(PanFore, Border.BorderBrushProperty, "ColorBrush1", AnimationColorOut)}, "MyButton Color " & Uuid)
+ 'AniStart({AaColor(PanFore, Border.BorderBrushProperty, "ColorBrush1", AnimationColorOut)}, "MyButton Color " & Uuid)
+ animation.To = New NColor("ColorBrush1")
+ animation.Duration = _animationColorOut
+ animation.RunFireAndForget(animatable)
End If
Case ColorState.Highlight
If IsMouseOver Then
'指向(Main 3)
- AniStart({AaColor(PanFore, Border.BorderBrushProperty, "ColorBrush3", AnimationColorIn)}, "MyButton Color " & Uuid)
+ 'AniStart({AaColor(PanFore, Border.BorderBrushProperty, "ColorBrush3", AnimationColorIn)}, "MyButton Color " & Uuid)
+ animation.To = New NColor("ColorBrush3")
+ animation.Duration = _animationColorIn
+ animation.RunFireAndForget(animatable)
Else
'高亮(Main 2)
- AniStart({AaColor(PanFore, Border.BorderBrushProperty, "ColorBrush2", AnimationColorOut)}, "MyButton Color " & Uuid)
+ 'AniStart({AaColor(PanFore, Border.BorderBrushProperty, "ColorBrush2", AnimationColorOut)}, "MyButton Color " & Uuid)
+ animation.To = New NColor("ColorBrush2")
+ animation.Duration = _animationColorOut
+ animation.RunFireAndForget(animatable)
End If
Case ColorState.Red
If IsMouseOver Then
'红色指向
- AniStart({AaColor(PanFore, Border.BorderBrushProperty, "ColorBrushRedLight", AnimationColorIn)}, "MyButton Color " & Uuid)
+ 'AniStart({AaColor(PanFore, Border.BorderBrushProperty, "ColorBrushRedLight", AnimationColorIn)}, "MyButton Color " & Uuid)
+ animation.To = New NColor("ColorBrushRedLight")
+ animation.Duration = _animationColorIn
+ animation.RunFireAndForget(animatable)
Else
'红色
- AniStart({AaColor(PanFore, Border.BorderBrushProperty, "ColorBrushRedDark", AnimationColorOut)}, "MyButton Color " & Uuid)
+ 'AniStart({AaColor(PanFore, Border.BorderBrushProperty, "ColorBrushRedDark", AnimationColorOut)}, "MyButton Color " & Uuid)
+ animation.To = New NColor("ColorBrushRedDark")
+ animation.Duration = _animationColorOut
+ animation.RunFireAndForget(animatable)
End If
End Select
Else
'不可用(Gray 4)
- AniStart({AaColor(PanFore, Border.BorderBrushProperty, ColorGray4 - PanFore.BorderBrush, AnimationColorOut)}, "MyButton Color " & Uuid)
+ 'AniStart({AaColor(PanFore, Border.BorderBrushProperty, ColorGray4 - PanFore.BorderBrush, AnimationColorOut)}, "MyButton Color " & Uuid)
+ animation.To = New NColor("ColorBrushGray4")
+ animation.Duration = _animationColorOut
+ animation.RunFireAndForget(animatable)
End If
Else
-
- AniStop("MyButton Color " & Uuid)
+ 'AniStop("MyButton Color " & Uuid)
+ AnimationService.CancelAnimationByName("MyButton Color " & Uuid)
If IsEnabled Then
Select Case ColorType
Case ColorState.Normal
If IsMouseOver Then
- PanFore.SetResourceReference(Border.BorderBrushProperty, "ColorBrush3")
+ PanFore.SetResourceReference(BorderBrushProperty, "ColorBrush3")
Else
- PanFore.SetResourceReference(Border.BorderBrushProperty, "ColorBrush1")
+ PanFore.SetResourceReference(BorderBrushProperty, "ColorBrush1")
End If
Case ColorState.Highlight
If IsMouseOver Then
- PanFore.SetResourceReference(Border.BorderBrushProperty, "ColorBrush3")
+ PanFore.SetResourceReference(BorderBrushProperty, "ColorBrush3")
Else
- PanFore.SetResourceReference(Border.BorderBrushProperty, "ColorBrush2")
+ PanFore.SetResourceReference(BorderBrushProperty, "ColorBrush2")
End If
Case ColorState.Red
If IsMouseOver Then
- PanFore.SetResourceReference(Border.BorderBrushProperty, "ColorBrushRedLight")
+ PanFore.SetResourceReference(BorderBrushProperty, "ColorBrushRedLight")
Else
- PanFore.SetResourceReference(Border.BorderBrushProperty, "ColorBrushRedDark")
+ PanFore.SetResourceReference(BorderBrushProperty, "ColorBrushRedDark")
End If
End Select
Else
@@ -176,26 +205,55 @@ Public Class MyButton
Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
IsMouseDown = True
Focus()
- AniStart({
- AaScaleTransform(PanFore, 0.955 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 80,, New AniEaseOutFluent(AniEasePower.ExtraStrong)),
- AaScaleTransform(PanFore, -0.01, 700,, New AniEaseOutFluent(AniEasePower.Middle))
- }, "MyButton Scale " & Uuid)
+' AniStart({
+' AaScaleTransform(PanFore, 0.955 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 80,, New AniEaseOutFluent(AniEasePower.ExtraStrong)),
+' AaScaleTransform(PanFore, -0.01, 700,, New AniEaseOutFluent(AniEasePower.Middle))
+' }, "MyButton Scale " & Uuid)
+ Dim animation = New NScaleTransformFromToAnimation _
+ With {.To = New NScaleTransform(0.954, 0.954),
+ .Duration = TimeSpan.FromMilliseconds(80),
+ .Easing = ExponentialEaseOut.Shared,
+ .Name = "MyButton Scale " & Uuid}
+ animation.RunFireAndForget(new WpfAnimatable(PanFore, RenderTransformProperty))
End Sub
Private Sub Button_MouseEnter() Handles Me.MouseEnter
- AniStart(AaColor(PanFore, BackgroundProperty, If(_ColorType = ColorState.Red, "ColorBrushRedBack", "ColorBrush7"), AnimationColorIn), "MyButton Background " & Uuid)
+' AniStart(AaColor(PanFore, BackgroundProperty, If(_ColorType = ColorState.Red, "ColorBrushRedBack", "ColorBrush7"), AnimationColorIn), "MyButton Background " & Uuid)
+ Dim animation = New NColorFromToAnimation
+ animation.Name = "MyButton Background " & Uuid
+ animation.To = New NColor(If(_ColorType = ColorState.Red, "ColorBrushRedBack", "ColorBrush7"))
+ animation.Duration = _animationColorIn
+ animation.RunFireAndForget(New WpfAnimatable(PanFore, BackgroundProperty))
End Sub
Private Sub Button_MouseUp() Handles Me.MouseLeftButtonUp
If Not IsMouseDown Then Return
IsMouseDown = False
- AniStart({
- AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 300, 10, New AniEaseOutFluent(AniEasePower.Middle))
- }, "MyButton Scale " & Uuid)
+' AniStart({
+' AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 300, 10, New AniEaseOutFluent(AniEasePower.Middle))
+' }, "MyButton Scale " & Uuid)
+ Dim animation = New NScaleTransformFromToAnimation()
+ animation.Name = "MyButton Scale " & Uuid
+ animation.To = New NScaleTransform(1, 1)
+ animation.Duration = TimeSpan.FromMilliseconds(300)
+ animation.Delay = TimeSpan.FromMilliseconds(10)
+ animation.Easing = CubicEaseOut.Shared
+ animation.RunFireAndForget(new WpfAnimatable(PanFore, RenderTransformProperty))
End Sub
Private Sub Button_MouseLeave() Handles Me.MouseLeave
- AniStart(AaColor(PanFore, BackgroundProperty, "ColorBrushHalfWhite", AnimationColorOut), "MyButton Background " & Uuid)
+' AniStart(AaColor(PanFore, BackgroundProperty, "ColorBrushHalfWhite", AnimationColorOut), "MyButton Background " & Uuid)
+ Dim animation1 = New NColorFromToAnimation
+ animation1.Name = "MyButton Background " & Uuid
+ animation1.To = New NColor("ColorBrushHalfWhite")
+ animation1.Duration = _animationColorOut
+ animation1.RunFireAndForget(New WpfAnimatable(PanFore, BackgroundProperty))
If Not IsMouseDown Then Return
IsMouseDown = False
- AniStart(AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)), "MyButton Scale " & Uuid)
+' AniStart(AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)), "MyButton Scale " & Uuid)
+ Dim animation2 = New NScaleTransformFromToAnimation()
+ animation2.Name = "MyButton Scale " & Uuid
+ animation2.To = New NScaleTransform(1, 1, 0.5, 0.5)
+ animation2.Duration = TimeSpan.FromMilliseconds(800)
+ animation2.Easing = QuinticEaseOut.Shared
+ animation2.RunFireAndForget(new WpfAnimatable(PanFore, RenderTransformProperty))
End Sub
End Class
diff --git a/Plain Craft Launcher 2/Controls/MyCard.vb b/Plain Craft Launcher 2/Controls/MyCard.vb
index 4b3f31047..d71e2d635 100644
--- a/Plain Craft Launcher 2/Controls/MyCard.vb
+++ b/Plain Craft Launcher 2/Controls/MyCard.vb
@@ -1,3 +1,9 @@
+Imports System.Threading.Tasks
+Imports PCL.Core.UI
+Imports PCL.Core.UI.Animation
+Imports PCL.Core.UI.Animation.Animatable
+Imports PCL.Core.UI.Animation.Core
+Imports PCL.Core.UI.Animation.Easings
Imports PCL.Core.UI.Controls
Public Class MyCard
@@ -149,25 +155,89 @@ Public Class MyCard
Public Property HasMouseAnimation As Boolean = True
Private Sub MyCard_MouseEnter(sender As Object, e As MouseEventArgs) Handles Me.MouseEnter
If Not HasMouseAnimation Then Return
- Dim AniList As New List(Of AniData)
- If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush2", 90))
- If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush2", 90))
- AniList.AddRange({
- AaColor(MainChrome, MyDropShadow.ColorProperty, "ColorObject4", 90),
- AaOpacity(MainChrome, DropShadowHoverOpacity - MainChrome.Opacity, 90)
- })
- If Not IsAnimating Then AniStart(AniList, "MyCard Mouse " & Uuid)
+' Dim AniList As New List(Of AniData)
+ Dim animation = New ParallelAnimationGroup
+ animation.Name = "MyCard Mouse " & Uuid
+
+ If Not IsNothing(MainTextBlock) Then
+' AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush2", 90))
+ Dim aniTextColor = New NColorFromToAnimation
+ aniTextColor.To = New NColor("ColorBrush2")
+ aniTextColor.Duration = TimeSpan.FromMilliseconds(90)
+ aniTextColor.SetValue(AnimationExtensions.TargetProperty, MainTextBlock)
+ aniTextColor.SetValue(AnimationExtensions.TargetPropertyProperty, TextBlock.ForegroundProperty)
+ animation.Children.Add(aniTextColor)
+ End If
+ If Not IsNothing(MainSwap) Then
+' AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush2", 90))
+ Dim aniSwapColor = New NColorFromToAnimation
+ aniSwapColor.To = New NColor("ColorBrush2")
+ aniSwapColor.Duration = TimeSpan.FromMilliseconds(90)
+ aniSwapColor.SetValue(AnimationExtensions.TargetProperty, MainSwap)
+ aniSwapColor.SetValue(AnimationExtensions.TargetPropertyProperty, Shapes.Path.FillProperty)
+ animation.Children.Add(aniSwapColor)
+ End If
+' AniList.AddRange({
+' AaColor(MainChrome, MyDropShadow.ColorProperty, "ColorObject4", 90),
+' AaOpacity(MainChrome, DropShadowHoverOpacity - MainChrome.Opacity, 90)
+' })
+ Dim aniChromeColor = New NColorFromToAnimation
+ aniChromeColor.To = New NColor("ColorObject4")
+ aniChromeColor.Duration = TimeSpan.FromMilliseconds(90)
+ aniChromeColor.SetValue(AnimationExtensions.TargetPropertyProperty, MyDropShadow.ColorProperty)
+ Dim aniChromeOpacity = New DoubleFromToAnimation
+ aniChromeOpacity.To = DropShadowHoverOpacity
+ aniChromeOpacity.Duration = TimeSpan.FromMilliseconds(90)
+ animation.Children.Add(aniChromeColor)
+ animation.Children.Add(aniChromeOpacity)
+
+
+ If Not IsAnimating Then
+' AniStart(AniList, "MyCard Mouse " & Uuid)
+ animation.RunFireAndForget(New WpfAnimatable(MainChrome, OpacityProperty))
+ End If
End Sub
Private Sub MyCard_MouseLeave(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
If Not HasMouseAnimation Then Return
- Dim AniList As New List(Of AniData)
- If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush1", 90))
- If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush1", 90))
- AniList.AddRange({
- AaColor(MainChrome, MyDropShadow.ColorProperty, "ColorObject1", 90),
- AaOpacity(MainChrome, DropShadowIdleOpacity - MainChrome.Opacity, 90)
- })
- If Not IsAnimating Then AniStart(AniList, "MyCard Mouse " & Uuid)
+' Dim AniList As New List(Of AniData)
+ Dim animation = New ParallelAnimationGroup
+ animation.Name = "MyCard Mouse " & Uuid
+
+ If Not IsNothing(MainTextBlock) Then
+' AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush1", 90))
+ Dim aniTextColor = New NColorFromToAnimation
+ aniTextColor.To = New NColor("ColorBrush1")
+ aniTextColor.Duration = TimeSpan.FromMilliseconds(90)
+ aniTextColor.SetValue(AnimationExtensions.TargetProperty, MainTextBlock)
+ aniTextColor.SetValue(AnimationExtensions.TargetPropertyProperty, TextBlock.ForegroundProperty)
+ animation.Children.Add(aniTextColor)
+ End If
+ If Not IsNothing(MainSwap) Then
+' AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush1", 90))
+ Dim aniSwapColor = New NColorFromToAnimation
+ aniSwapColor.To = New NColor("ColorBrush1")
+ aniSwapColor.Duration = TimeSpan.FromMilliseconds(90)
+ aniSwapColor.SetValue(AnimationExtensions.TargetProperty, MainSwap)
+ aniSwapColor.SetValue(AnimationExtensions.TargetPropertyProperty, Shapes.Path.FillProperty)
+ animation.Children.Add(aniSwapColor)
+ End If
+' AniList.AddRange({
+' AaColor(MainChrome, MyDropShadow.ColorProperty, "ColorObject1", 90),
+' AaOpacity(MainChrome, DropShadowIdleOpacity - MainChrome.Opacity, 90)
+' })
+ Dim aniChromeColor = New NColorFromToAnimation
+ aniChromeColor.To = New NColor("ColorObject1")
+ aniChromeColor.Duration = TimeSpan.FromMilliseconds(90)
+ aniChromeColor.SetValue(AnimationExtensions.TargetPropertyProperty, MyDropShadow.ColorProperty)
+ Dim aniChromeOpacity = New DoubleFromToAnimation
+ aniChromeOpacity.To = DropShadowIdleOpacity
+ aniChromeOpacity.Duration = TimeSpan.FromMilliseconds(90)
+ animation.Children.Add(aniChromeColor)
+ animation.Children.Add(aniChromeOpacity)
+ If Not IsAnimating Then
+' AniStart(AniList, "MyCard Mouse " & Uuid)
+ animation.RunFireAndForget(New WpfAnimatable(MainChrome, OpacityProperty))
+ End If
End Sub
#Region "高度改变动画"
@@ -291,7 +361,13 @@ Public Class MyCard
' 根据折叠状态旋转箭头图标
' 折叠时箭头指向右侧或向上(根据SwapLogoRight设置),展开时指向下方
- AniStart(AaRotateTransform(MainSwap, If(_IsSwapped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 250,, New AniEaseOutFluent(AniEasePower.ExtraStrong)), "MyCard Swap " & Uuid, True)
+' AniStart(AaRotateTransform(MainSwap, If(_IsSwapped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 250,, New AniEaseOutFluent(AniEasePower.ExtraStrong)), "MyCard Swap " & Uuid, True)
+ Dim aniRotate = New DoubleFromToAnimation
+ aniRotate.Name = "MyCard Swap " & Uuid
+ aniRotate.To = If(_IsSwapped, If(SwapLogoRight, 270, 0), 180)
+ aniRotate.Duration = TimeSpan.FromMilliseconds(250)
+ aniRotate.Easing = new QuinticEaseOut()
+ aniRotate.RunFireAndForget(New WpfAnimatable(MainSwap.RenderTransform, RotateTransform.AngleProperty))
End Set
End Property
Private _IsSwapped As Boolean = False
@@ -375,4 +451,42 @@ Partial Public Module ModAnimation
If CallBack IsNot Nothing Then CallBack(Control)
End If
End Sub
-End Module
\ No newline at end of file
+End Module
+
+'''
+''' 基于初始速度的流畅缓出动画。
+'''
+Public Class FluentEaseOutWithInitial
+ Inherits Easing
+
+ ' (初速度 / 平均速度) – 1
+ Private ReadOnly _alpha As Double
+
+ '''
+ ''' 初始化缓动类。
+ '''
+ ''' 初速度,px/s。
+ ''' 总时长,s。
+ ''' 总路程,px。
+ Public Sub New(initialPixelPerSecond As Double, totalSecond As Double, totalDistance As Double)
+ ' 防止除以零
+ If Math.Abs(totalDistance - 0) < 1e-6 Then
+ _alpha = 0
+ Else
+ ' 归一化初速度
+ Dim v0Norm As Double = initialPixelPerSecond * totalSecond / totalDistance
+ _alpha = v0Norm - 1.0
+ End If
+
+ ' 初速度小于平均速度时,退化为线性
+ If _alpha < 0 Then _alpha = 0
+ End Sub
+
+ Protected Overrides Function EaseCore(progress As Double) As Double
+ ' 如果 alpha 为 0,退化为线性 (y = x)
+ If Math.Abs(_alpha - 0) < 1e-6 Then Return progress
+
+ Return (_alpha + 1) * progress / (1 + _alpha * progress)
+ End Function
+
+End Class
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Controls/MyComboBox.vb b/Plain Craft Launcher 2/Controls/MyComboBox.vb
index ff1bf2150..adaaa715a 100644
--- a/Plain Craft Launcher 2/Controls/MyComboBox.vb
+++ b/Plain Craft Launcher 2/Controls/MyComboBox.vb
@@ -1,4 +1,9 @@
-Public Class MyComboBox
+Imports PCL.Core.UI
+Imports PCL.Core.UI.Animation
+Imports PCL.Core.UI.Animation.Animatable
+Imports PCL.Core.UI.Animation.Core
+
+Public Class MyComboBox
Inherits ComboBox
Public Event TextChanged(sender As Object, e As TextChangedEventArgs)
@@ -100,13 +105,32 @@
'触发颜色动画
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
'有动画
- AniStart({
- AaColor(Me, ForegroundProperty, ForeColorName, Time),
- AaColor(Me, BackgroundProperty, BackColorName, Time)
- }, "MyComboBox Color " & Uuid)
+' AniStart({
+' AaColor(Me, ForegroundProperty, ForeColorName, Time),
+' AaColor(Me, BackgroundProperty, BackColorName, Time)
+' }, "MyComboBox Color " & Uuid)
+ Dim animation = New ParallelAnimationGroup
+ animation.Name = "MyComboBox Color " & Uuid
+
+ Dim aniColorFore As New NColorFromToAnimation
+ aniColorFore.To = New NColor(ForeColorName)
+ aniColorFore.Duration = TimeSpan.FromMilliseconds(Time)
+ aniColorFore.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniColorFore.SetValue(AnimationExtensions.TargetPropertyProperty, ForegroundProperty)
+ animation.Children.Add(aniColorFore)
+
+ Dim aniColorBack As New NColorFromToAnimation
+ aniColorBack.To = New NColor(BackColorName)
+ aniColorBack.Duration = TimeSpan.FromMilliseconds(Time)
+ aniColorBack.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniColorBack.SetValue(AnimationExtensions.TargetPropertyProperty, BackgroundProperty)
+ animation.Children.Add(aniColorBack)
+
+ animation.RunFireAndForget(EmptyAnimatable.Instance)
Else
'无动画
- AniStop("MyComboBox Color " & Uuid)
+' AniStop("MyComboBox Color " & Uuid)
+ AnimationService.CancelAnimationByName("MyComboBox Color " & Uuid)
SetResourceReference(ForegroundProperty, ForeColorName)
SetResourceReference(BackgroundProperty, BackColorName)
End If
diff --git a/Plain Craft Launcher 2/Controls/MyComboBoxItem.vb b/Plain Craft Launcher 2/Controls/MyComboBoxItem.vb
index 5e431d5f5..12da2c551 100644
--- a/Plain Craft Launcher 2/Controls/MyComboBoxItem.vb
+++ b/Plain Craft Launcher 2/Controls/MyComboBoxItem.vb
@@ -1,4 +1,9 @@
-Public Class MyComboBoxItem
+Imports PCL.Core.UI
+Imports PCL.Core.UI.Animation
+Imports PCL.Core.UI.Animation.Animatable
+Imports PCL.Core.UI.Animation.Core
+
+Public Class MyComboBoxItem
Inherits ComboBoxItem
'基础
@@ -41,13 +46,32 @@
'触发颜色动画
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
'有动画
- AniStart({
- AaColor(Me, BackgroundProperty, BackColorName, Time),
- AaOpacity(Me, FontOpacity - Opacity, Time)
- }, "ComboBoxItem Color " & Uuid)
+' AniStart({
+' AaColor(Me, BackgroundProperty, BackColorName, Time),
+' AaOpacity(Me, FontOpacity - Opacity, Time)
+' }, "ComboBoxItem Color " & Uuid)
+ Dim animation = New ParallelAnimationGroup
+ animation.Name = "ComboBoxItem Color " & Uuid
+
+ Dim aniColor = New NColorFromToAnimation
+ aniColor.To = New NColor(BackColorName)
+ aniColor.Duration = TimeSpan.FromMilliseconds(Time)
+ aniColor.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniColor.SetValue(AnimationExtensions.TargetPropertyProperty, BackgroundProperty)
+ animation.Children.Add(aniColor)
+
+ Dim aniOpacity = New DoubleFromToAnimation
+ aniOpacity.To = FontOpacity
+ aniOpacity.Duration = TimeSpan.FromMilliseconds(Time)
+ aniOpacity.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniOpacity.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ animation.Children.Add(aniOpacity)
+
+ animation.RunFireAndForget(EmptyAnimatable.Instance)
Else
'无动画
- AniStop("ComboBoxItem Color " & Uuid)
+' AniStop("ComboBoxItem Color " & Uuid)
+ AnimationService.CancelAnimationByName("ComboBoxItem Color " & Uuid)
SetResourceReference(BackgroundProperty, BackColorName)
Opacity = FontOpacity
End If
diff --git a/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb
index 0bb1b3a35..01f5d0d57 100644
--- a/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb
+++ b/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb
@@ -1,4 +1,10 @@
-Public Class MyExtraButton
+Imports PCL.Core.UI
+Imports PCL.Core.UI.Animation
+Imports PCL.Core.UI.Animation.Animatable
+Imports PCL.Core.UI.Animation.Core
+Imports PCL.Core.UI.Animation.Easings
+
+Public Class MyExtraButton
'声明
Public Event Click(sender As Object, e As MouseButtonEventArgs) '自定义事件
@@ -53,25 +59,63 @@
Set(value As Boolean)
If _Show = value Then Return
_Show = value
- RunInUi(Sub()
- If value Then
- '有了
- Visibility = Visibility.Visible
- AniStart({
- AaScaleTransform(Me, 0.3 - CType(RenderTransform, ScaleTransform).ScaleX, 500, 60, New AniEaseOutFluent(AniEasePower.Weak)),
- AaScaleTransform(Me, 0.7, 500, 60, New AniEaseOutBack(AniEasePower.Weak)),
- AaHeight(Me, 50 - Height, 200,, New AniEaseOutFluent(AniEasePower.Weak))
- }, "MyExtraButton MainScale " & Uuid)
- Else
- '没了
- AniStart({
- AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak)),
- AaHeight(Me, -Height, 400, 100, New AniEaseOutFluent()),
- AaCode(Sub() Visibility = Visibility.Collapsed,, True)
- }, "MyExtraButton MainScale " & Uuid)
- End If
- IsHitTestVisible = value '防止缩放动画中依然可以点进去
- End Sub)
+ AnimationService.CancelAnimationByName("MyExtraButton Scale " & Uuid)
+' RunInUi(Sub()
+' If value Then
+' '有了
+' Visibility = Visibility.Visible
+' AniStart({
+' AaScaleTransform(Me, 0.3 - CType(RenderTransform, ScaleTransform).ScaleX, 500, 60, New AniEaseOutFluent(AniEasePower.Weak)),
+' AaScaleTransform(Me, 0.7, 500, 60, New AniEaseOutBack(AniEasePower.Weak)),
+' AaHeight(Me, 50 - Height, 200,, New AniEaseOutFluent(AniEasePower.Weak))
+' }, "MyExtraButton MainScale " & Uuid)
+' Else
+' '没了
+' AniStart({
+' AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak)),
+' AaHeight(Me, -Height, 400, 100, New AniEaseOutFluent()),
+' AaCode(Sub() Visibility = Visibility.Collapsed,, True)
+' }, "MyExtraButton MainScale " & Uuid)
+' End If
+' IsHitTestVisible = value '防止缩放动画中依然可以点进去
+' End Sub)
+ AnimationService.UIAccessProvider.Invoke(Sub()
+ If value Then
+ '有了
+ Visibility = Visibility.Visible
+ Height = 50
+
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.Name = "MyExtraButton MainScale " & Uuid
+ aniScale.To = New NScaleTransform(1, 1, 0.5, 0.5)
+ aniScale.Easing = New CompositeEasing((QuadEaseOut.Shared, TimeSpan.FromMilliseconds(500), 0.3),
+ (New BackEaseWithPowerOut(EasePower.Weak),TimeSpan.FromMilliseconds(500), 0.7))
+ aniScale.Duration = TimeSpan.FromMilliseconds(500)
+ aniScale.Delay = TimeSpan.FromMilliseconds(60)
+
+ aniScale.RunFireAndForget(New WpfAnimatable(Me, RenderTransformProperty))
+ Else
+ Dim animation = New SequentialAnimationGroup
+ animation.Name = "MyExtraButton MainScale " & Uuid
+
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.To = New NScaleTransform(0, 0, 0.5, 0.5)
+ aniScale.Easing = QuadEaseIn.Shared
+ aniScale.Duration = TimeSpan.FromMilliseconds(100)
+ aniScale.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniScale.SetValue(AnimationExtensions.TargetPropertyProperty, RenderTransformProperty)
+ animation.Children.Add(aniScale)
+
+ Dim aniCode = New ActionAnimation(Sub()
+ Height = 0
+ Visibility = Visibility.Collapsed
+ End Sub)
+ animation.Children.Add(aniCode)
+
+ animation.RunFireAndForget(EmptyAnimatable.Instance)
+ End If
+ IsHitTestVisible = value '防止缩放动画中依然可以点进去
+ End Sub)
End Set
End Property
Public Delegate Function ShowCheckDelegate() As Boolean
@@ -112,10 +156,16 @@
Private IsRightMouseHeld As Boolean = False
Private Sub Button_LeftMouseDown(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonDown
If Not IsLeftMouseHeld AndAlso Not IsRightMouseHeld Then
- AniStart({
- AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
- AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
- }, "MyExtraButton Scale " & Uuid)
+' AniStart({
+' AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
+' AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
+' }, "MyExtraButton Scale " & Uuid)
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.Name = "MyExtraButton Scale " & Uuid
+ aniScale.To = New NScaleTransform(0.8, 0.8, 0.5, 0.5)
+ aniScale.Easing = QuinticEaseOut.Shared
+ aniScale.Duration = TimeSpan.FromMilliseconds(800)
+ aniScale.RunFireAndForget(New WpfAnimatable(Me, RenderTransformProperty))
End If
IsLeftMouseHeld = True
Focus()
@@ -123,19 +173,31 @@
Private Sub Button_RightMouseDown(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseRightButtonDown
If Not CanRightClick Then Return
If Not IsLeftMouseHeld AndAlso Not IsRightMouseHeld Then
- AniStart({
- AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
- AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
- }, "MyExtraButton Scale " & Uuid)
+' AniStart({
+' AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
+' AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
+' }, "MyExtraButton Scale " & Uuid)
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.Name = "MyExtraButton Scale " & Uuid
+ aniScale.To = New NScaleTransform(0.8, 0.8, 0.5, 0.5)
+ aniScale.Easing = QuinticEaseOut.Shared
+ aniScale.Duration = TimeSpan.FromMilliseconds(800)
+ aniScale.RunFireAndForget(New WpfAnimatable(Me, RenderTransformProperty))
End If
IsRightMouseHeld = True
Focus()
End Sub
Private Sub Button_LeftMouseUp() Handles PanClick.MouseLeftButtonUp
If Not IsRightMouseHeld Then
- AniStart({
- AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
- }, "MyExtraButton Scale " & Uuid)
+' AniStart({
+' AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
+' }, "MyExtraButton Scale " & Uuid)
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.Name = "MyExtraButton Scale " & Uuid
+ aniScale.To = New NScaleTransform(1, 1, 0.5, 0.5)
+ aniScale.Easing = New BackEaseWithPowerOut(EasePower.Middle)
+ aniScale.Duration = TimeSpan.FromMilliseconds(300)
+ aniScale.RunFireAndForget(New WpfAnimatable(Me, RenderTransformProperty))
End If
IsLeftMouseHeld = False
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
@@ -143,9 +205,15 @@
Private Sub Button_RightMouseUp() Handles PanClick.MouseRightButtonUp
If Not CanRightClick Then Return
If Not IsLeftMouseHeld Then
- AniStart({
- AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
- }, "MyExtraButton Scale " & Uuid)
+' AniStart({
+' AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
+' }, "MyExtraButton Scale " & Uuid)
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.Name = "MyExtraButton Scale " & Uuid
+ aniScale.To = New NScaleTransform(1, 1, 0.5, 0.5)
+ aniScale.Easing = New BackEaseWithPowerOut(EasePower.Middle)
+ aniScale.Duration = TimeSpan.FromMilliseconds(300)
+ aniScale.RunFireAndForget(New WpfAnimatable(Me, RenderTransformProperty))
End If
IsRightMouseHeld = False
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
@@ -153,9 +221,15 @@
Private Sub Button_MouseLeave() Handles PanClick.MouseLeave
IsLeftMouseHeld = False
IsRightMouseHeld = False
- AniStart({
- AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 500,, New AniEaseOutFluent)
- }, "MyExtraButton Scale " & Uuid)
+' AniStart({
+' AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 500,, New AniEaseOutFluent)
+' }, "MyExtraButton Scale " & Uuid)
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.Name = "MyExtraButton Scale " & Uuid
+ aniScale.To = New NScaleTransform(1, 1, 0.5, 0.5)
+ aniScale.Easing = CubicEaseOut.Shared
+ aniScale.Duration = TimeSpan.FromMilliseconds(500)
+ aniScale.RunFireAndForget(New WpfAnimatable(Me, RenderTransformProperty))
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
End Sub
@@ -167,20 +241,32 @@
Try
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
+ Dim aniColor = New NColorFromToAnimation
+ aniColor.Name = "MyExtraButton Color " & Uuid
If Not IsEnabled Then
'禁用
- AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrushGray4", AnimationColorIn), "MyExtraButton Color " & Uuid)
+' AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrushGray4", AnimationColorIn), "MyExtraButton Color " & Uuid)
+ aniColor.To = New NColor("ColorBrushGray4")
+ aniColor.Duration = TimeSpan.FromMilliseconds(AnimationColorIn)
+ aniColor.RunFireAndForget(New WpfAnimatable(PanColor, BackgroundProperty))
ElseIf IsMouseOver Then
'指向
- AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush4", AnimationColorIn), "MyExtraButton Color " & Uuid)
+' AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush4", AnimationColorIn), "MyExtraButton Color " & Uuid)
+ aniColor.To = New NColor("ColorBrush4")
+ aniColor.Duration = TimeSpan.FromMilliseconds(AnimationColorIn)
+ aniColor.RunFireAndForget(New WpfAnimatable(PanColor, BackgroundProperty))
Else
'普通
- AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush3", AnimationColorOut), "MyExtraButton Color " & Uuid)
+' AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush3", AnimationColorOut), "MyExtraButton Color " & Uuid)
+ aniColor.To = New NColor("ColorBrush3")
+ aniColor.Duration = TimeSpan.FromMilliseconds(AnimationColorOut)
+ aniColor.RunFireAndForget(New WpfAnimatable(PanColor, BackgroundProperty))
End If
Else
- AniStop("MyExtraButton Color " & Uuid)
+' AniStop("MyExtraButton Color " & Uuid)
+ AnimationService.CancelAnimationByName("MyExtraButton Color " & Uuid)
If Not IsEnabled Then
PanColor.SetResourceReference(BackgroundProperty, "ColorBrushGray4")
ElseIf IsMouseOver Then
@@ -199,16 +285,48 @@
''' 发出一圈波浪效果提示。
'''
Public Sub Ribble()
- RunInUi(Sub()
- Dim Shape As New Border With {.CornerRadius = New CornerRadius(1000), .BorderThickness = New Thickness(0.001), .Opacity = 0.5, .RenderTransformOrigin = New Point(0.5, 0.5), .RenderTransform = New ScaleTransform()}
- Shape.SetResourceReference(Border.BackgroundProperty, "ColorBrush5")
- PanScale.Children.Insert(0, Shape)
- AniStart({
- AaScaleTransform(Shape, 13, 1000, Ease:=New AniEaseInoutFluent(AniEasePower.Strong, 0.3)),
- AaOpacity(Shape, -Shape.Opacity, 1000),
- AaCode(Sub() PanScale.Children.Remove(Shape), After:=True)
- }, "ExtraButton Ribble " & GetUuid())
- End Sub)
+' RunInUi(Sub()
+' Dim Shape As New Border With {.CornerRadius = New CornerRadius(1000), .BorderThickness = New Thickness(0.001), .Opacity = 0.5, .RenderTransformOrigin = New Point(0.5, 0.5), .RenderTransform = New ScaleTransform()}
+' Shape.SetResourceReference(Border.BackgroundProperty, "ColorBrush5")
+' PanScale.Children.Insert(0, Shape)
+' AniStart({
+' AaScaleTransform(Shape, 13, 1000, Ease:=New AniEaseInoutFluent(AniEasePower.Strong, 0.3)),
+' AaOpacity(Shape, -Shape.Opacity, 1000),
+' AaCode(Sub() PanScale.Children.Remove(Shape), After:=True)
+' }, "ExtraButton Ribble " & GetUuid())
+' End Sub)
+ AnimationService.UIAccessProvider.Invoke(Sub()
+ Dim shape As New Border With {.CornerRadius = New CornerRadius(1000), .BorderThickness = New Thickness(0.001), .Opacity = 0.5, .RenderTransformOrigin = New Point(0.5, 0.5), .RenderTransform = New ScaleTransform()}
+ shape.SetResourceReference(Border.BackgroundProperty, "ColorBrush5")
+ PanScale.Children.Insert(0, shape)
+
+ Dim aniGroup1 = New SequentialAnimationGroup
+ aniGroup1.Name = "ExtraButton Ribble " & GetUuid()
+
+ Dim aniGroup2 = New ParallelAnimationGroup
+
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.To = New NScaleTransform(13, 13, 0.5, 0.5)
+ aniScale.Duration = TimeSpan.FromMilliseconds(1000)
+ aniScale.Easing = New CombinedEasing(QuarticEaseIn.Shared, QuarticEaseOut.Shared, 0.3)
+ aniScale.SetValue(AnimationExtensions.TargetProperty, shape)
+ aniScale.SetValue(AnimationExtensions.TargetPropertyProperty, RenderTransformProperty)
+ aniGroup2.Children.Add(aniScale)
+
+ Dim aniOpacity = New DoubleFromToAnimation
+ aniOpacity.To = 0
+ aniOpacity.Duration = TimeSpan.FromMilliseconds(1000)
+ aniOpacity.SetValue(AnimationExtensions.TargetProperty, shape)
+ aniOpacity.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ aniGroup2.Children.Add(aniOpacity)
+
+ aniGroup1.Children.Add(aniGroup2)
+
+ Dim aniCode = New ActionAnimation(Sub()PanScale.Children.Remove(shape))
+ aniGroup1.Children.Add(aniCode)
+
+ aniGroup1.RunFireAndForget(EmptyAnimatable.Instance)
+ End Sub)
End Sub
End Class
diff --git a/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb
index 9784cd08a..6f3fd29d5 100644
--- a/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb
+++ b/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb
@@ -1,4 +1,9 @@
Imports System.Windows.Markup
+Imports PCL.Core.UI
+Imports PCL.Core.UI.Animation
+Imports PCL.Core.UI.Animation.Animatable
+Imports PCL.Core.UI.Animation.Core
+Imports PCL.Core.UI.Animation.Easings
Public Class MyExtraTextButton
@@ -57,25 +62,76 @@ Public Class MyExtraTextButton
Set(value As Boolean)
If _Show = value Then Return
_Show = value
- RunInUi(
- Sub()
+ AnimationService.CancelAnimationByName("MyExtraTextButton Scale " & Uuid)
+' RunInUi(
+' Sub()
+' If value Then
+' '有了
+' Opacity = 0
+' AniStart({
+' AaOpacity(Me, 1 - Opacity, 80, 50),
+' AaScaleTransform(Me, 0.15 - CType(RenderTransform, ScaleTransform).ScaleX, 400, 50, New AniEaseOutBack),
+' AaScaleTransform(Me, 0.85, 160, 50, New AniEaseOutFluent(AniEasePower.Middle))
+' }, "MyExtraTextButton MainScale " & Uuid)
+' Else
+' '没了
+' AniStart({
+' AaOpacity(Me, -Opacity, 50, 50),
+' AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak))
+' }, "MyExtraTextButton MainScale " & Uuid)
+' End If
+' IsHitTestVisible = value '防止缩放动画中依然可以点进去
+' End Sub)
+ AnimationService.UIAccessProvider.Invoke(Sub()
If value Then
'有了
Opacity = 0
- AniStart({
- AaOpacity(Me, 1 - Opacity, 80, 50),
- AaScaleTransform(Me, 0.15 - CType(RenderTransform, ScaleTransform).ScaleX, 400, 50, New AniEaseOutBack),
- AaScaleTransform(Me, 0.85, 160, 50, New AniEaseOutFluent(AniEasePower.Middle))
- }, "MyExtraTextButton MainScale " & Uuid)
- Else
+ Dim animation = New ParallelAnimationGroup
+ animation.Name = "MyExtraTextButton MainScale " & Uuid
+
+ Dim aniOpacity = New DoubleFromToAnimation
+ aniOpacity.To = 1
+ aniOpacity.Duration = TimeSpan.FromMilliseconds(80)
+ aniOpacity.Delay = TimeSpan.FromMilliseconds(50)
+ aniOpacity.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniOpacity.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ animation.Children.Add(aniOpacity)
+
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.To = New NScaleTransform(1, 1, 0.5, 0.5)
+ aniScale.Easing = New CompositeEasing((New BackEaseWithPowerOut(), TimeSpan.FromMilliseconds(400), 0.15),
+ (CubicEaseOut.Shared, TimeSpan.FromMilliseconds(160), 0.85))
+ aniScale.Duration = TimeSpan.FromMilliseconds(400)
+ aniScale.Delay = TimeSpan.FromMilliseconds(60)
+ aniScale.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniScale.SetValue(AnimationExtensions.TargetPropertyProperty, RenderTransformProperty)
+ animation.Children.Add(aniScale)
+
+ animation.RunFireAndForget(EmptyAnimatable.Instance)
+ Else
'没了
- AniStart({
- AaOpacity(Me, -Opacity, 50, 50),
- AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak))
- }, "MyExtraTextButton MainScale " & Uuid)
+ Dim animation = New ParallelAnimationGroup
+ animation.Name = "MyExtraTextButton MainScale " & Uuid
+
+ Dim aniOpacity = New DoubleFromToAnimation
+ aniOpacity.To = 0
+ aniOpacity.Duration = TimeSpan.FromMilliseconds(50)
+ aniOpacity.Delay = TimeSpan.FromMilliseconds(50)
+ aniOpacity.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniOpacity.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ animation.Children.Add(aniOpacity)
+
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.To = New NScaleTransform(0, 0, 0.5, 0.5)
+ aniScale.Easing = QuadEaseIn.Shared
+ aniScale.Duration = TimeSpan.FromMilliseconds(100)
+ aniScale.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniScale.SetValue(AnimationExtensions.TargetPropertyProperty, RenderTransformProperty)
+ animation.Children.Add(aniScale)
+
+ animation.RunFireAndForget(EmptyAnimatable.Instance)
End If
- IsHitTestVisible = value '防止缩放动画中依然可以点进去
- End Sub)
+ End Sub)
End Set
End Property
@@ -93,34 +149,58 @@ Public Class MyExtraTextButton
Private IsLeftMouseHeld As Boolean = False
Private Sub Button_LeftMouseDown(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonDown
If Not IsLeftMouseHeld Then
- AniStart({
- AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
- AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
- }, "MyExtraTextButton Scale " & Uuid)
+' AniStart({
+' AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
+' AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
+' }, "MyExtraTextButton Scale " & Uuid)
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.Name = "MyExtraTextButton Scale " & Uuid
+ aniScale.To = New NScaleTransform(0.8, 0.8, 0.5, 0.5)
+ aniScale.Easing = QuinticEaseOut.Shared
+ aniScale.Duration = TimeSpan.FromMilliseconds(800)
+ aniScale.RunFireAndForget(New WpfAnimatable(Me, RenderTransformProperty))
End If
IsLeftMouseHeld = True
Focus()
End Sub
Private Sub Button_LeftMouseUp() Handles PanClick.MouseLeftButtonUp
- AniStart({
- AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
- }, "MyExtraTextButton Scale " & Uuid)
+' AniStart({
+' AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
+' }, "MyExtraTextButton Scale " & Uuid)
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.Name = "MyExtraTextButton Scale " & Uuid
+ aniScale.To = New NScaleTransform(1, 1, 0.5, 0.5)
+ aniScale.Easing = New BackEaseWithPowerOut()
+ aniScale.Duration = TimeSpan.FromMilliseconds(300)
+ aniScale.RunFireAndForget(New WpfAnimatable(Me, RenderTransformProperty))
IsLeftMouseHeld = False
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
End Sub
Private Sub Button_RightMouseUp() Handles PanClick.MouseRightButtonUp
If Not IsLeftMouseHeld Then
- AniStart({
- AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
- }, "MyExtraTextButton Scale " & Uuid)
+' AniStart({
+' AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
+' }, "MyExtraTextButton Scale " & Uuid)
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.Name = "MyExtraTextButton Scale " & Uuid
+ aniScale.To = New NScaleTransform(1, 1, 0.5, 0.5)
+ aniScale.Easing = New BackEaseWithPowerOut()
+ aniScale.Duration = TimeSpan.FromMilliseconds(300)
+ aniScale.RunFireAndForget(New WpfAnimatable(Me, RenderTransformProperty))
End If
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
End Sub
Private Sub Button_MouseLeave() Handles PanClick.MouseLeave
IsLeftMouseHeld = False
- AniStart({
- AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 500,, New AniEaseOutFluent)
- }, "MyExtraTextButton Scale " & Uuid)
+' AniStart({
+' AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 500,, New AniEaseOutFluent)
+' }, "MyExtraTextButton Scale " & Uuid)
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.Name = "MyExtraTextButton Scale " & Uuid
+ aniScale.To = New NScaleTransform(1, 1, 0.5, 0.5)
+ aniScale.Easing = QuadEaseOut.Shared
+ aniScale.Duration = TimeSpan.FromMilliseconds(500)
+ aniScale.RunFireAndForget(New WpfAnimatable(Me, RenderTransformProperty))
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
End Sub
@@ -132,20 +212,32 @@ Public Class MyExtraTextButton
Try
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
+ Dim aniColor = New NColorFromToAnimation
+ aniColor.Name = "MyExtraTextButton Color " & Uuid
If Not IsEnabled Then
'禁用
- AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrushGray4", AnimationColorIn), "MyExtraTextButton Color " & Uuid)
+' AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrushGray4", AnimationColorIn), "MyExtraTextButton Color " & Uuid)
+ aniColor.To = New NColor("ColorBrushGray4")
+ aniColor.Duration = TimeSpan.FromMilliseconds(AnimationColorIn)
+ aniColor.RunFireAndForget(New WpfAnimatable(PanColor, BackgroundProperty))
ElseIf IsMouseOver Then
'指向
- AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush4", AnimationColorIn), "MyExtraTextButton Color " & Uuid)
+' AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush4", AnimationColorIn), "MyExtraTextButton Color " & Uuid)
+ aniColor.To = New NColor("ColorBrush4")
+ aniColor.Duration = TimeSpan.FromMilliseconds(AnimationColorIn)
+ aniColor.RunFireAndForget(New WpfAnimatable(PanColor, BackgroundProperty))
Else
'普通
- AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush3", AnimationColorOut), "MyExtraTextButton Color " & Uuid)
+' AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush3", AnimationColorOut), "MyExtraTextButton Color " & Uuid)
+ aniColor.To = New NColor("ColorBrush3")
+ aniColor.Duration = TimeSpan.FromMilliseconds(AnimationColorOut)
+ aniColor.RunFireAndForget(New WpfAnimatable(PanColor, BackgroundProperty))
End If
Else
- AniStop("MyExtraTextButton Color " & Uuid)
+' AniStop("MyExtraTextButton Color " & Uuid)
+ AnimationService.CancelAnimationByName("MyExtraTextButton Color " & Uuid)
If Not IsEnabled Then
PanColor.SetResourceReference(BackgroundProperty, "ColorBrushGray4")
ElseIf IsMouseOver Then
diff --git a/Plain Craft Launcher 2/Controls/MyHint.xaml.vb b/Plain Craft Launcher 2/Controls/MyHint.xaml.vb
index 450c2e2e0..a71cadd98 100644
--- a/Plain Craft Launcher 2/Controls/MyHint.xaml.vb
+++ b/Plain Craft Launcher 2/Controls/MyHint.xaml.vb
@@ -1,6 +1,11 @@
Imports System.Windows.Markup
Imports PCL.Core.App
Imports PCL.Core.UI.Theme
+Imports PCL.Core.UI
+Imports PCL.Core.UI.Animation
+Imports PCL.Core.UI.Animation.Animatable
+Imports PCL.Core.UI.Animation.Core
+Imports PCL.Core.UI.Animation.Easings
Public Class MyHint
@@ -103,7 +108,46 @@ Public Class MyHint
End Sub
Private Sub BtnClose_Click(sender As Object, e As EventArgs) Handles BtnClose.Click
Setup.Set(RelativeSetup, True)
- AniDispose(Me, False)
+' AniDispose(Me, False)
+ If Not IsHitTestVisible Then Return
+ IsHitTestVisible = False
+ Dim aniGroup1 = New SequentialAnimationGroup
+ aniGroup1.Name = "MyCard Dispose " & Uuid
+
+ Dim aniGroup2 = New ParallelAnimationGroup
+
+ Dim aniScale = New NScaleTransformFromToAnimation
+ aniScale.ValueType = AnimationValueType.Relative
+ aniScale.To = New NScaleTransform(-0.08, -0.08, 0.5, 0.5)
+ aniScale.Easing = QuadEaseIn.Shared
+ aniScale.Duration = TimeSpan.FromMilliseconds(200)
+ aniScale.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniScale.SetValue(AnimationExtensions.TargetPropertyProperty, RenderTransformProperty)
+ aniGroup2.Children.Add(aniScale)
+
+ Dim aniOpacity = New DoubleFromToAnimation
+ aniOpacity.To = 0
+ aniOpacity.Easing = QuadEaseOut.Shared
+ aniOpacity.Duration = TimeSpan.FromMilliseconds(200)
+ aniOpacity.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniOpacity.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ aniGroup2.Children.Add(aniOpacity)
+
+ Dim aniHeight = New DoubleFromToAnimation
+ aniHeight.To = 0
+ aniHeight.Easing = QuadEaseOut.Shared
+ aniHeight.Duration = TimeSpan.FromMilliseconds(100)
+ aniHeight.Delay = TimeSpan.FromMilliseconds(150)
+ aniHeight.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniHeight.SetValue(AnimationExtensions.TargetPropertyProperty, HeightProperty)
+ aniGroup2.Children.Add(aniHeight)
+
+ aniGroup1.Children.Add(aniGroup1)
+
+ Dim aniRemove = New ActionAnimation(Sub() Visibility = Visibility.Collapsed)
+ aniGroup1.Children.Add(aniRemove)
+
+ aniGroup1.RunFireAndForget(EmptyAnimatable.Instance)
End Sub
'触发点击事件
@@ -207,23 +251,23 @@ Public Class MyHint
' End Select
'End Sub
End Class
-Partial Public Module ModAnimation
- Public Sub AniDispose(Control As MyHint, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing)
- If Not Control.IsHitTestVisible Then Return
- Control.IsHitTestVisible = False
- AniStart({
- AaScaleTransform(Control, -0.08, 200,, New AniEaseInFluent),
- AaOpacity(Control, -1, 200,, New AniEaseOutFluent),
- AaHeight(Control, -Control.ActualHeight, 150, 100, New AniEaseOutFluent),
- AaCode(
- Sub()
- If RemoveFromChildren Then
- CType(Control.Parent, Object).Children.Remove(Control)
- Else
- Control.Visibility = Visibility.Collapsed
- End If
- If CallBack IsNot Nothing Then CallBack(Control)
- End Sub,, True)
- }, "MyCard Dispose " & Control.Uuid)
- End Sub
-End Module
+'Partial Public Module ModAnimation
+' Public Sub AniDispose(Control As MyHint, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing)
+' If Not Control.IsHitTestVisible Then Return
+' Control.IsHitTestVisible = False
+' AniStart({
+' AaScaleTransform(Control, -0.08, 200,, New AniEaseInFluent),
+' AaOpacity(Control, -1, 200,, New AniEaseOutFluent),
+' AaHeight(Control, -Control.ActualHeight, 150, 100, New AniEaseOutFluent),
+' AaCode(
+' Sub()
+' If RemoveFromChildren Then
+' CType(Control.Parent, Object).Children.Remove(Control)
+' Else
+' Control.Visibility = Visibility.Collapsed
+' End If
+' If CallBack IsNot Nothing Then CallBack(Control)
+' End Sub,, True)
+' }, "MyCard Dispose " & Control.Uuid)
+' End Sub
+'End Module
diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb
index 556b746f9..6e82356a4 100644
--- a/Plain Craft Launcher 2/FormMain.xaml.vb
+++ b/Plain Craft Launcher 2/FormMain.xaml.vb
@@ -6,6 +6,10 @@ Imports PCL.Core.App
Imports PCL.Core.App.IoC
Imports PCL.Core.Logging
Imports PCL.Core.UI
+Imports PCL.Core.UI.Animation
+Imports PCL.Core.UI.Animation.Animatable
+Imports PCL.Core.UI.Animation.Core
+Imports PCL.Core.UI.Animation.Easings
Imports PCL.Core.UI.Theme
Imports PCL.Core.Utils
Imports PCL.Core.Utils.OS
@@ -171,18 +175,77 @@ Public Class FormMain
ShowWindowToTop()
Dim HwndSource As Interop.HwndSource = PresentationSource.FromVisual(Me)
HwndSource.AddHook(New Interop.HwndSourceHook(AddressOf WndProc))
- AniStart({
- AaCode(Sub() AniControlEnabled -= 1, 50),
- AaOpacity(Me, Setup.Get("UiLauncherTransparent") / 1000 + 0.4, 250, 100),
- AaDouble(Sub(i) TransformPos.Y += i, -TransformPos.Y, 600, 100, New AniEaseOutBack(AniEasePower.Weak)),
- AaDouble(Sub(i) TransformRotate.Angle += i, -TransformRotate.Angle, 500, 100, New AniEaseOutBack(AniEasePower.Weak)),
- AaCode(
- Sub()
- RenderTransform = Nothing
- IsWindowLoadFinished = True
- Log($"[System] DPI:{DPI},系统版本:{Environment.OSVersion.VersionString},PCL 位置:{ExePathWithName}")
- End Sub, , True)
- }, "Form Show")
+' AniStart({
+' AaCode(Sub() AniControlEnabled -= 1, 50),
+' AaOpacity(Me, Setup.Get("UiLauncherTransparent") / 1000 + 0.4, 250, 100),
+' AaDouble(Sub(i) TransformPos.Y += i, -TransformPos.Y, 600, 100, New AniEaseOutBack(AniEasePower.Weak)),
+' AaDouble(Sub(i) TransformRotate.Angle += i, -TransformRotate.Angle, 500, 100, New AniEaseOutBack(AniEasePower.Weak)),
+' AaCode(
+' Sub()
+' PanBack.RenderTransform = Nothing
+' IsWindowLoadFinished = True
+' Log($"[System] DPI:{DPI},系统版本:{Environment.OSVersion.VersionString},PCL 位置:{ExePathWithName}")
+' End Sub, , True)
+' }, "Form Show")
+
+' Await Task.Delay(50)
+' AniControlEnabled -= 1
+
+ Dim aniGroup1 = New SequentialAnimationGroup
+ aniGroup1.Name = "Form Show"
+
+ Dim aniGroup2 = New ParallelAnimationGroup
+
+ Dim aniEnabled = New ActionAnimation(Sub() AniControlEnabled -= 1)
+ aniEnabled.Delay = TimeSpan.FromMilliseconds(50)
+ aniGroup2.Children.Add(aniEnabled)
+
+ Dim aniOpacity = New DoubleFromToAnimation
+ aniOpacity.To = Math.Clamp(Setup.Get("UiLauncherTransparent") / 1000 + 0.4, 0.0, 1.0)
+ aniOpacity.Duration = TimeSpan.FromMilliseconds(250)
+ aniOpacity.Delay = TimeSpan.FromMilliseconds(100)
+ aniOpacity.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniOpacity.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ aniGroup2.Children.Add(aniOpacity)
+
+ Dim aniPos = New DoubleFromToAnimation
+ aniPos.To = 0
+ aniPos.Duration = TimeSpan.FromMilliseconds(600)
+ aniPos.Delay = TimeSpan.FromMilliseconds(100)
+ aniPos.Easing = New BackEaseWithPowerOut(EasePower.Weak)
+ aniPos.SetValue(AnimationExtensions.TargetProperty, TransformPos)
+ aniPos.SetValue(AnimationExtensions.TargetPropertyProperty, TranslateTransform.YProperty)
+ aniGroup2.Children.Add(aniPos)
+
+ Dim aniRotate = New DoubleFromToAnimation
+ aniRotate.To = 0
+ aniRotate.Duration = TimeSpan.FromMilliseconds(500)
+ aniRotate.Delay = TimeSpan.FromMilliseconds(100)
+ aniRotate.Easing = New BackEaseWithPowerOut(EasePower.Weak)
+ aniRotate.SetValue(AnimationExtensions.TargetProperty, TransformRotate)
+ aniRotate.SetValue(AnimationExtensions.TargetPropertyProperty, RotateTransform.AngleProperty)
+ aniGroup2.Children.Add(aniRotate)
+
+ aniGroup1.Children.Add(aniGroup2)
+
+' AddHandler animation.Completed, Sub(sender, args)
+' RunInUi(Sub()
+' PanBack.RenderTransform = Nothing
+' IsWindowLoadFinished = True
+' Log($"[System] DPI:{DPI},系统版本:{Environment.OSVersion.VersionString},PCL 位置:{ExePathWithName}")
+' Debug.WriteLine(Opacity)
+' End Sub)
+' End Sub
+
+ Dim aniCompleted = New ActionAnimation(Sub()
+ RenderTransform = Nothing
+ IsWindowLoadFinished = True
+ Log($"[System] DPI:{DPI},系统版本:{Environment.OSVersion.VersionString},PCL 位置:{ExePathWithName}")
+ End Sub)
+ aniGroup1.Children.Add(aniCompleted)
+
+ aniGroup1.RunFireAndForget(EmptyAnimatable.Instance)
+
'Timer 启动
AniStart()
TimerMainStart()
@@ -499,24 +562,82 @@ Public Class FormMain
Dim TransformScale As New ScaleTransform(1, 1)
TransformScale.CenterX = Width / 2
TransformScale.CenterY = Height / 2
- RenderTransform = New TransformGroup() With {.Children = New TransformCollection({TransformRotate, TransformPos, TransformScale})}
- AniStart({
- AaOpacity(Me, -Opacity, 140, 40, New AniEaseOutFluent(AniEasePower.Weak)),
- AaDouble(
- Sub(i)
- TransformScale.ScaleX += i
- TransformScale.ScaleY += i
- End Sub, 0.88 - TransformScale.ScaleX, 180),
- AaDouble(Sub(i) TransformPos.Y += i, 20 - TransformPos.Y, 180, 0, New AniEaseOutFluent(AniEasePower.Weak)),
- AaDouble(Sub(i) TransformRotate.Angle += i, 0.6 - TransformRotate.Angle, 180, 0, New AniEaseInoutFluent(AniEasePower.Weak)),
- AaCode(
- Sub()
+ PanBack.RenderTransform = New TransformGroup() With {.Children = New TransformCollection({TransformRotate, TransformPos, TransformScale})}
+' AniStart({
+' AaOpacity(Me, -Opacity, 140, 40, New AniEaseOutFluent(AniEasePower.Weak)),
+' AaDouble(
+' Sub(i)
+' TransformScale.ScaleX += i
+' TransformScale.ScaleY += i
+' End Sub, 0.88 - TransformScale.ScaleX, 180),
+' AaDouble(Sub(i) TransformPos.Y += i, 20 - TransformPos.Y, 180, 0, New AniEaseOutFluent(AniEasePower.Weak)),
+' AaDouble(Sub(i) TransformRotate.Angle += i, 0.6 - TransformRotate.Angle, 180, 0, New AniEaseInoutFluent(AniEasePower.Weak)),
+' AaCode(
+' Sub()
+' IsHitTestVisible = False
+' Top = -10000
+' ShowInTaskbar = False
+' End Sub, 210),
+' AaCode(Sub() EndProgramForce(force:=False), 230)
+' }, "Form Close")
+
+ Dim animation = new ParallelAnimationGroup
+ animation.Name = "Form Close"
+
+ Dim aniOpacity = New DoubleFromToAnimation
+ aniOpacity.To = 0
+ aniOpacity.Duration = TimeSpan.FromMilliseconds(140)
+ aniOpacity.Delay = TimeSpan.FromMilliseconds(40)
+ aniOpacity.Easing = QuadEaseOut.Shared
+ aniOpacity.SetValue(AnimationExtensions.TargetProperty, Me)
+ aniOpacity.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ animation.Children.Add(aniOpacity)
+
+ Dim aniScaleX = New DoubleFromToAnimation
+ aniScaleX.To = 0.88
+ aniScaleX.Duration = TimeSpan.FromMilliseconds(180)
+ aniScaleX.SetValue(AnimationExtensions.TargetProperty, TransformScale)
+ aniScaleX.SetValue(AnimationExtensions.TargetPropertyProperty, ScaleTransform.ScaleXProperty)
+ animation.Children.Add(aniScaleX)
+
+ Dim aniScaleY = New DoubleFromToAnimation
+ aniScaleY.To = 0.88
+ aniScaleY.Duration = TimeSpan.FromMilliseconds(180)
+ aniScaleY.SetValue(AnimationExtensions.TargetProperty, TransformScale)
+ aniScaleY.SetValue(AnimationExtensions.TargetPropertyProperty, ScaleTransform.ScaleYProperty)
+ animation.Children.Add(aniScaleY)
+
+ Dim aniPos = New DoubleFromToAnimation
+ aniPos.To = 20
+ aniPos.Duration = TimeSpan.FromMilliseconds(180)
+ aniPos.Easing = QuadEaseOut.Shared
+ aniPos.SetValue(AnimationExtensions.TargetProperty, TransformPos)
+ aniPos.SetValue(AnimationExtensions.TargetPropertyProperty, TranslateTransform.YProperty)
+ animation.Children.Add(aniPos)
+
+ Dim aniRotate = New DoubleFromToAnimation
+ aniRotate.To = 0.6
+ aniRotate.Duration = TimeSpan.FromMilliseconds(180)
+ aniRotate.Easing = QuadEaseInOut.Shared
+ aniRotate.SetValue(AnimationExtensions.TargetProperty, TransformRotate)
+ aniRotate.SetValue(AnimationExtensions.TargetPropertyProperty, RotateTransform.AngleProperty)
+ animation.Children.Add(aniRotate)
+
+ AddHandler animation.Completed, Sub(sender, args)
+ RunInUi(Async Sub()
+ Await Task.Delay(30)
+
IsHitTestVisible = False
Visibility = Visibility.Collapsed
ShowInTaskbar = False
- End Sub, 210),
- AaCode(Sub() EndProgramForce(force:=False, isUpdating:=isUpdating), 230)
- }, "Form Close")
+
+ Await Task.Delay(20)
+
+ EndProgramForce(force:=False, isUpdating:=isUpdating)
+ End Sub)
+ End Sub
+
+ animation.RunFireAndForget(EmptyAnimatable.Instance)
Else
EndProgramForce(force:=False, isUpdating:=isUpdating)
End If
@@ -1346,11 +1467,39 @@ Public Class FormMain
'即将切换到一个子页面
If PageStack.Any Then
'子页面 → 另一个子页面,更新
- AniStart({
- AaOpacity(LabTitleInner, -LabTitleInner.Opacity, 130),
- AaCode(Sub() LabTitleInner.Text = PageName,, True),
- AaOpacity(LabTitleInner, 1, 150, 30)
- }, "FrmMain Titlebar SubLayer")
+' AniStart({
+' AaOpacity(LabTitleInner, -LabTitleInner.Opacity, 130),
+' AaCode(Sub() LabTitleInner.Text = PageName,, True),
+' AaOpacity(LabTitleInner, 1, 150, 30)
+' }, "FrmMain Titlebar SubLayer")
+
+ Dim aniOut1 = New SequentialAnimationGroup
+ aniOut1.Name = "FrmMain Titlebar SubLayer"
+
+ Dim aniOpacityOut = New DoubleFromToAnimation
+ aniOpacityOut.To = 0
+ aniOpacityOut.Duration = TimeSpan.FromMilliseconds(130)
+ aniOpacityOut.SetValue(AnimationExtensions.TargetProperty, LabTitleInner)
+ aniOpacityOut.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ aniOut1.Children.Add(aniOpacityOut)
+
+ Dim aniOut2 = New ParallelAnimationGroup
+
+ Dim aniChangeText = New ActionAnimation(Sub() LabTitleInner.Text = PageName)
+ aniOut2.Children.Add(aniChangeText)
+
+ Dim aniOpacityIn = New DoubleFromToAnimation
+ aniOpacityIn.To = 1
+ aniOpacityIn.Delay = TimeSpan.FromMilliseconds(30)
+ aniOpacityIn.Duration = TimeSpan.FromMilliseconds(150)
+ aniOpacityIn.SetValue(AnimationExtensions.TargetProperty, LabTitleInner)
+ aniOpacityIn.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ aniOut2.Children.Add(aniOpacityIn)
+
+ aniOut1.Children.Add(aniOut2)
+
+ aniOut1.RunFireAndForget(EmptyAnimatable.Instance)
+
If PageStack.Contains(Stack) Then
'返回到更上层的子页面
Do While PageStack.Contains(Stack)
@@ -1366,13 +1515,61 @@ Public Class FormMain
PanTitleMain.IsHitTestVisible = False
PanTitleInner.IsHitTestVisible = True
PageNameRefresh(Stack)
- AniStart({
- AaOpacity(PanTitleMain, -PanTitleMain.Opacity, 150),
- AaX(PanTitleMain, 12 - PanTitleMain.Margin.Left, 150,, New AniEaseInFluent(AniEasePower.Weak)),
- AaOpacity(PanTitleInner, 1 - PanTitleInner.Opacity, 150, 200),
- AaX(PanTitleInner, -PanTitleInner.Margin.Left, 350, 200, New AniEaseOutBack),
- AaCode(Sub() PanTitleMain.Visibility = Visibility.Collapsed,, True)
- }, "FrmMain Titlebar FirstLayer")
+' AniStart({
+' AaOpacity(PanTitleMain, -PanTitleMain.Opacity, 150),
+' AaX(PanTitleMain, 12 - PanTitleMain.Margin.Left, 150,, New AniEaseInFluent(AniEasePower.Weak)),
+' AaOpacity(PanTitleInner, 1 - PanTitleInner.Opacity, 150, 200),
+' AaX(PanTitleInner, -PanTitleInner.Margin.Left, 350, 200, New AniEaseOutBack),
+' AaCode(Sub() PanTitleMain.Visibility = Visibility.Collapsed,, True)
+' }, "FrmMain Titlebar FirstLayer")
+
+ Dim aniIn1 = New SequentialAnimationGroup
+ aniIn1.Name = "FrmMain Titlebar FirstLayer"
+
+ Dim aniIn2 = New ParallelAnimationGroup
+
+ Dim aniTitleMainOpacity = New DoubleFromToAnimation
+ aniTitleMainOpacity.To = 0
+ aniTitleMainOpacity.Duration = TimeSpan.FromMilliseconds(150)
+ aniTitleMainOpacity.SetValue(AnimationExtensions.TargetProperty, PanTitleMain)
+ aniTitleMainOpacity.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ aniIn2.Children.Add(aniTitleMainOpacity)
+
+ Dim aniTitleMainMove = New ThicknessFromToAnimation
+ aniTitleMainMove.To = New Thickness(12, PanTitleMain.Margin.Top, PanTitleMain.Margin.Right,
+ PanTitleMain.Margin.Bottom)
+ aniTitleMainMove.Duration = TimeSpan.FromMilliseconds(150)
+ aniTitleMainMove.Easing = QuadEaseIn.Shared
+ aniTitleMainMove.SetValue(AnimationExtensions.TargetProperty, PanTitleMain)
+ aniTitleMainMove.SetValue(AnimationExtensions.TargetPropertyProperty, MarginProperty)
+ aniIn2.Children.Add(aniTitleMainMove)
+
+ aniIn1.Children.Add(aniIn2)
+
+ Dim aniTitleInnerOpacity = New DoubleFromToAnimation
+ aniTitleInnerOpacity.To = 1
+ aniTitleInnerOpacity.Delay = TimeSpan.FromMilliseconds(200)
+ aniTitleInnerOpacity.Duration = TimeSpan.FromMilliseconds(150)
+ aniTitleInnerOpacity.SetValue(AnimationExtensions.TargetProperty, PanTitleInner)
+ aniTitleInnerOpacity.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ aniIn2.Children.Add(aniTitleInnerOpacity)
+
+ Dim aniTitleInnerMove = New ThicknessFromToAnimation
+ aniTitleInnerMove.To = New Thickness(0, PanTitleInner.Margin.Top, PanTitleInner.Margin.Right,
+ PanTitleInner.Margin.Bottom)
+ aniTitleInnerMove.Delay = TimeSpan.FromMilliseconds(200)
+ aniTitleInnerMove.Duration = TimeSpan.FromMilliseconds(350)
+ aniTitleInnerMove.Easing = New BackEaseWithPowerOut(EasePower.Middle)
+ aniTitleInnerMove.SetValue(AnimationExtensions.TargetProperty, PanTitleInner)
+ aniTitleInnerMove.SetValue(AnimationExtensions.TargetPropertyProperty, MarginProperty)
+ aniIn2.Children.Add(aniTitleInnerMove)
+
+ Dim aniVisibilityChange =
+ New ActionAnimation(Sub() PanTitleMain.Visibility = Visibility.Collapsed)
+ aniIn1.Children.Add(aniVisibilityChange)
+
+ aniIn1.RunFireAndForget(EmptyAnimatable.Instance)
+
PageStack.Insert(0, PageCurrent)
End If
End If
@@ -1436,9 +1633,17 @@ Public Class FormMain
AniControlEnabled -= 1
End Try
End Sub
+
+ Dim _aniFrmMainLeftChange As SequentialAnimationGroup = New SequentialAnimationGroup
+ Dim _aniPageLeftPageChange As SequentialAnimationGroup = New SequentialAnimationGroup
+
Private Sub PageChangeAnim(TargetLeft As FrameworkElement, TargetRight As FrameworkElement)
- AniStop("FrmMain LeftChange")
- AniStop("PageLeft PageChange") '停止左边栏变更导致的右页面切换动画,防止它与本动画一起触发多次 PageOnEnter
+' AniStop("FrmMain LeftChange")
+' AniStop("PageLeft PageChange") '停止左边栏变更导致的右页面切换动画,防止它与本动画一起触发多次 PageOnEnter
+
+ _aniFrmMainLeftChange.CancelAndClear()
+ _aniPageLeftPageChange.CancelAndClear() '停止左边栏变更导致的右页面切换动画,防止它与本动画一起触发多次 PageOnEnter
+
AniControlEnabled += 1
'清除新页面关联性
If Not IsNothing(TargetLeft.Parent) Then TargetLeft.SetValue(ContentPresenter.ContentProperty, Nothing)
@@ -1450,43 +1655,95 @@ Public Class FormMain
CType(PanMainRight.Child, MyPageRight).PageOnExit()
AniControlEnabled -= 1
'执行动画
- AniStart({
- AaCode(
- Sub()
- AniControlEnabled += 1
- '把新页面添加进容器
- PanMainLeft.Child = PageLeft
- PageLeft.Opacity = 0
- PanMainLeft.Background = Nothing
- AniControlEnabled -= 1
- RunInUi(Sub() PanMainLeft_Resize(PanMainLeft.ActualWidth), True)
- End Sub, 110),
- AaCode(
- Sub()
- '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
- PageLeft.Opacity = 1
- PageLeft.TriggerShowAnimation()
- End Sub, 30, True)
- }, "FrmMain PageChangeLeft")
- AniStart({
- AaCode(
- Sub()
- AniControlEnabled += 1
- CType(PanMainRight.Child, MyPageRight).PageOnForceExit()
- '把新页面添加进容器
- PanMainRight.Child = PageRight
- PageRight.Opacity = 0
- PanMainRight.Background = Nothing
- AniControlEnabled -= 1
- RunInUi(Sub() BtnExtraBack.ShowRefresh(), True)
- End Sub, 110),
- AaCode(
- Sub()
- '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
- PageRight.Opacity = 1
- PageRight.PageOnEnter()
- End Sub, 30, True)
- }, "FrmMain PageChangeRight")
+' AniStart({
+' AaCode(
+' Sub()
+' AniControlEnabled += 1
+' '把新页面添加进容器
+' PanMainLeft.Child = PageLeft
+' PageLeft.Opacity = 0
+' PanMainLeft.Background = Nothing
+' AniControlEnabled -= 1
+' RunInUi(Sub() PanMainLeft_Resize(PanMainLeft.ActualWidth), True)
+' End Sub, 110),
+' AaCode(
+' Sub()
+' '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
+' PageLeft.Opacity = 1
+' PageLeft.TriggerShowAnimation()
+' End Sub, 30, True)
+' }, "FrmMain PageChangeLeft")
+
+ _aniFrmMainLeftChange = New SequentialAnimationGroup
+ _aniFrmMainLeftChange.Name = "FrmMain PageChangeLeft"
+
+ Dim aniLeft1 = New ActionAnimation(Sub()
+ AniControlEnabled += 1
+ '把新页面添加进容器
+ PanMainLeft.Child = PageLeft
+ PageLeft.Opacity = 0
+ PanMainLeft.Background = Nothing
+ AniControlEnabled -= 1
+ PanMainLeft_Resize(PanMainLeft.ActualWidth)
+ End Sub)
+ aniLeft1.Delay = TimeSpan.FromMilliseconds(110)
+ _aniFrmMainLeftChange.Children.Add(aniLeft1)
+
+ Dim aniLeft2 = New ActionAnimation(Sub()
+ '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
+ PageLeft.Opacity = 1
+ PageLeft.TriggerShowAnimation()
+ End Sub)
+ aniLeft2.Delay = TimeSpan.FromMilliseconds(30)
+ _aniFrmMainLeftChange.Children.Add(aniLeft2)
+
+ _aniFrmMainLeftChange.RunFireAndForget(EmptyAnimatable.Instance)
+
+' AniStart({
+' AaCode(
+' Sub()
+' AniControlEnabled += 1
+' CType(PanMainRight.Child, MyPageRight).PageOnForceExit()
+' '把新页面添加进容器
+' PanMainRight.Child = PageRight
+' PageRight.Opacity = 0
+' PanMainRight.Background = Nothing
+' AniControlEnabled -= 1
+' RunInUi(Sub() BtnExtraBack.ShowRefresh(), True)
+' End Sub, 110),
+' AaCode(
+' Sub()
+' '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
+' PageRight.Opacity = 1
+' PageRight.PageOnEnter()
+' End Sub, 30, True)
+' }, "FrmMain PageChangeRight")
+
+ _aniPageLeftPageChange = New SequentialAnimationGroup()
+ _aniFrmMainLeftChange.Name = "FrmMain PageChangeRight"
+
+ Dim aniRight1 = New ActionAnimation(Sub()
+ AniControlEnabled += 1
+ CType(PanMainRight.Child, MyPageRight).PageOnForceExit()
+ '把新页面添加进容器
+ PanMainRight.Child = PageRight
+ PageRight.Opacity = 0
+ PanMainRight.Background = Nothing
+ AniControlEnabled -= 1
+ BtnExtraBack.ShowRefresh()
+ End Sub)
+ aniRight1.Delay = TimeSpan.FromMilliseconds(110)
+ _aniPageLeftPageChange.Children.Add(aniRight1)
+
+ Dim aniRight2 = New ActionAnimation(Sub()
+ '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
+ PageRight.Opacity = 1
+ PageRight.PageOnEnter()
+ End Sub)
+ aniRight2.Delay = TimeSpan.FromMilliseconds(30)
+ _aniPageLeftPageChange.Children.Add(aniRight2)
+
+ _aniPageLeftPageChange.RunFireAndForget(EmptyAnimatable.Instance)
End Sub
'''
''' 退出子界面。
@@ -1497,13 +1754,61 @@ Public Class FormMain
PanTitleMain.Visibility = Visibility.Visible
PanTitleMain.IsHitTestVisible = True
PanTitleInner.IsHitTestVisible = False
- AniStart({
- AaOpacity(PanTitleInner, -PanTitleInner.Opacity, 150),
- AaX(PanTitleInner, -18 - PanTitleInner.Margin.Left, 150,, New AniEaseInFluent),
- AaOpacity(PanTitleMain, 1 - PanTitleMain.Opacity, 150, 200),
- AaX(PanTitleMain, -PanTitleMain.Margin.Left, 350, 200, New AniEaseOutBack(AniEasePower.Weak)),
- AaCode(Sub() PanTitleInner.Visibility = Visibility.Collapsed,, True)
- }, "FrmMain Titlebar FirstLayer")
+' AniStart({
+' AaOpacity(PanTitleInner, -PanTitleInner.Opacity, 150),
+' AaX(PanTitleInner, -18 - PanTitleInner.Margin.Left, 150,, New AniEaseInFluent),
+' AaOpacity(PanTitleMain, 1 - PanTitleMain.Opacity, 150, 200),
+' AaX(PanTitleMain, -PanTitleMain.Margin.Left, 350, 200, New AniEaseOutBack(AniEasePower.Weak)),
+' AaCode(Sub() PanTitleInner.Visibility = Visibility.Collapsed,, True)
+' }, "FrmMain Titlebar FirstLayer")
+
+ Dim ani1 = New SequentialAnimationGroup
+ ani1.Name = "FrmMain Titlebar FirstLayer"
+
+ Dim ani2 = New ParallelAnimationGroup
+
+ Dim aniInnerOpacity = New DoubleFromToAnimation
+ aniInnerOpacity.To = 0
+ aniInnerOpacity.Duration = TimeSpan.FromMilliseconds(150)
+ aniInnerOpacity.SetValue(AnimationExtensions.TargetProperty, PanTitleInner)
+ aniInnerOpacity.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ ani2.Children.Add(aniInnerOpacity)
+
+ Dim aniInnerMove = New ThicknessFromToAnimation
+ aniInnerMove.To = New Thickness(-18, PanTitleInner.Margin.Top, PanTitleInner.Margin.Right,
+ PanTitleInner.Margin.Bottom)
+ aniInnerMove.Duration = TimeSpan.FromMilliseconds(150)
+ aniInnerMove.Easing = CubicEaseIn.Shared
+ aniInnerMove.SetValue(AnimationExtensions.TargetProperty, PanTitleInner)
+ aniInnerMove.SetValue(AnimationExtensions.TargetPropertyProperty, MarginProperty)
+ ani2.Children.Add(aniInnerMove)
+
+ Dim aniMainOpacity = New DoubleFromToAnimation
+ aniMainOpacity.To = 1
+ aniMainOpacity.Delay = TimeSpan.FromMilliseconds(200)
+ aniMainOpacity.Duration = TimeSpan.FromMilliseconds(150)
+ aniMainOpacity.SetValue(AnimationExtensions.TargetProperty, PanTitleMain)
+ aniMainOpacity.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ ani2.Children.Add(aniMainOpacity)
+
+ Dim aniMainMove = New ThicknessFromToAnimation
+ aniMainMove.To = New Thickness(0, PanTitleMain.Margin.Top, PanTitleMain.Margin.Right,
+ PanTitleMain.Margin.Bottom)
+ aniMainMove.Delay = TimeSpan.FromMilliseconds(200)
+ aniMainMove.Duration = TimeSpan.FromMilliseconds(350)
+ aniMainMove.Easing = New BackEaseWithPowerOut(EasePower.Weak)
+ aniMainMove.SetValue(AnimationExtensions.TargetProperty, PanTitleMain)
+ aniMainMove.SetValue(AnimationExtensions.TargetPropertyProperty, MarginProperty)
+ ani2.Children.Add(aniMainMove)
+
+ ani1.Children.Add(ani2)
+
+ Dim aniVisibilityChange =
+ New ActionAnimation(Sub() PanTitleInner.Visibility = Visibility.Collapsed)
+ ani1.Children.Add(aniVisibilityChange)
+
+ ani1.RunFireAndForget(EmptyAnimatable.Instance)
+
PageStack.Clear()
Else
'主页面 → 主页面,无事发生
@@ -1515,29 +1820,74 @@ Public Class FormMain
If Not e.WidthChanged Then Return
PanMainLeft_Resize(e.NewSize.Width)
End Sub
+
Private Sub PanMainLeft_Resize(NewWidth As Double)
Dim Delta As Double = NewWidth - RectLeftBackground.Width
+ Dim aniLeftChange = New ParallelAnimationGroup With {.Name = "FrmMain LeftChange"}
If Math.Abs(Delta) > 0.1 AndAlso AniControlEnabled = 0 Then
If PanMain.Opacity < 0.1 Then PanMainLeft.IsHitTestVisible = False '避免左边栏指向背景未能完美覆盖左边栏
If NewWidth > 0 Then
'宽度足够,显示
- AniStart({
- AaWidth(RectLeftBackground, NewWidth - RectLeftBackground.Width, 180,, New AniEaseOutFluent(AniEasePower.ExtraStrong)),
- AaOpacity(RectLeftShadow, 1 - RectLeftShadow.Opacity, 180),
- AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 150)
- }, "FrmMain LeftChange", True)
+' AniStart({
+' AaWidth(RectLeftBackground, NewWidth - RectLeftBackground.Width, 180,, New AniEaseOutFluent(AniEasePower.ExtraStrong)),
+' AaOpacity(RectLeftShadow, 1 - RectLeftShadow.Opacity, 180),
+' AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 150)
+' }, "FrmMain LeftChange", True)
+
+ Dim aniWidth = New DoubleFromToAnimation
+ aniWidth.To = NewWidth
+ aniWidth.Duration = TimeSpan.FromMilliseconds(180)
+ aniWidth.Easing = QuinticEaseOut.Shared
+ aniWidth.SetValue(AnimationExtensions.TargetProperty, RectLeftBackground)
+ aniWidth.SetValue(AnimationExtensions.TargetPropertyProperty, WidthProperty)
+ aniLeftChange.Children.Add(aniWidth)
+
+ Dim aniOpacity = New DoubleFromToAnimation
+ aniOpacity.To = 1
+ aniOpacity.Duration = TimeSpan.FromMilliseconds(180)
+ aniOpacity.SetValue(AnimationExtensions.TargetProperty, RectLeftShadow)
+ aniOpacity.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ aniLeftChange.Children.Add(aniOpacity)
+
+ Dim aniHitTest = New ActionAnimation(Sub() PanMainLeft.IsHitTestVisible = True)
+ aniHitTest.Delay = TimeSpan.FromMilliseconds(150)
+ aniLeftChange.Children.Add(aniHitTest)
+
+ aniLeftChange.RunFireAndForget(EmptyAnimatable.Instance)
Else
'宽度不足,隐藏
- AniStart({
- AaWidth(RectLeftBackground, -RectLeftBackground.Width, 180,, New AniEaseOutFluent),
- AaOpacity(RectLeftShadow, -RectLeftShadow.Opacity, 180),
- AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 150)
- }, "FrmMain LeftChange", True)
+' AniStart({
+' AaWidth(RectLeftBackground, -RectLeftBackground.Width, 180,, New AniEaseOutFluent),
+' AaOpacity(RectLeftShadow, -RectLeftShadow.Opacity, 180),
+' AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 150)
+' }, "FrmMain LeftChange", True)
+
+ Dim aniWidth = New DoubleFromToAnimation
+ aniWidth.To = 0
+ aniWidth.Duration = TimeSpan.FromMilliseconds(180)
+ aniWidth.Easing = CubicEaseOut.Shared
+ aniWidth.SetValue(AnimationExtensions.TargetProperty, RectLeftBackground)
+ aniWidth.SetValue(AnimationExtensions.TargetPropertyProperty, WidthProperty)
+ aniLeftChange.Children.Add(aniWidth)
+
+ Dim aniOpacity = New DoubleFromToAnimation
+ aniOpacity.To = 0
+ aniOpacity.Duration = TimeSpan.FromMilliseconds(180)
+ aniOpacity.SetValue(AnimationExtensions.TargetProperty, RectLeftShadow)
+ aniOpacity.SetValue(AnimationExtensions.TargetPropertyProperty, OpacityProperty)
+ aniLeftChange.Children.Add(aniOpacity)
+
+ Dim aniHitTest = New ActionAnimation(Sub() PanMainLeft.IsHitTestVisible = True)
+ aniHitTest.Delay = TimeSpan.FromMilliseconds(150)
+ aniLeftChange.Children.Add(aniHitTest)
+
+ aniLeftChange.RunFireAndForget(EmptyAnimatable.Instance)
End If
Else
RectLeftBackground.Width = NewWidth
PanMainLeft.IsHitTestVisible = True
- AniStop("FrmMain LeftChange")
+' AniStop("FrmMain LeftChange")
+ aniLeftChange.CancelAndClear()
End If
End Sub