diff --git a/MacOSExample/src/App.tsx b/MacOSExample/src/App.tsx index e928ef4893..b398ab6fc2 100644 --- a/MacOSExample/src/App.tsx +++ b/MacOSExample/src/App.tsx @@ -13,6 +13,7 @@ import { import Draggable from './basic/draggable'; import PinchableBox from './recipes/scaleAndRotate'; import Tap from './basic/tap'; +import LongPressExample from './basic/longPress'; import ManualExample from './basic/manual'; interface Example { @@ -32,6 +33,7 @@ const EXAMPLES: ExamplesSection[] = [ { name: 'Draggable', component: Draggable }, { name: 'Pinch & rotate', component: PinchableBox }, { name: 'Tap', component: Tap }, + { name: 'LongPress', component: LongPressExample }, { name: 'Manual', component: ManualExample }, ], }, diff --git a/MacOSExample/src/basic/longPress/index.tsx b/MacOSExample/src/basic/longPress/index.tsx new file mode 100644 index 0000000000..fcb4099fe0 --- /dev/null +++ b/MacOSExample/src/basic/longPress/index.tsx @@ -0,0 +1,103 @@ +import { StyleSheet, View } from 'react-native'; +import { Gesture, GestureDetector } from 'react-native-gesture-handler'; +import Animated, { + interpolateColor, + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; + +const Durations = { + LongPress: 750, + Reset: 350, + Scale: 120, +}; + +const Colors = { + Initial: '#0a2688', + Loading: '#6fcef5', + Success: '#32a852', + Fail: '#b02525', +}; + +export default function LongPressExample() { + const isPressed = useSharedValue(false); + const colorProgress = useSharedValue(0); + const color1 = useSharedValue(Colors.Initial); + const color2 = useSharedValue(Colors.Loading); + + const animatedStyles = useAnimatedStyle(() => { + const backgroundColor = interpolateColor( + colorProgress.value, + [0, 1], + [color1.value, color2.value] + ); + + return { + transform: [ + { + scale: withTiming(isPressed.value ? 1.2 : 1, { + duration: Durations.Scale, + }), + }, + ], + backgroundColor, + }; + }); + + const g = Gesture.LongPress() + .onBegin(() => { + console.log('onBegin'); + + isPressed.value = true; + colorProgress.value = withTiming(1, { + duration: Durations.LongPress, + }); + }) + .onStart(() => console.log('onStart')) + .onEnd(() => console.log('onEnd')) + .onFinalize((_, success) => { + console.log('onFinalize', success); + + isPressed.value = false; + + color1.value = Colors.Initial; + color2.value = success ? Colors.Success : Colors.Fail; + + colorProgress.value = withTiming( + 0, + { + duration: Durations.Reset, + }, + () => { + color2.value = Colors.Loading; + } + ); + }) + .onTouchesDown(() => console.log('onTouchesDown')) + .onTouchesMove(() => console.log('onTouchesMove')) + .onTouchesUp(() => console.log('onTouchesUp')) + .minDuration(Durations.LongPress); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'space-around', + alignItems: 'center', + }, + + pressBox: { + width: 100, + height: 100, + borderRadius: 20, + }, +}); diff --git a/apple/Handlers/RNLongPressHandler.m b/apple/Handlers/RNLongPressHandler.m index cd6e5dba9c..153fea91d7 100644 --- a/apple/Handlers/RNLongPressHandler.m +++ b/apple/Handlers/RNLongPressHandler.m @@ -7,22 +7,36 @@ // #import "RNLongPressHandler.h" +#import #if !TARGET_OS_OSX #import -#import - @interface RNBetterLongPressGestureRecognizer : UILongPressGestureRecognizer { +#else +@interface RNBetterLongPressGestureRecognizer : NSGestureRecognizer { + dispatch_block_t block; +#endif + CFTimeInterval startTime; CFTimeInterval previousTime; } +#if TARGET_OS_OSX +@property (nonatomic, assign) double minimumPressDuration; +@property (nonatomic, assign) double allowableMovement; +#endif + - (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler; -- (void)handleGesture:(UIGestureRecognizer *)recognizer; - (NSUInteger)getDuration; +#if !TARGET_OS_OSX +- (void)handleGesture:(UIGestureRecognizer *)recognizer; +#else +- (void)handleGesture:(NSGestureRecognizer *)recognizer; +#endif + @end @implementation RNBetterLongPressGestureRecognizer { @@ -55,6 +69,8 @@ - (CGPoint)translationInView return CGPointMake(currentPosition.x - _initPosition.x, currentPosition.y - _initPosition.y); } +#if !TARGET_OS_OSX + - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [_gestureHandler setCurrentPointerType:event]; @@ -72,10 +88,7 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event [super touchesMoved:touches withEvent:event]; [_gestureHandler.pointerTracker touchesMoved:touches withEvent:event]; - CGPoint trans = [self translationInView]; - if ((_gestureHandler.shouldCancelWhenOutside && ![_gestureHandler containsPointInView]) || - (TEST_MAX_IF_NOT_NAN( - fabs(trans.y * trans.y + trans.x * trans.x), self.allowableMovement * self.allowableMovement))) { + if ([self shouldCancelGesture]) { self.enabled = NO; self.enabled = YES; } @@ -94,6 +107,75 @@ - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)ev [self reset]; } +#else +- (void)mouseDown:(NSEvent *)event +{ + self.state = NSGestureRecognizerStateBegan; + startTime = CACurrentMediaTime(); + + [_gestureHandler.pointerTracker touchesBegan:[NSSet setWithObject:event] withEvent:event]; + + _initPosition = [self locationInView:self.view]; + + __weak typeof(self) weakSelf = self; + + block = dispatch_block_create(0, ^{ + __strong typeof(self) strongSelf = weakSelf; + + if (strongSelf) { + strongSelf.state = NSGestureRecognizerStateChanged; + } + }); + + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.minimumPressDuration * NSEC_PER_SEC)), + dispatch_get_main_queue(), + block); +} + +- (void)mouseDragged:(NSEvent *)event +{ + [_gestureHandler.pointerTracker touchesMoved:[NSSet setWithObject:event] withEvent:event]; + + if (block == nil) { + return; + } + + if ([self shouldCancelGesture]) { + dispatch_block_cancel(block); + block = nil; + + self.state = self.state == NSGestureRecognizerStateChanged ? NSGestureRecognizerStateCancelled + : NSGestureRecognizerStateFailed; + } +} + +- (void)mouseUp:(NSEvent *)event +{ + [_gestureHandler.pointerTracker touchesEnded:[NSSet setWithObject:event] withEvent:event]; + + if (block) { + dispatch_block_cancel(block); + block = nil; + } + + self.state = + self.state == NSGestureRecognizerStateChanged ? NSGestureRecognizerStateEnded : NSGestureRecognizerStateFailed; +} + +#endif + +- (BOOL)shouldCancelGesture +{ + CGPoint trans = [self translationInView]; + + BOOL shouldBeCancelledOutside = _gestureHandler.shouldCancelWhenOutside && ![_gestureHandler containsPointInView]; + BOOL distanceExceeded = + TEST_MAX_IF_NOT_NAN(fabs(trans.y * trans.y + trans.x * trans.x), self.allowableMovement * self.allowableMovement); + + return shouldBeCancelledOutside || distanceExceeded; +} + - (void)reset { if (self.state == UIGestureRecognizerStateFailed) { @@ -126,7 +208,7 @@ - (instancetype)initWithTag:(NSNumber *)tag - (void)resetConfig { [super resetConfig]; - UILongPressGestureRecognizer *recognizer = (UILongPressGestureRecognizer *)_recognizer; + RNBetterLongPressGestureRecognizer *recognizer = (RNBetterLongPressGestureRecognizer *)_recognizer; recognizer.minimumPressDuration = 0.5; recognizer.allowableMovement = 10; @@ -135,7 +217,7 @@ - (void)resetConfig - (void)configure:(NSDictionary *)config { [super configure:config]; - UILongPressGestureRecognizer *recognizer = (UILongPressGestureRecognizer *)_recognizer; + RNBetterLongPressGestureRecognizer *recognizer = (RNBetterLongPressGestureRecognizer *)_recognizer; id prop = config[@"minDurationMs"]; if (prop != nil) { @@ -148,6 +230,8 @@ - (void)configure:(NSDictionary *)config } } +#if !TARGET_OS_OSX + - (RNGestureHandlerState)state { // For long press recognizer we treat "Began" state as "active" @@ -178,21 +262,17 @@ - (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recogn withDuration:[(RNBetterLongPressGestureRecognizer *)recognizer getDuration] withPointerType:_pointerType]; } -@end #else -@implementation RNLongPressGestureHandler - -- (instancetype)initWithTag:(NSNumber *)tag +- (RNGestureHandlerEventExtraData *)eventExtraData:(NSGestureRecognizer *)recognizer { - RCTLogWarn(@"LongPressGestureHandler is not supported on macOS"); - if ((self = [super initWithTag:tag])) { - _recognizer = [NSGestureRecognizer alloc]; - } - return self; + return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:recognizer.view] + withAbsolutePosition:[recognizer locationInView:recognizer.view.window.contentView] + withNumberOfTouches:1 + withDuration:[(RNBetterLongPressGestureRecognizer *)recognizer getDuration] + withPointerType:RNGestureHandlerMouse]; } -@end - #endif +@end