react-native-smooth-clip-view 0.2.9 → 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 +60 -21
- package/android/build.gradle +6 -2
- package/android/proguard-rules.pro +3 -3
- package/android/src/main/cpp/SmoothClipAndroid.h +10 -0
- package/android/src/main/cpp/SmoothClipBindings.cpp +890 -4
- package/android/src/main/cpp/SmoothClipRegistry.cpp +1056 -44
- 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 +371 -18
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +138 -24
- package/cpp/SmoothClipAnimationCurve.h +151 -122
- package/cpp/SmoothClipRegistry.h +95 -3
- package/cpp/SmoothClipSharedGeometry.h +146 -7
- package/cpp/SmoothClipVelocityTracker.h +13 -9
- package/ios/SmoothClipGeometry.h +184 -6
- package/ios/SmoothClipRegistry.mm +1028 -22
- 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 +263 -63
- 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 +25 -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 +42 -11
- package/src/drivers.native.ts +824 -124
- 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
|
@@ -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,13 +76,57 @@ 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
|
|
|
73
131
|
// recordVelocity gates the 'inherit' velocity-sample recording for this write.
|
|
74
132
|
// Default true (the pre-flag behavior); Android's JS layer passes false for
|
|
@@ -82,23 +140,57 @@ void setPresentation(
|
|
|
82
140
|
bool overridePendingAnimation = false,
|
|
83
141
|
bool recordVelocity = true);
|
|
84
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);
|
|
85
171
|
int32_t animateTiming(
|
|
86
172
|
uint64_t driverId,
|
|
87
173
|
AnimationStart start,
|
|
88
174
|
Presentation presentation,
|
|
89
|
-
TimingAnimation animation
|
|
175
|
+
TimingAnimation animation,
|
|
176
|
+
AnimationValidationMode validationMode =
|
|
177
|
+
AnimationValidationMode::ProtocolV2);
|
|
90
178
|
int32_t animateSpring(
|
|
91
179
|
uint64_t driverId,
|
|
92
180
|
AnimationStart start,
|
|
93
181
|
Presentation presentation,
|
|
94
|
-
SpringAnimation animation
|
|
182
|
+
SpringAnimation animation,
|
|
183
|
+
AnimationValidationMode validationMode =
|
|
184
|
+
AnimationValidationMode::ProtocolV2);
|
|
95
185
|
int32_t animateKeyframes(
|
|
96
186
|
uint64_t driverId,
|
|
97
187
|
AnimationStart start,
|
|
98
188
|
Presentation presentation,
|
|
99
189
|
double durationMs,
|
|
100
190
|
std::vector<Keyframe> keyframes,
|
|
101
|
-
int32_t reduceMotion
|
|
191
|
+
int32_t reduceMotion,
|
|
192
|
+
AnimationValidationMode validationMode =
|
|
193
|
+
AnimationValidationMode::ProtocolV2);
|
|
102
194
|
int32_t rejectAnimation(uint64_t driverId);
|
|
103
195
|
CancelResult cancelAnimation(
|
|
104
196
|
uint64_t driverId,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
+
#include "SmoothClipRegistry.h"
|
|
4
|
+
|
|
3
5
|
#include <algorithm>
|
|
4
6
|
#include <cmath>
|
|
5
7
|
|
|
@@ -11,23 +13,110 @@ struct NormalizedClip {
|
|
|
11
13
|
double right = 0;
|
|
12
14
|
double bottom = 0;
|
|
13
15
|
double radius = 0;
|
|
16
|
+
double topLeftRadius = 0;
|
|
17
|
+
double topRightRadius = 0;
|
|
18
|
+
double bottomRightRadius = 0;
|
|
19
|
+
double bottomLeftRadius = 0;
|
|
20
|
+
ClipCurve curve = ClipCurve::Circular;
|
|
14
21
|
};
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
inline double SmoothClipResolvedRadius(double overrideValue, double radius) {
|
|
24
|
+
return std::isfinite(overrideValue) ? overrideValue : radius;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns true only when host normalization is the identity for this geometry.
|
|
29
|
+
*
|
|
30
|
+
* Timing/keyframe renderers interpolate already-normalized native values on
|
|
31
|
+
* iOS, while Android interpolates presentation scalars and normalizes each
|
|
32
|
+
* delivered frame. Those operations commute exactly when every anchor stays
|
|
33
|
+
* inside one host and the CSS corner-overlap rule never activates. Because
|
|
34
|
+
* each condition below is linear, it also holds for every convex interpolation
|
|
35
|
+
* between accepted anchors.
|
|
36
|
+
*
|
|
37
|
+
* Static clipping deliberately does not use this gate: out-of-bounds geometry
|
|
38
|
+
* remains valid and is still clipped by SmoothClipNormalizeV2.
|
|
39
|
+
*/
|
|
40
|
+
inline bool SmoothClipGeometryNormalizationIsIdentity(
|
|
41
|
+
const Geometry &geometry,
|
|
42
|
+
double hostWidth,
|
|
43
|
+
double hostHeight) {
|
|
44
|
+
const double topLeft = SmoothClipResolvedRadius(
|
|
45
|
+
geometry.topLeftRadius, geometry.radius);
|
|
46
|
+
const double topRight = SmoothClipResolvedRadius(
|
|
47
|
+
geometry.topRightRadius, geometry.radius);
|
|
48
|
+
const double bottomRight = SmoothClipResolvedRadius(
|
|
49
|
+
geometry.bottomRightRadius, geometry.radius);
|
|
50
|
+
const double bottomLeft = SmoothClipResolvedRadius(
|
|
51
|
+
geometry.bottomLeftRadius, geometry.radius);
|
|
52
|
+
const double right = geometry.x + geometry.width;
|
|
53
|
+
const double bottom = geometry.y + geometry.height;
|
|
54
|
+
const double topRadii = topLeft + topRight;
|
|
55
|
+
const double bottomRadii = bottomLeft + bottomRight;
|
|
56
|
+
const double leftRadii = topLeft + bottomLeft;
|
|
57
|
+
const double rightRadii = topRight + bottomRight;
|
|
58
|
+
const double values[] = {
|
|
59
|
+
geometry.x,
|
|
60
|
+
geometry.y,
|
|
61
|
+
geometry.width,
|
|
62
|
+
geometry.height,
|
|
63
|
+
geometry.radius,
|
|
64
|
+
topLeft,
|
|
65
|
+
topRight,
|
|
66
|
+
bottomRight,
|
|
67
|
+
bottomLeft,
|
|
68
|
+
right,
|
|
69
|
+
bottom,
|
|
70
|
+
topRadii,
|
|
71
|
+
bottomRadii,
|
|
72
|
+
leftRadii,
|
|
73
|
+
rightRadii,
|
|
74
|
+
hostWidth,
|
|
75
|
+
hostHeight,
|
|
76
|
+
};
|
|
77
|
+
for (const double value : values) {
|
|
78
|
+
if (!std::isfinite(value)) return false;
|
|
79
|
+
}
|
|
80
|
+
return hostWidth >= 0 && hostHeight >= 0 &&
|
|
81
|
+
geometry.x >= 0 && geometry.y >= 0 &&
|
|
82
|
+
geometry.width >= 0 && geometry.height >= 0 &&
|
|
83
|
+
right <= hostWidth && bottom <= hostHeight &&
|
|
84
|
+
topLeft >= 0 && topRight >= 0 &&
|
|
85
|
+
bottomRight >= 0 && bottomLeft >= 0 &&
|
|
86
|
+
topRadii <= geometry.width &&
|
|
87
|
+
bottomRadii <= geometry.width &&
|
|
88
|
+
leftRadii <= geometry.height &&
|
|
89
|
+
rightRadii <= geometry.height;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
inline bool SmoothClipNormalizeV2(
|
|
21
93
|
double x,
|
|
22
94
|
double y,
|
|
23
95
|
double width,
|
|
24
96
|
double height,
|
|
25
97
|
double radius,
|
|
98
|
+
double topLeftRadius,
|
|
99
|
+
double topRightRadius,
|
|
100
|
+
double bottomRightRadius,
|
|
101
|
+
double bottomLeftRadius,
|
|
102
|
+
ClipCurve curve,
|
|
26
103
|
double hostWidth,
|
|
27
104
|
double hostHeight,
|
|
28
105
|
NormalizedClip &out) {
|
|
106
|
+
const double resolvedTopLeft =
|
|
107
|
+
SmoothClipResolvedRadius(topLeftRadius, radius);
|
|
108
|
+
const double resolvedTopRight =
|
|
109
|
+
SmoothClipResolvedRadius(topRightRadius, radius);
|
|
110
|
+
const double resolvedBottomRight =
|
|
111
|
+
SmoothClipResolvedRadius(bottomRightRadius, radius);
|
|
112
|
+
const double resolvedBottomLeft =
|
|
113
|
+
SmoothClipResolvedRadius(bottomLeftRadius, radius);
|
|
29
114
|
if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(width) ||
|
|
30
115
|
!std::isfinite(height) || !std::isfinite(radius) ||
|
|
116
|
+
!std::isfinite(resolvedTopLeft) ||
|
|
117
|
+
!std::isfinite(resolvedTopRight) ||
|
|
118
|
+
!std::isfinite(resolvedBottomRight) ||
|
|
119
|
+
!std::isfinite(resolvedBottomLeft) ||
|
|
31
120
|
!std::isfinite(hostWidth) || !std::isfinite(hostHeight)) {
|
|
32
121
|
return false;
|
|
33
122
|
}
|
|
@@ -42,9 +131,59 @@ inline bool SmoothClipNormalize(
|
|
|
42
131
|
out.bottom = std::min(boundedHostHeight, std::max(0.0, requestedBottom));
|
|
43
132
|
const double visibleWidth = std::max(0.0, out.right - out.left);
|
|
44
133
|
const double visibleHeight = std::max(0.0, out.bottom - out.top);
|
|
45
|
-
|
|
46
|
-
|
|
134
|
+
|
|
135
|
+
double topLeft = std::max(0.0, resolvedTopLeft);
|
|
136
|
+
double topRight = std::max(0.0, resolvedTopRight);
|
|
137
|
+
double bottomRight = std::max(0.0, resolvedBottomRight);
|
|
138
|
+
double bottomLeft = std::max(0.0, resolvedBottomLeft);
|
|
139
|
+
double scale = 1.0;
|
|
140
|
+
const auto includeLimit = [&scale](double available, double requested) {
|
|
141
|
+
if (requested > 0) scale = std::min(scale, available / requested);
|
|
142
|
+
};
|
|
143
|
+
includeLimit(visibleWidth, topLeft + topRight);
|
|
144
|
+
includeLimit(visibleWidth, bottomLeft + bottomRight);
|
|
145
|
+
includeLimit(visibleHeight, topLeft + bottomLeft);
|
|
146
|
+
includeLimit(visibleHeight, topRight + bottomRight);
|
|
147
|
+
scale = std::max(0.0, std::min(1.0, scale));
|
|
148
|
+
out.topLeftRadius = topLeft * scale;
|
|
149
|
+
out.topRightRadius = topRight * scale;
|
|
150
|
+
out.bottomRightRadius = bottomRight * scale;
|
|
151
|
+
out.bottomLeftRadius = bottomLeft * scale;
|
|
152
|
+
const bool uniform = out.topLeftRadius == out.topRightRadius &&
|
|
153
|
+
out.topLeftRadius == out.bottomRightRadius &&
|
|
154
|
+
out.topLeftRadius == out.bottomLeftRadius;
|
|
155
|
+
out.radius = uniform ? out.topLeftRadius : 0.0;
|
|
156
|
+
out.curve = curve;
|
|
47
157
|
return true;
|
|
48
158
|
}
|
|
49
159
|
|
|
160
|
+
// Shared clip normalization emitting edges (Android's Outline consumes
|
|
161
|
+
// edges). Mirrors ios/SmoothClipGeometry.h (SmoothClipNormalizeGeometry) and
|
|
162
|
+
// ClipGeometryNormalizer.kt (normalizeClipGeometryPx); a change here must
|
|
163
|
+
// land in all three.
|
|
164
|
+
inline bool SmoothClipNormalize(
|
|
165
|
+
double x,
|
|
166
|
+
double y,
|
|
167
|
+
double width,
|
|
168
|
+
double height,
|
|
169
|
+
double radius,
|
|
170
|
+
double hostWidth,
|
|
171
|
+
double hostHeight,
|
|
172
|
+
NormalizedClip &out) {
|
|
173
|
+
return SmoothClipNormalizeV2(
|
|
174
|
+
x,
|
|
175
|
+
y,
|
|
176
|
+
width,
|
|
177
|
+
height,
|
|
178
|
+
radius,
|
|
179
|
+
radius,
|
|
180
|
+
radius,
|
|
181
|
+
radius,
|
|
182
|
+
radius,
|
|
183
|
+
ClipCurve::Circular,
|
|
184
|
+
hostWidth,
|
|
185
|
+
hostHeight,
|
|
186
|
+
out);
|
|
187
|
+
}
|
|
188
|
+
|
|
50
189
|
} // namespace smoothclip
|