react-native-smooth-clip-view 0.3.0 → 0.4.1
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 +135 -386
- package/android/build.gradle +4 -7
- package/android/proguard-rules.pro +2 -2
- package/android/src/main/cpp/SmoothClipAndroid.h +9 -12
- package/android/src/main/cpp/SmoothClipBindings.cpp +300 -704
- package/android/src/main/cpp/SmoothClipRegistry.cpp +348 -594
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +36 -227
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +16 -19
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +36 -294
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +369 -486
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +75 -77
- package/cpp/SmoothClipAnimationCurve.h +237 -346
- package/cpp/SmoothClipRegistry.h +24 -52
- package/cpp/SmoothClipSharedGeometry.h +64 -94
- package/cpp/SmoothClipVelocityTracker.h +5 -5
- package/ios/SmoothClipGeometry.h +23 -34
- package/ios/SmoothClipRegistry.mm +206 -711
- package/ios/SmoothClipTurboModule.cpp +217 -715
- package/ios/SmoothClipTurboModule.h +54 -297
- package/ios/SmoothClipView.mm +599 -505
- package/ios/SmoothClipViewRegistry.h +2 -7
- package/lib/module/NativeSmoothClipModule.js.map +1 -1
- package/lib/module/SmoothClipView.ios.js +84 -17
- package/lib/module/SmoothClipView.ios.js.map +1 -1
- package/lib/module/SmoothClipView.js +1 -1
- package/lib/module/SmoothClipView.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +20 -26
- package/lib/module/capabilities.native.js +1 -19
- package/lib/module/capabilities.native.js.map +1 -1
- package/lib/module/capabilityTypes.js +0 -9
- package/lib/module/capabilityTypes.js.map +1 -1
- package/lib/module/controllerInternals.js +28 -0
- package/lib/module/controllerInternals.js.map +1 -0
- package/lib/module/controllerLifecycle.js +9 -0
- package/lib/module/controllerLifecycle.js.map +1 -0
- package/lib/module/controllerTypes.js +4 -0
- package/lib/module/controllerTypes.js.map +1 -0
- package/lib/module/controllers.android.js +4 -0
- package/lib/module/controllers.android.js.map +1 -0
- package/lib/module/controllers.ios.js +4 -0
- package/lib/module/controllers.ios.js.map +1 -0
- package/lib/module/controllers.js +4 -0
- package/lib/module/controllers.js.map +1 -0
- package/lib/module/controllers.native.js +89 -0
- package/lib/module/controllers.native.js.map +1 -0
- package/lib/module/geometry.js +71 -51
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/groupTypes.js +4 -0
- package/lib/module/{groupDriverTypes.js.map → groupTypes.js.map} +1 -1
- package/lib/module/groups.android.js +4 -0
- package/lib/module/groups.android.js.map +1 -0
- package/lib/module/groups.ios.js +4 -0
- package/lib/module/groups.ios.js.map +1 -0
- package/lib/module/groups.js +4 -0
- package/lib/module/groups.js.map +1 -0
- package/lib/module/groups.native.js +399 -0
- package/lib/module/groups.native.js.map +1 -0
- package/lib/module/ids.js +8 -0
- package/lib/module/ids.js.map +1 -0
- package/lib/module/index.js +3 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/nativeCompletion.js +37 -16
- package/lib/module/nativeCompletion.js.map +1 -1
- package/lib/module/presentationCodec.js +81 -0
- package/lib/module/presentationCodec.js.map +1 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +14 -23
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts +18 -14
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +10 -5
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/capabilities.native.d.ts +1 -1
- package/lib/typescript/src/capabilities.native.d.ts.map +1 -1
- package/lib/typescript/src/capabilityTypes.d.ts +0 -6
- package/lib/typescript/src/capabilityTypes.d.ts.map +1 -1
- package/lib/typescript/src/controllerInternals.d.ts +14 -0
- package/lib/typescript/src/controllerInternals.d.ts.map +1 -0
- package/lib/typescript/src/controllerLifecycle.d.ts +3 -0
- package/lib/typescript/src/controllerLifecycle.d.ts.map +1 -0
- package/lib/typescript/src/controllerTypes.d.ts +62 -0
- package/lib/typescript/src/controllerTypes.d.ts.map +1 -0
- package/lib/typescript/src/controllers.android.d.ts +2 -0
- package/lib/typescript/src/controllers.android.d.ts.map +1 -0
- package/lib/typescript/src/controllers.d.ts +2 -0
- package/lib/typescript/src/controllers.d.ts.map +1 -0
- package/lib/typescript/src/controllers.ios.d.ts +2 -0
- package/lib/typescript/src/controllers.ios.d.ts.map +1 -0
- package/lib/typescript/src/controllers.native.d.ts +3 -0
- package/lib/typescript/src/controllers.native.d.ts.map +1 -0
- package/lib/typescript/src/geometry.d.ts +22 -12
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/groupTypes.d.ts +31 -0
- package/lib/typescript/src/groupTypes.d.ts.map +1 -0
- package/lib/typescript/src/groups.android.d.ts +2 -0
- package/lib/typescript/src/groups.android.d.ts.map +1 -0
- package/lib/typescript/src/groups.d.ts +2 -0
- package/lib/typescript/src/groups.d.ts.map +1 -0
- package/lib/typescript/src/groups.ios.d.ts +2 -0
- package/lib/typescript/src/groups.ios.d.ts.map +1 -0
- package/lib/typescript/src/groups.native.d.ts +3 -0
- package/lib/typescript/src/groups.native.d.ts.map +1 -0
- package/lib/typescript/src/ids.d.ts +2 -0
- package/lib/typescript/src/ids.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -6
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/nativeCompletion.d.ts +16 -6
- package/lib/typescript/src/nativeCompletion.d.ts.map +1 -1
- package/lib/typescript/src/presentationCodec.d.ts +10 -0
- package/lib/typescript/src/presentationCodec.d.ts.map +1 -0
- package/package.json +8 -3
- package/src/NativeSmoothClipModule.ts +41 -292
- package/src/SmoothClipView.ios.tsx +130 -23
- package/src/SmoothClipView.tsx +5 -1
- package/src/SmoothClipViewNativeComponent.ts +20 -26
- package/src/capabilities.native.ts +2 -52
- package/src/capabilityTypes.ts +0 -14
- package/src/controllerInternals.ts +52 -0
- package/src/controllerLifecycle.ts +6 -0
- package/src/controllerTypes.ts +87 -0
- package/src/controllers.android.ts +1 -0
- package/src/controllers.ios.ts +1 -0
- package/src/controllers.native.ts +104 -0
- package/src/controllers.ts +1 -0
- package/src/geometry.ts +118 -74
- package/src/groupTypes.ts +53 -0
- package/src/groups.android.ts +1 -0
- package/src/groups.ios.ts +1 -0
- package/src/groups.native.ts +604 -0
- package/src/groups.ts +1 -0
- package/src/ids.ts +6 -0
- package/src/index.ts +20 -28
- package/src/nativeCompletion.ts +67 -32
- package/src/presentationCodec.ts +134 -0
- package/lib/module/driverState.js +0 -82
- package/lib/module/driverState.js.map +0 -1
- package/lib/module/driverTypes.js +0 -4
- package/lib/module/driverTypes.js.map +0 -1
- package/lib/module/drivers.android.js +0 -6
- package/lib/module/drivers.android.js.map +0 -1
- package/lib/module/drivers.ios.js +0 -6
- package/lib/module/drivers.ios.js.map +0 -1
- package/lib/module/drivers.js +0 -4
- package/lib/module/drivers.js.map +0 -1
- package/lib/module/drivers.native.js +0 -756
- package/lib/module/drivers.native.js.map +0 -1
- package/lib/module/groupDriverTypes.js +0 -4
- package/lib/module/groupDrivers.android.js +0 -4
- package/lib/module/groupDrivers.android.js.map +0 -1
- package/lib/module/groupDrivers.ios.js +0 -4
- package/lib/module/groupDrivers.ios.js.map +0 -1
- package/lib/module/groupDrivers.js +0 -4
- package/lib/module/groupDrivers.js.map +0 -1
- package/lib/module/groupDrivers.native.js +0 -799
- package/lib/module/groupDrivers.native.js.map +0 -1
- package/lib/module/presentationProtocol.js +0 -15
- package/lib/module/presentationProtocol.js.map +0 -1
- package/lib/module/reactRequests.js +0 -56
- package/lib/module/reactRequests.js.map +0 -1
- package/lib/typescript/src/driverState.d.ts +0 -26
- package/lib/typescript/src/driverState.d.ts.map +0 -1
- package/lib/typescript/src/driverTypes.d.ts +0 -121
- package/lib/typescript/src/driverTypes.d.ts.map +0 -1
- package/lib/typescript/src/drivers.android.d.ts +0 -3
- package/lib/typescript/src/drivers.android.d.ts.map +0 -1
- package/lib/typescript/src/drivers.d.ts +0 -3
- package/lib/typescript/src/drivers.d.ts.map +0 -1
- package/lib/typescript/src/drivers.ios.d.ts +0 -3
- package/lib/typescript/src/drivers.ios.d.ts.map +0 -1
- package/lib/typescript/src/drivers.native.d.ts +0 -5
- package/lib/typescript/src/drivers.native.d.ts.map +0 -1
- package/lib/typescript/src/groupDriverTypes.d.ts +0 -66
- package/lib/typescript/src/groupDriverTypes.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.android.d.ts +0 -2
- package/lib/typescript/src/groupDrivers.android.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.d.ts +0 -2
- package/lib/typescript/src/groupDrivers.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.ios.d.ts +0 -2
- package/lib/typescript/src/groupDrivers.ios.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.native.d.ts +0 -4
- package/lib/typescript/src/groupDrivers.native.d.ts.map +0 -1
- package/lib/typescript/src/presentationProtocol.d.ts +0 -4
- package/lib/typescript/src/presentationProtocol.d.ts.map +0 -1
- package/lib/typescript/src/reactRequests.d.ts +0 -9
- package/lib/typescript/src/reactRequests.d.ts.map +0 -1
- package/src/driverState.ts +0 -118
- package/src/driverTypes.ts +0 -171
- package/src/drivers.android.ts +0 -8
- package/src/drivers.ios.ts +0 -8
- package/src/drivers.native.ts +0 -1599
- package/src/drivers.ts +0 -6
- package/src/groupDriverTypes.ts +0 -117
- package/src/groupDrivers.android.ts +0 -1
- package/src/groupDrivers.ios.ts +0 -1
- package/src/groupDrivers.native.ts +0 -1189
- package/src/groupDrivers.ts +0 -1
- package/src/presentationProtocol.ts +0 -28
- package/src/reactRequests.ts +0 -76
|
@@ -43,20 +43,11 @@
|
|
|
43
43
|
animation:(smoothclip::SpringAnimation)animation
|
|
44
44
|
animationId:(int32_t)animationId
|
|
45
45
|
sharedBeginTime:(CFTimeInterval)sharedBeginTime;
|
|
46
|
-
- (BOOL)smoothClipAnimateKeyframes:(smoothclip::Presentation)presentation
|
|
47
|
-
keyframes:(const std::vector<smoothclip::Keyframe> &)keyframes
|
|
48
|
-
durationMs:(double)durationMs
|
|
49
|
-
animationId:(int32_t)animationId;
|
|
50
|
-
- (BOOL)smoothClipAnimateKeyframes:(smoothclip::Presentation)presentation
|
|
51
|
-
keyframes:(const std::vector<smoothclip::Keyframe> &)keyframes
|
|
52
|
-
durationMs:(double)durationMs
|
|
53
|
-
animationId:(int32_t)animationId
|
|
54
|
-
sharedBeginTime:(CFTimeInterval)sharedBeginTime;
|
|
55
46
|
- (void)smoothClipCancelAnimationUsingTarget:(BOOL)useTarget;
|
|
56
47
|
- (BOOL)smoothClipIsJoinable;
|
|
57
48
|
- (BOOL)smoothClipCanDisplay;
|
|
58
|
-
- (
|
|
59
|
-
|
|
49
|
+
- (double)smoothClipInheritedVelocityToPresentation:
|
|
50
|
+
(smoothclip::Presentation)presentation;
|
|
60
51
|
- (void)smoothClipClearVelocitySamples;
|
|
61
52
|
@end
|
|
62
53
|
|
|
@@ -66,18 +57,15 @@ namespace {
|
|
|
66
57
|
using ViewKey = uintptr_t;
|
|
67
58
|
|
|
68
59
|
enum class Ownership { Interactive, Native };
|
|
69
|
-
enum class AnimationKind { Timing, Spring
|
|
60
|
+
enum class AnimationKind { Timing, Spring };
|
|
70
61
|
|
|
71
62
|
struct ActiveAnimation {
|
|
72
63
|
int32_t animationId;
|
|
73
|
-
|
|
64
|
+
int32_t completionTag = 0;
|
|
65
|
+
// Zero identifies a single-driver animation. A non-zero value binds
|
|
74
66
|
// the driver to an immutable group whose completion is aggregated once.
|
|
75
67
|
int32_t groupId = 0;
|
|
76
68
|
std::unordered_set<ViewKey> participants;
|
|
77
|
-
// Hosts that installed this id and temporarily lost displayability. They
|
|
78
|
-
// may rejoin the same remainder; completion is false if any remain
|
|
79
|
-
// unresolved when the run ends or unregister.
|
|
80
|
-
std::unordered_set<ViewKey> suspendedParticipants;
|
|
81
69
|
bool finished = true;
|
|
82
70
|
AnimationKind kind = AnimationKind::Timing;
|
|
83
71
|
// Geometry the transition started from. Used when a view must join the
|
|
@@ -85,16 +73,8 @@ struct ActiveAnimation {
|
|
|
85
73
|
Presentation start{{0, 0, 0, 0, 0}, 0, 0};
|
|
86
74
|
TimingAnimation timing{};
|
|
87
75
|
SpringAnimation spring{};
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
CFTimeInterval startedAt = 0;
|
|
91
|
-
// False while the animation is latched. Two ways in: built before any host
|
|
92
|
-
// view could display (pre-registration, or a host in a detached subtree), or
|
|
93
|
-
// re-latched by unregisterView when the last DISPLAYABLE host left
|
|
94
|
-
// mid-flight. Started by whichever registration/displayability update or
|
|
95
|
-
// joinActiveAnimation first finds a host that can produce a frame; each
|
|
96
|
-
// rebases startedAt through startLatchedAnimation. startedAt is only
|
|
97
|
-
// meaningful once started.
|
|
76
|
+
// False while a pre-ready run waits for its first displayable host. Host
|
|
77
|
+
// loss after the run starts finishes it at the target.
|
|
98
78
|
bool started = false;
|
|
99
79
|
};
|
|
100
80
|
|
|
@@ -123,9 +103,9 @@ struct GroupCompletionSink {
|
|
|
123
103
|
struct GroupState {
|
|
124
104
|
uint64_t controllerId;
|
|
125
105
|
int32_t groupId;
|
|
106
|
+
int32_t completionTag;
|
|
126
107
|
std::vector<uint64_t> driverIds;
|
|
127
108
|
std::unordered_set<uint64_t> remainingDriverIds;
|
|
128
|
-
GroupSuspensionPolicy suspensionPolicy = GroupSuspensionPolicy::Pause;
|
|
129
109
|
bool finished = true;
|
|
130
110
|
bool mutating = false;
|
|
131
111
|
};
|
|
@@ -203,7 +183,8 @@ std::vector<DriverSnapshot> cancelGroupInternal(
|
|
|
203
183
|
GroupCancelBehavior behavior,
|
|
204
184
|
bool completionFinished);
|
|
205
185
|
void tryStartGroup(int32_t groupId);
|
|
206
|
-
void
|
|
186
|
+
void finishGroupForHostLoss(int32_t groupId);
|
|
187
|
+
DriverSnapshot snapshotForDriver(uint64_t driverId);
|
|
207
188
|
|
|
208
189
|
Presentation unavailablePresentation() {
|
|
209
190
|
// Non-finite geometry makes the JS side fall back to its current value
|
|
@@ -240,6 +221,9 @@ void emitCompletion(
|
|
|
240
221
|
const int32_t groupId = state.animation.has_value()
|
|
241
222
|
? state.animation->groupId
|
|
242
223
|
: 0;
|
|
224
|
+
const int32_t completionTag = state.animation.has_value()
|
|
225
|
+
? state.animation->completionTag
|
|
226
|
+
: 0;
|
|
243
227
|
state.animation.reset();
|
|
244
228
|
if (groupId != 0) {
|
|
245
229
|
groupMemberCompleted(groupId, driverId, finished);
|
|
@@ -248,7 +232,8 @@ void emitCompletion(
|
|
|
248
232
|
{
|
|
249
233
|
std::lock_guard<std::mutex> lock(completionSinkMutex());
|
|
250
234
|
if (completionSink().callback) {
|
|
251
|
-
completionSink().callback(
|
|
235
|
+
completionSink().callback(
|
|
236
|
+
driverId, animationId, completionTag, finished);
|
|
252
237
|
}
|
|
253
238
|
}
|
|
254
239
|
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
@@ -262,12 +247,22 @@ void emitCompletion(
|
|
|
262
247
|
void emitGroupCompletion(
|
|
263
248
|
uint64_t controllerId,
|
|
264
249
|
int32_t groupId,
|
|
250
|
+
int32_t completionTag,
|
|
265
251
|
bool finished,
|
|
266
252
|
const std::vector<uint64_t> &driverIds) {
|
|
253
|
+
std::vector<DriverSnapshot> snapshots;
|
|
254
|
+
snapshots.reserve(driverIds.size());
|
|
255
|
+
for (const uint64_t driverId : driverIds) {
|
|
256
|
+
snapshots.push_back(snapshotForDriver(driverId));
|
|
257
|
+
}
|
|
267
258
|
std::lock_guard<std::mutex> lock(completionSinkMutex());
|
|
268
259
|
if (groupCompletionSink().callback) {
|
|
269
260
|
groupCompletionSink().callback(
|
|
270
|
-
controllerId,
|
|
261
|
+
controllerId,
|
|
262
|
+
groupId,
|
|
263
|
+
completionTag,
|
|
264
|
+
finished,
|
|
265
|
+
std::move(snapshots));
|
|
271
266
|
}
|
|
272
267
|
}
|
|
273
268
|
|
|
@@ -283,21 +278,28 @@ void groupMemberCompleted(
|
|
|
283
278
|
if (!group.remainingDriverIds.empty() || group.mutating) return;
|
|
284
279
|
|
|
285
280
|
const uint64_t controllerId = group.controllerId;
|
|
281
|
+
const int32_t completionTag = group.completionTag;
|
|
286
282
|
const bool aggregateFinished = group.finished;
|
|
287
283
|
const std::vector<uint64_t> driverIds = group.driverIds;
|
|
288
284
|
groupRegistry().erase(iterator);
|
|
289
285
|
emitGroupCompletion(
|
|
290
|
-
controllerId,
|
|
286
|
+
controllerId,
|
|
287
|
+
groupId,
|
|
288
|
+
completionTag,
|
|
289
|
+
aggregateFinished,
|
|
290
|
+
driverIds);
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
void emitStandaloneCompletion(
|
|
294
294
|
uint64_t driverId,
|
|
295
295
|
int32_t animationId,
|
|
296
|
+
int32_t completionTag,
|
|
296
297
|
bool finished) {
|
|
297
298
|
{
|
|
298
299
|
std::lock_guard<std::mutex> lock(completionSinkMutex());
|
|
299
300
|
if (completionSink().callback) {
|
|
300
|
-
completionSink().callback(
|
|
301
|
+
completionSink().callback(
|
|
302
|
+
driverId, animationId, completionTag, finished);
|
|
301
303
|
}
|
|
302
304
|
}
|
|
303
305
|
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
@@ -311,7 +313,7 @@ void emitStandaloneCompletion(
|
|
|
311
313
|
Presentation canonicalFrozenPresentation(
|
|
312
314
|
DriverState &state,
|
|
313
315
|
SmoothClipView *preferred = nil) {
|
|
314
|
-
// A
|
|
316
|
+
// A pre-ready animation never rendered, so state.latest already holds its
|
|
315
317
|
// target; freezing there would jump the clip. Freeze at the start instead.
|
|
316
318
|
Presentation canonical =
|
|
317
319
|
state.animation.has_value() && !state.animation->started
|
|
@@ -333,13 +335,12 @@ Presentation canonicalFrozenPresentation(
|
|
|
333
335
|
canonical = frozen;
|
|
334
336
|
hasCanonical = true;
|
|
335
337
|
} else if (!hasParticipantFallback && state.animation.has_value() &&
|
|
336
|
-
|
|
337
|
-
state.animation->suspendedParticipants.count(key) > 0)) {
|
|
338
|
+
state.animation->participants.count(key) > 0) {
|
|
338
339
|
// An installed participant whose HOST just lost its size still holds
|
|
339
340
|
// real mid-flight geometry on its clip layer (host bounds do not feed
|
|
340
341
|
// the clip container). Without this fallback, a sole running host
|
|
341
|
-
// resized to zero
|
|
342
|
-
//
|
|
342
|
+
// resized to zero must still freeze from its real clip layer rather
|
|
343
|
+
// than state.latest, which already holds the target.
|
|
343
344
|
hasParticipantFallback = true;
|
|
344
345
|
participantFallback = frozen;
|
|
345
346
|
}
|
|
@@ -371,65 +372,18 @@ bool driverReady(const DriverState &state) {
|
|
|
371
372
|
return false;
|
|
372
373
|
}
|
|
373
374
|
|
|
374
|
-
|
|
375
|
+
double resolvedSpringVelocity(
|
|
375
376
|
const DriverState &state,
|
|
376
|
-
const Presentation &
|
|
377
|
+
const Presentation &target,
|
|
378
|
+
const SpringAnimation &spring) {
|
|
379
|
+
if (!spring.inheritVelocity) return spring.initialVelocity;
|
|
377
380
|
for (const ViewKey key : state.views) {
|
|
378
381
|
SmoothClipView *view = viewForKey(key);
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
// only after its participating host has laid out.
|
|
382
|
-
if (![view smoothClipIsJoinable]) continue;
|
|
383
|
-
const CGSize hostSize = view.bounds.size;
|
|
384
|
-
if (!SmoothClipGeometryNormalizationIsIdentity(
|
|
385
|
-
presentation.clip, hostSize.width, hostSize.height)) {
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
return true;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
bool timingPlanPreservesLinearHostNormalization(
|
|
393
|
-
const DriverState &state,
|
|
394
|
-
const Presentation &start,
|
|
395
|
-
const Presentation &target,
|
|
396
|
-
const TimingAnimation &animation) {
|
|
397
|
-
// A cubic Bezier stays inside the convex hull of its Y control points. The
|
|
398
|
-
// bound is deliberately conservative: if an overshooting easing still fits
|
|
399
|
-
// at both extrema, every presentation it can generate fits too because all
|
|
400
|
-
// host/radius eligibility inequalities are linear.
|
|
401
|
-
const double minimumProgress = std::min(
|
|
402
|
-
{0.0, 1.0, animation.controlPoint1Y, animation.controlPoint2Y});
|
|
403
|
-
const double maximumProgress = std::max(
|
|
404
|
-
{0.0, 1.0, animation.controlPoint1Y, animation.controlPoint2Y});
|
|
405
|
-
const Presentation minimum =
|
|
406
|
-
interpolate(start, target, minimumProgress);
|
|
407
|
-
const Presentation maximum =
|
|
408
|
-
interpolate(start, target, maximumProgress);
|
|
409
|
-
return isFiniteProtocolV2Presentation(minimum) &&
|
|
410
|
-
isFiniteProtocolV2Presentation(maximum) &&
|
|
411
|
-
presentationFitsEveryKnownHostWithoutNormalization(state, minimum) &&
|
|
412
|
-
presentationFitsEveryKnownHostWithoutNormalization(state, maximum);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
bool keyframePlanPreservesLinearHostNormalization(
|
|
416
|
-
const DriverState &state,
|
|
417
|
-
const Presentation &resolvedStart,
|
|
418
|
-
const std::vector<Keyframe> &keyframes) {
|
|
419
|
-
if (!presentationFitsEveryKnownHostWithoutNormalization(
|
|
420
|
-
state, resolvedStart)) {
|
|
421
|
-
return false;
|
|
422
|
-
}
|
|
423
|
-
// Frame zero is replaced by resolvedStart for an implicit native retarget.
|
|
424
|
-
// Every later anchor must be identity-normalized; convex segment-wise linear
|
|
425
|
-
// interpolation then cannot cross a host boundary or activate radius scaling.
|
|
426
|
-
for (std::size_t index = 1; index < keyframes.size(); index += 1) {
|
|
427
|
-
if (!presentationFitsEveryKnownHostWithoutNormalization(
|
|
428
|
-
state, keyframes[index].presentation)) {
|
|
429
|
-
return false;
|
|
382
|
+
if ([view smoothClipIsJoinable]) {
|
|
383
|
+
return [view smoothClipInheritedVelocityToPresentation:target];
|
|
430
384
|
}
|
|
431
385
|
}
|
|
432
|
-
return
|
|
386
|
+
return 0;
|
|
433
387
|
}
|
|
434
388
|
|
|
435
389
|
DriverSnapshot snapshotForDriver(uint64_t driverId) {
|
|
@@ -476,11 +430,11 @@ std::vector<DriverSnapshot> cancelGroupInternal(
|
|
|
476
430
|
Presentation resolved;
|
|
477
431
|
if (behavior == GroupCancelBehavior::Finish) {
|
|
478
432
|
resolved = state.latest;
|
|
479
|
-
const bool
|
|
433
|
+
const bool wasPending = !state.animation->started;
|
|
480
434
|
for (const ViewKey key : state.views) {
|
|
481
435
|
[viewForKey(key) smoothClipCancelAnimationUsingTarget:YES];
|
|
482
436
|
}
|
|
483
|
-
if (
|
|
437
|
+
if (wasPending) {
|
|
484
438
|
for (const ViewKey key : state.views) {
|
|
485
439
|
[viewForKey(key) smoothClipApplyPresentation:resolved
|
|
486
440
|
recordVelocitySample:NO];
|
|
@@ -504,12 +458,17 @@ std::vector<DriverSnapshot> cancelGroupInternal(
|
|
|
504
458
|
remainingGroup.mutating = false;
|
|
505
459
|
if (remainingGroup.remainingDriverIds.empty()) {
|
|
506
460
|
const uint64_t controllerId = remainingGroup.controllerId;
|
|
461
|
+
const int32_t completionTag = remainingGroup.completionTag;
|
|
507
462
|
const bool finished = remainingGroup.finished;
|
|
508
463
|
const std::vector<uint64_t> completedDrivers =
|
|
509
464
|
remainingGroup.driverIds;
|
|
510
465
|
groupRegistry().erase(groupIterator);
|
|
511
466
|
emitGroupCompletion(
|
|
512
|
-
controllerId,
|
|
467
|
+
controllerId,
|
|
468
|
+
groupId,
|
|
469
|
+
completionTag,
|
|
470
|
+
finished,
|
|
471
|
+
completedDrivers);
|
|
513
472
|
}
|
|
514
473
|
}
|
|
515
474
|
return snapshots;
|
|
@@ -538,14 +497,28 @@ void cancelActive(
|
|
|
538
497
|
emitCompletion(driverId, state, animationId, false);
|
|
539
498
|
}
|
|
540
499
|
|
|
500
|
+
void finishStandaloneAtTarget(uint64_t driverId, DriverState &state) {
|
|
501
|
+
if (!state.animation.has_value()) return;
|
|
502
|
+
if (state.animation->groupId != 0) {
|
|
503
|
+
cancelGroupInternal(
|
|
504
|
+
state.animation->groupId, GroupCancelBehavior::Finish, true);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const int32_t animationId = state.animation->animationId;
|
|
508
|
+
for (const ViewKey key : state.views) {
|
|
509
|
+
[viewForKey(key) smoothClipCancelAnimationUsingTarget:YES];
|
|
510
|
+
}
|
|
511
|
+
state.ownership = Ownership::Interactive;
|
|
512
|
+
emitCompletion(driverId, state, animationId, true);
|
|
513
|
+
}
|
|
514
|
+
|
|
541
515
|
void finishIfNoInstalledParticipants(uint64_t driverId, DriverState &state) {
|
|
542
516
|
if (!state.animation.has_value() || !state.animation->started ||
|
|
543
517
|
!state.animation->participants.empty()) {
|
|
544
518
|
return;
|
|
545
519
|
}
|
|
546
520
|
const int32_t animationId = state.animation->animationId;
|
|
547
|
-
const bool finished = state.animation->finished
|
|
548
|
-
state.animation->suspendedParticipants.empty();
|
|
521
|
+
const bool finished = state.animation->finished;
|
|
549
522
|
// A detached/unlaid-out view can have a requested animation without any CA
|
|
550
523
|
// delegate capable of completing it. Once every installed animation ends,
|
|
551
524
|
// make those deferred views statically correct and invalidate their pending
|
|
@@ -561,6 +534,7 @@ void applyPresentation(
|
|
|
561
534
|
DriverState &state,
|
|
562
535
|
Presentation presentation,
|
|
563
536
|
bool recordVelocitySample = true) {
|
|
537
|
+
if (!canonicalizePresentation(presentation)) return;
|
|
564
538
|
state.latest = presentation;
|
|
565
539
|
state.hasLatest = true;
|
|
566
540
|
state.ownership = Ownership::Interactive;
|
|
@@ -596,9 +570,9 @@ Presentation resolvedAnimationStart(
|
|
|
596
570
|
if (start.hasInteractiveStart) {
|
|
597
571
|
return start.interactiveStart;
|
|
598
572
|
}
|
|
599
|
-
// Replacing a
|
|
573
|
+
// Replacing a pre-ready animation (e.g. open→close before the host mounted):
|
|
600
574
|
// it never rendered, and state.latest is its target — start from where the
|
|
601
|
-
// clip actually is, the
|
|
575
|
+
// clip actually is, the pending run's own start.
|
|
602
576
|
if (state.animation.has_value() && !state.animation->started) {
|
|
603
577
|
return state.animation->start;
|
|
604
578
|
}
|
|
@@ -607,66 +581,28 @@ Presentation resolvedAnimationStart(
|
|
|
607
581
|
: state.latest;
|
|
608
582
|
}
|
|
609
583
|
|
|
610
|
-
// Installs
|
|
611
|
-
|
|
612
|
-
// animation) and joinActiveAnimation (first layout after such a mount).
|
|
613
|
-
void dispatchActiveAnimationJoin(
|
|
584
|
+
// Installs a run that waited for its host to become displayable.
|
|
585
|
+
void installPendingAnimation(
|
|
614
586
|
DriverState &state,
|
|
615
587
|
SmoothClipView *view,
|
|
616
588
|
ViewKey key,
|
|
617
589
|
const Presentation &visible,
|
|
618
|
-
SmoothClipView *reference = nil,
|
|
619
590
|
CFTimeInterval sharedBeginTime = 0) {
|
|
620
591
|
auto &active = *state.animation;
|
|
621
|
-
const double elapsedMs = sharedBeginTime > 0
|
|
622
|
-
? 0
|
|
623
|
-
: (CACurrentMediaTime() - active.startedAt) * 1000.0;
|
|
624
|
-
const double remainingMs = MAX(0, active.durationMs - elapsedMs);
|
|
625
592
|
BOOL installed = NO;
|
|
626
593
|
if (active.kind == AnimationKind::Timing) {
|
|
627
|
-
const double progress = active.durationMs <= 0
|
|
628
|
-
? 1
|
|
629
|
-
: MIN(1, MAX(0, elapsedMs / active.durationMs));
|
|
630
|
-
TimingAnimation timing = timingRemainder(active.timing, progress).animation;
|
|
631
594
|
installed = [view smoothClipAnimateTiming:state.latest
|
|
632
|
-
animation:timing
|
|
595
|
+
animation:active.timing
|
|
633
596
|
animationId:active.animationId
|
|
634
597
|
sharedBeginTime:sharedBeginTime];
|
|
635
598
|
} else if (active.kind == AnimationKind::Spring) {
|
|
636
|
-
SpringAnimation spring = active.spring;
|
|
637
|
-
if (reference != nil) {
|
|
638
|
-
// A late host must join the spring's CURRENT physical state. Reusing the
|
|
639
|
-
// original launch velocity (or resolving `inherit` from the joining
|
|
640
|
-
// host's unrelated samples) creates a different spring at the seam.
|
|
641
|
-
spring.inheritVelocity = false;
|
|
642
|
-
spring.initialVelocity =
|
|
643
|
-
[reference smoothClipSpringContinuationVelocity];
|
|
644
|
-
}
|
|
645
599
|
installed = [view smoothClipAnimateSpring:state.latest
|
|
646
|
-
animation:spring
|
|
600
|
+
animation:active.spring
|
|
647
601
|
animationId:active.animationId
|
|
648
602
|
sharedBeginTime:sharedBeginTime];
|
|
649
|
-
} else {
|
|
650
|
-
const double progress = active.durationMs <= 0
|
|
651
|
-
? 1
|
|
652
|
-
: MIN(1, MAX(0, elapsedMs / active.durationMs));
|
|
653
|
-
std::vector<Keyframe> remaining;
|
|
654
|
-
remaining.push_back({0, visible});
|
|
655
|
-
for (const Keyframe &frame : active.keyframes) {
|
|
656
|
-
if (frame.offset <= progress) continue;
|
|
657
|
-
const double offset = (frame.offset - progress) / (1 - progress);
|
|
658
|
-
remaining.push_back({offset, frame.presentation});
|
|
659
|
-
}
|
|
660
|
-
if (remaining.size() == 1) remaining.push_back({1, state.latest});
|
|
661
|
-
installed = [view smoothClipAnimateKeyframes:state.latest
|
|
662
|
-
keyframes:remaining
|
|
663
|
-
durationMs:remainingMs
|
|
664
|
-
animationId:active.animationId
|
|
665
|
-
sharedBeginTime:sharedBeginTime];
|
|
666
603
|
}
|
|
667
604
|
if (installed) {
|
|
668
605
|
active.participants.insert(key);
|
|
669
|
-
active.suspendedParticipants.erase(key);
|
|
670
606
|
}
|
|
671
607
|
}
|
|
672
608
|
|
|
@@ -682,89 +618,11 @@ bool anyDisplayableView(const DriverState &state) {
|
|
|
682
618
|
return false;
|
|
683
619
|
}
|
|
684
620
|
|
|
685
|
-
void
|
|
686
|
-
uint64_t driverId,
|
|
687
|
-
DriverState &state,
|
|
688
|
-
SmoothClipView *preferred = nil) {
|
|
689
|
-
if (!state.animation.has_value() || !state.animation->started) return;
|
|
690
|
-
auto &active = *state.animation;
|
|
691
|
-
double springVelocity = 0;
|
|
692
|
-
if (active.kind == AnimationKind::Spring) {
|
|
693
|
-
SmoothClipView *source = preferred;
|
|
694
|
-
if (source == nil ||
|
|
695
|
-
active.participants.count(keyForView(source)) == 0) {
|
|
696
|
-
source = active.participants.empty()
|
|
697
|
-
? nil
|
|
698
|
-
: viewForKey(*active.participants.begin());
|
|
699
|
-
}
|
|
700
|
-
if (source != nil) {
|
|
701
|
-
springVelocity = [source smoothClipSpringContinuationVelocity];
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
const Presentation frozen = canonicalFrozenPresentation(state, preferred);
|
|
705
|
-
// Captured before the migration below: only hosts that were ALREADY
|
|
706
|
-
// suspended are unresolved. The active participants being frozen right now
|
|
707
|
-
// rendered every frame up to this instant.
|
|
708
|
-
const bool hadUnresolvedSuspended = !active.suspendedParticipants.empty();
|
|
709
|
-
active.suspendedParticipants.insert(
|
|
710
|
-
active.participants.begin(), active.participants.end());
|
|
711
|
-
active.participants.clear();
|
|
712
|
-
|
|
713
|
-
const double elapsedMs = MAX(
|
|
714
|
-
0, (CACurrentMediaTime() - active.startedAt) * 1000.0);
|
|
715
|
-
const double progress = active.durationMs <= 0
|
|
716
|
-
? 1
|
|
717
|
-
: MIN(1, MAX(0, elapsedMs / active.durationMs));
|
|
718
|
-
if (active.kind == AnimationKind::Timing) {
|
|
719
|
-
active.timing = timingRemainder(active.timing, progress).animation;
|
|
720
|
-
active.durationMs = active.timing.durationMs;
|
|
721
|
-
} else if (active.kind == AnimationKind::Keyframes) {
|
|
722
|
-
const KeyframeContinuation continuation = keyframeContinuation(
|
|
723
|
-
active.keyframes,
|
|
724
|
-
frozen,
|
|
725
|
-
state.latest,
|
|
726
|
-
active.durationMs,
|
|
727
|
-
progress);
|
|
728
|
-
active.keyframes = continuation.frames;
|
|
729
|
-
active.durationMs = continuation.durationMs;
|
|
730
|
-
} else {
|
|
731
|
-
active.spring.inheritVelocity = false;
|
|
732
|
-
active.spring.initialVelocity = springVelocity;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
if (state.destroyed ||
|
|
736
|
-
(active.kind != AnimationKind::Spring && active.durationMs <= 0)) {
|
|
737
|
-
// A residual trimmed to zero means the curve fully elapsed while a host
|
|
738
|
-
// displayed every frame — only the asynchronous didStop was outstanding
|
|
739
|
-
// when this freeze arrived. Report that run honestly instead of stamping
|
|
740
|
-
// the freeze-path false; hosts that were suspended before the freeze are
|
|
741
|
-
// unresolved and still poison it.
|
|
742
|
-
const bool ranToEnd = !state.destroyed && progress >= 1 &&
|
|
743
|
-
active.finished && !hadUnresolvedSuspended;
|
|
744
|
-
// Deferred peers hold pending installs for this id; invalidate them and
|
|
745
|
-
// finalize statically at the target, exactly as the didStop completion
|
|
746
|
-
// path does. Frozen participants already have no animation to cancel.
|
|
747
|
-
for (const ViewKey key : state.views) {
|
|
748
|
-
[viewForKey(key) smoothClipCancelAnimationUsingTarget:YES];
|
|
749
|
-
}
|
|
750
|
-
state.ownership = Ownership::Interactive;
|
|
751
|
-
emitCompletion(driverId, state, active.animationId, ranToEnd);
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
active.start = frozen;
|
|
755
|
-
active.started = false;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// Starts a latched animation: rebases the clock so no progress was burned
|
|
759
|
-
// while no view could display, then dispatches the join to every registered
|
|
760
|
-
// view. Views that still cannot display defer their install per-view and
|
|
761
|
-
// resume via a later positive displayability update.
|
|
762
|
-
void startLatchedAnimation(DriverState &state) {
|
|
621
|
+
void startPendingAnimation(DriverState &state) {
|
|
763
622
|
if (state.animation->groupId != 0) {
|
|
764
623
|
tryStartGroup(state.animation->groupId);
|
|
765
624
|
return;
|
|
766
625
|
}
|
|
767
|
-
state.animation->startedAt = CACurrentMediaTime();
|
|
768
626
|
state.animation->started = true;
|
|
769
627
|
// Participants describe live CA delegates, not registered views. Anything
|
|
770
628
|
// left from the frozen run can only produce a stale callback for that run.
|
|
@@ -773,7 +631,7 @@ void startLatchedAnimation(DriverState &state) {
|
|
|
773
631
|
for (const ViewKey key : state.views) {
|
|
774
632
|
SmoothClipView *participant = viewForKey(key);
|
|
775
633
|
[participant smoothClipApplyPresentation:start recordVelocitySample:NO];
|
|
776
|
-
|
|
634
|
+
installPendingAnimation(state, participant, key, start);
|
|
777
635
|
}
|
|
778
636
|
}
|
|
779
637
|
|
|
@@ -800,61 +658,26 @@ void tryStartGroup(int32_t groupId) {
|
|
|
800
658
|
for (const uint64_t driverId : driverIds) {
|
|
801
659
|
DriverState &state = registry().find(driverId)->second;
|
|
802
660
|
ActiveAnimation &active = *state.animation;
|
|
803
|
-
active.startedAt = sharedBeginTime;
|
|
804
661
|
active.started = true;
|
|
805
662
|
active.participants.clear();
|
|
806
663
|
const Presentation start = active.start;
|
|
807
664
|
for (const ViewKey key : state.views) {
|
|
808
665
|
SmoothClipView *participant = viewForKey(key);
|
|
809
666
|
[participant smoothClipApplyPresentation:start recordVelocitySample:NO];
|
|
810
|
-
|
|
811
|
-
state,
|
|
812
|
-
participant,
|
|
813
|
-
key,
|
|
814
|
-
start,
|
|
815
|
-
nil,
|
|
816
|
-
sharedBeginTime);
|
|
667
|
+
installPendingAnimation(
|
|
668
|
+
state, participant, key, start, sharedBeginTime);
|
|
817
669
|
}
|
|
818
670
|
}
|
|
819
671
|
[CATransaction commit];
|
|
820
672
|
}
|
|
821
673
|
|
|
822
|
-
void
|
|
674
|
+
void finishGroupForHostLoss(int32_t groupId) {
|
|
823
675
|
auto groupIterator = groupRegistry().find(groupId);
|
|
824
676
|
if (groupIterator == groupRegistry().end() ||
|
|
825
677
|
groupIterator->second.mutating) {
|
|
826
678
|
return;
|
|
827
679
|
}
|
|
828
|
-
|
|
829
|
-
GroupSuspensionPolicy::Finish) {
|
|
830
|
-
cancelGroupInternal(
|
|
831
|
-
groupId, GroupCancelBehavior::Finish, true);
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
groupIterator->second.mutating = true;
|
|
836
|
-
const std::vector<uint64_t> driverIds = groupIterator->second.driverIds;
|
|
837
|
-
for (const uint64_t driverId : driverIds) {
|
|
838
|
-
auto stateIterator = registry().find(driverId);
|
|
839
|
-
if (stateIterator != registry().end() &&
|
|
840
|
-
stateIterator->second.animation.has_value() &&
|
|
841
|
-
stateIterator->second.animation->groupId == groupId) {
|
|
842
|
-
relatchActiveAnimation(driverId, stateIterator->second);
|
|
843
|
-
} else {
|
|
844
|
-
groupMemberCompleted(groupId, driverId, false);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
groupIterator = groupRegistry().find(groupId);
|
|
849
|
-
if (groupIterator == groupRegistry().end()) return;
|
|
850
|
-
GroupState &group = groupIterator->second;
|
|
851
|
-
group.mutating = false;
|
|
852
|
-
if (!group.remainingDriverIds.empty()) return;
|
|
853
|
-
const uint64_t controllerId = group.controllerId;
|
|
854
|
-
const bool finished = group.finished;
|
|
855
|
-
const std::vector<uint64_t> completedDrivers = group.driverIds;
|
|
856
|
-
groupRegistry().erase(groupIterator);
|
|
857
|
-
emitGroupCompletion(controllerId, groupId, finished, completedDrivers);
|
|
680
|
+
cancelGroupInternal(groupId, GroupCancelBehavior::Freeze, false);
|
|
858
681
|
}
|
|
859
682
|
|
|
860
683
|
} // namespace
|
|
@@ -874,11 +697,11 @@ void applicationWillResignActive() {
|
|
|
874
697
|
groupIds.push_back(groupId);
|
|
875
698
|
}
|
|
876
699
|
for (const int32_t groupId : groupIds) {
|
|
877
|
-
|
|
700
|
+
finishGroupForHostLoss(groupId);
|
|
878
701
|
}
|
|
879
702
|
for (auto &[driverId, state] : registry()) {
|
|
880
703
|
if (!state.animation.has_value() || state.animation->groupId == 0) {
|
|
881
|
-
|
|
704
|
+
finishStandaloneAtTarget(driverId, state);
|
|
882
705
|
}
|
|
883
706
|
}
|
|
884
707
|
}
|
|
@@ -900,7 +723,7 @@ void applicationDidBecomeActive() {
|
|
|
900
723
|
(void)driverId;
|
|
901
724
|
if (state.animation.has_value() && !state.animation->started &&
|
|
902
725
|
state.animation->groupId == 0 && anyDisplayableView(state)) {
|
|
903
|
-
|
|
726
|
+
startPendingAnimation(state);
|
|
904
727
|
}
|
|
905
728
|
}
|
|
906
729
|
}
|
|
@@ -940,8 +763,17 @@ void registerView(
|
|
|
940
763
|
SmoothClipView *view,
|
|
941
764
|
Presentation initialPresentation) {
|
|
942
765
|
NSCAssert(NSThread.isMainThread, @"SmoothClip registry is main-thread only");
|
|
766
|
+
if (!canonicalizePresentation(initialPresentation)) return;
|
|
943
767
|
auto &state = registry()[driverId];
|
|
944
768
|
state.destroyed = false;
|
|
769
|
+
const ViewKey key = keyForView(view);
|
|
770
|
+
const auto existing = std::find(state.views.begin(), state.views.end(), key);
|
|
771
|
+
if (existing == state.views.end() && !state.views.empty()) {
|
|
772
|
+
#if DEBUG
|
|
773
|
+
NSCAssert(NO, @"One SmoothClip controller cannot drive two mounted hosts");
|
|
774
|
+
#endif
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
945
777
|
// Seed canonical geometry from the mounting view's initial props BEFORE
|
|
946
778
|
// sampling `visible`; otherwise the first registration captures the default
|
|
947
779
|
// empty {0,0,0,0} rect, seeding the view (and the presentation layer the
|
|
@@ -951,251 +783,74 @@ void registerView(
|
|
|
951
783
|
state.latest = initialPresentation;
|
|
952
784
|
state.hasLatest = true;
|
|
953
785
|
}
|
|
954
|
-
const bool
|
|
955
|
-
|
|
956
|
-
// A
|
|
786
|
+
const bool hasPendingRun =
|
|
787
|
+
state.animation.has_value() && !state.animation->started;
|
|
788
|
+
// A pending run may only start once this view can produce a visible frame; a
|
|
957
789
|
// registration from a detached subtree (transparentModal before its view
|
|
958
|
-
// controller is presented) keeps
|
|
790
|
+
// controller is presented) keeps it waiting; a positive
|
|
959
791
|
// displayability update starts it inside the attach commit.
|
|
960
|
-
const bool
|
|
961
|
-
|
|
962
|
-
hasAnimation && state.animation->started && !state.views.empty();
|
|
792
|
+
const bool startsPendingRun =
|
|
793
|
+
hasPendingRun && [view smoothClipCanDisplay];
|
|
963
794
|
Presentation visible = state.latest;
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
// A registered peer that never laid out reports zero geometry; joining
|
|
967
|
-
// from it would visibly collapse the clip. Prefer the first laid-out
|
|
968
|
-
// peer and fall back to the animation's start.
|
|
969
|
-
for (const ViewKey candidate : state.animation->participants) {
|
|
970
|
-
SmoothClipView *candidateView = viewForKey(candidate);
|
|
971
|
-
if ([candidateView smoothClipIsJoinable]) {
|
|
972
|
-
reference = candidateView;
|
|
973
|
-
break;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
visible = reference != nil
|
|
977
|
-
? [reference smoothClipCurrentPresentation]
|
|
978
|
-
: state.animation->start;
|
|
979
|
-
} else if (hasLatch) {
|
|
980
|
-
// state.latest already holds the latch's TARGET; applying it here is
|
|
795
|
+
if (hasPendingRun) {
|
|
796
|
+
// state.latest already holds the target; applying it here is
|
|
981
797
|
// exactly the jump this path exists to avoid.
|
|
982
798
|
visible = state.animation->start;
|
|
983
799
|
}
|
|
984
|
-
|
|
985
|
-
if (std::find(state.views.begin(), state.views.end(), key) ==
|
|
986
|
-
state.views.end()) {
|
|
800
|
+
if (existing == state.views.end()) {
|
|
987
801
|
state.views.push_back(key);
|
|
988
802
|
}
|
|
989
803
|
[view smoothClipApplyPresentation:visible recordVelocitySample:NO];
|
|
990
|
-
if (
|
|
991
|
-
|
|
992
|
-
// that actually install their CA groups become completion participants.
|
|
993
|
-
startLatchedAnimation(state);
|
|
994
|
-
return;
|
|
804
|
+
if (startsPendingRun) {
|
|
805
|
+
startPendingAnimation(state);
|
|
995
806
|
}
|
|
996
|
-
if (!shouldReplay) return;
|
|
997
|
-
dispatchActiveAnimationJoin(state, view, key, visible, reference);
|
|
998
807
|
}
|
|
999
808
|
|
|
1000
809
|
void viewDisplayabilityChanged(uint64_t driverId, SmoothClipView *view) {
|
|
1001
810
|
NSCAssert(NSThread.isMainThread, @"SmoothClip registry is main-thread only");
|
|
1002
811
|
auto iterator = registry().find(driverId);
|
|
1003
812
|
if (iterator == registry().end()) return;
|
|
1004
|
-
|
|
813
|
+
DriverState &state = iterator->second;
|
|
1005
814
|
if (!state.animation.has_value()) return;
|
|
1006
815
|
const ViewKey key = keyForView(view);
|
|
1007
816
|
if (std::find(state.views.begin(), state.views.end(), key) ==
|
|
1008
817
|
state.views.end()) {
|
|
1009
818
|
return;
|
|
1010
819
|
}
|
|
1011
|
-
const int32_t groupId = state.animation->groupId;
|
|
1012
|
-
if (groupId != 0) {
|
|
1013
|
-
if (![view smoothClipCanDisplay]) {
|
|
1014
|
-
if (!state.animation->started) {
|
|
1015
|
-
if (!driverReady(state)) suspendGroup(groupId);
|
|
1016
|
-
return;
|
|
1017
|
-
}
|
|
1018
|
-
if (state.animation->participants.count(key) == 0) {
|
|
1019
|
-
return;
|
|
1020
|
-
}
|
|
1021
|
-
if (!driverReady(state)) {
|
|
1022
|
-
suspendGroup(groupId);
|
|
1023
|
-
return;
|
|
1024
|
-
}
|
|
1025
|
-
[view smoothClipFreezePresentation];
|
|
1026
|
-
if (state.animation.has_value() &&
|
|
1027
|
-
state.animation->groupId == groupId) {
|
|
1028
|
-
state.animation->participants.erase(key);
|
|
1029
|
-
state.animation->suspendedParticipants.insert(key);
|
|
1030
|
-
}
|
|
1031
|
-
return;
|
|
1032
|
-
}
|
|
1033
|
-
if (!state.animation->started) {
|
|
1034
|
-
tryStartGroup(groupId);
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
if ([view smoothClipHasPendingInstall] ||
|
|
1038
|
-
state.animation->suspendedParticipants.count(key) > 0) {
|
|
1039
|
-
joinActiveAnimation(driverId, view);
|
|
1040
|
-
}
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
820
|
if (![view smoothClipCanDisplay]) {
|
|
1044
|
-
if (
|
|
1045
|
-
|
|
1046
|
-
if (
|
|
1047
|
-
|
|
1048
|
-
return;
|
|
821
|
+
if (state.animation->groupId != 0 && state.animation->started) {
|
|
822
|
+
finishGroupForHostLoss(state.animation->groupId);
|
|
823
|
+
} else if (state.animation->started) {
|
|
824
|
+
finishStandaloneAtTarget(driverId, state);
|
|
1049
825
|
}
|
|
1050
|
-
[view smoothClipFreezePresentation];
|
|
1051
|
-
state.animation->participants.erase(key);
|
|
1052
|
-
state.animation->suspendedParticipants.insert(key);
|
|
1053
826
|
return;
|
|
1054
827
|
}
|
|
1055
828
|
if (!state.animation->started) {
|
|
1056
|
-
startLatchedAnimation(state);
|
|
1057
|
-
return;
|
|
1058
|
-
}
|
|
1059
|
-
// A deferred or suspended host joins from the registry's rebased remainder.
|
|
1060
|
-
if ([view smoothClipHasPendingInstall] ||
|
|
1061
|
-
state.animation->suspendedParticipants.count(key) > 0) {
|
|
1062
|
-
joinActiveAnimation(driverId, view);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
bool joinActiveAnimation(uint64_t driverId, SmoothClipView *view) {
|
|
1067
|
-
NSCAssert(NSThread.isMainThread, @"SmoothClip registry is main-thread only");
|
|
1068
|
-
auto iterator = registry().find(driverId);
|
|
1069
|
-
if (iterator == registry().end()) {
|
|
1070
|
-
return false;
|
|
1071
|
-
}
|
|
1072
|
-
auto &state = iterator->second;
|
|
1073
|
-
if (!state.animation.has_value() || state.views.empty()) {
|
|
1074
|
-
return false;
|
|
1075
|
-
}
|
|
1076
|
-
const ViewKey key = keyForView(view);
|
|
1077
|
-
if (std::find(state.views.begin(), state.views.end(), key) ==
|
|
1078
|
-
state.views.end()) {
|
|
1079
|
-
return false;
|
|
1080
|
-
}
|
|
1081
|
-
// A pending install used to imply the animation was dispatched, which
|
|
1082
|
-
// implied started. Re-latching on displayability broke that: when the last
|
|
1083
|
-
// displayable host leaves, a peer that deferred its install keeps
|
|
1084
|
-
// `_pendingAnimationInstall` while the animation goes back to un-started, and
|
|
1085
|
-
// its first layout lands here. Start the latch — returning false would make
|
|
1086
|
-
// the caller clear `_activeAnimationId` and apply the requested geometry,
|
|
1087
|
-
// snapping this view straight to the target. A *join* must still never
|
|
1088
|
-
// compute elapsed time against an un-rebased latch clock, so this returns
|
|
1089
|
-
// instead of falling through.
|
|
1090
|
-
if (!state.animation->started) {
|
|
1091
|
-
if (![view smoothClipCanDisplay]) return false;
|
|
1092
829
|
if (state.animation->groupId != 0) {
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
}
|
|
1097
|
-
startLatchedAnimation(state);
|
|
1098
|
-
return true;
|
|
1099
|
-
}
|
|
1100
|
-
// Join from a laid-out peer's live presentation, exactly as a view that had
|
|
1101
|
-
// registered at this moment would. When the joining view is the only
|
|
1102
|
-
// registered host, its own layer never received geometry (the install was
|
|
1103
|
-
// deferred for lack of layout), so fall back to the animation's start.
|
|
1104
|
-
SmoothClipView *reference = nil;
|
|
1105
|
-
for (const ViewKey candidate : state.animation->participants) {
|
|
1106
|
-
if (candidate != key && [viewForKey(candidate) smoothClipIsJoinable]) {
|
|
1107
|
-
reference = viewForKey(candidate);
|
|
1108
|
-
break;
|
|
830
|
+
tryStartGroup(state.animation->groupId);
|
|
831
|
+
} else {
|
|
832
|
+
startPendingAnimation(state);
|
|
1109
833
|
}
|
|
1110
834
|
}
|
|
1111
|
-
const Presentation visible = reference != nil
|
|
1112
|
-
? [reference smoothClipCurrentPresentation]
|
|
1113
|
-
: state.animation->start;
|
|
1114
|
-
[view smoothClipApplyPresentation:visible recordVelocitySample:NO];
|
|
1115
|
-
dispatchActiveAnimationJoin(state, view, key, visible, reference);
|
|
1116
|
-
return true;
|
|
1117
835
|
}
|
|
1118
836
|
|
|
1119
837
|
void unregisterView(uint64_t driverId, SmoothClipView *view) {
|
|
1120
838
|
NSCAssert(NSThread.isMainThread, @"SmoothClip registry is main-thread only");
|
|
1121
839
|
auto iterator = registry().find(driverId);
|
|
1122
840
|
if (iterator == registry().end()) return;
|
|
1123
|
-
|
|
1124
|
-
auto &state = iterator->second;
|
|
841
|
+
DriverState &state = iterator->second;
|
|
1125
842
|
const ViewKey key = keyForView(view);
|
|
1126
|
-
const int32_t groupId = state.animation.has_value()
|
|
1127
|
-
? state.animation->groupId
|
|
1128
|
-
: 0;
|
|
1129
|
-
if (groupId != 0) {
|
|
1130
|
-
const bool installed =
|
|
1131
|
-
state.animation->participants.count(key) > 0 ||
|
|
1132
|
-
state.animation->suspendedParticipants.count(key) > 0;
|
|
1133
|
-
bool otherDisplayable = false;
|
|
1134
|
-
for (const ViewKey candidate : state.views) {
|
|
1135
|
-
if (candidate != key && [viewForKey(candidate) smoothClipCanDisplay]) {
|
|
1136
|
-
otherDisplayable = true;
|
|
1137
|
-
break;
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
if (!otherDisplayable) {
|
|
1141
|
-
suspendGroup(groupId);
|
|
1142
|
-
} else if (state.animation.has_value() && state.animation->started &&
|
|
1143
|
-
state.animation->participants.count(key) > 0) {
|
|
1144
|
-
[view smoothClipFreezePresentation];
|
|
1145
|
-
}
|
|
1146
|
-
state.views.erase(
|
|
1147
|
-
std::remove(state.views.begin(), state.views.end(), key),
|
|
1148
|
-
state.views.end());
|
|
1149
|
-
if (state.animation.has_value() &&
|
|
1150
|
-
state.animation->groupId == groupId) {
|
|
1151
|
-
state.animation->participants.erase(key);
|
|
1152
|
-
state.animation->suspendedParticipants.erase(key);
|
|
1153
|
-
// A duplicate rendering host may leave without poisoning the immutable
|
|
1154
|
-
// driver participant. If the driver lost its only host, the group's
|
|
1155
|
-
// suspension policy above owns the result.
|
|
1156
|
-
if (installed && otherDisplayable &&
|
|
1157
|
-
state.animation->participants.empty()) {
|
|
1158
|
-
suspendGroup(groupId);
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
if (state.destroyed && state.views.empty()) {
|
|
1162
|
-
registry().erase(iterator);
|
|
1163
|
-
}
|
|
1164
|
-
return;
|
|
1165
|
-
}
|
|
1166
|
-
bool installed = false;
|
|
1167
843
|
if (state.animation.has_value()) {
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
if (state.animation->started
|
|
1171
|
-
|
|
1172
|
-
bool otherDisplayable = false;
|
|
1173
|
-
for (const ViewKey candidate : state.views) {
|
|
1174
|
-
if (candidate != key && [viewForKey(candidate) smoothClipCanDisplay]) {
|
|
1175
|
-
otherDisplayable = true;
|
|
1176
|
-
break;
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
if (!otherDisplayable) {
|
|
1180
|
-
relatchActiveAnimation(driverId, state, view);
|
|
1181
|
-
}
|
|
844
|
+
if (state.animation->groupId != 0 && state.animation->started) {
|
|
845
|
+
finishGroupForHostLoss(state.animation->groupId);
|
|
846
|
+
} else if (state.animation->groupId == 0 && state.animation->started) {
|
|
847
|
+
finishStandaloneAtTarget(driverId, state);
|
|
1182
848
|
}
|
|
1183
849
|
}
|
|
1184
|
-
|
|
1185
850
|
state.views.erase(
|
|
1186
851
|
std::remove(state.views.begin(), state.views.end(), key),
|
|
1187
852
|
state.views.end());
|
|
1188
|
-
if (state.
|
|
1189
|
-
state.animation->participants.erase(key);
|
|
1190
|
-
state.animation->suspendedParticipants.erase(key);
|
|
1191
|
-
if (installed) state.animation->finished = false;
|
|
1192
|
-
if (state.animation->started && state.animation->participants.empty()) {
|
|
1193
|
-
finishIfNoInstalledParticipants(driverId, state);
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
if (state.destroyed && state.views.empty()) {
|
|
1197
|
-
registry().erase(iterator);
|
|
1198
|
-
}
|
|
853
|
+
if (state.destroyed && state.views.empty()) registry().erase(iterator);
|
|
1199
854
|
}
|
|
1200
855
|
|
|
1201
856
|
void setPresentation(
|
|
@@ -1204,6 +859,7 @@ void setPresentation(
|
|
|
1204
859
|
bool takeOwnership,
|
|
1205
860
|
bool overridePendingAnimation,
|
|
1206
861
|
bool recordVelocity) {
|
|
862
|
+
if (!canonicalizePresentation(presentation)) return;
|
|
1207
863
|
// CALayer writes require the main thread (not main-queue drain context).
|
|
1208
864
|
if (!NSThread.isMainThread) {
|
|
1209
865
|
// Never block an off-main caller: a synchronous hop can deadlock against
|
|
@@ -1255,7 +911,7 @@ void setPresentation(
|
|
|
1255
911
|
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
1256
912
|
if (signpostsEnabled) {
|
|
1257
913
|
os_signpost_interval_end(
|
|
1258
|
-
signpostLog(), identifier, "lookup-fanout", "ignored=
|
|
914
|
+
signpostLog(), identifier, "lookup-fanout", "ignored=pending-run");
|
|
1259
915
|
}
|
|
1260
916
|
#endif
|
|
1261
917
|
return;
|
|
@@ -1277,7 +933,8 @@ Presentation beginInteraction(uint64_t driverId) {
|
|
|
1277
933
|
// See setPresentation: blocking off-main risks a deadlock, so the call
|
|
1278
934
|
// fails defined and the JS side keeps its current value.
|
|
1279
935
|
return unavailablePresentation();
|
|
1280
|
-
}
|
|
936
|
+
}
|
|
937
|
+
auto iterator = registry().find(driverId);
|
|
1281
938
|
if (iterator == registry().end()) return unavailablePresentation();
|
|
1282
939
|
auto &state = iterator->second;
|
|
1283
940
|
state.destroyed = false;
|
|
@@ -1377,18 +1034,22 @@ std::vector<DriverSnapshot> beginGroupInteraction(
|
|
|
1377
1034
|
|
|
1378
1035
|
bool setPresentationBatch(const std::vector<BatchEntry> &entries) {
|
|
1379
1036
|
if (!NSThread.isMainThread) return false;
|
|
1037
|
+
std::vector<BatchEntry> canonicalEntries = entries;
|
|
1038
|
+
for (BatchEntry &entry : canonicalEntries) {
|
|
1039
|
+
if (!canonicalizePresentation(entry.presentation)) return false;
|
|
1040
|
+
}
|
|
1380
1041
|
std::unordered_set<uint64_t> unique;
|
|
1381
|
-
for (const BatchEntry &entry :
|
|
1042
|
+
for (const BatchEntry &entry : canonicalEntries) {
|
|
1382
1043
|
const auto iterator = registry().find(entry.driverId);
|
|
1383
1044
|
if (entry.driverId == 0 || !unique.insert(entry.driverId).second ||
|
|
1384
1045
|
iterator == registry().end() || iterator->second.destroyed ||
|
|
1385
|
-
!
|
|
1046
|
+
!isFinitePresentation(entry.presentation)) {
|
|
1386
1047
|
return false;
|
|
1387
1048
|
}
|
|
1388
1049
|
}
|
|
1389
1050
|
|
|
1390
1051
|
std::unordered_set<int32_t> cancelledGroups;
|
|
1391
|
-
for (const BatchEntry &entry :
|
|
1052
|
+
for (const BatchEntry &entry : canonicalEntries) {
|
|
1392
1053
|
DriverState &state = registry().find(entry.driverId)->second;
|
|
1393
1054
|
if (!state.animation.has_value()) continue;
|
|
1394
1055
|
const int32_t groupId = state.animation->groupId;
|
|
@@ -1409,7 +1070,7 @@ bool setPresentationBatch(const std::vector<BatchEntry> &entries) {
|
|
|
1409
1070
|
|
|
1410
1071
|
[CATransaction begin];
|
|
1411
1072
|
[CATransaction setDisableActions:YES];
|
|
1412
|
-
for (const BatchEntry &entry :
|
|
1073
|
+
for (const BatchEntry &entry : canonicalEntries) {
|
|
1413
1074
|
applyPresentation(
|
|
1414
1075
|
registry().find(entry.driverId)->second, entry.presentation, true);
|
|
1415
1076
|
}
|
|
@@ -1420,7 +1081,7 @@ bool setPresentationBatch(const std::vector<BatchEntry> &entries) {
|
|
|
1420
1081
|
namespace {
|
|
1421
1082
|
|
|
1422
1083
|
bool preflightGroupEntries(
|
|
1423
|
-
|
|
1084
|
+
std::vector<GroupMotionEntry> &entries,
|
|
1424
1085
|
AnimationKind kind,
|
|
1425
1086
|
const TimingAnimation &timing,
|
|
1426
1087
|
const SpringAnimation &spring,
|
|
@@ -1429,17 +1090,18 @@ bool preflightGroupEntries(
|
|
|
1429
1090
|
bool &reduced,
|
|
1430
1091
|
std::vector<Presentation> &resolvedStarts) {
|
|
1431
1092
|
if (entries.empty()) return false;
|
|
1093
|
+
for (GroupMotionEntry &entry : entries) {
|
|
1094
|
+
if ((entry.hasFrom && !canonicalizePresentation(entry.from)) ||
|
|
1095
|
+
!canonicalizePresentation(entry.target)) {
|
|
1096
|
+
return false;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1432
1099
|
if (kind == AnimationKind::Timing &&
|
|
1433
|
-
!
|
|
1100
|
+
!isValidTiming(timing)) {
|
|
1434
1101
|
return false;
|
|
1435
1102
|
}
|
|
1436
1103
|
if (kind == AnimationKind::Spring &&
|
|
1437
|
-
!
|
|
1438
|
-
return false;
|
|
1439
|
-
}
|
|
1440
|
-
if (kind == AnimationKind::Keyframes &&
|
|
1441
|
-
(!std::isfinite(durationMs) || durationMs < 0 ||
|
|
1442
|
-
!isValidReduceMotionCode(reduceMotion))) {
|
|
1104
|
+
(!isValidSpring(spring) || spring.inheritVelocity)) {
|
|
1443
1105
|
return false;
|
|
1444
1106
|
}
|
|
1445
1107
|
reduced = shouldReduceMotion(reduceMotion) ||
|
|
@@ -1459,36 +1121,16 @@ bool preflightGroupEntries(
|
|
|
1459
1121
|
const Presentation resolvedStart = entry.hasFrom
|
|
1460
1122
|
? entry.from
|
|
1461
1123
|
: canonicalVisiblePresentation(iterator->second);
|
|
1462
|
-
if (!
|
|
1463
|
-
!
|
|
1464
|
-
resolvedStart
|
|
1465
|
-
|
|
1466
|
-
}
|
|
1467
|
-
if (kind == AnimationKind::Spring &&
|
|
1468
|
-
!protocolV2SpringScaleIsProvablyPositive(
|
|
1469
|
-
resolvedStart, entry.target, spring)) {
|
|
1124
|
+
if (!isFinitePresentation(resolvedStart) ||
|
|
1125
|
+
!isFinitePresentation(entry.target) ||
|
|
1126
|
+
!isAutonomousUniformCircular(resolvedStart) ||
|
|
1127
|
+
!isAutonomousUniformCircular(entry.target)) {
|
|
1470
1128
|
return false;
|
|
1471
1129
|
}
|
|
1472
|
-
if (kind == AnimationKind::
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
resolvedStart,
|
|
1476
|
-
entry.target,
|
|
1477
|
-
entry.hasFrom)) {
|
|
1478
|
-
return false;
|
|
1479
|
-
}
|
|
1480
|
-
} else if (!entry.keyframes.empty()) {
|
|
1481
|
-
return false;
|
|
1482
|
-
}
|
|
1483
|
-
if (!reduced && iterator != registry().end()) {
|
|
1484
|
-
if (kind == AnimationKind::Timing &&
|
|
1485
|
-
!timingPlanPreservesLinearHostNormalization(
|
|
1486
|
-
iterator->second, resolvedStart, entry.target, timing)) {
|
|
1487
|
-
return false;
|
|
1488
|
-
}
|
|
1489
|
-
if (kind == AnimationKind::Keyframes &&
|
|
1490
|
-
!keyframePlanPreservesLinearHostNormalization(
|
|
1491
|
-
iterator->second, resolvedStart, entry.keyframes)) {
|
|
1130
|
+
if (kind == AnimationKind::Spring) {
|
|
1131
|
+
const double velocity = spring.initialVelocity;
|
|
1132
|
+
if (!springScaleStaysPositive(
|
|
1133
|
+
resolvedStart, entry.target, spring, velocity)) {
|
|
1492
1134
|
return false;
|
|
1493
1135
|
}
|
|
1494
1136
|
}
|
|
@@ -1532,7 +1174,7 @@ int32_t createGroupAnimation(
|
|
|
1532
1174
|
SpringAnimation spring,
|
|
1533
1175
|
double durationMs,
|
|
1534
1176
|
int32_t reduceMotion,
|
|
1535
|
-
|
|
1177
|
+
int32_t completionTag) {
|
|
1536
1178
|
std::vector<Presentation> resolvedStarts;
|
|
1537
1179
|
bool reduced = false;
|
|
1538
1180
|
if (!NSThread.isMainThread || controllerId == 0 ||
|
|
@@ -1572,16 +1214,17 @@ int32_t createGroupAnimation(
|
|
|
1572
1214
|
applyPresentation(state, entry.target, false);
|
|
1573
1215
|
}
|
|
1574
1216
|
[CATransaction commit];
|
|
1575
|
-
emitGroupCompletion(
|
|
1217
|
+
emitGroupCompletion(
|
|
1218
|
+
controllerId, groupId, completionTag, true, driverIds);
|
|
1576
1219
|
return groupId;
|
|
1577
1220
|
}
|
|
1578
1221
|
|
|
1579
1222
|
GroupState group{
|
|
1580
1223
|
controllerId,
|
|
1581
1224
|
groupId,
|
|
1225
|
+
completionTag,
|
|
1582
1226
|
driverIds,
|
|
1583
1227
|
std::unordered_set<uint64_t>(driverIds.begin(), driverIds.end()),
|
|
1584
|
-
suspensionPolicy,
|
|
1585
1228
|
};
|
|
1586
1229
|
groupRegistry().emplace(groupId, std::move(group));
|
|
1587
1230
|
|
|
@@ -1597,13 +1240,6 @@ int32_t createGroupAnimation(
|
|
|
1597
1240
|
applyPresentation(state, entry.from, true);
|
|
1598
1241
|
}
|
|
1599
1242
|
const Presentation start = resolvedStarts[entryIndex];
|
|
1600
|
-
if (kind == AnimationKind::Keyframes && !entry.keyframes.empty()) {
|
|
1601
|
-
// An omitted `from` is a native-to-native retarget: sample the visible
|
|
1602
|
-
// presentation before replacement and substitute that exact value as
|
|
1603
|
-
// frame zero. Explicit endpoints were already validated and are never
|
|
1604
|
-
// silently corrected by native.
|
|
1605
|
-
if (!entry.hasFrom) entry.keyframes.front().presentation = start;
|
|
1606
|
-
}
|
|
1607
1243
|
state.latest = entry.target;
|
|
1608
1244
|
state.hasLatest = true;
|
|
1609
1245
|
state.ownership = Ownership::Native;
|
|
@@ -1614,8 +1250,10 @@ int32_t createGroupAnimation(
|
|
|
1614
1250
|
active.start = start;
|
|
1615
1251
|
active.timing = timing;
|
|
1616
1252
|
active.spring = spring;
|
|
1617
|
-
|
|
1618
|
-
|
|
1253
|
+
if (kind == AnimationKind::Spring) {
|
|
1254
|
+
active.spring.initialVelocity = spring.initialVelocity;
|
|
1255
|
+
active.spring.inheritVelocity = false;
|
|
1256
|
+
}
|
|
1619
1257
|
active.started = false;
|
|
1620
1258
|
state.animation = std::move(active);
|
|
1621
1259
|
}
|
|
@@ -1629,7 +1267,7 @@ int32_t animateTimingGroup(
|
|
|
1629
1267
|
uint64_t controllerId,
|
|
1630
1268
|
std::vector<GroupMotionEntry> entries,
|
|
1631
1269
|
TimingAnimation animation,
|
|
1632
|
-
|
|
1270
|
+
int32_t completionTag,
|
|
1633
1271
|
double startedAtHintS) {
|
|
1634
1272
|
(void)startedAtHintS;
|
|
1635
1273
|
return createGroupAnimation(
|
|
@@ -1640,14 +1278,14 @@ int32_t animateTimingGroup(
|
|
|
1640
1278
|
{},
|
|
1641
1279
|
animation.durationMs,
|
|
1642
1280
|
animation.reduceMotion,
|
|
1643
|
-
|
|
1281
|
+
completionTag);
|
|
1644
1282
|
}
|
|
1645
1283
|
|
|
1646
1284
|
int32_t animateSpringGroup(
|
|
1647
1285
|
uint64_t controllerId,
|
|
1648
1286
|
std::vector<GroupMotionEntry> entries,
|
|
1649
1287
|
SpringAnimation animation,
|
|
1650
|
-
|
|
1288
|
+
int32_t completionTag,
|
|
1651
1289
|
double startedAtHintS) {
|
|
1652
1290
|
(void)startedAtHintS;
|
|
1653
1291
|
return createGroupAnimation(
|
|
@@ -1658,26 +1296,7 @@ int32_t animateSpringGroup(
|
|
|
1658
1296
|
animation,
|
|
1659
1297
|
0,
|
|
1660
1298
|
animation.reduceMotion,
|
|
1661
|
-
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
int32_t animateKeyframesGroup(
|
|
1665
|
-
uint64_t controllerId,
|
|
1666
|
-
std::vector<GroupMotionEntry> entries,
|
|
1667
|
-
double durationMs,
|
|
1668
|
-
int32_t reduceMotion,
|
|
1669
|
-
GroupSuspensionPolicy suspensionPolicy,
|
|
1670
|
-
double startedAtHintS) {
|
|
1671
|
-
(void)startedAtHintS;
|
|
1672
|
-
return createGroupAnimation(
|
|
1673
|
-
controllerId,
|
|
1674
|
-
std::move(entries),
|
|
1675
|
-
AnimationKind::Keyframes,
|
|
1676
|
-
{},
|
|
1677
|
-
{},
|
|
1678
|
-
durationMs,
|
|
1679
|
-
reduceMotion,
|
|
1680
|
-
suspensionPolicy);
|
|
1299
|
+
completionTag);
|
|
1681
1300
|
}
|
|
1682
1301
|
|
|
1683
1302
|
std::vector<DriverSnapshot> cancelAnimationGroup(
|
|
@@ -1695,26 +1314,19 @@ int32_t animateTiming(
|
|
|
1695
1314
|
AnimationStart start,
|
|
1696
1315
|
Presentation presentation,
|
|
1697
1316
|
TimingAnimation animation,
|
|
1698
|
-
|
|
1317
|
+
int32_t completionTag) {
|
|
1699
1318
|
if (!NSThread.isMainThread) {
|
|
1700
1319
|
// See setPresentation: blocking off-main risks a deadlock. 0 is the
|
|
1701
1320
|
// documented rejection sentinel.
|
|
1702
1321
|
return 0;
|
|
1703
1322
|
}
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
std::isfinite(animation.controlPoint2X) &&
|
|
1712
|
-
std::isfinite(animation.controlPoint2Y) &&
|
|
1713
|
-
isValidReduceMotionCode(animation.reduceMotion);
|
|
1714
|
-
if (!strictV2 && validAnimation) {
|
|
1715
|
-
animation.durationMs = std::max(0.0, animation.durationMs);
|
|
1716
|
-
}
|
|
1717
|
-
if (driverId == 0 || !isFiniteProtocolV2Presentation(presentation) ||
|
|
1323
|
+
if (!canonicalizePresentation(presentation) ||
|
|
1324
|
+
(start.hasInteractiveStart &&
|
|
1325
|
+
!canonicalizePresentation(start.interactiveStart))) {
|
|
1326
|
+
return 0;
|
|
1327
|
+
}
|
|
1328
|
+
const bool validAnimation = isValidTiming(animation);
|
|
1329
|
+
if (driverId == 0 || !isFinitePresentation(presentation) ||
|
|
1718
1330
|
!validAnimation) {
|
|
1719
1331
|
return 0;
|
|
1720
1332
|
}
|
|
@@ -1722,7 +1334,7 @@ int32_t animateTiming(
|
|
|
1722
1334
|
Presentation preflightStart;
|
|
1723
1335
|
if (iterator == registry().end()) {
|
|
1724
1336
|
if (!start.hasInteractiveStart ||
|
|
1725
|
-
!
|
|
1337
|
+
!isFinitePresentation(start.interactiveStart)) {
|
|
1726
1338
|
return 0;
|
|
1727
1339
|
}
|
|
1728
1340
|
preflightStart = start.interactiveStart;
|
|
@@ -1730,17 +1342,12 @@ int32_t animateTiming(
|
|
|
1730
1342
|
if (iterator->second.destroyed && !start.hasInteractiveStart) return 0;
|
|
1731
1343
|
preflightStart = resolvedAnimationStart(iterator->second, start);
|
|
1732
1344
|
}
|
|
1733
|
-
if (!
|
|
1345
|
+
if (!isFinitePresentation(preflightStart) ||
|
|
1734
1346
|
preflightStart.clip.curve != presentation.clip.curve) {
|
|
1735
1347
|
return 0;
|
|
1736
1348
|
}
|
|
1737
1349
|
const bool reduced = shouldReduceMotion(animation.reduceMotion) ||
|
|
1738
1350
|
animation.durationMs <= 0;
|
|
1739
|
-
if (strictV2 && !reduced && iterator != registry().end() &&
|
|
1740
|
-
!timingPlanPreservesLinearHostNormalization(
|
|
1741
|
-
iterator->second, preflightStart, presentation, animation)) {
|
|
1742
|
-
return 0;
|
|
1743
|
-
}
|
|
1744
1351
|
if (iterator == registry().end()) {
|
|
1745
1352
|
iterator = registry().try_emplace(driverId).first;
|
|
1746
1353
|
iterator->second.latest = start.interactiveStart;
|
|
@@ -1754,25 +1361,22 @@ int32_t animateTiming(
|
|
|
1754
1361
|
|
|
1755
1362
|
if (reduced) {
|
|
1756
1363
|
applyPresentation(state, presentation, false);
|
|
1757
|
-
|
|
1364
|
+
emitStandaloneCompletion(
|
|
1365
|
+
driverId, animationId, completionTag, true);
|
|
1758
1366
|
return animationId;
|
|
1759
1367
|
}
|
|
1760
1368
|
|
|
1761
1369
|
ActiveAnimation active{animationId};
|
|
1370
|
+
active.completionTag = completionTag;
|
|
1762
1371
|
active.kind = AnimationKind::Timing;
|
|
1763
1372
|
active.start = resolvedStart;
|
|
1764
1373
|
active.timing = animation;
|
|
1765
|
-
active.durationMs = animation.durationMs;
|
|
1766
|
-
active.startedAt = CACurrentMediaTime();
|
|
1767
1374
|
active.started = anyDisplayableView(state);
|
|
1768
1375
|
state.animation = std::move(active);
|
|
1769
1376
|
if (!state.animation->started) {
|
|
1770
|
-
//
|
|
1771
|
-
//
|
|
1772
|
-
//
|
|
1773
|
-
// displayable registration/attach rebases startedAt and starts it. The
|
|
1774
|
-
// non-zero id is still returned synchronously so the JS side does not
|
|
1775
|
-
// treat this as rejection.
|
|
1377
|
+
// No host can display yet. Ownership stays Native and the first
|
|
1378
|
+
// displayable registration starts the full run. A non-zero ID still
|
|
1379
|
+
// distinguishes this pending run from rejection.
|
|
1776
1380
|
return animationId;
|
|
1777
1381
|
}
|
|
1778
1382
|
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
@@ -1797,26 +1401,18 @@ int32_t animateSpring(
|
|
|
1797
1401
|
AnimationStart start,
|
|
1798
1402
|
Presentation presentation,
|
|
1799
1403
|
SpringAnimation animation,
|
|
1800
|
-
|
|
1404
|
+
int32_t completionTag) {
|
|
1801
1405
|
if (!NSThread.isMainThread) {
|
|
1802
1406
|
// See setPresentation: blocking off-main risks a deadlock.
|
|
1803
1407
|
return 0;
|
|
1804
1408
|
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
std::isfinite(animation.initialVelocity) &&
|
|
1813
|
-
isValidReduceMotionCode(animation.reduceMotion);
|
|
1814
|
-
if (!strictV2 && validAnimation) {
|
|
1815
|
-
animation.mass = std::max(0.0001, animation.mass);
|
|
1816
|
-
animation.stiffness = std::max(0.0001, animation.stiffness);
|
|
1817
|
-
animation.damping = std::max(0.0, animation.damping);
|
|
1818
|
-
}
|
|
1819
|
-
if (driverId == 0 || !isFiniteProtocolV2Presentation(presentation) ||
|
|
1409
|
+
if (!canonicalizePresentation(presentation) ||
|
|
1410
|
+
(start.hasInteractiveStart &&
|
|
1411
|
+
!canonicalizePresentation(start.interactiveStart))) {
|
|
1412
|
+
return 0;
|
|
1413
|
+
}
|
|
1414
|
+
const bool validAnimation = isValidSpring(animation);
|
|
1415
|
+
if (driverId == 0 || !isFinitePresentation(presentation) ||
|
|
1820
1416
|
!validAnimation) {
|
|
1821
1417
|
return 0;
|
|
1822
1418
|
}
|
|
@@ -1824,7 +1420,7 @@ int32_t animateSpring(
|
|
|
1824
1420
|
Presentation preflightStart;
|
|
1825
1421
|
if (iterator == registry().end()) {
|
|
1826
1422
|
if (!start.hasInteractiveStart ||
|
|
1827
|
-
!
|
|
1423
|
+
!isFinitePresentation(start.interactiveStart)) {
|
|
1828
1424
|
return 0;
|
|
1829
1425
|
}
|
|
1830
1426
|
preflightStart = start.interactiveStart;
|
|
@@ -1832,10 +1428,13 @@ int32_t animateSpring(
|
|
|
1832
1428
|
if (iterator->second.destroyed && !start.hasInteractiveStart) return 0;
|
|
1833
1429
|
preflightStart = resolvedAnimationStart(iterator->second, start);
|
|
1834
1430
|
}
|
|
1835
|
-
|
|
1431
|
+
const double velocity = iterator == registry().end()
|
|
1432
|
+
? animation.initialVelocity
|
|
1433
|
+
: resolvedSpringVelocity(iterator->second, presentation, animation);
|
|
1434
|
+
if (!isFinitePresentation(preflightStart) ||
|
|
1836
1435
|
preflightStart.clip.curve != presentation.clip.curve ||
|
|
1837
|
-
|
|
1838
|
-
preflightStart, presentation, animation))
|
|
1436
|
+
!springScaleStaysPositive(
|
|
1437
|
+
preflightStart, presentation, animation, velocity)) {
|
|
1839
1438
|
return 0;
|
|
1840
1439
|
}
|
|
1841
1440
|
if (iterator == registry().end()) {
|
|
@@ -1851,19 +1450,22 @@ int32_t animateSpring(
|
|
|
1851
1450
|
|
|
1852
1451
|
if (shouldReduceMotion(animation.reduceMotion)) {
|
|
1853
1452
|
applyPresentation(state, presentation, false);
|
|
1854
|
-
|
|
1453
|
+
emitStandaloneCompletion(
|
|
1454
|
+
driverId, animationId, completionTag, true);
|
|
1855
1455
|
return animationId;
|
|
1856
1456
|
}
|
|
1857
1457
|
|
|
1858
1458
|
ActiveAnimation active{animationId};
|
|
1459
|
+
active.completionTag = completionTag;
|
|
1859
1460
|
active.kind = AnimationKind::Spring;
|
|
1860
1461
|
active.start = resolvedStart;
|
|
1861
1462
|
active.spring = animation;
|
|
1862
|
-
active.
|
|
1463
|
+
active.spring.initialVelocity = velocity;
|
|
1464
|
+
active.spring.inheritVelocity = false;
|
|
1863
1465
|
active.started = anyDisplayableView(state);
|
|
1864
1466
|
state.animation = std::move(active);
|
|
1865
1467
|
if (!state.animation->started) {
|
|
1866
|
-
//
|
|
1468
|
+
// Pending until the first displayable view — see animateTiming.
|
|
1867
1469
|
return animationId;
|
|
1868
1470
|
}
|
|
1869
1471
|
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
@@ -1875,7 +1477,7 @@ int32_t animateSpring(
|
|
|
1875
1477
|
#endif
|
|
1876
1478
|
for (const ViewKey key : state.views) {
|
|
1877
1479
|
if ([viewForKey(key) smoothClipAnimateSpring:presentation
|
|
1878
|
-
animation:animation
|
|
1480
|
+
animation:state.animation->spring
|
|
1879
1481
|
animationId:animationId]) {
|
|
1880
1482
|
state.animation->participants.insert(key);
|
|
1881
1483
|
}
|
|
@@ -1883,110 +1485,6 @@ int32_t animateSpring(
|
|
|
1883
1485
|
return animationId;
|
|
1884
1486
|
}
|
|
1885
1487
|
|
|
1886
|
-
int32_t animateKeyframes(
|
|
1887
|
-
uint64_t driverId,
|
|
1888
|
-
AnimationStart start,
|
|
1889
|
-
Presentation presentation,
|
|
1890
|
-
double durationMs,
|
|
1891
|
-
std::vector<Keyframe> keyframes,
|
|
1892
|
-
int32_t reduceMotion,
|
|
1893
|
-
AnimationValidationMode validationMode) {
|
|
1894
|
-
if (!NSThread.isMainThread) {
|
|
1895
|
-
// See setPresentation: blocking off-main risks a deadlock.
|
|
1896
|
-
return 0;
|
|
1897
|
-
}
|
|
1898
|
-
const bool strictV2 =
|
|
1899
|
-
validationMode == AnimationValidationMode::ProtocolV2;
|
|
1900
|
-
const bool finiteDuration = std::isfinite(durationMs);
|
|
1901
|
-
if (!strictV2 && finiteDuration) durationMs = std::max(0.0, durationMs);
|
|
1902
|
-
if (driverId == 0 || !isFiniteProtocolV2Presentation(presentation) ||
|
|
1903
|
-
!finiteDuration || (strictV2 && durationMs < 0) ||
|
|
1904
|
-
!isValidReduceMotionCode(reduceMotion)) {
|
|
1905
|
-
return 0;
|
|
1906
|
-
}
|
|
1907
|
-
auto iterator = registry().find(driverId);
|
|
1908
|
-
Presentation preflightStart;
|
|
1909
|
-
if (iterator == registry().end()) {
|
|
1910
|
-
if (!start.hasInteractiveStart ||
|
|
1911
|
-
!isFiniteProtocolV2Presentation(start.interactiveStart)) {
|
|
1912
|
-
return 0;
|
|
1913
|
-
}
|
|
1914
|
-
preflightStart = start.interactiveStart;
|
|
1915
|
-
} else {
|
|
1916
|
-
if (iterator->second.destroyed && !start.hasInteractiveStart) return 0;
|
|
1917
|
-
preflightStart = resolvedAnimationStart(iterator->second, start);
|
|
1918
|
-
}
|
|
1919
|
-
bool validKeyframes = keyframes.size() >= 2 &&
|
|
1920
|
-
keyframes.front().offset == 0 && keyframes.back().offset == 1;
|
|
1921
|
-
double previousOffset = -1;
|
|
1922
|
-
for (const Keyframe &keyframe : keyframes) {
|
|
1923
|
-
validKeyframes = validKeyframes && std::isfinite(keyframe.offset) &&
|
|
1924
|
-
keyframe.offset > previousOffset && keyframe.offset >= 0 &&
|
|
1925
|
-
keyframe.offset <= 1 &&
|
|
1926
|
-
isFiniteProtocolV2Presentation(keyframe.presentation);
|
|
1927
|
-
previousOffset = keyframe.offset;
|
|
1928
|
-
}
|
|
1929
|
-
if (strictV2) {
|
|
1930
|
-
validKeyframes = isValidProtocolV2Keyframes(
|
|
1931
|
-
keyframes, preflightStart, presentation, start.hasInteractiveStart);
|
|
1932
|
-
}
|
|
1933
|
-
if (!isFiniteProtocolV2Presentation(preflightStart) || !validKeyframes) {
|
|
1934
|
-
return 0;
|
|
1935
|
-
}
|
|
1936
|
-
const bool reduced = shouldReduceMotion(reduceMotion) || durationMs <= 0;
|
|
1937
|
-
if (strictV2 && !reduced && iterator != registry().end() &&
|
|
1938
|
-
!keyframePlanPreservesLinearHostNormalization(
|
|
1939
|
-
iterator->second, preflightStart, keyframes)) {
|
|
1940
|
-
return 0;
|
|
1941
|
-
}
|
|
1942
|
-
if (iterator == registry().end()) {
|
|
1943
|
-
iterator = registry().try_emplace(driverId).first;
|
|
1944
|
-
iterator->second.latest = start.interactiveStart;
|
|
1945
|
-
iterator->second.hasLatest = true;
|
|
1946
|
-
}
|
|
1947
|
-
auto &state = iterator->second;
|
|
1948
|
-
state.destroyed = false;
|
|
1949
|
-
const int32_t animationId = allocateAnimationId();
|
|
1950
|
-
const Presentation resolvedStart = preflightStart;
|
|
1951
|
-
prepareAnimation(driverId, state, start, presentation);
|
|
1952
|
-
if (reduced) {
|
|
1953
|
-
applyPresentation(state, presentation, false);
|
|
1954
|
-
emitCompletion(driverId, state, animationId, true);
|
|
1955
|
-
return animationId;
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
// Frame zero is a placeholder when the caller omits `from`. Resolve it from
|
|
1959
|
-
// the presentation layer/native-visible state so native-to-native retargets
|
|
1960
|
-
// cannot jump back to a stale compiled sample. With an explicit `from`,
|
|
1961
|
-
// protocol validation requires the same value, so this assignment is a
|
|
1962
|
-
// harmless canonicalization in both cases.
|
|
1963
|
-
if (strictV2 && !start.hasInteractiveStart) {
|
|
1964
|
-
keyframes.front().presentation = resolvedStart;
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
|
-
ActiveAnimation active{animationId};
|
|
1968
|
-
active.kind = AnimationKind::Keyframes;
|
|
1969
|
-
active.start = resolvedStart;
|
|
1970
|
-
active.keyframes = keyframes;
|
|
1971
|
-
active.durationMs = durationMs;
|
|
1972
|
-
active.startedAt = CACurrentMediaTime();
|
|
1973
|
-
active.started = anyDisplayableView(state);
|
|
1974
|
-
state.animation = std::move(active);
|
|
1975
|
-
if (!state.animation->started) {
|
|
1976
|
-
// Latched until the first displayable view — see animateTiming.
|
|
1977
|
-
return animationId;
|
|
1978
|
-
}
|
|
1979
|
-
for (const ViewKey key : state.views) {
|
|
1980
|
-
if ([viewForKey(key) smoothClipAnimateKeyframes:presentation
|
|
1981
|
-
keyframes:keyframes
|
|
1982
|
-
durationMs:durationMs
|
|
1983
|
-
animationId:animationId]) {
|
|
1984
|
-
state.animation->participants.insert(key);
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
return animationId;
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
1488
|
int32_t rejectAnimation(uint64_t driverId) {
|
|
1991
1489
|
if (!NSThread.isMainThread) {
|
|
1992
1490
|
// See setPresentation: blocking off-main risks a deadlock.
|
|
@@ -1994,7 +1492,7 @@ int32_t rejectAnimation(uint64_t driverId) {
|
|
|
1994
1492
|
} auto iterator = registry().find(driverId);
|
|
1995
1493
|
if (iterator == registry().end()) return 0;
|
|
1996
1494
|
const int32_t animationId = allocateAnimationId();
|
|
1997
|
-
emitStandaloneCompletion(driverId, animationId, false);
|
|
1495
|
+
emitStandaloneCompletion(driverId, animationId, 0, false);
|
|
1998
1496
|
return animationId;
|
|
1999
1497
|
}
|
|
2000
1498
|
|
|
@@ -2037,7 +1535,7 @@ CancelResult cancelAnimation(
|
|
|
2037
1535
|
#endif
|
|
2038
1536
|
if (useTarget) {
|
|
2039
1537
|
const Presentation target = state.latest;
|
|
2040
|
-
// A
|
|
1538
|
+
// A pending run never dispatched to its view, so the host still has
|
|
2041
1539
|
// _activeAnimationId == 0 and smoothClipCancelAnimationUsingTarget
|
|
2042
1540
|
// early-returns without writing anything: state.latest would report the
|
|
2043
1541
|
// target while every layer still showed the pre-animation geometry, and
|
|
@@ -2045,15 +1543,15 @@ CancelResult cancelAnimation(
|
|
|
2045
1543
|
// unconditionally — match that. Read `started` before cancelActive, which
|
|
2046
1544
|
// resets state.animation.
|
|
2047
1545
|
//
|
|
2048
|
-
// Gated on the
|
|
1546
|
+
// Gated on the pending case so a started animation is not written twice
|
|
2049
1547
|
// (smoothClipCancelAnimationUsingTarget already applied the target there),
|
|
2050
1548
|
// and applied WITHOUT a velocity sample: a jump to the target is not
|
|
2051
1549
|
// interactive motion, and the cancel-to-target path deliberately keeps it
|
|
2052
1550
|
// out of the 'inherit' history on both platforms — Android's cancel
|
|
2053
1551
|
// fan-out records nothing either.
|
|
2054
|
-
const bool
|
|
1552
|
+
const bool wasPending = !state.animation->started;
|
|
2055
1553
|
cancelActive(driverId, state, true);
|
|
2056
|
-
if (
|
|
1554
|
+
if (wasPending) {
|
|
2057
1555
|
// state.latest already holds the target (set at animate time); only the
|
|
2058
1556
|
// view fan-out is missing.
|
|
2059
1557
|
for (const ViewKey key : state.views) {
|
|
@@ -2107,11 +1605,8 @@ void viewAnimationDidStop(
|
|
|
2107
1605
|
auto &state = iterator->second;
|
|
2108
1606
|
if (!state.animation.has_value() ||
|
|
2109
1607
|
state.animation->animationId != animationId) return;
|
|
2110
|
-
// A
|
|
2111
|
-
//
|
|
2112
|
-
// complete an animation that is waiting to resume. Only a running animation
|
|
2113
|
-
// may be completed by its installed delegates; a latch is completed by
|
|
2114
|
-
// replacement, cancel or destroy.
|
|
1608
|
+
// A pre-ready run has no installed delegate; it completes only through
|
|
1609
|
+
// replacement, cancellation, host loss, or controller destruction.
|
|
2115
1610
|
if (!state.animation->started) return;
|
|
2116
1611
|
|
|
2117
1612
|
const ViewKey key = keyForView(view);
|