Conversation
# Conflicts: # Plain Craft Launcher 2/FormMain.xaml.vb
# Conflicts: # Plain Craft Launcher 2/Controls/MyCard.vb # Plain Craft Launcher 2/Controls/MyHint.xaml.vb # Plain Craft Launcher 2/FormMain.xaml.vb
评审者指南对动画系统进行重构与扩展,使其基于状态驱动和动作应用:新增具名及可取消动画支持、全新的变换与颜色抽象、更丰富的缓动工具,以及通用的数值处理机制,同时更新动画分组与帧以适配新的模型。 AnimationService 中更新后的动画生命周期序列图sequenceDiagram
actor XamlTrigger as XamlTrigger
participant RunAction as RunAnimationAction
participant Anim as FromToAnimationBase_T
participant AnimService as AnimationService
participant ComputeTask as AnimationComputeTask
participant FrameChannel as FrameChannel
participant UIThread as UIThread
participant Target as IAnimatable
XamlTrigger->>RunAction: Invoke()
RunAction->>Anim: RunFireAndForget(Target)
activate Anim
Anim->>Anim: _RunCore(Target)
Anim->>AnimService: PushAnimationFireAndForget(thisClone, Target)
deactivate Anim
rect rgb(235,235,255)
note over AnimService: Handle named animation registration and conflict
AnimService->>AnimService: HandleNamedAnimationConflict(animation)
alt animation.Name exists in _namedAnimations
AnimService->>OldAnim: Cancel()
note over OldAnim: Status = Canceled
end
AnimService->>AnimService: _animationChannel.Writer.TryWrite((animation, target))
end
loop Background compute tasks
ComputeTask->>AnimService: _animationChannel.Reader.TryRead()
AnimService-->>ComputeTask: (animation, target)
activate ComputeTask
ComputeTask->>Anim: Status check
alt Status is Canceled or Completed
ComputeTask->>Anim: RaiseCompleted()
alt animation.Name not empty
ComputeTask->>AnimService: _namedAnimations.Remove(animation.Name)
end
ComputeTask->>ComputeTask: Remove from local list
else Animation still Running
ComputeTask->>Anim: ComputeNextFrame(target)
alt frame is not null
ComputeTask->>FrameChannel: Writer.TryWrite((frame, animation))
ComputeTask->>Anim: CurrentFrame++
end
end
ComputeTask->>ComputeTask: Wait _resetEvent (clock tick)
deactivate ComputeTask
end
loop UI apply loop
UIThread->>FrameChannel: Reader.WaitToReadAsync()
FrameChannel-->>UIThread: (frame, animation)
UIThread->>Anim: Status
alt animation.Status == Canceled
UIThread->>UIThread: discard frame
else
UIThread->>frame: GetAction()
frame-->>UIThread: action
UIThread->>UIThread: Invoke action()
note over Target: SetValue(...) executed on UI thread
end
end
note over AnimService: CancelAnimationByName(name) removes and cancels stored animation
核心动画模型更新后的类图classDiagram
class IAnimatable {
+object GetValue()
+void SetValue(object value)
+void SetValue~T~(T value)
}
class EmptyAnimatable {
+static EmptyAnimatable Instance
+object GetValue()
+void SetValue(object value)
+void SetValue~T~(T value)
}
IAnimatable <|.. EmptyAnimatable
class ClrAnimatable~T~ {
-T _value
+T GetValue()
+void SetValue(T value)
+object GetValue()
+void SetValue(object value)
+void SetValue~TValue~(TValue value)
}
IAnimatable <|.. ClrAnimatable
class WpfAnimatable {
+DependencyObject Owner
+DependencyProperty Property
+object GetValue()
+void SetValue(object value)
+void SetValue~T~(T value)
-void SetValueCore~T~(T value)
}
IAnimatable <|.. WpfAnimatable
class AnimationStatus {
<<enum>>
NotStarted
Running
Completed
Canceled
}
class IAnimation {
+string Name
+AnimationStatus Status
+int CurrentFrame
+Task~IAnimation~ RunAsync(IAnimatable target)
+IAnimation RunFireAndForget(IAnimatable target)
+void Cancel()
+IAnimationFrame ComputeNextFrame(IAnimatable target)
+event EventHandler Started
+event EventHandler Completed
}
class AnimationBase {
+string Name
-int _status
+AnimationStatus Status
+int CurrentFrame
+Task~IAnimation~ RunAsync(IAnimatable target)*
+IAnimation RunFireAndForget(IAnimatable target)*
+void Cancel()*
+IAnimationFrame ComputeNextFrame(IAnimatable target)*
+void RaiseStarted()
+void RaiseCompleted()
}
IAnimation <|.. AnimationBase
class FromToAnimationBase~T~ {
+IEasing Easing
+T From
+T To
+AnimationValueType ValueType
+TimeSpan Duration
+TimeSpan Delay
+T CurrentValue
+int TotalFrames
-int _currentFrame
-T _startValue
+Task~IAnimation~ RunAsync(IAnimatable target)
+IAnimation RunFireAndForget(IAnimatable target)
+void Cancel()
+IAnimationFrame ComputeNextFrame(IAnimatable target)
-void _RunCore(IAnimatable target)
}
AnimationBase <|-- FromToAnimationBase
class ActionAnimation {
+Action~CancellationToken~ Action
+int CurrentFrame
+TimeSpan Delay
-CancellationTokenSource _cts
-TaskCompletionSource _tcs
-int _called
+Task~IAnimation~ RunAsync(IAnimatable target)
+IAnimation RunFireAndForget(IAnimatable target)
+void Cancel()
+IAnimationFrame ComputeNextFrame(IAnimatable target)
}
AnimationBase <|-- ActionAnimation
class AnimationGroup {
+ObservableCollection~IAnimation~ Children
+int CurrentFrame
+List~IAnimation~ ChildrenCore
+void Cancel()
+void CancelAndClear()
+IAnimationFrame ComputeNextFrame(IAnimatable target)
#static IAnimatable ResolveTarget(IAnimation animation, IAnimatable defaultTarget)
#static Task CreateChildAwaiter(IAnimation animation)
}
AnimationBase <|-- AnimationGroup
class ParallelAnimationGroup {
-TaskCompletionSource _cancelTcs
+Task~IAnimation~ RunAsync(IAnimatable target)
+void Cancel()
+IAnimation RunFireAndForget(IAnimatable target)
}
AnimationGroup <|-- ParallelAnimationGroup
class SequentialAnimationGroup {
-TaskCompletionSource _cancelTcs
+Task~IAnimation~ RunAsync(IAnimatable target)
+void Cancel()
+IAnimation RunFireAndForget(IAnimatable target)
}
AnimationGroup <|-- SequentialAnimationGroup
class IAnimationFrame {
+Action GetAction()
}
class FromToAnimationFrame~T~ {
+IAnimatable Target
+T Value
+T StartValue
+Action GetAction()
}
IAnimationFrame <|.. FromToAnimationFrame
class ActionAnimationFrame {
+Action Action
+Action GetAction()
}
IAnimationFrame <|.. ActionAnimationFrame
class AnimationService {
-static Channel~(IAnimation,IAnimatable)~ _animationChannel
-static Channel~(IAnimationFrame,IAnimation)~ _frameChannel
-static ConcurrentDictionary~string,IAnimation~ _namedAnimations
-static IClock _clock
-static AsyncCountResetEvent _resetEvent
-static CancellationTokenSource _cts
+static int Fps
+static double Scale
+static IUIAccessProvider UIAccessProvider
+void Start()
+void Stop()
-static void _Initialize()
-static void _Uninitialize()
-static Task _AnimationComputeTaskAsync()
-static void HandleNamedAnimationConflict(IAnimation animation)
+static Task PushAnimationAsync(IAnimation animation, IAnimatable target)
+static void PushAnimationFireAndForget(IAnimation animation, IAnimatable target)
+static void CancelAnimationByName(string name)
}
class RunAnimationAction {
+IAnimation Animation
+DependencyProperty TargetProperty
+void Invoke(object parameter)
}
RunAnimationAction --> IAnimation : uses
FromToAnimationBase ..> IAnimatable : target
ActionAnimation ..> IAnimatable : target
AnimationGroup o--> IAnimation : Children
ParallelAnimationGroup ..> AnimationService : PushAnimationFireAndForget
SequentialAnimationGroup ..> AnimationService : PushAnimationFireAndForget
AnimationService o--> IAnimation : manages
AnimationService o--> IAnimatable : targets
AnimationService o--> IAnimationFrame : processes
FromToAnimationFrame ..> ValueProcessorManager
WpfAnimatable ..> NColor
WpfAnimatable ..> NScaleTransform
WpfAnimatable ..> NRotateTransform
数值处理与新 UI 数值类型的类图classDiagram
class IValueProcessor~T~ {
+T Filter(T value)
+T Add(T value1, T value2)
+T Subtract(T value1, T value2)
+T Scale(T value, double factor)
+T DefaultValue()
+bool Equal(T value1, T value2)
}
class ValueProcessorManager {
-Dictionary~Type,Func~object,object~~ Filters
-Dictionary~Type,Func~object,object,object~~ Adders
-Dictionary~Type,Func~object,double,object~~ Scalers
+void Register~T~(IValueProcessor~T~ processor)
+T Filter~T~(T value)
+object Filter(object value)
+T Add~T~(T value1, T value2)
+object Add(object value1, object value2)
+T Subtract~T~(T value1, T value2)
+T Scale~T~(T value, double factor)
+object Scale(object value, double factor)
+T DefaultValue~T~()
+bool Equal~T~(T value1, T value2)
}
IValueProcessor <|.. DoubleValueProcessor
class DoubleValueProcessor {
+double Filter(double value)
+double Add(double value1, double value2)
+double Subtract(double value1, double value2)
+double Scale(double value, double factor)
+double DefaultValue()
+bool Equal(double value1, double value2)
}
IValueProcessor <|.. NColorValueProcessor
class NColorValueProcessor {
+NColor Filter(NColor value)
+NColor Add(NColor value1, NColor value2)
+NColor Subtract(NColor value1, NColor value2)
+NColor Scale(NColor value, double factor)
+NColor DefaultValue()
+bool Equal(NColor value1, NColor value2)
}
IValueProcessor <|.. ThicknessValueProcessor
class ThicknessValueProcessor {
+Thickness Filter(Thickness value)
+Thickness Add(Thickness value1, Thickness value2)
+Thickness Subtract(Thickness value1, Thickness value2)
+Thickness Scale(Thickness value, double factor)
+Thickness DefaultValue()
+bool Equal(Thickness value1, Thickness value2)
}
IValueProcessor <|.. MatrixValueProcessor
class MatrixValueProcessor {
+Matrix Filter(Matrix value)
+Matrix Add(Matrix value1, Matrix value2)
+Matrix Subtract(Matrix value1, Matrix value2)
+Matrix Scale(Matrix value, double factor)
+Matrix DefaultValue()
+bool Equal(Matrix value1, Matrix value2)
}
IValueProcessor <|.. PointValueProcessor
class PointValueProcessor {
+Point Filter(Point value)
+Point Add(Point value1, Point value2)
+Point Subtract(Point value1, Point value2)
+Point Scale(Point value, double factor)
+Point DefaultValue()
+bool Equal(Point value1, Point value2)
}
IValueProcessor <|.. NScaleTransformValueProcessor
class NScaleTransformValueProcessor {
+NScaleTransform Filter(NScaleTransform value)
+NScaleTransform Add(NScaleTransform value1, NScaleTransform value2)
+NScaleTransform Subtract(NScaleTransform value1, NScaleTransform value2)
+NScaleTransform Scale(NScaleTransform value, double factor)
+NScaleTransform DefaultValue()
+bool Equal(NScaleTransform value1, NScaleTransform value2)
}
IValueProcessor <|.. NRotateTransformValueProcessor
class NRotateTransformValueProcessor {
+NRotateTransform Filter(NRotateTransform value)
+NRotateTransform Add(NRotateTransform value1, NRotateTransform value2)
+NRotateTransform Subtract(NRotateTransform value1, NRotateTransform value2)
+NRotateTransform Scale(NRotateTransform value, double factor)
+NRotateTransform DefaultValue()
+bool Equal(NRotateTransform value1, NRotateTransform value2)
}
ValueProcessorManager o--> IValueProcessor
class NColor {
-Vector4 _color
+float R
+float G
+float B
+float A
+NColor()
+NColor(float r, float g, float b, float a)
+NColor(Color color)
+NColor(SystemDrawingColor color)
+NColor(string str)
+NColor(Brush brush)
+NColor(SolidColorBrush brush)
+NColor op_Add(NColor a, NColor b)
+NColor op_Subtract(NColor a, NColor b)
+NColor op_Multiply(NColor a, float b)
+NColor op_Division(NColor a, float b)
+bool op_Equality(NColor a, NColor b)
+bool op_Inequality(NColor a, NColor b)
+Color op_Implicit(NColor color)
+Brush op_Implicit(NColor color)
+SolidColorBrush op_Implicit(NColor color)
+NColor op_Implicit(Color color)
+NColor op_Implicit(Brush brush)
+NColor op_Implicit(SolidColorBrush brush)
}
class NScaleTransform {
-Vector4 _scale
+float ScaleX
+float ScaleY
+float CenterX
+float CenterY
+NScaleTransform()
+NScaleTransform(float scaleX, float scaleY, float centerX, float centerY)
+NScaleTransform(ScaleTransform scaleTransform)
+NScaleTransform op_Add(NScaleTransform a, NScaleTransform b)
+NScaleTransform op_Subtract(NScaleTransform a, NScaleTransform b)
+NScaleTransform op_Multiply(NScaleTransform a, float b)
+NScaleTransform op_Division(NScaleTransform a, float b)
+bool op_Equality(NScaleTransform a, NScaleTransform b)
+bool op_Inequality(NScaleTransform a, NScaleTransform b)
+ScaleTransform op_Implicit(NScaleTransform st)
+NScaleTransform op_Implicit(ScaleTransform st)
}
class NRotateTransform {
-Vector3 _rotate
+float Angle
+float CenterX
+float CenterY
+NRotateTransform()
+NRotateTransform(float angle, float centerX, float centerY)
+NRotateTransform(RotateTransform rotateTransform)
+NRotateTransform op_Add(NRotateTransform a, NRotateTransform b)
+NRotateTransform op_Subtract(NRotateTransform a, NRotateTransform b)
+NRotateTransform op_Multiply(NRotateTransform a, float b)
+NRotateTransform op_Division(NRotateTransform a, float b)
+bool op_Equality(NRotateTransform a, NRotateTransform b)
+bool op_Inequality(NRotateTransform a, NRotateTransform b)
+RotateTransform op_Implicit(NRotateTransform rt)
+NRotateTransform op_Implicit(RotateTransform rt)
}
NColorValueProcessor ..> NColor
NScaleTransformValueProcessor ..> NScaleTransform
NRotateTransformValueProcessor ..> NRotateTransform
WpfAnimatable ..> NColor : converts
WpfAnimatable ..> NScaleTransform : converts
WpfAnimatable ..> NRotateTransform : converts
class IEasing {
+double Ease(double progress)
+double Ease(int currentFrame, int totalFrames)
}
class Easing {
+double Ease(double progress)
+double Ease(int currentFrame, int totalFrames)
#double EaseCore(double progress)*
}
IEasing <|.. Easing
class CompositeEasing {
-List~(IEasing,TimeSpan,TimeSpan,double)~ _easings
-TimeSpan _totalDuration
+CompositeEasing((IEasing,TimeSpan)[] easings)
+CompositeEasing((IEasing,TimeSpan,TimeSpan)[] easings)
+CompositeEasing((IEasing,TimeSpan,double)[] easings)
+CompositeEasing((IEasing,TimeSpan,TimeSpan,double)[] easings)
+TimeSpan TotalDuration
#double EaseCore(double progress)
}
Easing <|-- CompositeEasing
class CombinedEasing {
-IEasing _ease1
-IEasing _ease2
-double _split
#double EaseCore(double t)
}
Easing <|-- CombinedEasing
class BackEaseWithPowerIn {
-double _p
#double EaseCore(double progress)
}
Easing <|-- BackEaseWithPowerIn
class BackEaseWithPowerOut {
-double _p
#double EaseCore(double progress)
}
Easing <|-- BackEaseWithPowerOut
class BackEaseWithPowerInOut {
-double _p
#double EaseCore(double progress)
}
Easing <|-- BackEaseWithPowerInOut
class EasePower {
<<enum>>
Weak
Middle
Strong
ExtraStrong
}
CompositeEasing ..> IEasing
CombinedEasing ..> IEasing
BackEaseWithPowerIn ..> EasePower
BackEaseWithPowerOut ..> EasePower
BackEaseWithPowerInOut ..> EasePower
文件级变更
技巧与命令与 Sourcery 交互
自定义你的使用体验访问你的 控制面板 以:
获取帮助Original review guide in EnglishReviewer's GuideRefactors and extends the animation system to be status-driven and action-based, adds support for named and cancellable animations, new transform and color abstractions, richer easing utilities, and generalized value processing, while updating animation groups and frames to work with the new model. Sequence diagram for the updated animation lifecycle in AnimationServicesequenceDiagram
actor XamlTrigger as XamlTrigger
participant RunAction as RunAnimationAction
participant Anim as FromToAnimationBase_T
participant AnimService as AnimationService
participant ComputeTask as AnimationComputeTask
participant FrameChannel as FrameChannel
participant UIThread as UIThread
participant Target as IAnimatable
XamlTrigger->>RunAction: Invoke()
RunAction->>Anim: RunFireAndForget(Target)
activate Anim
Anim->>Anim: _RunCore(Target)
Anim->>AnimService: PushAnimationFireAndForget(thisClone, Target)
deactivate Anim
rect rgb(235,235,255)
note over AnimService: Handle named animation registration and conflict
AnimService->>AnimService: HandleNamedAnimationConflict(animation)
alt animation.Name exists in _namedAnimations
AnimService->>OldAnim: Cancel()
note over OldAnim: Status = Canceled
end
AnimService->>AnimService: _animationChannel.Writer.TryWrite((animation, target))
end
loop Background compute tasks
ComputeTask->>AnimService: _animationChannel.Reader.TryRead()
AnimService-->>ComputeTask: (animation, target)
activate ComputeTask
ComputeTask->>Anim: Status check
alt Status is Canceled or Completed
ComputeTask->>Anim: RaiseCompleted()
alt animation.Name not empty
ComputeTask->>AnimService: _namedAnimations.Remove(animation.Name)
end
ComputeTask->>ComputeTask: Remove from local list
else Animation still Running
ComputeTask->>Anim: ComputeNextFrame(target)
alt frame is not null
ComputeTask->>FrameChannel: Writer.TryWrite((frame, animation))
ComputeTask->>Anim: CurrentFrame++
end
end
ComputeTask->>ComputeTask: Wait _resetEvent (clock tick)
deactivate ComputeTask
end
loop UI apply loop
UIThread->>FrameChannel: Reader.WaitToReadAsync()
FrameChannel-->>UIThread: (frame, animation)
UIThread->>Anim: Status
alt animation.Status == Canceled
UIThread->>UIThread: discard frame
else
UIThread->>frame: GetAction()
frame-->>UIThread: action
UIThread->>UIThread: Invoke action()
note over Target: SetValue(...) executed on UI thread
end
end
note over AnimService: CancelAnimationByName(name) removes and cancels stored animation
Updated class diagram for core animation modelclassDiagram
class IAnimatable {
+object GetValue()
+void SetValue(object value)
+void SetValue~T~(T value)
}
class EmptyAnimatable {
+static EmptyAnimatable Instance
+object GetValue()
+void SetValue(object value)
+void SetValue~T~(T value)
}
IAnimatable <|.. EmptyAnimatable
class ClrAnimatable~T~ {
-T _value
+T GetValue()
+void SetValue(T value)
+object GetValue()
+void SetValue(object value)
+void SetValue~TValue~(TValue value)
}
IAnimatable <|.. ClrAnimatable
class WpfAnimatable {
+DependencyObject Owner
+DependencyProperty Property
+object GetValue()
+void SetValue(object value)
+void SetValue~T~(T value)
-void SetValueCore~T~(T value)
}
IAnimatable <|.. WpfAnimatable
class AnimationStatus {
<<enum>>
NotStarted
Running
Completed
Canceled
}
class IAnimation {
+string Name
+AnimationStatus Status
+int CurrentFrame
+Task~IAnimation~ RunAsync(IAnimatable target)
+IAnimation RunFireAndForget(IAnimatable target)
+void Cancel()
+IAnimationFrame ComputeNextFrame(IAnimatable target)
+event EventHandler Started
+event EventHandler Completed
}
class AnimationBase {
+string Name
-int _status
+AnimationStatus Status
+int CurrentFrame
+Task~IAnimation~ RunAsync(IAnimatable target)*
+IAnimation RunFireAndForget(IAnimatable target)*
+void Cancel()*
+IAnimationFrame ComputeNextFrame(IAnimatable target)*
+void RaiseStarted()
+void RaiseCompleted()
}
IAnimation <|.. AnimationBase
class FromToAnimationBase~T~ {
+IEasing Easing
+T From
+T To
+AnimationValueType ValueType
+TimeSpan Duration
+TimeSpan Delay
+T CurrentValue
+int TotalFrames
-int _currentFrame
-T _startValue
+Task~IAnimation~ RunAsync(IAnimatable target)
+IAnimation RunFireAndForget(IAnimatable target)
+void Cancel()
+IAnimationFrame ComputeNextFrame(IAnimatable target)
-void _RunCore(IAnimatable target)
}
AnimationBase <|-- FromToAnimationBase
class ActionAnimation {
+Action~CancellationToken~ Action
+int CurrentFrame
+TimeSpan Delay
-CancellationTokenSource _cts
-TaskCompletionSource _tcs
-int _called
+Task~IAnimation~ RunAsync(IAnimatable target)
+IAnimation RunFireAndForget(IAnimatable target)
+void Cancel()
+IAnimationFrame ComputeNextFrame(IAnimatable target)
}
AnimationBase <|-- ActionAnimation
class AnimationGroup {
+ObservableCollection~IAnimation~ Children
+int CurrentFrame
+List~IAnimation~ ChildrenCore
+void Cancel()
+void CancelAndClear()
+IAnimationFrame ComputeNextFrame(IAnimatable target)
#static IAnimatable ResolveTarget(IAnimation animation, IAnimatable defaultTarget)
#static Task CreateChildAwaiter(IAnimation animation)
}
AnimationBase <|-- AnimationGroup
class ParallelAnimationGroup {
-TaskCompletionSource _cancelTcs
+Task~IAnimation~ RunAsync(IAnimatable target)
+void Cancel()
+IAnimation RunFireAndForget(IAnimatable target)
}
AnimationGroup <|-- ParallelAnimationGroup
class SequentialAnimationGroup {
-TaskCompletionSource _cancelTcs
+Task~IAnimation~ RunAsync(IAnimatable target)
+void Cancel()
+IAnimation RunFireAndForget(IAnimatable target)
}
AnimationGroup <|-- SequentialAnimationGroup
class IAnimationFrame {
+Action GetAction()
}
class FromToAnimationFrame~T~ {
+IAnimatable Target
+T Value
+T StartValue
+Action GetAction()
}
IAnimationFrame <|.. FromToAnimationFrame
class ActionAnimationFrame {
+Action Action
+Action GetAction()
}
IAnimationFrame <|.. ActionAnimationFrame
class AnimationService {
-static Channel~(IAnimation,IAnimatable)~ _animationChannel
-static Channel~(IAnimationFrame,IAnimation)~ _frameChannel
-static ConcurrentDictionary~string,IAnimation~ _namedAnimations
-static IClock _clock
-static AsyncCountResetEvent _resetEvent
-static CancellationTokenSource _cts
+static int Fps
+static double Scale
+static IUIAccessProvider UIAccessProvider
+void Start()
+void Stop()
-static void _Initialize()
-static void _Uninitialize()
-static Task _AnimationComputeTaskAsync()
-static void HandleNamedAnimationConflict(IAnimation animation)
+static Task PushAnimationAsync(IAnimation animation, IAnimatable target)
+static void PushAnimationFireAndForget(IAnimation animation, IAnimatable target)
+static void CancelAnimationByName(string name)
}
class RunAnimationAction {
+IAnimation Animation
+DependencyProperty TargetProperty
+void Invoke(object parameter)
}
RunAnimationAction --> IAnimation : uses
FromToAnimationBase ..> IAnimatable : target
ActionAnimation ..> IAnimatable : target
AnimationGroup o--> IAnimation : Children
ParallelAnimationGroup ..> AnimationService : PushAnimationFireAndForget
SequentialAnimationGroup ..> AnimationService : PushAnimationFireAndForget
AnimationService o--> IAnimation : manages
AnimationService o--> IAnimatable : targets
AnimationService o--> IAnimationFrame : processes
FromToAnimationFrame ..> ValueProcessorManager
WpfAnimatable ..> NColor
WpfAnimatable ..> NScaleTransform
WpfAnimatable ..> NRotateTransform
Class diagram for value processing and new UI value typesclassDiagram
class IValueProcessor~T~ {
+T Filter(T value)
+T Add(T value1, T value2)
+T Subtract(T value1, T value2)
+T Scale(T value, double factor)
+T DefaultValue()
+bool Equal(T value1, T value2)
}
class ValueProcessorManager {
-Dictionary~Type,Func~object,object~~ Filters
-Dictionary~Type,Func~object,object,object~~ Adders
-Dictionary~Type,Func~object,double,object~~ Scalers
+void Register~T~(IValueProcessor~T~ processor)
+T Filter~T~(T value)
+object Filter(object value)
+T Add~T~(T value1, T value2)
+object Add(object value1, object value2)
+T Subtract~T~(T value1, T value2)
+T Scale~T~(T value, double factor)
+object Scale(object value, double factor)
+T DefaultValue~T~()
+bool Equal~T~(T value1, T value2)
}
IValueProcessor <|.. DoubleValueProcessor
class DoubleValueProcessor {
+double Filter(double value)
+double Add(double value1, double value2)
+double Subtract(double value1, double value2)
+double Scale(double value, double factor)
+double DefaultValue()
+bool Equal(double value1, double value2)
}
IValueProcessor <|.. NColorValueProcessor
class NColorValueProcessor {
+NColor Filter(NColor value)
+NColor Add(NColor value1, NColor value2)
+NColor Subtract(NColor value1, NColor value2)
+NColor Scale(NColor value, double factor)
+NColor DefaultValue()
+bool Equal(NColor value1, NColor value2)
}
IValueProcessor <|.. ThicknessValueProcessor
class ThicknessValueProcessor {
+Thickness Filter(Thickness value)
+Thickness Add(Thickness value1, Thickness value2)
+Thickness Subtract(Thickness value1, Thickness value2)
+Thickness Scale(Thickness value, double factor)
+Thickness DefaultValue()
+bool Equal(Thickness value1, Thickness value2)
}
IValueProcessor <|.. MatrixValueProcessor
class MatrixValueProcessor {
+Matrix Filter(Matrix value)
+Matrix Add(Matrix value1, Matrix value2)
+Matrix Subtract(Matrix value1, Matrix value2)
+Matrix Scale(Matrix value, double factor)
+Matrix DefaultValue()
+bool Equal(Matrix value1, Matrix value2)
}
IValueProcessor <|.. PointValueProcessor
class PointValueProcessor {
+Point Filter(Point value)
+Point Add(Point value1, Point value2)
+Point Subtract(Point value1, Point value2)
+Point Scale(Point value, double factor)
+Point DefaultValue()
+bool Equal(Point value1, Point value2)
}
IValueProcessor <|.. NScaleTransformValueProcessor
class NScaleTransformValueProcessor {
+NScaleTransform Filter(NScaleTransform value)
+NScaleTransform Add(NScaleTransform value1, NScaleTransform value2)
+NScaleTransform Subtract(NScaleTransform value1, NScaleTransform value2)
+NScaleTransform Scale(NScaleTransform value, double factor)
+NScaleTransform DefaultValue()
+bool Equal(NScaleTransform value1, NScaleTransform value2)
}
IValueProcessor <|.. NRotateTransformValueProcessor
class NRotateTransformValueProcessor {
+NRotateTransform Filter(NRotateTransform value)
+NRotateTransform Add(NRotateTransform value1, NRotateTransform value2)
+NRotateTransform Subtract(NRotateTransform value1, NRotateTransform value2)
+NRotateTransform Scale(NRotateTransform value, double factor)
+NRotateTransform DefaultValue()
+bool Equal(NRotateTransform value1, NRotateTransform value2)
}
ValueProcessorManager o--> IValueProcessor
class NColor {
-Vector4 _color
+float R
+float G
+float B
+float A
+NColor()
+NColor(float r, float g, float b, float a)
+NColor(Color color)
+NColor(SystemDrawingColor color)
+NColor(string str)
+NColor(Brush brush)
+NColor(SolidColorBrush brush)
+NColor op_Add(NColor a, NColor b)
+NColor op_Subtract(NColor a, NColor b)
+NColor op_Multiply(NColor a, float b)
+NColor op_Division(NColor a, float b)
+bool op_Equality(NColor a, NColor b)
+bool op_Inequality(NColor a, NColor b)
+Color op_Implicit(NColor color)
+Brush op_Implicit(NColor color)
+SolidColorBrush op_Implicit(NColor color)
+NColor op_Implicit(Color color)
+NColor op_Implicit(Brush brush)
+NColor op_Implicit(SolidColorBrush brush)
}
class NScaleTransform {
-Vector4 _scale
+float ScaleX
+float ScaleY
+float CenterX
+float CenterY
+NScaleTransform()
+NScaleTransform(float scaleX, float scaleY, float centerX, float centerY)
+NScaleTransform(ScaleTransform scaleTransform)
+NScaleTransform op_Add(NScaleTransform a, NScaleTransform b)
+NScaleTransform op_Subtract(NScaleTransform a, NScaleTransform b)
+NScaleTransform op_Multiply(NScaleTransform a, float b)
+NScaleTransform op_Division(NScaleTransform a, float b)
+bool op_Equality(NScaleTransform a, NScaleTransform b)
+bool op_Inequality(NScaleTransform a, NScaleTransform b)
+ScaleTransform op_Implicit(NScaleTransform st)
+NScaleTransform op_Implicit(ScaleTransform st)
}
class NRotateTransform {
-Vector3 _rotate
+float Angle
+float CenterX
+float CenterY
+NRotateTransform()
+NRotateTransform(float angle, float centerX, float centerY)
+NRotateTransform(RotateTransform rotateTransform)
+NRotateTransform op_Add(NRotateTransform a, NRotateTransform b)
+NRotateTransform op_Subtract(NRotateTransform a, NRotateTransform b)
+NRotateTransform op_Multiply(NRotateTransform a, float b)
+NRotateTransform op_Division(NRotateTransform a, float b)
+bool op_Equality(NRotateTransform a, NRotateTransform b)
+bool op_Inequality(NRotateTransform a, NRotateTransform b)
+RotateTransform op_Implicit(NRotateTransform rt)
+NRotateTransform op_Implicit(RotateTransform rt)
}
NColorValueProcessor ..> NColor
NScaleTransformValueProcessor ..> NScaleTransform
NRotateTransformValueProcessor ..> NRotateTransform
WpfAnimatable ..> NColor : converts
WpfAnimatable ..> NScaleTransform : converts
WpfAnimatable ..> NRotateTransform : converts
class IEasing {
+double Ease(double progress)
+double Ease(int currentFrame, int totalFrames)
}
class Easing {
+double Ease(double progress)
+double Ease(int currentFrame, int totalFrames)
#double EaseCore(double progress)*
}
IEasing <|.. Easing
class CompositeEasing {
-List~(IEasing,TimeSpan,TimeSpan,double)~ _easings
-TimeSpan _totalDuration
+CompositeEasing((IEasing,TimeSpan)[] easings)
+CompositeEasing((IEasing,TimeSpan,TimeSpan)[] easings)
+CompositeEasing((IEasing,TimeSpan,double)[] easings)
+CompositeEasing((IEasing,TimeSpan,TimeSpan,double)[] easings)
+TimeSpan TotalDuration
#double EaseCore(double progress)
}
Easing <|-- CompositeEasing
class CombinedEasing {
-IEasing _ease1
-IEasing _ease2
-double _split
#double EaseCore(double t)
}
Easing <|-- CombinedEasing
class BackEaseWithPowerIn {
-double _p
#double EaseCore(double progress)
}
Easing <|-- BackEaseWithPowerIn
class BackEaseWithPowerOut {
-double _p
#double EaseCore(double progress)
}
Easing <|-- BackEaseWithPowerOut
class BackEaseWithPowerInOut {
-double _p
#double EaseCore(double progress)
}
Easing <|-- BackEaseWithPowerInOut
class EasePower {
<<enum>>
Weak
Middle
Strong
ExtraStrong
}
CompositeEasing ..> IEasing
CombinedEasing ..> IEasing
BackEaseWithPowerIn ..> EasePower
BackEaseWithPowerOut ..> EasePower
BackEaseWithPowerInOut ..> EasePower
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我发现了 3 个问题,并留下了一些总体反馈:
- 在
CircularEaseIn中,Shared静态属性的类型被写成了CircularEaseOut而不是CircularEaseIn,这既会让调用方感到困惑,也会导致在不进行强制转换的情况下无法使用;请将属性类型更新为CircularEaseIn以与声明的类保持一致。 ActionAnimation.RunAsync/RunFireAndForget的逻辑在使用MemberwiseClone的同时,将_cts/_tcs存储在原始实例上;AnimationService使用的克隆实例会运行 action 并完成它自己的_tcs,而RunAsync中等待的是原始实例的_tcs,这可能导致其永远不完成——建议将 TCS/CTS 的创建移到克隆实例上,或者以其他方式确保RunAsync中等待的实例正是由ComputeNextFrame发出完成信号的那个实例。- 在
NScaleTransform中,+/-/*//运算符只对ScaleX/ScaleY进行操作,忽略了CenterX/CenterY,这在对中心点做动画时可能会出现令人意外的行为;请确认中心点是否也应该被插值/组合,并相应调整这些运算符(或者明确当前语义的文档)。
面向 AI Agent 的提示
Please address the comments from this code review:
## Overall Comments
- In `CircularEaseIn` the `Shared` static property is typed as `CircularEaseOut` instead of `CircularEaseIn`, which will both confuse callers and prevent using it without a cast; update the property type to `CircularEaseIn` to match the declaring class.
- The `ActionAnimation.RunAsync`/`RunFireAndForget` logic uses `MemberwiseClone` while storing `_cts`/`_tcs` on the original instance; the clone used by `AnimationService` runs the action and completes its own `_tcs`, leaving the original `_tcs` awaited in `RunAsync` potentially never completing—consider moving the TCS/CTS creation to the clone or otherwise ensuring the instance awaited in `RunAsync` is the one whose completion is signaled from `ComputeNextFrame`.
- In `NScaleTransform` the `+`/`-`/`*`/`/` operators only operate on `ScaleX`/`ScaleY` and ignore `CenterX`/`CenterY`, which may lead to surprising behavior when animating centers; verify whether centers should also be interpolated/combined and adjust the operators (or document the current semantics) accordingly.
## Individual Comments
### Comment 1
<location path="PCL.Core/UI/Animation/Easings/CircularEaseIn.cs" line_range="7" />
<code_context>
public class CircularEaseIn : Easing
{
+ public static CircularEaseOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
</code_context>
<issue_to_address>
**issue (bug_risk):** The `CircularEaseIn.Shared` singleton is declared with the wrong type.
`CircularEaseIn.Shared` is declared as `CircularEaseOut`, which appears to be a copy‑paste error and will cause incorrect typing/usage. It should be:
```csharp
public static CircularEaseIn Shared { get; } = new();
```
</issue_to_address>
### Comment 2
<location path="PCL.Core/UI/Animation/Core/FromToAnimationBase.cs" line_range="88-90" />
<code_context>
// 如果 From 为空,则根据动画值类型设置初始值
- From ??= ValueType == AnimationValueType.Relative ? default : _startValue;
+ if (!ValueProcessorManager.Equal(_startValue, From))
+ {
+ From = ValueType == AnimationValueType.Relative ? ValueProcessorManager.DefaultValue<T>() : _startValue;
+ }
</code_context>
<issue_to_address>
**issue (bug_risk):** The new `From` initialization logic overwrites explicit `From` values whenever they differ from the current target value.
Previously, `From` was only inferred when it was null/unspecified. With the new condition, any caller‑provided `From` that differs from `_startValue` is overwritten, so you can no longer animate from an arbitrary value.
If the goal is still “infer `From` only when it hasn’t been set”, consider instead checking for the unset/default case, e.g.:
```csharp
// if DefaultValue() represents "unset" for Relative animations
if (ValueProcessorManager.Equal(From, ValueProcessorManager.DefaultValue<T>()))
{
From = ValueType == AnimationValueType.Relative
? ValueProcessorManager.DefaultValue<T>()
: _startValue;
}
```
Alternatively, track an explicit `bool HasExplicitFrom` rather than inferring it via equality.
</issue_to_address>
### Comment 3
<location path="PCL.Core/UI/NScaleTransform.cs" line_range="79-81" />
<code_context>
+
+ #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) =>
</code_context>
<issue_to_address>
**issue (bug_risk):** `NScaleTransform` arithmetic operators ignore `CenterX`/`CenterY`, which may lead to incorrect composite transforms.
The overloaded operators currently construct new instances with `CenterX`/`CenterY` implicitly set to 0, so center information is discarded during arithmetic and composition. If centers are meant to participate in interpolation/composition, the operators should also combine them, e.g.
```csharp
new(
a.ScaleX + b.ScaleX,
a.ScaleY + b.ScaleY,
a.CenterX + b.CenterX,
a.CenterY + b.CenterY)
```
`NRotateTransform`’s operators have the same issue: they modify only the angle and ignore the center.
</issue_to_address>帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续的代码审查。
Original comment in English
Hey - I've found 3 issues, and left some high level feedback:
- In
CircularEaseIntheSharedstatic property is typed asCircularEaseOutinstead ofCircularEaseIn, which will both confuse callers and prevent using it without a cast; update the property type toCircularEaseInto match the declaring class. - The
ActionAnimation.RunAsync/RunFireAndForgetlogic usesMemberwiseClonewhile storing_cts/_tcson the original instance; the clone used byAnimationServiceruns the action and completes its own_tcs, leaving the original_tcsawaited inRunAsyncpotentially never completing—consider moving the TCS/CTS creation to the clone or otherwise ensuring the instance awaited inRunAsyncis the one whose completion is signaled fromComputeNextFrame. - In
NScaleTransformthe+/-/*//operators only operate onScaleX/ScaleYand ignoreCenterX/CenterY, which may lead to surprising behavior when animating centers; verify whether centers should also be interpolated/combined and adjust the operators (or document the current semantics) accordingly.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `CircularEaseIn` the `Shared` static property is typed as `CircularEaseOut` instead of `CircularEaseIn`, which will both confuse callers and prevent using it without a cast; update the property type to `CircularEaseIn` to match the declaring class.
- The `ActionAnimation.RunAsync`/`RunFireAndForget` logic uses `MemberwiseClone` while storing `_cts`/`_tcs` on the original instance; the clone used by `AnimationService` runs the action and completes its own `_tcs`, leaving the original `_tcs` awaited in `RunAsync` potentially never completing—consider moving the TCS/CTS creation to the clone or otherwise ensuring the instance awaited in `RunAsync` is the one whose completion is signaled from `ComputeNextFrame`.
- In `NScaleTransform` the `+`/`-`/`*`/`/` operators only operate on `ScaleX`/`ScaleY` and ignore `CenterX`/`CenterY`, which may lead to surprising behavior when animating centers; verify whether centers should also be interpolated/combined and adjust the operators (or document the current semantics) accordingly.
## Individual Comments
### Comment 1
<location path="PCL.Core/UI/Animation/Easings/CircularEaseIn.cs" line_range="7" />
<code_context>
public class CircularEaseIn : Easing
{
+ public static CircularEaseOut Shared { get; } = new();
+
protected override double EaseCore(double progress)
</code_context>
<issue_to_address>
**issue (bug_risk):** The `CircularEaseIn.Shared` singleton is declared with the wrong type.
`CircularEaseIn.Shared` is declared as `CircularEaseOut`, which appears to be a copy‑paste error and will cause incorrect typing/usage. It should be:
```csharp
public static CircularEaseIn Shared { get; } = new();
```
</issue_to_address>
### Comment 2
<location path="PCL.Core/UI/Animation/Core/FromToAnimationBase.cs" line_range="88-90" />
<code_context>
// 如果 From 为空,则根据动画值类型设置初始值
- From ??= ValueType == AnimationValueType.Relative ? default : _startValue;
+ if (!ValueProcessorManager.Equal(_startValue, From))
+ {
+ From = ValueType == AnimationValueType.Relative ? ValueProcessorManager.DefaultValue<T>() : _startValue;
+ }
</code_context>
<issue_to_address>
**issue (bug_risk):** The new `From` initialization logic overwrites explicit `From` values whenever they differ from the current target value.
Previously, `From` was only inferred when it was null/unspecified. With the new condition, any caller‑provided `From` that differs from `_startValue` is overwritten, so you can no longer animate from an arbitrary value.
If the goal is still “infer `From` only when it hasn’t been set”, consider instead checking for the unset/default case, e.g.:
```csharp
// if DefaultValue() represents "unset" for Relative animations
if (ValueProcessorManager.Equal(From, ValueProcessorManager.DefaultValue<T>()))
{
From = ValueType == AnimationValueType.Relative
? ValueProcessorManager.DefaultValue<T>()
: _startValue;
}
```
Alternatively, track an explicit `bool HasExplicitFrom` rather than inferring it via equality.
</issue_to_address>
### Comment 3
<location path="PCL.Core/UI/NScaleTransform.cs" line_range="79-81" />
<code_context>
+
+ #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) =>
</code_context>
<issue_to_address>
**issue (bug_risk):** `NScaleTransform` arithmetic operators ignore `CenterX`/`CenterY`, which may lead to incorrect composite transforms.
The overloaded operators currently construct new instances with `CenterX`/`CenterY` implicitly set to 0, so center information is discarded during arithmetic and composition. If centers are meant to participate in interpolation/composition, the operators should also combine them, e.g.
```csharp
new(
a.ScaleX + b.ScaleX,
a.ScaleY + b.ScaleY,
a.CenterX + b.CenterX,
a.CenterY + b.CenterY)
```
`NRotateTransform`’s operators have the same issue: they modify only the angle and ignore the center.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| if (!ValueProcessorManager.Equal(_startValue, From)) | ||
| { | ||
| From = ValueType == AnimationValueType.Relative ? ValueProcessorManager.DefaultValue<T>() : _startValue; |
There was a problem hiding this comment.
issue (bug_risk): 新的 From 初始化逻辑在 From 与当前目标值不同时会覆盖显式设置的 From 值。
之前,只有在 From 为 null/未指定时才会进行推断。使用新的条件后,任何与 _startValue 不相等的调用方提供的 From 都会被覆盖,因此就无法再从任意值开始做动画了。
如果目标仍然是“只有在尚未设置时才推断 From”,可以考虑改为检查未设置/默认的情况,例如:
// 如果对相对动画来说 DefaultValue() 表示“未设置”
if (ValueProcessorManager.Equal(From, ValueProcessorManager.DefaultValue<T>()))
{
From = ValueType == AnimationValueType.Relative
? ValueProcessorManager.DefaultValue<T>()
: _startValue;
}或者,显式维护一个 bool HasExplicitFrom 来表示是否已显式设置,而不是通过相等性来推断。
Original comment in English
issue (bug_risk): The new From initialization logic overwrites explicit From values whenever they differ from the current target value.
Previously, From was only inferred when it was null/unspecified. With the new condition, any caller‑provided From that differs from _startValue is overwritten, so you can no longer animate from an arbitrary value.
If the goal is still “infer From only when it hasn’t been set”, consider instead checking for the unset/default case, e.g.:
// if DefaultValue() represents "unset" for Relative animations
if (ValueProcessorManager.Equal(From, ValueProcessorManager.DefaultValue<T>()))
{
From = ValueType == AnimationValueType.Relative
? ValueProcessorManager.DefaultValue<T>()
: _startValue;
}Alternatively, track an explicit bool HasExplicitFrom rather than inferring it via equality.
| 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); |
There was a problem hiding this comment.
issue (bug_risk): NScaleTransform 的算术运算符忽略了 CenterX/CenterY,这可能会导致组合变换结果不正确。
当前的运算符重载在构造新实例时,会隐式地将 CenterX/CenterY 设置为 0,从而在算术和组合过程中丢失中心信息。如果中心点应参与插值/组合,那么这些运算符也应同时组合中心值,例如:
new(
a.ScaleX + b.ScaleX,
a.ScaleY + b.ScaleY,
a.CenterX + b.CenterX,
a.CenterY + b.CenterY)NRotateTransform 的运算符也存在同样的问题:它们只修改角度而忽略中心。
Original comment in English
issue (bug_risk): NScaleTransform arithmetic operators ignore CenterX/CenterY, which may lead to incorrect composite transforms.
The overloaded operators currently construct new instances with CenterX/CenterY implicitly set to 0, so center information is discarded during arithmetic and composition. If centers are meant to participate in interpolation/composition, the operators should also combine them, e.g.
new(
a.ScaleX + b.ScaleX,
a.ScaleY + b.ScaleY,
a.CenterX + b.CenterX,
a.CenterY + b.CenterY)NRotateTransform’s operators have the same issue: they modify only the angle and ignore the center.
Summary by Sourcery
修订动画基础设施,以支持更丰富的类型、命名、取消以及改进的缓动效果,并扩展 UI 值类型以简化与 WPF 的互操作。
新功能:
NScaleTransform和NRotateTransform类型的支持,包括动画、值处理器以及 WPF 互操作转换。ActionAnimation及相关帧/可动画辅助工具,通过动画管线执行任意操作。CompositeEasing、CombinedEasing和BackEaseWithPower等缓动变体,并为现有缓动函数提供共享单例实例。NColor支持通过资源键构造,并可以在颜色类型与画刷类型之间进行隐式转换。增强:
AnimationService的帧处理,以携带源动画、跳过已取消的帧,并通过逐帧操作驱动更新。AnimationStatus统一动画状态跟踪,用基于状态的逻辑替代IsCompleted,并使RunAsync/RunFireAndForget返回动画实例。FromToAnimationBase和动画组,以使用线程安全的帧计数器、基于处理器的默认值以及更好的取消语义。ValueProcessor基础设施,新增加法/缩放/默认值/相等性辅助工具,并在现有处理器中实现这些功能。WpfAnimatable和IAnimatable,以支持强类型SetValue和更丰富的 WPF 对象类型过滤。Original summary in English
Summary by Sourcery
Revise the animation infrastructure to support richer types, naming, cancellation and improved easing, and extend UI value types to ease WPF interop.
New Features:
Enhancements: