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