diff --git a/.prettierignore b/.prettierignore index 3adbbfa..dd44972 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1 @@ -*.js -*.md \ No newline at end of file +*.md diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..144799b --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + bracketSpacing: false, + jsxBracketSameLine: true, + singleQuote: true, + trailingComma: 'all', + tabWidth: 2, +}; diff --git a/package.json b/package.json index 01b232b..654e1a9 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "eslint-plugin-node": "^5.0.0", "eslint-plugin-promise": "^3.5.0", "eslint-plugin-react": "^7.0.1", - "eslint-plugin-standard": "^3.0.1" + "eslint-plugin-standard": "^3.0.1", + "prettier": "^3.5.3" } } diff --git a/src/Gallery.js b/src/Gallery.js index 5cc4835..c7913bb 100644 --- a/src/Gallery.js +++ b/src/Gallery.js @@ -1,291 +1,343 @@ -import React, { PureComponent } from 'react'; -import { View, ViewPropTypes } from 'react-native'; +import React, {PureComponent} from 'react'; +import {Dimensions, View, ViewPropTypes, ViewStyle} from 'react-native'; import PropTypes from 'prop-types'; -import { createResponder } from './libraries/GestureResponder'; +import {createResponder} from './libraries/GestureResponder'; import TransformableImage from './libraries/TransformableImage'; import ViewPager from './libraries/ViewPager'; const DEFAULT_FLAT_LIST_PROPS = { - windowSize: 3 + windowSize: 3, }; export default class Gallery extends PureComponent { - static propTypes = { - ...View.propTypes, - images: PropTypes.arrayOf(PropTypes.object), - initialPage: PropTypes.number, - scrollViewStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - pageMargin: PropTypes.number, - onPageSelected: PropTypes.func, - onPageScrollStateChanged: PropTypes.func, - onPageScroll: PropTypes.func, - onSingleTapConfirmed: PropTypes.func, - onGalleryStateChanged: PropTypes.func, - onLongPress: PropTypes.func, - removeClippedSubviews: PropTypes.bool, - imageComponent: PropTypes.func, - errorComponent: PropTypes.func, - flatListProps: PropTypes.object - }; - - static defaultProps = { - removeClippedSubviews: true, - imageComponent: undefined, - scrollViewStyle: {}, - flatListProps: DEFAULT_FLAT_LIST_PROPS - }; + static propTypes = { + ...View.propTypes, + images: PropTypes.arrayOf(PropTypes.object), + initialPage: PropTypes.number, + scrollViewStyle: ViewPropTypes ? ViewPropTypes.style : ViewStyle, + pageMargin: PropTypes.number, + onPageSelected: PropTypes.func, + onPageScrollStateChanged: PropTypes.func, + onPageScroll: PropTypes.func, + onSingleTapConfirmed: PropTypes.func, + onGalleryStateChanged: PropTypes.func, + onLongPress: PropTypes.func, + removeClippedSubviews: PropTypes.bool, + imageComponent: PropTypes.func, + errorComponent: PropTypes.func, + flatListProps: PropTypes.object, + maxScale: PropTypes.number, + }; - imageRefs = new Map(); - activeResponder = undefined; - firstMove = true; - currentPage = 0; - pageCount = 0; - gestureResponder = undefined; + static defaultProps = { + removeClippedSubviews: true, + imageComponent: undefined, + scrollViewStyle: {}, + flatListProps: DEFAULT_FLAT_LIST_PROPS, + maxScale: 3, + }; - constructor (props) { - super(props); + imageRefs = new Map(); + activeResponder = undefined; + firstMove = true; + currentPage = 0; + pageCount = 0; + gestureResponder = undefined; - this.renderPage = this.renderPage.bind(this); - this.onPageSelected = this.onPageSelected.bind(this); - this.onPageScrollStateChanged = this.onPageScrollStateChanged.bind(this); - this.getViewPagerInstance = this.getViewPagerInstance.bind(this); - this.getCurrentImageTransformer = this.getCurrentImageTransformer.bind(this); - this.getImageTransformer = this.getImageTransformer.bind(this); - this.getViewPagerInstance = this.getViewPagerInstance.bind(this); - this.activeImageResponder = this.activeImageResponder.bind(this); - } + constructor(props) { + super(props); - componentWillMount () { - let onResponderReleaseOrTerminate = (evt, gestureState) => { - if (this.activeResponder) { - if (this.activeResponder === this.viewPagerResponder && - !this.shouldScrollViewPager(evt, gestureState) && - Math.abs(gestureState.vx) > 0.5) { - this.activeResponder.onEnd(evt, gestureState, true); - this.getViewPagerInstance().flingToPage(this.currentPage, gestureState.vx); - } else { - this.activeResponder.onEnd(evt, gestureState); - } - this.activeResponder = null; - } - this.firstMove = true; - this.props.onGalleryStateChanged && this.props.onGalleryStateChanged(true); - }; + this.renderPage = this.renderPage.bind(this); + this.onPageSelected = this.onPageSelected.bind(this); + this.onPageScrollStateChanged = this.onPageScrollStateChanged.bind(this); + this.getViewPagerInstance = this.getViewPagerInstance.bind(this); + this.getCurrentImageTransformer = + this.getCurrentImageTransformer.bind(this); + this.getImageTransformer = this.getImageTransformer.bind(this); + this.getViewPagerInstance = this.getViewPagerInstance.bind(this); + this.activeImageResponder = this.activeImageResponder.bind(this); + } - this.gestureResponder = createResponder({ - onStartShouldSetResponderCapture: (evt, gestureState) => true, - onStartShouldSetResponder: (evt, gestureState) => true, - onResponderGrant: this.activeImageResponder, - onResponderMove: (evt, gestureState) => { - if (this.firstMove) { - this.firstMove = false; - if (this.shouldScrollViewPager(evt, gestureState)) { - this.activeViewPagerResponder(evt, gestureState); - } - this.props.onGalleryStateChanged && this.props.onGalleryStateChanged(false); - } - if (this.activeResponder === this.viewPagerResponder) { - const dx = gestureState.moveX - gestureState.previousMoveX; - const offset = this.getViewPagerInstance().getScrollOffsetFromCurrentPage(); - if (dx > 0 && offset > 0 && !this.shouldScrollViewPager(evt, gestureState)) { - if (dx > offset) { // active image responder - this.getViewPagerInstance().scrollByOffset(offset); - gestureState.moveX -= offset; - this.activeImageResponder(evt, gestureState); - } - } else if (dx < 0 && offset < 0 && !this.shouldScrollViewPager(evt, gestureState)) { - if (dx < offset) { // active image responder - this.getViewPagerInstance().scrollByOffset(offset); - gestureState.moveX -= offset; - this.activeImageResponder(evt, gestureState); - } - } - } - this.activeResponder.onMove(evt, gestureState); - }, - onResponderRelease: onResponderReleaseOrTerminate, - onResponderTerminate: onResponderReleaseOrTerminate, - onResponderTerminationRequest: (evt, gestureState) => false, // Do not allow parent view to intercept gesture - onResponderSingleTapConfirmed: (evt, gestureState) => { - this.props.onSingleTapConfirmed && this.props.onSingleTapConfirmed(this.currentPage); - } - }); + UNSAFE_componentWillMount() { + let onResponderReleaseOrTerminate = (evt, gestureState) => { + if (this.activeResponder) { + if ( + this.activeResponder === this.viewPagerResponder && + !this.shouldScrollViewPager(evt, gestureState) && + Math.abs(gestureState.vx) > 0.5 + ) { + this.activeResponder.onEnd(evt, gestureState, true); + this.getViewPagerInstance().flingToPage( + this.currentPage, + gestureState.vx, + ); + } else { + this.activeResponder.onEnd(evt, gestureState); + } + this.activeResponder = null; + } + this.firstMove = true; + this.props.onGalleryStateChanged && + this.props.onGalleryStateChanged(true); + }; - this.viewPagerResponder = { - onStart: (evt, gestureState) => { - this.getViewPagerInstance().onResponderGrant(evt, gestureState); - }, - onMove: (evt, gestureState) => { - this.getViewPagerInstance().onResponderMove(evt, gestureState); - }, - onEnd: (evt, gestureState, disableSettle) => { - this.getViewPagerInstance().onResponderRelease(evt, gestureState, disableSettle); + this.gestureResponder = createResponder({ + onStartShouldSetResponderCapture: (evt, gestureState) => true, + onStartShouldSetResponder: (evt, gestureState) => true, + onResponderGrant: this.activeImageResponder, + onResponderMove: (evt, gestureState) => { + if (this.firstMove) { + this.firstMove = false; + if (this.shouldScrollViewPager(evt, gestureState)) { + this.activeViewPagerResponder(evt, gestureState); + } + this.props.onGalleryStateChanged && + this.props.onGalleryStateChanged(false); + } + if (this.activeResponder === this.viewPagerResponder) { + const dx = gestureState.moveX - gestureState.previousMoveX; + const offset = + this.getViewPagerInstance().getScrollOffsetFromCurrentPage(); + if ( + dx > 0 && + offset > 0 && + !this.shouldScrollViewPager(evt, gestureState) + ) { + if (dx > offset) { + // active image responder + this.getViewPagerInstance().scrollByOffset(offset); + gestureState.moveX -= offset; + this.activeImageResponder(evt, gestureState); } - }; - - this.imageResponder = { - onStart: (evt, gestureState) => { - const currentImageTransformer = this.getCurrentImageTransformer(); - currentImageTransformer && currentImageTransformer.onResponderGrant(evt, gestureState); - if (this.props.onLongPress) { - this._longPressTimeout = setTimeout(() => { - this.props.onLongPress(gestureState); - }, 600); - } - }, - onMove: (evt, gestureState) => { - const currentImageTransformer = this.getCurrentImageTransformer(); - currentImageTransformer && currentImageTransformer.onResponderMove(evt, gestureState); - clearTimeout(this._longPressTimeout); - }, - onEnd: (evt, gestureState) => { - const currentImageTransformer = this.getCurrentImageTransformer(); - currentImageTransformer && currentImageTransformer.onResponderRelease(evt, gestureState); - clearTimeout(this._longPressTimeout); + } else if ( + dx < 0 && + offset < 0 && + !this.shouldScrollViewPager(evt, gestureState) + ) { + if (dx < offset) { + // active image responder + this.getViewPagerInstance().scrollByOffset(offset); + gestureState.moveX -= offset; + this.activeImageResponder(evt, gestureState); } - }; - } - - componentDidMount () { - this._isMounted = true; - } + } + } + this.activeResponder.onMove(evt, gestureState); + }, + onResponderRelease: onResponderReleaseOrTerminate, + onResponderTerminate: onResponderReleaseOrTerminate, + onResponderTerminationRequest: (evt, gestureState) => false, // Do not allow parent view to intercept gesture + onResponderSingleTapConfirmed: (evt, gestureState) => { + this.props.onSingleTapConfirmed && + this.props.onSingleTapConfirmed(this.currentPage); + }, + }); - componentWillUnmount () { - this._isMounted = false; - } + this.viewPagerResponder = { + onStart: (evt, gestureState) => { + this.getViewPagerInstance().onResponderGrant(evt, gestureState); + }, + onMove: (evt, gestureState) => { + this.getViewPagerInstance().onResponderMove(evt, gestureState); + }, + onEnd: (evt, gestureState, disableSettle) => { + this.getViewPagerInstance().onResponderRelease( + evt, + gestureState, + disableSettle, + ); + }, + }; - shouldScrollViewPager (evt, gestureState) { - if (gestureState.numberActiveTouches > 1) { - return false; - } - const viewTransformer = this.getCurrentImageTransformer(); - if (!viewTransformer) { - return false; + this.imageResponder = { + onStart: (evt, gestureState) => { + const currentImageTransformer = this.getCurrentImageTransformer(); + currentImageTransformer && + currentImageTransformer.onResponderGrant(evt, gestureState); + if (this.props.onLongPress) { + this._longPressTimeout = setTimeout(() => { + this.props.onLongPress(gestureState); + }, 600); } + }, + onMove: (evt, gestureState) => { + const currentImageTransformer = this.getCurrentImageTransformer(); + currentImageTransformer && + currentImageTransformer.onResponderMove(evt, gestureState); + clearTimeout(this._longPressTimeout); + }, + onEnd: (evt, gestureState) => { + const currentImageTransformer = this.getCurrentImageTransformer(); + currentImageTransformer && + currentImageTransformer.onResponderRelease(evt, gestureState); + clearTimeout(this._longPressTimeout); + }, + }; + } - const space = viewTransformer.getAvailableTranslateSpace(); - const dx = gestureState.moveX - gestureState.previousMoveX; + componentDidMount() { + this._isMounted = true; + } - if (dx > 0 && space.left <= 0 && this.currentPage > 0) { - return true; - } - if (dx < 0 && space.right <= 0 && this.currentPage < this.pageCount - 1) { - return true; - } - return false; - } + componentWillUnmount() { + this._isMounted = false; + } - activeImageResponder (evt, gestureState) { - if (this.activeResponder !== this.imageResponder) { - if (this.activeResponder === this.viewPagerResponder) { - this.viewPagerResponder.onEnd(evt, gestureState, true); // pass true to disable ViewPager settle - } - this.activeResponder = this.imageResponder; - this.imageResponder.onStart(evt, gestureState); - } + shouldScrollViewPager(evt, gestureState) { + if (gestureState.numberActiveTouches > 1) { + return false; } - - activeViewPagerResponder (evt, gestureState) { - if (this.activeResponder !== this.viewPagerResponder) { - if (this.activeResponder === this.imageResponder) { - this.imageResponder.onEnd(evt, gestureState); - } - this.activeResponder = this.viewPagerResponder; - this.viewPagerResponder.onStart(evt, gestureState); - } + const viewTransformer = this.getCurrentImageTransformer(); + if (!viewTransformer) { + return false; } - getImageTransformer (page) { - if (page >= 0 && page < this.pageCount) { - let ref = this.imageRefs.get(page); - if (ref) { - return ref.getViewTransformerInstance(); - } - } - } + const space = viewTransformer.getAvailableTranslateSpace(); + const dx = gestureState.moveX - gestureState.previousMoveX; - getCurrentImageTransformer () { - return this.getImageTransformer(this.currentPage); + if (dx > 0 && space.left <= 0 && this.currentPage > 0) { + return true; } - - getViewPagerInstance () { - return this.refs['galleryViewPager']; + if (dx < 0 && space.right <= 0 && this.currentPage < this.pageCount - 1) { + return true; } + return false; + } - onPageSelected (page) { - this.currentPage = page; - this.props.onPageSelected && this.props.onPageSelected(page); + activeImageResponder(evt, gestureState) { + if (this.activeResponder !== this.imageResponder) { + if (this.activeResponder === this.viewPagerResponder) { + this.viewPagerResponder.onEnd(evt, gestureState, true); // pass true to disable ViewPager settle + } + this.activeResponder = this.imageResponder; + this.imageResponder.onStart(evt, gestureState); } + } - onPageScrollStateChanged (state) { - if (state === 'idle') { - this.resetHistoryImageTransform(); - } - this.props.onPageScrollStateChanged && this.props.onPageScrollStateChanged(state); + activeViewPagerResponder(evt, gestureState) { + if (this.activeResponder !== this.viewPagerResponder) { + if (this.activeResponder === this.imageResponder) { + this.imageResponder.onEnd(evt, gestureState); + } + this.activeResponder = this.viewPagerResponder; + this.viewPagerResponder.onStart(evt, gestureState); } + } - renderPage (pageData, pageId) { - const { onViewTransformed, onTransformGestureReleased, errorComponent, imageComponent } = this.props; - return ( - { - onViewTransformed && onViewTransformed(transform, pageId); - })} - onTransformGestureReleased={((transform) => { - // need the 'return' here because the return value is checked in ViewTransformer - return onTransformGestureReleased && onTransformGestureReleased(transform, pageId); - })} - ref={((ref) => { this.imageRefs.set(pageId, ref); })} - key={'innerImage#' + pageId} - errorComponent={errorComponent} - imageComponent={imageComponent} - image={pageData} - /> - ); + getImageTransformer(page) { + if (page >= 0 && page < this.pageCount) { + let ref = this.imageRefs.get(page); + if (ref) { + return ref.getViewTransformerInstance(); + } } + } - resetHistoryImageTransform () { - let transformer = this.getImageTransformer(this.currentPage + 1); - if (transformer) { - transformer.forceUpdateTransform({scale: 1, translateX: 0, translateY: 0}); - } + getCurrentImageTransformer() { + return this.getImageTransformer(this.currentPage); + } - transformer = this.getImageTransformer(this.currentPage - 1); - if (transformer) { - transformer.forceUpdateTransform({scale: 1, translateX: 0, translateY: 0}); - } + getViewPagerInstance() { + return this.refs['galleryViewPager']; + } + + onPageSelected(page) { + this.currentPage = page; + this.props.onPageSelected && this.props.onPageSelected(page); + } + + onPageScrollStateChanged(state) { + if (state === 'idle') { + this.resetHistoryImageTransform(); } + this.props.onPageScrollStateChanged && + this.props.onPageScrollStateChanged(state); + } - render () { - let gestureResponder = this.gestureResponder; + renderPage(pageData, pageId) { + const { + onViewTransformed, + onTransformGestureReleased, + errorComponent, + imageComponent, + } = this.props; + return ( + { + onViewTransformed && onViewTransformed(transform, pageId); + }} + onTransformGestureReleased={(transform) => { + // need the 'return' here because the return value is checked in ViewTransformer + return ( + onTransformGestureReleased && + onTransformGestureReleased(transform, pageId) + ); + }} + ref={(ref) => { + this.imageRefs.set(pageId, ref); + }} + key={'innerImage#' + pageId} + errorComponent={errorComponent} + imageComponent={imageComponent} + image={pageData} + maxScale={this.props.maxScale} + /> + ); + } - let images = this.props.images; - if (!images) { - images = []; - } - this.pageCount = images.length; + resetHistoryImageTransform() { + let transformer = this.getImageTransformer(this.currentPage + 1); + if (transformer) { + transformer.forceUpdateTransform({ + scale: 1, + translateX: 0, + translateY: 0, + }); + } - if (this.pageCount <= 0) { - gestureResponder = {}; - } + transformer = this.getImageTransformer(this.currentPage - 1); + if (transformer) { + transformer.forceUpdateTransform({ + scale: 1, + translateX: 0, + translateY: 0, + }); + } + } - const flatListProps = {...DEFAULT_FLAT_LIST_PROPS, ...this.props.flatListProps}; + render() { + let gestureResponder = this.gestureResponder; - return ( - - ); + let images = this.props.images; + if (!images) { + images = []; } + this.pageCount = images.length; + + if (this.pageCount <= 0) { + gestureResponder = {}; + } + + const flatListProps = { + ...DEFAULT_FLAT_LIST_PROPS, + ...this.props.flatListProps, + }; + + return ( + + ); + } } diff --git a/src/libraries/GestureResponder/TouchDistanceMath.js b/src/libraries/GestureResponder/TouchDistanceMath.js index 89b74c2..10bc4a0 100644 --- a/src/libraries/GestureResponder/TouchDistanceMath.js +++ b/src/libraries/GestureResponder/TouchDistanceMath.js @@ -1,40 +1,40 @@ 'use strict'; -export function distance (touchTrackA, touchTrackB, ofCurrent) { - let xa, ya, xb, yb; - if (ofCurrent) { - xa = touchTrackA.currentPageX; - ya = touchTrackA.currentPageY; - xb = touchTrackB.currentPageX; - yb = touchTrackB.currentPageY; - } else { - xa = touchTrackA.previousPageX; - ya = touchTrackA.previousPageY; - xb = touchTrackB.previousPageX; - yb = touchTrackB.previousPageY; - } - return Math.sqrt(Math.pow(xa - xb, 2) + Math.pow(ya - yb, 2)); +export function distance(touchTrackA, touchTrackB, ofCurrent) { + let xa, ya, xb, yb; + if (ofCurrent) { + xa = touchTrackA.currentPageX; + ya = touchTrackA.currentPageY; + xb = touchTrackB.currentPageX; + yb = touchTrackB.currentPageY; + } else { + xa = touchTrackA.previousPageX; + ya = touchTrackA.previousPageY; + xb = touchTrackB.previousPageX; + yb = touchTrackB.previousPageY; + } + return Math.sqrt(Math.pow(xa - xb, 2) + Math.pow(ya - yb, 2)); } -export function maxDistance (touchBank, ofCurrent) { - let max = 0; - for (let i = 0; i < touchBank.length - 1; i++) { - for (let j = i + 1; j < touchBank.length; j++) { - let d = distance(touchBank[i], touchBank[j], ofCurrent); - if (d > max) { - max = d; - } - } +export function maxDistance(touchBank, ofCurrent) { + let max = 0; + for (let i = 0; i < touchBank.length - 1; i++) { + for (let j = i + 1; j < touchBank.length; j++) { + let d = distance(touchBank[i], touchBank[j], ofCurrent); + if (d > max) { + max = d; + } } - return max; + } + return max; } -export function pinchDistance (touchHistory, touchesChangedAfter, ofCurrent) { - let touchBank = touchHistory.touchBank; - if (touchHistory.numberActiveTouches > 1) { - let filteredTouchBank = touchBank.filter((touchTrack) => { - return touchTrack && touchTrack.currentTimeStamp >= touchesChangedAfter; - }); - return maxDistance(filteredTouchBank, ofCurrent); - } +export function pinchDistance(touchHistory, touchesChangedAfter, ofCurrent) { + let touchBank = touchHistory.touchBank; + if (touchHistory.numberActiveTouches > 1) { + let filteredTouchBank = touchBank.filter((touchTrack) => { + return touchTrack && touchTrack.currentTimeStamp >= touchesChangedAfter; + }); + return maxDistance(filteredTouchBank, ofCurrent); + } } diff --git a/src/libraries/GestureResponder/TouchHistoryMath.js b/src/libraries/GestureResponder/TouchHistoryMath.js index 8c7e317..1efbf61 100644 --- a/src/libraries/GestureResponder/TouchHistoryMath.js +++ b/src/libraries/GestureResponder/TouchHistoryMath.js @@ -21,79 +21,130 @@ var TouchHistoryMath = { * touches vs. previous centroid of now actively moving touches. * @return {number} value of centroid in specified dimension. */ - centroidDimension: function (touchHistory, touchesChangedAfter, isXAxis, ofCurrent) { - var touchBank = touchHistory.touchBank; - var total = 0; - var count = 0; + centroidDimension: function ( + touchHistory, + touchesChangedAfter, + isXAxis, + ofCurrent, + ) { + var touchBank = touchHistory.touchBank; + var total = 0; + var count = 0; - var oneTouchData = touchHistory.numberActiveTouches === 1 ? touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] : null; + var oneTouchData = + touchHistory.numberActiveTouches === 1 + ? touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] + : null; - if (oneTouchData !== null) { - if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) { - total += ofCurrent && isXAxis ? oneTouchData.currentPageX : ofCurrent && !isXAxis ? oneTouchData.currentPageY : !ofCurrent && isXAxis ? oneTouchData.previousPageX : oneTouchData.previousPageY; - count = 1; - } - } else { - for (var i = 0; i < touchBank.length; i++) { - var touchTrack = touchBank[i]; - if (touchTrack !== null && touchTrack !== undefined && touchTrack.touchActive && touchTrack.currentTimeStamp >= touchesChangedAfter) { - var toAdd; // Yuck, program temporarily in invalid state. - if (ofCurrent && isXAxis) { - toAdd = touchTrack.currentPageX; - } else if (ofCurrent && !isXAxis) { - toAdd = touchTrack.currentPageY; - } else if (!ofCurrent && isXAxis) { - toAdd = touchTrack.previousPageX; - } else { - toAdd = touchTrack.previousPageY; - } - total += toAdd; - count++; - } - } + if (oneTouchData !== null) { + if ( + oneTouchData.touchActive && + oneTouchData.currentTimeStamp > touchesChangedAfter + ) { + total += + ofCurrent && isXAxis + ? oneTouchData.currentPageX + : ofCurrent && !isXAxis + ? oneTouchData.currentPageY + : !ofCurrent && isXAxis + ? oneTouchData.previousPageX + : oneTouchData.previousPageY; + count = 1; + } + } else { + for (var i = 0; i < touchBank.length; i++) { + var touchTrack = touchBank[i]; + if ( + touchTrack !== null && + touchTrack !== undefined && + touchTrack.touchActive && + touchTrack.currentTimeStamp >= touchesChangedAfter + ) { + var toAdd; // Yuck, program temporarily in invalid state. + if (ofCurrent && isXAxis) { + toAdd = touchTrack.currentPageX; + } else if (ofCurrent && !isXAxis) { + toAdd = touchTrack.currentPageY; + } else if (!ofCurrent && isXAxis) { + toAdd = touchTrack.previousPageX; + } else { + toAdd = touchTrack.previousPageY; + } + total += toAdd; + count++; } - return count > 0 ? total / count : TouchHistoryMath.noCentroid; - }, + } + } + return count > 0 ? total / count : TouchHistoryMath.noCentroid; + }, - currentCentroidXOfTouchesChangedAfter: function (touchHistory, touchesChangedAfter) { - return TouchHistoryMath.centroidDimension(touchHistory, touchesChangedAfter, true, // isXAxis - true // ofCurrent + currentCentroidXOfTouchesChangedAfter: function ( + touchHistory, + touchesChangedAfter, + ) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + true, // isXAxis + true, // ofCurrent ); - }, + }, - currentCentroidYOfTouchesChangedAfter: function (touchHistory, touchesChangedAfter) { - return TouchHistoryMath.centroidDimension(touchHistory, touchesChangedAfter, false, // isXAxis - true // ofCurrent + currentCentroidYOfTouchesChangedAfter: function ( + touchHistory, + touchesChangedAfter, + ) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + false, // isXAxis + true, // ofCurrent ); - }, + }, - previousCentroidXOfTouchesChangedAfter: function (touchHistory, touchesChangedAfter) { - return TouchHistoryMath.centroidDimension(touchHistory, touchesChangedAfter, true, // isXAxis - false // ofCurrent + previousCentroidXOfTouchesChangedAfter: function ( + touchHistory, + touchesChangedAfter, + ) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + true, // isXAxis + false, // ofCurrent ); - }, + }, - previousCentroidYOfTouchesChangedAfter: function (touchHistory, touchesChangedAfter) { - return TouchHistoryMath.centroidDimension(touchHistory, touchesChangedAfter, false, // isXAxis - false // ofCurrent + previousCentroidYOfTouchesChangedAfter: function ( + touchHistory, + touchesChangedAfter, + ) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + false, // isXAxis + false, // ofCurrent ); - }, + }, - currentCentroidX: function (touchHistory) { - return TouchHistoryMath.centroidDimension(touchHistory, 0, // touchesChangedAfter - true, // isXAxis - true // ofCurrent + currentCentroidX: function (touchHistory) { + return TouchHistoryMath.centroidDimension( + touchHistory, + 0, // touchesChangedAfter + true, // isXAxis + true, // ofCurrent ); - }, + }, - currentCentroidY: function (touchHistory) { - return TouchHistoryMath.centroidDimension(touchHistory, 0, // touchesChangedAfter - false, // isXAxis - true // ofCurrent + currentCentroidY: function (touchHistory) { + return TouchHistoryMath.centroidDimension( + touchHistory, + 0, // touchesChangedAfter + false, // isXAxis + true, // ofCurrent ); - }, + }, - noCentroid: -1 + noCentroid: -1, }; module.exports = TouchHistoryMath; diff --git a/src/libraries/GestureResponder/createResponder.js b/src/libraries/GestureResponder/createResponder.js index 9096690..b0b7d0e 100644 --- a/src/libraries/GestureResponder/createResponder.js +++ b/src/libraries/GestureResponder/createResponder.js @@ -9,10 +9,14 @@ import TouchHistoryMath from './TouchHistoryMath'; // copied from react/lib/Touc import {pinchDistance} from './TouchDistanceMath'; import TimerMixin from 'react-timer-mixin'; -const currentCentroidXOfTouchesChangedAfter = TouchHistoryMath.currentCentroidXOfTouchesChangedAfter; -const currentCentroidYOfTouchesChangedAfter = TouchHistoryMath.currentCentroidYOfTouchesChangedAfter; -const previousCentroidXOfTouchesChangedAfter = TouchHistoryMath.previousCentroidXOfTouchesChangedAfter; -const previousCentroidYOfTouchesChangedAfter = TouchHistoryMath.previousCentroidYOfTouchesChangedAfter; +const currentCentroidXOfTouchesChangedAfter = + TouchHistoryMath.currentCentroidXOfTouchesChangedAfter; +const currentCentroidYOfTouchesChangedAfter = + TouchHistoryMath.currentCentroidYOfTouchesChangedAfter; +const previousCentroidXOfTouchesChangedAfter = + TouchHistoryMath.previousCentroidXOfTouchesChangedAfter; +const previousCentroidYOfTouchesChangedAfter = + TouchHistoryMath.previousCentroidYOfTouchesChangedAfter; const currentCentroidX = TouchHistoryMath.currentCentroidX; const currentCentroidY = TouchHistoryMath.currentCentroidY; @@ -22,61 +26,69 @@ const MOVE_THRESHOLD = 2; let DEV = false; -function initializeGestureState (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; +function initializeGestureState(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; - - gestureState.previousMoveX = 0; - gestureState.previousMoveY = 0; - gestureState.pinch = undefined; - gestureState.previousPinch = undefined; - gestureState.singleTapUp = false; - gestureState.doubleTapUp = false; - gestureState._singleTabFailed = false; + gestureState._accountsForMovesUpTo = 0; + + gestureState.previousMoveX = 0; + gestureState.previousMoveY = 0; + gestureState.pinch = undefined; + gestureState.previousPinch = undefined; + gestureState.singleTapUp = false; + gestureState.doubleTapUp = false; + gestureState._singleTabFailed = false; } -function updateGestureStateOnMove (gestureState, touchHistory, e) { - 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 dx = x - prevX; - const dy = y - prevY; - - gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - gestureState.moveX = x; - gestureState.moveY = y; +function updateGestureStateOnMove(gestureState, touchHistory, e) { + 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 dx = x - prevX; + const dy = y - prevY; + + gestureState.numberActiveTouches = touchHistory.numberActiveTouches; + gestureState.moveX = x; + gestureState.moveY = y; // TODO: This must be filtered intelligently. // const dt = touchHistory.mostRecentTimeStamp - movedAfter; - const dt = convertToMillisecIfNeeded(touchHistory.mostRecentTimeStamp - movedAfter); - gestureState.vx = dx / dt; - gestureState.vy = dy / dt; - gestureState.dx += dx; - gestureState.dy += dy; - gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp; - - gestureState.previousMoveX = prevX; - gestureState.previousMoveY = prevY; - gestureState.pinch = pinchDistance(touchHistory, movedAfter, true); - gestureState.previousPinch = pinchDistance(touchHistory, movedAfter, false); + const dt = convertToMillisecIfNeeded( + touchHistory.mostRecentTimeStamp - movedAfter, + ); + gestureState.vx = dx / dt; + gestureState.vy = dy / dt; + gestureState.dx += dx; + gestureState.dy += dy; + gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp; + + gestureState.previousMoveX = prevX; + gestureState.previousMoveY = prevY; + gestureState.pinch = pinchDistance(touchHistory, movedAfter, true); + gestureState.previousPinch = pinchDistance(touchHistory, movedAfter, false); } -function clearInteractionHandle (interactionState) { - if (interactionState.handle) { - InteractionManager.clearInteractionHandle(interactionState.handle); - interactionState.handle = null; - } +function clearInteractionHandle(interactionState) { + if (interactionState.handle) { + InteractionManager.clearInteractionHandle(interactionState.handle); + interactionState.handle = null; + } } /** @@ -85,18 +97,18 @@ function clearInteractionHandle (interactionState) { * @param interval * @returns {*} */ -function convertToMillisecIfNeeded (interval) { - if (interval > 1000000) { - return interval / 1000000; - } - return interval; +function convertToMillisecIfNeeded(interval) { + if (interval > 1000000) { + return interval / 1000000; + } + return interval; } -function cancelSingleTapConfirm (gestureState) { - if (typeof gestureState._singleTapConfirmId !== 'undefined') { - TimerMixin.clearTimeout(gestureState._singleTapConfirmId); - gestureState._singleTapConfirmId = undefined; - } +function cancelSingleTapConfirm(gestureState) { + if (typeof gestureState._singleTapConfirmId !== 'undefined') { + TimerMixin.clearTimeout(gestureState._singleTapConfirmId); + gestureState._singleTapConfirmId = undefined; + } } /** @@ -113,180 +125,197 @@ function cancelSingleTapConfirm (gestureState) { * @param debug true to enable debug logs * @returns {{}} */ -export default function create (config) { - if (config.debug) { - DEV = true; - } - - const interactionState = { - handle: null - }; - const gestureState = { +export default function create(config) { + if (config.debug) { + DEV = true; + } + + const interactionState = { + handle: null, + }; + const gestureState = { // Useful for debugging - stateID: Math.random() - }; - initializeGestureState(gestureState); - - const handlers = { - onStartShouldSetResponder: function (e) { - DEV && console.log('onStartShouldSetResponder...'); - cancelSingleTapConfirm(gestureState); - return config.onStartShouldSetResponder ? - config.onStartShouldSetResponder(e, gestureState) : - false; - }, - onMoveShouldSetResponder: function (e) { - DEV && console.log('onMoveShouldSetResponder...'); - - return config.onMoveShouldSetResponder && effectiveMove(config, gestureState) ? - config.onMoveShouldSetResponder(e, gestureState) : - false; - }, - onStartShouldSetResponderCapture: function (e) { - DEV && console.log('onStartShouldSetResponderCapture...'); - cancelSingleTapConfirm(gestureState); + stateID: Math.random(), + }; + initializeGestureState(gestureState); + + const handlers = { + onStartShouldSetResponder: function (e) { + DEV && console.log('onStartShouldSetResponder...'); + cancelSingleTapConfirm(gestureState); + return config.onStartShouldSetResponder + ? config.onStartShouldSetResponder(e, gestureState) + : false; + }, + onMoveShouldSetResponder: function (e) { + DEV && console.log('onMoveShouldSetResponder...'); + + return config.onMoveShouldSetResponder && + effectiveMove(config, gestureState) + ? config.onMoveShouldSetResponder(e, gestureState) + : false; + }, + onStartShouldSetResponderCapture: function (e) { + DEV && console.log('onStartShouldSetResponderCapture...'); + cancelSingleTapConfirm(gestureState); // TODO: Actually, we should reinitialize the state any time // touches.length increases from 0 active to > 0 active. - if (e.nativeEvent.touches.length === 1) { - initializeGestureState(gestureState); - } - gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches; - return config.onStartShouldSetResponderCapture ? - config.onStartShouldSetResponderCapture(e, gestureState) : - false; - }, - - onMoveShouldSetResponderCapture: function (e) { - DEV && console.log('onMoveShouldSetResponderCapture...'); - const touchHistory = e.touchHistory; + if (e.nativeEvent.touches.length === 1) { + initializeGestureState(gestureState); + } + gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches; + return config.onStartShouldSetResponderCapture + ? config.onStartShouldSetResponderCapture(e, gestureState) + : false; + }, + + onMoveShouldSetResponderCapture: function (e) { + DEV && console.log('onMoveShouldSetResponderCapture...'); + 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; - } - updateGestureStateOnMove(gestureState, touchHistory, e); - return config.onMoveShouldSetResponderCapture && effectiveMove(config, gestureState) ? - config.onMoveShouldSetResponderCapture(e, gestureState) : - false; - }, - - onResponderGrant: function (e) { - DEV && console.log('onResponderGrant...'); - cancelSingleTapConfirm(gestureState); - if (!interactionState.handle) { - interactionState.handle = InteractionManager.createInteractionHandle(); - } - gestureState._grantTimestamp = e.touchHistory.mostRecentTimeStamp; - gestureState.x0 = currentCentroidX(e.touchHistory); - gestureState.y0 = currentCentroidY(e.touchHistory); - gestureState.dx = 0; - gestureState.dy = 0; - if (config.onResponderGrant) { - config.onResponderGrant(e, gestureState); - } + if ( + gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp + ) { + return false; + } + updateGestureStateOnMove(gestureState, touchHistory, e); + return config.onMoveShouldSetResponderCapture && + effectiveMove(config, gestureState) + ? config.onMoveShouldSetResponderCapture(e, gestureState) + : false; + }, + + onResponderGrant: function (e) { + DEV && console.log('onResponderGrant...'); + cancelSingleTapConfirm(gestureState); + if (!interactionState.handle) { + interactionState.handle = InteractionManager.createInteractionHandle(); + } + gestureState._grantTimestamp = e.touchHistory.mostRecentTimeStamp; + gestureState.x0 = currentCentroidX(e.touchHistory); + gestureState.y0 = currentCentroidY(e.touchHistory); + gestureState.dx = 0; + gestureState.dy = 0; + if (config.onResponderGrant) { + config.onResponderGrant(e, gestureState); + } // TODO: t7467124 investigate if this can be removed - return config.onShouldBlockNativeResponder === undefined ? - true : - config.onShouldBlockNativeResponder(); - }, - - onResponderReject: function (e) { - DEV && console.log('onResponderReject...'); - clearInteractionHandle(interactionState); - config.onResponderReject && config.onResponderReject(e, gestureState); - }, - - onResponderRelease: function (e) { - if (gestureState.singleTapUp) { - if (gestureState._lastSingleTapUp) { - if (convertToMillisecIfNeeded(e.touchHistory.mostRecentTimeStamp - gestureState._lastReleaseTimestamp) < TAP_UP_TIME_THRESHOLD) { - gestureState.doubleTapUp = true; - } - } - gestureState._lastSingleTapUp = true; + return config.onShouldBlockNativeResponder === undefined + ? true + : config.onShouldBlockNativeResponder(); + }, + + onResponderReject: function (e) { + DEV && console.log('onResponderReject...'); + clearInteractionHandle(interactionState); + config.onResponderReject && config.onResponderReject(e, gestureState); + }, + + onResponderRelease: function (e) { + if (gestureState.singleTapUp) { + if (gestureState._lastSingleTapUp) { + if ( + convertToMillisecIfNeeded( + e.touchHistory.mostRecentTimeStamp - + gestureState._lastReleaseTimestamp, + ) < TAP_UP_TIME_THRESHOLD + ) { + gestureState.doubleTapUp = true; + } + } + gestureState._lastSingleTapUp = true; // schedule to confirm single tap - if (!gestureState.doubleTapUp) { - const snapshot = Object.assign({}, gestureState); - const timeoutId = TimerMixin.setTimeout(() => { - if (gestureState._singleTapConfirmId === timeoutId) { - DEV && console.log('onResponderSingleTapConfirmed...'); - config.onResponderSingleTapConfirmed && config.onResponderSingleTapConfirmed(e, snapshot); - } - }, TAP_UP_TIME_THRESHOLD); - gestureState._singleTapConfirmId = timeoutId; - } - } - gestureState._lastReleaseTimestamp = e.touchHistory.mostRecentTimeStamp; - - DEV && console.log('onResponderRelease...' + JSON.stringify(gestureState)); - clearInteractionHandle(interactionState); - config.onResponderRelease && config.onResponderRelease(e, gestureState); - initializeGestureState(gestureState); - }, - - onResponderStart: function (e) { - DEV && console.log('onResponderStart...'); - const touchHistory = e.touchHistory; - gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - if (config.onResponderStart) { - config.onResponderStart(e, gestureState); + if (!gestureState.doubleTapUp) { + const snapshot = Object.assign({}, gestureState); + const timeoutId = TimerMixin.setTimeout(() => { + if (gestureState._singleTapConfirmId === timeoutId) { + DEV && console.log('onResponderSingleTapConfirmed...'); + config.onResponderSingleTapConfirmed && + config.onResponderSingleTapConfirmed(e, snapshot); } - }, - - onResponderMove: function (e) { - const touchHistory = e.touchHistory; + }, TAP_UP_TIME_THRESHOLD); + gestureState._singleTapConfirmId = timeoutId; + } + } + gestureState._lastReleaseTimestamp = e.touchHistory.mostRecentTimeStamp; + + DEV && + console.log('onResponderRelease...' + JSON.stringify(gestureState)); + clearInteractionHandle(interactionState); + config.onResponderRelease && config.onResponderRelease(e, gestureState); + initializeGestureState(gestureState); + }, + + onResponderStart: function (e) { + DEV && console.log('onResponderStart...'); + const touchHistory = e.touchHistory; + gestureState.numberActiveTouches = touchHistory.numberActiveTouches; + if (config.onResponderStart) { + config.onResponderStart(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; - } + 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. - updateGestureStateOnMove(gestureState, touchHistory, e); - - DEV && console.log('onResponderMove...' + JSON.stringify(gestureState)); - if (config.onResponderMove && effectiveMove(config, gestureState)) { - config.onResponderMove(e, gestureState); - } - }, - - onResponderEnd: function (e) { - const touchHistory = e.touchHistory; - gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - - if (touchHistory.numberActiveTouches > 0 || - convertToMillisecIfNeeded(touchHistory.mostRecentTimeStamp - gestureState._grantTimestamp) > TAP_UP_TIME_THRESHOLD || + updateGestureStateOnMove(gestureState, touchHistory, e); + + DEV && console.log('onResponderMove...' + JSON.stringify(gestureState)); + if (config.onResponderMove && effectiveMove(config, gestureState)) { + config.onResponderMove(e, gestureState); + } + }, + + onResponderEnd: function (e) { + const touchHistory = e.touchHistory; + gestureState.numberActiveTouches = touchHistory.numberActiveTouches; + + if ( + touchHistory.numberActiveTouches > 0 || + convertToMillisecIfNeeded( + touchHistory.mostRecentTimeStamp - gestureState._grantTimestamp, + ) > TAP_UP_TIME_THRESHOLD || Math.abs(gestureState.dx) >= TAP_MOVE_THRESHOLD || Math.abs(gestureState.dy) >= TAP_MOVE_THRESHOLD ) { - gestureState._singleTabFailed = true; - } - if (!gestureState._singleTabFailed) { - gestureState.singleTapUp = true; - } - - DEV && console.log('onResponderEnd...' + JSON.stringify(gestureState)); - clearInteractionHandle(interactionState); - config.onResponderEnd && config.onResponderEnd(e, gestureState); - }, - - onResponderTerminate: function (e) { - DEV && console.log('onResponderTerminate...'); - clearInteractionHandle(interactionState); - config.onResponderTerminate && config.onResponderTerminate(e, gestureState); - initializeGestureState(gestureState); - }, - - onResponderTerminationRequest: function (e) { - DEV && console.log('onResponderTerminationRequest...'); - return config.onResponderTerminationRequest ? - config.onResponderTerminationRequest(e.gestureState) : - true; - } - }; - return {...handlers}; + gestureState._singleTabFailed = true; + } + if (!gestureState._singleTabFailed) { + gestureState.singleTapUp = true; + } + + DEV && console.log('onResponderEnd...' + JSON.stringify(gestureState)); + clearInteractionHandle(interactionState); + config.onResponderEnd && config.onResponderEnd(e, gestureState); + }, + + onResponderTerminate: function (e) { + DEV && console.log('onResponderTerminate...'); + clearInteractionHandle(interactionState); + config.onResponderTerminate && + config.onResponderTerminate(e, gestureState); + initializeGestureState(gestureState); + }, + + onResponderTerminationRequest: function (e) { + DEV && console.log('onResponderTerminationRequest...'); + return config.onResponderTerminationRequest + ? config.onResponderTerminationRequest(e.gestureState) + : true; + }, + }; + return {...handlers}; } /** @@ -296,18 +325,21 @@ export default function create (config) { * @param gestureState * @returns {boolean} */ -function effectiveMove (config, gestureState) { - if (gestureState.numberActiveTouches > 1) { +function effectiveMove(config, gestureState) { + if (gestureState.numberActiveTouches > 1) { // on iOS simulator, a pinch gesture(move with alt pressed) will not change gestureState.dx(always 0) - return true; - } - - let moveThreshold = MOVE_THRESHOLD; - if (typeof config.moveThreshold === 'number') { - moveThreshold = config.minMoveDistance; - } - if (Math.abs(gestureState.dx) >= moveThreshold || Math.abs(gestureState.dy) >= moveThreshold) { - return true; - } - return false; + return true; + } + + let moveThreshold = MOVE_THRESHOLD; + if (typeof config.moveThreshold === 'number') { + moveThreshold = config.minMoveDistance; + } + if ( + Math.abs(gestureState.dx) >= moveThreshold || + Math.abs(gestureState.dy) >= moveThreshold + ) { + return true; + } + return false; } diff --git a/src/libraries/GestureResponder/index.js b/src/libraries/GestureResponder/index.js index 675464a..1715814 100644 --- a/src/libraries/GestureResponder/index.js +++ b/src/libraries/GestureResponder/index.js @@ -2,4 +2,4 @@ import createResponder from './createResponder'; -export { createResponder }; +export {createResponder}; diff --git a/src/libraries/Scroller/AnimationUtils.js b/src/libraries/Scroller/AnimationUtils.js index 8dfd258..c9a071f 100644 --- a/src/libraries/Scroller/AnimationUtils.js +++ b/src/libraries/Scroller/AnimationUtils.js @@ -1,3 +1,3 @@ export function currentAnimationTimeMillis() { - return Date.now(); + return Date.now(); } diff --git a/src/libraries/Scroller/ViscousFluidInterpolator.js b/src/libraries/Scroller/ViscousFluidInterpolator.js index 263f32b..d2f394f 100644 --- a/src/libraries/Scroller/ViscousFluidInterpolator.js +++ b/src/libraries/Scroller/ViscousFluidInterpolator.js @@ -4,26 +4,26 @@ const VISCOUS_FLUID_SCALE = 8; const VISCOUS_FLUID_NORMALIZE = 1 / viscousFluid(1); const VISCOUS_FLUID_OFFSET = 1 - VISCOUS_FLUID_NORMALIZE * viscousFluid(1); -function viscousFluid (x) { - x *= VISCOUS_FLUID_SCALE; - if (x < 1) { - x -= (1 - Math.exp(-x)); - } else { - var start = 0.36787944117; // 1/e == exp(-1) - x = 1 - Math.exp(1 - x); - x = start + x * (1 - start); - } - return x; +function viscousFluid(x) { + x *= VISCOUS_FLUID_SCALE; + if (x < 1) { + x -= 1 - Math.exp(-x); + } else { + var start = 0.36787944117; // 1/e == exp(-1) + x = 1 - Math.exp(1 - x); + x = start + x * (1 - start); + } + return x; } const ViscousFluidInterpolator = { - getInterpolation: function (input) { - var interpolated = VISCOUS_FLUID_NORMALIZE * viscousFluid(input); - if (interpolated > 0) { - return interpolated + VISCOUS_FLUID_OFFSET; - } - return interpolated; + getInterpolation: function (input) { + var interpolated = VISCOUS_FLUID_NORMALIZE * viscousFluid(input); + if (interpolated > 0) { + return interpolated + VISCOUS_FLUID_OFFSET; } + return interpolated; + }, }; export default ViscousFluidInterpolator; diff --git a/src/libraries/Scroller/index.js b/src/libraries/Scroller/index.js index fde05f0..b696199 100644 --- a/src/libraries/Scroller/index.js +++ b/src/libraries/Scroller/index.js @@ -32,49 +32,49 @@ const SPLINE_TIME = []; const GRAVITY_EARTH = 9.80665; (function () { - var x_min = 0; - var y_min = 0; - for (let i = 0; i < NB_SAMPLES; i++) { - let alpha = i / NB_SAMPLES; - - let x_max = 1; - let x, tx, coef; - while (true) { - x = x_min + (x_max - x_min) / 2.0; - coef = 3.0 * x * (1.0 - x); - tx = coef * ((1.0 - x) * P1 + x * P2) + x * x * x; - if (Math.abs(tx - alpha) < 1E-5) break; - if (tx > alpha) x_max = x; - else x_min = x; - } - SPLINE_POSITION[i] = coef * ((1.0 - x) * START_TENSION + x) + x * x * x; - - let y_max = 1.0; - let y, dy; - while (true) { - y = y_min + (y_max - y_min) / 2.0; - coef = 3.0 * y * (1.0 - y); - dy = coef * ((1.0 - y) * START_TENSION + y) + y * y * y; - if (Math.abs(dy - alpha) < 1E-5) break; - if (dy > alpha) y_max = y; - else y_min = y; - } - SPLINE_TIME[i] = coef * ((1.0 - y) * P1 + y * P2) + y * y * y; + var x_min = 0; + var y_min = 0; + for (let i = 0; i < NB_SAMPLES; i++) { + let alpha = i / NB_SAMPLES; + + let x_max = 1; + let x, tx, coef; + while (true) { + x = x_min + (x_max - x_min) / 2.0; + coef = 3.0 * x * (1.0 - x); + tx = coef * ((1.0 - x) * P1 + x * P2) + x * x * x; + if (Math.abs(tx - alpha) < 1e-5) break; + if (tx > alpha) x_max = x; + else x_min = x; } - SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0; + SPLINE_POSITION[i] = coef * ((1.0 - x) * START_TENSION + x) + x * x * x; + + let y_max = 1.0; + let y, dy; + while (true) { + y = y_min + (y_max - y_min) / 2.0; + coef = 3.0 * y * (1.0 - y); + dy = coef * ((1.0 - y) * START_TENSION + y) + y * y * y; + if (Math.abs(dy - alpha) < 1e-5) break; + if (dy > alpha) y_max = y; + else y_min = y; + } + SPLINE_TIME[i] = coef * ((1.0 - y) * P1 + y * P2) + y * y * y; + } + SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0; })(); -function signum (number) { - if (isNaN(number)) { - return NaN; - } - var sig = number; - if (number > 0) { - sig = 1; - } else if (number < 0) { - sig = -1; - } - return sig; +function signum(number) { + if (isNaN(number)) { + return NaN; + } + var sig = number; + if (number > 0) { + sig = 1; + } else if (number < 0) { + sig = -1; + } + return sig; } export default class Scroller { @@ -82,148 +82,156 @@ export default class Scroller { * * @param flywheel specify whether or not to support progressive "flywheel" behavior in flinging. */ - constructor (flywheel, onScrollCallback) { - this.mCurrX = 0; - this.mCurrY = 0; - this.mFinished = true; - this.mInterpolator = ViscousFluidInterpolator; + constructor(flywheel, onScrollCallback) { + this.mCurrX = 0; + this.mCurrY = 0; + this.mFinished = true; + this.mInterpolator = ViscousFluidInterpolator; // this.mPpi = PixelRatio.get() * 160; - this.mPpi = 160; - this.mDeceleration = this.computeDeceleration(SCROLL_FRICTION); - this.mFlywheel = flywheel; + this.mPpi = 160; + this.mDeceleration = this.computeDeceleration(SCROLL_FRICTION); + this.mFlywheel = flywheel; - this.mPhysicalCoeff = this.computeDeceleration(0.84); // look and feel tuning + this.mPhysicalCoeff = this.computeDeceleration(0.84); // look and feel tuning - this.mFlingFriction = SCROLL_FRICTION; - this.onScrollCallback = onScrollCallback; - } + this.mFlingFriction = SCROLL_FRICTION; + this.onScrollCallback = onScrollCallback; + } - computeDeceleration (friction) { - return GRAVITY_EARTH * 39.37 * this.mPpi * friction; - } + computeDeceleration(friction) { + return GRAVITY_EARTH * 39.37 * this.mPpi * friction; + } /** * Returns whether the scroller has finished scrolling. * @returns {Boolean} True if the scroller has finished scrolling, false otherwise. */ - isFinished () { - return this.mFinished; - } + isFinished() { + return this.mFinished; + } /** * Force the finished field to a particular value. * @param finished The new finished value. */ - forceFinished (finished) { - this.mFinished = finished; - } + forceFinished(finished) { + this.mFinished = finished; + } /** * Returns the current X offset in the scroll. * @returns {*} The new X offset as an absolute distance from the origin. */ - getCurrX () { - return this.mCurrX; - } + getCurrX() { + return this.mCurrX; + } /** * Returns the current Y offset in the scroll. * @returns {*} The new Y offset as an absolute distance from the origin. */ - getCurrY () { - return this.mCurrY; - } - - getCurrVelocity () { - return this.mMode === FLING_MODE ? - this.mCurrVelocity : this.mVelocity - this.mDeceleration * this.timePassed() / 2000.0; + getCurrY() { + return this.mCurrY; + } + + getCurrVelocity() { + return this.mMode === FLING_MODE + ? this.mCurrVelocity + : this.mVelocity - (this.mDeceleration * this.timePassed()) / 2000.0; + } + + computeScrollOffset() { + if (this.mFinished) { + this.onScrollCallback && this.onScrollCallback(0, 0, this); + return false; } - computeScrollOffset () { - if (this.mFinished) { - this.onScrollCallback && this.onScrollCallback(0, 0, this); - return false; - } - - var timePassed = currentAnimationTimeMillis() - this.mStartTime; - - if (timePassed < this.mDuration) { - switch (this.mMode) { - case SCROLL_MODE: - let x = this.mInterpolator.getInterpolation(timePassed * this.mDurationReciprocal); - this.mCurrX = this.mStartX + Math.round(x * this.mDeltaX); - this.mCurrY = this.mStartY + Math.round(x * this.mDeltaY); - break; - case FLING_MODE: - let t = timePassed / this.mDuration; - let index = parseInt(NB_SAMPLES * t); - let distanceCoef = 1; - let velocityCoef = 0; - if (index < NB_SAMPLES) { - let t_inf = index / NB_SAMPLES; - let t_sup = (index + 1) / NB_SAMPLES; - let d_inf = SPLINE_POSITION[index]; - let d_sup = SPLINE_POSITION[index + 1]; - velocityCoef = (d_sup - d_inf) / (t_sup - t_inf); - distanceCoef = d_inf + (t - t_inf) * velocityCoef; - } - - this.mCurrVelocity = velocityCoef * this.mDistance / this.mDuration * 1000; - - this.mCurrX = this.mStartX + Math.round(distanceCoef * (this.mFinalX - this.mStartX)); + var timePassed = currentAnimationTimeMillis() - this.mStartTime; + + if (timePassed < this.mDuration) { + switch (this.mMode) { + case SCROLL_MODE: + let x = this.mInterpolator.getInterpolation( + timePassed * this.mDurationReciprocal, + ); + this.mCurrX = this.mStartX + Math.round(x * this.mDeltaX); + this.mCurrY = this.mStartY + Math.round(x * this.mDeltaY); + break; + case FLING_MODE: + let t = timePassed / this.mDuration; + let index = parseInt(NB_SAMPLES * t); + let distanceCoef = 1; + let velocityCoef = 0; + if (index < NB_SAMPLES) { + let t_inf = index / NB_SAMPLES; + let t_sup = (index + 1) / NB_SAMPLES; + let d_inf = SPLINE_POSITION[index]; + let d_sup = SPLINE_POSITION[index + 1]; + velocityCoef = (d_sup - d_inf) / (t_sup - t_inf); + distanceCoef = d_inf + (t - t_inf) * velocityCoef; + } + + this.mCurrVelocity = + ((velocityCoef * this.mDistance) / this.mDuration) * 1000; + + this.mCurrX = + this.mStartX + + Math.round(distanceCoef * (this.mFinalX - this.mStartX)); // Pin to mMinX <= mCurrX <= mMaxX // this.mCurrX = Math.min(this.mCurrX, this.mMaxX); // this.mCurrX = Math.max(this.mCurrX, this.mMinX); - this.mCurrY = this.mStartY + Math.round(distanceCoef * (this.mFinalY - this.mStartY)); + this.mCurrY = + this.mStartY + + Math.round(distanceCoef * (this.mFinalY - this.mStartY)); // Pin to mMinY <= mCurrY <= mMaxY - this.mCurrY = Math.min(this.mCurrY, this.mMaxY); - this.mCurrY = Math.max(this.mCurrY, this.mMinY); - - if (this.mCurrX == this.mFinalX && this.mCurrY == this.mFinalY) { - this.mFinished = true; - } - - break; - } - } else { - this.mCurrX = this.mFinalX; - this.mCurrY = this.mFinalY; + this.mCurrY = Math.min(this.mCurrY, this.mMaxY); + this.mCurrY = Math.max(this.mCurrY, this.mMinY); + + if (this.mCurrX == this.mFinalX && this.mCurrY == this.mFinalY) { this.mFinished = true; - } + } + + break; + } + } else { + this.mCurrX = this.mFinalX; + this.mCurrY = this.mFinalY; + this.mFinished = true; + } - var dx = this.mCurrX - this.mLastX; - var dy = this.mCurrY - this.mLastY; + var dx = this.mCurrX - this.mLastX; + var dy = this.mCurrY - this.mLastY; - this.mLastX = this.mCurrX; - this.mLastY = this.mCurrY; + this.mLastX = this.mCurrX; + this.mLastY = this.mCurrY; - this.onScrollCallback && this.onScrollCallback(dx, dy, this); + this.onScrollCallback && this.onScrollCallback(dx, dy, this); - if (dx === 0 && dy === 0 && this.mFinished) { - return false; - } - return true; - } - - startScroll (startX, startY, dx, dy, duration = DEFAULT_DURATION) { - this.mMode = SCROLL_MODE; - this.mFinished = false; - this.mDuration = duration; - this.mStartTime = currentAnimationTimeMillis(); - this.mStartX = startX; - this.mStartY = startY; - this.mFinalX = startX + dx; - this.mFinalY = startY + dy; - this.mDeltaX = dx; - this.mDeltaY = dy; - this.mDurationReciprocal = 1.0 / this.mDuration; - - this.mLastX = this.mStartX; - this.mLastY = this.mStartY; - - this.performAnimation(); + if (dx === 0 && dy === 0 && this.mFinished) { + return false; } + return true; + } + + startScroll(startX, startY, dx, dy, duration = DEFAULT_DURATION) { + this.mMode = SCROLL_MODE; + this.mFinished = false; + this.mDuration = duration; + this.mStartTime = currentAnimationTimeMillis(); + this.mStartX = startX; + this.mStartY = startY; + this.mFinalX = startX + dx; + this.mFinalY = startY + dy; + this.mDeltaX = dx; + this.mDeltaY = dy; + this.mDurationReciprocal = 1.0 / this.mDuration; + + this.mLastX = this.mStartX; + this.mLastY = this.mStartY; + + this.performAnimation(); + } /** * Start scrolling based on a fling gesture. The distance travelled will @@ -237,119 +245,136 @@ export default class Scroller { * @param minY * @param maxY */ - fling (startX, startY, velocityX, velocityY, - minX, maxX, minY, maxY) { + fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY) { // Continue a scroll or fling in progress - if (this.mFlywheel && !this.mFinished) { - let oldVel = this.getCurrVelocity(); - - let dx = this.mFinalX - this.mStartX; - let dy = this.mFinalY - this.mStartY; - let hyp = Math.hypot(dx, dy); - - let ndx = dx / hyp; - let ndy = dy / hyp; - - let oldVelocityX = ndx * oldVel; - let oldVelocityY = ndy * oldVel; - if (signum(velocityX) === signum(oldVelocityX) && - signum(velocityY) === signum(oldVelocityY)) { - velocityX += oldVelocityX; - velocityY += oldVelocityY; - } - } + if (this.mFlywheel && !this.mFinished) { + let oldVel = this.getCurrVelocity(); + + let dx = this.mFinalX - this.mStartX; + let dy = this.mFinalY - this.mStartY; + let hyp = Math.hypot(dx, dy); + + let ndx = dx / hyp; + let ndy = dy / hyp; + + let oldVelocityX = ndx * oldVel; + let oldVelocityY = ndy * oldVel; + if ( + signum(velocityX) === signum(oldVelocityX) && + signum(velocityY) === signum(oldVelocityY) + ) { + velocityX += oldVelocityX; + velocityY += oldVelocityY; + } + } - this.mMode = FLING_MODE; - this.mFinished = false; + this.mMode = FLING_MODE; + this.mFinished = false; - let velocity = Math.hypot(velocityX, velocityY); + let velocity = Math.hypot(velocityX, velocityY); - this.mVelocity = velocity; - this.mDuration = this.getSplineFlingDuration(velocity); - this.mStartTime = currentAnimationTimeMillis(); - this.mStartX = startX; - this.mStartY = startY; + this.mVelocity = velocity; + this.mDuration = this.getSplineFlingDuration(velocity); + this.mStartTime = currentAnimationTimeMillis(); + this.mStartX = startX; + this.mStartY = startY; - let coeffX = velocity == 0 ? 1.0 : velocityX / velocity; - let coeffY = velocity == 0 ? 1.0 : velocityY / velocity; + let coeffX = velocity == 0 ? 1.0 : velocityX / velocity; + let coeffY = velocity == 0 ? 1.0 : velocityY / velocity; - let totalDistance = this.getSplineFlingDistance(velocity); - this.mDistance = totalDistance * signum(velocity); + let totalDistance = this.getSplineFlingDistance(velocity); + this.mDistance = totalDistance * signum(velocity); - this.mMinX = minX; - this.mMaxX = maxX; - this.mMinY = minY; - this.mMaxY = maxY; + this.mMinX = minX; + this.mMaxX = maxX; + this.mMinY = minY; + this.mMaxY = maxY; - this.mFinalX = startX + Math.round(totalDistance * coeffX); + this.mFinalX = startX + Math.round(totalDistance * coeffX); // Pin to mMinX <= mFinalX <= mMaxX - this.mFinalX = Math.min(this.mFinalX, this.mMaxX); - this.mFinalX = Math.max(this.mFinalX, this.mMinX); + this.mFinalX = Math.min(this.mFinalX, this.mMaxX); + this.mFinalX = Math.max(this.mFinalX, this.mMinX); - this.mFinalY = startY + Math.round(totalDistance * coeffY); + this.mFinalY = startY + Math.round(totalDistance * coeffY); // Pin to mMinY <= mFinalY <= mMaxY - this.mFinalY = Math.min(this.mFinalY, this.mMaxY); - this.mFinalY = Math.max(this.mFinalY, this.mMinY); - - this.mLastX = this.mStartX; - this.mLastY = this.mStartY; - - this.performAnimation(); - } - - getSplineDeceleration (velocity) { - return Math.log(INFLEXION * Math.abs(velocity) / (this.mFlingFriction * this.mPhysicalCoeff)); - } - - getSplineFlingDuration (velocity) { - var l = this.getSplineDeceleration(velocity); - var decelMinusOne = DECELERATION_RATE - 1.0; - return 1000.0 * Math.exp(l / decelMinusOne); - } - - getSplineFlingDistance (velocity) { - var l = this.getSplineDeceleration(velocity); - var decelMinusOne = DECELERATION_RATE - 1.0; - return this.mFlingFriction * this.mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l); - } - - performAnimation () { - if (this.computeScrollOffset()) { - requestAnimationFrame(this.performAnimation.bind(this)); - } else { - } - } - - abortAnimation () { - this.mCurrX = this.mFinalX; - this.mCurrY = this.mFinalY; - this.mFinished = true; - } - - extendDuration (extend) { - var passed = timePassed(); - this.mDuration = passed + extend; - this.mDurationReciprocal = 1.0 / this.mDuration; - this.mFinished = false; - } - - timePassed () { - return currentAnimationTimeMillis() - this.mStartTime; - } - - setFinalX (newX) { - this.mFinalX = newX; - this.mDeltaX = this.mFinalX - this.mStartX; - this.mFinished = false; - } - - setFinalY (newY) { - this.mFinalY = newY; - this.mDeltaY = this.mFinalY - this.mStartY; - this.mFinished = false; - } - - debugInfo () { - return 'cur=' + this.mCurrX + ' ' + this.mCurrY + ', final=' + this.mFinalX + ' ' + this.mFinalY; + this.mFinalY = Math.min(this.mFinalY, this.mMaxY); + this.mFinalY = Math.max(this.mFinalY, this.mMinY); + + this.mLastX = this.mStartX; + this.mLastY = this.mStartY; + + this.performAnimation(); + } + + getSplineDeceleration(velocity) { + return Math.log( + (INFLEXION * Math.abs(velocity)) / + (this.mFlingFriction * this.mPhysicalCoeff), + ); + } + + getSplineFlingDuration(velocity) { + var l = this.getSplineDeceleration(velocity); + var decelMinusOne = DECELERATION_RATE - 1.0; + return 1000.0 * Math.exp(l / decelMinusOne); + } + + getSplineFlingDistance(velocity) { + var l = this.getSplineDeceleration(velocity); + var decelMinusOne = DECELERATION_RATE - 1.0; + return ( + this.mFlingFriction * + this.mPhysicalCoeff * + Math.exp((DECELERATION_RATE / decelMinusOne) * l) + ); + } + + performAnimation() { + if (this.computeScrollOffset()) { + requestAnimationFrame(this.performAnimation.bind(this)); + } else { } + } + + abortAnimation() { + this.mCurrX = this.mFinalX; + this.mCurrY = this.mFinalY; + this.mFinished = true; + } + + extendDuration(extend) { + var passed = timePassed(); + this.mDuration = passed + extend; + this.mDurationReciprocal = 1.0 / this.mDuration; + this.mFinished = false; + } + + timePassed() { + return currentAnimationTimeMillis() - this.mStartTime; + } + + setFinalX(newX) { + this.mFinalX = newX; + this.mDeltaX = this.mFinalX - this.mStartX; + this.mFinished = false; + } + + setFinalY(newY) { + this.mFinalY = newY; + this.mDeltaY = this.mFinalY - this.mStartY; + this.mFinished = false; + } + + debugInfo() { + return ( + 'cur=' + + this.mCurrX + + ' ' + + this.mCurrY + + ', final=' + + this.mFinalX + + ' ' + + this.mFinalY + ); + } } diff --git a/src/libraries/TransformableImage/index.js b/src/libraries/TransformableImage/index.js index 8ff8469..12dd051 100644 --- a/src/libraries/TransformableImage/index.js +++ b/src/libraries/TransformableImage/index.js @@ -1,207 +1,268 @@ -import React, { PureComponent } from 'react'; -import { View, Text, Image, ViewPropTypes } from 'react-native'; +import React, {PureComponent} from 'react'; +import { + View, + Text, + Image, + Dimensions, + ViewStyle, + ViewPropTypes, +} from 'react-native'; import PropTypes from 'prop-types'; import ViewTransformer from '../ViewTransformer'; export default class TransformableImage extends PureComponent { - static propTypes = { - image: PropTypes.shape({ - source: PropTypes.oneOfType([ - PropTypes.object, - PropTypes.number - ]).isRequired, - dimensions: PropTypes.shape({ width: PropTypes.number, height: PropTypes.number }) - }).isRequired, - style: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - onLoad: PropTypes.func, - onLoadStart: PropTypes.func, - enableTransform: PropTypes.bool, - enableScale: PropTypes.bool, - enableTranslate: PropTypes.bool, - onTransformGestureReleased: PropTypes.func, - onViewTransformed: PropTypes.func, - imageComponent: PropTypes.func, - resizeMode: PropTypes.string, - errorComponent: PropTypes.func - }; + static propTypes = { + image: PropTypes.shape({ + source: PropTypes.oneOfType([PropTypes.object, PropTypes.number]) + .isRequired, + dimensions: PropTypes.shape({ + width: PropTypes.number, + height: PropTypes.number, + }), + }).isRequired, + style: ViewPropTypes ? ViewPropTypes.style : ViewStyle, + onLoad: PropTypes.func, + onLoadStart: PropTypes.func, + enableTransform: PropTypes.bool, + enableScale: PropTypes.bool, + enableTranslate: PropTypes.bool, + onTransformGestureReleased: PropTypes.func, + onViewTransformed: PropTypes.func, + imageComponent: PropTypes.func, + resizeMode: PropTypes.string, + errorComponent: PropTypes.func, + maxScale: PropTypes.number, + }; + + static defaultProps = { + enableTransform: true, + enableScale: true, + enableTranslate: true, + imageComponent: undefined, + resizeMode: 'contain', + }; + + constructor(props) { + super(props); - static defaultProps = { - enableTransform: true, - enableScale: true, - enableTranslate: true, - imageComponent: undefined, - resizeMode: 'contain' + this.onLayout = this.onLayout.bind(this); + this.onLoad = this.onLoad.bind(this); + this.onLoadStart = this.onLoadStart.bind(this); + this.getViewTransformerInstance = + this.getViewTransformerInstance.bind(this); + this.renderError = this.renderError.bind(this); + + this.state = { + viewWidth: 0, + viewHeight: 0, + imageLoaded: false, + imageDimensions: props.image.dimensions, + keyAcumulator: 1, }; + } - constructor (props) { - super(props); - - this.onLayout = this.onLayout.bind(this); - this.onLoad = this.onLoad.bind(this); - this.onLoadStart = this.onLoadStart.bind(this); - this.getViewTransformerInstance = this.getViewTransformerInstance.bind(this); - this.renderError = this.renderError.bind(this); - - this.state = { - viewWidth: 0, - viewHeight: 0, - imageLoaded: false, - imageDimensions: props.image.dimensions, - keyAcumulator: 1 - }; + UNSAFE_componentWillMount() { + if (!this.state.imageDimensions) { + this.getImageSize(this.props.image); } + } - componentWillMount () { - if (!this.state.imageDimensions) { - this.getImageSize(this.props.image); - } - } + componentDidMount() { + this._mounted = true; + } - componentDidMount () { - this._mounted = true; + UNSAFE_componentWillReceiveProps(nextProps) { + if (!sameImage(this.props.image, nextProps.image)) { + // image source changed, clear last image's imageDimensions info if any + this.setState({ + imageDimensions: nextProps.image.dimensions, + keyAcumulator: this.state.keyAcumulator + 1, + }); + if (!nextProps.image.dimensions) { + // if we don't have image dimensions provided in source + this.getImageSize(nextProps.image); + } } + } - componentWillReceiveProps (nextProps) { - if (!sameImage(this.props.image, nextProps.image)) { - // image source changed, clear last image's imageDimensions info if any - this.setState({ imageDimensions: nextProps.image.dimensions, keyAcumulator: this.state.keyAcumulator + 1 }); - if (!nextProps.image.dimensions) { // if we don't have image dimensions provided in source - this.getImageSize(nextProps.image); - } - } - } + componentWillUnmount() { + this._mounted = false; + } - componentWillUnmount () { - this._mounted = false; + onLoadStart(e) { + this.props.onLoadStart && this.props.onLoadStart(e); + if (this.state.imageLoaded) { + this.setState({imageLoaded: false}); } + } - onLoadStart (e) { - this.props.onLoadStart && this.props.onLoadStart(e); - if (this.state.imageLoaded) { - this.setState({ imageLoaded: false }); - } + onLoad(e) { + this.props.onLoad && this.props.onLoad(e); + if (!this.state.imageLoaded) { + this.setState({imageLoaded: true}); } + } - onLoad (e) { - this.props.onLoad && this.props.onLoad(e); - if (!this.state.imageLoaded) { - this.setState({ imageLoaded: true }); - } + onLayout(e) { + let {width, height} = e.nativeEvent.layout; + if (this.state.viewWidth !== width || this.state.viewHeight !== height) { + this.setState({viewWidth: width, viewHeight: height}); } + } - onLayout (e) { - let {width, height} = e.nativeEvent.layout; - if (this.state.viewWidth !== width || this.state.viewHeight !== height) { - this.setState({ viewWidth: width, viewHeight: height }); - } + getImageSize(image) { + if (!image) { + return; } + const {source, dimensions} = image; - getImageSize (image) { - if (!image) { - return; - } - const { source, dimensions } = image; - - if (dimensions) { - this.setState({ imageDimensions: dimensions }); - return; - } - - if (source && source.uri) { - Image.getSize( - source.uri, - (width, height) => { - if (width && height) { - if (this.state.imageDimensions && this.state.imageDimensions.width === width && this.state.imageDimensions.height === height) { - // no need to update state - } else { - this._mounted && this.setState({ imageDimensions: { width, height } }); - } - } - }, - () => { - this._mounted && this.setState({ error: true }); - } - ); - } else { - console.warn('react-native-image-gallery', 'Please provide dimensions of your local images'); - } + if (dimensions) { + this.setState({imageDimensions: dimensions}); + return; } - getViewTransformerInstance () { - return this.refs['viewTransformer']; + if (source && source.uri) { + Image.getSize( + source.uri, + (width, height) => { + if (width && height) { + if ( + this.state.imageDimensions && + this.state.imageDimensions.width === width && + this.state.imageDimensions.height === height + ) { + // no need to update state + } else { + this._mounted && + this.setState({imageDimensions: {width, height}}); + } + } + }, + () => { + this._mounted && this.setState({error: true}); + }, + ); + } else { + console.warn( + 'react-native-image-gallery', + 'Please provide dimensions of your local images', + ); } + } - renderError () { - return (this.props.errorComponent && this.props.errorComponent()) || ( - - This image cannot be displayed... - - ); - } + getViewTransformerInstance() { + return this.refs['viewTransformer']; + } - render () { - const { imageDimensions, viewWidth, viewHeight, error, keyAccumulator, imageLoaded } = this.state; - const { style, image, imageComponent, resizeMode, enableTransform, enableScale, enableTranslate, onTransformGestureReleased, onViewTransformed } = this.props; + renderError() { + return ( + (this.props.errorComponent && this.props.errorComponent()) || ( + + + This image cannot be displayed... + + + ) + ); + } - let maxScale = 1; - let contentAspectRatio; - let width, height; // imageDimensions + render() { + const { + imageDimensions, + viewWidth, + viewHeight, + error, + keyAccumulator, + imageLoaded, + } = this.state; + const { + style, + image, + imageComponent, + resizeMode, + enableTransform, + enableScale, + enableTranslate, + onTransformGestureReleased, + onViewTransformed, + } = this.props; - if (imageDimensions) { - width = imageDimensions.width; - height = imageDimensions.height; - } + let maxScale = this.props.maxScale; + let contentAspectRatio; + let width, height; // imageDimensions - if (width && height) { - contentAspectRatio = width / height; - if (viewWidth && viewHeight) { - maxScale = Math.max(width / viewWidth, height / viewHeight); - maxScale = Math.max(1, maxScale); - } - } - - const imageProps = { - ...this.props, - imageLoaded, - source: image.source, - style: [style, { backgroundColor: 'transparent' }], - resizeMode: resizeMode, - onLoadStart: this.onLoadStart, - onLoad: this.onLoad, - capInsets: { left: 0.1, top: 0.1, right: 0.1, bottom: 0.1 } - }; - - const content = imageComponent ? imageComponent(imageProps, imageDimensions) : ; - - return ( - - { error ? this.renderError() : content } - - ); + if (imageDimensions) { + width = imageDimensions.width; + height = imageDimensions.height; } -} -function sameImage (source, nextSource) { - if (source === nextSource) { - return true; + if (width && height) { + contentAspectRatio = width / height; + // if (viewWidth && viewHeight) { + // maxScale = Math.max(width / viewWidth, height / viewHeight); + // maxScale = Math.max(1, maxScale); + // } } - if (source && nextSource) { - if (source.uri && nextSource.uri) { - return source.uri === nextSource.uri; - } + + const imageProps = { + ...this.props, + imageLoaded, + source: image.source, + style: [ + style, + { + backgroundColor: 'transparent', + width: Dimensions.get('screen').width, + height: Dimensions.get('screen').width, + }, + ], + resizeMode: resizeMode, + resizeMethod: 'resize', + onLoadStart: this.onLoadStart, + onLoad: this.onLoad, + capInsets: {left: 0.1, top: 0.1, right: 0.1, bottom: 0.1}, + }; + + const content = imageComponent ? ( + imageComponent(imageProps, imageDimensions) + ) : ( + + ); + + return ( + + {error ? this.renderError() : content} + + ); + } +} + +function sameImage(source, nextSource) { + if (source === nextSource) { + return true; + } + if (source && nextSource) { + if (source.uri && nextSource.uri) { + return source.uri === nextSource.uri; } - return false; + } + return false; } diff --git a/src/libraries/ViewPager/index.js b/src/libraries/ViewPager/index.js index b786ad1..96d9bab 100644 --- a/src/libraries/ViewPager/index.js +++ b/src/libraries/ViewPager/index.js @@ -1,338 +1,396 @@ -import React, { PureComponent } from 'react'; +import React, {PureComponent} from 'react'; import { - View, - FlatList, - ViewPropTypes, - InteractionManager, - Dimensions + View, + FlatList, + InteractionManager, + Dimensions, + ViewStyle, + ViewPropTypes, } from 'react-native'; import PropTypes from 'prop-types'; import Scroller from '../Scroller'; -import { createResponder } from '../GestureResponder'; +import {createResponder} from '../GestureResponder'; const MIN_FLING_VELOCITY = 0.5; // Dimensions are only used initially. // onLayout should handle orientation swap. -const { width, height } = Dimensions.get('window'); +const {width, height} = Dimensions.get('window'); export default class ViewPager extends PureComponent { - static propTypes = { - ...View.propTypes, - initialPage: PropTypes.number, - pageMargin: PropTypes.number, - scrollViewStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style, - scrollEnabled: PropTypes.bool, - renderPage: PropTypes.func, - pageDataArray: PropTypes.array, - initialListSize: PropTypes.number, - removeClippedSubviews: PropTypes.bool, - onPageSelected: PropTypes.func, - onPageScrollStateChanged: PropTypes.func, - onPageScroll: PropTypes.func, - flatListProps: PropTypes.object - }; - - static defaultProps = { - initialPage: 0, - pageMargin: 0, - scrollEnabled: true, - pageDataArray: [], - initialListSize: 10, - removeClippedSubviews: true, - flatListProps: {} - }; - - currentPage = undefined; // Do not initialize to make onPageSelected(0) be dispatched - layoutChanged = false; - activeGesture = false; - gestureResponder = undefined; - - state = { width, height }; - - constructor (props) { - super(props); - - this.onLayout = this.onLayout.bind(this); - this.renderRow = this.renderRow.bind(this); - this.onResponderGrant = this.onResponderGrant.bind(this); - this.onResponderMove = this.onResponderMove.bind(this); - this.onResponderRelease = this.onResponderRelease.bind(this); - this.getItemLayout = this.getItemLayout.bind(this); - - this.scroller = this.createScroller(); - } - - createScroller () { - return new Scroller(true, (dx, dy, scroller) => { - if (dx === 0 && dy === 0 && scroller.isFinished()) { - if (!this.activeGesture) { - this.onPageScrollStateChanged('idle'); - } - } else { - const curX = this.scroller.getCurrX(); - this.refs['innerFlatList'] && this.refs['innerFlatList'].scrollToOffset({ offset: curX, animated: false }); - - let position = Math.floor(curX / (this.state.width + this.props.pageMargin)); - position = this.validPage(position); - let offset = (curX - this.getScrollOffsetOfPage(position)) / (this.state.width + this.props.pageMargin); - let fraction = (curX - this.getScrollOffsetOfPage(position) - this.props.pageMargin) / this.state.width; - if (fraction < 0) { - fraction = 0; - } - this.props.onPageScroll && this.props.onPageScroll({ - position, offset, fraction - }); - } - }); - } - - componentWillMount () { - this.gestureResponder = createResponder({ - onStartShouldSetResponder: (evt, gestureState) => true, - onResponderGrant: this.onResponderGrant, - onResponderMove: this.onResponderMove, - onResponderRelease: this.onResponderRelease, - onResponderTerminate: this.onResponderRelease - }); - } - - componentDidMount () { - // FlatList is set to render at initialPage. - // The scroller we use is not aware of this. - // Let it know by simulating most of what happens in scrollToPage() - this.onPageScrollStateChanged('settling'); - - const page = this.validPage(this.props.initialPage); - this.onPageChanged(page); - - const finalX = this.getScrollOffsetOfPage(page); - this.scroller.startScroll(this.scroller.getCurrX(), 0, finalX - this.scroller.getCurrX(), 0, 0); - - requestAnimationFrame(() => { - // this is here to work around a bug in FlatList, as discussed here - // https://github.com/facebook/react-native/issues/1831 - // (and solved here https://github.com/facebook/react-native/commit/03ae65bc ?) - this.scrollByOffset(1); - this.scrollByOffset(-1); - }); - } - - componentDidUpdate (prevProps) { - if (this.layoutChanged) { - this.layoutChanged = false; - if (typeof this.currentPage === 'number') { - this.scrollToPage(this.currentPage, true); - } - } else if (this.currentPage + 1 >= this.props.pageDataArray.length && - this.props.pageDataArray.length !== prevProps.pageDataArray.length) { - this.scrollToPage(this.props.pageDataArray.length, true); - } - } - - onLayout (e) { - let { width, height } = e.nativeEvent.layout; - let sizeChanged = this.state.width !== width || this.state.height !== height; - if (width && height && sizeChanged) { - this.layoutChanged = true; - this.setState({ width, height }); - } - } - - onResponderGrant (evt, gestureState) { - // this.scroller.forceFinished(true); - this.activeGesture = true; - this.onPageScrollStateChanged('dragging'); - } - - onResponderMove (evt, gestureState) { - let dx = gestureState.moveX - gestureState.previousMoveX; - this.scrollByOffset(dx); - } - - onResponderRelease (evt, gestureState, disableSettle) { - this.activeGesture = false; - if (!disableSettle) { - this.settlePage(gestureState.vx); + static propTypes = { + ...View.propTypes, + initialPage: PropTypes.number, + pageMargin: PropTypes.number, + scrollViewStyle: ViewPropTypes ? ViewPropTypes.style : ViewStyle, + scrollEnabled: PropTypes.bool, + renderPage: PropTypes.func, + pageDataArray: PropTypes.array, + initialListSize: PropTypes.number, + removeClippedSubviews: PropTypes.bool, + onPageSelected: PropTypes.func, + onPageScrollStateChanged: PropTypes.func, + onPageScroll: PropTypes.func, + flatListProps: PropTypes.object, + }; + + static defaultProps = { + initialPage: 0, + pageMargin: 0, + scrollEnabled: true, + pageDataArray: [], + initialListSize: 10, + removeClippedSubviews: true, + flatListProps: {}, + }; + + currentPage = undefined; // Do not initialize to make onPageSelected(0) be dispatched + layoutChanged = false; + activeGesture = false; + gestureResponder = undefined; + + state = {width, height}; + + constructor(props) { + super(props); + + this.onLayout = this.onLayout.bind(this); + this.renderRow = this.renderRow.bind(this); + this.onResponderGrant = this.onResponderGrant.bind(this); + this.onResponderMove = this.onResponderMove.bind(this); + this.onResponderRelease = this.onResponderRelease.bind(this); + this.getItemLayout = this.getItemLayout.bind(this); + + this.scroller = this.createScroller(); + } + + createScroller() { + return new Scroller(true, (dx, dy, scroller) => { + if (dx === 0 && dy === 0 && scroller.isFinished()) { + if (!this.activeGesture) { + this.onPageScrollStateChanged('idle'); } - } - - onPageChanged (page) { - if (this.currentPage !== page) { - this.currentPage = page; - this.props.onPageSelected && this.props.onPageSelected(page); + } else { + const curX = this.scroller.getCurrX(); + this.refs['innerFlatList'] && + this.refs['innerFlatList'].scrollToOffset({ + offset: curX, + animated: false, + }); + + let position = Math.floor( + curX / (this.state.width + this.props.pageMargin), + ); + position = this.validPage(position); + let offset = + (curX - this.getScrollOffsetOfPage(position)) / + (this.state.width + this.props.pageMargin); + let fraction = + (curX - + this.getScrollOffsetOfPage(position) - + this.props.pageMargin) / + this.state.width; + if (fraction < 0) { + fraction = 0; } + this.props.onPageScroll && + this.props.onPageScroll({ + position, + offset, + fraction, + }); + } + }); + } + + UNSAFE_componentWillMount() { + this.gestureResponder = createResponder({ + onStartShouldSetResponder: (evt, gestureState) => true, + onResponderGrant: this.onResponderGrant, + onResponderMove: this.onResponderMove, + onResponderRelease: this.onResponderRelease, + onResponderTerminate: this.onResponderRelease, + }); + } + + componentDidMount() { + // FlatList is set to render at initialPage. + // The scroller we use is not aware of this. + // Let it know by simulating most of what happens in scrollToPage() + this.onPageScrollStateChanged('settling'); + + const page = this.validPage(this.props.initialPage); + this.onPageChanged(page); + + const finalX = this.getScrollOffsetOfPage(page); + this.scroller.startScroll( + this.scroller.getCurrX(), + 0, + finalX - this.scroller.getCurrX(), + 0, + 0, + ); + + requestAnimationFrame(() => { + // this is here to work around a bug in FlatList, as discussed here + // https://github.com/facebook/react-native/issues/1831 + // (and solved here https://github.com/facebook/react-native/commit/03ae65bc ?) + this.scrollByOffset(1); + this.scrollByOffset(-1); + }); + } + + componentDidUpdate(prevProps) { + if (this.layoutChanged) { + this.layoutChanged = false; + if (typeof this.currentPage === 'number') { + this.scrollToPage(this.currentPage, true); + } + } else if ( + this.currentPage + 1 >= this.props.pageDataArray.length && + this.props.pageDataArray.length !== prevProps.pageDataArray.length + ) { + this.scrollToPage(this.props.pageDataArray.length, true); } - - onPageScrollStateChanged (state) { - this.props.onPageScrollStateChanged && this.props.onPageScrollStateChanged(state); + } + + onLayout(e) { + let {width, height} = e.nativeEvent.layout; + let sizeChanged = + this.state.width !== width || this.state.height !== height; + if (width && height && sizeChanged) { + this.layoutChanged = true; + this.setState({width, height}); } - - settlePage (vx) { - const { pageDataArray } = this.props; - - if (vx < -MIN_FLING_VELOCITY) { - if (this.currentPage < pageDataArray.length - 1) { - this.flingToPage(this.currentPage + 1, vx); - } else { - this.flingToPage(pageDataArray.length - 1, vx); - } - } else if (vx > MIN_FLING_VELOCITY) { - if (this.currentPage > 0) { - this.flingToPage(this.currentPage - 1, vx); - } else { - this.flingToPage(0, vx); - } - } else { - let page = this.currentPage; - let progress = (this.scroller.getCurrX() - this.getScrollOffsetOfPage(this.currentPage)) / this.state.width; - if (progress > 1 / 3) { - page += 1; - } else if (progress < -1 / 3) { - page -= 1; - } - page = Math.min(pageDataArray.length - 1, page); - page = Math.max(0, page); - this.scrollToPage(page); - } + } + + onResponderGrant(evt, gestureState) { + // this.scroller.forceFinished(true); + this.activeGesture = true; + this.onPageScrollStateChanged('dragging'); + } + + onResponderMove(evt, gestureState) { + let dx = gestureState.moveX - gestureState.previousMoveX; + this.scrollByOffset(dx); + } + + onResponderRelease(evt, gestureState, disableSettle) { + this.activeGesture = false; + if (!disableSettle) { + this.settlePage(gestureState.vx); } + } - getScrollOffsetOfPage (page) { - return this.getItemLayout(this.props.pageDataArray, page).offset; + onPageChanged(page) { + if (this.currentPage !== page) { + this.currentPage = page; + this.props.onPageSelected && this.props.onPageSelected(page); } - - flingToPage (page, velocityX) { - this.onPageScrollStateChanged('settling'); - - page = this.validPage(page); - this.onPageChanged(page); - - velocityX *= -1000; // per sec - const finalX = this.getScrollOffsetOfPage(page); - this.scroller.fling(this.scroller.getCurrX(), 0, velocityX, 0, finalX, finalX, 0, 0); + } + + onPageScrollStateChanged(state) { + this.props.onPageScrollStateChanged && + this.props.onPageScrollStateChanged(state); + } + + settlePage(vx) { + const {pageDataArray} = this.props; + + if (vx < -MIN_FLING_VELOCITY) { + if (this.currentPage < pageDataArray.length - 1) { + this.flingToPage(this.currentPage + 1, vx); + } else { + this.flingToPage(pageDataArray.length - 1, vx); + } + } else if (vx > MIN_FLING_VELOCITY) { + if (this.currentPage > 0) { + this.flingToPage(this.currentPage - 1, vx); + } else { + this.flingToPage(0, vx); + } + } else { + let page = this.currentPage; + let progress = + (this.scroller.getCurrX() - + this.getScrollOffsetOfPage(this.currentPage)) / + this.state.width; + if (progress > 1 / 3) { + page += 1; + } else if (progress < -1 / 3) { + page -= 1; + } + page = Math.min(pageDataArray.length - 1, page); + page = Math.max(0, page); + this.scrollToPage(page); } - - scrollToPage (page, immediate) { - this.onPageScrollStateChanged('settling'); - - page = this.validPage(page); - this.onPageChanged(page); - - const finalX = this.getScrollOffsetOfPage(page); - if (immediate) { - InteractionManager.runAfterInteractions(() => { - this.scroller.startScroll(this.scroller.getCurrX(), 0, finalX - this.scroller.getCurrX(), 0, 0); - this.refs['innerFlatList'] && this.refs['innerFlatList'].scrollToOffset({offset: finalX, animated: false}); - this.refs['innerFlatList'] && this.refs['innerFlatList'].recordInteraction(); - }); - } else { - this.scroller.startScroll(this.scroller.getCurrX(), 0, finalX - this.scroller.getCurrX(), 0, 400); - } + } + + getScrollOffsetOfPage(page) { + return this.getItemLayout(this.props.pageDataArray, page).offset; + } + + flingToPage(page, velocityX) { + this.onPageScrollStateChanged('settling'); + + page = this.validPage(page); + this.onPageChanged(page); + + velocityX *= -1000; // per sec + const finalX = this.getScrollOffsetOfPage(page); + this.scroller.fling( + this.scroller.getCurrX(), + 0, + velocityX, + 0, + finalX, + finalX, + 0, + 0, + ); + } + + scrollToPage(page, immediate) { + this.onPageScrollStateChanged('settling'); + + page = this.validPage(page); + this.onPageChanged(page); + + const finalX = this.getScrollOffsetOfPage(page); + if (immediate) { + InteractionManager.runAfterInteractions(() => { + this.scroller.startScroll( + this.scroller.getCurrX(), + 0, + finalX - this.scroller.getCurrX(), + 0, + 0, + ); + this.refs['innerFlatList'] && + this.refs['innerFlatList'].scrollToOffset({ + offset: finalX, + animated: false, + }); + this.refs['innerFlatList'] && + this.refs['innerFlatList'].recordInteraction(); + }); + } else { + this.scroller.startScroll( + this.scroller.getCurrX(), + 0, + finalX - this.scroller.getCurrX(), + 0, + 400, + ); } + } + + scrollByOffset(dx) { + this.scroller.startScroll(this.scroller.getCurrX(), 0, -dx, 0, 0); + } + + validPage(page) { + page = Math.min(this.props.pageDataArray.length - 1, page); + page = Math.max(0, page); + return page; + } + + getScrollOffsetFromCurrentPage() { + return ( + this.scroller.getCurrX() - this.getScrollOffsetOfPage(this.currentPage) + ); + } + + getItemLayout(data, index) { + // this method is called 'getItemLayout', but it is not actually used + // as the 'getItemLayout' function for the FlatList. We use it within + // the code on this page though. The reason for this is that working + // with 'getItemLayout' for FlatList is buggy. You might end up with + // unrendered / missing content. Therefore we work around it, as + // described here + // https://github.com/facebook/react-native/issues/15734#issuecomment-330616697 + return { + length: this.state.width + this.props.pageMargin, + offset: (this.state.width + this.props.pageMargin) * index, + index, + }; + } - scrollByOffset (dx) { - this.scroller.startScroll(this.scroller.getCurrX(), 0, -dx, 0, 0); - } + keyExtractor(item, index) { + return index.toString(); + } - validPage (page) { - page = Math.min(this.props.pageDataArray.length - 1, page); - page = Math.max(0, page); - return page; - } + renderRow({item, index}) { + const {width, height} = this.state; + let page = this.props.renderPage(item, index); - getScrollOffsetFromCurrentPage () { - return this.scroller.getCurrX() - this.getScrollOffsetOfPage(this.currentPage); + const layout = { + width, + height, + position: 'relative', + }; + const style = page.props.style ? [page.props.style, layout] : layout; + + let newProps = {...page.props, ref: page.ref, style}; + const element = React.createElement(page.type, newProps); + + if (this.props.pageMargin > 0 && index > 0) { + // Do not using margin style to implement pageMargin. + // The ListView seems to calculate a wrong width for children views with margin. + return ( + + {element} + + ); + } else { + return element; } + } - getItemLayout (data, index) { - // this method is called 'getItemLayout', but it is not actually used - // as the 'getItemLayout' function for the FlatList. We use it within - // the code on this page though. The reason for this is that working - // with 'getItemLayout' for FlatList is buggy. You might end up with - // unrendered / missing content. Therefore we work around it, as - // described here - // https://github.com/facebook/react-native/issues/15734#issuecomment-330616697 - return { - length: this.state.width + this.props.pageMargin, - offset: (this.state.width + this.props.pageMargin) * index, - index - }; - } + render() { + const {width, height} = this.state; + const {pageDataArray, scrollEnabled, style, scrollViewStyle} = this.props; - keyExtractor (item, index) { - return index; + if (width && height) { + let list = pageDataArray; + if (!list) { + list = []; + } } - renderRow ({ item, index }) { - const { width, height } = this.state; - let page = this.props.renderPage(item, index); - - const layout = { - width, - height, - position: 'relative' - }; - const style = page.props.style ? [page.props.style, layout] : layout; - - let newProps = { ...page.props, ref: page.ref, style }; - const element = React.createElement(page.type, newProps); - - if (this.props.pageMargin > 0 && index > 0) { - // Do not using margin style to implement pageMargin. - // The ListView seems to calculate a wrong width for children views with margin. - return ( - - { element } - - ); - } else { - return element; - } + let gestureResponder = this.gestureResponder; + if (!scrollEnabled || pageDataArray.length <= 0) { + gestureResponder = {}; } - render () { - const { width, height } = this.state; - const { pageDataArray, scrollEnabled, style, scrollViewStyle } = this.props; - - if (width && height) { - let list = pageDataArray; - if (!list) { - list = []; - } - } - - let gestureResponder = this.gestureResponder; - if (!scrollEnabled || pageDataArray.length <= 0) { - gestureResponder = {}; - } - - return ( - - - - ); - } + return ( + + + + ); + } } diff --git a/src/libraries/ViewTransformer/Rect.js b/src/libraries/ViewTransformer/Rect.js index 41b6d46..b9b053d 100644 --- a/src/libraries/ViewTransformer/Rect.js +++ b/src/libraries/ViewTransformer/Rect.js @@ -1,71 +1,73 @@ export default class Rect { - constructor (left, top, right, bottom) { - this.left = left; - this.top = top; - this.right = right; - this.bottom = bottom; - } + constructor(left, top, right, bottom) { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } - set (left, top, right, bottom) { - this.left = left; - this.top = top; - this.right = right; - this.bottom = bottom; - } + set(left, top, right, bottom) { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } - width () { - return this.right - this.left; - } + width() { + return this.right - this.left; + } - height () { - return this.bottom - this.top; - } + height() { + return this.bottom - this.top; + } - centerX () { - return (this.left + this.right) / 2; - } + centerX() { + return (this.left + this.right) / 2; + } - centerY () { - return (this.top + this.bottom) / 2; - } + centerY() { + return (this.top + this.bottom) / 2; + } - offset (dx, dy) { - this.left += dx; - this.right += dx; - this.top += dy; - this.bottom += dy; - return this; - } + offset(dx, dy) { + this.left += dx; + this.right += dx; + this.top += dy; + this.bottom += dy; + return this; + } - copy () { - return new Rect(this.left, this.top, this.right, this.bottom); - } + copy() { + return new Rect(this.left, this.top, this.right, this.bottom); + } - equals (rect, epsilon) { - if (!epsilon) { - return ( - this.left === rect.left && - this.top === rect.top && - this.right === rect.right && - this.bottom === rect.bottom - ); - } else { - return ( - Math.abs(this.left - rect.left) < epsilon && - Math.abs(this.top - rect.top) < epsilon && - Math.abs(this.right - rect.right) < epsilon && - Math.abs(this.bottom - rect.bottom) < epsilon - ); - } + equals(rect, epsilon) { + if (!epsilon) { + return ( + this.left === rect.left && + this.top === rect.top && + this.right === rect.right && + this.bottom === rect.bottom + ); + } else { + return ( + Math.abs(this.left - rect.left) < epsilon && + Math.abs(this.top - rect.top) < epsilon && + Math.abs(this.right - rect.right) < epsilon && + Math.abs(this.bottom - rect.bottom) < epsilon + ); } + } - isValid () { - if (typeof this.left === 'number' && - typeof this.right === 'number' && - typeof this.top === 'number' && - typeof this.bottom === 'number') { - return true; - } - return false; + isValid() { + if ( + typeof this.left === 'number' && + typeof this.right === 'number' && + typeof this.top === 'number' && + typeof this.bottom === 'number' + ) { + return true; } + return false; + } } diff --git a/src/libraries/ViewTransformer/TransformUtils.js b/src/libraries/ViewTransformer/TransformUtils.js index bbdbd2f..8f399b8 100644 --- a/src/libraries/ViewTransformer/TransformUtils.js +++ b/src/libraries/ViewTransformer/TransformUtils.js @@ -1,55 +1,60 @@ import Rect from './Rect'; -export { Rect }; +export {Rect}; export class Transform { - constructor (scale, translateX, translateY, pivot) { - this.scale = scale; - this.translateX = translateX; - this.translateY = translateY; - this.pivot = pivot; - } + constructor(scale, translateX, translateY, pivot) { + this.scale = scale; + this.translateX = translateX; + this.translateY = translateY; + this.pivot = pivot; + } } -function isValidNumber (number) { - if (typeof number === 'number') { - if (!isNaN(number)) { - return true; - } +function isValidNumber(number) { + if (typeof number === 'number') { + if (!isNaN(number)) { + return true; } - return false; + } + return false; } -function isValidRect (rect) { - if (rect instanceof Rect && rect.isValid()) { - return true; - } - return false; +function isValidRect(rect) { + if (rect instanceof Rect && rect.isValid()) { + return true; + } + return false; } -function isValidTransform (transform) { - if (transform && isValidNumber(transform.scale) && isValidNumber(transform.translateX) && isValidNumber(transform.translateY)) { - return true; - } - return false; +function isValidTransform(transform) { + if ( + transform && + isValidNumber(transform.scale) && + isValidNumber(transform.translateX) && + isValidNumber(transform.translateY) + ) { + return true; + } + return false; } -export function fitCenterRect (contentAspectRatio, containerRect) { - let w = containerRect.width(); - let h = containerRect.height(); - let viewAspectRatio = w / h; +export function fitCenterRect(contentAspectRatio, containerRect) { + let w = containerRect.width(); + let h = containerRect.height(); + let viewAspectRatio = w / h; - if (contentAspectRatio > viewAspectRatio) { - h = w / contentAspectRatio; - } else { - w = h * contentAspectRatio; - } + if (contentAspectRatio > viewAspectRatio) { + h = w / contentAspectRatio; + } else { + w = h * contentAspectRatio; + } - return new Rect( + return new Rect( containerRect.centerX() - w / 2, containerRect.centerY() - h / 2, containerRect.centerX() + w / 2, - containerRect.centerY() + h / 2 + containerRect.centerY() + h / 2, ); } @@ -60,48 +65,52 @@ export function fitCenterRect (contentAspectRatio, containerRect) { * @param transform * @returns {*} */ -export function transformedRect (rect, transform) { - if (!isValidRect(rect)) { - throw new Error('transformedRect...invalid rect'); - } - if (!isValidTransform(transform)) { - throw new Error('transformedRect...invalid transform'); - } +export function transformedRect(rect, transform) { + if (!isValidRect(rect)) { + throw new Error('transformedRect...invalid rect'); + } + if (!isValidTransform(transform)) { + throw new Error('transformedRect...invalid transform'); + } + + let scale = transform.scale; + let translateX = transform.translateX; + let translateY = transform.translateY; + + let pivot = transform.pivot; + if (pivot === undefined || pivot === null) { + let width = rect.width() * scale; + let height = rect.height() * scale; + let centerX = rect.centerX() + translateX * scale; + let centerY = rect.centerY() + translateY * scale; - let scale = transform.scale; - let translateX = transform.translateX; - let translateY = transform.translateY; - - let pivot = transform.pivot; - if (pivot === undefined || pivot === null) { - let width = rect.width() * scale; - let height = rect.height() * scale; - let centerX = rect.centerX() + translateX * scale; - let centerY = rect.centerY() + translateY * scale; - - return new Rect( + return new Rect( centerX - width / 2, centerY - height / 2, centerX + width / 2, - centerY + height / 2 + centerY + height / 2, ); - } else { - let pivotX = pivot.x; - let pivotY = pivot.y; - if (!isValidNumber(pivotX) || !isValidNumber(pivotY)) { - throw new Error('transformedRect...invalid pivot x=' + pivot.x + ', y=' + pivot.y); - } + } else { + let pivotX = pivot.x; + let pivotY = pivot.y; + if (!isValidNumber(pivotX) || !isValidNumber(pivotY)) { + throw new Error( + 'transformedRect...invalid pivot x=' + pivot.x + ', y=' + pivot.y, + ); + } // first make the center still - let resultRect = transformedRect(rect, { - scale, translateX, translateY - }); + let resultRect = transformedRect(rect, { + scale, + translateX, + translateY, + }); // the pivot moved during scaling, now move it back - let dx = (scale - 1) * (pivotX - resultRect.centerX()); - let dy = (scale - 1) * (pivotY - resultRect.centerY()); - return resultRect.offset(-dx, -dy); - } + let dx = (scale - 1) * (pivotX - resultRect.centerX()); + let dy = (scale - 1) * (pivotY - resultRect.centerY()); + return resultRect.offset(-dx, -dy); + } } /** @@ -110,12 +119,12 @@ export function transformedRect (rect, transform) { * @param toRect * @returns {Transform} */ -export function getTransform (fromRect, toRect) { - let scale = toRect.width() / fromRect.width(); - let translateX = (toRect.centerX() - fromRect.centerX()) / scale; - let translateY = (toRect.centerY() - fromRect.centerY()) / scale; +export function getTransform(fromRect, toRect) { + let scale = toRect.width() / fromRect.width(); + let translateX = (toRect.centerX() - fromRect.centerX()) / scale; + let translateY = (toRect.centerY() - fromRect.centerY()) / scale; - return new Transform(scale, translateX, translateY); + return new Transform(scale, translateX, translateY); } /** @@ -124,38 +133,38 @@ export function getTransform (fromRect, toRect) { * @param viewPortRect * @returns {*|{line, column}|{column, line}|{x}} */ -export function alignedRect (rect, viewPortRect) { - let dx = 0; - let dy = 0; - - if (rect.width() > viewPortRect.width()) { - if (rect.left > viewPortRect.left) { - dx = viewPortRect.left - rect.left; - } else if (rect.right < viewPortRect.right) { - dx = viewPortRect.right - rect.right; - } - } else { - dx = viewPortRect.centerX() - rect.centerX(); +export function alignedRect(rect, viewPortRect) { + let dx = 0; + let dy = 0; + + if (rect.width() > viewPortRect.width()) { + if (rect.left > viewPortRect.left) { + dx = viewPortRect.left - rect.left; + } else if (rect.right < viewPortRect.right) { + dx = viewPortRect.right - rect.right; } - - if (rect.height() > viewPortRect.height()) { - if (rect.top > viewPortRect.top) { - dy = viewPortRect.top - rect.top; - } else if (rect.bottom < viewPortRect.bottom) { - dy = viewPortRect.bottom - rect.bottom; - } - } else { - dy = viewPortRect.centerY() - rect.centerY(); + } else { + dx = viewPortRect.centerX() - rect.centerX(); + } + + if (rect.height() > viewPortRect.height()) { + if (rect.top > viewPortRect.top) { + dy = viewPortRect.top - rect.top; + } else if (rect.bottom < viewPortRect.bottom) { + dy = viewPortRect.bottom - rect.bottom; } + } else { + dy = viewPortRect.centerY() - rect.centerY(); + } - return rect.copy().offset(dx, dy); + return rect.copy().offset(dx, dy); } -export function availableTranslateSpace (rect, viewPortRect) { - return { - left: viewPortRect.left - rect.left, - right: rect.right - viewPortRect.right, - top: viewPortRect.top - rect.top, - bottom: rect.bottom - viewPortRect.bottom - }; +export function availableTranslateSpace(rect, viewPortRect) { + return { + left: viewPortRect.left - rect.left, + right: rect.right - viewPortRect.right, + top: viewPortRect.top - rect.top, + bottom: rect.bottom - viewPortRect.bottom, + }; } diff --git a/src/libraries/ViewTransformer/index.js b/src/libraries/ViewTransformer/index.js index f919e99..68d42c5 100644 --- a/src/libraries/ViewTransformer/index.js +++ b/src/libraries/ViewTransformer/index.js @@ -1,421 +1,452 @@ import React from 'react'; -import ReactNative, { View, Animated, Easing, NativeModules } from 'react-native'; +import ReactNative, {View, Animated, Easing, NativeModules} from 'react-native'; import Scroller from '../Scroller'; import PropTypes from 'prop-types'; -import { createResponder } from '../GestureResponder'; -import { Rect, Transform, transformedRect, availableTranslateSpace, fitCenterRect, alignedRect, getTransform } from './TransformUtils'; +import {createResponder} from '../GestureResponder'; +import { + Rect, + Transform, + transformedRect, + availableTranslateSpace, + fitCenterRect, + alignedRect, + getTransform, +} from './TransformUtils'; export default class ViewTransformer extends React.Component { - static Rect = Rect; - static getTransform = getTransform; - - static propTypes = { - enableTransform: PropTypes.bool, - enableScale: PropTypes.bool, - enableTranslate: PropTypes.bool, - maxOverScrollDistance: PropTypes.number, - maxScale: PropTypes.number, - contentAspectRatio: PropTypes.number, - enableResistance: PropTypes.bool, - onViewTransformed: PropTypes.func, - onTransformGestureReleased: PropTypes.func, - onSingleTapConfirmed: PropTypes.func, - onLayout: PropTypes.func, - onTransformStart: PropTypes.func, - children: PropTypes.node + static Rect = Rect; + static getTransform = getTransform; + + static propTypes = { + enableTransform: PropTypes.bool, + enableScale: PropTypes.bool, + enableTranslate: PropTypes.bool, + maxOverScrollDistance: PropTypes.number, + maxScale: PropTypes.number, + contentAspectRatio: PropTypes.number, + enableResistance: PropTypes.bool, + onViewTransformed: PropTypes.func, + onTransformGestureReleased: PropTypes.func, + onSingleTapConfirmed: PropTypes.func, + onLayout: PropTypes.func, + onTransformStart: PropTypes.func, + children: PropTypes.node, + }; + + static defaultProps = { + maxOverScrollDistance: 20, + enableScale: true, + enableTranslate: true, + enableTransform: true, + enableResistance: false, + }; + + constructor(props) { + super(props); + this.state = { + // transform state + scale: 1, + translateX: 0, + translateY: 0, + // animation state + animator: new Animated.Value(0), + // layout + width: 0, + height: 0, + pageX: 0, + pageY: 0, }; - - static defaultProps = { - maxOverScrollDistance: 20, - enableScale: true, - enableTranslate: true, - enableTransform: true, - maxScale: 1, - enableResistance: false - }; - - constructor (props) { - super(props); - this.state = { - // transform state - scale: 1, - translateX: 0, - translateY: 0, - // animation state - animator: new Animated.Value(0), - // layout - width: 0, - height: 0, - pageX: 0, - pageY: 0 - }; - this._viewPortRect = new Rect(); // A holder to avoid new too much - - this.onLayout = this.onLayout.bind(this); - this.cancelAnimation = this.cancelAnimation.bind(this); - this.contentRect = this.contentRect.bind(this); - this.transformedContentRect = this.transformedContentRect.bind(this); - this.animate = this.animate.bind(this); - - this.scroller = new Scroller(true, (dx, dy, scroller) => { - if (dx === 0 && dy === 0 && scroller.isFinished()) { - this.animateBounce(); - return; - } - - this.updateTransform({ - translateX: this.state.translateX + dx / this.state.scale, - translateY: this.state.translateY + dy / this.state.scale - }); - }); - } - - viewPortRect () { - this._viewPortRect.set(0, 0, this.state.width, this.state.height); - return this._viewPortRect; - } - - contentRect () { - let rect = this.viewPortRect().copy(); - if (this.props.contentAspectRatio && this.props.contentAspectRatio > 0) { - rect = fitCenterRect(this.props.contentAspectRatio, rect); - } - return rect; - } - - transformedContentRect () { - let rect = transformedRect(this.viewPortRect(), this.currentTransform()); - if (this.props.contentAspectRatio && this.props.contentAspectRatio > 0) { - rect = fitCenterRect(this.props.contentAspectRatio, rect); - } - return rect; - } - - currentTransform () { - return new Transform(this.state.scale, this.state.translateX, this.state.translateY); - } - - componentWillMount () { - this.gestureResponder = createResponder({ - onStartShouldSetResponder: (evt, gestureState) => true, - onMoveShouldSetResponderCapture: (evt, gestureState) => true, - // onMoveShouldSetResponder: this.handleMove, - onResponderMove: this.onResponderMove, - onResponderGrant: this.onResponderGrant, - onResponderRelease: this.onResponderRelease, - onResponderTerminate: this.onResponderRelease, - onResponderTerminationRequest: (evt, gestureState) => false, // Do not allow parent view to intercept gesture - onResponderSingleTapConfirmed: (evt, gestureState) => { - this.props.onSingleTapConfirmed && this.props.onSingleTapConfirmed(); - } - }); + this._viewPortRect = new Rect(); // A holder to avoid new too much + + this.onLayout = this.onLayout.bind(this); + this.cancelAnimation = this.cancelAnimation.bind(this); + this.contentRect = this.contentRect.bind(this); + this.transformedContentRect = this.transformedContentRect.bind(this); + this.animate = this.animate.bind(this); + + this.scroller = new Scroller(true, (dx, dy, scroller) => { + if (dx === 0 && dy === 0 && scroller.isFinished()) { + this.animateBounce(); + return; + } + + this.updateTransform({ + translateX: this.state.translateX + dx / this.state.scale, + translateY: this.state.translateY + dy / this.state.scale, + }); + }); + } + + viewPortRect() { + this._viewPortRect.set(0, 0, this.state.width, this.state.height); + return this._viewPortRect; + } + + contentRect() { + let rect = this.viewPortRect().copy(); + if (this.props.contentAspectRatio && this.props.contentAspectRatio > 0) { + rect = fitCenterRect(this.props.contentAspectRatio, rect); } + return rect; + } - componentDidUpdate (prevProps, prevState) { - this.props.onViewTransformed && this.props.onViewTransformed({ - scale: this.state.scale, - translateX: this.state.translateX, - translateY: this.state.translateY - }); + transformedContentRect() { + let rect = transformedRect(this.viewPortRect(), this.currentTransform()); + if (this.props.contentAspectRatio && this.props.contentAspectRatio > 0) { + rect = fitCenterRect(this.props.contentAspectRatio, rect); } - - componentWillUnmount () { - this.cancelAnimation(); + return rect; + } + + currentTransform() { + return new Transform( + this.state.scale, + this.state.translateX, + this.state.translateY, + ); + } + + UNSAFE_componentWillMount() { + this.gestureResponder = createResponder({ + onStartShouldSetResponder: (evt, gestureState) => true, + onMoveShouldSetResponderCapture: (evt, gestureState) => true, + // onMoveShouldSetResponder: this.handleMove, + onResponderMove: this.onResponderMove.bind(this), + onResponderGrant: this.onResponderGrant.bind(this), + onResponderRelease: this.onResponderRelease.bind(this), + onResponderTerminate: this.onResponderRelease.bind(this), + onResponderTerminationRequest: (evt, gestureState) => false, // Do not allow parent view to intercept gesture + onResponderSingleTapConfirmed: (evt, gestureState) => { + this.props.onSingleTapConfirmed && this.props.onSingleTapConfirmed(); + }, + }); + } + + componentDidUpdate(prevProps, prevState) { + this.props.onViewTransformed && + this.props.onViewTransformed({ + scale: this.state.scale, + translateX: this.state.translateX, + translateY: this.state.translateY, + }); + } + + componentWillUnmount() { + this.cancelAnimation(); + } + + render() { + let gestureResponder = this.gestureResponder; + if (!this.props.enableTransform) { + gestureResponder = {}; } - render () { - let gestureResponder = this.gestureResponder; - if (!this.props.enableTransform) { - gestureResponder = {}; - } - - return ( - - - { this.props.children } - - - ); + return ( + + + {this.props.children} + + + ); + } + + onLayout(e) { + const {width, height} = e.nativeEvent.layout; + if (width !== this.state.width || height !== this.state.height) { + this.setState({width, height}); } - - onLayout (e) { - const {width, height} = e.nativeEvent.layout; - if (width !== this.state.width || height !== this.state.height) { - this.setState({width, height}); + this.measureLayout(); + + this.props.onLayout && this.props.onLayout(e); + } + + measureLayout() { + let handle = ReactNative.findNodeHandle(this.refs['innerViewRef']); + NativeModules.UIManager.measure( + handle, + (x, y, width, height, pageX, pageY) => { + if (typeof pageX === 'number' && typeof pageY === 'number') { + // avoid undefined values on Android devices + if (this.state.pageX !== pageX || this.state.pageY !== pageY) { + this.setState({pageX: pageX, pageY: pageY}); + } } - this.measureLayout(); - - this.props.onLayout && this.props.onLayout(e); + }, + ); + } + + onResponderGrant(evt, gestureState) { + this.props.onTransformStart && this.props.onTransformStart(); + this.setState({responderGranted: true}); + this.measureLayout(); + } + + onResponderMove(evt, gestureState) { + this.cancelAnimation(); + + let dx = gestureState.moveX - gestureState.previousMoveX; + let dy = gestureState.moveY - gestureState.previousMoveY; + if (this.props.enableResistance) { + let d = this.applyResistance(dx, dy); + dx = d.dx; + dy = d.dy; } - measureLayout () { - let handle = ReactNative.findNodeHandle(this.refs['innerViewRef']); - NativeModules.UIManager.measure(handle, (x, y, width, height, pageX, pageY) => { - if (typeof pageX === 'number' && typeof pageY === 'number') { // avoid undefined values on Android devices - if (this.state.pageX !== pageX || this.state.pageY !== pageY) { - this.setState({ pageX: pageX, pageY: pageY }); - } - } - }); + if (!this.props.enableTranslate) { + dx = dy = 0; } - onResponderGrant (evt, gestureState) { - this.props.onTransformStart && this.props.onTransformStart(); - this.setState({responderGranted: true}); - this.measureLayout(); + let transform = {}; + if ( + gestureState.previousPinch && + gestureState.pinch && + this.props.enableScale + ) { + let scaleBy = gestureState.pinch / gestureState.previousPinch; + let pivotX = gestureState.moveX - this.state.pageX; + let pivotY = gestureState.moveY - this.state.pageY; + + let rect = transformedRect( + transformedRect(this.contentRect(), this.currentTransform()), + new Transform(scaleBy, dx, dy, {x: pivotX, y: pivotY}), + ); + transform = getTransform(this.contentRect(), rect); + } else { + if (Math.abs(dx) > 2 * Math.abs(dy)) { + dy = 0; + } else if (Math.abs(dy) > 2 * Math.abs(dx)) { + dx = 0; + } + transform.translateX = this.state.translateX + dx / this.state.scale; + transform.translateY = this.state.translateY + dy / this.state.scale; } - onResponderMove (evt, gestureState) { - this.cancelAnimation(); - - let dx = gestureState.moveX - gestureState.previousMoveX; - let dy = gestureState.moveY - gestureState.previousMoveY; - if (this.props.enableResistance) { - let d = this.applyResistance(dx, dy); - dx = d.dx; - dy = d.dy; - } - - if (!this.props.enableTranslate) { - dx = dy = 0; - } - - let transform = {}; - if (gestureState.previousPinch && gestureState.pinch && this.props.enableScale) { - let scaleBy = gestureState.pinch / gestureState.previousPinch; - let pivotX = gestureState.moveX - this.state.pageX; - let pivotY = gestureState.moveY - this.state.pageY; - - let rect = transformedRect( - transformedRect( - this.contentRect(), - this.currentTransform() - ), - new Transform(scaleBy, dx, dy, { x: pivotX, y: pivotY }) - ); - transform = getTransform(this.contentRect(), rect); - } else { - if (Math.abs(dx) > 2 * Math.abs(dy)) { - dy = 0; - } else if (Math.abs(dy) > 2 * Math.abs(dx)) { - dx = 0; - } - transform.translateX = this.state.translateX + dx / this.state.scale; - transform.translateY = this.state.translateY + dy / this.state.scale; - } - - this.updateTransform(transform); - return true; + this.updateTransform(transform); + return true; + } + + onResponderRelease(evt, gestureState) { + let handled = + this.props.onTransformGestureReleased && + this.props.onTransformGestureReleased({ + scale: this.state.scale, + translateX: this.state.translateX, + translateY: this.state.translateY, + }); + // if (handled) { + // return; + // } + + if (gestureState.doubleTapUp) { + if (!this.props.enableScale) { + this.animateBounce(); + return; + } + let pivotX = 0; + let pivotY = 0; + if (gestureState.dx || gestureState.dy) { + pivotX = gestureState.moveX - this.state.pageX; + pivotY = gestureState.moveY - this.state.pageY; + } else { + pivotX = gestureState.x0 - this.state.pageX; + pivotY = gestureState.y0 - this.state.pageY; + } + + this.performDoubleTapUp(pivotX, pivotY); + } else { + if (this.props.enableTranslate) { + this.performFling(gestureState.vx, gestureState.vy); + } else { + this.animateBounce(); + } } - - onResponderRelease (evt, gestureState) { - let handled = this.props.onTransformGestureReleased && this.props.onTransformGestureReleased({ - scale: this.state.scale, - translateX: this.state.translateX, - translateY: this.state.translateY - }); - if (handled) { - return; - } - - if (gestureState.doubleTapUp) { - if (!this.props.enableScale) { - this.animateBounce(); - return; - } - let pivotX = 0; - let pivotY = 0; - if (gestureState.dx || gestureState.dy) { - pivotX = gestureState.moveX - this.state.pageX; - pivotY = gestureState.moveY - this.state.pageY; - } else { - pivotX = gestureState.x0 - this.state.pageX; - pivotY = gestureState.y0 - this.state.pageY; - } - - this.performDoubleTapUp(pivotX, pivotY); - } else { - if (this.props.enableTranslate) { - this.performFling(gestureState.vx, gestureState.vy); - } else { - this.animateBounce(); - } - } + } + + performFling(vx, vy) { + let startX = 0; + let startY = 0; + let maxX, minX, maxY, minY; + let availablePanDistance = availableTranslateSpace( + this.transformedContentRect(), + this.viewPortRect(), + ); + if (vx > 0) { + minX = 0; + if (availablePanDistance.left > 0) { + maxX = availablePanDistance.left + this.props.maxOverScrollDistance; + } else { + maxX = 0; + } + } else { + maxX = 0; + if (availablePanDistance.right > 0) { + minX = -availablePanDistance.right - this.props.maxOverScrollDistance; + } else { + minX = 0; + } } - - performFling (vx, vy) { - let startX = 0; - let startY = 0; - let maxX, minX, maxY, minY; - let availablePanDistance = availableTranslateSpace(this.transformedContentRect(), this.viewPortRect()); - if (vx > 0) { - minX = 0; - if (availablePanDistance.left > 0) { - maxX = availablePanDistance.left + this.props.maxOverScrollDistance; - } else { - maxX = 0; - } - } else { - maxX = 0; - if (availablePanDistance.right > 0) { - minX = -availablePanDistance.right - this.props.maxOverScrollDistance; - } else { - minX = 0; - } - } - if (vy > 0) { - minY = 0; - if (availablePanDistance.top > 0) { - maxY = availablePanDistance.top + this.props.maxOverScrollDistance; - } else { - maxY = 0; - } - } else { - maxY = 0; - if (availablePanDistance.bottom > 0) { - minY = -availablePanDistance.bottom - this.props.maxOverScrollDistance; - } else { - minY = 0; - } - } - - vx *= 1000; // per second - vy *= 1000; - if (Math.abs(vx) > 2 * Math.abs(vy)) { - vy = 0; - } else if (Math.abs(vy) > 2 * Math.abs(vx)) { - vx = 0; - } - - this.scroller.fling(startX, startY, vx, vy, minX, maxX, minY, maxY); + if (vy > 0) { + minY = 0; + if (availablePanDistance.top > 0) { + maxY = availablePanDistance.top + this.props.maxOverScrollDistance; + } else { + maxY = 0; + } + } else { + maxY = 0; + if (availablePanDistance.bottom > 0) { + minY = -availablePanDistance.bottom - this.props.maxOverScrollDistance; + } else { + minY = 0; + } } - performDoubleTapUp (pivotX, pivotY) { - let curScale = this.state.scale; - let scaleBy; - if (curScale > (1 + this.props.maxScale) / 2) { - scaleBy = 1 / curScale; - } else { - scaleBy = this.props.maxScale / curScale; - } - - let rect = transformedRect( - this.transformedContentRect(), - new Transform(scaleBy, 0, 0, { x: pivotX, y: pivotY }) - ); - rect = transformedRect( - rect, - new Transform( - 1, - this.viewPortRect().centerX() - pivotX, - this.viewPortRect().centerY() - pivotY - ) - ); - rect = alignedRect(rect, this.viewPortRect()); - this.animate(rect); + vx *= 1000; // per second + vy *= 1000; + if (Math.abs(vx) > 2 * Math.abs(vy)) { + vy = 0; + } else if (Math.abs(vy) > 2 * Math.abs(vx)) { + vx = 0; } - applyResistance (dx, dy) { - let availablePanDistance = availableTranslateSpace(this.transformedContentRect(), this.viewPortRect()); + this.scroller.fling(startX, startY, vx, vy, minX, maxX, minY, maxY); + } - if ((dx > 0 && availablePanDistance.left < 0) || - (dx < 0 && availablePanDistance.right < 0)) { - dx /= 3; - } - if ((dy > 0 && availablePanDistance.top < 0) || - (dy < 0 && availablePanDistance.bottom < 0)) { - dy /= 3; - } - return { dx, dy }; + performDoubleTapUp(pivotX, pivotY) { + let curScale = this.state.scale; + let scaleBy; + if (curScale > (1 + this.props.maxScale) / 2) { + scaleBy = 1 / curScale; + } else { + scaleBy = this.props.maxScale / curScale; } - cancelAnimation () { - this.state.animator.stopAnimation(); + let rect = transformedRect( + this.transformedContentRect(), + new Transform(scaleBy, 0, 0, {x: pivotX, y: pivotY}), + ); + rect = transformedRect( + rect, + new Transform( + 1, + this.viewPortRect().centerX() - pivotX, + this.viewPortRect().centerY() - pivotY, + ), + ); + rect = alignedRect(rect, this.viewPortRect()); + this.animate(rect); + } + + applyResistance(dx, dy) { + let availablePanDistance = availableTranslateSpace( + this.transformedContentRect(), + this.viewPortRect(), + ); + + if ( + (dx > 0 && availablePanDistance.left < 0) || + (dx < 0 && availablePanDistance.right < 0) + ) { + dx /= 3; } - - animate (targetRect, durationInMillis) { - let duration = 200; - if (durationInMillis) { - duration = durationInMillis; - } - - let fromRect = this.transformedContentRect(); - if (fromRect.equals(targetRect, 0.01)) { - return; - } - - this.state.animator.removeAllListeners(); - this.state.animator.setValue(0); - this.state.animator.addListener((state) => { - let progress = state.value; - - let left = fromRect.left + (targetRect.left - fromRect.left) * progress; - let right = fromRect.right + (targetRect.right - fromRect.right) * progress; - let top = fromRect.top + (targetRect.top - fromRect.top) * progress; - let bottom = fromRect.bottom + (targetRect.bottom - fromRect.bottom) * progress; - - let transform = getTransform(this.contentRect(), new Rect(left, top, right, bottom)); - this.updateTransform(transform); - }); - - Animated.timing( - this.state.animator, - { - toValue: 1, - duration: duration, - easing: Easing.inOut(Easing.ease) - } - ).start(); + if ( + (dy > 0 && availablePanDistance.top < 0) || + (dy < 0 && availablePanDistance.bottom < 0) + ) { + dy /= 3; } + return {dx, dy}; + } - animateBounce () { - let curScale = this.state.scale; - let minScale = 1; - let maxScale = this.props.maxScale; - let scaleBy = 1; - if (curScale > maxScale) { - scaleBy = maxScale / curScale; - } else if (curScale < minScale) { - scaleBy = minScale / curScale; - } + cancelAnimation() { + this.state.animator.stopAnimation(); + } - let rect = transformedRect( - this.transformedContentRect(), - new Transform( - scaleBy, - 0, - 0, - { - x: this.viewPortRect().centerX(), - y: this.viewPortRect().centerY() - } - ) - ); - rect = alignedRect(rect, this.viewPortRect()); - this.animate(rect); + animate(targetRect, durationInMillis) { + let duration = 200; + if (durationInMillis) { + duration = durationInMillis; } - updateTransform (transform) { - this.setState(transform); + let fromRect = this.transformedContentRect(); + if (fromRect.equals(targetRect, 0.01)) { + return; } - forceUpdateTransform (transform) { - this.setState(transform); + this.state.animator.removeAllListeners(); + this.state.animator.setValue(0); + this.state.animator.addListener((state) => { + let progress = state.value; + + let left = fromRect.left + (targetRect.left - fromRect.left) * progress; + let right = + fromRect.right + (targetRect.right - fromRect.right) * progress; + let top = fromRect.top + (targetRect.top - fromRect.top) * progress; + let bottom = + fromRect.bottom + (targetRect.bottom - fromRect.bottom) * progress; + + let transform = getTransform( + this.contentRect(), + new Rect(left, top, right, bottom), + ); + this.updateTransform(transform); + }); + + Animated.timing(this.state.animator, { + toValue: 1, + duration: duration, + easing: Easing.inOut(Easing.ease), + useNativeDriver: true, + }).start(); + } + + animateBounce() { + let curScale = this.state.scale; + let minScale = 1; + let maxScale = this.props.maxScale; + let scaleBy = 1; + if (curScale > maxScale) { + scaleBy = maxScale / curScale; + } else if (curScale < minScale) { + scaleBy = minScale / curScale; } - getAvailableTranslateSpace () { - return availableTranslateSpace(this.transformedContentRect(), this.viewPortRect()); - } + let rect = transformedRect( + this.transformedContentRect(), + new Transform(scaleBy, 0, 0, { + x: this.viewPortRect().centerX(), + y: this.viewPortRect().centerY(), + }), + ); + rect = alignedRect(rect, this.viewPortRect()); + this.animate(rect); + } + + updateTransform(transform) { + this.setState(transform); + } + + forceUpdateTransform(transform) { + this.setState(transform); + } + + getAvailableTranslateSpace() { + return availableTranslateSpace( + this.transformedContentRect(), + this.viewPortRect(), + ); + } }