@rafaelaugusto/jolt 0.1.0-beta.14 → 0.1.0-beta.16

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.
Files changed (91) hide show
  1. package/README.md +11 -4
  2. package/dist/drag.js +1 -1
  3. package/dist/drag.js.map +1 -1
  4. package/dist/driver/android.d.ts.map +1 -1
  5. package/dist/driver/android.js +2 -0
  6. package/dist/driver/android.js.map +1 -1
  7. package/dist/driver/ios-scroll-delegate.d.ts +4 -0
  8. package/dist/driver/ios-scroll-delegate.d.ts.map +1 -0
  9. package/dist/driver/ios-scroll-delegate.js +26 -0
  10. package/dist/driver/ios-scroll-delegate.js.map +1 -0
  11. package/dist/driver/ios.d.ts.map +1 -1
  12. package/dist/driver/ios.js +2 -0
  13. package/dist/driver/ios.js.map +1 -1
  14. package/dist/gesture-native.d.ts +27 -0
  15. package/dist/gesture-native.d.ts.map +1 -0
  16. package/dist/gesture-native.js +55 -0
  17. package/dist/gesture-native.js.map +1 -0
  18. package/dist/gestures/index.d.ts +65 -0
  19. package/dist/gestures/index.d.ts.map +1 -0
  20. package/dist/gestures/index.js +251 -0
  21. package/dist/gestures/index.js.map +1 -0
  22. package/dist/index.d.ts +4 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +2 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/live-spring-driver.d.ts +52 -0
  27. package/dist/live-spring-driver.d.ts.map +1 -0
  28. package/dist/live-spring-driver.js +104 -0
  29. package/dist/live-spring-driver.js.map +1 -0
  30. package/dist/mixer.d.ts +6 -0
  31. package/dist/mixer.d.ts.map +1 -1
  32. package/dist/mixer.js +41 -1
  33. package/dist/mixer.js.map +1 -1
  34. package/dist/motion-scope.d.ts +2 -0
  35. package/dist/motion-scope.d.ts.map +1 -1
  36. package/dist/motion-scope.js +6 -0
  37. package/dist/motion-scope.js.map +1 -1
  38. package/dist/motion-value.d.ts +34 -2
  39. package/dist/motion-value.d.ts.map +1 -1
  40. package/dist/motion-value.js +277 -10
  41. package/dist/motion-value.js.map +1 -1
  42. package/dist/native/index.d.ts +1 -0
  43. package/dist/native/index.d.ts.map +1 -1
  44. package/dist/native/index.js +1 -0
  45. package/dist/native/index.js.map +1 -1
  46. package/dist/scene-camera.d.ts +5 -0
  47. package/dist/scene-camera.d.ts.map +1 -0
  48. package/dist/scene-camera.js +23 -0
  49. package/dist/scene-camera.js.map +1 -0
  50. package/dist/size-layout.d.ts +19 -0
  51. package/dist/size-layout.d.ts.map +1 -0
  52. package/dist/size-layout.js +216 -0
  53. package/dist/size-layout.js.map +1 -0
  54. package/dist/size.d.ts +19 -0
  55. package/dist/size.d.ts.map +1 -0
  56. package/dist/size.js +251 -0
  57. package/dist/size.js.map +1 -0
  58. package/dist/value-observers.d.ts +3 -1
  59. package/dist/value-observers.d.ts.map +1 -1
  60. package/dist/value-observers.js +10 -2
  61. package/dist/value-observers.js.map +1 -1
  62. package/package.json +5 -1
  63. package/platforms/android/include.gradle +9 -0
  64. package/platforms/android/java/org/rafaelaugusto/jolt/GestureSession.java +193 -0
  65. package/platforms/android/java/org/rafaelaugusto/jolt/GestureSurface.java +53 -0
  66. package/platforms/android/java/org/rafaelaugusto/jolt/LiveSpring.java +270 -0
  67. package/platforms/android/java/org/rafaelaugusto/jolt/SpringState.java +39 -0
  68. package/platforms/ios/src/JoltGestureSession.h +17 -0
  69. package/platforms/ios/src/JoltGestureSession.m +217 -0
  70. package/platforms/ios/src/JoltLiveSpring.h +28 -0
  71. package/platforms/ios/src/JoltLiveSpring.m +236 -0
  72. package/platforms/ios/src/JoltSpringState.h +35 -0
  73. package/platforms/ios/src/module.modulemap +2 -0
  74. package/src/android-plugin.d.ts +59 -0
  75. package/src/drag.ts +1 -1
  76. package/src/driver/android.ts +2 -0
  77. package/src/driver/ios-scroll-delegate.ts +23 -0
  78. package/src/driver/ios.ts +2 -0
  79. package/src/gesture-native.ts +124 -0
  80. package/src/gestures/index.ts +350 -0
  81. package/src/index.ts +5 -1
  82. package/src/ios-plugin.d.ts +61 -0
  83. package/src/live-spring-driver.ts +181 -0
  84. package/src/mixer.ts +43 -1
  85. package/src/motion-scope.ts +14 -0
  86. package/src/motion-value.ts +318 -11
  87. package/src/native/index.ts +1 -0
  88. package/src/scene-camera.ts +30 -0
  89. package/src/size-layout.ts +237 -0
  90. package/src/size.ts +302 -0
  91. package/src/value-observers.ts +12 -2
@@ -0,0 +1,236 @@
1
+ #import "JoltLiveSpring.h"
2
+ #import "JoltSpringState.h"
3
+ #import <QuartzCore/QuartzCore.h>
4
+
5
+ static id decode(NSString *json) {
6
+ return [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
7
+ }
8
+ static double mapped(double value, NSArray *mappings) {
9
+ for (NSDictionary *mapping in mappings) {
10
+ NSArray *input = mapping[@"input"], *output = mapping[@"output"];
11
+ BOOL clamp = [mapping[@"clamp"] boolValue];
12
+ if (clamp && value <= [input.firstObject doubleValue]) { value = [output.firstObject doubleValue]; continue; }
13
+ if (clamp && value >= [input.lastObject doubleValue]) { value = [output.lastObject doubleValue]; continue; }
14
+ NSUInteger index = 0;
15
+ while (index < input.count - 2 && value > [input[index + 1] doubleValue]) index++;
16
+ double span = [input[index + 1] doubleValue] - [input[index] doubleValue];
17
+ double fraction = span > 0 ? (value - [input[index] doubleValue]) / span : value < [input[index] doubleValue] ? 0 : 1;
18
+ value = [output[index] doubleValue] + ([output[index + 1] doubleValue] - [output[index] doubleValue]) * fraction;
19
+ }
20
+ return value;
21
+ }
22
+ static BOOL inputRunning(NSDictionary *input, double anchor, double now) {
23
+ double repeat = [input[@"repeat"] doubleValue];
24
+ return [input[@"running"] boolValue] && (repeat < 0 || now - anchor + [input[@"elapsed"] doubleValue] <
25
+ [input[@"delay"] doubleValue] + [input[@"duration"] doubleValue] * (repeat + 1));
26
+ }
27
+ static double inputValue(NSDictionary *input, double anchor, double now) {
28
+ NSArray *values = input[@"values"], *times = input[@"times"];
29
+ if (![input[@"running"] boolValue] || values.count < 2) return [input[@"value"] doubleValue];
30
+ if (!inputRunning(input, anchor, now)) return [input[@"to"] doubleValue];
31
+ double time = MAX(0, now - anchor + [input[@"elapsed"] doubleValue] - [input[@"delay"] doubleValue]);
32
+ double duration = MAX(1, [input[@"duration"] doubleValue]);
33
+ double iteration = floor(time / duration), progress = fmod(time, duration) / duration;
34
+ if ([input[@"reverse"] boolValue] && fmod(iteration, 2) >= 1) progress = 1 - progress;
35
+ NSUInteger lo = 0, hi = times.count - 1;
36
+ while (lo + 1 < hi) { NSUInteger mid = (lo + hi) / 2; if ([times[mid] doubleValue] <= progress) lo = mid; else hi = mid; }
37
+ double start = [times[lo] doubleValue], end = [times[hi] doubleValue];
38
+ double fraction = end > start ? (progress - start) / (end - start) : 1;
39
+ return [values[lo] doubleValue] + ([values[hi] doubleValue] - [values[lo] doubleValue]) * MAX(0, MIN(1, fraction));
40
+ }
41
+
42
+ @interface JoltLiveSpring () {
43
+ JoltSpringState _state;
44
+ }
45
+ @property(nonatomic) double stiffness, damping, mass, restDelta, restSpeed;
46
+ @property(nonatomic, readwrite) BOOL moving;
47
+ @property(nonatomic) BOOL reduced, paused, disposed, direct, inputHeld;
48
+ @property(nonatomic) double targetValue;
49
+ @property(nonatomic, nullable) JoltLiveSpring *source;
50
+ @property(nonatomic) NSArray *sourceMaps;
51
+ @property(nonatomic) NSDictionary *input;
52
+ @property(nonatomic) double anchor;
53
+ @property(nonatomic) NSMutableArray<JoltLiveSpringBinding *> *bindings;
54
+ - (void)step:(double)now seconds:(double)seconds;
55
+ - (void)apply;
56
+ - (void)wake;
57
+ @end
58
+ @interface JoltLiveSpringBinding ()
59
+ @property(nonatomic, weak) UIView *view;
60
+ @property(nonatomic, weak) JoltLiveSpring *node;
61
+ @property(nonatomic) NSString *property;
62
+ @property(nonatomic) NSArray *mappings;
63
+ @property(nonatomic) BOOL stopped;
64
+ @property(nonatomic) NSArray *sampledLayers;
65
+ @property(nonatomic) double layerAnchor;
66
+ - (BOOL)running:(double)now;
67
+ - (void)apply;
68
+ @end
69
+
70
+ @implementation JoltLiveSpring
71
+ static NSMutableArray<JoltLiveSpring *> *nodes;
72
+ static CADisplayLink *displayLink;
73
+ static double previousFrame;
74
+ + (void)initialize {
75
+ if (self == [JoltLiveSpring class]) nodes = [NSMutableArray new];
76
+ }
77
+ + (void)schedule {
78
+ if (displayLink || !nodes.count) return;
79
+ displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
80
+ if (@available(iOS 15.0, *)) {
81
+ float maximum = MIN(120, UIScreen.mainScreen.maximumFramesPerSecond);
82
+ displayLink.preferredFrameRateRange = CAFrameRateRangeMake(MIN(80, maximum), maximum, maximum);
83
+ }
84
+ [displayLink addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
85
+ }
86
+ + (void)tick:(CADisplayLink *)link {
87
+ double now = link.timestamp * 1000;
88
+ double remaining = previousFrame == 0 ? 1.0 / 120 : MIN(.064, MAX(0, link.timestamp - previousFrame));
89
+ previousFrame = link.timestamp;
90
+ while (remaining > 1e-9) {
91
+ double dt = MIN(remaining, 1.0 / 240);
92
+ remaining -= dt;
93
+ for (JoltLiveSpring *node in nodes) [node step:now - remaining * 1000 seconds:dt];
94
+ }
95
+ BOOL active = NO;
96
+ [CATransaction begin]; [CATransaction setDisableActions:YES];
97
+ for (JoltLiveSpring *node in nodes) {
98
+ [node apply]; active |= node.moving;
99
+ for (JoltLiveSpringBinding *binding in node.bindings) active |= [binding running:now];
100
+ }
101
+ [CATransaction commit];
102
+ if (!active) { [displayLink invalidate]; displayLink = nil; previousFrame = 0; }
103
+ }
104
+ + (instancetype)create:(double)initial stiffness:(double)stiffness damping:(double)damping mass:(double)mass
105
+ restDelta:(double)restDelta restSpeed:(double)restSpeed reduced:(BOOL)reduced {
106
+ JoltLiveSpring *node = [self new];
107
+ node->_state = (JoltSpringState){initial, 0};
108
+ node.stiffness = stiffness; node.damping = damping; node.mass = mass;
109
+ node.restDelta = restDelta; node.restSpeed = restSpeed; node->_reduced = reduced;
110
+ node.direct = YES; node.targetValue = initial;
111
+ node.bindings = [NSMutableArray new]; node.sourceMaps = @[];
112
+ [nodes addObject:node];
113
+ return node;
114
+ }
115
+ - (double)position { return _state.position; }
116
+ - (double)velocity { return _state.velocity; }
117
+ + (NSUInteger)activeCount { return displayLink ? 1 : 0; }
118
+ + (NSUInteger)nodeCount { return nodes.count; }
119
+ - (void)follow:(JoltLiveSpring *)source mappings:(NSString *)mappings {
120
+ if (self.disposed || source.disposed || [nodes indexOfObject:source] >= [nodes indexOfObject:self])
121
+ [NSException raise:NSInvalidArgumentException format:@"A spring can only follow an existing live node"];
122
+ self.sourceMaps = decode(mappings); self.source = source; [self wake];
123
+ }
124
+ - (void)updateInput:(NSString *)snapshot {
125
+ if (self.disposed) return;
126
+ self.input = decode(snapshot); self.direct = NO; self.anchor = CACurrentMediaTime() * 1000; [self wake];
127
+ }
128
+ - (void)setTarget:(double)value {
129
+ if (self.disposed) return;
130
+ self.direct = YES; self.targetValue = value; self.input = nil; [self wake];
131
+ }
132
+ - (void)holdInput:(double)value velocity:(double)velocity {
133
+ if (self.disposed) return;
134
+ self.inputHeld = YES; self.direct = YES; self.targetValue = value; self.input = nil;
135
+ _state = (JoltSpringState){value, velocity}; self.moving = YES;
136
+ [CATransaction begin]; [CATransaction setDisableActions:YES]; [self apply]; [CATransaction commit];
137
+ [JoltLiveSpring schedule];
138
+ }
139
+ - (void)releaseInput:(double)target velocity:(double)velocity {
140
+ if (self.disposed) return;
141
+ self.inputHeld = NO; _state.velocity = velocity; [self setTarget:target];
142
+ }
143
+ - (void)setReduced:(BOOL)value { _reduced = value; [self wake]; }
144
+ - (void)setPaused:(BOOL)value {
145
+ _paused = value;
146
+ if (value) self.moving = NO; else [self wake];
147
+ }
148
+ - (void)wake {
149
+ if (self.disposed || self.paused) return;
150
+ self.moving = YES;
151
+ if (self.reduced) {
152
+ [self step:CACurrentMediaTime() * 1000 seconds:0];
153
+ [CATransaction begin]; [CATransaction setDisableActions:YES]; [self apply]; [CATransaction commit];
154
+ }
155
+ [JoltLiveSpring schedule];
156
+ }
157
+ - (void)step:(double)now seconds:(double)seconds {
158
+ if (self.disposed || self.paused || self.inputHeld) return;
159
+ double target = self.source ? mapped(self.source.position, self.sourceMaps) : (self.direct ? self.targetValue : inputValue(self.input, self.anchor, now));
160
+ BOOL inputMoving = self.source ? self.source.moving : (!self.direct && inputRunning(self.input, self.anchor, now));
161
+ if (self.reduced) _state = (JoltSpringState){target, 0};
162
+ else _state = JoltSpringAdvance(_state, target, self.stiffness, self.damping, self.mass, seconds);
163
+ BOOL rest = fabs(_state.position - target) <= self.restDelta && fabs(_state.velocity) <= self.restSpeed;
164
+ if (rest && !inputMoving) _state = (JoltSpringState){target, 0};
165
+ self.moving = inputMoving || !rest;
166
+ }
167
+ - (JoltLiveSpringBinding *)bind:(UIView *)view property:(NSString *)property mappings:(NSString *)mappings {
168
+ if (self.disposed) [NSException raise:NSInvalidArgumentException format:@"Spring disposed"];
169
+ JoltLiveSpringBinding *binding = [JoltLiveSpringBinding new];
170
+ binding.node = self; binding.view = view; binding.property = property; binding.mappings = decode(mappings);
171
+ [self.bindings addObject:binding];
172
+ [CATransaction begin]; [CATransaction setDisableActions:YES]; [binding apply]; [CATransaction commit];
173
+ return binding;
174
+ }
175
+ - (void)apply {
176
+ for (NSInteger i = self.bindings.count - 1; i >= 0; i--) {
177
+ JoltLiveSpringBinding *binding = self.bindings[i];
178
+ if (!binding.view) [self.bindings removeObjectAtIndex:i]; else [binding apply];
179
+ }
180
+ }
181
+ + (void)sync:(UIView *)view {
182
+ [CATransaction begin]; [CATransaction setDisableActions:YES];
183
+ for (JoltLiveSpring *node in nodes)
184
+ for (JoltLiveSpringBinding *binding in node.bindings)
185
+ if (binding.view == view) [binding apply];
186
+ [CATransaction commit];
187
+ }
188
+ - (void)dispose {
189
+ if (self.disposed) return;
190
+ self.disposed = YES; self.moving = NO; [self.bindings removeAllObjects]; [nodes removeObject:self];
191
+ for (JoltLiveSpring *child in nodes) if (child.source == self) {
192
+ child.source = nil; [child setTarget:mapped(self.position, child.sourceMaps)];
193
+ }
194
+ self.source = nil;
195
+ if (!nodes.count) { [displayLink invalidate]; displayLink = nil; previousFrame = 0; }
196
+ }
197
+ @end
198
+
199
+ @implementation JoltLiveSpringBinding
200
+ - (double)value { return mapped(self.node.position, self.mappings); }
201
+ - (void)setLayers:(NSString *)inputs {
202
+ self.sampledLayers = decode(inputs); self.layerAnchor = CACurrentMediaTime() * 1000;
203
+ [CATransaction begin]; [CATransaction setDisableActions:YES]; [self apply]; [CATransaction commit];
204
+ [JoltLiveSpring schedule];
205
+ }
206
+ - (BOOL)running:(double)now {
207
+ for (NSDictionary *layer in self.sampledLayers) if (inputRunning(layer, self.layerAnchor, now)) return YES;
208
+ return NO;
209
+ }
210
+ - (void)cancel { self.stopped = YES; [self.node.bindings removeObject:self]; }
211
+ - (void)apply {
212
+ UIView *view = self.view;
213
+ if (self.stopped || !view.window) return;
214
+ NSString *key = self.property;
215
+ double value = self.value;
216
+ BOOL multiply = [key isEqualToString:@"opacity"] || [key hasPrefix:@"scale"];
217
+ double now = CACurrentMediaTime() * 1000;
218
+ for (NSDictionary *layer in self.sampledLayers) {
219
+ double part = inputValue(layer, self.layerAnchor, now);
220
+ value = multiply ? value * part : value + part;
221
+ }
222
+ if ([key isEqualToString:@"opacity"]) { view.alpha = MAX(0, MIN(1, value)); return; }
223
+ if ([key isEqualToString:@"x"]) key = @"transform.translation.x";
224
+ else if ([key isEqualToString:@"y"]) key = @"transform.translation.y";
225
+ else if ([key isEqualToString:@"scaleX"]) key = @"transform.scale.x";
226
+ else if ([key isEqualToString:@"scaleY"]) key = @"transform.scale.y";
227
+ else if ([key isEqualToString:@"rotate"]) { key = @"transform.rotation.z"; value *= M_PI / 180; }
228
+ else if ([key isEqualToString:@"rotateX"]) { key = @"transform.rotation.x"; value *= M_PI / 180; }
229
+ else if ([key isEqualToString:@"rotateY"]) { key = @"transform.rotation.y"; value *= M_PI / 180; }
230
+ [view.layer setValue:@(value) forKeyPath:key];
231
+ if ([view respondsToSelector:NSSelectorFromString(@"outerShadowContainerLayer")]) {
232
+ CALayer *shadow = [view valueForKey:@"outerShadowContainerLayer"];
233
+ [shadow setValue:@(value) forKeyPath:key];
234
+ }
235
+ }
236
+ @end
@@ -0,0 +1,35 @@
1
+ #ifndef JOLT_SPRING_STATE_H
2
+ #define JOLT_SPRING_STATE_H
3
+ #include <math.h>
4
+
5
+ /* Exact constant-target solution. Position and velocity survive every retarget. */
6
+ typedef struct { double position, velocity; } JoltSpringState;
7
+ static inline JoltSpringState JoltSpringAdvance(JoltSpringState state, double target,
8
+ double stiffness, double damping, double mass, double seconds) {
9
+ if (seconds <= 0) return state;
10
+ const double x = state.position - target;
11
+ const double w = sqrt(stiffness / mass);
12
+ const double z = damping / (2 * sqrt(stiffness * mass));
13
+ double next, speed;
14
+ if (fabs(z - 1) < 1e-6) {
15
+ const double b = state.velocity + w * x, e = exp(-w * seconds);
16
+ next = e * (x + b * seconds);
17
+ speed = e * (b - w * (x + b * seconds));
18
+ } else if (z < 1) {
19
+ const double a = z * w, d = w * sqrt(1 - z * z);
20
+ const double b = (state.velocity + a * x) / d;
21
+ const double c = cos(d * seconds), s = sin(d * seconds), e = exp(-a * seconds);
22
+ next = e * (x * c + b * s);
23
+ speed = e * ((-a * x + b * d) * c + (-a * b - x * d) * s);
24
+ } else {
25
+ const double q = sqrt(z * z - 1);
26
+ /* Rationalized slow root avoids catastrophic cancellation at high damping. */
27
+ const double r1 = -w / (z + q), r2 = -w * (z + q);
28
+ const double c1 = (state.velocity - r2 * x) / (r1 - r2), c2 = x - c1;
29
+ const double e1 = exp(r1 * seconds), e2 = exp(r2 * seconds);
30
+ next = c1 * e1 + c2 * e2;
31
+ speed = c1 * r1 * e1 + c2 * r2 * e2;
32
+ }
33
+ return (JoltSpringState){target + next, speed};
34
+ }
35
+ #endif
@@ -2,5 +2,7 @@ module JoltMotion {
2
2
  header "JoltImageEffect.h"
3
3
  header "JoltPhotoZoom.h"
4
4
  header "JoltInteractiveAnimation.h"
5
+ header "JoltLiveSpring.h"
6
+ header "JoltGestureSession.h"
5
7
  export *
6
8
  }
@@ -1,4 +1,63 @@
1
1
  declare namespace org.rafaelaugusto.jolt {
2
+ class GestureSurface extends org.nativescript.widgets.GridLayout {
3
+ constructor(context: android.content.Context)
4
+ }
5
+ class GestureSession {
6
+ constructor(
7
+ surface: GestureSurface,
8
+ scroll: android.view.View | null,
9
+ x: LiveSpring,
10
+ y: LiveSpring,
11
+ scale: LiveSpring,
12
+ rotation: LiveSpring,
13
+ options: string,
14
+ listener: GestureSession.Listener,
15
+ )
16
+ isActive(): boolean
17
+ cancel(): void
18
+ setEnabled(value: boolean): void
19
+ settle(x: number, y: number, scale: number, rotation: number, animated: boolean): void
20
+ updateBounds(left: number, right: number, top: number, bottom: number): void
21
+ dispose(): void
22
+ }
23
+ namespace GestureSession {
24
+ class Listener {
25
+ constructor(implementation: {
26
+ onState(state: number, velocityX: number, velocityY: number): void
27
+ })
28
+ }
29
+ }
30
+ class LiveSpring {
31
+ constructor(
32
+ initial: number,
33
+ stiffness: number,
34
+ damping: number,
35
+ mass: number,
36
+ restDelta: number,
37
+ restSpeed: number,
38
+ reduced: boolean,
39
+ )
40
+ static activeCount(): number
41
+ static nodeCount(): number
42
+ static sync(view: android.view.View): void
43
+ getPosition(): number
44
+ getVelocity(): number
45
+ isMoving(): boolean
46
+ follow(source: LiveSpring, mappings: string): void
47
+ updateInput(snapshot: string): void
48
+ setTarget(value: number): void
49
+ setReduced(value: boolean): void
50
+ setPaused(value: boolean): void
51
+ bind(view: android.view.View, property: string, mappings: string): LiveSpring.Binding
52
+ dispose(): void
53
+ }
54
+ namespace LiveSpring {
55
+ class Binding {
56
+ getValue(): number
57
+ setLayers(inputs: string): void
58
+ cancel(): void
59
+ }
60
+ }
2
61
  class ParticleEmitter {
3
62
  constructor(
4
63
  target: android.view.View,
package/src/drag.ts CHANGED
@@ -428,7 +428,7 @@ class DragController implements Drag {
428
428
  const [axis] = this.config.axes
429
429
  const value = this.config.value
430
430
  const animation = value
431
- ? value.play(createTrack(axis!, [event[axis!]], plans[0]!.transition, 0))
431
+ ? value.playTrack(createTrack(axis!, [event[axis!]], plans[0]!.transition, 0))
432
432
  : new ViewAnimation(this.view, plans)
433
433
  this.release = animation
434
434
  void animation.finished.then((finished) => {
@@ -4,6 +4,7 @@ import {
4
4
  type AndroidPlayback,
5
5
  } from './android-playback.js'
6
6
  import { isDecorationProperty } from '../decoration.js'
7
+ import { syncLiveSprings } from '../live-spring-driver.js'
7
8
  import {
8
9
  syncAndroidDecoration,
9
10
  playAndroidDecoration,
@@ -188,6 +189,7 @@ export function createAndroidDriver(): Driver {
188
189
  sync(view) {
189
190
  syncAndroidDecoration(view)
190
191
  syncAndroidColor(view)
192
+ syncLiveSprings(view)
191
193
  },
192
194
 
193
195
  elapsed: (handle) => (handle as Handle).elapsed(),
@@ -0,0 +1,23 @@
1
+ import type { ScrollView } from '@nativescript/core'
2
+
3
+ /** Preserve Core's lazy scroll listener attach/detach without adding a JS scroll observer. */
4
+ export function retainScrollDelegate(scroll: ScrollView, refresh: () => void) {
5
+ const host = scroll as ScrollView & { attachNative(): void; detachNative(): void }
6
+ const restore: (() => void)[] = []
7
+ for (const key of ['attachNative', 'detachNative'] as const) {
8
+ const original = host[key]
9
+ if (typeof original !== 'function') continue
10
+ const descriptor = Object.getOwnPropertyDescriptor(host, key)
11
+ const wrapper = function (this: typeof host) {
12
+ original.call(this)
13
+ refresh()
14
+ }
15
+ host[key] = wrapper
16
+ restore.push(() => {
17
+ if (host[key] !== wrapper) return
18
+ if (descriptor) Object.defineProperty(host, key, descriptor)
19
+ else delete (host as Partial<typeof host>)[key]
20
+ })
21
+ }
22
+ return () => restore.forEach((action) => action())
23
+ }
package/src/driver/ios.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { iosPlayback } from './ios-playback.js'
2
+ import { syncLiveSprings } from '../live-spring-driver.js'
2
3
  import { isDecorationProperty } from '../decoration.js'
3
4
  import {
4
5
  playIOSDecoration,
@@ -197,6 +198,7 @@ export function createIOSDriver(): Driver {
197
198
  view.nativeViewProtected,
198
199
  excluding ? (keyPaths[excluding as NumericProperty] ?? null) : null,
199
200
  )
201
+ syncLiveSprings(view)
200
202
  },
201
203
 
202
204
  presented(view, property) {
@@ -0,0 +1,124 @@
1
+ import { isIOS, type View, type ScrollView } from '@nativescript/core'
2
+ import { liveValueInput, type DerivedValue } from './motion-value.js'
3
+ import { nativeLiveNode } from './live-spring-driver.js'
4
+ import { retainScrollDelegate } from './driver/ios-scroll-delegate.js'
5
+
6
+ export interface GestureSettings {
7
+ axis: number
8
+ threshold: number
9
+ failOffset: number
10
+ pinch: boolean
11
+ rotate: boolean
12
+ tap: boolean
13
+ doubleTap: boolean
14
+ minX: number
15
+ maxX: number
16
+ minY: number
17
+ maxY: number
18
+ minScale: number
19
+ maxScale: number
20
+ }
21
+ export interface GestureDriver {
22
+ readonly active: boolean
23
+ cancel(): void
24
+ enabled(value: boolean): void
25
+ settle(values: readonly number[], animated: boolean): void
26
+ bounds(settings: GestureSettings): void
27
+ dispose(): void
28
+ }
29
+ export function createGestureDriver(
30
+ surface: View,
31
+ scroll: ScrollView | undefined,
32
+ values: readonly DerivedValue[],
33
+ options: GestureSettings,
34
+ callback: (state: number, vx: number, vy: number) => void,
35
+ ): GestureDriver {
36
+ const inputs = values.map(liveValueInput)
37
+ const nodes = inputs.map((input) => nativeLiveNode(input.node))
38
+ const changed = (state: number, vx: number, vy: number) => {
39
+ for (const input of inputs) input.changed()
40
+ callback(state, vx, vy)
41
+ }
42
+ const json = JSON.stringify(options)
43
+ if (isIOS) {
44
+ if (typeof JoltGestureSession === 'undefined')
45
+ throw new Error('Rebuild the native app to use gestures().')
46
+ const [x, y, scale, rotation] = nodes as [
47
+ JoltLiveSpring,
48
+ JoltLiveSpring,
49
+ JoltLiveSpring,
50
+ JoltLiveSpring,
51
+ ]
52
+ const native = JoltGestureSession.attachScrollXYScaleRotationOptionsOnState(
53
+ surface.nativeViewProtected,
54
+ scroll?.nativeViewProtected ?? null,
55
+ x,
56
+ y,
57
+ scale,
58
+ rotation,
59
+ json,
60
+ changed,
61
+ )
62
+ const releaseDelegate = scroll
63
+ ? retainScrollDelegate(scroll, () => native.refreshScrollDelegate())
64
+ : () => {}
65
+ return {
66
+ get active() {
67
+ return native.active
68
+ },
69
+ cancel: () => native.cancel(),
70
+ enabled: (value) => native.setEnabled(value),
71
+ settle: (values, animated) => {
72
+ native.settleXYScaleRotationAnimated(
73
+ values[0]!,
74
+ values[1]!,
75
+ values[2]!,
76
+ values[3]!,
77
+ animated,
78
+ )
79
+ inputs.forEach((input) => input.changed())
80
+ },
81
+ bounds: (settings) =>
82
+ native.updateLeftRightTopBottom(
83
+ settings.minX,
84
+ settings.maxX,
85
+ settings.minY,
86
+ settings.maxY,
87
+ ),
88
+ dispose: () => {
89
+ releaseDelegate()
90
+ native.dispose()
91
+ },
92
+ }
93
+ }
94
+ const [x, y, scale, rotation] = nodes as [
95
+ org.rafaelaugusto.jolt.LiveSpring,
96
+ org.rafaelaugusto.jolt.LiveSpring,
97
+ org.rafaelaugusto.jolt.LiveSpring,
98
+ org.rafaelaugusto.jolt.LiveSpring,
99
+ ]
100
+ const native = new org.rafaelaugusto.jolt.GestureSession(
101
+ surface.nativeViewProtected,
102
+ scroll?.nativeViewProtected ?? null,
103
+ x,
104
+ y,
105
+ scale,
106
+ rotation,
107
+ json,
108
+ new org.rafaelaugusto.jolt.GestureSession.Listener({ onState: changed }),
109
+ )
110
+ return {
111
+ get active() {
112
+ return native.isActive()
113
+ },
114
+ cancel: () => native.cancel(),
115
+ enabled: (value) => native.setEnabled(value),
116
+ settle: (values, animated) => {
117
+ native.settle(values[0]!, values[1]!, values[2]!, values[3]!, animated)
118
+ inputs.forEach((input) => input.changed())
119
+ },
120
+ bounds: (settings) =>
121
+ native.updateBounds(settings.minX, settings.maxX, settings.minY, settings.maxY),
122
+ dispose: () => native.dispose(),
123
+ }
124
+ }