Skip to content

Commit a6d645e

Browse files
authored
Add combineAnimation support (#460)
1 parent dc1f070 commit a6d645e

File tree

2 files changed

+111
-12
lines changed

2 files changed

+111
-12
lines changed

Sources/OpenSwiftUICore/Animation/Animatable/AnimatableAttribute.swift

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -672,15 +672,3 @@ private struct FrameVelocityFilter {
672672
previous = (time, rect)
673673
}
674674
}
675-
676-
// FIXME
677-
func combineAnimation<A>(
678-
into: inout Animation,
679-
state: inout AnimationState<A>,
680-
value: A,
681-
elapsed: Double,
682-
newAnimation: Animation,
683-
newValue: A
684-
) -> () where A: VectorArithmetic {
685-
_openSwiftUIUnimplementedFailure()
686-
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//
2+
// DefaultCombiningAnimation.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited: 6.5.4
6+
// Status: Complete
7+
// ID: 0E899C244938BDADF95265D65460D266 (SwiftUICore)
8+
9+
import Foundation
10+
11+
@_specialize(exported: false, kind: partial, where V == Double)
12+
@_specialize(exported: false, kind: partial, where V == AnimatablePair<AnimatablePair<CGFloat, CGFloat>, AnimatablePair<CGFloat, CGFloat>>)
13+
func combineAnimation<V>(
14+
into animation: inout Animation,
15+
state: inout AnimationState<V>,
16+
value: V,
17+
elapsed: Double,
18+
newAnimation: Animation,
19+
newValue: V
20+
) where V: VectorArithmetic {
21+
if var defaultCombiningAnimation = animation.as(DefaultCombiningAnimation.self) {
22+
state.combinedState.entries.append(.init(value: value + newValue, state: .init()))
23+
defaultCombiningAnimation.entries.append(.init(animation: newAnimation, elapsed: elapsed))
24+
animation = Animation(defaultCombiningAnimation)
25+
} else {
26+
var s = AnimationState<V>()
27+
s.combinedState.entries.append(.init(value: value, state: state))
28+
s.combinedState.entries.append(.init(value: value + newValue, state: .init()))
29+
state = s
30+
animation = Animation(DefaultCombiningAnimation(entries: [
31+
.init(animation: animation, elapsed: .zero),
32+
.init(animation: newAnimation, elapsed: elapsed)
33+
]))
34+
}
35+
}
36+
37+
extension AnimationState {
38+
fileprivate var combinedState: CombinedAnimationState<Value> {
39+
get { self[CombinedAnimationState<Value>.self] }
40+
set { self[CombinedAnimationState<Value>.self] = newValue }
41+
}
42+
}
43+
44+
struct CombinedAnimationState<Value>: AnimationStateKey where Value: VectorArithmetic {
45+
static var defaultValue: Self {
46+
.init(entries: [])
47+
}
48+
49+
struct Entry {
50+
var value: Value
51+
var state: AnimationState<Value>?
52+
}
53+
54+
var entries: [Entry]
55+
}
56+
57+
private struct DefaultCombiningAnimation: CustomAnimation {
58+
struct Entry: Hashable {
59+
var animation: Animation
60+
var elapsed: Double
61+
}
62+
63+
var entries: [Entry]
64+
65+
@_specialize(exported: false, kind: partial, where V == Double)
66+
@_specialize(exported: false, kind: partial, where V == AnimatablePair<AnimatablePair<CGFloat, CGFloat>, AnimatablePair<CGFloat, CGFloat>>)
67+
nonisolated func animate<V>(
68+
value: V,
69+
time: TimeInterval,
70+
context: inout AnimationContext<V>
71+
) -> V? where V: VectorArithmetic {
72+
let combinedStateEntryCount = context.state.combinedState.entries.count
73+
guard combinedStateEntryCount == entries.count else {
74+
return nil
75+
}
76+
var result: V = .zero
77+
for index in 0 ..< combinedStateEntryCount {
78+
let entry = entries[index]
79+
guard let combinedStateEntryState = context.state.combinedState.entries[index].state else {
80+
result = context.state.combinedState.entries[index].value
81+
continue
82+
}
83+
var entryContext = context
84+
entryContext.state = combinedStateEntryState
85+
var entryValue = context.state.combinedState.entries[index].value
86+
entryValue -= result
87+
let elapsed = time - entry.elapsed
88+
let entryAnimatedValue = entry.animation.animate(
89+
value: entryValue,
90+
time: elapsed,
91+
context: &entryContext
92+
)
93+
if let entryAnimatedValue {
94+
context.state.combinedState.entries[index].state = entryContext.state
95+
result += entryAnimatedValue
96+
} else {
97+
context.state.combinedState.entries[index].state = nil
98+
result += entryValue
99+
}
100+
if index == combinedStateEntryCount - 1 {
101+
context.isLogicallyComplete = entryContext.isLogicallyComplete
102+
if entryAnimatedValue == nil {
103+
return nil
104+
} else {
105+
return result
106+
}
107+
}
108+
}
109+
return nil
110+
}
111+
}

0 commit comments

Comments
 (0)