@rafaelaugusto/jolt 0.1.0-beta.6 → 0.1.0-beta.8
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 +21 -4
- package/dist/animate.d.ts +2 -0
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +7 -2
- package/dist/animate.js.map +1 -1
- package/dist/drag-input.d.ts +15 -0
- package/dist/drag-input.d.ts.map +1 -0
- package/dist/drag-input.js +66 -0
- package/dist/drag-input.js.map +1 -0
- package/dist/drag-physics.d.ts +20 -0
- package/dist/drag-physics.d.ts.map +1 -0
- package/dist/drag-physics.js +75 -0
- package/dist/drag-physics.js.map +1 -0
- package/dist/drag.d.ts +58 -0
- package/dist/drag.d.ts.map +1 -0
- package/dist/drag.js +324 -0
- package/dist/drag.js.map +1 -0
- package/dist/driver/ios.d.ts.map +1 -1
- package/dist/driver/ios.js +38 -6
- package/dist/driver/ios.js.map +1 -1
- package/dist/engine.d.ts +2 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +9 -0
- package/dist/engine.js.map +1 -1
- package/dist/image-effect-native.d.ts +26 -0
- package/dist/image-effect-native.d.ts.map +1 -0
- package/dist/image-effect-native.js +73 -0
- package/dist/image-effect-native.js.map +1 -0
- package/dist/image-effect.d.ts +50 -0
- package/dist/image-effect.d.ts.map +1 -0
- package/dist/image-effect.js +290 -0
- package/dist/image-effect.js.map +1 -0
- package/dist/image-owner.d.ts +3 -0
- package/dist/image-owner.d.ts.map +1 -0
- package/dist/image-owner.js +11 -0
- package/dist/image-owner.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/interaction-surface.d.ts +12 -0
- package/dist/interaction-surface.d.ts.map +1 -0
- package/dist/interaction-surface.js +36 -0
- package/dist/interaction-surface.js.map +1 -0
- package/dist/liquid.d.ts.map +1 -1
- package/dist/liquid.js +4 -0
- package/dist/liquid.js.map +1 -1
- package/dist/motion-scope.d.ts +37 -0
- package/dist/motion-scope.d.ts.map +1 -0
- package/dist/motion-scope.js +232 -0
- package/dist/motion-scope.js.map +1 -0
- package/dist/motion-states.d.ts +36 -0
- package/dist/motion-states.d.ts.map +1 -0
- package/dist/motion-states.js +165 -0
- package/dist/motion-states.js.map +1 -0
- package/dist/press.d.ts.map +1 -1
- package/dist/press.js +14 -3
- package/dist/press.js.map +1 -1
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +21 -4
- package/dist/track.js.map +1 -1
- package/dist/transition.d.ts +2 -1
- package/dist/transition.d.ts.map +1 -1
- package/dist/transition.js.map +1 -1
- package/dist/vue/index.d.ts +3 -1
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +17 -4
- package/dist/vue/index.js.map +1 -1
- package/dist/zoom-native.d.ts +30 -0
- package/dist/zoom-native.d.ts.map +1 -0
- package/dist/zoom-native.js +56 -0
- package/dist/zoom-native.js.map +1 -0
- package/dist/zoomable.d.ts +48 -0
- package/dist/zoomable.d.ts.map +1 -0
- package/dist/zoomable.js +249 -0
- package/dist/zoomable.js.map +1 -0
- package/package.json +2 -1
- package/platforms/android/java/org/rafaelaugusto/jolt/ImageEffect.java +288 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/PhotoZoom.java +250 -0
- package/platforms/android/proguard-rules.pro +4 -0
- package/platforms/ios/build.xcconfig +1 -0
- package/platforms/ios/src/JoltImageEffect.h +25 -0
- package/platforms/ios/src/JoltImageEffect.mm +242 -0
- package/platforms/ios/src/JoltInteractiveAnimation.h +18 -0
- package/platforms/ios/src/JoltInteractiveAnimation.m +123 -0
- package/platforms/ios/src/JoltPhotoZoom.h +23 -0
- package/platforms/ios/src/JoltPhotoZoom.m +229 -0
- package/platforms/ios/src/module.modulemap +6 -0
- package/src/android-plugin.d.ts +78 -0
- package/src/animate.ts +15 -8
- package/src/drag-input.ts +74 -0
- package/src/drag-physics.ts +110 -0
- package/src/drag.ts +395 -0
- package/src/driver/ios.ts +57 -8
- package/src/engine.ts +11 -0
- package/src/image-effect-native.ts +129 -0
- package/src/image-effect.ts +336 -0
- package/src/image-owner.ts +12 -0
- package/src/index.ts +23 -0
- package/src/interaction-surface.ts +41 -0
- package/src/ios-plugin.d.ts +81 -0
- package/src/liquid.ts +4 -0
- package/src/motion-scope.ts +265 -0
- package/src/motion-states.ts +215 -0
- package/src/press.ts +15 -3
- package/src/track.ts +25 -4
- package/src/transition.ts +2 -0
- package/src/vue/index.ts +20 -3
- package/src/zoom-native.ts +124 -0
- package/src/zoomable.ts +330 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#import "JoltInteractiveAnimation.h"
|
|
2
|
+
#import <QuartzCore/QuartzCore.h>
|
|
3
|
+
|
|
4
|
+
@interface JoltInteractiveAnimation ()
|
|
5
|
+
@property(nonatomic, weak) UIView *view;
|
|
6
|
+
@property(nonatomic, copy) NSArray<CALayer *> *layers;
|
|
7
|
+
@property(nonatomic, copy) NSString *keyPath;
|
|
8
|
+
@property(nonatomic, copy) NSArray<NSNumber *> *values;
|
|
9
|
+
@property(nonatomic, copy) NSArray<NSNumber *> *times;
|
|
10
|
+
@property(nonatomic) double duration, delay, repeats, anchor, offset, finalValue;
|
|
11
|
+
@property(nonatomic) BOOL reverse, paused, stopped;
|
|
12
|
+
@property(nonatomic, copy, nullable) void (^completion)(BOOL);
|
|
13
|
+
@end
|
|
14
|
+
|
|
15
|
+
@implementation JoltInteractiveAnimation
|
|
16
|
+
static NSMapTable<UIView *, NSMutableDictionary<NSString *, JoltInteractiveAnimation *> *> *views;
|
|
17
|
+
static NSMutableSet<JoltInteractiveAnimation *> *playing;
|
|
18
|
+
static CADisplayLink *displayLink;
|
|
19
|
+
|
|
20
|
+
+ (void)initialize {
|
|
21
|
+
if (self != [JoltInteractiveAnimation class]) return;
|
|
22
|
+
views = [NSMapTable weakToStrongObjectsMapTable];
|
|
23
|
+
playing = [NSMutableSet new];
|
|
24
|
+
}
|
|
25
|
+
+ (void)schedule {
|
|
26
|
+
if (playing.count && !displayLink) {
|
|
27
|
+
displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
|
|
28
|
+
if (@available(iOS 15.0, *)) {
|
|
29
|
+
float maximum = MIN(120, UIScreen.mainScreen.maximumFramesPerSecond);
|
|
30
|
+
displayLink.preferredFrameRateRange = CAFrameRateRangeMake(MIN(80, maximum), maximum, maximum);
|
|
31
|
+
}
|
|
32
|
+
[displayLink addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
|
|
33
|
+
} else if (!playing.count && displayLink) {
|
|
34
|
+
[displayLink invalidate]; displayLink = nil;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
+ (void)tick:(CADisplayLink *)link {
|
|
38
|
+
for (JoltInteractiveAnimation *track in playing.allObjects) {
|
|
39
|
+
if (track.stopped || track.paused) continue;
|
|
40
|
+
if (!track.view) { [track finish:NO]; continue; }
|
|
41
|
+
double elapsed = track.elapsed;
|
|
42
|
+
[track apply:elapsed];
|
|
43
|
+
if (isfinite(track.repeats) && elapsed >= track.delay + track.duration * (track.repeats + 1))
|
|
44
|
+
[track finish:YES];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
+ (instancetype)startView:(UIView *)view layers:(NSArray<CALayer *> *)layers keyPath:(NSString *)keyPath
|
|
48
|
+
values:(NSArray<NSNumber *> *)values times:(NSArray<NSNumber *> *)times
|
|
49
|
+
duration:(double)duration delay:(double)delay repeat:(double)repeat
|
|
50
|
+
reverse:(BOOL)reverse finalValue:(double)finalValue
|
|
51
|
+
startedAt:(double)startedAt completion:(void (^)(BOOL))completion {
|
|
52
|
+
JoltInteractiveAnimation *track = [self new];
|
|
53
|
+
track.view = view; track.layers = layers; track.keyPath = keyPath; track.values = values; track.times = times;
|
|
54
|
+
track.duration = MAX(1, duration); track.delay = delay; track.repeats = repeat;
|
|
55
|
+
track.reverse = reverse; track.finalValue = finalValue;
|
|
56
|
+
track.anchor = startedAt; track.completion = completion;
|
|
57
|
+
NSMutableDictionary *properties = [views objectForKey:view];
|
|
58
|
+
if (!properties) { properties = [NSMutableDictionary new]; [views setObject:properties forKey:view]; }
|
|
59
|
+
JoltInteractiveAnimation *previous = properties[keyPath];
|
|
60
|
+
[previous cancel];
|
|
61
|
+
// Cancelling the last property can remove the dictionary from the weak map.
|
|
62
|
+
[views setObject:properties forKey:view];
|
|
63
|
+
properties[keyPath] = track;
|
|
64
|
+
[track apply:track.elapsed];
|
|
65
|
+
[playing addObject:track]; [self schedule];
|
|
66
|
+
return track;
|
|
67
|
+
}
|
|
68
|
+
- (double)elapsed { return self.offset + (self.paused ? 0 : CACurrentMediaTime() * 1000 - self.anchor); }
|
|
69
|
+
- (double)value:(double)elapsed {
|
|
70
|
+
double total = self.delay + self.duration * (self.repeats + 1);
|
|
71
|
+
if (isfinite(total) && elapsed >= total) return self.finalValue;
|
|
72
|
+
double time = MAX(0, elapsed - self.delay);
|
|
73
|
+
double iteration = floor(time / self.duration);
|
|
74
|
+
double progress = fmod(time, self.duration) / self.duration;
|
|
75
|
+
if (self.reverse && fmod(iteration, 2) >= 1) progress = 1 - progress;
|
|
76
|
+
NSUInteger low = 0, high = self.times.count - 1;
|
|
77
|
+
while (low + 1 < high) {
|
|
78
|
+
NSUInteger middle = (low + high) / 2;
|
|
79
|
+
if (self.times[middle].doubleValue <= progress) low = middle; else high = middle;
|
|
80
|
+
}
|
|
81
|
+
double start = self.times[low].doubleValue, end = self.times[high].doubleValue;
|
|
82
|
+
double fraction = end > start ? MIN(1, MAX(0, (progress - start) / (end - start))) : 1;
|
|
83
|
+
return self.values[low].doubleValue + (self.values[high].doubleValue - self.values[low].doubleValue) * fraction;
|
|
84
|
+
}
|
|
85
|
+
- (void)apply:(double)elapsed {
|
|
86
|
+
UIView *view = self.view;
|
|
87
|
+
if (!view || self.stopped) return;
|
|
88
|
+
double value = [self value:elapsed];
|
|
89
|
+
[CATransaction begin]; [CATransaction setDisableActions:YES];
|
|
90
|
+
if ([self.keyPath isEqualToString:@"opacity"]) view.alpha = value;
|
|
91
|
+
for (CALayer *layer in self.layers) [layer setValue:@(value) forKeyPath:self.keyPath];
|
|
92
|
+
[CATransaction commit];
|
|
93
|
+
}
|
|
94
|
+
- (void)control:(double)elapsed paused:(BOOL)paused {
|
|
95
|
+
if (self.stopped) return;
|
|
96
|
+
self.offset = elapsed; self.anchor = CACurrentMediaTime() * 1000; self.paused = paused;
|
|
97
|
+
[self apply:elapsed];
|
|
98
|
+
if (paused) [playing removeObject:self]; else [playing addObject:self];
|
|
99
|
+
[[self class] schedule];
|
|
100
|
+
}
|
|
101
|
+
- (void)finish:(BOOL)completed {
|
|
102
|
+
if (self.stopped) return;
|
|
103
|
+
self.stopped = YES;
|
|
104
|
+
[playing removeObject:self];
|
|
105
|
+
UIView *view = self.view;
|
|
106
|
+
NSMutableDictionary *properties = view ? [views objectForKey:view] : nil;
|
|
107
|
+
if (properties[self.keyPath] == self) [properties removeObjectForKey:self.keyPath];
|
|
108
|
+
if (view && !properties.count) [views removeObjectForKey:view];
|
|
109
|
+
void (^callback)(BOOL) = self.completion; self.completion = nil;
|
|
110
|
+
[[self class] schedule];
|
|
111
|
+
if (callback) callback(completed);
|
|
112
|
+
}
|
|
113
|
+
- (void)cancel { [self finish:NO]; }
|
|
114
|
+
+ (void)syncView:(UIView *)view excluding:(NSString *)keyPath {
|
|
115
|
+
for (JoltInteractiveAnimation *track in [views objectForKey:view].allValues)
|
|
116
|
+
if (![track.keyPath isEqualToString:keyPath]) [track apply:track.elapsed];
|
|
117
|
+
}
|
|
118
|
+
+ (NSNumber *)valueForView:(UIView *)view keyPath:(NSString *)keyPath {
|
|
119
|
+
JoltInteractiveAnimation *track = [views objectForKey:view][keyPath];
|
|
120
|
+
return track ? [view.layer valueForKeyPath:keyPath] : nil;
|
|
121
|
+
}
|
|
122
|
+
+ (NSUInteger)activeCount { return playing.count; }
|
|
123
|
+
@end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
3
|
+
@interface JoltPhotoZoom : NSObject
|
|
4
|
+
+ (instancetype)attachImage:(UIImageView *)image viewport:(UIView *)viewport
|
|
5
|
+
minimum:(double)minimum maximum:(double)maximum doubleTap:(double)doubleTap
|
|
6
|
+
dismiss:(BOOL)dismiss reduced:(BOOL)reduced
|
|
7
|
+
backdrop:(nullable UIView *)backdrop chrome:(nullable UIView *)chrome
|
|
8
|
+
onDismiss:(void (^)(double,double,double,double,double))onDismiss
|
|
9
|
+
onSettled:(void (^)(double,double,double))onSettled;
|
|
10
|
+
@property(nonatomic, readonly) double x;
|
|
11
|
+
@property(nonatomic, readonly) double y;
|
|
12
|
+
@property(nonatomic, readonly) double scale;
|
|
13
|
+
@property(nonatomic, readonly) double dismissProgress;
|
|
14
|
+
@property(nonatomic, readonly) BOOL dismissPending;
|
|
15
|
+
- (void)zoom:(double)scale focusX:(double)x focusY:(double)y animated:(BOOL)animated;
|
|
16
|
+
- (void)reset:(BOOL)animated;
|
|
17
|
+
- (void)requestDismiss;
|
|
18
|
+
- (void)setReduced:(BOOL)reduced;
|
|
19
|
+
- (void)setActive:(BOOL)active;
|
|
20
|
+
- (void)layout;
|
|
21
|
+
- (void)stop;
|
|
22
|
+
@end
|
|
23
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#import "JoltPhotoZoom.h"
|
|
2
|
+
#import <QuartzCore/QuartzCore.h>
|
|
3
|
+
|
|
4
|
+
static double JoltClamp(double v,double lo,double hi){return MAX(lo,MIN(hi,v));}
|
|
5
|
+
static double JoltRubber(double v,double lo,double hi,double size){
|
|
6
|
+
double edge=JoltClamp(v,lo,hi),d=v-edge;
|
|
7
|
+
return edge+copysign(size*(1-1/(fabs(d)*.55/MAX(size,1)+1)),d);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@interface JoltPhotoZoom () <UIGestureRecognizerDelegate>
|
|
11
|
+
@property(nonatomic,weak) UIImageView *image;
|
|
12
|
+
@property(nonatomic,weak) UIView *viewport,*backdrop,*chrome;
|
|
13
|
+
@property(nonatomic,strong) UIPinchGestureRecognizer *pinch;
|
|
14
|
+
@property(nonatomic,strong) UIPanGestureRecognizer *pan;
|
|
15
|
+
@property(nonatomic,strong) UITapGestureRecognizer *tap;
|
|
16
|
+
@property(nonatomic,strong) CADisplayLink *clock;
|
|
17
|
+
@property(nonatomic,copy) void (^onDismiss)(double,double,double,double,double);
|
|
18
|
+
@property(nonatomic,copy) void (^onSettled)(double,double,double);
|
|
19
|
+
@property(nonatomic) CGAffineTransform original;
|
|
20
|
+
@property(nonatomic) BOOL originalInteraction,originalClip,reduced,active,disposed,dismiss,closing;
|
|
21
|
+
@property(nonatomic) double minimum,maximum,doubleTap,backdropAlpha,chromeAlpha;
|
|
22
|
+
@property(nonatomic,readwrite) double x,y,scale,dismissProgress;
|
|
23
|
+
@property(nonatomic,readwrite) BOOL dismissPending;
|
|
24
|
+
@property(nonatomic) CGPoint anchor,lastFocus,lastPan,initialTranslation;
|
|
25
|
+
@property(nonatomic) CGPoint rawPan;
|
|
26
|
+
@property(nonatomic) double pinchFloor;
|
|
27
|
+
@property(nonatomic) CGRect content;
|
|
28
|
+
@property(nonatomic) CGSize viewportSize,imageSize;
|
|
29
|
+
@property(nonatomic) NSInteger mode,touches;
|
|
30
|
+
@property(nonatomic) double vx,vy,startTime,startX,startY,startScale,startProgress,targetX,targetY,targetScale;
|
|
31
|
+
@end
|
|
32
|
+
|
|
33
|
+
@implementation JoltPhotoZoom
|
|
34
|
+
+ (instancetype)attachImage:(UIImageView *)image viewport:(UIView *)viewport
|
|
35
|
+
minimum:(double)minimum maximum:(double)maximum doubleTap:(double)doubleTap
|
|
36
|
+
dismiss:(BOOL)dismiss reduced:(BOOL)reduced backdrop:(UIView *)backdrop chrome:(UIView *)chrome
|
|
37
|
+
onDismiss:(void (^)(double,double,double,double,double))onDismiss
|
|
38
|
+
onSettled:(void (^)(double,double,double))onSettled {
|
|
39
|
+
JoltPhotoZoom *z=[JoltPhotoZoom new];
|
|
40
|
+
z.image=image;z.viewport=viewport;z.backdrop=backdrop;z.chrome=chrome;
|
|
41
|
+
z.minimum=minimum;z.maximum=maximum;z.doubleTap=doubleTap;z.dismiss=dismiss;z->_reduced=reduced;z.active=YES;
|
|
42
|
+
z.onDismiss=onDismiss;z.onSettled=onSettled;
|
|
43
|
+
z.original=image.transform;z.originalInteraction=viewport.userInteractionEnabled;z.originalClip=viewport.clipsToBounds;
|
|
44
|
+
z.backdropAlpha=backdrop?backdrop.alpha:1;z.chromeAlpha=chrome?chrome.alpha:1;z.scale=minimum;
|
|
45
|
+
z.pan=[[UIPanGestureRecognizer alloc] initWithTarget:z action:@selector(panned:)];
|
|
46
|
+
z.pinch=[[UIPinchGestureRecognizer alloc] initWithTarget:z action:@selector(pinched:)];
|
|
47
|
+
z.tap=[[UITapGestureRecognizer alloc] initWithTarget:z action:@selector(tapped:)];
|
|
48
|
+
z.tap.numberOfTapsRequired=2;z.pan.delegate=z;z.pinch.delegate=z;z.tap.delegate=z;
|
|
49
|
+
[viewport addGestureRecognizer:z.pan];[viewport addGestureRecognizer:z.pinch];[viewport addGestureRecognizer:z.tap];
|
|
50
|
+
viewport.userInteractionEnabled=YES;viewport.clipsToBounds=YES;
|
|
51
|
+
[z measure];[z apply];
|
|
52
|
+
return z;
|
|
53
|
+
}
|
|
54
|
+
- (void)measure {
|
|
55
|
+
self.viewportSize=self.viewport.bounds.size;self.imageSize=self.image.bounds.size;
|
|
56
|
+
CGSize intrinsic=self.image.image.size;
|
|
57
|
+
double iw=self.imageSize.width,ih=self.imageSize.height,dw=intrinsic.width,dh=intrinsic.height;
|
|
58
|
+
if(dw<=0 || dh<=0){dw=iw;dh=ih;}
|
|
59
|
+
UIViewContentMode fit=self.image.contentMode;
|
|
60
|
+
double factor=fit==UIViewContentModeScaleAspectFill?MAX(iw/dw,ih/dh):MIN(iw/dw,ih/dh);
|
|
61
|
+
if(fit==UIViewContentModeScaleToFill){dw=iw;dh=ih;}
|
|
62
|
+
else if(fit!=UIViewContentModeCenter && fit!=UIViewContentModeTopLeft){dw*=factor;dh*=factor;}
|
|
63
|
+
CGPoint pivot=CGPointMake(self.image.layer.anchorPoint.x*iw,self.image.layer.anchorPoint.y*ih);
|
|
64
|
+
self.anchor=CGPointMake(self.image.layer.position.x+self.original.tx,self.image.layer.position.y+self.original.ty);
|
|
65
|
+
double ox=fit==UIViewContentModeTopLeft?0:(iw-dw)/2,oy=fit==UIViewContentModeTopLeft?0:(ih-dh)/2;
|
|
66
|
+
double left=MAX(0,ox),top=MAX(0,oy),right=MIN(iw,ox+dw),bottom=MIN(ih,oy+dh);
|
|
67
|
+
self.content=CGRectMake((left-pivot.x)*self.original.a,(top-pivot.y)*self.original.d,(right-left)*self.original.a,(bottom-top)*self.original.d);
|
|
68
|
+
}
|
|
69
|
+
- (CGRect)boundsFor:(double)scale {
|
|
70
|
+
double loX=self.viewportSize.width-self.anchor.x-CGRectGetMaxX(self.content)*scale;
|
|
71
|
+
double hiX=-self.anchor.x-CGRectGetMinX(self.content)*scale;
|
|
72
|
+
double loY=self.viewportSize.height-self.anchor.y-CGRectGetMaxY(self.content)*scale;
|
|
73
|
+
double hiY=-self.anchor.y-CGRectGetMinY(self.content)*scale;
|
|
74
|
+
if(loX>hiX)loX=hiX=(loX+hiX)/2;
|
|
75
|
+
if(loY>hiY)loY=hiY=(loY+hiY)/2;
|
|
76
|
+
return CGRectMake(loX,loY,hiX-loX,hiY-loY);
|
|
77
|
+
}
|
|
78
|
+
- (void)apply {
|
|
79
|
+
self.image.transform=CGAffineTransformMake(self.original.a*self.scale,0,0,self.original.d*self.scale,self.original.tx+self.x,self.original.ty+self.y);
|
|
80
|
+
self.backdrop.alpha=self.backdropAlpha*(1-self.dismissProgress);
|
|
81
|
+
self.chrome.alpha=self.chromeAlpha*(1-self.dismissProgress);
|
|
82
|
+
}
|
|
83
|
+
- (void)zoom:(double)value focusX:(double)fx focusY:(double)fy animated:(BOOL)animated {
|
|
84
|
+
if(self.disposed || self.dismissPending)return;
|
|
85
|
+
[self cancelAnimation];double next=JoltClamp(value,self.minimum,self.maximum);
|
|
86
|
+
double x=fx-self.anchor.x-(fx-self.anchor.x-self.x)*next/self.scale;
|
|
87
|
+
double y=fy-self.anchor.y-(fy-self.anchor.y-self.y)*next/self.scale;
|
|
88
|
+
CGRect b=[self boundsFor:next];
|
|
89
|
+
[self springX:JoltClamp(x,CGRectGetMinX(b),CGRectGetMaxX(b)) y:JoltClamp(y,CGRectGetMinY(b),CGRectGetMaxY(b)) scale:next vx:0 vy:0 animated:animated close:NO];
|
|
90
|
+
}
|
|
91
|
+
- (void)reset:(BOOL)animated {
|
|
92
|
+
if(self.disposed)return;
|
|
93
|
+
self.dismissPending=NO;self.mode=0;CGRect b=[self boundsFor:self.minimum];
|
|
94
|
+
[self springX:JoltClamp(0,CGRectGetMinX(b),CGRectGetMaxX(b)) y:JoltClamp(0,CGRectGetMinY(b),CGRectGetMaxY(b)) scale:self.minimum vx:0 vy:0 animated:animated close:NO];
|
|
95
|
+
}
|
|
96
|
+
- (void)requestDismiss {
|
|
97
|
+
if(self.disposed || self.dismissPending || !self.dismiss)return;
|
|
98
|
+
CGRect b=[self boundsFor:self.minimum];
|
|
99
|
+
[self springX:JoltClamp(0,CGRectGetMinX(b),CGRectGetMaxX(b)) y:JoltClamp(0,CGRectGetMinY(b),CGRectGetMaxY(b)) scale:self.minimum vx:0 vy:0 animated:YES close:YES];
|
|
100
|
+
}
|
|
101
|
+
- (void)setReduced:(BOOL)reduced { _reduced=reduced;if(reduced){if(self.dismissPending)[self reset:NO];else [self settle:NO];} }
|
|
102
|
+
- (void)setActive:(BOOL)active {
|
|
103
|
+
_active=active;self.pan.enabled=active;self.pinch.enabled=active;self.tap.enabled=active;
|
|
104
|
+
if(!active){[self cancelAnimation];self.dismissPending=NO;[self reset:NO];}
|
|
105
|
+
}
|
|
106
|
+
- (void)layout {
|
|
107
|
+
if(self.disposed)return;
|
|
108
|
+
if(CGSizeEqualToSize(self.viewportSize,self.viewport.bounds.size) && CGSizeEqualToSize(self.imageSize,self.image.bounds.size))return;
|
|
109
|
+
[self cancelAnimation];self.dismissPending=NO;[self measure];self.vx=0;self.vy=0;self.mode=0;[self settle:NO];
|
|
110
|
+
}
|
|
111
|
+
- (void)stop {
|
|
112
|
+
if(self.disposed)return;
|
|
113
|
+
self.disposed=YES;[self cancelAnimation];
|
|
114
|
+
[self.viewport removeGestureRecognizer:self.pan];[self.viewport removeGestureRecognizer:self.pinch];[self.viewport removeGestureRecognizer:self.tap];
|
|
115
|
+
self.viewport.userInteractionEnabled=self.originalInteraction;self.viewport.clipsToBounds=self.originalClip;
|
|
116
|
+
self.image.transform=self.original;self.backdrop.alpha=self.backdropAlpha;self.chrome.alpha=self.chromeAlpha;
|
|
117
|
+
self.onDismiss=nil;self.onSettled=nil;
|
|
118
|
+
}
|
|
119
|
+
- (void)cancelAnimation { [self.clock invalidate];self.clock=nil;self.closing=NO; }
|
|
120
|
+
- (void)springX:(double)x y:(double)y scale:(double)scale vx:(double)vx vy:(double)vy animated:(BOOL)animated close:(BOOL)close {
|
|
121
|
+
[self cancelAnimation];
|
|
122
|
+
if(!animated || self.reduced){
|
|
123
|
+
self.x=x;self.y=y;self.scale=scale;self.dismissProgress=0;[self apply];
|
|
124
|
+
if(close)[self emitDismiss:0];else if(self.onSettled)self.onSettled(x,y,scale);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
self.startX=self.x;self.startY=self.y;self.startScale=self.scale;self.startProgress=self.dismissProgress;
|
|
128
|
+
self.targetX=x;self.targetY=y;self.targetScale=scale;self.vx=vx;self.vy=vy;self.closing=close;
|
|
129
|
+
self.startTime=CACurrentMediaTime();self.clock=[CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
|
|
130
|
+
self.clock.preferredFramesPerSecond=MIN(120,self.viewport.window.screen.maximumFramesPerSecond);
|
|
131
|
+
[self.clock addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
|
|
132
|
+
}
|
|
133
|
+
- (void)tick:(CADisplayLink *)clock {
|
|
134
|
+
double t=MIN(.55,MAX(0,CACurrentMediaTime()-self.startTime)),decay=exp(-16*t);
|
|
135
|
+
self.x=self.targetX+((self.startX-self.targetX)+(self.vx+16*(self.startX-self.targetX))*t)*decay;
|
|
136
|
+
self.y=self.targetY+((self.startY-self.targetY)+(self.vy+16*(self.startY-self.targetY))*t)*decay;
|
|
137
|
+
self.scale=self.targetScale+(self.startScale-self.targetScale)*(1+16*t)*decay;
|
|
138
|
+
self.dismissProgress=self.startProgress*(1+16*t)*decay;
|
|
139
|
+
if(t>=.55){self.x=self.targetX;self.y=self.targetY;self.scale=self.targetScale;self.dismissProgress=0;}
|
|
140
|
+
[self apply];
|
|
141
|
+
if(t>=.55){BOOL close=self.closing;[self cancelAnimation];if(close)[self emitDismiss:0];else if(self.onSettled)self.onSettled(self.x,self.y,self.scale);}
|
|
142
|
+
}
|
|
143
|
+
- (void)settle:(BOOL)animated {
|
|
144
|
+
if(self.disposed)return;
|
|
145
|
+
double s=JoltClamp(self.scale,self.minimum,self.maximum);CGRect b=[self boundsFor:s];
|
|
146
|
+
double x=JoltClamp(self.x+self.vx*.12,CGRectGetMinX(b),CGRectGetMaxX(b));
|
|
147
|
+
double y=JoltClamp(self.y+self.vy*.12,CGRectGetMinY(b),CGRectGetMaxY(b));
|
|
148
|
+
if(self.mode==2){s=self.minimum;b=[self boundsFor:s];x=JoltClamp(0,CGRectGetMinX(b),CGRectGetMaxX(b));y=JoltClamp(0,CGRectGetMinY(b),CGRectGetMaxY(b));}
|
|
149
|
+
[self springX:x y:y scale:s vx:self.vx vy:self.vy animated:animated close:NO];
|
|
150
|
+
}
|
|
151
|
+
- (void)emitDismiss:(double)velocity {
|
|
152
|
+
if(self.disposed || self.dismissPending)return;
|
|
153
|
+
self.dismissPending=YES;
|
|
154
|
+
if(self.onDismiss)self.onDismiss(self.x,self.y,self.scale,velocity,self.dismissProgress);
|
|
155
|
+
}
|
|
156
|
+
- (void)pinched:(UIPinchGestureRecognizer *)gesture {
|
|
157
|
+
if(self.disposed || self.dismissPending)return;
|
|
158
|
+
CGPoint focus=[gesture locationInView:self.viewport];
|
|
159
|
+
if(gesture.state==UIGestureRecognizerStateBegan){
|
|
160
|
+
[self cancelAnimation];self.mode=1;self.pinchFloor=MIN(self.minimum,self.scale);self.lastFocus=focus;gesture.scale=1;
|
|
161
|
+
} else if(gesture.state==UIGestureRecognizerStateChanged){
|
|
162
|
+
double next=JoltClamp(self.scale*gesture.scale,self.pinchFloor,self.maximum);
|
|
163
|
+
self.x=focus.x-self.anchor.x-(self.lastFocus.x-self.anchor.x-self.x)*next/self.scale;
|
|
164
|
+
self.y=focus.y-self.anchor.y-(self.lastFocus.y-self.anchor.y-self.y)*next/self.scale;
|
|
165
|
+
self.scale=next;self.lastFocus=focus;gesture.scale=1;
|
|
166
|
+
self.dismissProgress=JoltClamp((self.minimum-self.scale)/(self.minimum*.15),0,1);
|
|
167
|
+
[self rebasePan];[self apply];
|
|
168
|
+
} else if(gesture.state==UIGestureRecognizerStateEnded || gesture.state==UIGestureRecognizerStateCancelled){
|
|
169
|
+
self.lastPan=[self.pan translationInView:self.viewport];self.touches=self.pan.numberOfTouches;
|
|
170
|
+
self.vx=0;self.vy=0;
|
|
171
|
+
if(self.pan.state!=UIGestureRecognizerStateChanged && self.pan.state!=UIGestureRecognizerStateBegan)[self settle:YES];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
- (void)panned:(UIPanGestureRecognizer *)gesture {
|
|
175
|
+
if(self.disposed || self.dismissPending)return;
|
|
176
|
+
CGPoint translation=[gesture translationInView:self.viewport];
|
|
177
|
+
if(gesture.state==UIGestureRecognizerStateBegan){
|
|
178
|
+
[self cancelAnimation];self.vx=0;self.vy=0;self.lastPan=CGPointZero;self.touches=gesture.numberOfTouches;
|
|
179
|
+
[self rebasePan];
|
|
180
|
+
self.mode=self.scale<=self.minimum+.001 && self.pinch.state!=UIGestureRecognizerStateBegan && self.pinch.state!=UIGestureRecognizerStateChanged?2:1;
|
|
181
|
+
}
|
|
182
|
+
if(gesture.state==UIGestureRecognizerStateBegan || gesture.state==UIGestureRecognizerStateChanged){
|
|
183
|
+
if(self.pinch.state==UIGestureRecognizerStateBegan || self.pinch.state==UIGestureRecognizerStateChanged || self.touches!=gesture.numberOfTouches){
|
|
184
|
+
self.lastPan=translation;self.touches=gesture.numberOfTouches;[self rebasePan];return;
|
|
185
|
+
}
|
|
186
|
+
double dx=translation.x-self.lastPan.x,dy=translation.y-self.lastPan.y;
|
|
187
|
+
self.lastPan=translation;
|
|
188
|
+
if(self.mode==2){
|
|
189
|
+
self.x+=dx*.4;self.y+=dy;self.dismissProgress=JoltClamp(fabs(self.y)/MAX(self.viewportSize.height*.5,1),0,1);
|
|
190
|
+
self.scale=self.minimum*(1-self.dismissProgress*.15);
|
|
191
|
+
}else{
|
|
192
|
+
CGRect b=[self boundsFor:self.scale];
|
|
193
|
+
self.rawPan=CGPointMake(self.rawPan.x+dx,self.rawPan.y+dy);
|
|
194
|
+
self.x=JoltRubber(self.rawPan.x,CGRectGetMinX(b),CGRectGetMaxX(b),self.viewportSize.width);
|
|
195
|
+
self.y=JoltRubber(self.rawPan.y,CGRectGetMinY(b),CGRectGetMaxY(b),self.viewportSize.height);
|
|
196
|
+
}
|
|
197
|
+
[self apply];
|
|
198
|
+
}else if(gesture.state==UIGestureRecognizerStateEnded || gesture.state==UIGestureRecognizerStateCancelled || gesture.state==UIGestureRecognizerStateFailed){
|
|
199
|
+
CGPoint velocity=[gesture velocityInView:self.viewport];self.vx=velocity.x;self.vy=velocity.y;
|
|
200
|
+
BOOL commit=gesture.state==UIGestureRecognizerStateEnded && self.mode==2 &&
|
|
201
|
+
(self.dismissProgress>.35 || (fabs(velocity.y)>1000 && velocity.y*self.y>0));
|
|
202
|
+
if(commit)[self emitDismiss:velocity.y];
|
|
203
|
+
else if(self.pinch.state!=UIGestureRecognizerStateChanged && self.pinch.state!=UIGestureRecognizerStateBegan)[self settle:YES];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
- (void)tapped:(UITapGestureRecognizer *)gesture {
|
|
207
|
+
if(gesture.state!=UIGestureRecognizerStateEnded || self.dismissPending)return;
|
|
208
|
+
CGPoint point=[gesture locationInView:self.viewport];
|
|
209
|
+
[self zoom:self.scale>self.minimum+.01?self.minimum:MIN(self.maximum,self.doubleTap) focusX:point.x focusY:point.y animated:YES];
|
|
210
|
+
}
|
|
211
|
+
- (void)rebasePan {
|
|
212
|
+
CGRect b=[self boundsFor:self.scale];
|
|
213
|
+
double ex=JoltClamp(self.x,CGRectGetMinX(b),CGRectGetMaxX(b)),ey=JoltClamp(self.y,CGRectGetMinY(b),CGRectGetMaxY(b));
|
|
214
|
+
double dx=self.x-ex,dy=self.y-ey;
|
|
215
|
+
self.rawPan=CGPointMake(ex+dx/(.55*MAX(.001,1-fabs(dx)/MAX(1,self.viewportSize.width))),
|
|
216
|
+
ey+dy/(.55*MAX(.001,1-fabs(dy)/MAX(1,self.viewportSize.height))));
|
|
217
|
+
}
|
|
218
|
+
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gesture {
|
|
219
|
+
if(self.disposed || !self.active || self.dismissPending)return NO;
|
|
220
|
+
if(gesture==self.pan && self.scale<=self.minimum+.001){
|
|
221
|
+
CGPoint velocity=[self.pan velocityInView:self.viewport];
|
|
222
|
+
return self.dismiss && fabs(velocity.y)>fabs(velocity.x)*1.2;
|
|
223
|
+
}
|
|
224
|
+
return YES;
|
|
225
|
+
}
|
|
226
|
+
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)one shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)other {
|
|
227
|
+
return (one==self.pan && other==self.pinch)||(one==self.pinch && other==self.pan);
|
|
228
|
+
}
|
|
229
|
+
@end
|
package/src/android-plugin.d.ts
CHANGED
|
@@ -1,4 +1,82 @@
|
|
|
1
1
|
declare namespace org.rafaelaugusto.jolt {
|
|
2
|
+
class PhotoZoom {
|
|
3
|
+
constructor(
|
|
4
|
+
image: android.widget.ImageView,
|
|
5
|
+
viewport: android.view.View,
|
|
6
|
+
bitmapWidth: number,
|
|
7
|
+
bitmapHeight: number,
|
|
8
|
+
minimum: number,
|
|
9
|
+
maximum: number,
|
|
10
|
+
doubleTap: number,
|
|
11
|
+
dismiss: boolean,
|
|
12
|
+
reduced: boolean,
|
|
13
|
+
backdrop: android.view.View | null,
|
|
14
|
+
chrome: android.view.View | null,
|
|
15
|
+
previousTouch: android.view.View.OnTouchListener | null,
|
|
16
|
+
previousImageTouch: android.view.View.OnTouchListener | null,
|
|
17
|
+
listener: PhotoZoom.Listener,
|
|
18
|
+
)
|
|
19
|
+
getX(): number
|
|
20
|
+
getY(): number
|
|
21
|
+
getScale(): number
|
|
22
|
+
getDismissProgress(): number
|
|
23
|
+
isDismissPending(): boolean
|
|
24
|
+
zoom(scale: number, x: number, y: number, animated: boolean): void
|
|
25
|
+
reset(animated: boolean): void
|
|
26
|
+
requestDismiss(): void
|
|
27
|
+
setReduced(value: boolean): void
|
|
28
|
+
setActive(value: boolean): void
|
|
29
|
+
stop(): void
|
|
30
|
+
}
|
|
31
|
+
namespace PhotoZoom {
|
|
32
|
+
class Listener {
|
|
33
|
+
constructor(implementation: {
|
|
34
|
+
onDismiss(
|
|
35
|
+
x: number,
|
|
36
|
+
y: number,
|
|
37
|
+
scale: number,
|
|
38
|
+
velocity: number,
|
|
39
|
+
progress: number,
|
|
40
|
+
): void
|
|
41
|
+
onSettled(x: number, y: number, scale: number): void
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
class ImageEffect {
|
|
46
|
+
constructor(
|
|
47
|
+
image: android.widget.ImageView,
|
|
48
|
+
source: android.graphics.Bitmap | null,
|
|
49
|
+
destination: android.graphics.Bitmap | null,
|
|
50
|
+
geometry: androidNative.Array<number>,
|
|
51
|
+
mode: number,
|
|
52
|
+
x: number,
|
|
53
|
+
y: number,
|
|
54
|
+
amplitude: number,
|
|
55
|
+
radius: number,
|
|
56
|
+
duration: number,
|
|
57
|
+
seed: number,
|
|
58
|
+
previousTouch: android.view.View.OnTouchListener | null,
|
|
59
|
+
listener: ImageEffect.Listener,
|
|
60
|
+
)
|
|
61
|
+
isSupported(): boolean
|
|
62
|
+
getFailureReason(): string
|
|
63
|
+
getProgress(): number
|
|
64
|
+
getFrames(): number
|
|
65
|
+
getTextureBytes(): number
|
|
66
|
+
getLongestFrameNanos(): number
|
|
67
|
+
play(endpoint: number): void
|
|
68
|
+
pause(): void
|
|
69
|
+
resume(): void
|
|
70
|
+
seek(progress: number): void
|
|
71
|
+
finish(endpoint: number): void
|
|
72
|
+
stop(): void
|
|
73
|
+
stopPreservingImage(): void
|
|
74
|
+
}
|
|
75
|
+
namespace ImageEffect {
|
|
76
|
+
class Listener {
|
|
77
|
+
constructor(implementation: { onEnd(completed: boolean, progress: number): void })
|
|
78
|
+
}
|
|
79
|
+
}
|
|
2
80
|
class KeyboardFollower {
|
|
3
81
|
constructor(
|
|
4
82
|
view: android.view.View,
|
package/src/animate.ts
CHANGED
|
@@ -67,6 +67,15 @@ export function animate(
|
|
|
67
67
|
animation: Keyframes | PresetName,
|
|
68
68
|
options?: AnimateOptions | SpringName,
|
|
69
69
|
): PlaybackAnimation {
|
|
70
|
+
return prepareAnimation(target, animation, options)()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Validate and snapshot a complete animation before changing any view ownership. */
|
|
74
|
+
export function prepareAnimation(
|
|
75
|
+
target: Target | readonly Target[],
|
|
76
|
+
animation: Keyframes | PresetName,
|
|
77
|
+
options?: AnimateOptions | SpringName,
|
|
78
|
+
): () => PlaybackAnimation {
|
|
70
79
|
const { keyframes, base, overrides, stagger, relative, haptic, path } = prepare(
|
|
71
80
|
animation,
|
|
72
81
|
options,
|
|
@@ -78,11 +87,8 @@ export function animate(
|
|
|
78
87
|
for (const property of Object.keys(keyframes))
|
|
79
88
|
if (isColorProperty(property)) validateColorTarget(view, property)
|
|
80
89
|
else if (isDecorationProperty(property)) validateDecorationTarget(view, property)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
plan(keyframes, base, overrides, 0, relative, path),
|
|
84
|
-
feedback,
|
|
85
|
-
)
|
|
90
|
+
const plans = plan(keyframes, base, overrides, 0, relative, path)
|
|
91
|
+
return () => new ViewAnimation(view, plans, feedback)
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
const views = target.map(toView)
|
|
@@ -100,9 +106,10 @@ export function animate(
|
|
|
100
106
|
path,
|
|
101
107
|
),
|
|
102
108
|
)
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
return () =>
|
|
110
|
+
playbackGroup(
|
|
111
|
+
views.map((view, index) => new ViewAnimation(view, plans[index]!, feedback, true)),
|
|
112
|
+
)
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
/** Starts at the current translation and slows down from a velocity in DIPs per second. */
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Utils, type PanGestureEventData, type View } from '@nativescript/core'
|
|
2
|
+
import { getDriver } from './driver/index.js'
|
|
3
|
+
|
|
4
|
+
interface Sample {
|
|
5
|
+
x: number
|
|
6
|
+
y: number
|
|
7
|
+
time: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Android's raw coordinates avoid feedback from translating the touched view. */
|
|
11
|
+
export class DragInput {
|
|
12
|
+
private samples: Sample[] = []
|
|
13
|
+
private lastMoved = 0
|
|
14
|
+
reset() {
|
|
15
|
+
this.samples = []
|
|
16
|
+
this.lastMoved = 0
|
|
17
|
+
}
|
|
18
|
+
read(event: PanGestureEventData, view: View) {
|
|
19
|
+
const ios = event.ios as UIPanGestureRecognizer | undefined
|
|
20
|
+
const android = event.android?.current as android.view.MotionEvent | undefined
|
|
21
|
+
const parent = view.parent?.nativeViewProtected ?? view.nativeViewProtected
|
|
22
|
+
const delta = ios
|
|
23
|
+
? ios.translationInView(parent)
|
|
24
|
+
: { x: event.deltaX, y: event.deltaY }
|
|
25
|
+
const density = Utils.layout.getDisplayDensity()
|
|
26
|
+
const sample: Sample = android
|
|
27
|
+
? {
|
|
28
|
+
x: android.getRawX() / density,
|
|
29
|
+
y: android.getRawY() / density,
|
|
30
|
+
time: Number(android.getEventTime()),
|
|
31
|
+
}
|
|
32
|
+
: { ...delta, time: getDriver().now() }
|
|
33
|
+
if (
|
|
34
|
+
Number.isFinite(sample.x) &&
|
|
35
|
+
Number.isFinite(sample.y) &&
|
|
36
|
+
Number.isFinite(sample.time)
|
|
37
|
+
) {
|
|
38
|
+
const previous = this.samples.at(-1)
|
|
39
|
+
if (!previous || sample.x !== previous.x || sample.y !== previous.y)
|
|
40
|
+
this.lastMoved = sample.time
|
|
41
|
+
// Replace duplicate times (Android sends began + changed for the same event).
|
|
42
|
+
if (this.samples.at(-1)?.time === sample.time) this.samples.pop()
|
|
43
|
+
this.samples.push(sample)
|
|
44
|
+
while (this.samples.length > 2 && this.samples[1]!.time < sample.time - 100)
|
|
45
|
+
this.samples.shift()
|
|
46
|
+
}
|
|
47
|
+
const first = this.samples[0]
|
|
48
|
+
const elapsed = first ? sample.time - first.time : 0
|
|
49
|
+
// UIKit may retain its last movement velocity when the finger holds still
|
|
50
|
+
// before lifting. A stale velocity must not turn that hold into a fling.
|
|
51
|
+
const nativeVelocity = ios
|
|
52
|
+
? sample.time - this.lastMoved > 100
|
|
53
|
+
? { x: 0, y: 0 }
|
|
54
|
+
: ios.velocityInView(parent)
|
|
55
|
+
: undefined
|
|
56
|
+
const velocity = nativeVelocity ?? {
|
|
57
|
+
x: elapsed > 0 && elapsed <= 150 ? ((sample.x - first!.x) * 1000) / elapsed : 0,
|
|
58
|
+
y: elapsed > 0 && elapsed <= 150 ? ((sample.y - first!.y) * 1000) / elapsed : 0,
|
|
59
|
+
}
|
|
60
|
+
const action = android?.getActionMasked()
|
|
61
|
+
return {
|
|
62
|
+
x: delta.x,
|
|
63
|
+
y: delta.y,
|
|
64
|
+
velocityX: Number.isFinite(velocity.x) ? velocity.x : 0,
|
|
65
|
+
velocityY: Number.isFinite(velocity.y) ? velocity.y : 0,
|
|
66
|
+
cancelled:
|
|
67
|
+
event.state === 0 ||
|
|
68
|
+
action === 3 ||
|
|
69
|
+
action === 5 ||
|
|
70
|
+
action === 6 ||
|
|
71
|
+
(android ? android.getPointerCount() > 1 : (ios?.numberOfTouches ?? 1) > 1),
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|