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
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
#include <algorithm>
|
|
10
10
|
#include <array>
|
|
11
11
|
#include <atomic>
|
|
12
|
+
#include <cassert>
|
|
12
13
|
#include <chrono>
|
|
13
14
|
#include <cmath>
|
|
14
15
|
#include <limits>
|
|
@@ -26,8 +27,8 @@ using facebook::jni::alias_ref;
|
|
|
26
27
|
using facebook::jni::global_ref;
|
|
27
28
|
|
|
28
29
|
enum class Ownership { Interactive, Native };
|
|
29
|
-
enum class AnimationKind { Timing, Spring
|
|
30
|
-
enum class ViewParticipation { Deferred, Active
|
|
30
|
+
enum class AnimationKind { Timing, Spring };
|
|
31
|
+
enum class ViewParticipation { Deferred, Active };
|
|
31
32
|
|
|
32
33
|
// Load-bearing invariant, not just a convenience clock: advance()'s frame-clock
|
|
33
34
|
// anchor compares this value ABSOLUTELY against a choreographer frame stamp
|
|
@@ -51,40 +52,35 @@ double nowSeconds() {
|
|
|
51
52
|
|
|
52
53
|
struct ActiveAnimation {
|
|
53
54
|
int32_t id = 0;
|
|
55
|
+
int32_t completionTag = 0;
|
|
54
56
|
AnimationKind kind = AnimationKind::Timing;
|
|
55
57
|
Presentation start{{0, 0, 0, 0, 0}, 0, 0};
|
|
56
58
|
Presentation target{{0, 0, 0, 0, 0}, 0, 0};
|
|
57
59
|
Presentation current{{0, 0, 0, 0, 0}, 0, 0};
|
|
58
60
|
TimingAnimation timing{};
|
|
59
61
|
SpringAnimation spring{};
|
|
60
|
-
// Owns the segment-linear keyframes and a cursor into the active segment.
|
|
61
|
-
KeyframeCurve keyframes;
|
|
62
62
|
double durationS = 0;
|
|
63
63
|
double startedAtS = 0;
|
|
64
|
-
// Integrated spring state
|
|
65
|
-
//
|
|
64
|
+
// Integrated spring state. Shadow channels are skipped when neither endpoint
|
|
65
|
+
// has a visible shadow.
|
|
66
66
|
Channels springPosition{};
|
|
67
67
|
Channels springVelocity{};
|
|
68
|
+
std::size_t springChannelCount = kBaseChannelCount;
|
|
68
69
|
double lastFrameS = 0;
|
|
69
|
-
|
|
70
|
-
// False while
|
|
71
|
-
//
|
|
72
|
-
// (pre-registration, or a detached/unsized host), or re-latched by
|
|
73
|
-
// unregisterViewAndroid when the last DISPLAYABLE host left mid-flight.
|
|
74
|
-
// Started by whichever lifecycle/geometry update first sees a displayable
|
|
75
|
-
// entry; each rebases the clock through startLatchedAnimation.
|
|
70
|
+
ScalarSpringState scalarSpring{0, 0};
|
|
71
|
+
// False only while a pre-ready animation waits for its first displayable
|
|
72
|
+
// host. Once started, host loss freezes the visible presentation.
|
|
76
73
|
bool started = false;
|
|
77
74
|
// False until the first advance() translates the wall-clock start stamp
|
|
78
75
|
// onto the choreographer frame-time axis (elapsed-preserving). Every
|
|
79
|
-
//
|
|
80
|
-
// a start that carried the JS-captured Reanimated stamp, which arrives
|
|
76
|
+
// A start that carried the JS-captured Reanimated stamp arrives
|
|
81
77
|
// pre-anchored (see startAnimation) and must not be min()'d again.
|
|
82
78
|
bool frameClockAnchored = false;
|
|
83
79
|
};
|
|
84
80
|
|
|
85
81
|
// Per-view fanout state. Density and host metrics are pushed from Kotlin at
|
|
86
|
-
// register time and on size/density changes; density 0
|
|
87
|
-
//
|
|
82
|
+
// register time and on size/density changes; density 0 falls back to DIP
|
|
83
|
+
// delivery until physical host metrics arrive.
|
|
88
84
|
struct ViewEntry {
|
|
89
85
|
global_ref<JSmoothClipView> view;
|
|
90
86
|
double density = 0;
|
|
@@ -130,10 +126,11 @@ struct GroupMemberAnimation {
|
|
|
130
126
|
struct ActiveGroup {
|
|
131
127
|
uint64_t controllerId = 0;
|
|
132
128
|
int32_t id = 0;
|
|
129
|
+
int32_t completionTag = 0;
|
|
133
130
|
AnimationKind kind = AnimationKind::Timing;
|
|
134
|
-
GroupSuspensionPolicy suspensionPolicy = GroupSuspensionPolicy::Pause;
|
|
135
131
|
TimingAnimation timing{};
|
|
136
132
|
SpringAnimation spring{};
|
|
133
|
+
ScalarSpringState scalarSpring{0, 0};
|
|
137
134
|
double durationS = 0;
|
|
138
135
|
double startedAtS = 0;
|
|
139
136
|
double lastFrameS = 0;
|
|
@@ -142,6 +139,8 @@ struct ActiveGroup {
|
|
|
142
139
|
std::vector<GroupMemberAnimation> members;
|
|
143
140
|
};
|
|
144
141
|
|
|
142
|
+
bool anyDisplayableView(const DriverState &state);
|
|
143
|
+
|
|
145
144
|
std::unordered_map<uint64_t, DriverState> ®istry() {
|
|
146
145
|
static std::unordered_map<uint64_t, DriverState> value;
|
|
147
146
|
return value;
|
|
@@ -255,9 +254,16 @@ void scheduleFrame() {
|
|
|
255
254
|
|
|
256
255
|
// --- Math (ported from SmoothClipRegistry.kt so both platforms match) -----
|
|
257
256
|
//
|
|
258
|
-
// clamp01, toChannels/fromChannels, interpolate, the frame-clock anchor
|
|
259
|
-
//
|
|
260
|
-
//
|
|
257
|
+
// clamp01, toChannels/fromChannels, interpolate, and the frame-clock anchor
|
|
258
|
+
// live in cpp/SmoothClipAnimationCurve.h so a test binary can reach them
|
|
259
|
+
// without linking fbjni; ios/tests pins their behavior.
|
|
260
|
+
|
|
261
|
+
std::array<double, 11> velocityChannels(const Presentation &presentation) {
|
|
262
|
+
const Channels channels = toChannels(presentation);
|
|
263
|
+
std::array<double, 11> result{};
|
|
264
|
+
std::copy_n(channels.begin(), result.size(), result.begin());
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
261
267
|
|
|
262
268
|
// Springs settle below these per-channel thresholds (DIP and DIP/s).
|
|
263
269
|
constexpr double kSpringSettleDisplacement = 0.05;
|
|
@@ -272,7 +278,9 @@ constexpr double kSpringMaxDurationS = 10.0;
|
|
|
272
278
|
// stiffness/mass = 200k. Termination is settle-based: every channel must be
|
|
273
279
|
// near its target and nearly stationary at the same time.
|
|
274
280
|
bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
275
|
-
const auto
|
|
281
|
+
const auto normalizedEndpoints = normalizeShadowEndpoints(
|
|
282
|
+
animation.start, animation.target);
|
|
283
|
+
const auto target = toChannels(normalizedEndpoints.second);
|
|
276
284
|
double dt = now - animation.lastFrameS;
|
|
277
285
|
animation.lastFrameS = now;
|
|
278
286
|
if (dt < 0) dt = 0;
|
|
@@ -284,7 +292,7 @@ bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
|
284
292
|
while (dt > 0) {
|
|
285
293
|
const double step = std::min(dt, kMaxStepS);
|
|
286
294
|
dt -= step;
|
|
287
|
-
for (std::size_t index = 0; index <
|
|
295
|
+
for (std::size_t index = 0; index < animation.springChannelCount; index += 1) {
|
|
288
296
|
const double displacement =
|
|
289
297
|
animation.springPosition[index] - target[index];
|
|
290
298
|
const double acceleration =
|
|
@@ -296,7 +304,7 @@ bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
|
296
304
|
}
|
|
297
305
|
}
|
|
298
306
|
bool settled = true;
|
|
299
|
-
for (std::size_t index = 0; index <
|
|
307
|
+
for (std::size_t index = 0; index < animation.springChannelCount; index += 1) {
|
|
300
308
|
if (!std::isfinite(animation.springPosition[index]) ||
|
|
301
309
|
!std::isfinite(animation.springVelocity[index])) {
|
|
302
310
|
// A divergent configuration must never freeze the clip: end at target.
|
|
@@ -311,8 +319,14 @@ bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
|
311
319
|
settled = false;
|
|
312
320
|
}
|
|
313
321
|
}
|
|
314
|
-
animation.current =
|
|
315
|
-
|
|
322
|
+
animation.current = fromChannels(
|
|
323
|
+
animation.springPosition,
|
|
324
|
+
animation.start.clip.curve,
|
|
325
|
+
animation.start.shadow.enabled || animation.target.shadow.enabled);
|
|
326
|
+
if (!canonicalizePresentation(animation.current)) {
|
|
327
|
+
animation.current = animation.target;
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
316
330
|
return settled || now - animation.startedAtS >= kSpringMaxDurationS;
|
|
317
331
|
}
|
|
318
332
|
|
|
@@ -332,23 +346,42 @@ bool shouldReduceMotion(int32_t setting) {
|
|
|
332
346
|
|
|
333
347
|
// --- State helpers --------------------------------------------------------
|
|
334
348
|
|
|
335
|
-
void emitCompletion(
|
|
349
|
+
void emitCompletion(
|
|
350
|
+
uint64_t driverId,
|
|
351
|
+
int32_t animationId,
|
|
352
|
+
int32_t completionTag,
|
|
353
|
+
bool finished) {
|
|
336
354
|
std::lock_guard<std::mutex> lock(completionSinkMutex());
|
|
337
355
|
if (completionSink().callback) {
|
|
338
|
-
completionSink().callback(
|
|
356
|
+
completionSink().callback(
|
|
357
|
+
driverId, animationId, completionTag, finished);
|
|
339
358
|
}
|
|
340
359
|
}
|
|
341
360
|
|
|
342
361
|
void emitGroupCompletion(const ActiveGroup &group, bool finished) {
|
|
343
|
-
std::vector<
|
|
344
|
-
|
|
362
|
+
std::vector<DriverSnapshot> snapshots;
|
|
363
|
+
snapshots.reserve(group.members.size());
|
|
345
364
|
for (const GroupMemberAnimation &member : group.members) {
|
|
346
|
-
|
|
365
|
+
const auto iterator = registry().find(member.driverId);
|
|
366
|
+
if (iterator == registry().end() || iterator->second.destroyed) {
|
|
367
|
+
snapshots.push_back(
|
|
368
|
+
{member.driverId, unavailablePresentation(), false});
|
|
369
|
+
} else {
|
|
370
|
+
snapshots.push_back({
|
|
371
|
+
member.driverId,
|
|
372
|
+
iterator->second.latest,
|
|
373
|
+
anyDisplayableView(iterator->second),
|
|
374
|
+
});
|
|
375
|
+
}
|
|
347
376
|
}
|
|
348
377
|
std::lock_guard<std::mutex> lock(completionSinkMutex());
|
|
349
378
|
if (groupCompletionSink().callback) {
|
|
350
379
|
groupCompletionSink().callback(
|
|
351
|
-
group.controllerId,
|
|
380
|
+
group.controllerId,
|
|
381
|
+
group.id,
|
|
382
|
+
group.completionTag,
|
|
383
|
+
finished,
|
|
384
|
+
std::move(snapshots));
|
|
352
385
|
}
|
|
353
386
|
}
|
|
354
387
|
|
|
@@ -374,67 +407,28 @@ Presentation visiblePresentation(
|
|
|
374
407
|
return state.animation.has_value() ? state.animation->current : state.latest;
|
|
375
408
|
}
|
|
376
409
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
presentation.clip.topLeftRadius, presentation.clip.radius);
|
|
380
|
-
const double topRight = SmoothClipResolvedRadius(
|
|
381
|
-
presentation.clip.topRightRadius, presentation.clip.radius);
|
|
382
|
-
const double bottomRight = SmoothClipResolvedRadius(
|
|
383
|
-
presentation.clip.bottomRightRadius, presentation.clip.radius);
|
|
384
|
-
const double bottomLeft = SmoothClipResolvedRadius(
|
|
385
|
-
presentation.clip.bottomLeftRadius, presentation.clip.radius);
|
|
386
|
-
return presentation.clip.curve != ClipCurve::Circular ||
|
|
387
|
-
presentation.contentScale != 1.0 ||
|
|
388
|
-
topLeft != presentation.clip.radius ||
|
|
389
|
-
topRight != presentation.clip.radius ||
|
|
390
|
-
bottomRight != presentation.clip.radius ||
|
|
391
|
-
bottomLeft != presentation.clip.radius;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Terminal per-frame delivery: scale DIP -> px and normalize against the
|
|
395
|
-
// pushed host metrics in C++, so the JNI call carries final pixel floats and
|
|
396
|
-
// the Kotlin side reduces to field stores + invalidateOutline().
|
|
410
|
+
// Terminal per-frame delivery: scale canonical DIP geometry to pixels without
|
|
411
|
+
// consulting host metrics. Each SmoothClipView applies the fixed viewport crop.
|
|
397
412
|
void deliverToView(const ViewEntry &entry, const Presentation &presentation) {
|
|
398
|
-
const bool deliverV2 = needsV2Delivery(presentation);
|
|
399
413
|
if (entry.density <= 0) {
|
|
400
|
-
|
|
401
|
-
entry.view->applyClipV2(presentation);
|
|
402
|
-
} else {
|
|
403
|
-
entry.view->applyClip(presentation);
|
|
404
|
-
}
|
|
414
|
+
entry.view->applyClip(presentation);
|
|
405
415
|
return;
|
|
406
416
|
}
|
|
407
417
|
const double density = entry.density;
|
|
408
|
-
|
|
409
|
-
if (
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
entry.hostHeightPx,
|
|
423
|
-
clip)) {
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
} else {
|
|
427
|
-
if (!SmoothClipNormalize(
|
|
428
|
-
presentation.clip.x * density,
|
|
429
|
-
presentation.clip.y * density,
|
|
430
|
-
presentation.clip.width * density,
|
|
431
|
-
presentation.clip.height * density,
|
|
432
|
-
presentation.clip.radius * density,
|
|
433
|
-
entry.hostWidthPx,
|
|
434
|
-
entry.hostHeightPx,
|
|
435
|
-
clip)) {
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
418
|
+
CanonicalClip clip;
|
|
419
|
+
if (!SmoothClipCanonicalize(
|
|
420
|
+
presentation.clip.x * density,
|
|
421
|
+
presentation.clip.y * density,
|
|
422
|
+
presentation.clip.width * density,
|
|
423
|
+
presentation.clip.height * density,
|
|
424
|
+
presentation.clip.radius * density,
|
|
425
|
+
presentation.clip.topLeftRadius * density,
|
|
426
|
+
presentation.clip.topRightRadius * density,
|
|
427
|
+
presentation.clip.bottomRightRadius * density,
|
|
428
|
+
presentation.clip.bottomLeftRadius * density,
|
|
429
|
+
presentation.clip.curve,
|
|
430
|
+
clip)) {
|
|
431
|
+
return;
|
|
438
432
|
}
|
|
439
433
|
const double translateX = presentation.contentTranslateX * density;
|
|
440
434
|
const double translateY = presentation.contentTranslateY * density;
|
|
@@ -444,12 +438,17 @@ void deliverToView(const ViewEntry &entry, const Presentation &presentation) {
|
|
|
444
438
|
presentation.contentScale <= 0) {
|
|
445
439
|
return;
|
|
446
440
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
441
|
+
Shadow shadowPx = presentation.shadow;
|
|
442
|
+
shadowPx.offsetX *= density;
|
|
443
|
+
shadowPx.offsetY *= density;
|
|
444
|
+
shadowPx.blurRadius *= density;
|
|
445
|
+
shadowPx.spreadDistance *= density;
|
|
446
|
+
entry.view->applyClipPx(
|
|
447
|
+
clip,
|
|
448
|
+
translateX,
|
|
449
|
+
translateY,
|
|
450
|
+
presentation.contentScale,
|
|
451
|
+
shadowPx);
|
|
453
452
|
}
|
|
454
453
|
|
|
455
454
|
void applyToViews(DriverState &state, const Presentation &presentation) {
|
|
@@ -458,6 +457,22 @@ void applyToViews(DriverState &state, const Presentation &presentation) {
|
|
|
458
457
|
}
|
|
459
458
|
}
|
|
460
459
|
|
|
460
|
+
void setAutonomousMotion(DriverState &state, bool active) {
|
|
461
|
+
for (const ViewEntry &entry : state.views) {
|
|
462
|
+
entry.view->setAutonomousMotion(active);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
void applyAnimationFrameToViews(
|
|
467
|
+
DriverState &state,
|
|
468
|
+
const Presentation &presentation) {
|
|
469
|
+
for (const auto &entry : state.views) {
|
|
470
|
+
if (entry.participation == ViewParticipation::Active) {
|
|
471
|
+
deliverToView(entry, presentation);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
461
476
|
void applyPresentation(DriverState &state, const Presentation &presentation) {
|
|
462
477
|
state.latest = presentation;
|
|
463
478
|
state.hasLatest = true;
|
|
@@ -474,6 +489,7 @@ std::vector<uint64_t> &animatingDrivers() {
|
|
|
474
489
|
// visits animating drivers. Removal is eager: a stale id surviving a driver
|
|
475
490
|
// erase/recreate cycle would let one frame integrate the animation twice.
|
|
476
491
|
void clearActiveAnimation(uint64_t driverId, DriverState &state) {
|
|
492
|
+
setAutonomousMotion(state, false);
|
|
477
493
|
state.animation.reset();
|
|
478
494
|
for (ViewEntry &entry : state.views) {
|
|
479
495
|
entry.participation = ViewParticipation::Deferred;
|
|
@@ -486,8 +502,9 @@ void clearActiveAnimation(uint64_t driverId, DriverState &state) {
|
|
|
486
502
|
void finishActive(uint64_t driverId, DriverState &state, bool finished) {
|
|
487
503
|
if (!state.animation.has_value()) return;
|
|
488
504
|
const int32_t animationId = state.animation->id;
|
|
505
|
+
const int32_t completionTag = state.animation->completionTag;
|
|
489
506
|
clearActiveAnimation(driverId, state);
|
|
490
|
-
emitCompletion(driverId, animationId, finished);
|
|
507
|
+
emitCompletion(driverId, animationId, completionTag, finished);
|
|
491
508
|
}
|
|
492
509
|
|
|
493
510
|
Presentation prepareAnimation(
|
|
@@ -522,77 +539,6 @@ bool entryDisplayable(const ViewEntry &entry) {
|
|
|
522
539
|
entry.lifecycleVisible;
|
|
523
540
|
}
|
|
524
541
|
|
|
525
|
-
Geometry geometryInPixels(
|
|
526
|
-
const Geometry &geometry,
|
|
527
|
-
double density) {
|
|
528
|
-
Geometry result = geometry;
|
|
529
|
-
result.x *= density;
|
|
530
|
-
result.y *= density;
|
|
531
|
-
result.width *= density;
|
|
532
|
-
result.height *= density;
|
|
533
|
-
result.radius *= density;
|
|
534
|
-
result.topLeftRadius *= density;
|
|
535
|
-
result.topRightRadius *= density;
|
|
536
|
-
result.bottomRightRadius *= density;
|
|
537
|
-
result.bottomLeftRadius *= density;
|
|
538
|
-
return result;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
bool presentationFitsEveryKnownHostWithoutNormalization(
|
|
542
|
-
const DriverState &state,
|
|
543
|
-
const Presentation &presentation) {
|
|
544
|
-
for (const ViewEntry &entry : state.views) {
|
|
545
|
-
// Zero host metrics are still waiting on Fabric layout and remain covered
|
|
546
|
-
// by the existing readiness latch. Once metrics exist, density is required
|
|
547
|
-
// to compare the DIP presentation against the exact pixel host bounds.
|
|
548
|
-
if (entry.hostWidthPx <= 0 || entry.hostHeightPx <= 0) continue;
|
|
549
|
-
if (entry.density <= 0 ||
|
|
550
|
-
!SmoothClipGeometryNormalizationIsIdentity(
|
|
551
|
-
geometryInPixels(presentation.clip, entry.density),
|
|
552
|
-
entry.hostWidthPx,
|
|
553
|
-
entry.hostHeightPx)) {
|
|
554
|
-
return false;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
return true;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
bool timingPlanPreservesLinearHostNormalization(
|
|
561
|
-
const DriverState &state,
|
|
562
|
-
const Presentation &start,
|
|
563
|
-
const Presentation &target,
|
|
564
|
-
const TimingAnimation &animation) {
|
|
565
|
-
const double minimumProgress = std::min(
|
|
566
|
-
{0.0, 1.0, animation.controlPoint1Y, animation.controlPoint2Y});
|
|
567
|
-
const double maximumProgress = std::max(
|
|
568
|
-
{0.0, 1.0, animation.controlPoint1Y, animation.controlPoint2Y});
|
|
569
|
-
const Presentation minimum =
|
|
570
|
-
interpolate(start, target, minimumProgress);
|
|
571
|
-
const Presentation maximum =
|
|
572
|
-
interpolate(start, target, maximumProgress);
|
|
573
|
-
return isFiniteProtocolV2Presentation(minimum) &&
|
|
574
|
-
isFiniteProtocolV2Presentation(maximum) &&
|
|
575
|
-
presentationFitsEveryKnownHostWithoutNormalization(state, minimum) &&
|
|
576
|
-
presentationFitsEveryKnownHostWithoutNormalization(state, maximum);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
bool keyframePlanPreservesLinearHostNormalization(
|
|
580
|
-
const DriverState &state,
|
|
581
|
-
const Presentation &resolvedStart,
|
|
582
|
-
const std::vector<Keyframe> &keyframes) {
|
|
583
|
-
if (!presentationFitsEveryKnownHostWithoutNormalization(
|
|
584
|
-
state, resolvedStart)) {
|
|
585
|
-
return false;
|
|
586
|
-
}
|
|
587
|
-
for (std::size_t index = 1; index < keyframes.size(); index += 1) {
|
|
588
|
-
if (!presentationFitsEveryKnownHostWithoutNormalization(
|
|
589
|
-
state, keyframes[index].presentation)) {
|
|
590
|
-
return false;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
return true;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
542
|
bool anyDisplayableView(const DriverState &state) {
|
|
597
543
|
for (const ViewEntry &entry : state.views) {
|
|
598
544
|
if (entryDisplayable(entry)) return true;
|
|
@@ -617,6 +563,8 @@ void removeAnimatingGroup(int32_t groupId) {
|
|
|
617
563
|
}
|
|
618
564
|
|
|
619
565
|
void applyGroupCurrent(const ActiveGroup &group) {
|
|
566
|
+
// Group readiness is the participation contract. Each controller owns one
|
|
567
|
+
// host, and host loss removes the group before another frame can advance.
|
|
620
568
|
for (const GroupMemberAnimation &member : group.members) {
|
|
621
569
|
const auto iterator = registry().find(member.driverId);
|
|
622
570
|
if (iterator != registry().end()) {
|
|
@@ -625,6 +573,23 @@ void applyGroupCurrent(const ActiveGroup &group) {
|
|
|
625
573
|
}
|
|
626
574
|
}
|
|
627
575
|
|
|
576
|
+
void startGroupFrameLoop(ActiveGroup &group) {
|
|
577
|
+
assert(group.started);
|
|
578
|
+
assert(allGroupMembersReady(group));
|
|
579
|
+
for (const GroupMemberAnimation &member : group.members) {
|
|
580
|
+
auto iterator = registry().find(member.driverId);
|
|
581
|
+
if (iterator != registry().end()) {
|
|
582
|
+
setAutonomousMotion(iterator->second, true);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
auto &active = animatingGroups();
|
|
586
|
+
if (std::find(active.begin(), active.end(), group.id) == active.end()) {
|
|
587
|
+
active.push_back(group.id);
|
|
588
|
+
}
|
|
589
|
+
applyGroupCurrent(group);
|
|
590
|
+
scheduleFrame();
|
|
591
|
+
}
|
|
592
|
+
|
|
628
593
|
std::vector<DriverSnapshot> finishGroupImpl(
|
|
629
594
|
int32_t groupId,
|
|
630
595
|
bool useTarget,
|
|
@@ -650,6 +615,7 @@ std::vector<DriverSnapshot> finishGroupImpl(
|
|
|
650
615
|
state.latest = result;
|
|
651
616
|
state.hasLatest = true;
|
|
652
617
|
state.ownership = Ownership::Interactive;
|
|
618
|
+
setAutonomousMotion(state, false);
|
|
653
619
|
applyToViews(state, result);
|
|
654
620
|
snapshots.push_back(
|
|
655
621
|
{member.driverId, result, !state.destroyed && anyDisplayableView(state)});
|
|
@@ -658,7 +624,7 @@ std::vector<DriverSnapshot> finishGroupImpl(
|
|
|
658
624
|
return snapshots;
|
|
659
625
|
}
|
|
660
626
|
|
|
661
|
-
void
|
|
627
|
+
void startPendingGroup(ActiveGroup &group) {
|
|
662
628
|
const double now = nowSeconds();
|
|
663
629
|
group.started = true;
|
|
664
630
|
group.startedAtS = now;
|
|
@@ -669,45 +635,7 @@ void startLatchedGroup(ActiveGroup &group) {
|
|
|
669
635
|
member.animation.lastFrameS = now;
|
|
670
636
|
member.animation.frameClockAnchored = false;
|
|
671
637
|
}
|
|
672
|
-
|
|
673
|
-
if (std::find(active.begin(), active.end(), group.id) == active.end()) {
|
|
674
|
-
active.push_back(group.id);
|
|
675
|
-
}
|
|
676
|
-
applyGroupCurrent(group);
|
|
677
|
-
scheduleFrame();
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
void pauseGroup(ActiveGroup &group) {
|
|
681
|
-
if (group.kind == AnimationKind::Timing) {
|
|
682
|
-
const double rawProgress = timingFraction(
|
|
683
|
-
group.lastFrameS, group.startedAtS, group.durationS);
|
|
684
|
-
const TimingRemainder remainder = timingRemainder(group.timing, rawProgress);
|
|
685
|
-
group.timing = remainder.animation;
|
|
686
|
-
group.durationS = remainder.animation.durationMs / 1000.0;
|
|
687
|
-
for (GroupMemberAnimation &member : group.members) {
|
|
688
|
-
member.animation.start = member.animation.current;
|
|
689
|
-
member.animation.timing = remainder.animation;
|
|
690
|
-
}
|
|
691
|
-
} else if (group.kind == AnimationKind::Keyframes) {
|
|
692
|
-
double remainingDurationMs = 0;
|
|
693
|
-
for (GroupMemberAnimation &member : group.members) {
|
|
694
|
-
const KeyframeContinuation continuation = keyframeContinuationAtFrame(
|
|
695
|
-
member.animation.keyframes.frames(),
|
|
696
|
-
member.animation.current,
|
|
697
|
-
member.animation.target,
|
|
698
|
-
group.lastFrameS,
|
|
699
|
-
group.startedAtS,
|
|
700
|
-
group.durationS);
|
|
701
|
-
member.animation.start = continuation.start;
|
|
702
|
-
member.animation.current = continuation.start;
|
|
703
|
-
member.animation.keyframes.reset(continuation.frames);
|
|
704
|
-
remainingDurationMs = continuation.durationMs;
|
|
705
|
-
}
|
|
706
|
-
group.durationS = remainingDurationMs / 1000.0;
|
|
707
|
-
}
|
|
708
|
-
group.started = false;
|
|
709
|
-
group.frameClockAnchored = false;
|
|
710
|
-
removeAnimatingGroup(group.id);
|
|
638
|
+
startGroupFrameLoop(group);
|
|
711
639
|
}
|
|
712
640
|
|
|
713
641
|
void reconcileGroupReadiness(uint64_t driverId) {
|
|
@@ -724,27 +652,20 @@ void reconcileGroupReadiness(uint64_t driverId) {
|
|
|
724
652
|
ActiveGroup &group = groupIterator->second;
|
|
725
653
|
const bool ready = allGroupMembersReady(group);
|
|
726
654
|
if (!group.started) {
|
|
727
|
-
if (ready)
|
|
655
|
+
if (ready) startPendingGroup(group);
|
|
728
656
|
return;
|
|
729
657
|
}
|
|
730
658
|
if (ready) return;
|
|
731
|
-
|
|
732
|
-
finishGroupImpl(groupId, true, true);
|
|
733
|
-
} else {
|
|
734
|
-
pauseGroup(group);
|
|
735
|
-
}
|
|
659
|
+
finishGroupImpl(groupId, false, false);
|
|
736
660
|
}
|
|
737
661
|
|
|
738
|
-
// Starts a
|
|
739
|
-
//
|
|
740
|
-
void
|
|
662
|
+
// Starts a pre-ready animation with its full duration, then joins the
|
|
663
|
+
// choreographer loop.
|
|
664
|
+
void startPendingAnimation(uint64_t driverId, DriverState &state) {
|
|
741
665
|
auto &animation = *state.animation;
|
|
742
666
|
animation.started = true;
|
|
743
667
|
animation.startedAtS = nowSeconds();
|
|
744
668
|
animation.lastFrameS = animation.startedAtS;
|
|
745
|
-
// Load-bearing for re-latch resumes: unregisterViewAndroid rewrites this
|
|
746
|
-
// ActiveAnimation in place, so a stale anchor would replay the fraction-0
|
|
747
|
-
// first frame the anchor exists to remove.
|
|
748
669
|
animation.frameClockAnchored = false;
|
|
749
670
|
auto &active = animatingDrivers();
|
|
750
671
|
if (std::find(active.begin(), active.end(), driverId) == active.end()) {
|
|
@@ -755,6 +676,7 @@ void startLatchedAnimation(uint64_t driverId, DriverState &state) {
|
|
|
755
676
|
entry.participation = ViewParticipation::Active;
|
|
756
677
|
}
|
|
757
678
|
}
|
|
679
|
+
setAutonomousMotion(state, true);
|
|
758
680
|
applyToViews(state, animation.start);
|
|
759
681
|
scheduleFrame();
|
|
760
682
|
}
|
|
@@ -770,16 +692,16 @@ int32_t startAnimation(
|
|
|
770
692
|
// animation arrives pre-anchored and advance() must not min() it against
|
|
771
693
|
// the dispatching frame, which for a CALLBACK_INPUT start is EARLIER than
|
|
772
694
|
// the call and would re-open the intra-frame lead the hint closes. Without
|
|
773
|
-
// a hint (NaN:
|
|
695
|
+
// a hint (NaN: unstamped callers, tests, iOS ignoring the field)
|
|
774
696
|
// this reduces exactly to the old nowSeconds() + min() anchor path.
|
|
775
697
|
const double wallNow = nowSeconds();
|
|
776
698
|
const StartStamp stamp = resolveStartStamp(startedAtHintS, wallNow);
|
|
777
699
|
animation.startedAtS = stamp.startedAtS;
|
|
778
700
|
animation.lastFrameS = stamp.startedAtS;
|
|
779
701
|
animation.frameClockAnchored = stamp.frameClockAnchored;
|
|
780
|
-
// current = start
|
|
702
|
+
// current = start while pre-ready is load-bearing: cancelAnimation,
|
|
781
703
|
// beginInteraction, prepareAnimation's visibleBefore and registerView's
|
|
782
|
-
// visible all read animation->current, giving a never-rendered
|
|
704
|
+
// visible all read animation->current, giving a never-rendered pending-run
|
|
783
705
|
// freeze-at-start / replace-from-start semantics with no extra branches.
|
|
784
706
|
animation.current = animation.start;
|
|
785
707
|
animation.started = anyDisplayableView(state);
|
|
@@ -790,9 +712,9 @@ int32_t startAnimation(
|
|
|
790
712
|
: ViewParticipation::Deferred;
|
|
791
713
|
}
|
|
792
714
|
if (!state.animation->started) {
|
|
793
|
-
//
|
|
794
|
-
//
|
|
795
|
-
//
|
|
715
|
+
// No host can display yet (animateTo raced the mount, or the host is
|
|
716
|
+
// detached/unsized). The first displayable registration, host-geometry
|
|
717
|
+
// push, or window attach starts the full clock, joins
|
|
796
718
|
// animatingDrivers() and schedules the frame loop. Non-zero id is still
|
|
797
719
|
// returned so the JS side does not treat this as rejection.
|
|
798
720
|
return state.animation->id;
|
|
@@ -801,60 +723,23 @@ int32_t startAnimation(
|
|
|
801
723
|
if (std::find(active.begin(), active.end(), driverId) == active.end()) {
|
|
802
724
|
active.push_back(driverId);
|
|
803
725
|
}
|
|
726
|
+
setAutonomousMotion(state, true);
|
|
804
727
|
applyToViews(state, state.animation->start);
|
|
805
728
|
scheduleFrame();
|
|
806
729
|
return state.animation->id;
|
|
807
730
|
}
|
|
808
731
|
|
|
809
|
-
void
|
|
732
|
+
void finishIfHostUnavailable(uint64_t driverId, DriverState &state) {
|
|
810
733
|
if (!state.animation.has_value() || !state.animation->started ||
|
|
811
734
|
anyDisplayableView(state)) {
|
|
812
735
|
return;
|
|
813
736
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
if (animation.kind == AnimationKind::Timing) {
|
|
822
|
-
const TimingContinuation continuation = timingContinuationAtFrame(
|
|
823
|
-
animation.timing,
|
|
824
|
-
animation.start,
|
|
825
|
-
animation.target,
|
|
826
|
-
animation.lastFrameS,
|
|
827
|
-
animation.startedAtS,
|
|
828
|
-
animation.durationS);
|
|
829
|
-
animation.current = continuation.start;
|
|
830
|
-
animation.timing = continuation.animation;
|
|
831
|
-
animation.durationS = continuation.animation.durationMs / 1000.0;
|
|
832
|
-
} else if (animation.kind == AnimationKind::Keyframes) {
|
|
833
|
-
const KeyframeContinuation continuation = keyframeContinuationAtFrame(
|
|
834
|
-
animation.keyframes.frames(),
|
|
835
|
-
animation.current,
|
|
836
|
-
animation.target,
|
|
837
|
-
animation.lastFrameS,
|
|
838
|
-
animation.startedAtS,
|
|
839
|
-
animation.durationS);
|
|
840
|
-
animation.current = continuation.start;
|
|
841
|
-
animation.keyframes.reset(continuation.frames);
|
|
842
|
-
animation.durationS = continuation.durationMs / 1000.0;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
if (state.destroyed ||
|
|
846
|
-
(animation.kind != AnimationKind::Spring && animation.durationS <= 0)) {
|
|
847
|
-
state.ownership = Ownership::Interactive;
|
|
848
|
-
finishActive(driverId, state, false);
|
|
849
|
-
return;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
animation.start = animation.current;
|
|
853
|
-
animation.started = false;
|
|
854
|
-
animation.frameClockAnchored = false;
|
|
855
|
-
auto &active = animatingDrivers();
|
|
856
|
-
active.erase(
|
|
857
|
-
std::remove(active.begin(), active.end(), driverId), active.end());
|
|
737
|
+
const Presentation target = state.animation->target;
|
|
738
|
+
state.latest = target;
|
|
739
|
+
state.hasLatest = true;
|
|
740
|
+
state.ownership = Ownership::Interactive;
|
|
741
|
+
applyToViews(state, target);
|
|
742
|
+
finishActive(driverId, state, true);
|
|
858
743
|
}
|
|
859
744
|
|
|
860
745
|
void reconcileViewDisplayability(
|
|
@@ -863,7 +748,7 @@ void reconcileViewDisplayability(
|
|
|
863
748
|
ViewEntry &entry) {
|
|
864
749
|
if (!state.animation.has_value()) return;
|
|
865
750
|
if (!state.animation->started) {
|
|
866
|
-
if (entryDisplayable(entry))
|
|
751
|
+
if (entryDisplayable(entry)) startPendingAnimation(driverId, state);
|
|
867
752
|
return;
|
|
868
753
|
}
|
|
869
754
|
|
|
@@ -873,20 +758,20 @@ void reconcileViewDisplayability(
|
|
|
873
758
|
deliverToView(entry, state.animation->current);
|
|
874
759
|
}
|
|
875
760
|
} else if (entry.participation == ViewParticipation::Active) {
|
|
876
|
-
entry.participation = ViewParticipation::
|
|
761
|
+
entry.participation = ViewParticipation::Deferred;
|
|
877
762
|
}
|
|
878
|
-
|
|
763
|
+
finishIfHostUnavailable(driverId, state);
|
|
879
764
|
}
|
|
880
765
|
|
|
881
766
|
void advance(uint64_t driverId, DriverState &state, double now) {
|
|
882
767
|
ActiveAnimation &animation = *state.animation;
|
|
883
768
|
|
|
884
769
|
if (!animation.frameClockAnchored) {
|
|
885
|
-
// This is the
|
|
770
|
+
// This is the unstamped/pre-ready fallback. Worklet-issued animations carry
|
|
886
771
|
// Reanimated's exact `__frameTimestamp || _getAnimationTimestamp()` value
|
|
887
772
|
// and arrive pre-anchored, so they skip this approximation — including the
|
|
888
773
|
// CALLBACK_INPUT case where the current frame stamp predates the call.
|
|
889
|
-
// Here startedAtS/lastFrameS hold nowSeconds() sampled at native
|
|
774
|
+
// Here startedAtS/lastFrameS hold nowSeconds() sampled at native
|
|
890
775
|
// attach or by an older caller, while `now` is the frame's vsync stamp on
|
|
891
776
|
// the same CLOCK_MONOTONIC timebase. Taking the earlier stamp prevents a
|
|
892
777
|
// mid-frame native start from duplicating fraction zero.
|
|
@@ -910,44 +795,36 @@ void advance(uint64_t driverId, DriverState &state, double now) {
|
|
|
910
795
|
if (animation.kind == AnimationKind::Spring) {
|
|
911
796
|
done = advanceSpring(animation, now);
|
|
912
797
|
} else {
|
|
913
|
-
// This is the frame whose presentation is delivered below. Lifecycle
|
|
914
|
-
// re-latch must trim from this timestamp, never from a later wall sample.
|
|
915
798
|
animation.lastFrameS = now;
|
|
916
799
|
const double fraction =
|
|
917
800
|
timingFraction(now, animation.startedAtS, animation.durationS);
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
animation.current =
|
|
801
|
+
const double eased = cubicBezier(
|
|
802
|
+
animation.timing.controlPoint1X,
|
|
803
|
+
animation.timing.controlPoint1Y,
|
|
804
|
+
animation.timing.controlPoint2X,
|
|
805
|
+
animation.timing.controlPoint2Y,
|
|
806
|
+
fraction);
|
|
807
|
+
animation.current = interpolate(animation.start, animation.target, eased);
|
|
808
|
+
const bool canonical = canonicalizePresentation(animation.current);
|
|
809
|
+
if (!canonical) {
|
|
810
|
+
animation.current = animation.target;
|
|
928
811
|
}
|
|
929
|
-
done = fraction >= 1.0;
|
|
812
|
+
done = !canonical || fraction >= 1.0;
|
|
930
813
|
}
|
|
931
814
|
// The completion branch below fans out the exact target; applying the
|
|
932
815
|
// integrated value too would double every JNI crossing on the final frame.
|
|
933
|
-
if (!done)
|
|
816
|
+
if (!done) applyAnimationFrameToViews(state, animation.current);
|
|
934
817
|
|
|
935
818
|
if (done) {
|
|
936
819
|
const int32_t animationId = animation.id;
|
|
937
|
-
|
|
938
|
-
for (const ViewEntry &entry : state.views) {
|
|
939
|
-
if (entry.participation == ViewParticipation::Suspended) {
|
|
940
|
-
finished = false;
|
|
941
|
-
break;
|
|
942
|
-
}
|
|
943
|
-
}
|
|
820
|
+
const int32_t completionTag = animation.completionTag;
|
|
944
821
|
const Presentation target = animation.target;
|
|
945
822
|
clearActiveAnimation(driverId, state);
|
|
946
823
|
state.latest = target;
|
|
947
824
|
state.hasLatest = true;
|
|
948
825
|
state.ownership = Ownership::Interactive;
|
|
949
826
|
applyToViews(state, target);
|
|
950
|
-
emitCompletion(driverId, animationId,
|
|
827
|
+
emitCompletion(driverId, animationId, completionTag, true);
|
|
951
828
|
}
|
|
952
829
|
}
|
|
953
830
|
|
|
@@ -965,31 +842,44 @@ void advanceGroup(int32_t groupId, ActiveGroup &group, double now) {
|
|
|
965
842
|
|
|
966
843
|
bool done = false;
|
|
967
844
|
if (group.kind == AnimationKind::Spring) {
|
|
968
|
-
|
|
845
|
+
const double deltaTime = now - group.lastFrameS;
|
|
846
|
+
group.lastFrameS = now;
|
|
847
|
+
group.scalarSpring = advanceScalarSpring(
|
|
848
|
+
group.scalarSpring, group.spring, deltaTime);
|
|
849
|
+
done = now - group.startedAtS >= kSpringMaxDurationS ||
|
|
850
|
+
relativeSpringEnergy(group.scalarSpring, group.spring) <=
|
|
851
|
+
group.spring.energyThreshold;
|
|
969
852
|
for (GroupMemberAnimation &member : group.members) {
|
|
970
|
-
|
|
853
|
+
member.animation.current = interpolate(
|
|
854
|
+
member.animation.start,
|
|
855
|
+
member.animation.target,
|
|
856
|
+
done ? 1.0 : group.scalarSpring.position);
|
|
857
|
+
if (!canonicalizePresentation(member.animation.current)) {
|
|
858
|
+
member.animation.current = member.animation.target;
|
|
859
|
+
done = true;
|
|
860
|
+
}
|
|
971
861
|
}
|
|
972
862
|
} else {
|
|
973
863
|
group.lastFrameS = now;
|
|
974
864
|
const double fraction =
|
|
975
865
|
timingFraction(now, group.startedAtS, group.durationS);
|
|
866
|
+
const double eased = cubicBezier(
|
|
867
|
+
group.timing.controlPoint1X,
|
|
868
|
+
group.timing.controlPoint1Y,
|
|
869
|
+
group.timing.controlPoint2X,
|
|
870
|
+
group.timing.controlPoint2Y,
|
|
871
|
+
fraction);
|
|
872
|
+
bool canonical = true;
|
|
976
873
|
for (GroupMemberAnimation &member : group.members) {
|
|
977
874
|
member.animation.lastFrameS = now;
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
group.timing.controlPoint1X,
|
|
984
|
-
group.timing.controlPoint1Y,
|
|
985
|
-
group.timing.controlPoint2X,
|
|
986
|
-
group.timing.controlPoint2Y,
|
|
987
|
-
fraction);
|
|
988
|
-
member.animation.current = interpolate(
|
|
989
|
-
member.animation.start, member.animation.target, eased);
|
|
875
|
+
member.animation.current = interpolate(
|
|
876
|
+
member.animation.start, member.animation.target, eased);
|
|
877
|
+
if (!canonicalizePresentation(member.animation.current)) {
|
|
878
|
+
member.animation.current = member.animation.target;
|
|
879
|
+
canonical = false;
|
|
990
880
|
}
|
|
991
881
|
}
|
|
992
|
-
done = fraction >= 1.0;
|
|
882
|
+
done = !canonical || fraction >= 1.0;
|
|
993
883
|
}
|
|
994
884
|
|
|
995
885
|
if (done) {
|
|
@@ -1147,12 +1037,13 @@ std::vector<DriverSnapshot> beginGroupInteraction(
|
|
|
1147
1037
|
DriverState &state = registry().at(driverId);
|
|
1148
1038
|
if (state.animation.has_value()) {
|
|
1149
1039
|
const int32_t animationId = state.animation->id;
|
|
1040
|
+
const int32_t completionTag = state.animation->completionTag;
|
|
1150
1041
|
const Presentation current = state.animation->current;
|
|
1151
1042
|
clearActiveAnimation(driverId, state);
|
|
1152
1043
|
state.latest = current;
|
|
1153
1044
|
state.hasLatest = true;
|
|
1154
1045
|
applyToViews(state, current);
|
|
1155
|
-
emitCompletion(driverId, animationId, false);
|
|
1046
|
+
emitCompletion(driverId, animationId, completionTag, false);
|
|
1156
1047
|
}
|
|
1157
1048
|
state.ownership = Ownership::Interactive;
|
|
1158
1049
|
}
|
|
@@ -1161,17 +1052,21 @@ std::vector<DriverSnapshot> beginGroupInteraction(
|
|
|
1161
1052
|
|
|
1162
1053
|
bool setPresentationBatch(const std::vector<BatchEntry> &entries) {
|
|
1163
1054
|
if (!isOnMainThread() || entries.empty()) return false;
|
|
1055
|
+
std::vector<BatchEntry> canonicalEntries = entries;
|
|
1056
|
+
for (BatchEntry &entry : canonicalEntries) {
|
|
1057
|
+
if (!canonicalizePresentation(entry.presentation)) return false;
|
|
1058
|
+
}
|
|
1164
1059
|
std::vector<uint64_t> unique;
|
|
1165
1060
|
std::vector<int32_t> groupsToCancel;
|
|
1166
1061
|
unique.reserve(entries.size());
|
|
1167
|
-
for (const BatchEntry &entry :
|
|
1062
|
+
for (const BatchEntry &entry : canonicalEntries) {
|
|
1168
1063
|
if (entry.driverId == 0 ||
|
|
1169
1064
|
std::find(unique.begin(), unique.end(), entry.driverId) != unique.end()) {
|
|
1170
1065
|
return false;
|
|
1171
1066
|
}
|
|
1172
1067
|
const auto iterator = registry().find(entry.driverId);
|
|
1173
1068
|
if (iterator == registry().end() || iterator->second.destroyed ||
|
|
1174
|
-
!
|
|
1069
|
+
!isFinitePresentation(entry.presentation)) {
|
|
1175
1070
|
return false;
|
|
1176
1071
|
}
|
|
1177
1072
|
if (iterator != registry().end() && iterator->second.groupId != 0 &&
|
|
@@ -1192,16 +1087,17 @@ bool setPresentationBatch(const std::vector<BatchEntry> &entries) {
|
|
|
1192
1087
|
}
|
|
1193
1088
|
|
|
1194
1089
|
const double now = nowSeconds();
|
|
1195
|
-
for (const BatchEntry &entry :
|
|
1090
|
+
for (const BatchEntry &entry : canonicalEntries) {
|
|
1196
1091
|
DriverState &state = registry().at(entry.driverId);
|
|
1197
1092
|
state.destroyed = false;
|
|
1198
1093
|
finishActive(entry.driverId, state, false);
|
|
1199
1094
|
state.latest = entry.presentation;
|
|
1200
1095
|
state.hasLatest = true;
|
|
1201
1096
|
state.ownership = Ownership::Interactive;
|
|
1202
|
-
recordVelocitySample(
|
|
1097
|
+
recordVelocitySample(
|
|
1098
|
+
state.samples, velocityChannels(entry.presentation), now);
|
|
1203
1099
|
}
|
|
1204
|
-
for (const BatchEntry &entry :
|
|
1100
|
+
for (const BatchEntry &entry : canonicalEntries) {
|
|
1205
1101
|
applyToViews(registry().at(entry.driverId), entry.presentation);
|
|
1206
1102
|
}
|
|
1207
1103
|
return true;
|
|
@@ -1217,24 +1113,23 @@ int32_t startGroupCommon(
|
|
|
1217
1113
|
SpringAnimation spring,
|
|
1218
1114
|
double durationMs,
|
|
1219
1115
|
int32_t reduceMotion,
|
|
1220
|
-
|
|
1116
|
+
int32_t completionTag,
|
|
1221
1117
|
double startedAtHintS) {
|
|
1222
|
-
if (!isOnMainThread() || controllerId == 0 || entries.empty()
|
|
1223
|
-
(suspensionPolicy != GroupSuspensionPolicy::Pause &&
|
|
1224
|
-
suspensionPolicy != GroupSuspensionPolicy::Finish)) {
|
|
1118
|
+
if (!isOnMainThread() || controllerId == 0 || entries.empty()) {
|
|
1225
1119
|
return 0;
|
|
1226
1120
|
}
|
|
1121
|
+
for (GroupMotionEntry &entry : entries) {
|
|
1122
|
+
if ((entry.hasFrom && !canonicalizePresentation(entry.from)) ||
|
|
1123
|
+
!canonicalizePresentation(entry.target)) {
|
|
1124
|
+
return 0;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1227
1127
|
if (kind == AnimationKind::Timing &&
|
|
1228
|
-
!
|
|
1128
|
+
!isValidTiming(timing)) {
|
|
1229
1129
|
return 0;
|
|
1230
1130
|
}
|
|
1231
1131
|
if (kind == AnimationKind::Spring &&
|
|
1232
|
-
!
|
|
1233
|
-
return 0;
|
|
1234
|
-
}
|
|
1235
|
-
if (kind == AnimationKind::Keyframes &&
|
|
1236
|
-
(!std::isfinite(durationMs) || durationMs < 0 ||
|
|
1237
|
-
!isValidReduceMotionCode(reduceMotion))) {
|
|
1132
|
+
(!isValidSpring(spring) || spring.inheritVelocity)) {
|
|
1238
1133
|
return 0;
|
|
1239
1134
|
}
|
|
1240
1135
|
const bool reduce = shouldReduceMotion(reduceMotion) ||
|
|
@@ -1264,36 +1159,16 @@ int32_t startGroupCommon(
|
|
|
1264
1159
|
const Presentation resolvedStart = entry.hasFrom
|
|
1265
1160
|
? entry.from
|
|
1266
1161
|
: visiblePresentation(entry.driverId, iterator->second);
|
|
1267
|
-
if (!
|
|
1268
|
-
!
|
|
1269
|
-
resolvedStart
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1272
|
-
if (kind == AnimationKind::Spring &&
|
|
1273
|
-
!protocolV2SpringScaleIsProvablyPositive(
|
|
1274
|
-
resolvedStart, entry.target, spring)) {
|
|
1275
|
-
return 0;
|
|
1276
|
-
}
|
|
1277
|
-
if (kind == AnimationKind::Keyframes) {
|
|
1278
|
-
if (!isValidProtocolV2Keyframes(
|
|
1279
|
-
entry.keyframes,
|
|
1280
|
-
resolvedStart,
|
|
1281
|
-
entry.target,
|
|
1282
|
-
entry.hasFrom)) {
|
|
1283
|
-
return 0;
|
|
1284
|
-
}
|
|
1285
|
-
} else if (!entry.keyframes.empty()) {
|
|
1162
|
+
if (!isFinitePresentation(resolvedStart) ||
|
|
1163
|
+
!isFinitePresentation(entry.target) ||
|
|
1164
|
+
!isAutonomousUniformCircular(resolvedStart) ||
|
|
1165
|
+
!isAutonomousUniformCircular(entry.target)) {
|
|
1286
1166
|
return 0;
|
|
1287
1167
|
}
|
|
1288
|
-
if (
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
return 0;
|
|
1293
|
-
}
|
|
1294
|
-
if (kind == AnimationKind::Keyframes &&
|
|
1295
|
-
!keyframePlanPreservesLinearHostNormalization(
|
|
1296
|
-
iterator->second, resolvedStart, entry.keyframes)) {
|
|
1168
|
+
if (kind == AnimationKind::Spring) {
|
|
1169
|
+
const double velocity = spring.initialVelocity;
|
|
1170
|
+
if (!springScaleStaysPositive(
|
|
1171
|
+
resolvedStart, entry.target, spring, velocity)) {
|
|
1297
1172
|
return 0;
|
|
1298
1173
|
}
|
|
1299
1174
|
}
|
|
@@ -1311,10 +1186,11 @@ int32_t startGroupCommon(
|
|
|
1311
1186
|
ActiveGroup group;
|
|
1312
1187
|
group.controllerId = controllerId;
|
|
1313
1188
|
group.id = allocateAnimationId();
|
|
1189
|
+
group.completionTag = completionTag;
|
|
1314
1190
|
group.kind = kind;
|
|
1315
|
-
group.suspensionPolicy = suspensionPolicy;
|
|
1316
1191
|
group.timing = timing;
|
|
1317
1192
|
group.spring = spring;
|
|
1193
|
+
group.scalarSpring = {0, spring.initialVelocity};
|
|
1318
1194
|
group.durationS = durationMs / 1000.0;
|
|
1319
1195
|
group.members.reserve(entries.size());
|
|
1320
1196
|
const double wallNow = nowSeconds();
|
|
@@ -1340,32 +1216,13 @@ int32_t startGroupCommon(
|
|
|
1340
1216
|
member.animation.timing = timing;
|
|
1341
1217
|
member.animation.spring = spring;
|
|
1342
1218
|
member.animation.durationS = group.durationS;
|
|
1343
|
-
if (kind == AnimationKind::Keyframes) {
|
|
1344
|
-
if (!entry.hasFrom) {
|
|
1345
|
-
entry.keyframes.front().presentation = resolvedStart;
|
|
1346
|
-
}
|
|
1347
|
-
member.animation.keyframes.reset(std::move(entry.keyframes));
|
|
1348
|
-
} else if (kind == AnimationKind::Spring) {
|
|
1349
|
-
const double velocity = spring.inheritVelocity
|
|
1350
|
-
? inheritedVelocity(state.samples, toChannels(entry.target), wallNow)
|
|
1351
|
-
: spring.initialVelocity;
|
|
1352
|
-
member.animation.spring.initialVelocity = velocity;
|
|
1353
|
-
member.animation.spring.inheritVelocity = false;
|
|
1354
|
-
const Channels startChannels = toChannels(resolvedStart);
|
|
1355
|
-
const Channels targetChannels = toChannels(entry.target);
|
|
1356
|
-
for (std::size_t index = 0; index < kChannelCount; index += 1) {
|
|
1357
|
-
member.animation.springPosition[index] = startChannels[index];
|
|
1358
|
-
member.animation.springVelocity[index] =
|
|
1359
|
-
velocity * (targetChannels[index] - startChannels[index]);
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
1219
|
group.members.push_back(std::move(member));
|
|
1363
1220
|
}
|
|
1364
1221
|
|
|
1365
1222
|
// Apply every explicit start after the whole participant set has passed
|
|
1366
1223
|
// preflight and acquired the new immutable group id. This remains one
|
|
1367
1224
|
// synchronous main-thread transaction even when the initial readiness
|
|
1368
|
-
// barrier is
|
|
1225
|
+
// barrier is pending; ready hosts must not keep displaying their old frame
|
|
1369
1226
|
// while snapshots already report `from`.
|
|
1370
1227
|
for (std::size_t entryIndex = 0; entryIndex < entries.size(); entryIndex += 1) {
|
|
1371
1228
|
if (!entries[entryIndex].hasFrom) continue;
|
|
@@ -1392,9 +1249,7 @@ int32_t startGroupCommon(
|
|
|
1392
1249
|
}
|
|
1393
1250
|
ActiveGroup &stored = groupRegistry().at(groupId);
|
|
1394
1251
|
if (stored.started) {
|
|
1395
|
-
|
|
1396
|
-
applyGroupCurrent(stored);
|
|
1397
|
-
scheduleFrame();
|
|
1252
|
+
startGroupFrameLoop(stored);
|
|
1398
1253
|
}
|
|
1399
1254
|
return groupId;
|
|
1400
1255
|
}
|
|
@@ -1405,7 +1260,7 @@ int32_t animateTimingGroup(
|
|
|
1405
1260
|
uint64_t controllerId,
|
|
1406
1261
|
std::vector<GroupMotionEntry> entries,
|
|
1407
1262
|
TimingAnimation animation,
|
|
1408
|
-
|
|
1263
|
+
int32_t completionTag,
|
|
1409
1264
|
double startedAtHintS) {
|
|
1410
1265
|
return startGroupCommon(
|
|
1411
1266
|
controllerId,
|
|
@@ -1415,7 +1270,7 @@ int32_t animateTimingGroup(
|
|
|
1415
1270
|
{},
|
|
1416
1271
|
animation.durationMs,
|
|
1417
1272
|
animation.reduceMotion,
|
|
1418
|
-
|
|
1273
|
+
completionTag,
|
|
1419
1274
|
startedAtHintS);
|
|
1420
1275
|
}
|
|
1421
1276
|
|
|
@@ -1423,7 +1278,7 @@ int32_t animateSpringGroup(
|
|
|
1423
1278
|
uint64_t controllerId,
|
|
1424
1279
|
std::vector<GroupMotionEntry> entries,
|
|
1425
1280
|
SpringAnimation animation,
|
|
1426
|
-
|
|
1281
|
+
int32_t completionTag,
|
|
1427
1282
|
double startedAtHintS) {
|
|
1428
1283
|
return startGroupCommon(
|
|
1429
1284
|
controllerId,
|
|
@@ -1433,26 +1288,7 @@ int32_t animateSpringGroup(
|
|
|
1433
1288
|
animation,
|
|
1434
1289
|
kSpringMaxDurationS * 1000.0,
|
|
1435
1290
|
animation.reduceMotion,
|
|
1436
|
-
|
|
1437
|
-
startedAtHintS);
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
int32_t animateKeyframesGroup(
|
|
1441
|
-
uint64_t controllerId,
|
|
1442
|
-
std::vector<GroupMotionEntry> entries,
|
|
1443
|
-
double durationMs,
|
|
1444
|
-
int32_t reduceMotion,
|
|
1445
|
-
GroupSuspensionPolicy suspensionPolicy,
|
|
1446
|
-
double startedAtHintS) {
|
|
1447
|
-
return startGroupCommon(
|
|
1448
|
-
controllerId,
|
|
1449
|
-
std::move(entries),
|
|
1450
|
-
AnimationKind::Keyframes,
|
|
1451
|
-
{},
|
|
1452
|
-
{},
|
|
1453
|
-
durationMs,
|
|
1454
|
-
reduceMotion,
|
|
1455
|
-
suspensionPolicy,
|
|
1291
|
+
completionTag,
|
|
1456
1292
|
startedAtHintS);
|
|
1457
1293
|
}
|
|
1458
1294
|
|
|
@@ -1472,6 +1308,7 @@ void setPresentation(
|
|
|
1472
1308
|
bool overridePendingAnimation,
|
|
1473
1309
|
bool recordVelocity) {
|
|
1474
1310
|
if (!isOnMainThread()) return;
|
|
1311
|
+
if (!canonicalizePresentation(presentation)) return;
|
|
1475
1312
|
SMOOTH_CLIP_TRACE("SmoothClip.setPresentation");
|
|
1476
1313
|
auto iterator = registry().find(driverId);
|
|
1477
1314
|
if (!takeOwnership) {
|
|
@@ -1493,7 +1330,7 @@ void setPresentation(
|
|
|
1493
1330
|
state.destroyed = false;
|
|
1494
1331
|
// Passive seeds and public setters must not displace newer pending intent.
|
|
1495
1332
|
// The fused animation.from write is different: it is the caller's newest,
|
|
1496
|
-
// authoritative start and explicitly
|
|
1333
|
+
// authoritative start and explicitly cancels the pending run.
|
|
1497
1334
|
if (state.animation.has_value() && !state.animation->started &&
|
|
1498
1335
|
!overridePendingAnimation) {
|
|
1499
1336
|
return;
|
|
@@ -1504,7 +1341,8 @@ void setPresentation(
|
|
|
1504
1341
|
state.hasLatest = true;
|
|
1505
1342
|
state.ownership = Ownership::Interactive;
|
|
1506
1343
|
if (recordVelocity) {
|
|
1507
|
-
recordVelocitySample(
|
|
1344
|
+
recordVelocitySample(
|
|
1345
|
+
state.samples, velocityChannels(presentation), nowSeconds());
|
|
1508
1346
|
} else if (state.samples.hasLatest) {
|
|
1509
1347
|
// Untracked movement invalidates the recorded velocity pair.
|
|
1510
1348
|
clearVelocitySamples(state.samples);
|
|
@@ -1526,6 +1364,7 @@ Presentation beginInteraction(uint64_t driverId) {
|
|
|
1526
1364
|
return state.latest;
|
|
1527
1365
|
}
|
|
1528
1366
|
const int32_t animationId = state.animation->id;
|
|
1367
|
+
const int32_t completionTag = state.animation->completionTag;
|
|
1529
1368
|
const Presentation current = state.animation->current;
|
|
1530
1369
|
clearActiveAnimation(driverId, state);
|
|
1531
1370
|
state.latest = current;
|
|
@@ -1534,7 +1373,7 @@ Presentation beginInteraction(uint64_t driverId) {
|
|
|
1534
1373
|
// The frozen frame came from native animation movement, not an interactive
|
|
1535
1374
|
// write. Do not let it become a later `initialVelocity: inherit` sample.
|
|
1536
1375
|
applyToViews(state, current);
|
|
1537
|
-
emitCompletion(driverId, animationId, false);
|
|
1376
|
+
emitCompletion(driverId, animationId, completionTag, false);
|
|
1538
1377
|
return current;
|
|
1539
1378
|
}
|
|
1540
1379
|
|
|
@@ -1543,29 +1382,22 @@ int32_t animateTiming(
|
|
|
1543
1382
|
AnimationStart start,
|
|
1544
1383
|
Presentation presentation,
|
|
1545
1384
|
TimingAnimation animation,
|
|
1546
|
-
|
|
1547
|
-
const bool
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
std::isfinite(animation.controlPoint1X) &&
|
|
1553
|
-
std::isfinite(animation.controlPoint1Y) &&
|
|
1554
|
-
std::isfinite(animation.controlPoint2X) &&
|
|
1555
|
-
std::isfinite(animation.controlPoint2Y) &&
|
|
1556
|
-
isValidReduceMotionCode(animation.reduceMotion);
|
|
1557
|
-
if (!strictV2 && validAnimation) {
|
|
1558
|
-
animation.durationMs = std::max(0.0, animation.durationMs);
|
|
1385
|
+
int32_t completionTag) {
|
|
1386
|
+
const bool validAnimation = isValidTiming(animation);
|
|
1387
|
+
if (!canonicalizePresentation(presentation) ||
|
|
1388
|
+
(start.hasInteractiveStart &&
|
|
1389
|
+
!canonicalizePresentation(start.interactiveStart))) {
|
|
1390
|
+
return 0;
|
|
1559
1391
|
}
|
|
1560
1392
|
if (!isOnMainThread() || driverId == 0 ||
|
|
1561
|
-
!
|
|
1393
|
+
!isFinitePresentation(presentation) || !validAnimation) {
|
|
1562
1394
|
return 0;
|
|
1563
1395
|
}
|
|
1564
1396
|
auto iterator = registry().find(driverId);
|
|
1565
1397
|
Presentation preflightStart;
|
|
1566
1398
|
if (iterator == registry().end()) {
|
|
1567
1399
|
if (!start.hasInteractiveStart ||
|
|
1568
|
-
!
|
|
1400
|
+
!isFinitePresentation(start.interactiveStart)) {
|
|
1569
1401
|
return 0;
|
|
1570
1402
|
}
|
|
1571
1403
|
preflightStart = start.interactiveStart;
|
|
@@ -1575,17 +1407,12 @@ int32_t animateTiming(
|
|
|
1575
1407
|
? start.interactiveStart
|
|
1576
1408
|
: visiblePresentation(driverId, iterator->second);
|
|
1577
1409
|
}
|
|
1578
|
-
if (!
|
|
1410
|
+
if (!isFinitePresentation(preflightStart) ||
|
|
1579
1411
|
preflightStart.clip.curve != presentation.clip.curve) {
|
|
1580
1412
|
return 0;
|
|
1581
1413
|
}
|
|
1582
1414
|
const bool reduce = shouldReduceMotion(animation.reduceMotion) ||
|
|
1583
1415
|
animation.durationMs <= 0;
|
|
1584
|
-
if (strictV2 && !reduce && iterator != registry().end() &&
|
|
1585
|
-
!timingPlanPreservesLinearHostNormalization(
|
|
1586
|
-
iterator->second, preflightStart, presentation, animation)) {
|
|
1587
|
-
return 0;
|
|
1588
|
-
}
|
|
1589
1416
|
if (iterator == registry().end()) {
|
|
1590
1417
|
iterator = registry().try_emplace(driverId).first;
|
|
1591
1418
|
iterator->second.latest = start.interactiveStart;
|
|
@@ -1599,11 +1426,12 @@ int32_t animateTiming(
|
|
|
1599
1426
|
prepareAnimation(driverId, state, start, presentation);
|
|
1600
1427
|
if (reduce) {
|
|
1601
1428
|
applyPresentation(state, presentation);
|
|
1602
|
-
emitCompletion(driverId, animationId, true);
|
|
1429
|
+
emitCompletion(driverId, animationId, completionTag, true);
|
|
1603
1430
|
return animationId;
|
|
1604
1431
|
}
|
|
1605
1432
|
ActiveAnimation active;
|
|
1606
1433
|
active.id = animationId;
|
|
1434
|
+
active.completionTag = completionTag;
|
|
1607
1435
|
active.kind = AnimationKind::Timing;
|
|
1608
1436
|
active.timing = animation;
|
|
1609
1437
|
active.start = resolvedStart;
|
|
@@ -1617,30 +1445,22 @@ int32_t animateSpring(
|
|
|
1617
1445
|
AnimationStart start,
|
|
1618
1446
|
Presentation presentation,
|
|
1619
1447
|
SpringAnimation animation,
|
|
1620
|
-
|
|
1621
|
-
const bool
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
std::isfinite(animation.stiffness) &&
|
|
1627
|
-
std::isfinite(animation.damping) &&
|
|
1628
|
-
std::isfinite(animation.initialVelocity) &&
|
|
1629
|
-
isValidReduceMotionCode(animation.reduceMotion);
|
|
1630
|
-
if (!strictV2 && validAnimation) {
|
|
1631
|
-
animation.mass = std::max(0.0001, animation.mass);
|
|
1632
|
-
animation.stiffness = std::max(0.0001, animation.stiffness);
|
|
1633
|
-
animation.damping = std::max(0.0, animation.damping);
|
|
1448
|
+
int32_t completionTag) {
|
|
1449
|
+
const bool validAnimation = isValidSpring(animation);
|
|
1450
|
+
if (!canonicalizePresentation(presentation) ||
|
|
1451
|
+
(start.hasInteractiveStart &&
|
|
1452
|
+
!canonicalizePresentation(start.interactiveStart))) {
|
|
1453
|
+
return 0;
|
|
1634
1454
|
}
|
|
1635
1455
|
if (!isOnMainThread() || driverId == 0 ||
|
|
1636
|
-
!
|
|
1456
|
+
!isFinitePresentation(presentation) || !validAnimation) {
|
|
1637
1457
|
return 0;
|
|
1638
1458
|
}
|
|
1639
1459
|
auto iterator = registry().find(driverId);
|
|
1640
1460
|
Presentation preflightStart;
|
|
1641
1461
|
if (iterator == registry().end()) {
|
|
1642
1462
|
if (!start.hasInteractiveStart ||
|
|
1643
|
-
!
|
|
1463
|
+
!isFinitePresentation(start.interactiveStart)) {
|
|
1644
1464
|
return 0;
|
|
1645
1465
|
}
|
|
1646
1466
|
preflightStart = start.interactiveStart;
|
|
@@ -1650,10 +1470,17 @@ int32_t animateSpring(
|
|
|
1650
1470
|
? start.interactiveStart
|
|
1651
1471
|
: visiblePresentation(driverId, iterator->second);
|
|
1652
1472
|
}
|
|
1653
|
-
|
|
1473
|
+
const double velocity = animation.inheritVelocity &&
|
|
1474
|
+
iterator != registry().end()
|
|
1475
|
+
? inheritedVelocity(
|
|
1476
|
+
iterator->second.samples,
|
|
1477
|
+
velocityChannels(presentation),
|
|
1478
|
+
nowSeconds())
|
|
1479
|
+
: animation.initialVelocity;
|
|
1480
|
+
if (!isFinitePresentation(preflightStart) ||
|
|
1654
1481
|
preflightStart.clip.curve != presentation.clip.curve ||
|
|
1655
|
-
|
|
1656
|
-
preflightStart, presentation, animation))
|
|
1482
|
+
!springScaleStaysPositive(
|
|
1483
|
+
preflightStart, presentation, animation, velocity)) {
|
|
1657
1484
|
return 0;
|
|
1658
1485
|
}
|
|
1659
1486
|
if (iterator == registry().end()) {
|
|
@@ -1668,18 +1495,16 @@ int32_t animateSpring(
|
|
|
1668
1495
|
// Scalar velocity along the current-to-target trajectory; each channel is
|
|
1669
1496
|
// seeded with velocity·displacement to match the iOS CASpringAnimation
|
|
1670
1497
|
// per-keypath behavior.
|
|
1671
|
-
const double velocity = animation.inheritVelocity
|
|
1672
|
-
? inheritedVelocity(state.samples, toChannels(presentation), nowSeconds())
|
|
1673
|
-
: animation.initialVelocity;
|
|
1674
1498
|
const Presentation resolvedStart =
|
|
1675
1499
|
prepareAnimation(driverId, state, start, presentation);
|
|
1676
1500
|
if (shouldReduceMotion(animation.reduceMotion)) {
|
|
1677
1501
|
applyPresentation(state, presentation);
|
|
1678
|
-
emitCompletion(driverId, animationId, true);
|
|
1502
|
+
emitCompletion(driverId, animationId, completionTag, true);
|
|
1679
1503
|
return animationId;
|
|
1680
1504
|
}
|
|
1681
1505
|
ActiveAnimation active;
|
|
1682
1506
|
active.id = animationId;
|
|
1507
|
+
active.completionTag = completionTag;
|
|
1683
1508
|
active.kind = AnimationKind::Spring;
|
|
1684
1509
|
active.spring = animation;
|
|
1685
1510
|
active.spring.initialVelocity = velocity;
|
|
@@ -1687,106 +1512,31 @@ int32_t animateSpring(
|
|
|
1687
1512
|
active.start = resolvedStart;
|
|
1688
1513
|
active.target = presentation;
|
|
1689
1514
|
active.durationS = kSpringMaxDurationS;
|
|
1690
|
-
const auto
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1515
|
+
const auto normalizedEndpoints = normalizeShadowEndpoints(
|
|
1516
|
+
resolvedStart, presentation);
|
|
1517
|
+
const auto startChannels = toChannels(normalizedEndpoints.first);
|
|
1518
|
+
const auto targetChannels = toChannels(normalizedEndpoints.second);
|
|
1519
|
+
active.springChannelCount =
|
|
1520
|
+
(resolvedStart.shadow.enabled && resolvedStart.shadow.alpha > 0) ||
|
|
1521
|
+
(presentation.shadow.enabled && presentation.shadow.alpha > 0)
|
|
1522
|
+
? kChannelCount
|
|
1523
|
+
: kBaseChannelCount;
|
|
1524
|
+
active.springPosition = startChannels;
|
|
1525
|
+
for (std::size_t index = 0;
|
|
1526
|
+
index < active.springChannelCount;
|
|
1527
|
+
index += 1) {
|
|
1694
1528
|
active.springVelocity[index] =
|
|
1695
1529
|
velocity * (targetChannels[index] - startChannels[index]);
|
|
1696
1530
|
}
|
|
1697
1531
|
return startAnimation(driverId, state, std::move(active), start.startedAtHintS);
|
|
1698
1532
|
}
|
|
1699
1533
|
|
|
1700
|
-
int32_t animateKeyframes(
|
|
1701
|
-
uint64_t driverId,
|
|
1702
|
-
AnimationStart start,
|
|
1703
|
-
Presentation presentation,
|
|
1704
|
-
double durationMs,
|
|
1705
|
-
std::vector<Keyframe> keyframes,
|
|
1706
|
-
int32_t reduceMotion,
|
|
1707
|
-
AnimationValidationMode validationMode) {
|
|
1708
|
-
const bool strictV2 =
|
|
1709
|
-
validationMode == AnimationValidationMode::ProtocolV2;
|
|
1710
|
-
const bool finiteDuration = std::isfinite(durationMs);
|
|
1711
|
-
if (!strictV2 && finiteDuration) durationMs = std::max(0.0, durationMs);
|
|
1712
|
-
if (!isOnMainThread() || driverId == 0 ||
|
|
1713
|
-
!isFiniteProtocolV2Presentation(presentation) ||
|
|
1714
|
-
!finiteDuration || (strictV2 && durationMs < 0) ||
|
|
1715
|
-
!isValidReduceMotionCode(reduceMotion)) {
|
|
1716
|
-
return 0;
|
|
1717
|
-
}
|
|
1718
|
-
auto iterator = registry().find(driverId);
|
|
1719
|
-
Presentation preflightStart;
|
|
1720
|
-
if (iterator == registry().end()) {
|
|
1721
|
-
if (!start.hasInteractiveStart ||
|
|
1722
|
-
!isFiniteProtocolV2Presentation(start.interactiveStart)) {
|
|
1723
|
-
return 0;
|
|
1724
|
-
}
|
|
1725
|
-
preflightStart = start.interactiveStart;
|
|
1726
|
-
} else {
|
|
1727
|
-
if (iterator->second.destroyed && !start.hasInteractiveStart) return 0;
|
|
1728
|
-
preflightStart = start.hasInteractiveStart
|
|
1729
|
-
? start.interactiveStart
|
|
1730
|
-
: visiblePresentation(driverId, iterator->second);
|
|
1731
|
-
}
|
|
1732
|
-
bool validKeyframes = keyframes.size() >= 2 &&
|
|
1733
|
-
keyframes.front().offset == 0 && keyframes.back().offset == 1;
|
|
1734
|
-
double previousOffset = -1;
|
|
1735
|
-
for (const Keyframe &keyframe : keyframes) {
|
|
1736
|
-
validKeyframes = validKeyframes && std::isfinite(keyframe.offset) &&
|
|
1737
|
-
keyframe.offset > previousOffset && keyframe.offset >= 0 &&
|
|
1738
|
-
keyframe.offset <= 1 &&
|
|
1739
|
-
isFiniteProtocolV2Presentation(keyframe.presentation);
|
|
1740
|
-
previousOffset = keyframe.offset;
|
|
1741
|
-
}
|
|
1742
|
-
if (strictV2) {
|
|
1743
|
-
validKeyframes = isValidProtocolV2Keyframes(
|
|
1744
|
-
keyframes, preflightStart, presentation, start.hasInteractiveStart);
|
|
1745
|
-
}
|
|
1746
|
-
if (!isFiniteProtocolV2Presentation(preflightStart) || !validKeyframes) {
|
|
1747
|
-
return 0;
|
|
1748
|
-
}
|
|
1749
|
-
const bool reduce = shouldReduceMotion(reduceMotion) || durationMs <= 0;
|
|
1750
|
-
if (strictV2 && !reduce && iterator != registry().end() &&
|
|
1751
|
-
!keyframePlanPreservesLinearHostNormalization(
|
|
1752
|
-
iterator->second, preflightStart, keyframes)) {
|
|
1753
|
-
return 0;
|
|
1754
|
-
}
|
|
1755
|
-
if (iterator == registry().end()) {
|
|
1756
|
-
iterator = registry().try_emplace(driverId).first;
|
|
1757
|
-
iterator->second.latest = start.interactiveStart;
|
|
1758
|
-
iterator->second.hasLatest = true;
|
|
1759
|
-
}
|
|
1760
|
-
auto &state = iterator->second;
|
|
1761
|
-
if (state.groupId != 0) finishGroupImpl(state.groupId, false, false);
|
|
1762
|
-
state.destroyed = false;
|
|
1763
|
-
const int32_t animationId = allocateAnimationId();
|
|
1764
|
-
const Presentation resolvedStart =
|
|
1765
|
-
prepareAnimation(driverId, state, start, presentation);
|
|
1766
|
-
if (reduce) {
|
|
1767
|
-
applyPresentation(state, presentation);
|
|
1768
|
-
emitCompletion(driverId, animationId, true);
|
|
1769
|
-
return animationId;
|
|
1770
|
-
}
|
|
1771
|
-
if (strictV2 && !start.hasInteractiveStart) {
|
|
1772
|
-
keyframes.front().presentation = resolvedStart;
|
|
1773
|
-
}
|
|
1774
|
-
ActiveAnimation active;
|
|
1775
|
-
active.id = animationId;
|
|
1776
|
-
active.kind = AnimationKind::Keyframes;
|
|
1777
|
-
active.keyframes.reset(std::move(keyframes));
|
|
1778
|
-
active.start = resolvedStart;
|
|
1779
|
-
active.target = presentation;
|
|
1780
|
-
active.durationS = durationMs / 1000.0;
|
|
1781
|
-
return startAnimation(driverId, state, std::move(active), start.startedAtHintS);
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
1534
|
int32_t rejectAnimation(uint64_t driverId) {
|
|
1785
1535
|
if (!isOnMainThread()) return 0;
|
|
1786
1536
|
auto iterator = registry().find(driverId);
|
|
1787
1537
|
if (iterator == registry().end()) return 0;
|
|
1788
1538
|
const int32_t animationId = allocateAnimationId();
|
|
1789
|
-
emitCompletion(driverId, animationId, false);
|
|
1539
|
+
emitCompletion(driverId, animationId, 0, false);
|
|
1790
1540
|
return animationId;
|
|
1791
1541
|
}
|
|
1792
1542
|
|
|
@@ -1820,12 +1570,13 @@ CancelResult cancelAnimation(
|
|
|
1820
1570
|
const Presentation result =
|
|
1821
1571
|
useTarget ? state.animation->target : state.animation->current;
|
|
1822
1572
|
const int32_t activeId = state.animation->id;
|
|
1573
|
+
const int32_t completionTag = state.animation->completionTag;
|
|
1823
1574
|
clearActiveAnimation(driverId, state);
|
|
1824
1575
|
state.latest = result;
|
|
1825
1576
|
state.hasLatest = true;
|
|
1826
1577
|
state.ownership = Ownership::Interactive;
|
|
1827
1578
|
applyToViews(state, result);
|
|
1828
|
-
emitCompletion(driverId, activeId, false);
|
|
1579
|
+
emitCompletion(driverId, activeId, completionTag, false);
|
|
1829
1580
|
return {true, result};
|
|
1830
1581
|
}
|
|
1831
1582
|
|
|
@@ -1856,23 +1607,6 @@ void destroyDriver(uint64_t driverId) {
|
|
|
1856
1607
|
// --- Android view lifecycle ----------------------------------------------
|
|
1857
1608
|
|
|
1858
1609
|
void JSmoothClipView::applyClip(const Presentation &presentation) const {
|
|
1859
|
-
static const auto method =
|
|
1860
|
-
javaClassStatic()
|
|
1861
|
-
->getMethod<void(
|
|
1862
|
-
jdouble, jdouble, jdouble, jdouble, jdouble, jdouble, jdouble)>(
|
|
1863
|
-
"setClipPresentationDip");
|
|
1864
|
-
method(
|
|
1865
|
-
self(),
|
|
1866
|
-
presentation.clip.x,
|
|
1867
|
-
presentation.clip.y,
|
|
1868
|
-
presentation.clip.width,
|
|
1869
|
-
presentation.clip.height,
|
|
1870
|
-
presentation.clip.radius,
|
|
1871
|
-
presentation.contentTranslateX,
|
|
1872
|
-
presentation.contentTranslateY);
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
void JSmoothClipView::applyClipV2(const Presentation &presentation) const {
|
|
1876
1610
|
static const auto method =
|
|
1877
1611
|
javaClassStatic()
|
|
1878
1612
|
->getMethod<void(
|
|
@@ -1887,7 +1621,16 @@ void JSmoothClipView::applyClipV2(const Presentation &presentation) const {
|
|
|
1887
1621
|
jint,
|
|
1888
1622
|
jdouble,
|
|
1889
1623
|
jdouble,
|
|
1890
|
-
jdouble
|
|
1624
|
+
jdouble,
|
|
1625
|
+
jboolean,
|
|
1626
|
+
jdouble,
|
|
1627
|
+
jdouble,
|
|
1628
|
+
jdouble,
|
|
1629
|
+
jdouble,
|
|
1630
|
+
jdouble,
|
|
1631
|
+
jdouble,
|
|
1632
|
+
jdouble,
|
|
1633
|
+
jdouble)>("setClipPresentationDip");
|
|
1891
1634
|
method(
|
|
1892
1635
|
self(),
|
|
1893
1636
|
presentation.clip.x,
|
|
@@ -1905,34 +1648,24 @@ void JSmoothClipView::applyClipV2(const Presentation &presentation) const {
|
|
|
1905
1648
|
static_cast<jint>(presentation.clip.curve),
|
|
1906
1649
|
presentation.contentTranslateX,
|
|
1907
1650
|
presentation.contentTranslateY,
|
|
1908
|
-
presentation.contentScale
|
|
1651
|
+
presentation.contentScale,
|
|
1652
|
+
presentation.shadow.enabled,
|
|
1653
|
+
presentation.shadow.red,
|
|
1654
|
+
presentation.shadow.green,
|
|
1655
|
+
presentation.shadow.blue,
|
|
1656
|
+
presentation.shadow.alpha,
|
|
1657
|
+
presentation.shadow.offsetX,
|
|
1658
|
+
presentation.shadow.offsetY,
|
|
1659
|
+
presentation.shadow.blurRadius,
|
|
1660
|
+
presentation.shadow.spreadDistance);
|
|
1909
1661
|
}
|
|
1910
1662
|
|
|
1911
1663
|
void JSmoothClipView::applyClipPx(
|
|
1912
|
-
const
|
|
1913
|
-
double contentTranslateXPx,
|
|
1914
|
-
double contentTranslateYPx) const {
|
|
1915
|
-
static const auto method =
|
|
1916
|
-
javaClassStatic()
|
|
1917
|
-
->getMethod<void(
|
|
1918
|
-
jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jfloat)>(
|
|
1919
|
-
"setClipPresentationPx");
|
|
1920
|
-
method(
|
|
1921
|
-
self(),
|
|
1922
|
-
static_cast<jfloat>(clip.left),
|
|
1923
|
-
static_cast<jfloat>(clip.top),
|
|
1924
|
-
static_cast<jfloat>(clip.right),
|
|
1925
|
-
static_cast<jfloat>(clip.bottom),
|
|
1926
|
-
static_cast<jfloat>(clip.radius),
|
|
1927
|
-
static_cast<jfloat>(contentTranslateXPx),
|
|
1928
|
-
static_cast<jfloat>(contentTranslateYPx));
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
|
-
void JSmoothClipView::applyClipV2Px(
|
|
1932
|
-
const NormalizedClip &clip,
|
|
1664
|
+
const CanonicalClip &clip,
|
|
1933
1665
|
double contentTranslateXPx,
|
|
1934
1666
|
double contentTranslateYPx,
|
|
1935
|
-
double contentScale
|
|
1667
|
+
double contentScale,
|
|
1668
|
+
const Shadow &shadowPx) const {
|
|
1936
1669
|
static const auto method =
|
|
1937
1670
|
javaClassStatic()
|
|
1938
1671
|
->getMethod<void(
|
|
@@ -1947,7 +1680,16 @@ void JSmoothClipView::applyClipV2Px(
|
|
|
1947
1680
|
jint,
|
|
1948
1681
|
jfloat,
|
|
1949
1682
|
jfloat,
|
|
1950
|
-
jfloat
|
|
1683
|
+
jfloat,
|
|
1684
|
+
jboolean,
|
|
1685
|
+
jfloat,
|
|
1686
|
+
jfloat,
|
|
1687
|
+
jfloat,
|
|
1688
|
+
jfloat,
|
|
1689
|
+
jfloat,
|
|
1690
|
+
jfloat,
|
|
1691
|
+
jfloat,
|
|
1692
|
+
jfloat)>("setClipPresentationPx");
|
|
1951
1693
|
method(
|
|
1952
1694
|
self(),
|
|
1953
1695
|
static_cast<jfloat>(clip.left),
|
|
@@ -1961,7 +1703,22 @@ void JSmoothClipView::applyClipV2Px(
|
|
|
1961
1703
|
static_cast<jint>(clip.curve),
|
|
1962
1704
|
static_cast<jfloat>(contentTranslateXPx),
|
|
1963
1705
|
static_cast<jfloat>(contentTranslateYPx),
|
|
1964
|
-
static_cast<jfloat>(contentScale)
|
|
1706
|
+
static_cast<jfloat>(contentScale),
|
|
1707
|
+
shadowPx.enabled,
|
|
1708
|
+
static_cast<jfloat>(shadowPx.red),
|
|
1709
|
+
static_cast<jfloat>(shadowPx.green),
|
|
1710
|
+
static_cast<jfloat>(shadowPx.blue),
|
|
1711
|
+
static_cast<jfloat>(shadowPx.alpha),
|
|
1712
|
+
static_cast<jfloat>(shadowPx.offsetX),
|
|
1713
|
+
static_cast<jfloat>(shadowPx.offsetY),
|
|
1714
|
+
static_cast<jfloat>(shadowPx.blurRadius),
|
|
1715
|
+
static_cast<jfloat>(shadowPx.spreadDistance));
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
void JSmoothClipView::setAutonomousMotion(bool active) const {
|
|
1719
|
+
static const auto method =
|
|
1720
|
+
javaClassStatic()->getMethod<void(jboolean)>("setAutonomousMotion");
|
|
1721
|
+
method(self(), active);
|
|
1965
1722
|
}
|
|
1966
1723
|
|
|
1967
1724
|
void registerViewAndroid(
|
|
@@ -1972,6 +1729,7 @@ void registerViewAndroid(
|
|
|
1972
1729
|
double hostWidthPx,
|
|
1973
1730
|
double hostHeightPx,
|
|
1974
1731
|
bool lifecycleVisible) {
|
|
1732
|
+
if (!canonicalizePresentation(initialPresentation)) return;
|
|
1975
1733
|
auto &state = registry()[driverId];
|
|
1976
1734
|
state.destroyed = false;
|
|
1977
1735
|
if (!state.hasLatest) {
|
|
@@ -1992,6 +1750,10 @@ void registerViewAndroid(
|
|
|
1992
1750
|
return;
|
|
1993
1751
|
}
|
|
1994
1752
|
}
|
|
1753
|
+
// The public controller contract permits one simultaneous host. The React
|
|
1754
|
+
// wrapper reports this as a development error; native also refuses a second
|
|
1755
|
+
// host so release behavior cannot silently fan one controller out.
|
|
1756
|
+
if (!state.views.empty()) return;
|
|
1995
1757
|
ViewEntry entry{
|
|
1996
1758
|
facebook::jni::make_global(view),
|
|
1997
1759
|
density,
|
|
@@ -1999,8 +1761,7 @@ void registerViewAndroid(
|
|
|
1999
1761
|
hostHeightPx,
|
|
2000
1762
|
lifecycleVisible,
|
|
2001
1763
|
ViewParticipation::Deferred};
|
|
2002
|
-
// `visible`
|
|
2003
|
-
// registering view — the correct first frame for a latched animation.
|
|
1764
|
+
// `visible` already contains the start of a pre-ready animation.
|
|
2004
1765
|
deliverToView(entry, visible);
|
|
2005
1766
|
state.views.push_back(std::move(entry));
|
|
2006
1767
|
reconcileViewDisplayability(driverId, state, state.views.back());
|
|
@@ -2057,12 +1818,9 @@ void unregisterViewAndroid(
|
|
|
2057
1818
|
if (iterator == registry().end()) return;
|
|
2058
1819
|
auto &state = iterator->second;
|
|
2059
1820
|
bool removed = false;
|
|
2060
|
-
bool removedParticipant = false;
|
|
2061
1821
|
JNIEnv *env = facebook::jni::Environment::current();
|
|
2062
1822
|
for (auto view_it = state.views.begin(); view_it != state.views.end();) {
|
|
2063
1823
|
if (env->IsSameObject(view_it->view.get(), view.get())) {
|
|
2064
|
-
removedParticipant = removedParticipant ||
|
|
2065
|
-
view_it->participation != ViewParticipation::Deferred;
|
|
2066
1824
|
view_it = state.views.erase(view_it);
|
|
2067
1825
|
removed = true;
|
|
2068
1826
|
} else {
|
|
@@ -2070,10 +1828,6 @@ void unregisterViewAndroid(
|
|
|
2070
1828
|
}
|
|
2071
1829
|
}
|
|
2072
1830
|
if (removed && state.animation.has_value()) {
|
|
2073
|
-
// Only a host that actually joined (or is unresolved after suspension)
|
|
2074
|
-
// affects completion. A detached/unsized registration that stayed deferred
|
|
2075
|
-
// never installed or rendered this animation.
|
|
2076
|
-
if (removedParticipant) state.animation->finished = false;
|
|
2077
1831
|
if (!isOnMainThread()) {
|
|
2078
1832
|
// Blast-radius reduction, NOT a synchronization fix — be clear about
|
|
2079
1833
|
// which. Both call sites (SmoothClipViewManager.onAfterUpdateTransaction
|
|
@@ -2087,7 +1841,7 @@ void unregisterViewAndroid(
|
|
|
2087
1841
|
// for the next main-thread event (attach, host geometry, destroy) to
|
|
2088
1842
|
// resolve.
|
|
2089
1843
|
} else {
|
|
2090
|
-
|
|
1844
|
+
finishIfHostUnavailable(driverId, state);
|
|
2091
1845
|
}
|
|
2092
1846
|
}
|
|
2093
1847
|
if (removed && state.groupId != 0 && isOnMainThread()) {
|