1- struct AnimationInstance {
2- struct TrackingReference {
3- let instanceID : AnimationTracker . InstanceID
4- let tracker : AnimationTracker
5- }
6-
7- let startTime : Double
1+ private struct RunningAnimation {
2+ let trackedInstance : AnimationTracker . Instance ?
83 let animation : Animation
9- let trackingReference : TrackingReference ?
4+ let startTime : Double
5+ let target : AnimatableVector
6+ var context : AnimationContext
7+ var hasLogicallyCompleted : Bool = false
108
11- init ( startTime: Double , animation: Animation , trackingReference: TrackingReference ? = nil ) {
12- self . startTime = startTime
13- self . animation = animation
14- self . trackingReference = trackingReference
15- }
9+ mutating func animate( time: Double , additionalVector: AnimatableVector ? ) -> ( AnimatableVector , Bool ) ? {
10+ let result : AnimatableVector ?
1611
17- func reportLogicallyComplete( ) {
18- trackingReference? . tracker. reportLogicallyComplete ( trackingReference!. instanceID)
19- }
12+ let before = context. isLogicallyComplete
2013
21- func reportRemoved( ) {
22- trackingReference? . tracker. reportRemoved ( trackingReference!. instanceID)
23- }
24- }
25-
26- private struct RunningAnimation {
27- let instance : AnimationInstance
28- let target : AnimatableVector
29-
30- borrowing func animate( time: Double , context: inout AnimationContext , additionalVector: AnimatableVector ? ) -> AnimatableVector ? {
3114 if let additionalVector {
32- instance . animation. animate ( value: target + additionalVector, time: time - instance . startTime, context: & context)
15+ result = animation. animate ( value: target + additionalVector, time: time - startTime, context: & context)
3316 } else {
34- instance . animation. animate ( value: target, time: time - instance . startTime, context: & context)
17+ result = animation. animate ( value: target, time: time - startTime, context: & context)
3518 }
19+
20+ return result. map { ( $0, !before && context. isLogicallyComplete) }
21+ }
22+
23+ mutating func reportRemoved( ) {
24+ // TODO: maybe make this non-copyable and have a consuming remove func
25+ trackedInstance? . reportRemoved ( )
26+ }
27+
28+ mutating func reportLogicallyComplete( ) {
29+ trackedInstance? . reportLogicallyComplete ( )
3630 }
3731}
3832
@@ -42,7 +36,6 @@ struct AnimatedValue<Value: AnimatableVectorConvertible>: ~Copyable {
4236 private var currentAnimationValue : Value
4337
4438 private var animationBase : AnimatableVector
45- private var context : AnimationContext
4639
4740 var model : Value { borrowing get { currentTarget } }
4841 var presentation : Value { borrowing get { currentAnimationValue } }
@@ -52,50 +45,76 @@ struct AnimatedValue<Value: AnimatableVectorConvertible>: ~Copyable {
5245 self . animationBase = value. animatableVector
5346 self . currentTarget = value
5447 self . currentAnimationValue = value
55- self . context = AnimationContext ( )
5648 }
5749
5850 mutating func setValue( _ value: Value ) {
5951 self . animationBase = value. animatableVector
6052 self . currentTarget = value
6153 self . currentAnimationValue = value
54+ if !runningAnimations. isEmpty {
55+ removeAnimations ( upThrough: runningAnimations. endIndex - 1 , skipBaseUpdate: true )
56+ }
57+ }
6258
63- removeAnimations ( upThrough: runningAnimations. endIndex - 1 , skipBaseUpdate: true )
59+ mutating func cancelAnimation( ) {
60+ guard isAnimating else { return }
61+
62+ // setting value cancels all animations
63+ setValue ( currentTarget)
6464 }
6565
66- mutating func animate( to value: Value , animation: AnimationInstance ) {
67- self . progressToTime ( animation. startTime)
66+ mutating func animate( to value: Value , startTime: Double , animation: Animation , tracker: AnimationTracker ? = nil ) {
67+
68+ self . progressToTime ( startTime)
6869 var animationTarget = value. animatableVector - currentTarget. animatableVector
70+ var context = AnimationContext ( )
6971
7072 if let previous = runningAnimations. last {
71- let elapsedTime = animation. startTime - previous. instance. startTime
72- let shouldMerge = animation. animation. shouldMerge (
73- previous: previous. instance. animation,
73+ var previousContext = previous. context
74+ let elapsedTime = startTime - previous. startTime
75+ let shouldMerge = animation. shouldMerge (
76+ previous: previous. animation,
7477 value: previous. target,
7578 time: elapsedTime,
76- context: & context
79+ context: & previousContext
7780 )
7881
7982 if shouldMerge {
8083 self . animationBase = currentAnimationValue. animatableVector
8184 self . removeAnimations ( upThrough: runningAnimations. endIndex - 1 , skipBaseUpdate: true )
8285 animationTarget = value. animatableVector - self . animationBase
86+ context = previousContext
87+ // FIXME: this feels very hacky....
88+ context. isLogicallyComplete = false
8389 }
8490 }
8591
8692 self . currentTarget = value
87- runningAnimations. append ( RunningAnimation ( instance: animation, target: animationTarget) )
93+ runningAnimations. append (
94+ RunningAnimation (
95+ trackedInstance: tracker? . addAnimation ( ) ,
96+ animation: animation,
97+ startTime: startTime,
98+ target: animationTarget,
99+ context: context
100+ )
101+ )
88102 }
89103
90104 mutating func progressToTime( _ time: Double ) {
91105 guard isAnimating else { return }
92106
93- let ( animatedVector, finishedAnimationIndex) = calculateAnimationAtTime (
107+ let ( animatedVector, completedIndexes , finishedAnimationIndex) = calculateAnimationAtTime (
94108 time,
95- runningAnimations: runningAnimations,
96- context: & context
109+ runningAnimations: & runningAnimations[ ... ] ,
97110 )
98111
112+ // Report logical completion for any animations that became logically complete at this time.
113+ // This must happen here (mutating path), not inside calculateAnimationAtTime, so peeking stays non-mutating.
114+ for index in completedIndexes {
115+ runningAnimations [ index] . reportLogicallyComplete ( )
116+ }
117+
99118 if let finishedAnimationIndex {
100119 removeAnimations ( upThrough: finishedAnimationIndex)
101120 }
@@ -109,42 +128,52 @@ struct AnimatedValue<Value: AnimatableVectorConvertible>: ~Copyable {
109128 }
110129 }
111130
112- // TODO: figure out the shape for this
113- func peekFutureValues ( _ times: StrideThrough < Double > ) -> [ Value ] {
131+ // TODO: this can't be the best shape of this function...
132+ func peekFutureValuesUnlessCompletedOrFinished ( _ times: StrideThrough < Double > ) -> [ Value ] {
114133 var results : [ Value ] = [ ]
115- var contextCopy = context
116134 var runningAnimations = runningAnimations [ ... ]
117135 var base = animationBase
118136
119137 results. reserveCapacity ( times. underestimatedCount)
120138
121139 for time in times {
122- let ( animatedVector, completedIndex) = calculateAnimationAtTime (
140+ var shouldBailEarly = false
141+
142+ let ( animatedVector, completedIndexes, removedUpToIndex) = calculateAnimationAtTime (
123143 time,
124- runningAnimations: runningAnimations,
125- context: & contextCopy
144+ runningAnimations: & runningAnimations,
126145 )
127146
128- if let completedIndex {
129- for i in runningAnimations. startIndex... completedIndex {
147+ if !completedIndexes. isEmpty {
148+ shouldBailEarly = true
149+ }
150+
151+ if let removedUpToIndex {
152+ for i in runningAnimations. startIndex... removedUpToIndex {
130153 base += runningAnimations [ i] . target
131154 }
132- runningAnimations = runningAnimations [ ( completedIndex + 1 ) ... ]
155+ runningAnimations = runningAnimations [ ( removedUpToIndex + 1 ) ... ]
156+ shouldBailEarly = true
133157 }
134158
135159 if runningAnimations. isEmpty {
136160 results. append ( self . currentTarget)
137- break
161+ shouldBailEarly = true
162+ } else {
163+ results. append ( Value ( base + animatedVector) )
138164 }
139165
140- results. append ( Value ( base + animatedVector) )
166+ if shouldBailEarly {
167+ break
168+ }
141169 }
142170 return results
143171 }
144172
145173 private mutating func removeAnimations( upThrough index: Int , skipBaseUpdate: Bool = false ) {
146174 for i in 0 ... index {
147- runningAnimations [ i] . instance. reportRemoved ( )
175+ // NOTE: completion is triggered automatically on removal, no extra handling needed here
176+ runningAnimations [ i] . reportRemoved ( )
148177 if !skipBaseUpdate {
149178 self . animationBase += runningAnimations [ i] . target
150179 }
@@ -153,22 +182,22 @@ struct AnimatedValue<Value: AnimatableVectorConvertible>: ~Copyable {
153182 }
154183}
155184
156- private func calculateAnimationAtTime< AnimationList > (
185+ private func calculateAnimationAtTime(
157186 _ time: Double ,
158- runningAnimations: AnimationList ,
159- context: inout AnimationContext ,
160- ) -> ( animatedVector: AnimatableVector , finishedAnimationIndex: AnimationList . Index ? )
161- where AnimationList: Collection < RunningAnimation > {
187+ runningAnimations: inout ArraySlice < RunningAnimation >
188+ ) -> ( animatedVector: AnimatableVector , completedIndexes: [ Int ] , finishedAnimationIndex: Int ? ) {
162189 guard runningAnimations. count > 1 else {
163190 assert ( runningAnimations. first != nil , " Running animations should not be empty " )
164- if let vector = runningAnimations. first!. animate ( time: time, context: & context, additionalVector: nil ) {
165- return ( vector, nil )
191+ let index = runningAnimations. startIndex
192+ if let ( vector, logicallyCompleted) = runningAnimations [ index] . animate ( time: time, additionalVector: nil ) {
193+ return ( vector, logicallyCompleted ? [ index] : [ ] , nil )
166194 } else {
167- return ( runningAnimations. first! . target, runningAnimations . indices . first )
195+ return ( runningAnimations [ 0 ] . target, [ ] , index )
168196 }
169197 }
170198
171- var finishedAnimationIndex : AnimationList . Index ?
199+ var finishedAnimationIndex : Int ?
200+ var completedIndexes : [ Int ] = [ ]
172201 var index = runningAnimations. startIndex
173202
174203 let zero = AnimatableVector . zero ( runningAnimations. first!. target)
@@ -177,11 +206,12 @@ where AnimationList: Collection<RunningAnimation> {
177206 var carryOverVector = zero
178207
179208 while index < runningAnimations. endIndex {
180- let runningAnimation = runningAnimations [ index]
181-
182- if let vector = runningAnimation. animate ( time: time, context: & context, additionalVector: carryOverVector) {
209+ if let ( vector, logicallyCompleted) = runningAnimations [ index] . animate ( time: time, additionalVector: carryOverVector) {
183210 totalAnimationVector += vector
184- carryOverVector = runningAnimation. target - vector
211+ carryOverVector = runningAnimations [ index] . target - vector
212+ if logicallyCompleted {
213+ completedIndexes. append ( index)
214+ }
185215 } else {
186216 finishedAnimationIndex = index
187217 //totalAnimationVector = zero
@@ -191,7 +221,7 @@ where AnimationList: Collection<RunningAnimation> {
191221 runningAnimations. formIndex ( after: & index)
192222 }
193223
194- return ( totalAnimationVector, finishedAnimationIndex)
224+ return ( totalAnimationVector, completedIndexes , finishedAnimationIndex)
195225}
196226
197227internal extension AnimatedValue {
@@ -200,8 +230,15 @@ internal extension AnimatedValue {
200230
201231 let wasAnimating = isAnimating
202232
203- if let animation = context. transaction? . newAnimation ( at: context. currentFrameTime) {
204- self . animate ( to: value, animation: animation)
233+ if !context. transaction. disablesAnimation,
234+ let animation = context. transaction. animation
235+ {
236+ self . animate (
237+ to: value,
238+ startTime: context. currentFrameTime,
239+ animation: animation,
240+ tracker: context. transaction. _animationTracker
241+ )
205242 } else {
206243 self . setValue ( value)
207244 }
0 commit comments