| |
| const MAXIMUM_TIME_FOR_RECORDING_GESTURES = 100; |
| const MAXIMUM_DECELERATION_TIME = 500; |
| |
| class PinchGestureRecognizer extends GestureRecognizer |
| { |
| |
| constructor(target, delegate) |
| { |
| super(target, delegate); |
| |
| this.scaleThreshold = 0; |
| this._scaledMinimumAmount = false; |
| } |
| |
| // Public |
| |
| get velocity() |
| { |
| const lastGesture = this._gestures[this._gestures.length - 1]; |
| if (!lastGesture) |
| return this._velocity; |
| |
| const elapsedTime = Date.now() - (lastGesture.timeStamp + MAXIMUM_TIME_FOR_RECORDING_GESTURES); |
| if (elapsedTime <= 0) |
| return this._velocity; |
| |
| const f = Math.max((MAXIMUM_DECELERATION_TIME - elapsedTime) / MAXIMUM_DECELERATION_TIME, 0); |
| return this._velocity * f; |
| } |
| |
| // Protected |
| |
| touchesBegan(event) |
| { |
| if (event.currentTarget !== this.target) |
| return; |
| |
| // Additional setup for when the the platform doesn't natively |
| // provide us with gesture events. |
| if (!GestureRecognizer.SupportsGestures) { |
| // A pinch gesture can only be performed with 2 fingers, anything more |
| // and we failed our gesture. |
| if (this.numberOfTouches > 2) { |
| this.enterFailedState(); |
| return; |
| } |
| |
| // We can only start tracking touches with 2 fingers. |
| if (this.numberOfTouches !== 2) |
| return; |
| |
| this._startDistance = this._distance(); |
| |
| // We manually add a start value so that we always have 2 entries in the |
| // _gestures array so that we don't have to check for the existence of 2 |
| // entries when computing velocity. |
| this._recordGesture(1); |
| |
| this._scaledMinimumAmount = false; |
| this._updateStateWithEvent(event); |
| } else if (this.numberOfTouches !== 2) { |
| // When we support gesture events, we only care about the case where we're |
| // using two fingers. |
| return; |
| } |
| |
| super.touchesBegan(event); |
| } |
| |
| touchesMoved(event) |
| { |
| // This method only needs to be overriden in the case where the platform |
| // doesn't natively provide us with gesture events. |
| if (GestureRecognizer.SupportsGestures) |
| return; |
| |
| if (this.numberOfTouches !== 2) |
| return; |
| |
| this._updateStateWithEvent(event); |
| } |
| |
| touchesEnded(event) |
| { |
| // This method only needs to be overriden in the case where the platform |
| // doesn't natively provide us with gesture events. |
| if (GestureRecognizer.SupportsGestures) |
| return; |
| |
| // If we don't have the required number of touches or have not event |
| // obtained 2 fingers, then there's nothing for us to do. |
| if (this.numberOfTouches >= 2 || !this._startDistance) |
| return; |
| |
| if (this._scaledMinimumAmount) |
| this.enterEndedState(); |
| else |
| this.enterFailedState(); |
| } |
| |
| gestureBegan(event) |
| { |
| super.gestureBegan(event); |
| |
| // We manually add a start value so that we always have 2 entries in the |
| // _gestures array so that we don't have to check for the existence of 2 |
| // entries when computing velocity. |
| this._recordGesture(event.scale); |
| |
| this._scaledMinimumAmount = false; |
| this._updateStateWithEvent(event); |
| |
| event.preventDefault(); |
| } |
| |
| gestureChanged(event) |
| { |
| event.preventDefault(); |
| |
| this._updateStateWithEvent(event); |
| } |
| |
| gestureEnded(event) |
| { |
| if (this._scaledMinimumAmount) |
| this.enterEndedState(); |
| else |
| this.enterFailedState(); |
| } |
| |
| reset() |
| { |
| this.scale = 1; |
| this._velocity = 0; |
| this._gestures = []; |
| delete this._startDistance; |
| } |
| |
| // Private |
| |
| _recordGesture(scale) |
| { |
| const currentTime = Date.now(); |
| const count = this._gestures.push({ |
| scale: scale, |
| timeStamp: currentTime |
| }); |
| |
| // We want to keep at least two gestures at all times. |
| if (count <= 2) |
| return; |
| |
| const scaleDirection = this._gestures[count - 1].scale >= this._gestures[count - 2].scale; |
| let i = count - 3; |
| for (; i >= 0; --i) { |
| let gesture = this._gestures[i]; |
| if (currentTime - gesture.timeStamp > MAXIMUM_TIME_FOR_RECORDING_GESTURES || |
| this._gestures[i + 1].scale >= gesture.scale !== scaleDirection) |
| break; |
| } |
| |
| if (i > 0) |
| this._gestures = this._gestures.slice(i + 1); |
| } |
| |
| _updateStateWithEvent(event) |
| { |
| const scaleSinceStart = GestureRecognizer.SupportsGestures ? event.scale : this._distance() / this._startDistance; |
| |
| if (!this._scaledMinimumAmount) { |
| if (Math.abs(1 - scaleSinceStart) >= this.scaleThreshold) { |
| this._scaledMinimumAmount = true; |
| this.scale = 1; |
| this.enterBeganState(); |
| } |
| return; |
| } |
| |
| this._recordGesture(scaleSinceStart); |
| |
| const oldestGesture = this._gestures[0]; |
| const ds = scaleSinceStart - oldestGesture.scale; |
| const dt = Date.now() - oldestGesture.timeStamp; |
| this._velocity = (dt === 0) ? 0 : ds / dt * 1000; |
| |
| this.scale *= scaleSinceStart / this._gestures[this._gestures.length - 2].scale; |
| |
| this.enterChangedState(); |
| } |
| |
| _distance() |
| { |
| console.assert(this.numberOfTouches === 2); |
| |
| const firstTouch = this._targetTouches[0]; |
| const firstTouchPoint = new DOMPoint(firstTouch.pageX, firstTouch.pageY); |
| |
| const secondTouch = this._targetTouches[1]; |
| const secondTouchPoint = new DOMPoint(secondTouch.pageX, secondTouch.pageY); |
| |
| return Math.sqrt(Math.pow(firstTouchPoint.x - secondTouchPoint.x, 2) + Math.pow(firstTouchPoint.y - secondTouchPoint.y, 2)); |
| } |
| |
| } |