react-native-smooth-clip-view 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -5
- package/android/src/main/cpp/SmoothClipAndroid.h +1 -0
- package/android/src/main/cpp/SmoothClipBindings.cpp +8 -0
- package/android/src/main/cpp/SmoothClipRegistry.cpp +79 -4
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +6 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +37 -1
- package/cpp/SmoothClipAnimationCurve.h +8 -0
- package/ios/SmoothClipRegistry.mm +173 -17
- package/ios/SmoothClipView.mm +169 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,9 +107,9 @@ UI-runtime runs without retaining a consumer worklet for the animation lifetime.
|
|
|
107
107
|
It does not update React state, animated props, Yoga, the ShadowTree, or a public
|
|
108
108
|
SharedValue.
|
|
109
109
|
|
|
110
|
-
`beginInteraction()` atomically interrupts native motion and returns
|
|
111
|
-
raw presentation. Apply the final gesture sample with `setFrame()`
|
|
112
|
-
it when the release must start from that exact frame.
|
|
110
|
+
`beginInteraction()` atomically interrupts native motion and returns
|
|
111
|
+
its visible raw presentation. Apply the final gesture sample with `setFrame()`
|
|
112
|
+
before calling it when the release must start from that exact frame.
|
|
113
113
|
|
|
114
114
|
## React API
|
|
115
115
|
|
|
@@ -135,8 +135,21 @@ relative energy threshold `6e-9`. A spring is rejected when its resolved native
|
|
|
135
135
|
trajectory could make `contentScale` nonpositive.
|
|
136
136
|
|
|
137
137
|
An animation requested before the host has a positive layout waits, then starts
|
|
138
|
-
with its full duration.
|
|
139
|
-
|
|
138
|
+
with its full duration. While the application is inactive, native animation
|
|
139
|
+
transactions stay pending and do not report cancellation. Timing animations
|
|
140
|
+
retain wall-clock progress and catch up after activation. Springs stay frozen;
|
|
141
|
+
their first foreground step consumes at most 64 ms, matching Reanimated, and
|
|
142
|
+
then they continue normally. If the platform discarded an underlying animation
|
|
143
|
+
object, the transaction settles successfully at its requested target.
|
|
144
|
+
|
|
145
|
+
Interactive `setFrame()` updates remain application-owned. The library does not
|
|
146
|
+
reset a drag or mutate related SharedValues or React state when lifecycle state
|
|
147
|
+
changes, so applications do not need lifecycle integration for the clip itself.
|
|
148
|
+
|
|
149
|
+
Host loss while the application is active keeps the existing semantics:
|
|
150
|
+
standalone animations apply their target and finish successfully, while groups
|
|
151
|
+
(including public controllers because they use one-member groups) freeze every
|
|
152
|
+
member and resolve `false`. Replacement, cancellation, destruction, and
|
|
140
153
|
rejection also settle once with `false`.
|
|
141
154
|
|
|
142
155
|
## Atomic groups
|
|
@@ -54,6 +54,7 @@ void setViewLifecycleVisibilityAndroid(
|
|
|
54
54
|
uint64_t driverId,
|
|
55
55
|
facebook::jni::alias_ref<JSmoothClipView> view,
|
|
56
56
|
bool lifecycleVisible);
|
|
57
|
+
void setApplicationActiveAndroid(bool active);
|
|
57
58
|
|
|
58
59
|
// Advances the registry frame loop. Called from Kotlin (SmoothClipBindings)
|
|
59
60
|
// inside Choreographer#doFrame with the frame's vsync timestamp converted to
|
|
@@ -918,6 +918,12 @@ void nativeSetViewLifecycleVisibility(
|
|
|
918
918
|
static_cast<uint64_t>(driverId), view, lifecycleVisible != 0);
|
|
919
919
|
}
|
|
920
920
|
|
|
921
|
+
void nativeSetApplicationActive(
|
|
922
|
+
jni::alias_ref<jni::JObject>,
|
|
923
|
+
jboolean active) {
|
|
924
|
+
smoothclip::setApplicationActiveAndroid(active != 0);
|
|
925
|
+
}
|
|
926
|
+
|
|
921
927
|
void nativeInvalidate(jni::alias_ref<jni::JObject>) {
|
|
922
928
|
smoothclip::invalidateBindings();
|
|
923
929
|
}
|
|
@@ -942,6 +948,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
|
|
|
942
948
|
makeNativeMethod(
|
|
943
949
|
"nativeSetViewLifecycleVisibility",
|
|
944
950
|
nativeSetViewLifecycleVisibility),
|
|
951
|
+
makeNativeMethod(
|
|
952
|
+
"nativeSetApplicationActive", nativeSetApplicationActive),
|
|
945
953
|
makeNativeMethod("nativeInvalidate", nativeInvalidate),
|
|
946
954
|
makeNativeMethod("nativeOnFrame", nativeOnFrame),
|
|
947
955
|
});
|
|
@@ -166,6 +166,16 @@ std::vector<int32_t> &animatingGroups() {
|
|
|
166
166
|
return value;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
bool &applicationActiveState() {
|
|
170
|
+
static bool value = true;
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
std::optional<double> &applicationInactiveSinceS() {
|
|
175
|
+
static std::optional<double> value;
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
|
|
169
179
|
// The sink is written from the JS thread (installBindings / invalidate) and
|
|
170
180
|
// invoked on main. Invoking under the lock also keeps the owning bindings
|
|
171
181
|
// state alive for the duration of the callback.
|
|
@@ -639,6 +649,7 @@ void startPendingGroup(ActiveGroup &group) {
|
|
|
639
649
|
}
|
|
640
650
|
|
|
641
651
|
void reconcileGroupReadiness(uint64_t driverId) {
|
|
652
|
+
if (!applicationActiveState()) return;
|
|
642
653
|
const auto stateIterator = registry().find(driverId);
|
|
643
654
|
if (stateIterator == registry().end() || stateIterator->second.groupId == 0) {
|
|
644
655
|
return;
|
|
@@ -704,7 +715,8 @@ int32_t startAnimation(
|
|
|
704
715
|
// visible all read animation->current, giving a never-rendered pending-run
|
|
705
716
|
// freeze-at-start / replace-from-start semantics with no extra branches.
|
|
706
717
|
animation.current = animation.start;
|
|
707
|
-
animation.started =
|
|
718
|
+
animation.started =
|
|
719
|
+
applicationActiveState() && anyDisplayableView(state);
|
|
708
720
|
state.animation = std::move(animation);
|
|
709
721
|
for (ViewEntry &entry : state.views) {
|
|
710
722
|
entry.participation = state.animation->started && entryDisplayable(entry)
|
|
@@ -730,7 +742,8 @@ int32_t startAnimation(
|
|
|
730
742
|
}
|
|
731
743
|
|
|
732
744
|
void finishIfHostUnavailable(uint64_t driverId, DriverState &state) {
|
|
733
|
-
if (!
|
|
745
|
+
if (!applicationActiveState() || !state.animation.has_value() ||
|
|
746
|
+
!state.animation->started ||
|
|
734
747
|
anyDisplayableView(state)) {
|
|
735
748
|
return;
|
|
736
749
|
}
|
|
@@ -746,7 +759,7 @@ void reconcileViewDisplayability(
|
|
|
746
759
|
uint64_t driverId,
|
|
747
760
|
DriverState &state,
|
|
748
761
|
ViewEntry &entry) {
|
|
749
|
-
if (!state.animation.has_value()) return;
|
|
762
|
+
if (!applicationActiveState() || !state.animation.has_value()) return;
|
|
750
763
|
if (!state.animation->started) {
|
|
751
764
|
if (entryDisplayable(entry)) startPendingAnimation(driverId, state);
|
|
752
765
|
return;
|
|
@@ -892,6 +905,7 @@ void advanceGroup(int32_t groupId, ActiveGroup &group, double now) {
|
|
|
892
905
|
void onFrameImpl(double now) {
|
|
893
906
|
SMOOTH_CLIP_TRACE("SmoothClip.frame");
|
|
894
907
|
gFrameScheduled = false;
|
|
908
|
+
if (!applicationActiveState()) return;
|
|
895
909
|
// Snapshot: advance() clears live-list entries on completion. advance()
|
|
896
910
|
// never erases registry entries and never starts animations (completion
|
|
897
911
|
// delivery hops through CallInvoker::invokeAsync), so the find() guard
|
|
@@ -939,6 +953,66 @@ void onFrameAndroid(double frameTimeS) {
|
|
|
939
953
|
}
|
|
940
954
|
}
|
|
941
955
|
|
|
956
|
+
void setApplicationActiveAndroid(bool active) {
|
|
957
|
+
if (!isOnMainThread() || applicationActiveState() == active) return;
|
|
958
|
+
if (!active) {
|
|
959
|
+
applicationActiveState() = false;
|
|
960
|
+
applicationInactiveSinceS() = nowSeconds();
|
|
961
|
+
gFrameScheduled = false;
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const double now = nowSeconds();
|
|
966
|
+
const double inactiveDurationS = applicationInactiveSinceS().has_value()
|
|
967
|
+
? std::max(0.0, now - *applicationInactiveSinceS())
|
|
968
|
+
: 0.0;
|
|
969
|
+
applicationInactiveSinceS().reset();
|
|
970
|
+
applicationActiveState() = true;
|
|
971
|
+
|
|
972
|
+
// Reanimated's spring frame delta includes the inactive interval and is
|
|
973
|
+
// clamped to 64 ms, but its termination is energy-based rather than a wall
|
|
974
|
+
// clock timeout. Keep lastFrameS unchanged so the first resumed step sees
|
|
975
|
+
// that clamped delta, while moving only the safety-cap origin forward so an
|
|
976
|
+
// ordinary background interval cannot terminate the spring by itself.
|
|
977
|
+
if (inactiveDurationS > 0) {
|
|
978
|
+
for (auto &[driverId, state] : registry()) {
|
|
979
|
+
(void)driverId;
|
|
980
|
+
if (state.groupId == 0 && state.animation.has_value() &&
|
|
981
|
+
state.animation->started &&
|
|
982
|
+
state.animation->kind == AnimationKind::Spring) {
|
|
983
|
+
state.animation->startedAtS = rebaseSpringStartAfterInactivity(
|
|
984
|
+
state.animation->startedAtS, inactiveDurationS);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
for (auto &[groupId, group] : groupRegistry()) {
|
|
988
|
+
(void)groupId;
|
|
989
|
+
if (!group.started || group.kind != AnimationKind::Spring) continue;
|
|
990
|
+
group.startedAtS = rebaseSpringStartAfterInactivity(
|
|
991
|
+
group.startedAtS, inactiveDurationS);
|
|
992
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
993
|
+
member.animation.startedAtS = rebaseSpringStartAfterInactivity(
|
|
994
|
+
member.animation.startedAtS, inactiveDurationS);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
for (auto &[driverId, state] : registry()) {
|
|
1000
|
+
if (state.groupId == 0 && state.animation.has_value() &&
|
|
1001
|
+
!state.animation->started && anyDisplayableView(state)) {
|
|
1002
|
+
startPendingAnimation(driverId, state);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
for (auto &[groupId, group] : groupRegistry()) {
|
|
1006
|
+
(void)groupId;
|
|
1007
|
+
if (!group.started && allGroupMembersReady(group)) {
|
|
1008
|
+
startPendingGroup(group);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (!animatingDrivers().empty() || !animatingGroups().empty()) {
|
|
1012
|
+
scheduleFrame();
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
942
1016
|
// --- Public smoothclip:: interface (shared with iOS via SmoothClipRegistry.h)
|
|
943
1017
|
|
|
944
1018
|
void setCompletionCallback(const void *owner, CompletionCallback callback) {
|
|
@@ -1241,7 +1315,8 @@ int32_t startGroupCommon(
|
|
|
1241
1315
|
member.animation.lastFrameS = stamp.startedAtS;
|
|
1242
1316
|
member.animation.frameClockAnchored = stamp.frameClockAnchored;
|
|
1243
1317
|
}
|
|
1244
|
-
group.started =
|
|
1318
|
+
group.started =
|
|
1319
|
+
applicationActiveState() && allGroupMembersReady(group);
|
|
1245
1320
|
groupRegistry().emplace(groupId, std::move(group));
|
|
1246
1321
|
if (reduce) {
|
|
1247
1322
|
finishGroupImpl(groupId, true, true);
|
|
@@ -77,6 +77,8 @@ internal object SmoothClipBindings {
|
|
|
77
77
|
lifecycleVisible: Boolean,
|
|
78
78
|
)
|
|
79
79
|
|
|
80
|
+
external fun nativeSetApplicationActive(active: Boolean)
|
|
81
|
+
|
|
80
82
|
/**
|
|
81
83
|
* Releases runtime-bound listener state during host teardown; must run
|
|
82
84
|
* while the JS runtime is still alive (module invalidate).
|
|
@@ -99,6 +101,10 @@ internal object SmoothClipBindings {
|
|
|
99
101
|
Choreographer.getInstance().postFrameCallback(frameCallback)
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
fun pauseFrameLoop() {
|
|
105
|
+
Choreographer.getInstance().removeFrameCallback(frameCallback)
|
|
106
|
+
}
|
|
107
|
+
|
|
102
108
|
/** Advances the frame loop with the doFrame vsync timestamp. */
|
|
103
109
|
external fun nativeOnFrame(frameTimeNanos: Long)
|
|
104
110
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
package com.smoothclipview
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
4
5
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
6
|
import com.facebook.react.bridge.ReadableArray
|
|
6
7
|
import com.facebook.react.bridge.UiThreadUtil
|
|
7
8
|
import com.facebook.react.bridge.WritableArray
|
|
9
|
+
import com.facebook.react.common.LifecycleState
|
|
8
10
|
import com.facebook.react.module.annotations.ReactModule
|
|
9
11
|
import com.facebook.react.turbomodule.core.interfaces.BindingsInstallerHolder
|
|
10
12
|
import com.facebook.react.turbomodule.core.interfaces.TurboModuleWithJSIBindings
|
|
@@ -17,12 +19,46 @@ import com.facebook.react.turbomodule.core.interfaces.TurboModuleWithJSIBindings
|
|
|
17
19
|
*/
|
|
18
20
|
@ReactModule(name = NativeSmoothClipModuleSpec.NAME)
|
|
19
21
|
class SmoothClipModule(context: ReactApplicationContext) :
|
|
20
|
-
NativeSmoothClipModuleSpec(context),
|
|
22
|
+
NativeSmoothClipModuleSpec(context),
|
|
23
|
+
TurboModuleWithJSIBindings,
|
|
24
|
+
LifecycleEventListener {
|
|
25
|
+
|
|
26
|
+
init {
|
|
27
|
+
context.addLifecycleEventListener(this)
|
|
28
|
+
setApplicationActive(context.lifecycleState == LifecycleState.RESUMED)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private fun setApplicationActive(active: Boolean) {
|
|
32
|
+
val updateState = {
|
|
33
|
+
if (!active) {
|
|
34
|
+
SmoothClipBindings.pauseFrameLoop()
|
|
35
|
+
}
|
|
36
|
+
SmoothClipBindings.nativeSetApplicationActive(active)
|
|
37
|
+
}
|
|
38
|
+
if (UiThreadUtil.isOnUiThread()) {
|
|
39
|
+
updateState()
|
|
40
|
+
} else {
|
|
41
|
+
UiThreadUtil.runOnUiThread(updateState)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override fun onHostResume() {
|
|
46
|
+
setApplicationActive(true)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
override fun onHostPause() {
|
|
50
|
+
setApplicationActive(false)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun onHostDestroy() {
|
|
54
|
+
setApplicationActive(false)
|
|
55
|
+
}
|
|
21
56
|
|
|
22
57
|
override fun getBindingsInstaller(): BindingsInstallerHolder =
|
|
23
58
|
SmoothClipBindings.getBindingsInstaller()
|
|
24
59
|
|
|
25
60
|
override fun invalidate() {
|
|
61
|
+
reactApplicationContext.removeLifecycleEventListener(this)
|
|
26
62
|
super.invalidate()
|
|
27
63
|
// Completions must stop and runtime-bound listener functions must be
|
|
28
64
|
// released before the JS runtime is torn down; otherwise a later host
|
|
@@ -91,6 +91,14 @@ inline double timingFraction(
|
|
|
91
91
|
return clamp01((nowS - startedAtS) / durationS);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// Springs clamp a long frame delta, so inactive wall time must remain visible
|
|
95
|
+
// to lastFrameS. Only the independent safety-cap origin moves forward.
|
|
96
|
+
inline double rebaseSpringStartAfterInactivity(
|
|
97
|
+
double startedAtS,
|
|
98
|
+
double inactiveDurationS) {
|
|
99
|
+
return startedAtS + std::max(0.0, inactiveDurationS);
|
|
100
|
+
}
|
|
101
|
+
|
|
94
102
|
// --- Timing curve --------------------------------------------------------
|
|
95
103
|
|
|
96
104
|
inline double cubicBezierParameterForX(
|
|
@@ -44,6 +44,12 @@
|
|
|
44
44
|
animationId:(int32_t)animationId
|
|
45
45
|
sharedBeginTime:(CFTimeInterval)sharedBeginTime;
|
|
46
46
|
- (void)smoothClipCancelAnimationUsingTarget:(BOOL)useTarget;
|
|
47
|
+
- (BOOL)smoothClipPauseSpringAnimation:(int32_t)animationId
|
|
48
|
+
atMediaTime:(CFTimeInterval)mediaTime;
|
|
49
|
+
- (BOOL)smoothClipResumeSpringAnimation:(int32_t)animationId
|
|
50
|
+
atMediaTime:(CFTimeInterval)mediaTime
|
|
51
|
+
catchUpBy:(CFTimeInterval)catchUp;
|
|
52
|
+
- (BOOL)smoothClipHasInstalledAnimation:(int32_t)animationId;
|
|
47
53
|
- (BOOL)smoothClipIsJoinable;
|
|
48
54
|
- (BOOL)smoothClipCanDisplay;
|
|
49
55
|
- (double)smoothClipInheritedVelocityToPresentation:
|
|
@@ -76,6 +82,8 @@ struct ActiveAnimation {
|
|
|
76
82
|
// False while a pre-ready run waits for its first displayable host. Host
|
|
77
83
|
// loss after the run starts finishes it at the target.
|
|
78
84
|
bool started = false;
|
|
85
|
+
bool exposedToInactivity = false;
|
|
86
|
+
bool needsForegroundReconciliation = false;
|
|
79
87
|
};
|
|
80
88
|
|
|
81
89
|
struct DriverState {
|
|
@@ -155,6 +163,11 @@ bool &applicationActiveState() {
|
|
|
155
163
|
return active;
|
|
156
164
|
}
|
|
157
165
|
|
|
166
|
+
std::optional<CFTimeInterval> &applicationInactiveSince() {
|
|
167
|
+
static std::optional<CFTimeInterval> value;
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
|
|
158
171
|
CompletionSink &completionSink() {
|
|
159
172
|
static CompletionSink sink;
|
|
160
173
|
return sink;
|
|
@@ -185,6 +198,113 @@ std::vector<DriverSnapshot> cancelGroupInternal(
|
|
|
185
198
|
void tryStartGroup(int32_t groupId);
|
|
186
199
|
void finishGroupForHostLoss(int32_t groupId);
|
|
187
200
|
DriverSnapshot snapshotForDriver(uint64_t driverId);
|
|
201
|
+
SmoothClipView *viewForKey(ViewKey key);
|
|
202
|
+
|
|
203
|
+
bool animationNeedsForegroundReconciliation(const DriverState &state) {
|
|
204
|
+
if (!state.animation.has_value()) return false;
|
|
205
|
+
const ActiveAnimation &animation = *state.animation;
|
|
206
|
+
if (animation.needsForegroundReconciliation) return true;
|
|
207
|
+
if (!animation.exposedToInactivity) return false;
|
|
208
|
+
if (animation.participants.empty()) return true;
|
|
209
|
+
for (const ViewKey key : animation.participants) {
|
|
210
|
+
if (![viewForKey(key)
|
|
211
|
+
smoothClipHasInstalledAnimation:animation.animationId]) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
bool groupNeedsForegroundReconciliation(int32_t groupId) {
|
|
219
|
+
const auto groupIterator = groupRegistry().find(groupId);
|
|
220
|
+
if (groupIterator == groupRegistry().end()) return false;
|
|
221
|
+
for (const uint64_t driverId : groupIterator->second.driverIds) {
|
|
222
|
+
const auto stateIterator = registry().find(driverId);
|
|
223
|
+
if (stateIterator != registry().end() &&
|
|
224
|
+
stateIterator->second.animation.has_value() &&
|
|
225
|
+
stateIterator->second.animation->groupId == groupId &&
|
|
226
|
+
animationNeedsForegroundReconciliation(stateIterator->second)) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
bool pauseSpringForInactivity(
|
|
234
|
+
DriverState &state,
|
|
235
|
+
CFTimeInterval mediaTime) {
|
|
236
|
+
if (!state.animation.has_value() || !state.animation->started ||
|
|
237
|
+
state.animation->kind != AnimationKind::Spring) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
ActiveAnimation &animation = *state.animation;
|
|
241
|
+
if (animation.participants.empty()) {
|
|
242
|
+
animation.needsForegroundReconciliation = true;
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
bool paused = true;
|
|
246
|
+
for (const ViewKey key : animation.participants) {
|
|
247
|
+
paused = [viewForKey(key)
|
|
248
|
+
smoothClipPauseSpringAnimation:animation.animationId
|
|
249
|
+
atMediaTime:mediaTime] && paused;
|
|
250
|
+
}
|
|
251
|
+
if (!paused) animation.needsForegroundReconciliation = true;
|
|
252
|
+
return paused;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
bool resumeSpringAfterInactivity(
|
|
256
|
+
DriverState &state,
|
|
257
|
+
CFTimeInterval mediaTime,
|
|
258
|
+
CFTimeInterval catchUp) {
|
|
259
|
+
if (!state.animation.has_value() || !state.animation->started ||
|
|
260
|
+
state.animation->kind != AnimationKind::Spring) {
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
ActiveAnimation &animation = *state.animation;
|
|
264
|
+
bool resumed = !animation.participants.empty();
|
|
265
|
+
for (const ViewKey key : animation.participants) {
|
|
266
|
+
resumed = [viewForKey(key)
|
|
267
|
+
smoothClipResumeSpringAnimation:animation.animationId
|
|
268
|
+
atMediaTime:mediaTime
|
|
269
|
+
catchUpBy:catchUp] && resumed;
|
|
270
|
+
}
|
|
271
|
+
if (!resumed) animation.needsForegroundReconciliation = true;
|
|
272
|
+
return resumed;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
bool resumeGroupSpringsAfterInactivity(
|
|
276
|
+
int32_t groupId,
|
|
277
|
+
CFTimeInterval mediaTime,
|
|
278
|
+
CFTimeInterval catchUp) {
|
|
279
|
+
const auto groupIterator = groupRegistry().find(groupId);
|
|
280
|
+
if (groupIterator == groupRegistry().end()) return false;
|
|
281
|
+
bool resumed = true;
|
|
282
|
+
for (const uint64_t driverId : groupIterator->second.driverIds) {
|
|
283
|
+
const auto stateIterator = registry().find(driverId);
|
|
284
|
+
if (stateIterator == registry().end() ||
|
|
285
|
+
!stateIterator->second.animation.has_value() ||
|
|
286
|
+
stateIterator->second.animation->groupId != groupId) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
resumed = resumeSpringAfterInactivity(
|
|
290
|
+
stateIterator->second, mediaTime, catchUp) && resumed;
|
|
291
|
+
}
|
|
292
|
+
return resumed;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
void clearGroupInactivityExposure(int32_t groupId) {
|
|
296
|
+
const auto groupIterator = groupRegistry().find(groupId);
|
|
297
|
+
if (groupIterator == groupRegistry().end()) return;
|
|
298
|
+
for (const uint64_t driverId : groupIterator->second.driverIds) {
|
|
299
|
+
const auto stateIterator = registry().find(driverId);
|
|
300
|
+
if (stateIterator != registry().end() &&
|
|
301
|
+
stateIterator->second.animation.has_value() &&
|
|
302
|
+
stateIterator->second.animation->groupId == groupId) {
|
|
303
|
+
stateIterator->second.animation->exposedToInactivity = false;
|
|
304
|
+
stateIterator->second.animation->needsForegroundReconciliation = false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
188
308
|
|
|
189
309
|
Presentation unavailablePresentation() {
|
|
190
310
|
// Non-finite geometry makes the JS side fall back to its current value
|
|
@@ -647,6 +767,7 @@ void tryStartGroup(int32_t groupId) {
|
|
|
647
767
|
if (stateIterator == registry().end() ||
|
|
648
768
|
!stateIterator->second.animation.has_value() ||
|
|
649
769
|
stateIterator->second.animation->groupId != groupId ||
|
|
770
|
+
stateIterator->second.animation->started ||
|
|
650
771
|
!driverReady(stateIterator->second)) {
|
|
651
772
|
return;
|
|
652
773
|
}
|
|
@@ -689,19 +810,14 @@ bool applicationIsActive() {
|
|
|
689
810
|
void applicationWillResignActive() {
|
|
690
811
|
NSCAssert(NSThread.isMainThread, @"SmoothClip registry is main-thread only");
|
|
691
812
|
if (!applicationActiveState()) return;
|
|
813
|
+
const CFTimeInterval mediaTime = CACurrentMediaTime();
|
|
692
814
|
applicationActiveState() = false;
|
|
693
|
-
|
|
694
|
-
groupIds.reserve(groupRegistry().size());
|
|
695
|
-
for (const auto &[groupId, group] : groupRegistry()) {
|
|
696
|
-
(void)group;
|
|
697
|
-
groupIds.push_back(groupId);
|
|
698
|
-
}
|
|
699
|
-
for (const int32_t groupId : groupIds) {
|
|
700
|
-
finishGroupForHostLoss(groupId);
|
|
701
|
-
}
|
|
815
|
+
applicationInactiveSince() = mediaTime;
|
|
702
816
|
for (auto &[driverId, state] : registry()) {
|
|
703
|
-
|
|
704
|
-
|
|
817
|
+
(void)driverId;
|
|
818
|
+
if (state.animation.has_value() && state.animation->started) {
|
|
819
|
+
state.animation->exposedToInactivity = true;
|
|
820
|
+
pauseSpringForInactivity(state, mediaTime);
|
|
705
821
|
}
|
|
706
822
|
}
|
|
707
823
|
}
|
|
@@ -709,6 +825,14 @@ void applicationWillResignActive() {
|
|
|
709
825
|
void applicationDidBecomeActive() {
|
|
710
826
|
NSCAssert(NSThread.isMainThread, @"SmoothClip registry is main-thread only");
|
|
711
827
|
if (applicationActiveState()) return;
|
|
828
|
+
const CFTimeInterval mediaTime = CACurrentMediaTime();
|
|
829
|
+
const CFTimeInterval inactiveDuration = applicationInactiveSince().has_value()
|
|
830
|
+
? std::max((CFTimeInterval)0,
|
|
831
|
+
mediaTime - *applicationInactiveSince())
|
|
832
|
+
: 0;
|
|
833
|
+
const CFTimeInterval springCatchUp =
|
|
834
|
+
std::min((CFTimeInterval)0.064, inactiveDuration);
|
|
835
|
+
applicationInactiveSince().reset();
|
|
712
836
|
applicationActiveState() = true;
|
|
713
837
|
std::vector<int32_t> groupIds;
|
|
714
838
|
groupIds.reserve(groupRegistry().size());
|
|
@@ -717,13 +841,35 @@ void applicationDidBecomeActive() {
|
|
|
717
841
|
groupIds.push_back(groupId);
|
|
718
842
|
}
|
|
719
843
|
for (const int32_t groupId : groupIds) {
|
|
720
|
-
|
|
844
|
+
bool needsReconciliation = groupNeedsForegroundReconciliation(groupId);
|
|
845
|
+
if (!needsReconciliation) {
|
|
846
|
+
needsReconciliation = !resumeGroupSpringsAfterInactivity(
|
|
847
|
+
groupId, mediaTime, springCatchUp);
|
|
848
|
+
}
|
|
849
|
+
if (needsReconciliation) {
|
|
850
|
+
cancelGroupInternal(groupId, GroupCancelBehavior::Finish, true);
|
|
851
|
+
} else {
|
|
852
|
+
clearGroupInactivityExposure(groupId);
|
|
853
|
+
tryStartGroup(groupId);
|
|
854
|
+
}
|
|
721
855
|
}
|
|
722
856
|
for (auto &[driverId, state] : registry()) {
|
|
723
|
-
(
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
857
|
+
if (!state.animation.has_value() || state.animation->groupId != 0) {
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
bool needsReconciliation = animationNeedsForegroundReconciliation(state);
|
|
861
|
+
if (!needsReconciliation) {
|
|
862
|
+
needsReconciliation = !resumeSpringAfterInactivity(
|
|
863
|
+
state, mediaTime, springCatchUp);
|
|
864
|
+
}
|
|
865
|
+
if (needsReconciliation) {
|
|
866
|
+
finishStandaloneAtTarget(driverId, state);
|
|
867
|
+
} else {
|
|
868
|
+
state.animation->exposedToInactivity = false;
|
|
869
|
+
state.animation->needsForegroundReconciliation = false;
|
|
870
|
+
if (!state.animation->started && anyDisplayableView(state)) {
|
|
871
|
+
startPendingAnimation(state);
|
|
872
|
+
}
|
|
727
873
|
}
|
|
728
874
|
}
|
|
729
875
|
}
|
|
@@ -818,6 +964,9 @@ void viewDisplayabilityChanged(uint64_t driverId, SmoothClipView *view) {
|
|
|
818
964
|
return;
|
|
819
965
|
}
|
|
820
966
|
if (![view smoothClipCanDisplay]) {
|
|
967
|
+
if (!applicationActiveState()) {
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
821
970
|
if (state.animation->groupId != 0 && state.animation->started) {
|
|
822
971
|
finishGroupForHostLoss(state.animation->groupId);
|
|
823
972
|
} else if (state.animation->started) {
|
|
@@ -841,7 +990,10 @@ void unregisterView(uint64_t driverId, SmoothClipView *view) {
|
|
|
841
990
|
DriverState &state = iterator->second;
|
|
842
991
|
const ViewKey key = keyForView(view);
|
|
843
992
|
if (state.animation.has_value()) {
|
|
844
|
-
if (
|
|
993
|
+
if (!applicationActiveState() && state.animation->started) {
|
|
994
|
+
state.animation->participants.erase(key);
|
|
995
|
+
state.animation->needsForegroundReconciliation = true;
|
|
996
|
+
} else if (state.animation->groupId != 0 && state.animation->started) {
|
|
845
997
|
finishGroupForHostLoss(state.animation->groupId);
|
|
846
998
|
} else if (state.animation->groupId == 0 && state.animation->started) {
|
|
847
999
|
finishStandaloneAtTarget(driverId, state);
|
|
@@ -1611,6 +1763,10 @@ void viewAnimationDidStop(
|
|
|
1611
1763
|
|
|
1612
1764
|
const ViewKey key = keyForView(view);
|
|
1613
1765
|
if (state.animation->participants.erase(key) == 0) return;
|
|
1766
|
+
if (!applicationActiveState() || state.animation->exposedToInactivity) {
|
|
1767
|
+
state.animation->needsForegroundReconciliation = true;
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1614
1770
|
state.animation->finished = state.animation->finished && finished;
|
|
1615
1771
|
finishIfNoInstalledParticipants(driverId, state);
|
|
1616
1772
|
}
|
package/ios/SmoothClipView.mm
CHANGED
|
@@ -365,6 +365,9 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
365
365
|
smoothclip::TimingAnimation _timingAnimation;
|
|
366
366
|
smoothclip::SpringAnimation _springAnimation;
|
|
367
367
|
SmoothClipAnimationDelegate *_animationDelegate;
|
|
368
|
+
BOOL _springPausedForInactivity;
|
|
369
|
+
BOOL _pausedSpringHadMaskAnimation;
|
|
370
|
+
BOOL _pausedSpringHadShadowAnimation;
|
|
368
371
|
|
|
369
372
|
// 'inherit' velocity samples (raw canonical geometry, per view); recording/
|
|
370
373
|
// coalescing/projection live in the shared cpp/SmoothClipVelocityTracker.h
|
|
@@ -445,6 +448,9 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
445
448
|
_ignoreAnimationCallback = NO;
|
|
446
449
|
_activeAnimationId = 0;
|
|
447
450
|
_activeAnimationKind = 0;
|
|
451
|
+
_springPausedForInactivity = NO;
|
|
452
|
+
_pausedSpringHadMaskAnimation = NO;
|
|
453
|
+
_pausedSpringHadShadowAnimation = NO;
|
|
448
454
|
smoothclip::clearVelocitySamples(_velocitySamples);
|
|
449
455
|
// App-state transitions are observed by the registry itself (see
|
|
450
456
|
// installApplicationStateObservers): the active flag is process-global,
|
|
@@ -927,6 +933,9 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
927
933
|
_activeAnimationId = 0;
|
|
928
934
|
_activeAnimationKind = 0;
|
|
929
935
|
_animationDelegate = nil;
|
|
936
|
+
_springPausedForInactivity = NO;
|
|
937
|
+
_pausedSpringHadMaskAnimation = NO;
|
|
938
|
+
_pausedSpringHadShadowAnimation = NO;
|
|
930
939
|
}
|
|
931
940
|
|
|
932
941
|
- (CABasicAnimation *)basicAnimationForKeyPath:(NSString *)keyPath
|
|
@@ -1318,6 +1327,12 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1318
1327
|
maskAnimation.duration = group.duration;
|
|
1319
1328
|
}
|
|
1320
1329
|
|
|
1330
|
+
// An explicit media-time origin is required to pause and resume spring
|
|
1331
|
+
// groups without touching the parent layer clock (which would also pause
|
|
1332
|
+
// consumer animations in the clipped subtree).
|
|
1333
|
+
if (sharedBeginTime <= 0) {
|
|
1334
|
+
sharedBeginTime = CACurrentMediaTime();
|
|
1335
|
+
}
|
|
1321
1336
|
if (sharedBeginTime > 0) {
|
|
1322
1337
|
// Each layer receives the same absolute media timestamp translated into
|
|
1323
1338
|
// its local clock. Group participants therefore advance from one epoch
|
|
@@ -1343,6 +1358,9 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1343
1358
|
_animationDelegate.driverId = _driverId;
|
|
1344
1359
|
_animationDelegate.animationId = _activeAnimationId;
|
|
1345
1360
|
group.delegate = _animationDelegate;
|
|
1361
|
+
_springPausedForInactivity = NO;
|
|
1362
|
+
_pausedSpringHadMaskAnimation = NO;
|
|
1363
|
+
_pausedSpringHadShadowAnimation = NO;
|
|
1346
1364
|
[layer addAnimation:group forKey:@"smoothClip.geometry"];
|
|
1347
1365
|
[_contentContainer.layer addAnimation:contentGroup
|
|
1348
1366
|
forKey:@"smoothClip.content"];
|
|
@@ -1465,6 +1483,154 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1465
1483
|
}
|
|
1466
1484
|
}
|
|
1467
1485
|
|
|
1486
|
+
- (BOOL)smoothClipPauseSpringAnimation:(int32_t)animationId
|
|
1487
|
+
atMediaTime:(CFTimeInterval)mediaTime {
|
|
1488
|
+
if (_activeAnimationId != animationId || _activeAnimationKind != 2 ||
|
|
1489
|
+
_springPausedForInactivity) {
|
|
1490
|
+
return NO;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
CALayer *geometryLayer = _clipContainer.layer;
|
|
1494
|
+
CALayer *contentLayer = _contentContainer.layer;
|
|
1495
|
+
CAAnimation *installedGeometry =
|
|
1496
|
+
[geometryLayer animationForKey:@"smoothClip.geometry"];
|
|
1497
|
+
CAAnimation *installedContent =
|
|
1498
|
+
[contentLayer animationForKey:@"smoothClip.content"];
|
|
1499
|
+
if (installedGeometry == nil || installedContent == nil) return NO;
|
|
1500
|
+
|
|
1501
|
+
auto pausedCopy = ^CAAnimation *(CALayer *layer, CAAnimation *installed) {
|
|
1502
|
+
if (installed == nil) return (CAAnimation *)nil;
|
|
1503
|
+
CAAnimation *paused = [installed copy];
|
|
1504
|
+
const CFTimeInterval localNow =
|
|
1505
|
+
[layer convertTime:mediaTime fromLayer:nil];
|
|
1506
|
+
const CFTimeInterval elapsed = std::clamp(
|
|
1507
|
+
localNow - installed.beginTime,
|
|
1508
|
+
(CFTimeInterval)0,
|
|
1509
|
+
installed.duration);
|
|
1510
|
+
paused.speed = 0;
|
|
1511
|
+
paused.timeOffset = elapsed;
|
|
1512
|
+
paused.beginTime = 0;
|
|
1513
|
+
return paused;
|
|
1514
|
+
};
|
|
1515
|
+
|
|
1516
|
+
CAAnimation *geometry = pausedCopy(geometryLayer, installedGeometry);
|
|
1517
|
+
CAAnimation *content = pausedCopy(contentLayer, installedContent);
|
|
1518
|
+
CAAnimation *installedMask =
|
|
1519
|
+
[_unequalCornerMask animationForKey:@"smoothClip.mask"];
|
|
1520
|
+
CAAnimation *mask = pausedCopy(_unequalCornerMask, installedMask);
|
|
1521
|
+
CAAnimation *installedShadow =
|
|
1522
|
+
[_shadowLayer animationForKey:@"smoothClip.shadow"];
|
|
1523
|
+
CAAnimation *shadow = pausedCopy(_shadowLayer, installedShadow);
|
|
1524
|
+
|
|
1525
|
+
_animationDelegate.invalidated = YES;
|
|
1526
|
+
_ignoreAnimationCallback = YES;
|
|
1527
|
+
[geometryLayer removeAnimationForKey:@"smoothClip.geometry"];
|
|
1528
|
+
[contentLayer removeAnimationForKey:@"smoothClip.content"];
|
|
1529
|
+
[_unequalCornerMask removeAnimationForKey:@"smoothClip.mask"];
|
|
1530
|
+
[_shadowLayer removeAnimationForKey:@"smoothClip.shadow"];
|
|
1531
|
+
|
|
1532
|
+
_animationDelegate = [SmoothClipAnimationDelegate new];
|
|
1533
|
+
_animationDelegate.view = self;
|
|
1534
|
+
_animationDelegate.driverId = _driverId;
|
|
1535
|
+
_animationDelegate.animationId = animationId;
|
|
1536
|
+
geometry.delegate = _animationDelegate;
|
|
1537
|
+
[geometryLayer addAnimation:geometry forKey:@"smoothClip.geometry"];
|
|
1538
|
+
[contentLayer addAnimation:content forKey:@"smoothClip.content"];
|
|
1539
|
+
if (mask != nil) {
|
|
1540
|
+
[_unequalCornerMask addAnimation:mask forKey:@"smoothClip.mask"];
|
|
1541
|
+
}
|
|
1542
|
+
if (shadow != nil) {
|
|
1543
|
+
[_shadowLayer addAnimation:shadow forKey:@"smoothClip.shadow"];
|
|
1544
|
+
}
|
|
1545
|
+
_ignoreAnimationCallback = NO;
|
|
1546
|
+
_springPausedForInactivity = YES;
|
|
1547
|
+
_pausedSpringHadMaskAnimation = mask != nil;
|
|
1548
|
+
_pausedSpringHadShadowAnimation = shadow != nil;
|
|
1549
|
+
return YES;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
- (BOOL)smoothClipResumeSpringAnimation:(int32_t)animationId
|
|
1553
|
+
atMediaTime:(CFTimeInterval)mediaTime
|
|
1554
|
+
catchUpBy:(CFTimeInterval)catchUp {
|
|
1555
|
+
if (_activeAnimationId != animationId || _activeAnimationKind != 2 ||
|
|
1556
|
+
!_springPausedForInactivity) {
|
|
1557
|
+
return NO;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
CALayer *geometryLayer = _clipContainer.layer;
|
|
1561
|
+
CALayer *contentLayer = _contentContainer.layer;
|
|
1562
|
+
CAAnimation *installedGeometry =
|
|
1563
|
+
[geometryLayer animationForKey:@"smoothClip.geometry"];
|
|
1564
|
+
CAAnimation *installedContent =
|
|
1565
|
+
[contentLayer animationForKey:@"smoothClip.content"];
|
|
1566
|
+
CAAnimation *installedMask =
|
|
1567
|
+
[_unequalCornerMask animationForKey:@"smoothClip.mask"];
|
|
1568
|
+
CAAnimation *installedShadow =
|
|
1569
|
+
[_shadowLayer animationForKey:@"smoothClip.shadow"];
|
|
1570
|
+
if (installedGeometry == nil || installedContent == nil ||
|
|
1571
|
+
(_pausedSpringHadMaskAnimation && installedMask == nil) ||
|
|
1572
|
+
(_pausedSpringHadShadowAnimation && installedShadow == nil)) {
|
|
1573
|
+
return NO;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
auto resumedCopy = ^CAAnimation *(CALayer *layer, CAAnimation *installed) {
|
|
1577
|
+
if (installed == nil) return (CAAnimation *)nil;
|
|
1578
|
+
CAAnimation *resumed = [installed copy];
|
|
1579
|
+
const CFTimeInterval localNow =
|
|
1580
|
+
[layer convertTime:mediaTime fromLayer:nil];
|
|
1581
|
+
const CFTimeInterval resumedTime = std::min(
|
|
1582
|
+
installed.duration,
|
|
1583
|
+
std::max((CFTimeInterval)0, installed.timeOffset + catchUp));
|
|
1584
|
+
resumed.speed = 1;
|
|
1585
|
+
resumed.timeOffset = 0;
|
|
1586
|
+
resumed.beginTime = localNow - resumedTime;
|
|
1587
|
+
return resumed;
|
|
1588
|
+
};
|
|
1589
|
+
|
|
1590
|
+
CAAnimation *geometry = resumedCopy(geometryLayer, installedGeometry);
|
|
1591
|
+
CAAnimation *content = resumedCopy(contentLayer, installedContent);
|
|
1592
|
+
CAAnimation *mask = resumedCopy(_unequalCornerMask, installedMask);
|
|
1593
|
+
CAAnimation *shadow = resumedCopy(_shadowLayer, installedShadow);
|
|
1594
|
+
|
|
1595
|
+
_animationDelegate.invalidated = YES;
|
|
1596
|
+
_ignoreAnimationCallback = YES;
|
|
1597
|
+
[geometryLayer removeAnimationForKey:@"smoothClip.geometry"];
|
|
1598
|
+
[contentLayer removeAnimationForKey:@"smoothClip.content"];
|
|
1599
|
+
[_unequalCornerMask removeAnimationForKey:@"smoothClip.mask"];
|
|
1600
|
+
[_shadowLayer removeAnimationForKey:@"smoothClip.shadow"];
|
|
1601
|
+
|
|
1602
|
+
_animationDelegate = [SmoothClipAnimationDelegate new];
|
|
1603
|
+
_animationDelegate.view = self;
|
|
1604
|
+
_animationDelegate.driverId = _driverId;
|
|
1605
|
+
_animationDelegate.animationId = animationId;
|
|
1606
|
+
geometry.delegate = _animationDelegate;
|
|
1607
|
+
[geometryLayer addAnimation:geometry forKey:@"smoothClip.geometry"];
|
|
1608
|
+
[contentLayer addAnimation:content forKey:@"smoothClip.content"];
|
|
1609
|
+
if (mask != nil) {
|
|
1610
|
+
[_unequalCornerMask addAnimation:mask forKey:@"smoothClip.mask"];
|
|
1611
|
+
}
|
|
1612
|
+
if (shadow != nil) {
|
|
1613
|
+
[_shadowLayer addAnimation:shadow forKey:@"smoothClip.shadow"];
|
|
1614
|
+
}
|
|
1615
|
+
_ignoreAnimationCallback = NO;
|
|
1616
|
+
_springPausedForInactivity = NO;
|
|
1617
|
+
_pausedSpringHadMaskAnimation = NO;
|
|
1618
|
+
_pausedSpringHadShadowAnimation = NO;
|
|
1619
|
+
return YES;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
- (BOOL)smoothClipHasInstalledAnimation:(int32_t)animationId {
|
|
1623
|
+
return _activeAnimationId == animationId &&
|
|
1624
|
+
[_clipContainer.layer animationForKey:@"smoothClip.geometry"] != nil &&
|
|
1625
|
+
[_contentContainer.layer animationForKey:@"smoothClip.content"] != nil &&
|
|
1626
|
+
(!_springPausedForInactivity ||
|
|
1627
|
+
(!_pausedSpringHadMaskAnimation ||
|
|
1628
|
+
[_unequalCornerMask animationForKey:@"smoothClip.mask"] != nil)) &&
|
|
1629
|
+
(!_springPausedForInactivity ||
|
|
1630
|
+
(!_pausedSpringHadShadowAnimation ||
|
|
1631
|
+
[_shadowLayer animationForKey:@"smoothClip.shadow"] != nil));
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1468
1634
|
- (void)smoothClipAnimationDidStopWithDriverId:(uint64_t)driverId
|
|
1469
1635
|
animationId:(int32_t)animationId
|
|
1470
1636
|
finished:(BOOL)finished {
|
|
@@ -1472,6 +1638,9 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1472
1638
|
_activeAnimationId = 0;
|
|
1473
1639
|
_activeAnimationKind = 0;
|
|
1474
1640
|
_animationDelegate = nil;
|
|
1641
|
+
_springPausedForInactivity = NO;
|
|
1642
|
+
_pausedSpringHadMaskAnimation = NO;
|
|
1643
|
+
_pausedSpringHadShadowAnimation = NO;
|
|
1475
1644
|
[_unequalCornerMask removeAnimationForKey:@"smoothClip.mask"];
|
|
1476
1645
|
[_shadowLayer removeAnimationForKey:@"smoothClip.shadow"];
|
|
1477
1646
|
[self applyStaticCornerRepresentation:[self canonicalGeometryValue]];
|
package/package.json
CHANGED