@rafaelaugusto/jolt 0.1.0-rc.2 → 0.1.0-rc.4
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 +4 -4
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +20 -5
- package/dist/animate.js.map +1 -1
- package/dist/bottom-sheet/geometry.d.ts +7 -2
- package/dist/bottom-sheet/geometry.d.ts.map +1 -1
- package/dist/bottom-sheet/geometry.js +7 -3
- package/dist/bottom-sheet/geometry.js.map +1 -1
- package/dist/bottom-sheet/index.d.ts +10 -0
- package/dist/bottom-sheet/index.d.ts.map +1 -1
- package/dist/bottom-sheet/index.js +72 -22
- package/dist/bottom-sheet/index.js.map +1 -1
- package/dist/bottom-sheet/native.d.ts +5 -0
- package/dist/bottom-sheet/native.d.ts.map +1 -1
- package/dist/bottom-sheet/native.js +6 -0
- package/dist/bottom-sheet/native.js.map +1 -1
- package/dist/driver/android.d.ts.map +1 -1
- package/dist/driver/android.js +15 -7
- package/dist/driver/android.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interactive-navigation/index.d.ts +2 -0
- package/dist/interactive-navigation/index.d.ts.map +1 -1
- package/dist/interactive-navigation/index.js +1 -0
- package/dist/interactive-navigation/index.js.map +1 -1
- package/dist/interactive-navigation/modal.d.ts +34 -0
- package/dist/interactive-navigation/modal.d.ts.map +1 -0
- package/dist/interactive-navigation/modal.js +253 -0
- package/dist/interactive-navigation/modal.js.map +1 -0
- package/dist/live-spring-driver.d.ts +5 -0
- package/dist/live-spring-driver.d.ts.map +1 -1
- package/dist/live-spring-driver.js +13 -0
- package/dist/live-spring-driver.js.map +1 -1
- package/dist/mixer.d.ts +7 -1
- package/dist/mixer.d.ts.map +1 -1
- package/dist/mixer.js +57 -22
- package/dist/mixer.js.map +1 -1
- package/dist/motion-value.d.ts +12 -1
- package/dist/motion-value.d.ts.map +1 -1
- package/dist/motion-value.js +115 -7
- package/dist/motion-value.js.map +1 -1
- package/dist/scroll-header/geometry.d.ts +1 -1
- package/dist/scroll-header/geometry.d.ts.map +1 -1
- package/dist/scroll-header/geometry.js +7 -2
- package/dist/scroll-header/geometry.js.map +1 -1
- package/dist/scroll-header/index.d.ts.map +1 -1
- package/dist/scroll-header/index.js +13 -0
- package/dist/scroll-header/index.js.map +1 -1
- package/dist/shapes/index.d.ts +1 -1
- package/dist/shapes/index.d.ts.map +1 -1
- package/dist/shapes/index.js +23 -9
- package/dist/shapes/index.js.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/value-track.d.ts +9 -0
- package/dist/value-track.d.ts.map +1 -1
- package/dist/value-track.js +28 -16
- package/dist/value-track.js.map +1 -1
- package/package.json +1 -1
- package/platforms/android/java/org/rafaelaugusto/jolt/BlurEffect.java +14 -2
- package/platforms/android/java/org/rafaelaugusto/jolt/GradientLayer.java +30 -8
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyframeFactory.java +26 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/LiveSpring.java +36 -9
- package/platforms/android/java/org/rafaelaugusto/jolt/ScrollHeader.java +3 -2
- package/platforms/android/java/org/rafaelaugusto/jolt/ShapeLayer.java +17 -7
- package/platforms/android/proguard-rules.pro +1 -0
- package/platforms/ios/src/JoltInteractiveModal.h +16 -0
- package/platforms/ios/src/JoltInteractiveModal.m +178 -0
- package/platforms/ios/src/JoltLiveSpring.h +1 -0
- package/platforms/ios/src/JoltLiveSpring.m +183 -67
- package/platforms/ios/src/module.modulemap +1 -0
- package/src/android-plugin.d.ts +10 -0
- package/src/animate.ts +23 -5
- package/src/bottom-sheet/geometry.ts +9 -2
- package/src/bottom-sheet/index.ts +94 -20
- package/src/bottom-sheet/native.ts +6 -0
- package/src/driver/android.ts +31 -14
- package/src/index.ts +1 -1
- package/src/interactive-navigation/index.ts +7 -0
- package/src/interactive-navigation/modal.ts +306 -0
- package/src/ios-plugin.d.ts +23 -0
- package/src/live-spring-driver.ts +15 -0
- package/src/mixer.ts +62 -22
- package/src/motion-value.ts +121 -9
- package/src/scroll-header/geometry.ts +7 -2
- package/src/scroll-header/index.ts +12 -0
- package/src/shapes/index.ts +20 -9
- package/src/types.ts +5 -2
- package/src/value-track.ts +25 -14
|
@@ -2,67 +2,133 @@
|
|
|
2
2
|
#import "JoltSpringState.h"
|
|
3
3
|
#import <QuartzCore/QuartzCore.h>
|
|
4
4
|
|
|
5
|
+
/* Sampled inputs and mappings are decoded once; frames read plain C arrays. */
|
|
6
|
+
typedef struct {
|
|
7
|
+
double value, elapsed, duration, delay, repeat, to;
|
|
8
|
+
BOOL reverse, playing;
|
|
9
|
+
NSUInteger count;
|
|
10
|
+
double *times, *values;
|
|
11
|
+
} JoltSampled;
|
|
12
|
+
typedef struct {
|
|
13
|
+
NSUInteger count;
|
|
14
|
+
double *input, *output;
|
|
15
|
+
BOOL clamp;
|
|
16
|
+
} JoltMapping;
|
|
17
|
+
|
|
5
18
|
static id decode(NSString *json) {
|
|
6
19
|
return [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
|
|
7
20
|
}
|
|
8
|
-
static double
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
static double *doubles(NSArray *numbers, NSUInteger count) {
|
|
22
|
+
double *result = malloc(sizeof(double) * MAX(1, count));
|
|
23
|
+
for (NSUInteger i = 0; i < count; i++) result[i] = [numbers[i] doubleValue];
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
static JoltSampled *decodeSampled(NSArray *list, NSUInteger *count) {
|
|
27
|
+
*count = list.count;
|
|
28
|
+
if (!*count) return NULL;
|
|
29
|
+
JoltSampled *result = calloc(*count, sizeof(JoltSampled));
|
|
30
|
+
for (NSUInteger i = 0; i < *count; i++) {
|
|
31
|
+
NSDictionary *input = list[i];
|
|
32
|
+
NSArray *values = input[@"values"], *times = input[@"times"];
|
|
33
|
+
JoltSampled *s = &result[i];
|
|
34
|
+
s->value = [input[@"value"] doubleValue]; s->elapsed = [input[@"elapsed"] doubleValue];
|
|
35
|
+
s->duration = [input[@"duration"] doubleValue]; s->delay = [input[@"delay"] doubleValue];
|
|
36
|
+
s->repeat = [input[@"repeat"] doubleValue]; s->to = [input[@"to"] doubleValue];
|
|
37
|
+
s->reverse = [input[@"reverse"] boolValue]; s->playing = [input[@"running"] boolValue];
|
|
38
|
+
s->count = MIN(values.count, times.count);
|
|
39
|
+
s->times = doubles(times, s->count);
|
|
40
|
+
s->values = doubles(values, s->count);
|
|
19
41
|
}
|
|
20
|
-
return
|
|
42
|
+
return result;
|
|
21
43
|
}
|
|
22
|
-
static
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!
|
|
31
|
-
|
|
32
|
-
double
|
|
44
|
+
static void freeSampled(JoltSampled *list, NSUInteger count) {
|
|
45
|
+
for (NSUInteger i = 0; i < count; i++) { free(list[i].times); free(list[i].values); }
|
|
46
|
+
free(list);
|
|
47
|
+
}
|
|
48
|
+
static BOOL sampledRunning(const JoltSampled *s, double anchor, double now) {
|
|
49
|
+
return s->playing && (s->repeat < 0 || now - anchor + s->elapsed < s->delay + s->duration * (s->repeat + 1));
|
|
50
|
+
}
|
|
51
|
+
static double sampledValue(const JoltSampled *s, double anchor, double now) {
|
|
52
|
+
if (!s->playing || s->count < 2) return s->value;
|
|
53
|
+
if (!sampledRunning(s, anchor, now)) return s->to;
|
|
54
|
+
double time = MAX(0, now - anchor + s->elapsed - s->delay);
|
|
55
|
+
double duration = MAX(1, s->duration);
|
|
33
56
|
double iteration = floor(time / duration), progress = fmod(time, duration) / duration;
|
|
34
|
-
if (
|
|
35
|
-
NSUInteger lo = 0, hi =
|
|
36
|
-
while (lo + 1 < hi) { NSUInteger mid = (lo + hi) / 2; if (
|
|
37
|
-
double start =
|
|
57
|
+
if (s->reverse && fmod(iteration, 2) >= 1) progress = 1 - progress;
|
|
58
|
+
NSUInteger lo = 0, hi = s->count - 1;
|
|
59
|
+
while (lo + 1 < hi) { NSUInteger mid = (lo + hi) / 2; if (s->times[mid] <= progress) lo = mid; else hi = mid; }
|
|
60
|
+
double start = s->times[lo], end = s->times[hi];
|
|
38
61
|
double fraction = end > start ? (progress - start) / (end - start) : 1;
|
|
39
|
-
return
|
|
62
|
+
return s->values[lo] + (s->values[hi] - s->values[lo]) * MAX(0, MIN(1, fraction));
|
|
40
63
|
}
|
|
64
|
+
static JoltMapping *decodeMappings(NSString *json, NSUInteger *count) {
|
|
65
|
+
NSArray *list = decode(json);
|
|
66
|
+
*count = list.count;
|
|
67
|
+
if (!*count) return NULL;
|
|
68
|
+
JoltMapping *result = calloc(*count, sizeof(JoltMapping));
|
|
69
|
+
for (NSUInteger i = 0; i < *count; i++) {
|
|
70
|
+
NSDictionary *mapping = list[i];
|
|
71
|
+
NSArray *input = mapping[@"input"], *output = mapping[@"output"];
|
|
72
|
+
result[i].count = MIN(input.count, output.count);
|
|
73
|
+
result[i].input = doubles(input, result[i].count);
|
|
74
|
+
result[i].output = doubles(output, result[i].count);
|
|
75
|
+
result[i].clamp = [mapping[@"clamp"] boolValue];
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
static void freeMappings(JoltMapping *maps, NSUInteger count) {
|
|
80
|
+
for (NSUInteger i = 0; i < count; i++) { free(maps[i].input); free(maps[i].output); }
|
|
81
|
+
free(maps);
|
|
82
|
+
}
|
|
83
|
+
static double mapped(double value, const JoltMapping *maps, NSUInteger count) {
|
|
84
|
+
for (NSUInteger m = 0; m < count; m++) {
|
|
85
|
+
const JoltMapping *mapping = &maps[m];
|
|
86
|
+
if (mapping->count < 2) continue;
|
|
87
|
+
if (mapping->clamp && value <= mapping->input[0]) { value = mapping->output[0]; continue; }
|
|
88
|
+
if (mapping->clamp && value >= mapping->input[mapping->count - 1]) { value = mapping->output[mapping->count - 1]; continue; }
|
|
89
|
+
NSUInteger index = 0;
|
|
90
|
+
while (index < mapping->count - 2 && value > mapping->input[index + 1]) index++;
|
|
91
|
+
double span = mapping->input[index + 1] - mapping->input[index];
|
|
92
|
+
double fraction = span > 0 ? (value - mapping->input[index]) / span : value < mapping->input[index] ? 0 : 1;
|
|
93
|
+
value = mapping->output[index] + (mapping->output[index + 1] - mapping->output[index]) * fraction;
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@protocol JoltShadowedView
|
|
99
|
+
- (CALayer *)outerShadowContainerLayer;
|
|
100
|
+
@end
|
|
41
101
|
|
|
42
102
|
@interface JoltLiveSpring () {
|
|
43
103
|
JoltSpringState _state;
|
|
104
|
+
JoltSampled *_inputs;
|
|
105
|
+
NSUInteger _inputCount;
|
|
106
|
+
JoltMapping *_sourceMaps;
|
|
107
|
+
NSUInteger _sourceMapCount;
|
|
44
108
|
}
|
|
45
109
|
@property(nonatomic) double stiffness, damping, mass, restDelta, restSpeed;
|
|
46
110
|
@property(nonatomic, readwrite) BOOL moving;
|
|
47
111
|
@property(nonatomic) BOOL reduced, paused, disposed, direct, inputHeld, sensorDriven;
|
|
48
112
|
@property(nonatomic) double targetValue;
|
|
49
113
|
@property(nonatomic, nullable) JoltLiveSpring *source;
|
|
50
|
-
@property(nonatomic)
|
|
51
|
-
@property(nonatomic) NSDictionary *input;
|
|
52
|
-
@property(nonatomic) double anchor;
|
|
114
|
+
@property(nonatomic) double inputRest, anchor;
|
|
53
115
|
@property(nonatomic) NSMutableArray<JoltLiveSpringBinding *> *bindings;
|
|
54
116
|
- (void)step:(double)now seconds:(double)seconds;
|
|
55
117
|
- (void)apply;
|
|
56
118
|
- (void)wake;
|
|
57
119
|
@end
|
|
58
|
-
@interface JoltLiveSpringBinding ()
|
|
120
|
+
@interface JoltLiveSpringBinding () {
|
|
121
|
+
JoltMapping *_maps;
|
|
122
|
+
NSUInteger _mapCount;
|
|
123
|
+
JoltSampled *_layers;
|
|
124
|
+
NSUInteger _layerCount;
|
|
125
|
+
}
|
|
59
126
|
@property(nonatomic, weak) UIView *view;
|
|
60
127
|
@property(nonatomic, weak) JoltLiveSpring *node;
|
|
61
|
-
@property(nonatomic) NSString *property;
|
|
62
|
-
@property(nonatomic)
|
|
63
|
-
@property(nonatomic) BOOL stopped;
|
|
64
|
-
@property(nonatomic) NSArray *sampledLayers;
|
|
128
|
+
@property(nonatomic) NSString *property, *keyPath;
|
|
129
|
+
@property(nonatomic) BOOL stopped, multiply, radians, alpha, shadowed;
|
|
65
130
|
@property(nonatomic) double layerAnchor;
|
|
131
|
+
- (void)useMappings:(NSString *)mappings;
|
|
66
132
|
- (BOOL)running:(double)now;
|
|
67
133
|
- (void)apply;
|
|
68
134
|
@end
|
|
@@ -90,7 +156,9 @@ static double previousFrame;
|
|
|
90
156
|
while (remaining > 1e-9) {
|
|
91
157
|
double dt = MIN(remaining, 1.0 / 240);
|
|
92
158
|
remaining -= dt;
|
|
93
|
-
|
|
159
|
+
// An instant node only needs the frame's final time; springs integrate every substep.
|
|
160
|
+
for (JoltLiveSpring *node in nodes)
|
|
161
|
+
if (!node.reduced || remaining <= 1e-9) [node step:now - remaining * 1000 seconds:dt];
|
|
94
162
|
}
|
|
95
163
|
BOOL active = NO;
|
|
96
164
|
[CATransaction begin]; [CATransaction setDisableActions:YES];
|
|
@@ -108,36 +176,68 @@ static double previousFrame;
|
|
|
108
176
|
node.stiffness = stiffness; node.damping = damping; node.mass = mass;
|
|
109
177
|
node.restDelta = restDelta; node.restSpeed = restSpeed; node->_reduced = reduced;
|
|
110
178
|
node.direct = YES; node.targetValue = initial;
|
|
111
|
-
node.bindings = [NSMutableArray new];
|
|
179
|
+
node.bindings = [NSMutableArray new];
|
|
112
180
|
[nodes addObject:node];
|
|
113
181
|
return node;
|
|
114
182
|
}
|
|
183
|
+
- (void)dealloc {
|
|
184
|
+
freeSampled(_inputs, _inputCount);
|
|
185
|
+
freeMappings(_sourceMaps, _sourceMapCount);
|
|
186
|
+
}
|
|
115
187
|
- (double)position { return _state.position; }
|
|
116
188
|
- (double)velocity { return _state.velocity; }
|
|
117
189
|
+ (NSUInteger)activeCount { return displayLink ? 1 : 0; }
|
|
118
190
|
+ (NSUInteger)nodeCount { return nodes.count; }
|
|
191
|
+
- (void)replaceInputs:(NSArray *)list rest:(double)rest {
|
|
192
|
+
freeSampled(_inputs, _inputCount);
|
|
193
|
+
_inputs = decodeSampled(list, &_inputCount);
|
|
194
|
+
self.inputRest = rest; self.anchor = CACurrentMediaTime() * 1000;
|
|
195
|
+
}
|
|
119
196
|
- (void)follow:(JoltLiveSpring *)source mappings:(NSString *)mappings {
|
|
120
197
|
if (self.disposed || source.disposed || [nodes indexOfObject:source] >= [nodes indexOfObject:self])
|
|
121
198
|
[NSException raise:NSInvalidArgumentException format:@"A spring can only follow an existing live node"];
|
|
122
|
-
|
|
199
|
+
freeMappings(_sourceMaps, _sourceMapCount);
|
|
200
|
+
_sourceMaps = decodeMappings(mappings, &_sourceMapCount);
|
|
201
|
+
self.source = source; [self wake];
|
|
123
202
|
}
|
|
124
203
|
- (void)updateInput:(NSString *)snapshot {
|
|
125
204
|
if (self.disposed) return;
|
|
126
|
-
self.sensorDriven = NO; self.
|
|
205
|
+
self.sensorDriven = NO; self.direct = NO;
|
|
206
|
+
[self replaceInputs:@[decode(snapshot)] rest:0];
|
|
207
|
+
[self wake];
|
|
208
|
+
}
|
|
209
|
+
- (void)setInputs:(NSString *)snapshots rest:(double)rest {
|
|
210
|
+
if (self.disposed) return;
|
|
211
|
+
self.sensorDriven = NO; self.direct = NO;
|
|
212
|
+
[self replaceInputs:decode(snapshots) rest:rest];
|
|
213
|
+
[self wake];
|
|
214
|
+
}
|
|
215
|
+
- (double)inputValue:(double)now {
|
|
216
|
+
double value = self.inputRest;
|
|
217
|
+
for (NSUInteger i = 0; i < _inputCount; i++) value += sampledValue(&_inputs[i], self.anchor, now);
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
- (BOOL)inputMoving:(double)now {
|
|
221
|
+
for (NSUInteger i = 0; i < _inputCount; i++) if (sampledRunning(&_inputs[i], self.anchor, now)) return YES;
|
|
222
|
+
return NO;
|
|
127
223
|
}
|
|
128
224
|
- (void)setSensorInput:(double)value {
|
|
129
225
|
if (self.disposed || !isfinite(value) || (self.sensorDriven && self.inputHeld && _state.position == value)) return;
|
|
130
|
-
self.sensorDriven = YES; self.inputHeld = YES; self.direct = YES; self.targetValue = value;
|
|
226
|
+
self.sensorDriven = YES; self.inputHeld = YES; self.direct = YES; self.targetValue = value;
|
|
227
|
+
[self replaceInputs:@[] rest:0];
|
|
131
228
|
_state = (JoltSpringState){value, 0}; self.moving = NO;
|
|
132
229
|
[JoltLiveSpring schedule];
|
|
133
230
|
}
|
|
134
231
|
- (void)setTarget:(double)value {
|
|
135
232
|
if (self.disposed) return;
|
|
136
|
-
self.sensorDriven = NO; self.direct = YES; self.targetValue = value;
|
|
233
|
+
self.sensorDriven = NO; self.direct = YES; self.targetValue = value;
|
|
234
|
+
[self replaceInputs:@[] rest:0];
|
|
235
|
+
[self wake];
|
|
137
236
|
}
|
|
138
237
|
- (void)holdInput:(double)value velocity:(double)velocity {
|
|
139
238
|
if (self.disposed) return;
|
|
140
|
-
self.sensorDriven = NO; self.inputHeld = YES; self.direct = YES; self.targetValue = value;
|
|
239
|
+
self.sensorDriven = NO; self.inputHeld = YES; self.direct = YES; self.targetValue = value;
|
|
240
|
+
[self replaceInputs:@[] rest:0];
|
|
141
241
|
_state = (JoltSpringState){value, velocity}; self.moving = YES;
|
|
142
242
|
[CATransaction begin]; [CATransaction setDisableActions:YES]; [self apply]; [CATransaction commit];
|
|
143
243
|
[JoltLiveSpring schedule];
|
|
@@ -163,8 +263,8 @@ static double previousFrame;
|
|
|
163
263
|
}
|
|
164
264
|
- (void)step:(double)now seconds:(double)seconds {
|
|
165
265
|
if (self.disposed || self.paused || self.inputHeld) return;
|
|
166
|
-
double target = self.source ? mapped(self.source.position,
|
|
167
|
-
BOOL inputMoving = self.source ? self.source.moving : (!self.direct &&
|
|
266
|
+
double target = self.source ? mapped(self.source.position, _sourceMaps, _sourceMapCount) : (self.direct ? self.targetValue : [self inputValue:now]);
|
|
267
|
+
BOOL inputMoving = self.source ? self.source.moving : (!self.direct && [self inputMoving:now]);
|
|
168
268
|
if (self.reduced) _state = (JoltSpringState){target, 0};
|
|
169
269
|
else _state = JoltSpringAdvance(_state, target, self.stiffness, self.damping, self.mass, seconds);
|
|
170
270
|
BOOL rest = fabs(_state.position - target) <= self.restDelta && fabs(_state.velocity) <= self.restSpeed;
|
|
@@ -174,7 +274,20 @@ static double previousFrame;
|
|
|
174
274
|
- (JoltLiveSpringBinding *)bind:(UIView *)view property:(NSString *)property mappings:(NSString *)mappings {
|
|
175
275
|
if (self.disposed) [NSException raise:NSInvalidArgumentException format:@"Spring disposed"];
|
|
176
276
|
JoltLiveSpringBinding *binding = [JoltLiveSpringBinding new];
|
|
177
|
-
binding.node = self; binding.view = view; binding.property = property;
|
|
277
|
+
binding.node = self; binding.view = view; binding.property = property;
|
|
278
|
+
[binding useMappings:mappings];
|
|
279
|
+
NSString *key = property;
|
|
280
|
+
if ([key isEqualToString:@"opacity"]) binding.alpha = YES;
|
|
281
|
+
else if ([key isEqualToString:@"x"]) key = @"transform.translation.x";
|
|
282
|
+
else if ([key isEqualToString:@"y"]) key = @"transform.translation.y";
|
|
283
|
+
else if ([key isEqualToString:@"scaleX"]) key = @"transform.scale.x";
|
|
284
|
+
else if ([key isEqualToString:@"scaleY"]) key = @"transform.scale.y";
|
|
285
|
+
else if ([key isEqualToString:@"rotate"]) { key = @"transform.rotation.z"; binding.radians = YES; }
|
|
286
|
+
else if ([key isEqualToString:@"rotateX"]) { key = @"transform.rotation.x"; binding.radians = YES; }
|
|
287
|
+
else if ([key isEqualToString:@"rotateY"]) { key = @"transform.rotation.y"; binding.radians = YES; }
|
|
288
|
+
binding.keyPath = key;
|
|
289
|
+
binding.multiply = binding.alpha || [property hasPrefix:@"scale"];
|
|
290
|
+
binding.shadowed = [view respondsToSelector:@selector(outerShadowContainerLayer)];
|
|
178
291
|
[self.bindings addObject:binding];
|
|
179
292
|
[CATransaction begin]; [CATransaction setDisableActions:YES]; [binding apply]; [CATransaction commit];
|
|
180
293
|
return binding;
|
|
@@ -196,7 +309,7 @@ static double previousFrame;
|
|
|
196
309
|
if (self.disposed) return;
|
|
197
310
|
self.disposed = YES; self.moving = NO; [self.bindings removeAllObjects]; [nodes removeObject:self];
|
|
198
311
|
for (JoltLiveSpring *child in nodes) if (child.source == self) {
|
|
199
|
-
child.source = nil; [child setTarget:mapped(self.position, child
|
|
312
|
+
child.source = nil; [child setTarget:mapped(self.position, child->_sourceMaps, child->_sourceMapCount)];
|
|
200
313
|
}
|
|
201
314
|
self.source = nil;
|
|
202
315
|
if (!nodes.count) { [displayLink invalidate]; displayLink = nil; previousFrame = 0; }
|
|
@@ -204,40 +317,43 @@ static double previousFrame;
|
|
|
204
317
|
@end
|
|
205
318
|
|
|
206
319
|
@implementation JoltLiveSpringBinding
|
|
207
|
-
- (
|
|
320
|
+
- (void)dealloc {
|
|
321
|
+
freeMappings(_maps, _mapCount);
|
|
322
|
+
freeSampled(_layers, _layerCount);
|
|
323
|
+
}
|
|
324
|
+
- (void)useMappings:(NSString *)mappings {
|
|
325
|
+
freeMappings(_maps, _mapCount);
|
|
326
|
+
_maps = decodeMappings(mappings, &_mapCount);
|
|
327
|
+
}
|
|
328
|
+
- (double)value { return mapped(self.node.position, _maps, _mapCount); }
|
|
208
329
|
- (void)setLayers:(NSString *)inputs {
|
|
209
|
-
|
|
330
|
+
freeSampled(_layers, _layerCount);
|
|
331
|
+
_layers = decodeSampled(decode(inputs), &_layerCount);
|
|
332
|
+
self.layerAnchor = CACurrentMediaTime() * 1000;
|
|
210
333
|
[CATransaction begin]; [CATransaction setDisableActions:YES]; [self apply]; [CATransaction commit];
|
|
211
334
|
[JoltLiveSpring schedule];
|
|
212
335
|
}
|
|
213
336
|
- (BOOL)running:(double)now {
|
|
214
|
-
for (
|
|
337
|
+
for (NSUInteger i = 0; i < _layerCount; i++) if (sampledRunning(&_layers[i], self.layerAnchor, now)) return YES;
|
|
215
338
|
return NO;
|
|
216
339
|
}
|
|
217
340
|
- (void)cancel { self.stopped = YES; [self.node.bindings removeObject:self]; }
|
|
218
341
|
- (void)apply {
|
|
219
342
|
UIView *view = self.view;
|
|
220
343
|
if (self.stopped || !view.window) return;
|
|
221
|
-
NSString *key = self.property;
|
|
222
344
|
double value = self.value;
|
|
223
|
-
BOOL multiply = [key isEqualToString:@"opacity"] || [key hasPrefix:@"scale"];
|
|
224
345
|
double now = CACurrentMediaTime() * 1000;
|
|
225
|
-
for (
|
|
226
|
-
double part =
|
|
227
|
-
value = multiply ? value * part : value + part;
|
|
346
|
+
for (NSUInteger i = 0; i < _layerCount; i++) {
|
|
347
|
+
double part = sampledValue(&_layers[i], self.layerAnchor, now);
|
|
348
|
+
value = self.multiply ? value * part : value + part;
|
|
228
349
|
}
|
|
229
|
-
if (
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
else if ([key isEqualToString:@"rotateY"]) { key = @"transform.rotation.y"; value *= M_PI / 180; }
|
|
237
|
-
[view.layer setValue:@(value) forKeyPath:key];
|
|
238
|
-
if ([view respondsToSelector:NSSelectorFromString(@"outerShadowContainerLayer")]) {
|
|
239
|
-
CALayer *shadow = [view valueForKey:@"outerShadowContainerLayer"];
|
|
240
|
-
[shadow setValue:@(value) forKeyPath:key];
|
|
350
|
+
if (self.alpha) { view.alpha = MAX(0, MIN(1, value)); return; }
|
|
351
|
+
if (self.radians) value *= M_PI / 180;
|
|
352
|
+
NSNumber *boxed = @(value);
|
|
353
|
+
[view.layer setValue:boxed forKeyPath:self.keyPath];
|
|
354
|
+
if (self.shadowed) {
|
|
355
|
+
CALayer *shadow = [(id<JoltShadowedView>)view outerShadowContainerLayer];
|
|
356
|
+
if (shadow) [shadow setValue:boxed forKeyPath:self.keyPath];
|
|
241
357
|
}
|
|
242
358
|
}
|
|
243
359
|
@end
|
package/src/android-plugin.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
declare namespace org.rafaelaugusto.jolt {
|
|
2
|
+
class KeyframeFactory {
|
|
3
|
+
static create(
|
|
4
|
+
view: android.view.View,
|
|
5
|
+
property: android.util.Property<android.view.View, java.lang.Float>,
|
|
6
|
+
times: readonly number[],
|
|
7
|
+
values: readonly number[],
|
|
8
|
+
unit: number,
|
|
9
|
+
): android.animation.ObjectAnimator
|
|
10
|
+
}
|
|
2
11
|
class ShapeLayer extends android.graphics.drawable.Drawable {
|
|
3
12
|
constructor(options: string)
|
|
4
13
|
static liveCount(): number
|
|
@@ -162,6 +171,7 @@ declare namespace org.rafaelaugusto.jolt {
|
|
|
162
171
|
isMoving(): boolean
|
|
163
172
|
follow(source: LiveSpring, mappings: string): void
|
|
164
173
|
updateInput(snapshot: string): void
|
|
174
|
+
setInputs(rest: number, snapshots: string): void
|
|
165
175
|
setTarget(value: number): void
|
|
166
176
|
setReduced(value: boolean): void
|
|
167
177
|
setPaused(value: boolean): void
|
package/src/animate.ts
CHANGED
|
@@ -308,12 +308,30 @@ export function group(animations: readonly Animation[]): Animation {
|
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
export function toView(target: Target): View {
|
|
311
|
-
|
|
312
|
-
const view =
|
|
313
|
-
(target as { nativeView?: unknown } | null)?.nativeView ??
|
|
314
|
-
(target as { nativeElement?: unknown } | null)?.nativeElement
|
|
311
|
+
const view = unwrap(target)
|
|
315
312
|
if (view instanceof View) return view
|
|
316
|
-
throw new TypeError(`Expected a NativeScript view, got ${
|
|
313
|
+
throw new TypeError(`Expected a NativeScript view, got ${describe(target)}.`)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function unwrap(target: unknown, nested = false): unknown {
|
|
317
|
+
if (target instanceof View || target === null || typeof target !== 'object')
|
|
318
|
+
return target
|
|
319
|
+
const holder = target as {
|
|
320
|
+
nativeView?: unknown
|
|
321
|
+
nativeElement?: unknown
|
|
322
|
+
$el?: unknown
|
|
323
|
+
}
|
|
324
|
+
if (!nested && '$el' in holder) return unwrap(holder.$el, true)
|
|
325
|
+
return holder.nativeView ?? holder.nativeElement
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function describe(target: unknown) {
|
|
329
|
+
if (target === null || typeof target !== 'object')
|
|
330
|
+
return typeof target === 'function' ? 'a function' : String(target)
|
|
331
|
+
if (Array.isArray(target)) return 'an array'
|
|
332
|
+
if ('$el' in target) return 'a component instance without a root view'
|
|
333
|
+
const name = Object.getPrototypeOf(target)?.constructor?.name
|
|
334
|
+
return name && name !== 'Object' ? `a ${name}` : 'an object without a view'
|
|
317
335
|
}
|
|
318
336
|
|
|
319
337
|
export function toViews(target: Target | readonly Target[]): View[] {
|
|
@@ -10,6 +10,8 @@ export interface SheetGeometry {
|
|
|
10
10
|
height: number
|
|
11
11
|
closedY: number
|
|
12
12
|
top: number
|
|
13
|
+
/** How far the panel extends below the usable area, under a home indicator the keyboard does not cover. */
|
|
14
|
+
extension: number
|
|
13
15
|
stops: SheetStop[]
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -31,7 +33,10 @@ export function validateSnaps(points: readonly SheetSnap[]): void {
|
|
|
31
33
|
throw new RangeError('Sheet snap points must not repeat.')
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Keyboard overlap is relative to the host, so adjustResize is not subtracted twice.
|
|
38
|
+
* The panel extends under the bottom inset only when its host lets it reach that edge.
|
|
39
|
+
*/
|
|
35
40
|
export function sheetGeometry(
|
|
36
41
|
points: readonly SheetSnap[],
|
|
37
42
|
hostHeight: number,
|
|
@@ -41,6 +46,7 @@ export function sheetGeometry(
|
|
|
41
46
|
safeTop: number,
|
|
42
47
|
safeBottom: number,
|
|
43
48
|
contentHeight: number,
|
|
49
|
+
extendable = true,
|
|
44
50
|
): SheetGeometry {
|
|
45
51
|
const top = Math.max(0, safeTop)
|
|
46
52
|
const overlap = Math.max(
|
|
@@ -48,6 +54,7 @@ export function sheetGeometry(
|
|
|
48
54
|
Math.min(hostHeight, hostBottom - (windowHeight - keyboardHeight)),
|
|
49
55
|
)
|
|
50
56
|
const bottom = Math.max(Math.max(0, safeBottom), overlap)
|
|
57
|
+
const extension = extendable ? Math.max(0, Math.max(0, safeBottom) - overlap) : 0
|
|
51
58
|
const height = Math.max(1, hostHeight - top - bottom)
|
|
52
59
|
const closedY = Math.max(height, hostHeight - top)
|
|
53
60
|
const stops = points.map((point) => {
|
|
@@ -60,7 +67,7 @@ export function sheetGeometry(
|
|
|
60
67
|
const visible = Math.max(1, Math.min(height, desired))
|
|
61
68
|
return { point, height: visible, y: height - visible }
|
|
62
69
|
})
|
|
63
|
-
return { height, closedY, top, stops }
|
|
70
|
+
return { height, closedY, top, extension, stops }
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
/** Projection selects a stop; the native spring retains the actual release velocity. */
|