react-native-smooth-clip-view 0.2.5 → 0.2.6
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 +49 -21
- package/android/CMakeLists.txt +0 -1
- package/android/src/main/cpp/SmoothClipAndroid.h +5 -0
- package/android/src/main/cpp/SmoothClipBindings.cpp +28 -8
- package/android/src/main/cpp/SmoothClipRegistry.cpp +192 -191
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +61 -11
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +20 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +1 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +105 -34
- package/cpp/SmoothClipAnimationCurve.h +468 -0
- package/cpp/SmoothClipAnimationId.h +32 -0
- package/cpp/SmoothClipRegistry.h +11 -1
- package/cpp/SmoothClipVelocityTracker.h +27 -5
- package/ios/SmoothClipRegistry.mm +246 -97
- package/ios/SmoothClipTurboModule.cpp +4 -2
- package/ios/SmoothClipTurboModule.h +2 -1
- package/ios/SmoothClipView.mm +73 -16
- package/lib/module/NativeSmoothClipModule.js.map +1 -1
- package/lib/module/drivers.native.js +98 -29
- package/lib/module/drivers.native.js.map +1 -1
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +1 -1
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
- package/lib/typescript/src/driverTypes.d.ts +3 -2
- package/lib/typescript/src/driverTypes.d.ts.map +1 -1
- package/lib/typescript/src/drivers.native.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeSmoothClipModule.ts +2 -1
- package/src/driverTypes.ts +3 -2
- package/src/drivers.native.ts +132 -25
package/README.md
CHANGED
|
@@ -165,6 +165,15 @@ const gesture = Gesture.Pan()
|
|
|
165
165
|
| `driver` | `SmoothClipDriver` | Reusable hybrid clip driver. |
|
|
166
166
|
| `children` | `ReactNode` | Content rendered inside the fixed host. |
|
|
167
167
|
|
|
168
|
+
The host hides itself, drops out of the accessibility tree, and stops accepting
|
|
169
|
+
touches while the clip is empty. The two platforms draw the boundary where they
|
|
170
|
+
actually stop rendering, which differs below one pixel: Android emits an integer
|
|
171
|
+
`Outline`, which collapses to nothing under half a physical pixel, so an extent
|
|
172
|
+
in `(0, 0.5)` px counts as empty there; iOS masks in floats and treats only a
|
|
173
|
+
zero-or-negative extent as empty. A clip animating through that band therefore
|
|
174
|
+
turns non-interactive one frame earlier on Android — matching what each platform
|
|
175
|
+
puts on screen, which is the property worth keeping identical.
|
|
176
|
+
|
|
168
177
|
### Driver
|
|
169
178
|
|
|
170
179
|
- `useSmoothClipDriver(initialPresentation, options)` returns one hybrid driver
|
|
@@ -190,8 +199,12 @@ const gesture = Gesture.Pan()
|
|
|
190
199
|
remaining distance in one second). Every geometry channel continues with the
|
|
191
200
|
same normalized rate, so grab/release preserves the felt direction and
|
|
192
201
|
speed. `'inherit'` (the default) estimates the scalar from the last two
|
|
193
|
-
interactive samples on iOS and Android;
|
|
194
|
-
|
|
202
|
+
interactive samples on iOS and Android; the web fallback always inherits
|
|
203
|
+
zero. How long the finger has been still since that last sample scales the
|
|
204
|
+
result: full credit for one frame (16.7 ms), then a linear decay to zero at
|
|
205
|
+
100 ms. A release straight out of a drag is therefore untouched, and holding
|
|
206
|
+
still before releasing bleeds the momentum off smoothly instead of keeping
|
|
207
|
+
all of it until 99 ms and none at 101 ms. Two writes landing inside the same frame
|
|
195
208
|
(< 4 ms apart, e.g. a release-sample `from` seed right after the last drag
|
|
196
209
|
write) coalesce into one sample, and an identical re-write is ignored, so a
|
|
197
210
|
fused handoff can neither zero nor inflate the inherited velocity.
|
|
@@ -217,35 +230,50 @@ const gesture = Gesture.Pan()
|
|
|
217
230
|
write issued immediately before the handoff, so the animation starts from
|
|
218
231
|
exactly that value (pass `frames[0].presentation` for keyframes, which
|
|
219
232
|
interpolate absolutely). A non-finite `from` rejects the whole call; against
|
|
220
|
-
a held pending-animation latch
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
233
|
+
a held pending-animation latch, explicit `from` is the newer intent: it
|
|
234
|
+
cancels that latch once with `finished: false`, records/applies `from`, then
|
|
235
|
+
starts the replacement from that native value. Passive hook seeds and public
|
|
236
|
+
`set`/`setScalars` writes still leave a held latch intact. `from` behaves the
|
|
237
|
+
same on both platforms (it is driver-layer, not native): on iOS the seed
|
|
238
|
+
stops any running Core Animation and writes the model layer first; keyframes
|
|
239
|
+
then start exactly at `from`, while timing/spring sample their from-value off
|
|
240
|
+
the presentation layer — the last committed frame, at most one frame behind
|
|
241
|
+
`from` — identical to the explicit two-call pattern.
|
|
227
242
|
- `cancel()` freezes visible presentation by default. Pass `'target'` as its
|
|
228
243
|
behavior to jump to the requested endpoint.
|
|
229
244
|
- `options.onAnimationComplete` fires exactly once per animation with its ID
|
|
230
245
|
and `finished` state, including cancellation, replacement, and native-side
|
|
231
246
|
rejection (`animateTo` then returns a fresh non-zero id whose single
|
|
232
247
|
`finished: false` completion follows — key completion handling by the
|
|
233
|
-
returned id, never by `0`).
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
248
|
+
returned id, never by `0`). A valid pre-registration request carries its
|
|
249
|
+
authoritative interactive start, creates the missing driver state and
|
|
250
|
+
returns a real id. `0` — with no completion — is reserved for off-main,
|
|
251
|
+
invalid-id, or otherwise unsupported dispatch: a missing-state native request
|
|
252
|
+
with no authoritative start, and any `driver.ui.animateTo` issued after the
|
|
253
|
+
driver's hook has unmounted. (The second case is decided on the UI runtime,
|
|
254
|
+
because a destroyed driver and a not-yet-seeded one are the same missing
|
|
255
|
+
registry entry to native — accepting it would build a latch nothing can start
|
|
256
|
+
and nothing can cancel.) Removing the last displayable
|
|
257
|
+
host mid-flight does not complete the animation: the remainder is re-latched
|
|
258
|
+
even if detached or unsized peers remain registered, and the next displayable
|
|
259
|
+
host resumes it, so the completion arrives when it finishes there — or unfinished on
|
|
238
260
|
replacement, cancellation, `beginInteraction()`, or driver destruction.
|
|
239
|
-
With multiple hosts on one driver, unregistering any
|
|
240
|
-
makes the eventual completion `finished: false` —
|
|
241
|
-
|
|
261
|
+
With multiple hosts on one driver, unregistering any host that installed the
|
|
262
|
+
running animation makes the eventual completion `finished: false` — a
|
|
263
|
+
detached or un-laid-out host does not become a completion participant until
|
|
264
|
+
its native animation is actually installed. `finished: true` means every
|
|
265
|
+
installed participant ran the animation to its end.
|
|
242
266
|
- An `animateTo` issued before any host view can produce a visible frame (for
|
|
243
267
|
example from an effect in the same commit that mounts the host, or inside a
|
|
244
268
|
modal route whose subtree attaches to its window late) is held pending and
|
|
245
|
-
starts with its full duration at the first
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
269
|
+
starts with its full duration at the first moment a registered host can
|
|
270
|
+
produce a frame — registration, first layout, or window attach, whichever
|
|
271
|
+
comes last. This also covers an animation worklet that runs before the
|
|
272
|
+
hook's seed worklet: the animation creates the state and the later passive
|
|
273
|
+
seed cannot reset its ownership or active id. A pending animation owns the
|
|
274
|
+
driver: ordinary take-ownership writes (`set`, `setScalars`, the hook's seed)
|
|
275
|
+
are dropped while it is held. Replace it with another `animateTo`, override
|
|
276
|
+
it with an explicit `animation.from`, or cancel it via `beginInteraction()`
|
|
249
277
|
or `cancel()`. If no view ever becomes displayable, it survives until it is
|
|
250
278
|
replaced, cancelled, or the driver is destroyed — at which point its single
|
|
251
279
|
`finished: false` completion is delivered.
|
package/android/CMakeLists.txt
CHANGED
|
@@ -53,6 +53,11 @@ void viewBecameDisplayableAndroid(
|
|
|
53
53
|
uint64_t driverId,
|
|
54
54
|
facebook::jni::alias_ref<JSmoothClipView> view);
|
|
55
55
|
|
|
56
|
+
// Advances the registry frame loop. Called from Kotlin (SmoothClipBindings)
|
|
57
|
+
// inside Choreographer#doFrame with the frame's vsync timestamp converted to
|
|
58
|
+
// seconds; never lets a failure unwind back into doFrame.
|
|
59
|
+
void onFrameAndroid(double frameTimeS);
|
|
60
|
+
|
|
56
61
|
// Installs `global.__SmoothClipView` (worklet-callable host functions) into the
|
|
57
62
|
// JS runtime and wires native animation completion delivery through the
|
|
58
63
|
// CallInvoker. Invoked by the TurboModule BindingsInstaller.
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
#include <jsi/jsi.h>
|
|
7
7
|
|
|
8
8
|
#include <cmath>
|
|
9
|
+
#include <limits>
|
|
9
10
|
#include <memory>
|
|
10
11
|
#include <unordered_map>
|
|
11
12
|
#include <utility>
|
|
@@ -45,6 +46,18 @@ bool boolArg(const Value *args, size_t count, size_t index) {
|
|
|
45
46
|
return index < count && args[index].isBool() && args[index].getBool();
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
// Optional trailing start stamp (Reanimated-rule milliseconds captured in the
|
|
50
|
+
// issuing worklet), converted to the registry's CLOCK_MONOTONIC seconds. A
|
|
51
|
+
// missing or non-numeric argument — and the JS side's deliberate NaN fallback
|
|
52
|
+
// when the worklet globals are absent — resolves to NaN, which
|
|
53
|
+
// resolveStartStamp() treats as "no hint": nowSeconds() plus the min() anchor,
|
|
54
|
+
// exactly the pre-hint behavior.
|
|
55
|
+
double startStampArg(const Value *args, size_t count, size_t index) {
|
|
56
|
+
return index < count && args[index].isNumber()
|
|
57
|
+
? args[index].asNumber() / 1000.0
|
|
58
|
+
: std::numeric_limits<double>::quiet_NaN();
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
Presentation presentationFromArgs(const Value *args, size_t offset) {
|
|
49
62
|
return Presentation{
|
|
50
63
|
{args[offset].asNumber(),
|
|
@@ -153,15 +166,17 @@ void installBindings(
|
|
|
153
166
|
Object bindings(runtime);
|
|
154
167
|
|
|
155
168
|
setHostFunction(
|
|
156
|
-
runtime, bindings, "setClipPresentation",
|
|
169
|
+
runtime, bindings, "setClipPresentation", 10,
|
|
157
170
|
[](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
158
171
|
if (count < 9) return Value::undefined();
|
|
159
172
|
const double driverId = args[0].asNumber();
|
|
160
173
|
const Presentation presentation = presentationFromArgs(args, 1);
|
|
161
174
|
const bool takeOwnership = boolArg(args, count, 8);
|
|
175
|
+
const bool overridePendingAnimation = boolArg(args, count, 9);
|
|
162
176
|
if (validDriverId(driverId) && finitePresentation(presentation)) {
|
|
163
177
|
setPresentation(
|
|
164
|
-
static_cast<uint64_t>(driverId), presentation, takeOwnership
|
|
178
|
+
static_cast<uint64_t>(driverId), presentation, takeOwnership,
|
|
179
|
+
overridePendingAnimation);
|
|
165
180
|
}
|
|
166
181
|
return Value::undefined();
|
|
167
182
|
});
|
|
@@ -177,7 +192,7 @@ void installBindings(
|
|
|
177
192
|
});
|
|
178
193
|
|
|
179
194
|
setHostFunction(
|
|
180
|
-
runtime, bindings, "animateTiming",
|
|
195
|
+
runtime, bindings, "animateTiming", 23,
|
|
181
196
|
[](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
182
197
|
if (count < 22) return Value(0);
|
|
183
198
|
const double driverId = args[0].asNumber();
|
|
@@ -194,13 +209,13 @@ void installBindings(
|
|
|
194
209
|
}
|
|
195
210
|
return Value(animateTiming(
|
|
196
211
|
static_cast<uint64_t>(driverId),
|
|
197
|
-
{boolArg(args, count, 1), start},
|
|
212
|
+
{boolArg(args, count, 1), start, startStampArg(args, count, 22)},
|
|
198
213
|
target,
|
|
199
214
|
animation));
|
|
200
215
|
});
|
|
201
216
|
|
|
202
217
|
setHostFunction(
|
|
203
|
-
runtime, bindings, "animateSpring",
|
|
218
|
+
runtime, bindings, "animateSpring", 23,
|
|
204
219
|
[](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
205
220
|
if (count < 22) return Value(0);
|
|
206
221
|
const double driverId = args[0].asNumber();
|
|
@@ -221,13 +236,13 @@ void installBindings(
|
|
|
221
236
|
}
|
|
222
237
|
return Value(animateSpring(
|
|
223
238
|
static_cast<uint64_t>(driverId),
|
|
224
|
-
{boolArg(args, count, 1), start},
|
|
239
|
+
{boolArg(args, count, 1), start, startStampArg(args, count, 22)},
|
|
225
240
|
target,
|
|
226
241
|
animation));
|
|
227
242
|
});
|
|
228
243
|
|
|
229
244
|
setHostFunction(
|
|
230
|
-
runtime, bindings, "animateKeyframes",
|
|
245
|
+
runtime, bindings, "animateKeyframes", 20,
|
|
231
246
|
[](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
232
247
|
if (count < 19 || !args[17].isObject() ||
|
|
233
248
|
!args[17].getObject(rt).isArray(rt)) {
|
|
@@ -270,7 +285,7 @@ void installBindings(
|
|
|
270
285
|
}
|
|
271
286
|
return Value(animateKeyframes(
|
|
272
287
|
static_cast<uint64_t>(driverId),
|
|
273
|
-
{boolArg(args, count, 1), start},
|
|
288
|
+
{boolArg(args, count, 1), start, startStampArg(args, count, 19)},
|
|
274
289
|
target,
|
|
275
290
|
durationMs,
|
|
276
291
|
std::move(keyframes),
|
|
@@ -419,6 +434,10 @@ void nativeInvalidate(jni::alias_ref<jni::JObject>) {
|
|
|
419
434
|
smoothclip::invalidateBindings();
|
|
420
435
|
}
|
|
421
436
|
|
|
437
|
+
void nativeOnFrame(jni::alias_ref<jni::JObject>, jlong frameTimeNanos) {
|
|
438
|
+
smoothclip::onFrameAndroid(static_cast<double>(frameTimeNanos) / 1e9);
|
|
439
|
+
}
|
|
440
|
+
|
|
422
441
|
} // namespace
|
|
423
442
|
|
|
424
443
|
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
|
|
@@ -434,6 +453,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
|
|
|
434
453
|
makeNativeMethod(
|
|
435
454
|
"nativeViewBecameDisplayable", nativeViewBecameDisplayable),
|
|
436
455
|
makeNativeMethod("nativeInvalidate", nativeInvalidate),
|
|
456
|
+
makeNativeMethod("nativeOnFrame", nativeOnFrame),
|
|
437
457
|
});
|
|
438
458
|
});
|
|
439
459
|
}
|