Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[macOS] Add FlingGestureHandler #3028

Merged
merged 27 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ab06d73
Add Vector
m-bert Aug 5, 2024
21d4f1d
Something works something doesn't
m-bert Aug 5, 2024
347d1fa
It works
m-bert Aug 5, 2024
97e93ff
Merge branch 'main' into @mbert/add-fling-macos
m-bert Aug 6, 2024
a40a00b
Merge branch 'main' into @mbert/add-fling-macos
m-bert Aug 13, 2024
3925e82
Cancel timoeut on mouseUp
m-bert Aug 13, 2024
f915313
Adjust duration
m-bert Aug 14, 2024
4c86068
It works
m-bert Aug 14, 2024
9e71b5a
Merge handler implemantations
m-bert Aug 14, 2024
52612ca
Add example
m-bert Aug 14, 2024
3fcac48
Update signature
m-bert Aug 20, 2024
880e830
Change recognizer state
m-bert Aug 20, 2024
79ab75c
Remove duplicate implementation
m-bert Aug 20, 2024
1570c9e
Fix mixed names
m-bert Aug 20, 2024
61ee3c2
Merge branch 'main' into @mbert/add-fling-macos
m-bert Aug 20, 2024
a9bb4f6
Change enum values
m-bert Aug 21, 2024
6406cd0
Check axial directions first
m-bert Aug 21, 2024
6900228
Safely calculate unit vector
m-bert Aug 21, 2024
ba5dab8
Merge branch 'main' into @mbert/add-fling-macos
m-bert Aug 21, 2024
e3cc87f
Change eps name
m-bert Aug 21, 2024
e09f18e
add lacking macos package to project root
latekvo Aug 21, 2024
f585293
Revert "add lacking macos package to project root"
latekvo Aug 21, 2024
6a04fd6
Use total movement and total time
m-bert Aug 22, 2024
36cfbad
Merge branch '@mbert/add-fling-macos' of github.com:software-mansion/…
m-bert Aug 22, 2024
36d4c96
Merge branch 'main' into @mbert/add-fling-macos
m-bert Aug 22, 2024
9a3737c
Change fields to readonly
m-bert Aug 22, 2024
d85a156
Merge branch 'main' into @mbert/add-fling-macos
m-bert Aug 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions MacOSExample/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Tap from './basic/tap';
import LongPressExample from './basic/longPress';
import ManualExample from './basic/manual';
import HoverExample from './basic/hover';
import FlingExample from './basic/fling';

interface Example {
name: string;
Expand All @@ -37,6 +38,7 @@ const EXAMPLES: ExamplesSection[] = [
{ name: 'LongPress', component: LongPressExample },
{ name: 'Manual', component: ManualExample },
{ name: 'Hover', component: HoverExample },
{ name: 'Fling', component: FlingExample },
],
},
];
Expand Down
90 changes: 90 additions & 0 deletions MacOSExample/src/basic/fling/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { StyleSheet, View } from 'react-native';
import {
Directions,
Gesture,
GestureDetector,
} from 'react-native-gesture-handler';
import Animated, {
interpolateColor,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';

const AnimationDuration = 100;

const Colors = {
Initial: '#0a2688',
Active: '#6fcef5',
};

export default function FlingExample() {
const isActive = useSharedValue(false);
const colorProgress = useSharedValue(0);
const color1 = useSharedValue(Colors.Initial);
const color2 = useSharedValue(Colors.Active);

const animatedStyles = useAnimatedStyle(() => {
const backgroundColor = interpolateColor(
colorProgress.value,
[0, 1],
[color1.value, color2.value]
);

return {
transform: [
{
scale: withTiming(isActive.value ? 1.2 : 1, {
duration: AnimationDuration,
}),
},
],
backgroundColor,
};
});

const g = Gesture.Fling()
.direction(Directions.LEFT | Directions.UP)
.onBegin(() => {
console.log('onBegin');
})
.onStart(() => {
console.log('onStart');
isActive.value = true;
colorProgress.value = withTiming(1, {
duration: AnimationDuration,
});
})
.onEnd(() => console.log('onEnd'))
.onFinalize((_, success) => {
console.log('onFinalize', success);

isActive.value = false;

colorProgress.value = withTiming(0, {
duration: AnimationDuration,
});
});

return (
<View style={styles.container}>
<GestureDetector gesture={g}>
<Animated.View style={[styles.box, animatedStyles]} />
</GestureDetector>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'space-around',
alignItems: 'center',
},

box: {
width: 100,
height: 100,
borderRadius: 20,
},
});
1 change: 1 addition & 0 deletions apple/Handlers/RNFlingHandler.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#import "../RNGHVector.h"
#import "RNGestureHandler.h"

@interface RNFlingGestureHandler : RNGestureHandler
Expand Down
172 changes: 153 additions & 19 deletions apple/Handlers/RNFlingHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,154 @@ - (CGPoint)getLastLocation

@end

#else

@interface RNBetterSwipeGestureRecognizer : NSGestureRecognizer {
dispatch_block_t failFlingAction;
int maxDuration;
int minVelocity;
double defaultAlignmentCone;
double axialDeviationCosine;
double diagonalDeviationCosine;
}

@property (atomic, assign) RNGestureHandlerDirection direction;
@property (atomic, assign) int numberOfTouchesRequired;

- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;

@end

@implementation RNBetterSwipeGestureRecognizer {
__weak RNGestureHandler *_gestureHandler;

NSPoint startPosition;
double startTime;
}

- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
{
if ((self = [super initWithTarget:self action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;

maxDuration = 1.0;
minVelocity = 700;

defaultAlignmentCone = 30;
axialDeviationCosine = [self coneToDeviation:defaultAlignmentCone];
diagonalDeviationCosine = [self coneToDeviation:(90 - defaultAlignmentCone)];
}
return self;
}

- (void)handleGesture:(NSPanGestureRecognizer *)gestureRecognizer
{
[_gestureHandler handleGesture:self];
}

- (void)mouseDown:(NSEvent *)event
{
[super mouseDown:event];

startPosition = [self locationInView:self.view];
startTime = CACurrentMediaTime();

self.state = NSGestureRecognizerStatePossible;

__weak typeof(self) weakSelf = self;

failFlingAction = dispatch_block_create(0, ^{
__strong typeof(self) strongSelf = weakSelf;

if (strongSelf) {
strongSelf.state = NSGestureRecognizerStateFailed;
}
});

dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(maxDuration * NSEC_PER_SEC)),
dispatch_get_main_queue(),
failFlingAction);
}

- (void)mouseDragged:(NSEvent *)event
{
[super mouseDragged:event];

NSPoint currentPosition = [self locationInView:self.view];
double currentTime = CACurrentMediaTime();

NSPoint distance;
distance.x = currentPosition.x - startPosition.x;
distance.y = startPosition.y - currentPosition.y;

double timeDelta = currentTime - startTime;

Vector *velocityVector = [Vector fromVelocityX:(distance.x / timeDelta) withVelocityY:(distance.y / timeDelta)];

[self tryActivate:velocityVector];
}

- (void)mouseUp:(NSEvent *)event
{
[super mouseUp:event];

dispatch_block_cancel(failFlingAction);

self.state =
self.state == NSGestureRecognizerStateChanged ? NSGestureRecognizerStateEnded : NSGestureRecognizerStateFailed;
}

- (void)tryActivate:(Vector *)velocityVector
{
bool isAligned = NO;

for (int i = 0; i < directionsSize; ++i) {
if ([self getAlignment:axialDirections[i]
withMinimalAlignmentCosine:axialDeviationCosine
withVelocityVector:velocityVector]) {
isAligned = YES;
break;
}
}

if (!isAligned) {
for (int i = 0; i < directionsSize; ++i) {
if ([self getAlignment:diagonalDirections[i]
withMinimalAlignmentCosine:diagonalDeviationCosine
withVelocityVector:velocityVector]) {
isAligned = YES;
break;
}
}
}

bool isFastEnough = velocityVector.magnitude >= minVelocity;

if (isAligned && isFastEnough) {
self.state = NSGestureRecognizerStateChanged;
}
}

- (BOOL)getAlignment:(RNGestureHandlerDirection)direction
withMinimalAlignmentCosine:(double)minimalAlignmentCosine
withVelocityVector:(Vector *)velocityVector
{
Vector *directionVector = [Vector fromDirection:direction];
return ((self.direction & direction) == direction) &&
[velocityVector isSimilar:directionVector withThreshold:minimalAlignmentCosine];
}

- (double)coneToDeviation:(double)degrees
{
double radians = (degrees * M_PI) / 180;
return cos(radians / 2);
}

@end

#endif

@implementation RNFlingGestureHandler
j-piasecki marked this conversation as resolved.
Show resolved Hide resolved

- (instancetype)initWithTag:(NSNumber *)tag
Expand All @@ -100,8 +248,8 @@ - (instancetype)initWithTag:(NSNumber *)tag
- (void)resetConfig
{
[super resetConfig];
UISwipeGestureRecognizer *recognizer = (UISwipeGestureRecognizer *)_recognizer;
recognizer.direction = UISwipeGestureRecognizerDirectionRight;
RNBetterSwipeGestureRecognizer *recognizer = (RNBetterSwipeGestureRecognizer *)_recognizer;
recognizer.direction = RNGestureHandlerDirectionRight;
#if !TARGET_OS_TV
recognizer.numberOfTouchesRequired = 1;
#endif
Expand All @@ -110,7 +258,7 @@ - (void)resetConfig
- (void)configure:(NSDictionary *)config
{
[super configure:config];
UISwipeGestureRecognizer *recognizer = (UISwipeGestureRecognizer *)_recognizer;
RNBetterSwipeGestureRecognizer *recognizer = (RNBetterSwipeGestureRecognizer *)_recognizer;

id prop = config[@"direction"];
if (prop != nil) {
Expand All @@ -125,6 +273,7 @@ - (void)configure:(NSDictionary *)config
#endif
}

#if !TARGET_OS_OSX
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
RNGestureHandlerState savedState = _lastState;
Expand Down Expand Up @@ -154,21 +303,6 @@ - (RNGestureHandlerEventExtraData *)eventExtraData:(id)_recognizer
withNumberOfTouches:recognizer.numberOfTouches
withPointerType:_pointerType];
}
@end

#else

@implementation RNFlingGestureHandler

- (instancetype)initWithTag:(NSNumber *)tag
{
RCTLogWarn(@"FlingGestureHandler is not supported on macOS");
if ((self = [super initWithTag:tag])) {
_recognizer = [NSGestureRecognizer alloc];
}
return self;
}
#endif

@end

#endif
31 changes: 31 additions & 0 deletions apple/RNGHVector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// RNGHVector.h
// Pods
//
// Created by Michał Bert on 05/08/2024.
//

#import "RNGestureHandlerDirection.h"

#ifndef RNGHVector_h
#define RNGHVector_h

@interface Vector : NSObject

@property (atomic, readonly, assign) double x;
@property (atomic, readonly, assign) double y;
@property (atomic, readonly, assign) double unitX;
@property (atomic, readonly, assign) double unitY;
@property (atomic, readonly, assign) double magnitude;

+ (Vector *_Nonnull)fromDirection:(RNGestureHandlerDirection)direction;
+ (Vector *_Nonnull)fromVelocityX:(double)vx withVelocityY:(double)vy;
- (nonnull instancetype)initWithX:(double)x withY:(double)y;
- (double)computeSimilarity:(Vector *_Nonnull)other;
- (BOOL)isSimilar:(Vector *_Nonnull)other withThreshold:(double)threshold;

@end

static double MINIMAL_RECOGNIZABLE_MAGNITUDE = 0.1;

#endif /* RNGHVector_h */
Loading
Loading