/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.uimanager.events;
import android.view.MotionEvent;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.PixelUtil;
/**
* Class responsible for generating catalyst touch events based on android {@link MotionEvent}.
*/
/*package*/ class TouchesHelper {
private static final String PAGE_X_KEY = "pageX";
private static final String PAGE_Y_KEY = "pageY";
private static final String TARGET_KEY = "target";
private static final String TIMESTAMP_KEY = "timestamp";
private static final String POINTER_IDENTIFIER_KEY = "identifier";
private static final String LOCATION_X_KEY = "locationX";
private static final String LOCATION_Y_KEY = "locationY";
/**
* Creates catalyst pointers array in format that is expected by RCTEventEmitter JS module from
* given {@param event} instance. This method use {@param reactTarget} parameter to set as a
* target view id associated with current gesture.
*/
private static WritableArray createsPointersArray(int reactTarget, TouchEvent event) {
WritableArray touches = Arguments.createArray();
MotionEvent motionEvent = event.getMotionEvent();
// Calculate the coordinates for the target view.
// The MotionEvent contains the X,Y of the touch in the coordinate space of the root view
// The TouchEvent contains the X,Y of the touch in the coordinate space of the target view
// Subtracting them allows us to get the coordinates of the target view's top left corner
// We then use this when computing the view specific touches below
// Since only one view is actually handling even multiple touches, the values are all relative
// to this one target view.
float targetViewCoordinateX = motionEvent.getX() - event.getViewX();
float targetViewCoordinateY = motionEvent.getY() - event.getViewY();
for (int index = 0; index < motionEvent.getPointerCount(); index++) {
WritableMap touch = Arguments.createMap();
// pageX,Y values are relative to the RootReactView
// the motionEvent already contains coordinates in that view
touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(motionEvent.getX(index)));
touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(motionEvent.getY(index)));
// locationX,Y values are relative to the target view
// To compute the values for the view, we subtract that views location from the event X,Y
float locationX = motionEvent.getX(index) - targetViewCoordinateX;
float locationY = motionEvent.getY(index) - targetViewCoordinateY;
touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(locationX));
touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(locationY));
touch.putInt(TARGET_KEY, reactTarget);
touch.putDouble(TIMESTAMP_KEY, event.getTimestampMs());
touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index));
touches.pushMap(touch);
}
return touches;
}
/**
* Generate and send touch event to RCTEventEmitter JS module associated with the given
* {@param context}. Touch event can encode multiple concurrent touches (pointers).
*
* @param rctEventEmitter Event emitter used to execute JS module call
* @param type type of the touch event (see {@link TouchEventType})
* @param reactTarget target view react id associated with this gesture
* @param touchEvent native touch event to read pointers count and coordinates from
*/
public static void sendTouchEvent(
RCTEventEmitter rctEventEmitter,
TouchEventType type,
int reactTarget,
TouchEvent touchEvent) {
WritableArray pointers = createsPointersArray(reactTarget, touchEvent);
MotionEvent motionEvent = touchEvent.getMotionEvent();
// For START and END events send only index of the pointer that is associated with that event
// For MOVE and CANCEL events 'changedIndices' array should contain all the pointers indices
WritableArray changedIndices = Arguments.createArray();
if (type == TouchEventType.MOVE || type == TouchEventType.CANCEL) {
for (int i = 0; i < motionEvent.getPointerCount(); i++) {
changedIndices.pushInt(i);
}
} else if (type == TouchEventType.START || type == TouchEventType.END) {
changedIndices.pushInt(motionEvent.getActionIndex());
} else {
throw new RuntimeException("Unknown touch type: " + type);
}
rctEventEmitter.receiveTouches(
type.getJSEventName(),
pointers,
changedIndices);
}
}