diff --git a/src/apis/PanResponder/index.js b/src/apis/PanResponder/index.js index e893ac86..882851a3 100644 --- a/src/apis/PanResponder/index.js +++ b/src/apis/PanResponder/index.js @@ -1,394 +1,2 @@ -/** - * Copyright (c) 2016-present, Nicolas Gallagher. - * 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. - * - * @providesModule PanResponder - * @noflow - */ - -import TouchHistoryMath from '../../vendor/TouchHistoryMath'; - -const currentCentroidXOfTouchesChangedAfter = - TouchHistoryMath.currentCentroidXOfTouchesChangedAfter; -const currentCentroidYOfTouchesChangedAfter = - TouchHistoryMath.currentCentroidYOfTouchesChangedAfter; -const previousCentroidXOfTouchesChangedAfter = - TouchHistoryMath.previousCentroidXOfTouchesChangedAfter; -const previousCentroidYOfTouchesChangedAfter = - TouchHistoryMath.previousCentroidYOfTouchesChangedAfter; -const currentCentroidX = TouchHistoryMath.currentCentroidX; -const currentCentroidY = TouchHistoryMath.currentCentroidY; - -/** - * `PanResponder` reconciles several touches into a single gesture. It makes - * single-touch gestures resilient to extra touches, and can be used to - * recognize simple multi-touch gestures. - * - * It provides a predictable wrapper of the responder handlers provided by the - * [gesture responder system](docs/gesture-responder-system.html). - * For each handler, it provides a new `gestureState` object alongside the - * native event object: - * - * ``` - * onPanResponderMove: (event, gestureState) => {} - * ``` - * - * A native event is a synthetic touch event with the following form: - * - * - `nativeEvent` - * + `changedTouches` - Array of all touch events that have changed since the last event - * + `identifier` - The ID of the touch - * + `locationX` - The X position of the touch, relative to the element - * + `locationY` - The Y position of the touch, relative to the element - * + `pageX` - The X position of the touch, relative to the root element - * + `pageY` - The Y position of the touch, relative to the root element - * + `target` - The node id of the element receiving the touch event - * + `timestamp` - A time identifier for the touch, useful for velocity calculation - * + `touches` - Array of all current touches on the screen - * - * A `gestureState` object has the following: - * - * - `stateID` - ID of the gestureState- persisted as long as there at least - * one touch on screen - * - `moveX` - the latest screen coordinates of the recently-moved touch - * - `moveY` - the latest screen coordinates of the recently-moved touch - * - `x0` - the screen coordinates of the responder grant - * - `y0` - the screen coordinates of the responder grant - * - `dx` - accumulated distance of the gesture since the touch started - * - `dy` - accumulated distance of the gesture since the touch started - * - `vx` - current velocity of the gesture - * - `vy` - current velocity of the gesture - * - `numberActiveTouches` - Number of touches currently on screen - * - * ### Basic Usage - * - * ``` - * componentWillMount: function() { - * this._panResponder = PanResponder.create({ - * // Ask to be the responder: - * onStartShouldSetPanResponder: (evt, gestureState) => true, - * onStartShouldSetPanResponderCapture: (evt, gestureState) => true, - * onMoveShouldSetPanResponder: (evt, gestureState) => true, - * onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, - * - * onPanResponderGrant: (evt, gestureState) => { - * // The guesture has started. Show visual feedback so the user knows - * // what is happening! - * - * // gestureState.{x,y}0 will be set to zero now - * }, - * onPanResponderMove: (evt, gestureState) => { - * // The most recent move distance is gestureState.move{X,Y} - * - * // The accumulated gesture distance since becoming responder is - * // gestureState.d{x,y} - * }, - * onPanResponderTerminationRequest: (evt, gestureState) => true, - * onPanResponderRelease: (evt, gestureState) => { - * // The user has released all touches while this view is the - * // responder. This typically means a gesture has succeeded - * }, - * onPanResponderTerminate: (evt, gestureState) => { - * // Another component has become the responder, so this gesture - * // should be cancelled - * }, - * onShouldBlockNativeResponder: (evt, gestureState) => { - * // Returns whether this component should block native components from becoming the JS - * // responder. Returns true by default. Is currently only supported on android. - * return true; - * }, - * }); - * }, - * - * render: function() { - * return ( - * - * ); - * }, - * - * ``` - * - * ### Working Example - * - * To see it in action, try the - * [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/PanResponderExample.js) - */ - -const PanResponder = { - /** - * - * A graphical explanation of the touch data flow: - * - * +----------------------------+ +--------------------------------+ - * | ResponderTouchHistoryStore | |TouchHistoryMath | - * +----------------------------+ +----------+---------------------+ - * |Global store of touchHistory| |Allocation-less math util | - * |including activeness, start | |on touch history (centroids | - * |position, prev/cur position.| |and multitouch movement etc) | - * | | | | - * +----^-----------------------+ +----^---------------------------+ - * | | - * | (records relevant history | - * | of touches relevant for | - * | implementing higher level | - * | gestures) | - * | | - * +----+-----------------------+ +----|---------------------------+ - * | ResponderEventPlugin | | | Your App/Component | - * +----------------------------+ +----|---------------------------+ - * |Negotiates which view gets | Low level | | High level | - * |onResponderMove events. | events w/ | +-+-------+ events w/ | - * |Also records history into | touchHistory| | Pan | multitouch + | - * |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative| - * +----------------------------+ attached to | | | distance and | - * each event | +---------+ velocity. | - * | | - * | | - * +--------------------------------+ - * - * - * - * Gesture that calculates cumulative movement over time in a way that just - * "does the right thing" for multiple touches. The "right thing" is very - * nuanced. When moving two touches in opposite directions, the cumulative - * distance is zero in each dimension. When two touches move in parallel five - * pixels in the same direction, the cumulative distance is five, not ten. If - * two touches start, one moves five in a direction, then stops and the other - * touch moves fives in the same direction, the cumulative distance is ten. - * - * This logic requires a kind of processing of time "clusters" of touch events - * so that two touch moves that essentially occur in parallel but move every - * other frame respectively, are considered part of the same movement. - * - * Explanation of some of the non-obvious fields: - * - * - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is - * invalid. If a move event has been observed, `(moveX, moveY)` is the - * centroid of the most recently moved "cluster" of active touches. - * (Currently all move have the same timeStamp, but later we should add some - * threshold for what is considered to be "moving"). If a palm is - * accidentally counted as a touch, but a finger is moving greatly, the palm - * will move slightly, but we only want to count the single moving touch. - * - x0/y0: Centroid location (non-cumulative) at the time of becoming - * responder. - * - dx/dy: Cumulative touch distance - not the same thing as sum of each touch - * distance. Accounts for touch moves that are clustered together in time, - * moving the same direction. Only valid when currently responder (otherwise, - * it only represents the drag distance below the threshold). - * - vx/vy: Velocity. - */ - - _initializeGestureState: function(gestureState) { - gestureState.moveX = 0; - gestureState.moveY = 0; - gestureState.x0 = 0; - gestureState.y0 = 0; - gestureState.dx = 0; - gestureState.dy = 0; - gestureState.vx = 0; - gestureState.vy = 0; - gestureState.numberActiveTouches = 0; - // All `gestureState` accounts for timeStamps up until: - gestureState._accountsForMovesUpTo = 0; - }, - - /** - * This is nuanced and is necessary. It is incorrect to continuously take all - * active *and* recently moved touches, find the centroid, and track how that - * result changes over time. Instead, we must take all recently moved - * touches, and calculate how the centroid has changed just for those - * recently moved touches, and append that change to an accumulator. This is - * to (at least) handle the case where the user is moving three fingers, and - * then one of the fingers stops but the other two continue. - * - * This is very different than taking all of the recently moved touches and - * storing their centroid as `dx/dy`. For correctness, we must *accumulate - * changes* in the centroid of recently moved touches. - * - * There is also some nuance with how we handle multiple moved touches in a - * single event. With the way `ReactNativeEventEmitter` dispatches touches as - * individual events, multiple touches generate two 'move' events, each of - * them triggering `onResponderMove`. But with the way `PanResponder` works, - * all of the gesture inference is performed on the first dispatch, since it - * looks at all of the touches (even the ones for which there hasn't been a - * native dispatch yet). Therefore, `PanResponder` does not call - * `onResponderMove` passed the first dispatch. This diverges from the - * typical responder callback pattern (without using `PanResponder`), but - * avoids more dispatches than necessary. - */ - _updateGestureStateOnMove: function(gestureState, touchHistory) { - gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - gestureState.moveX = currentCentroidXOfTouchesChangedAfter( - touchHistory, - gestureState._accountsForMovesUpTo - ); - gestureState.moveY = currentCentroidYOfTouchesChangedAfter( - touchHistory, - gestureState._accountsForMovesUpTo - ); - const movedAfter = gestureState._accountsForMovesUpTo; - const prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter); - const x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter); - const prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter); - const y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter); - const nextDX = gestureState.dx + (x - prevX); - const nextDY = gestureState.dy + (y - prevY); - - // TODO: This must be filtered intelligently. - const dt = touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo; - gestureState.vx = (nextDX - gestureState.dx) / dt; - gestureState.vy = (nextDY - gestureState.dy) / dt; - - gestureState.dx = nextDX; - gestureState.dy = nextDY; - gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp; - }, - - /** - * @param {object} config Enhanced versions of all of the responder callbacks - * that provide not only the typical `ResponderSyntheticEvent`, but also the - * `PanResponder` gesture state. Simply replace the word `Responder` with - * `PanResponder` in each of the typical `onResponder*` callbacks. For - * example, the `config` object would look like: - * - * - `onMoveShouldSetPanResponder: (e, gestureState) => {...}` - * - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}` - * - `onStartShouldSetPanResponder: (e, gestureState) => {...}` - * - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}` - * - `onPanResponderReject: (e, gestureState) => {...}` - * - `onPanResponderGrant: (e, gestureState) => {...}` - * - `onPanResponderStart: (e, gestureState) => {...}` - * - `onPanResponderEnd: (e, gestureState) => {...}` - * - `onPanResponderRelease: (e, gestureState) => {...}` - * - `onPanResponderMove: (e, gestureState) => {...}` - * - `onPanResponderTerminate: (e, gestureState) => {...}` - * - `onPanResponderTerminationRequest: (e, gestureState) => {...}` - * - `onShouldBlockNativeResponder: (e, gestureState) => {...}` - * - * In general, for events that have capture equivalents, we update the - * gestureState once in the capture phase and can use it in the bubble phase - * as well. - * - * Be careful with onStartShould* callbacks. They only reflect updated - * `gestureState` for start/end events that bubble/capture to the Node. - * Once the node is the responder, you can rely on every start/end event - * being processed by the gesture and `gestureState` being updated - * accordingly. (numberActiveTouches) may not be totally accurate unless you - * are the responder. - */ - create: function(config) { - const gestureState = { - // Useful for debugging - stateID: Math.random() - }; - PanResponder._initializeGestureState(gestureState); - const panHandlers = { - onStartShouldSetResponder: function(e) { - return config.onStartShouldSetPanResponder === undefined - ? false - : config.onStartShouldSetPanResponder(e, gestureState); - }, - onMoveShouldSetResponder: function(e) { - return config.onMoveShouldSetPanResponder === undefined - ? false - : config.onMoveShouldSetPanResponder(e, gestureState); - }, - onStartShouldSetResponderCapture: function(e) { - // TODO: Actually, we should reinitialize the state any time - // touches.length increases from 0 active to > 0 active. - if (e.nativeEvent.touches) { - if (e.nativeEvent.touches.length === 1) { - PanResponder._initializeGestureState(gestureState); - } - } else if ( - e.nativeEvent.originalEvent && - e.nativeEvent.originalEvent.type === 'mousedown' - ) { - PanResponder._initializeGestureState(gestureState); - } - gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches; - return config.onStartShouldSetPanResponderCapture !== undefined - ? config.onStartShouldSetPanResponderCapture(e, gestureState) - : false; - }, - - onMoveShouldSetResponderCapture: function(e) { - const touchHistory = e.touchHistory; - // Responder system incorrectly dispatches should* to current responder - // Filter out any touch moves past the first one - we would have - // already processed multi-touch geometry during the first event. - if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) { - return false; - } - PanResponder._updateGestureStateOnMove(gestureState, touchHistory); - return config.onMoveShouldSetPanResponderCapture - ? config.onMoveShouldSetPanResponderCapture(e, gestureState) - : false; - }, - - onResponderGrant: function(e) { - gestureState.x0 = currentCentroidX(e.touchHistory); - gestureState.y0 = currentCentroidY(e.touchHistory); - gestureState.dx = 0; - gestureState.dy = 0; - config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState); - // TODO: t7467124 investigate if this can be removed - return config.onShouldBlockNativeResponder === undefined - ? true - : config.onShouldBlockNativeResponder(); - }, - - onResponderReject: function(e) { - config.onPanResponderReject && config.onPanResponderReject(e, gestureState); - }, - - onResponderRelease: function(e) { - config.onPanResponderRelease && config.onPanResponderRelease(e, gestureState); - PanResponder._initializeGestureState(gestureState); - }, - - onResponderStart: function(e) { - const touchHistory = e.touchHistory; - gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - config.onPanResponderStart && config.onPanResponderStart(e, gestureState); - }, - - onResponderMove: function(e) { - const touchHistory = e.touchHistory; - // Guard against the dispatch of two touch moves when there are two - // simultaneously changed touches. - if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) { - return; - } - // Filter out any touch moves past the first one - we would have - // already processed multi-touch geometry during the first event. - PanResponder._updateGestureStateOnMove(gestureState, touchHistory); - config.onPanResponderMove && config.onPanResponderMove(e, gestureState); - }, - - onResponderEnd: function(e) { - const touchHistory = e.touchHistory; - gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - config.onPanResponderEnd && config.onPanResponderEnd(e, gestureState); - }, - - onResponderTerminate: function(e) { - config.onPanResponderTerminate && config.onPanResponderTerminate(e, gestureState); - PanResponder._initializeGestureState(gestureState); - }, - - onResponderTerminationRequest: function(e) { - return config.onPanResponderTerminationRequest === undefined - ? true - : config.onPanResponderTerminationRequest(e, gestureState); - } - }; - return { panHandlers: panHandlers }; - } -}; - +import PanResponder from '../../vendor/PanResponder'; export default PanResponder; diff --git a/src/vendor/PanResponder/SHA b/src/vendor/PanResponder/SHA new file mode 100644 index 00000000..c21c3495 --- /dev/null +++ b/src/vendor/PanResponder/SHA @@ -0,0 +1 @@ +facebook/react-native@71006f74cdafdae7212c8a10603fb972c6ee338c diff --git a/src/vendor/PanResponder/index.js b/src/vendor/PanResponder/index.js new file mode 100644 index 00000000..9fcf730f --- /dev/null +++ b/src/vendor/PanResponder/index.js @@ -0,0 +1,415 @@ +/** + * Copyright (c) 2013-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. + * + * @providesModule PanResponder + */ + +'use strict'; + +const InteractionManager = require('../../apis/InteractionManager').default; +const TouchHistoryMath = require('../TouchHistoryMath'); + +const currentCentroidXOfTouchesChangedAfter = TouchHistoryMath.currentCentroidXOfTouchesChangedAfter; +const currentCentroidYOfTouchesChangedAfter = TouchHistoryMath.currentCentroidYOfTouchesChangedAfter; +const previousCentroidXOfTouchesChangedAfter = TouchHistoryMath.previousCentroidXOfTouchesChangedAfter; +const previousCentroidYOfTouchesChangedAfter = TouchHistoryMath.previousCentroidYOfTouchesChangedAfter; +const currentCentroidX = TouchHistoryMath.currentCentroidX; +const currentCentroidY = TouchHistoryMath.currentCentroidY; + +/** + * `PanResponder` reconciles several touches into a single gesture. It makes + * single-touch gestures resilient to extra touches, and can be used to + * recognize simple multi-touch gestures. + * + * By default, `PanResponder` holds an `InteractionManager` handle to block + * long-running JS events from interrupting active gestures. + * + * It provides a predictable wrapper of the responder handlers provided by the + * [gesture responder system](docs/gesture-responder-system.html). + * For each handler, it provides a new `gestureState` object alongside the + * native event object: + * + * ``` + * onPanResponderMove: (event, gestureState) => {} + * ``` + * + * A native event is a synthetic touch event with the following form: + * + * - `nativeEvent` + * + `changedTouches` - Array of all touch events that have changed since the last event + * + `identifier` - The ID of the touch + * + `locationX` - The X position of the touch, relative to the element + * + `locationY` - The Y position of the touch, relative to the element + * + `pageX` - The X position of the touch, relative to the root element + * + `pageY` - The Y position of the touch, relative to the root element + * + `target` - The node id of the element receiving the touch event + * + `timestamp` - A time identifier for the touch, useful for velocity calculation + * + `touches` - Array of all current touches on the screen + * + * A `gestureState` object has the following: + * + * - `stateID` - ID of the gestureState- persisted as long as there at least + * one touch on screen + * - `moveX` - the latest screen coordinates of the recently-moved touch + * - `moveY` - the latest screen coordinates of the recently-moved touch + * - `x0` - the screen coordinates of the responder grant + * - `y0` - the screen coordinates of the responder grant + * - `dx` - accumulated distance of the gesture since the touch started + * - `dy` - accumulated distance of the gesture since the touch started + * - `vx` - current velocity of the gesture + * - `vy` - current velocity of the gesture + * - `numberActiveTouches` - Number of touches currently on screen + * + * ### Basic Usage + * + * ``` + * componentWillMount: function() { + * this._panResponder = PanResponder.create({ + * // Ask to be the responder: + * onStartShouldSetPanResponder: (evt, gestureState) => true, + * onStartShouldSetPanResponderCapture: (evt, gestureState) => true, + * onMoveShouldSetPanResponder: (evt, gestureState) => true, + * onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, + * + * onPanResponderGrant: (evt, gestureState) => { + * // The gesture has started. Show visual feedback so the user knows + * // what is happening! + * + * // gestureState.d{x,y} will be set to zero now + * }, + * onPanResponderMove: (evt, gestureState) => { + * // The most recent move distance is gestureState.move{X,Y} + * + * // The accumulated gesture distance since becoming responder is + * // gestureState.d{x,y} + * }, + * onPanResponderTerminationRequest: (evt, gestureState) => true, + * onPanResponderRelease: (evt, gestureState) => { + * // The user has released all touches while this view is the + * // responder. This typically means a gesture has succeeded + * }, + * onPanResponderTerminate: (evt, gestureState) => { + * // Another component has become the responder, so this gesture + * // should be cancelled + * }, + * onShouldBlockNativeResponder: (evt, gestureState) => { + * // Returns whether this component should block native components from becoming the JS + * // responder. Returns true by default. Is currently only supported on android. + * return true; + * }, + * }); + * }, + * + * render: function() { + * return ( + * + * ); + * }, + * + * ``` + * + * ### Working Example + * + * To see it in action, try the + * [PanResponder example in RNTester](https://github.com/facebook/react-native/blob/master/RNTester/js/PanResponderExample.js) + */ + +const PanResponder = { + + /** + * + * A graphical explanation of the touch data flow: + * + * +----------------------------+ +--------------------------------+ + * | ResponderTouchHistoryStore | |TouchHistoryMath | + * +----------------------------+ +----------+---------------------+ + * |Global store of touchHistory| |Allocation-less math util | + * |including activeness, start | |on touch history (centroids | + * |position, prev/cur position.| |and multitouch movement etc) | + * | | | | + * +----^-----------------------+ +----^---------------------------+ + * | | + * | (records relevant history | + * | of touches relevant for | + * | implementing higher level | + * | gestures) | + * | | + * +----+-----------------------+ +----|---------------------------+ + * | ResponderEventPlugin | | | Your App/Component | + * +----------------------------+ +----|---------------------------+ + * |Negotiates which view gets | Low level | | High level | + * |onResponderMove events. | events w/ | +-+-------+ events w/ | + * |Also records history into | touchHistory| | Pan | multitouch + | + * |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative| + * +----------------------------+ attached to | | | distance and | + * each event | +---------+ velocity. | + * | | + * | | + * +--------------------------------+ + * + * + * + * Gesture that calculates cumulative movement over time in a way that just + * "does the right thing" for multiple touches. The "right thing" is very + * nuanced. When moving two touches in opposite directions, the cumulative + * distance is zero in each dimension. When two touches move in parallel five + * pixels in the same direction, the cumulative distance is five, not ten. If + * two touches start, one moves five in a direction, then stops and the other + * touch moves fives in the same direction, the cumulative distance is ten. + * + * This logic requires a kind of processing of time "clusters" of touch events + * so that two touch moves that essentially occur in parallel but move every + * other frame respectively, are considered part of the same movement. + * + * Explanation of some of the non-obvious fields: + * + * - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is + * invalid. If a move event has been observed, `(moveX, moveY)` is the + * centroid of the most recently moved "cluster" of active touches. + * (Currently all move have the same timeStamp, but later we should add some + * threshold for what is considered to be "moving"). If a palm is + * accidentally counted as a touch, but a finger is moving greatly, the palm + * will move slightly, but we only want to count the single moving touch. + * - x0/y0: Centroid location (non-cumulative) at the time of becoming + * responder. + * - dx/dy: Cumulative touch distance - not the same thing as sum of each touch + * distance. Accounts for touch moves that are clustered together in time, + * moving the same direction. Only valid when currently responder (otherwise, + * it only represents the drag distance below the threshold). + * - vx/vy: Velocity. + */ + + _initializeGestureState: function (gestureState) { + gestureState.moveX = 0; + gestureState.moveY = 0; + gestureState.x0 = 0; + gestureState.y0 = 0; + gestureState.dx = 0; + gestureState.dy = 0; + gestureState.vx = 0; + gestureState.vy = 0; + gestureState.numberActiveTouches = 0; + // All `gestureState` accounts for timeStamps up until: + gestureState._accountsForMovesUpTo = 0; + }, + + /** + * This is nuanced and is necessary. It is incorrect to continuously take all + * active *and* recently moved touches, find the centroid, and track how that + * result changes over time. Instead, we must take all recently moved + * touches, and calculate how the centroid has changed just for those + * recently moved touches, and append that change to an accumulator. This is + * to (at least) handle the case where the user is moving three fingers, and + * then one of the fingers stops but the other two continue. + * + * This is very different than taking all of the recently moved touches and + * storing their centroid as `dx/dy`. For correctness, we must *accumulate + * changes* in the centroid of recently moved touches. + * + * There is also some nuance with how we handle multiple moved touches in a + * single event. With the way `ReactNativeEventEmitter` dispatches touches as + * individual events, multiple touches generate two 'move' events, each of + * them triggering `onResponderMove`. But with the way `PanResponder` works, + * all of the gesture inference is performed on the first dispatch, since it + * looks at all of the touches (even the ones for which there hasn't been a + * native dispatch yet). Therefore, `PanResponder` does not call + * `onResponderMove` passed the first dispatch. This diverges from the + * typical responder callback pattern (without using `PanResponder`), but + * avoids more dispatches than necessary. + */ + _updateGestureStateOnMove: function (gestureState, touchHistory) { + gestureState.numberActiveTouches = touchHistory.numberActiveTouches; + gestureState.moveX = currentCentroidXOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo); + gestureState.moveY = currentCentroidYOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo); + const movedAfter = gestureState._accountsForMovesUpTo; + const prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter); + const x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter); + const prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter); + const y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter); + const nextDX = gestureState.dx + (x - prevX); + const nextDY = gestureState.dy + (y - prevY); + + // TODO: This must be filtered intelligently. + const dt = touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo; + gestureState.vx = (nextDX - gestureState.dx) / dt; + gestureState.vy = (nextDY - gestureState.dy) / dt; + + gestureState.dx = nextDX; + gestureState.dy = nextDY; + gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp; + }, + + /** + * @param {object} config Enhanced versions of all of the responder callbacks + * that provide not only the typical `ResponderSyntheticEvent`, but also the + * `PanResponder` gesture state. Simply replace the word `Responder` with + * `PanResponder` in each of the typical `onResponder*` callbacks. For + * example, the `config` object would look like: + * + * - `onMoveShouldSetPanResponder: (e, gestureState) => {...}` + * - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}` + * - `onStartShouldSetPanResponder: (e, gestureState) => {...}` + * - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}` + * - `onPanResponderReject: (e, gestureState) => {...}` + * - `onPanResponderGrant: (e, gestureState) => {...}` + * - `onPanResponderStart: (e, gestureState) => {...}` + * - `onPanResponderEnd: (e, gestureState) => {...}` + * - `onPanResponderRelease: (e, gestureState) => {...}` + * - `onPanResponderMove: (e, gestureState) => {...}` + * - `onPanResponderTerminate: (e, gestureState) => {...}` + * - `onPanResponderTerminationRequest: (e, gestureState) => {...}` + * - `onShouldBlockNativeResponder: (e, gestureState) => {...}` + * + * In general, for events that have capture equivalents, we update the + * gestureState once in the capture phase and can use it in the bubble phase + * as well. + * + * Be careful with onStartShould* callbacks. They only reflect updated + * `gestureState` for start/end events that bubble/capture to the Node. + * Once the node is the responder, you can rely on every start/end event + * being processed by the gesture and `gestureState` being updated + * accordingly. (numberActiveTouches) may not be totally accurate unless you + * are the responder. + */ + create: function (config) { + const interactionState = { + handle: (null: ?number), + }; + const gestureState = { + // Useful for debugging + stateID: Math.random(), + }; + PanResponder._initializeGestureState(gestureState); + const panHandlers = { + onStartShouldSetResponder: function (e) { + return config.onStartShouldSetPanResponder === undefined ? + false : + config.onStartShouldSetPanResponder(e, gestureState); + }, + onMoveShouldSetResponder: function (e) { + return config.onMoveShouldSetPanResponder === undefined ? + false : + config.onMoveShouldSetPanResponder(e, gestureState); + }, + onStartShouldSetResponderCapture: function (e) { + // TODO: Actually, we should reinitialize the state any time + // touches.length increases from 0 active to > 0 active. + if (e.nativeEvent.touches.length === 1) { + PanResponder._initializeGestureState(gestureState); + } + gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches; + return config.onStartShouldSetPanResponderCapture !== undefined ? + config.onStartShouldSetPanResponderCapture(e, gestureState) : + false; + }, + + onMoveShouldSetResponderCapture: function (e) { + const touchHistory = e.touchHistory; + // Responder system incorrectly dispatches should* to current responder + // Filter out any touch moves past the first one - we would have + // already processed multi-touch geometry during the first event. + if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) { + return false; + } + PanResponder._updateGestureStateOnMove(gestureState, touchHistory); + return config.onMoveShouldSetPanResponderCapture ? + config.onMoveShouldSetPanResponderCapture(e, gestureState) : + false; + }, + + onResponderGrant: function (e) { + if (!interactionState.handle) { + interactionState.handle = InteractionManager.createInteractionHandle(); + } + gestureState.x0 = currentCentroidX(e.touchHistory); + gestureState.y0 = currentCentroidY(e.touchHistory); + gestureState.dx = 0; + gestureState.dy = 0; + if (config.onPanResponderGrant) { + config.onPanResponderGrant(e, gestureState); + } + // TODO: t7467124 investigate if this can be removed + return config.onShouldBlockNativeResponder === undefined ? + true : + config.onShouldBlockNativeResponder(); + }, + + onResponderReject: function (e) { + clearInteractionHandle(interactionState, config.onPanResponderReject, e, gestureState); + }, + + onResponderRelease: function (e) { + clearInteractionHandle(interactionState, config.onPanResponderRelease, e, gestureState); + PanResponder._initializeGestureState(gestureState); + }, + + onResponderStart: function (e) { + const touchHistory = e.touchHistory; + gestureState.numberActiveTouches = touchHistory.numberActiveTouches; + if (config.onPanResponderStart) { + config.onPanResponderStart(e, gestureState); + } + }, + + onResponderMove: function (e) { + const touchHistory = e.touchHistory; + // Guard against the dispatch of two touch moves when there are two + // simultaneously changed touches. + if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) { + return; + } + // Filter out any touch moves past the first one - we would have + // already processed multi-touch geometry during the first event. + PanResponder._updateGestureStateOnMove(gestureState, touchHistory); + if (config.onPanResponderMove) { + config.onPanResponderMove(e, gestureState); + } + }, + + onResponderEnd: function (e) { + const touchHistory = e.touchHistory; + gestureState.numberActiveTouches = touchHistory.numberActiveTouches; + clearInteractionHandle(interactionState, config.onPanResponderEnd, e, gestureState); + }, + + onResponderTerminate: function (e) { + clearInteractionHandle(interactionState, config.onPanResponderTerminate, e, gestureState); + PanResponder._initializeGestureState(gestureState); + }, + + onResponderTerminationRequest: function (e) { + return config.onPanResponderTerminationRequest === undefined ? + true : + config.onPanResponderTerminationRequest(e, gestureState); + } + }; + return { + panHandlers, + getInteractionHandle(): ?number { + return interactionState.handle; + }, + }; + } +}; + +function clearInteractionHandle( + interactionState: {handle: ?number}, + callback: Function, + event: Object, + gestureState: Object +) { + if (interactionState.handle) { + InteractionManager.clearInteractionHandle(interactionState.handle); + interactionState.handle = null; + } + if (callback) { + callback(event, gestureState); + } +} + +module.exports = PanResponder; diff --git a/src/vendor/TouchHistoryMath/index.js b/src/vendor/TouchHistoryMath/index.js index bac290dd..cef08aa8 100644 --- a/src/vendor/TouchHistoryMath/index.js +++ b/src/vendor/TouchHistoryMath/index.js @@ -121,4 +121,4 @@ var TouchHistoryMath = { noCentroid: -1 }; -export default TouchHistoryMath; +module.exports = TouchHistoryMath;