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

Improve interop touches by using UIScrollView-like strategy #1440

Merged
merged 31 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2e7f94e
Move state logic to Kotlin
elijah-semyonov Jul 9, 2024
0df0c23
Setup golden path
elijah-semyonov Jul 9, 2024
8b0a412
Setup golden path
elijah-semyonov Jul 9, 2024
200df35
Manually stop tracking when failing
elijah-semyonov Jul 9, 2024
f4f0ebd
Minor refactor
elijah-semyonov Jul 9, 2024
3554031
Minor refactor
elijah-semyonov Jul 9, 2024
0f6c275
Setup failure scheduling
elijah-semyonov Jul 10, 2024
5740501
Add doc
elijah-semyonov Jul 10, 2024
f286942
Minor change
elijah-semyonov Jul 10, 2024
c0e22b8
Minor change
elijah-semyonov Jul 10, 2024
f5bfacc
Implement everything
elijah-semyonov Jul 11, 2024
b5b672d
Remove prints
elijah-semyonov Jul 11, 2024
f97fc46
Remove prints
elijah-semyonov Jul 11, 2024
1678443
Remove prints
elijah-semyonov Jul 11, 2024
3a08659
Remove redundant import
elijah-semyonov Jul 11, 2024
733fb01
Remove comment
elijah-semyonov Jul 11, 2024
40d8d03
Remove debug prints
elijah-semyonov Jul 12, 2024
288b3f9
Remove spaces
elijah-semyonov Jul 12, 2024
82dde9f
Add comment
elijah-semyonov Jul 12, 2024
cc88ed0
Change the link
elijah-semyonov Jul 12, 2024
d5caf40
Move expressions for consistency
elijah-semyonov Jul 12, 2024
9c9fa41
Rename for clarity
elijah-semyonov Jul 12, 2024
f2ebb8f
Extract CUPERTINO_PAN_GESTURE_SLOP_VALUE
elijah-semyonov Jul 12, 2024
325473f
Change name
elijah-semyonov Jul 12, 2024
65338d0
Update compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitU…
elijah-semyonov Jul 12, 2024
65b8397
Rename
elijah-semyonov Jul 12, 2024
ced8856
Modify
elijah-semyonov Jul 12, 2024
7d59be2
Update compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/…
elijah-semyonov Jul 12, 2024
db4cc40
Update compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/…
elijah-semyonov Jul 12, 2024
ac5a031
Rename
elijah-semyonov Jul 12, 2024
92ea0a3
Fix typo
elijah-semyonov Jul 12, 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
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,24 @@
#import <UIKit/UIGestureRecognizerSubclass.h>

NS_ASSUME_NONNULL_BEGIN

@protocol CMPGestureRecognizerHandler <NSObject>

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sry, it looks like here some formatting issues with spaces.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent * _Nullable)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent * _Nullable)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent * _Nullable)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent * _Nullable)event;
- (BOOL)shouldRecognizeSimultaneously:(UIGestureRecognizer *)first withOther:(UIGestureRecognizer *)second;
- (void)onFailure;

@end

@interface CMPGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>

@property (weak, nonatomic) id <CMPGestureRecognizerHandler> handler;

- (void)cancelFailure;
- (void)scheduleFailure;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -7,82 +7,110 @@

#import "CMPGestureRecognizer.h"

@implementation CMPGestureRecognizer
@implementation CMPGestureRecognizer {
dispatch_block_t _scheduledFailureBlock;
}

- (instancetype)init {
self = [super init];

if (self) {
self.cancelsTouchesInView = false;
if (self) {
self.delegate = self;
[self addTarget:self action:@selector(handleStateChange)];
}

return self;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
id <CMPGestureRecognizerHandler> handler = self.handler;
- (void)handleStateChange {
switch (self.state) {
case UIGestureRecognizerStateBegan:
NSLog(@"state = Began");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems like logs should be removed

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed

break;

case UIGestureRecognizerStateChanged:
NSLog(@"state = Changed");
break;

case UIGestureRecognizerStateEnded:
[self cancelFailure];
break;

case UIGestureRecognizerStateCancelled:
[self cancelFailure];
break;
elijah-semyonov marked this conversation as resolved.
Show resolved Hide resolved

default:
break;
}
}

- (BOOL)shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
UIView *view = self.view;
UIView *otherView = otherGestureRecognizer.view;

if (handler) {
return [handler shouldRecognizeSimultaneously:gestureRecognizer withOther:otherGestureRecognizer];
} else {
if (view == nil || otherView == nil) {
return NO;
}

if ([otherView isDescendantOfView:view]) {
return NO;
} else {
return YES;
}
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.handler touchesBegan:touches withEvent:event];
- (BOOL)shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return NO;
}

- (BOOL)shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}

- (void)cancelFailure {
if (_scheduledFailureBlock) {
dispatch_block_cancel(_scheduledFailureBlock);
_scheduledFailureBlock = NULL;
}
}

- (void)fail {
[self.handler onFailure];
}

- (void)scheduleFailure {
__weak typeof(self) weakSelf = self;
dispatch_block_t dispatchBlock = dispatch_block_create(0, ^{
[weakSelf fail];
});

if (self.state == UIGestureRecognizerStatePossible) {
self.state = UIGestureRecognizerStateBegan;
if (_scheduledFailureBlock) {
dispatch_block_cancel(_scheduledFailureBlock);
}
_scheduledFailureBlock = dispatchBlock;

// Calculate the delay time in dispatch_time_t
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, move 0.15 to constants. Or add some meaningful comment because it looks like it require some explanation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to a local variable with an explanation


// Schedule the block to be executed after the delay on the main queue
dispatch_after(delay, dispatch_get_main_queue(), dispatchBlock);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.handler touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.handler touchesMoved:touches withEvent:event];

switch (self.state) {
case UIGestureRecognizerStateBegan:
case UIGestureRecognizerStateChanged:
self.state = UIGestureRecognizerStateChanged;
break;
default:
break;
}
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.handler touchesEnded:touches withEvent:event];

switch (self.state) {
case UIGestureRecognizerStateBegan:
case UIGestureRecognizerStateChanged:
if (self.numberOfTouches == 0) {
self.state = UIGestureRecognizerStateEnded;
} else {
self.state = UIGestureRecognizerStateChanged;
}
break;
default:
break;
}
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.handler touchesCancelled:touches withEvent:event];

switch (self.state) {
case UIGestureRecognizerStateBegan:
case UIGestureRecognizerStateChanged:
if (self.numberOfTouches == 0) {
self.state = UIGestureRecognizerStateCancelled;
} else {
self.state = UIGestureRecognizerStateChanged;
}
break;
default:
break;
}
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import platform.CoreGraphics.CGRect
import platform.CoreGraphics.CGRectMake
import platform.CoreGraphics.CGRectZero
import platform.CoreGraphics.CGSize
import platform.QuartzCore.CACurrentMediaTime
import platform.QuartzCore.CATransaction
import platform.UIKit.NSLayoutConstraint
import platform.UIKit.UIEvent
Expand Down Expand Up @@ -367,29 +368,41 @@ internal class ComposeSceneMediator(
* @param event the [UIEvent] associated with the touches
* @param phase the [CupertinoTouchesPhase] of the touches
*/
private fun onTouchesEvent(view: UIView, touches: Set<*>, event: UIEvent, phase: CupertinoTouchesPhase) {
private fun onTouchesEvent(view: UIView, touches: Set<*>, event: UIEvent?, phase: CupertinoTouchesPhase) {
val pointers = touches.map {
val touch = it as UITouch
val id = touch.hashCode().toLong()
val position = touch.offsetInView(view, density.density)
ComposeScenePointer(
id = PointerId(id),
position = position,
pressed = touch.isPressed,
pressed = when (phase) {
// When CMPGestureRecognizer is failed, all tracked touches are sent immediately
// as CANCELLED. In this case, we should not consider the touch as pressed
// despite them being on the screen. This is the last event for Compose in a
// given gesture sequence and should be treated as such.
CupertinoTouchesPhase.CANCELLED -> false
else -> touch.isPressed
},
type = PointerType.Touch,
pressure = touch.force.toFloat(),
historical = event.historicalChangesForTouch(
historical = event?.historicalChangesForTouch(
touch,
view,
density.density
)
) ?: emptyList()
)
} ?: emptyList()
}

// If the touches were cancelled due to gesture failure, the timestamp is not available,
// because no actual event with touch updates happened. We just use the current time in
// this case.
val timestamp = event?.timestamp ?: CACurrentMediaTime()

scene.sendPointerEvent(
eventType = phase.toPointerEventType(),
pointers = pointers,
timeMillis = (event.timestamp * 1e3).toLong(),
timeMillis = (timestamp * 1e3).toLong(),
nativeEvent = event
)
}
Expand Down
Loading