/*
* Copyright 2012 Daniel Kurka
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.googlecode.mgwt.dom.client.recognizer.swipe;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.TouchCancelEvent;
import com.google.gwt.event.dom.client.TouchEndEvent;
import com.google.gwt.event.dom.client.TouchMoveEvent;
import com.google.gwt.event.dom.client.TouchStartEvent;
import com.google.gwt.event.shared.HasHandlers;
import com.googlecode.mgwt.dom.client.event.touch.TouchCopy;
import com.googlecode.mgwt.dom.client.event.touch.TouchHandler;
import com.googlecode.mgwt.dom.client.recognizer.EventPropagator;
import com.googlecode.mgwt.dom.client.recognizer.swipe.SwipeEvent.DIRECTION;
public class SwipeRecognizer implements TouchHandler {
private static EventPropagator DEFAULT_EVENT_PROPAGATOR;
private final HasHandlers source;
private EventPropagator eventPropagator;
private final int minDistance;
private final int threshold;
private int touchCount;
private enum State {
INVALID, READY, FINDER_DOWN, FOUND_DIRECTION
}
private State state;
private DIRECTION direction;
private int lastDistance;
private int x;
private int y;
/**
* construct a swipe recognizer
*
* @param source the source to fire events on
*/
public SwipeRecognizer(HasHandlers source) {
this(source, 40);
}
/**
* construct a swipe recognizer
*
* @param source the source to fire events on
* @param minDistance the minimum distance to cover before this counts as a swipe
*/
public SwipeRecognizer(HasHandlers source, int minDistance) {
this(source, minDistance, 10);
}
/**
* construct a swipe recognizer
*
* @param source the source to fire events on
* @param minDistance the minimum distance to cover before this counts as a swipe
* @param threshold the initial threshold before swipe start is fired
*/
public SwipeRecognizer(HasHandlers source, int minDistance, int threshold) {
if (source == null)
throw new IllegalArgumentException("source can not be null");
if (minDistance <= 0 || minDistance < threshold) {
throw new IllegalArgumentException("minDistance > 0 and minDistance > threshold");
}
if (threshold <= 0) {
throw new IllegalArgumentException("threshold > 0");
}
this.source = source;
this.minDistance = minDistance;
this.threshold = threshold;
this.touchCount = 0;
state = State.READY;
}
@Override
public void onTouchStart(TouchStartEvent event) {
touchCount++;
switch (state) {
case INVALID:
break;
case READY:
state = State.FINDER_DOWN;
x = event.getTouches().get(0).getPageX();
y = event.getTouches().get(0).getPageY();
break;
case FINDER_DOWN:
default:
state = State.INVALID;
break;
}
}
@Override
public void onTouchMove(TouchMoveEvent event) {
Touch touch = event.getTouches().get(0);
switch (state) {
case INVALID:
break;
case READY:
// WTF?
state = State.INVALID;
break;
case FINDER_DOWN:
// log(" X: " + touch.getPageX() + " old: " + touchStart.getPageX() + " test: " + x);
if (Math.abs(touch.getPageX() - x) >= threshold) {
state = State.FOUND_DIRECTION;
direction = touch.getPageX() - x > 0 ? DIRECTION.LEFT_TO_RIGHT : DIRECTION.RIGHT_TO_LEFT;
SwipeStartEvent swipeStartEvent =
new SwipeStartEvent(TouchCopy.copy(touch), touch.getPageX() - x, direction);
getEventPropagator().fireEvent(source, swipeStartEvent);
} else {
if (Math.abs(touch.getPageY() - y) >= threshold) {
state = State.FOUND_DIRECTION;
direction = touch.getPageY() - y > 0 ? DIRECTION.TOP_TO_BOTTOM : DIRECTION.BOTTOM_TO_TOP;
SwipeStartEvent swipeStartEvent =
new SwipeStartEvent(TouchCopy.copy(touch), touch.getPageY() - y, direction);
getEventPropagator().fireEvent(source, swipeStartEvent);
}
}
break;
case FOUND_DIRECTION:
switch (direction) {
case TOP_TO_BOTTOM:
case BOTTOM_TO_TOP:
lastDistance = Math.abs(touch.getPageY() - y);
getEventPropagator().fireEvent(
source,
new SwipeMoveEvent(TouchCopy.copy(touch), lastDistance > minDistance,
lastDistance, direction));
break;
case LEFT_TO_RIGHT:
case RIGHT_TO_LEFT:
lastDistance = Math.abs(touch.getPageX() - x);
getEventPropagator().fireEvent(
source,
new SwipeMoveEvent(TouchCopy.copy(touch), lastDistance > minDistance,
lastDistance, direction));
break;
default:
break;
}
break;
default:
break;
}
}
@Override
public void onTouchEnd(TouchEndEvent event) {
touchCount--;
switch (state) {
case FOUND_DIRECTION:
getEventPropagator().fireEvent(source, new SwipeEndEvent(lastDistance > minDistance, lastDistance, direction));
reset();
break;
default:
reset();
break;
}
}
@Override
public void onTouchCancel(TouchCancelEvent event) {
touchCount--;
if (touchCount <= 0) {
reset();
}
}
/**
* the threshold before an event is fired (deadzone)
*
* @return the threshold in px
*/
public int getThreshold() {
return threshold;
}
/**
* the distance that needs to be covered before counting as a swipe
*
* @return the distance in px
*/
public int getMinDistance() {
return minDistance;
}
private void reset() {
state = State.READY;
touchCount = 0;
}
// Visible for testing
EventPropagator getEventPropagator() {
if (eventPropagator == null) {
if (DEFAULT_EVENT_PROPAGATOR == null) {
DEFAULT_EVENT_PROPAGATOR = GWT.create(EventPropagator.class);
}
eventPropagator = DEFAULT_EVENT_PROPAGATOR;
}
return eventPropagator;
}
}