react-native-smooth-clip-view 0.2.8 → 0.3.0
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 +86 -21
- package/android/CMakeLists.txt +7 -0
- package/android/build.gradle +14 -2
- package/android/proguard-rules.pro +3 -3
- package/android/src/main/cpp/SmoothClipAndroid.h +10 -0
- package/android/src/main/cpp/SmoothClipBindings.cpp +896 -6
- package/android/src/main/cpp/SmoothClipRegistry.cpp +1067 -46
- package/android/src/main/cpp/SmoothClipTrace.h +25 -0
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +265 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +21 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +283 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +385 -24
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +138 -24
- package/cpp/SmoothClipAnimationCurve.h +151 -122
- package/cpp/SmoothClipRegistry.h +102 -4
- package/cpp/SmoothClipSharedGeometry.h +146 -7
- package/cpp/SmoothClipVelocityTracker.h +13 -9
- package/ios/SmoothClipGeometry.h +184 -6
- package/ios/SmoothClipRegistry.mm +1033 -25
- package/ios/SmoothClipRegistrySnapshot.h +13 -0
- package/ios/SmoothClipTurboModule.cpp +857 -2
- package/ios/SmoothClipTurboModule.h +310 -0
- package/ios/SmoothClipView.mm +714 -183
- package/ios/SmoothClipViewRegistry.h +1 -0
- package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/lib/module/NativeSmoothClipModule.js.map +1 -1
- package/lib/module/SmoothClipView.ios.js +22 -6
- package/lib/module/SmoothClipView.ios.js.map +1 -1
- package/lib/module/SmoothClipView.js +1 -46
- package/lib/module/SmoothClipView.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +27 -1
- package/lib/module/capabilities.js +4 -0
- package/lib/module/capabilities.js.map +1 -0
- package/lib/module/capabilities.native.js +36 -0
- package/lib/module/capabilities.native.js.map +1 -0
- package/lib/module/capabilityTypes.js +11 -0
- package/lib/module/capabilityTypes.js.map +1 -0
- package/lib/module/driverState.js +0 -28
- package/lib/module/driverState.js.map +1 -1
- package/lib/module/drivers.js +1 -281
- package/lib/module/drivers.js.map +1 -1
- package/lib/module/drivers.native.js +340 -75
- package/lib/module/drivers.native.js.map +1 -1
- package/lib/module/geometry.js +147 -17
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/groupDriverTypes.js +4 -0
- package/lib/module/groupDriverTypes.js.map +1 -0
- package/lib/module/groupDrivers.android.js +4 -0
- package/lib/module/groupDrivers.android.js.map +1 -0
- package/lib/module/groupDrivers.ios.js +4 -0
- package/lib/module/groupDrivers.ios.js.map +1 -0
- package/lib/module/groupDrivers.js +4 -0
- package/lib/module/groupDrivers.js.map +1 -0
- package/lib/module/groupDrivers.native.js +799 -0
- package/lib/module/groupDrivers.native.js.map +1 -0
- package/lib/module/index.js +3 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/presentationProtocol.js +15 -0
- package/lib/module/presentationProtocol.js.map +1 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +25 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts +2 -10
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts +133 -2
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +8 -0
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/capabilities.d.ts +3 -0
- package/lib/typescript/src/capabilities.d.ts.map +1 -0
- package/lib/typescript/src/capabilities.native.d.ts +3 -0
- package/lib/typescript/src/capabilities.native.d.ts.map +1 -0
- package/lib/typescript/src/capabilityTypes.d.ts +10 -0
- package/lib/typescript/src/capabilityTypes.d.ts.map +1 -0
- package/lib/typescript/src/driverState.d.ts +1 -6
- package/lib/typescript/src/driverState.d.ts.map +1 -1
- package/lib/typescript/src/driverTypes.d.ts +39 -11
- package/lib/typescript/src/driverTypes.d.ts.map +1 -1
- package/lib/typescript/src/drivers.d.ts +2 -4
- package/lib/typescript/src/drivers.d.ts.map +1 -1
- package/lib/typescript/src/drivers.native.d.ts.map +1 -1
- package/lib/typescript/src/geometry.d.ts +36 -2
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/groupDriverTypes.d.ts +66 -0
- package/lib/typescript/src/groupDriverTypes.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.android.d.ts +2 -0
- package/lib/typescript/src/groupDrivers.android.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.d.ts +2 -0
- package/lib/typescript/src/groupDrivers.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.ios.d.ts +2 -0
- package/lib/typescript/src/groupDrivers.ios.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.native.d.ts +4 -0
- package/lib/typescript/src/groupDrivers.native.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/presentationProtocol.d.ts +4 -0
- package/lib/typescript/src/presentationProtocol.d.ts.map +1 -0
- package/package.json +10 -11
- package/src/NativeSmoothClipModule.ts +275 -0
- package/src/SmoothClipView.ios.tsx +30 -11
- package/src/SmoothClipView.tsx +2 -76
- package/src/SmoothClipViewNativeComponent.ts +27 -1
- package/src/capabilities.native.ts +69 -0
- package/src/capabilities.ts +2 -0
- package/src/capabilityTypes.ts +17 -0
- package/src/driverState.ts +1 -41
- package/src/driverTypes.ts +56 -11
- package/src/drivers.native.ts +921 -151
- package/src/drivers.ts +2 -423
- package/src/geometry.ts +231 -27
- package/src/groupDriverTypes.ts +117 -0
- package/src/groupDrivers.android.ts +1 -0
- package/src/groupDrivers.ios.ts +1 -0
- package/src/groupDrivers.native.ts +1189 -0
- package/src/groupDrivers.ts +1 -0
- package/src/index.ts +30 -2
- package/src/presentationProtocol.ts +28 -0
- package/lib/module/SmoothClipView.web.js +0 -54
- package/lib/module/SmoothClipView.web.js.map +0 -1
- package/lib/module/index.web.js +0 -8
- package/lib/module/index.web.js.map +0 -1
- package/lib/module/webClipPath.js +0 -25
- package/lib/module/webClipPath.js.map +0 -1
- package/lib/typescript/src/SmoothClipView.web.d.ts +0 -10
- package/lib/typescript/src/SmoothClipView.web.d.ts.map +0 -1
- package/lib/typescript/src/index.web.d.ts +0 -9
- package/lib/typescript/src/index.web.d.ts.map +0 -1
- package/lib/typescript/src/webClipPath.d.ts +0 -7
- package/lib/typescript/src/webClipPath.d.ts.map +0 -1
- package/src/SmoothClipView.web.tsx +0 -66
- package/src/index.web.ts +0 -23
- package/src/webClipPath.ts +0 -30
|
@@ -21,8 +21,15 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
21
21
|
var width: Double = 0.0,
|
|
22
22
|
var height: Double = 0.0,
|
|
23
23
|
var radius: Double = 0.0,
|
|
24
|
+
var presentationVersion: Int = 1,
|
|
25
|
+
var topLeftRadius: Double = 0.0,
|
|
26
|
+
var topRightRadius: Double = 0.0,
|
|
27
|
+
var bottomRightRadius: Double = 0.0,
|
|
28
|
+
var bottomLeftRadius: Double = 0.0,
|
|
29
|
+
var curveCode: Int = CLIP_CURVE_CIRCULAR,
|
|
24
30
|
var contentTranslateX: Double = 0.0,
|
|
25
31
|
var contentTranslateY: Double = 0.0,
|
|
32
|
+
var contentScale: Double = 1.0,
|
|
26
33
|
var driverId: Double = 0.0,
|
|
27
34
|
// Whether any prop setter ran in the current update transaction.
|
|
28
35
|
var dirty: Boolean = false,
|
|
@@ -95,6 +102,36 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
95
102
|
pending(view).radius = value
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
@ReactProp(name = "presentationVersion")
|
|
106
|
+
override fun setPresentationVersion(view: SmoothClipView, value: Int) {
|
|
107
|
+
pending(view).presentationVersion = value
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@ReactProp(name = "initialClipTopLeftRadius")
|
|
111
|
+
override fun setInitialClipTopLeftRadius(view: SmoothClipView, value: Double) {
|
|
112
|
+
pending(view).topLeftRadius = value
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@ReactProp(name = "initialClipTopRightRadius")
|
|
116
|
+
override fun setInitialClipTopRightRadius(view: SmoothClipView, value: Double) {
|
|
117
|
+
pending(view).topRightRadius = value
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@ReactProp(name = "initialClipBottomRightRadius")
|
|
121
|
+
override fun setInitialClipBottomRightRadius(view: SmoothClipView, value: Double) {
|
|
122
|
+
pending(view).bottomRightRadius = value
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@ReactProp(name = "initialClipBottomLeftRadius")
|
|
126
|
+
override fun setInitialClipBottomLeftRadius(view: SmoothClipView, value: Double) {
|
|
127
|
+
pending(view).bottomLeftRadius = value
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@ReactProp(name = "initialClipCurve")
|
|
131
|
+
override fun setInitialClipCurve(view: SmoothClipView, value: Int) {
|
|
132
|
+
pending(view).curveCode = value
|
|
133
|
+
}
|
|
134
|
+
|
|
98
135
|
@ReactProp(name = "initialContentTranslateX")
|
|
99
136
|
override fun setInitialContentTranslateX(view: SmoothClipView, value: Double) {
|
|
100
137
|
pending(view).contentTranslateX = value
|
|
@@ -105,6 +142,11 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
105
142
|
pending(view).contentTranslateY = value
|
|
106
143
|
}
|
|
107
144
|
|
|
145
|
+
@ReactProp(name = "initialContentScale")
|
|
146
|
+
override fun setInitialContentScale(view: SmoothClipView, value: Double) {
|
|
147
|
+
pending(view).contentScale = value
|
|
148
|
+
}
|
|
149
|
+
|
|
108
150
|
override fun setClipGeometry(
|
|
109
151
|
view: SmoothClipView,
|
|
110
152
|
x: Double,
|
|
@@ -139,6 +181,38 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
139
181
|
)
|
|
140
182
|
}
|
|
141
183
|
|
|
184
|
+
override fun setClipPresentationV2(
|
|
185
|
+
view: SmoothClipView,
|
|
186
|
+
x: Double,
|
|
187
|
+
y: Double,
|
|
188
|
+
width: Double,
|
|
189
|
+
height: Double,
|
|
190
|
+
topLeftRadius: Double,
|
|
191
|
+
topRightRadius: Double,
|
|
192
|
+
bottomRightRadius: Double,
|
|
193
|
+
bottomLeftRadius: Double,
|
|
194
|
+
curveCode: Int,
|
|
195
|
+
contentTranslateX: Double,
|
|
196
|
+
contentTranslateY: Double,
|
|
197
|
+
contentScale: Double,
|
|
198
|
+
) {
|
|
199
|
+
view.commandIsAuthoritative = true
|
|
200
|
+
view.setClipPresentationV2Dip(
|
|
201
|
+
x,
|
|
202
|
+
y,
|
|
203
|
+
width,
|
|
204
|
+
height,
|
|
205
|
+
topLeftRadius,
|
|
206
|
+
topRightRadius,
|
|
207
|
+
bottomRightRadius,
|
|
208
|
+
bottomLeftRadius,
|
|
209
|
+
curveCode,
|
|
210
|
+
contentTranslateX,
|
|
211
|
+
contentTranslateY,
|
|
212
|
+
contentScale,
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
142
216
|
override fun onAfterUpdateTransaction(view: SmoothClipView) {
|
|
143
217
|
super.onAfterUpdateTransaction(view)
|
|
144
218
|
val initial = pendingInitialClips[view]
|
|
@@ -154,33 +228,73 @@ class SmoothClipViewManager : ViewGroupManager<SmoothClipView>(),
|
|
|
154
228
|
}
|
|
155
229
|
view.boundDriverId = initial.driverId
|
|
156
230
|
if (initial.driverId != 0.0) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
231
|
+
if (initial.presentationVersion >= 2) {
|
|
232
|
+
SmoothClipBindings.nativeRegisterViewV2(
|
|
233
|
+
initial.driverId,
|
|
234
|
+
view,
|
|
235
|
+
initial.x,
|
|
236
|
+
initial.y,
|
|
237
|
+
initial.width,
|
|
238
|
+
initial.height,
|
|
239
|
+
initial.topLeftRadius,
|
|
240
|
+
initial.topRightRadius,
|
|
241
|
+
initial.bottomRightRadius,
|
|
242
|
+
initial.bottomLeftRadius,
|
|
243
|
+
initial.curveCode,
|
|
244
|
+
initial.contentTranslateX,
|
|
245
|
+
initial.contentTranslateY,
|
|
246
|
+
initial.contentScale,
|
|
247
|
+
view.densityScale(),
|
|
248
|
+
view.width.toDouble(),
|
|
249
|
+
view.height.toDouble(),
|
|
250
|
+
view.isHostLifecycleVisible(),
|
|
251
|
+
)
|
|
252
|
+
} else {
|
|
253
|
+
SmoothClipBindings.nativeRegisterView(
|
|
254
|
+
initial.driverId,
|
|
255
|
+
view,
|
|
256
|
+
initial.x,
|
|
257
|
+
initial.y,
|
|
258
|
+
initial.width,
|
|
259
|
+
initial.height,
|
|
260
|
+
initial.radius,
|
|
261
|
+
initial.contentTranslateX,
|
|
262
|
+
initial.contentTranslateY,
|
|
263
|
+
view.densityScale(),
|
|
264
|
+
view.width.toDouble(),
|
|
265
|
+
view.height.toDouble(),
|
|
266
|
+
view.isHostLifecycleVisible(),
|
|
267
|
+
)
|
|
268
|
+
}
|
|
172
269
|
} else if (!view.commandIsAuthoritative) {
|
|
173
270
|
// Command-driven mode (driverId 0): the initial props are the only
|
|
174
271
|
// source of the first clip, so apply them directly.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
272
|
+
if (initial.presentationVersion >= 2) {
|
|
273
|
+
view.setClipPresentationV2Dip(
|
|
274
|
+
initial.x,
|
|
275
|
+
initial.y,
|
|
276
|
+
initial.width,
|
|
277
|
+
initial.height,
|
|
278
|
+
initial.topLeftRadius,
|
|
279
|
+
initial.topRightRadius,
|
|
280
|
+
initial.bottomRightRadius,
|
|
281
|
+
initial.bottomLeftRadius,
|
|
282
|
+
initial.curveCode,
|
|
283
|
+
initial.contentTranslateX,
|
|
284
|
+
initial.contentTranslateY,
|
|
285
|
+
initial.contentScale,
|
|
286
|
+
)
|
|
287
|
+
} else {
|
|
288
|
+
view.setClipPresentationDip(
|
|
289
|
+
initial.x,
|
|
290
|
+
initial.y,
|
|
291
|
+
initial.width,
|
|
292
|
+
initial.height,
|
|
293
|
+
initial.radius,
|
|
294
|
+
initial.contentTranslateX,
|
|
295
|
+
initial.contentTranslateY,
|
|
296
|
+
)
|
|
297
|
+
}
|
|
184
298
|
} else {
|
|
185
299
|
view.reapplyClipPresentation()
|
|
186
300
|
}
|
|
@@ -289,24 +289,154 @@ inline double springContinuationVelocity(
|
|
|
289
289
|
|
|
290
290
|
// --- Presentation channels ------------------------------------------------
|
|
291
291
|
|
|
292
|
-
|
|
292
|
+
// Eleven continuous V2 channels. The curve is categorical and is deliberately
|
|
293
|
+
// carried outside the scalar array.
|
|
294
|
+
constexpr std::size_t kChannelCount = 11;
|
|
293
295
|
using Channels = std::array<double, kChannelCount>;
|
|
294
296
|
|
|
297
|
+
inline double resolvedRadius(double overrideValue, double shorthand) {
|
|
298
|
+
return std::isfinite(overrideValue) ? overrideValue : shorthand;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
inline bool radiiAreUniform(
|
|
302
|
+
double topLeft,
|
|
303
|
+
double topRight,
|
|
304
|
+
double bottomRight,
|
|
305
|
+
double bottomLeft) {
|
|
306
|
+
return topLeft == topRight && topLeft == bottomRight &&
|
|
307
|
+
topLeft == bottomLeft;
|
|
308
|
+
}
|
|
309
|
+
|
|
295
310
|
inline Channels toChannels(const Presentation &presentation) {
|
|
311
|
+
const double topLeft = resolvedRadius(
|
|
312
|
+
presentation.clip.topLeftRadius, presentation.clip.radius);
|
|
313
|
+
const double topRight = resolvedRadius(
|
|
314
|
+
presentation.clip.topRightRadius, presentation.clip.radius);
|
|
315
|
+
const double bottomRight = resolvedRadius(
|
|
316
|
+
presentation.clip.bottomRightRadius, presentation.clip.radius);
|
|
317
|
+
const double bottomLeft = resolvedRadius(
|
|
318
|
+
presentation.clip.bottomLeftRadius, presentation.clip.radius);
|
|
296
319
|
return {presentation.clip.x,
|
|
297
320
|
presentation.clip.y,
|
|
298
321
|
presentation.clip.width,
|
|
299
322
|
presentation.clip.height,
|
|
300
|
-
|
|
323
|
+
topLeft,
|
|
324
|
+
topRight,
|
|
325
|
+
bottomRight,
|
|
326
|
+
bottomLeft,
|
|
301
327
|
presentation.contentTranslateX,
|
|
302
|
-
presentation.contentTranslateY
|
|
328
|
+
presentation.contentTranslateY,
|
|
329
|
+
presentation.contentScale};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// --- Protocol-V2 animation validation ------------------------------------
|
|
333
|
+
//
|
|
334
|
+
// Keep the invariants at the native ownership boundary as well as in JS. OTA
|
|
335
|
+
// callers and direct host-function users can bypass the TypeScript preflight;
|
|
336
|
+
// rejecting here prevents an invalid request from dissolving an existing
|
|
337
|
+
// animation group before the registry notices it cannot install the new plan.
|
|
338
|
+
|
|
339
|
+
inline bool isProtocolV2Curve(ClipCurve curve) {
|
|
340
|
+
return curve == ClipCurve::Circular || curve == ClipCurve::Continuous;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
inline bool isFiniteProtocolV2Presentation(
|
|
344
|
+
const Presentation &presentation) {
|
|
345
|
+
const Channels channels = toChannels(presentation);
|
|
346
|
+
return std::all_of(
|
|
347
|
+
channels.begin(), channels.end(),
|
|
348
|
+
[](double value) { return std::isfinite(value); }) &&
|
|
349
|
+
isProtocolV2Curve(presentation.clip.curve) &&
|
|
350
|
+
presentation.contentScale > 0;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
inline bool protocolV2PresentationsEqual(
|
|
354
|
+
const Presentation &first,
|
|
355
|
+
const Presentation &second) {
|
|
356
|
+
return first.clip.curve == second.clip.curve &&
|
|
357
|
+
toChannels(first) == toChannels(second);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
inline bool isValidReduceMotionCode(int32_t value) {
|
|
361
|
+
return value >= 0 && value <= 2;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
inline bool isValidProtocolV2Timing(const TimingAnimation &animation) {
|
|
365
|
+
return std::isfinite(animation.durationMs) && animation.durationMs >= 0 &&
|
|
366
|
+
std::isfinite(animation.controlPoint1X) &&
|
|
367
|
+
std::isfinite(animation.controlPoint1Y) &&
|
|
368
|
+
std::isfinite(animation.controlPoint2X) &&
|
|
369
|
+
std::isfinite(animation.controlPoint2Y) &&
|
|
370
|
+
animation.controlPoint1X >= 0 && animation.controlPoint1X <= 1 &&
|
|
371
|
+
animation.controlPoint2X >= 0 && animation.controlPoint2X <= 1 &&
|
|
372
|
+
isValidReduceMotionCode(animation.reduceMotion);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
inline bool isValidProtocolV2Spring(const SpringAnimation &animation) {
|
|
376
|
+
return std::isfinite(animation.mass) && animation.mass > 0 &&
|
|
377
|
+
std::isfinite(animation.stiffness) && animation.stiffness > 0 &&
|
|
378
|
+
std::isfinite(animation.damping) && animation.damping >= 0 &&
|
|
379
|
+
std::isfinite(animation.initialVelocity) &&
|
|
380
|
+
isValidReduceMotionCode(animation.reduceMotion);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
inline bool protocolV2SpringScaleIsProvablyPositive(
|
|
384
|
+
const Presentation &start,
|
|
385
|
+
const Presentation &target,
|
|
386
|
+
const SpringAnimation &animation) {
|
|
387
|
+
if (start.contentScale == target.contentScale) return true;
|
|
388
|
+
// With zero initial velocity, a critically/over-damped unit response is
|
|
389
|
+
// monotone, so it remains between two positive endpoints. Inherited or
|
|
390
|
+
// under-damped velocity can overshoot through zero and must be compiled to
|
|
391
|
+
// keyframes by the caller.
|
|
392
|
+
return !animation.inheritVelocity && animation.initialVelocity == 0 &&
|
|
393
|
+
animation.damping * animation.damping >=
|
|
394
|
+
4 * animation.mass * animation.stiffness;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
inline bool isValidProtocolV2Keyframes(
|
|
398
|
+
const std::vector<Keyframe> &keyframes,
|
|
399
|
+
const Presentation &resolvedStart,
|
|
400
|
+
const Presentation &target,
|
|
401
|
+
bool requireExplicitStart) {
|
|
402
|
+
if (keyframes.size() < 2 || keyframes.front().offset != 0 ||
|
|
403
|
+
keyframes.back().offset != 1 ||
|
|
404
|
+
!protocolV2PresentationsEqual(keyframes.back().presentation, target)) {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
if (requireExplicitStart && !protocolV2PresentationsEqual(
|
|
408
|
+
keyframes.front().presentation,
|
|
409
|
+
resolvedStart)) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
double previousOffset = -1;
|
|
413
|
+
for (const Keyframe &keyframe : keyframes) {
|
|
414
|
+
if (!std::isfinite(keyframe.offset) || keyframe.offset <= previousOffset ||
|
|
415
|
+
keyframe.offset < 0 || keyframe.offset > 1 ||
|
|
416
|
+
!isFiniteProtocolV2Presentation(keyframe.presentation) ||
|
|
417
|
+
keyframe.presentation.clip.curve != target.clip.curve) {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
previousOffset = keyframe.offset;
|
|
421
|
+
}
|
|
422
|
+
return resolvedStart.clip.curve == target.clip.curve;
|
|
303
423
|
}
|
|
304
424
|
|
|
305
|
-
inline Presentation fromChannels(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
channels[
|
|
425
|
+
inline Presentation fromChannels(
|
|
426
|
+
const Channels &channels,
|
|
427
|
+
ClipCurve curve = ClipCurve::Circular) {
|
|
428
|
+
Geometry geometry{
|
|
429
|
+
channels[0], channels[1], channels[2], channels[3], 0.0};
|
|
430
|
+
geometry.topLeftRadius = channels[4];
|
|
431
|
+
geometry.topRightRadius = channels[5];
|
|
432
|
+
geometry.bottomRightRadius = channels[6];
|
|
433
|
+
geometry.bottomLeftRadius = channels[7];
|
|
434
|
+
geometry.radius = radiiAreUniform(
|
|
435
|
+
channels[4], channels[5], channels[6], channels[7])
|
|
436
|
+
? channels[4]
|
|
437
|
+
: 0.0;
|
|
438
|
+
geometry.curve = curve;
|
|
439
|
+
return Presentation{geometry, channels[8], channels[9], channels[10]};
|
|
310
440
|
}
|
|
311
441
|
|
|
312
442
|
inline Presentation interpolate(
|
|
@@ -316,14 +446,13 @@ inline Presentation interpolate(
|
|
|
316
446
|
const auto mix = [progress](double start, double end) {
|
|
317
447
|
return start + (end - start) * progress;
|
|
318
448
|
};
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
mix(from.contentTranslateY, to.contentTranslateY)};
|
|
449
|
+
Channels blended{};
|
|
450
|
+
const Channels fromChannelsValue = toChannels(from);
|
|
451
|
+
const Channels toChannelsValue = toChannels(to);
|
|
452
|
+
for (std::size_t index = 0; index < kChannelCount; index += 1) {
|
|
453
|
+
blended[index] = mix(fromChannelsValue[index], toChannelsValue[index]);
|
|
454
|
+
}
|
|
455
|
+
return fromChannels(blended, from.clip.curve);
|
|
327
456
|
}
|
|
328
457
|
|
|
329
458
|
// Freezes a timing animation at one already-rendered raw-time phase and
|
|
@@ -361,95 +490,14 @@ inline TimingContinuation timingContinuationAtFrame(
|
|
|
361
490
|
|
|
362
491
|
// --- Keyframe curve -------------------------------------------------------
|
|
363
492
|
|
|
364
|
-
// Keyframes
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
// Straight lerping between keyframes is exact at every keyframe and wrong in
|
|
368
|
-
// between in a way that matters more than its position error suggests: the
|
|
369
|
-
// interpolated VELOCITY is a staircase that steps at every keyframe boundary,
|
|
370
|
-
// beside content whose Reanimated curve is continuous. Consumers bake dozens of
|
|
371
|
-
// keyframes precisely because their geometry path is not affine in progress, so
|
|
372
|
-
// reconstructing that path smoothly is the honest reading of their intent.
|
|
373
|
-
//
|
|
374
|
-
// Monotone rather than plain Catmull-Rom because the alternative can overshoot,
|
|
375
|
-
// and these channels do not tolerate it: width, height and radius must never
|
|
376
|
-
// dip below zero or bulge past the values the consumer asked for. The
|
|
377
|
-
// Fritsch-Carlson tangent clamp keeps every segment inside the range of its own
|
|
378
|
-
// endpoints. Two keyframes degenerate to the old straight line (both tangents
|
|
379
|
-
// equal the secant) — equal in real arithmetic, within one ulp (~1e-13 of the
|
|
380
|
-
// travel) in doubles because the Hermite blend orders its operations
|
|
381
|
-
// differently than the lerp it replaced. The jfloat delivery cast erases the
|
|
382
|
-
// difference long before a pixel could see it.
|
|
383
|
-
//
|
|
384
|
-
// PARITY NOTE: iOS builds a CAKeyframeAnimation with `kCAAnimationLinear`
|
|
385
|
-
// (SmoothClipView.mm) and cannot run this evaluator — CoreAnimation interpolates
|
|
386
|
-
// off-thread. The platforms therefore differ mid-segment by the linearization
|
|
387
|
-
// error this removes, sub-pixel at any realistic keyframe density and zero at
|
|
388
|
-
// every keyframe. Closing it needs either `kCAAnimationCubic` (a different
|
|
389
|
-
// spline that CAN overshoot, so no) or resampling the keyframes densely through
|
|
390
|
-
// this curve before handing them to CA.
|
|
493
|
+
// Keyframes interpolate segment-wise linearly on every platform. This is a
|
|
494
|
+
// public V2 contract: consumers compile unsupported springs/easings into exact
|
|
495
|
+
// scalar samples, and native must not reinterpret those samples as a spline.
|
|
391
496
|
class KeyframeCurve {
|
|
392
497
|
public:
|
|
393
498
|
void reset(std::vector<Keyframe> frames) {
|
|
394
499
|
frames_ = std::move(frames);
|
|
395
500
|
cursor_ = 1;
|
|
396
|
-
tangents_.assign(frames_.size(), Channels{});
|
|
397
|
-
const std::size_t count = frames_.size();
|
|
398
|
-
if (count < 2) return;
|
|
399
|
-
|
|
400
|
-
// Secant slope of every segment, per channel. A non-increasing offset pair
|
|
401
|
-
// (the re-latch remap can produce one) contributes no slope rather than
|
|
402
|
-
// dividing by zero; evaluate() short-circuits those segments too.
|
|
403
|
-
std::vector<Channels> secants(count - 1);
|
|
404
|
-
for (std::size_t index = 0; index + 1 < count; index += 1) {
|
|
405
|
-
const double span = frames_[index + 1].offset - frames_[index].offset;
|
|
406
|
-
const Channels lower = toChannels(frames_[index].presentation);
|
|
407
|
-
const Channels upper = toChannels(frames_[index + 1].presentation);
|
|
408
|
-
for (std::size_t channel = 0; channel < kChannelCount; channel += 1) {
|
|
409
|
-
secants[index][channel] =
|
|
410
|
-
span > 0 ? (upper[channel] - lower[channel]) / span : 0.0;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// One-sided at the ends, averaged inside.
|
|
415
|
-
for (std::size_t channel = 0; channel < kChannelCount; channel += 1) {
|
|
416
|
-
tangents_[0][channel] = secants[0][channel];
|
|
417
|
-
tangents_[count - 1][channel] = secants[count - 2][channel];
|
|
418
|
-
for (std::size_t index = 1; index + 1 < count; index += 1) {
|
|
419
|
-
tangents_[index][channel] =
|
|
420
|
-
(secants[index - 1][channel] + secants[index][channel]) / 2.0;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// Fritsch-Carlson clamp: this is what makes the curve non-overshooting.
|
|
425
|
-
for (std::size_t index = 0; index + 1 < count; index += 1) {
|
|
426
|
-
for (std::size_t channel = 0; channel < kChannelCount; channel += 1) {
|
|
427
|
-
const double secant = secants[index][channel];
|
|
428
|
-
if (secant == 0.0) {
|
|
429
|
-
// Flat segment: pin both ends flat so the curve cannot bulge off a
|
|
430
|
-
// pair of equal keyframes.
|
|
431
|
-
tangents_[index][channel] = 0.0;
|
|
432
|
-
tangents_[index + 1][channel] = 0.0;
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
double alpha = tangents_[index][channel] / secant;
|
|
436
|
-
double beta = tangents_[index + 1][channel] / secant;
|
|
437
|
-
if (alpha < 0.0) {
|
|
438
|
-
tangents_[index][channel] = 0.0;
|
|
439
|
-
alpha = 0.0;
|
|
440
|
-
}
|
|
441
|
-
if (beta < 0.0) {
|
|
442
|
-
tangents_[index + 1][channel] = 0.0;
|
|
443
|
-
beta = 0.0;
|
|
444
|
-
}
|
|
445
|
-
const double magnitude = alpha * alpha + beta * beta;
|
|
446
|
-
if (magnitude > 9.0) {
|
|
447
|
-
const double scale = 3.0 / std::sqrt(magnitude);
|
|
448
|
-
tangents_[index][channel] = scale * alpha * secant;
|
|
449
|
-
tangents_[index + 1][channel] = scale * beta * secant;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
501
|
}
|
|
454
502
|
|
|
455
503
|
const std::vector<Keyframe> &frames() const { return frames_; }
|
|
@@ -458,7 +506,7 @@ class KeyframeCurve {
|
|
|
458
506
|
|
|
459
507
|
Presentation evaluate(double progress) {
|
|
460
508
|
const std::size_t count = frames_.size();
|
|
461
|
-
if (count == 0) return Presentation{{0, 0, 0, 0, 0}, 0, 0};
|
|
509
|
+
if (count == 0) return Presentation{{0, 0, 0, 0, 0}, 0, 0, 1};
|
|
462
510
|
if (count == 1) return frames_[0].presentation;
|
|
463
511
|
|
|
464
512
|
// The scan resumes from the previous segment instead of restarting at 1.
|
|
@@ -476,30 +524,11 @@ class KeyframeCurve {
|
|
|
476
524
|
if (span <= 0) return higher.presentation;
|
|
477
525
|
|
|
478
526
|
const double t = clamp01((progress - lower.offset) / span);
|
|
479
|
-
|
|
480
|
-
const double t3 = t2 * t;
|
|
481
|
-
// Cubic Hermite basis.
|
|
482
|
-
const double h00 = 2 * t3 - 3 * t2 + 1;
|
|
483
|
-
const double h10 = t3 - 2 * t2 + t;
|
|
484
|
-
const double h01 = -2 * t3 + 3 * t2;
|
|
485
|
-
const double h11 = t3 - t2;
|
|
486
|
-
|
|
487
|
-
const Channels from = toChannels(lower.presentation);
|
|
488
|
-
const Channels to = toChannels(higher.presentation);
|
|
489
|
-
const Channels &fromSlope = tangents_[upper - 1];
|
|
490
|
-
const Channels &toSlope = tangents_[upper];
|
|
491
|
-
Channels blended{};
|
|
492
|
-
for (std::size_t channel = 0; channel < kChannelCount; channel += 1) {
|
|
493
|
-
blended[channel] = h00 * from[channel] +
|
|
494
|
-
h10 * span * fromSlope[channel] + h01 * to[channel] +
|
|
495
|
-
h11 * span * toSlope[channel];
|
|
496
|
-
}
|
|
497
|
-
return fromChannels(blended);
|
|
527
|
+
return interpolate(lower.presentation, higher.presentation, t);
|
|
498
528
|
}
|
|
499
529
|
|
|
500
|
-
|
|
530
|
+
private:
|
|
501
531
|
std::vector<Keyframe> frames_;
|
|
502
|
-
std::vector<Channels> tangents_;
|
|
503
532
|
std::size_t cursor_ = 1;
|
|
504
533
|
};
|
|
505
534
|
|
package/cpp/SmoothClipRegistry.h
CHANGED
|
@@ -7,18 +7,32 @@
|
|
|
7
7
|
|
|
8
8
|
namespace smoothclip {
|
|
9
9
|
|
|
10
|
+
enum class ClipCurve : int32_t {
|
|
11
|
+
Circular = 0,
|
|
12
|
+
Continuous = 1,
|
|
13
|
+
};
|
|
14
|
+
|
|
10
15
|
struct Geometry {
|
|
11
16
|
double x;
|
|
12
17
|
double y;
|
|
13
18
|
double width;
|
|
14
19
|
double height;
|
|
15
20
|
double radius;
|
|
21
|
+
// NaN means "use radius". Keeping the overrides at the end preserves every
|
|
22
|
+
// existing five-value aggregate initializer used by the V1 implementation
|
|
23
|
+
// and its native tests.
|
|
24
|
+
double topLeftRadius = std::numeric_limits<double>::quiet_NaN();
|
|
25
|
+
double topRightRadius = std::numeric_limits<double>::quiet_NaN();
|
|
26
|
+
double bottomRightRadius = std::numeric_limits<double>::quiet_NaN();
|
|
27
|
+
double bottomLeftRadius = std::numeric_limits<double>::quiet_NaN();
|
|
28
|
+
ClipCurve curve = ClipCurve::Circular;
|
|
16
29
|
};
|
|
17
30
|
|
|
18
31
|
struct Presentation {
|
|
19
32
|
Geometry clip;
|
|
20
33
|
double contentTranslateX;
|
|
21
34
|
double contentTranslateY;
|
|
35
|
+
double contentScale = 1.0;
|
|
22
36
|
};
|
|
23
37
|
|
|
24
38
|
struct Keyframe {
|
|
@@ -62,37 +76,121 @@ struct CancelResult {
|
|
|
62
76
|
Presentation presentation;
|
|
63
77
|
};
|
|
64
78
|
|
|
79
|
+
struct DriverSnapshot {
|
|
80
|
+
uint64_t driverId;
|
|
81
|
+
Presentation presentation;
|
|
82
|
+
bool ready;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
struct BatchEntry {
|
|
86
|
+
uint64_t driverId;
|
|
87
|
+
Presentation presentation;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
struct GroupMotionEntry {
|
|
91
|
+
uint64_t driverId;
|
|
92
|
+
bool hasFrom;
|
|
93
|
+
Presentation from;
|
|
94
|
+
Presentation target;
|
|
95
|
+
std::vector<Keyframe> keyframes;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
enum class GroupSuspensionPolicy : int32_t {
|
|
99
|
+
Pause = 0,
|
|
100
|
+
Finish = 1,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
enum class GroupCancelBehavior : int32_t {
|
|
104
|
+
Freeze = 0,
|
|
105
|
+
Finish = 1,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// The seven-scalar V1 bridge predates protocol-V2 host-normalization
|
|
109
|
+
// preflight and strict spring/duration rejection. Keep that wire contract
|
|
110
|
+
// selectable without widening any V1 entry point; V2 and grouped calls retain
|
|
111
|
+
// the conservative validation required for portable native ownership.
|
|
112
|
+
enum class AnimationValidationMode : int32_t {
|
|
113
|
+
LegacyV1 = 1,
|
|
114
|
+
ProtocolV2 = 2,
|
|
115
|
+
};
|
|
116
|
+
|
|
65
117
|
using CompletionCallback =
|
|
66
118
|
std::function<void(uint64_t, int32_t, bool)>;
|
|
119
|
+
using GroupCompletionCallback =
|
|
120
|
+
std::function<void(uint64_t, int32_t, bool, std::vector<uint64_t>)>;
|
|
67
121
|
|
|
68
122
|
void setCompletionCallback(
|
|
69
123
|
const void *owner,
|
|
70
124
|
CompletionCallback callback);
|
|
71
125
|
void clearCompletionCallback(const void *owner);
|
|
126
|
+
void setGroupCompletionCallback(
|
|
127
|
+
const void *owner,
|
|
128
|
+
GroupCompletionCallback callback);
|
|
129
|
+
void clearGroupCompletionCallback(const void *owner);
|
|
72
130
|
|
|
131
|
+
// recordVelocity gates the 'inherit' velocity-sample recording for this write.
|
|
132
|
+
// Default true (the pre-flag behavior); Android's JS layer passes false for
|
|
133
|
+
// setScalars hot writes on drivers without SmoothClipDriverOptions
|
|
134
|
+
// .velocityTracking, so the per-frame drag stream skips the clock read and
|
|
135
|
+
// channel copies. iOS callers never pass it and keep recording always.
|
|
73
136
|
void setPresentation(
|
|
74
137
|
uint64_t driverId,
|
|
75
138
|
Presentation presentation,
|
|
76
139
|
bool takeOwnership,
|
|
77
|
-
bool overridePendingAnimation = false
|
|
140
|
+
bool overridePendingAnimation = false,
|
|
141
|
+
bool recordVelocity = true);
|
|
78
142
|
Presentation beginInteraction(uint64_t driverId);
|
|
143
|
+
Presentation snapshotCurrent(uint64_t driverId);
|
|
144
|
+
std::vector<DriverSnapshot> beginGroupInteraction(
|
|
145
|
+
const std::vector<uint64_t> &driverIds);
|
|
146
|
+
std::vector<DriverSnapshot> snapshotGroup(
|
|
147
|
+
const std::vector<uint64_t> &driverIds);
|
|
148
|
+
bool setPresentationBatch(const std::vector<BatchEntry> &entries);
|
|
149
|
+
int32_t animateTimingGroup(
|
|
150
|
+
uint64_t controllerId,
|
|
151
|
+
std::vector<GroupMotionEntry> entries,
|
|
152
|
+
TimingAnimation animation,
|
|
153
|
+
GroupSuspensionPolicy suspensionPolicy,
|
|
154
|
+
double startedAtHintS = std::numeric_limits<double>::quiet_NaN());
|
|
155
|
+
int32_t animateSpringGroup(
|
|
156
|
+
uint64_t controllerId,
|
|
157
|
+
std::vector<GroupMotionEntry> entries,
|
|
158
|
+
SpringAnimation animation,
|
|
159
|
+
GroupSuspensionPolicy suspensionPolicy,
|
|
160
|
+
double startedAtHintS = std::numeric_limits<double>::quiet_NaN());
|
|
161
|
+
int32_t animateKeyframesGroup(
|
|
162
|
+
uint64_t controllerId,
|
|
163
|
+
std::vector<GroupMotionEntry> entries,
|
|
164
|
+
double durationMs,
|
|
165
|
+
int32_t reduceMotion,
|
|
166
|
+
GroupSuspensionPolicy suspensionPolicy,
|
|
167
|
+
double startedAtHintS = std::numeric_limits<double>::quiet_NaN());
|
|
168
|
+
std::vector<DriverSnapshot> cancelAnimationGroup(
|
|
169
|
+
int32_t groupId,
|
|
170
|
+
GroupCancelBehavior behavior);
|
|
79
171
|
int32_t animateTiming(
|
|
80
172
|
uint64_t driverId,
|
|
81
173
|
AnimationStart start,
|
|
82
174
|
Presentation presentation,
|
|
83
|
-
TimingAnimation animation
|
|
175
|
+
TimingAnimation animation,
|
|
176
|
+
AnimationValidationMode validationMode =
|
|
177
|
+
AnimationValidationMode::ProtocolV2);
|
|
84
178
|
int32_t animateSpring(
|
|
85
179
|
uint64_t driverId,
|
|
86
180
|
AnimationStart start,
|
|
87
181
|
Presentation presentation,
|
|
88
|
-
SpringAnimation animation
|
|
182
|
+
SpringAnimation animation,
|
|
183
|
+
AnimationValidationMode validationMode =
|
|
184
|
+
AnimationValidationMode::ProtocolV2);
|
|
89
185
|
int32_t animateKeyframes(
|
|
90
186
|
uint64_t driverId,
|
|
91
187
|
AnimationStart start,
|
|
92
188
|
Presentation presentation,
|
|
93
189
|
double durationMs,
|
|
94
190
|
std::vector<Keyframe> keyframes,
|
|
95
|
-
int32_t reduceMotion
|
|
191
|
+
int32_t reduceMotion,
|
|
192
|
+
AnimationValidationMode validationMode =
|
|
193
|
+
AnimationValidationMode::ProtocolV2);
|
|
96
194
|
int32_t rejectAnimation(uint64_t driverId);
|
|
97
195
|
CancelResult cancelAnimation(
|
|
98
196
|
uint64_t driverId,
|