SwipeRecognizer.java
/*
* Copyright (C) 2000 - 2025 Silverpeas
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* As a special exception to the terms and conditions of version 3.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* Open Source Software ("FLOSS") applications as described in Silverpeas's
* FLOSS exception. You should have received a copy of the text describing
* the FLOSS exception, and it is also available here:
* "https://www.silverpeas.org/legal/floss_exception.html"
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.silverpeas.mobile.client.common.reconizer.swipe;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.*;
import org.silverpeas.mobile.client.common.EventBus;
import org.silverpeas.mobile.client.common.event.touch.TouchCopy;
public class SwipeRecognizer implements TouchStartHandler, TouchMoveHandler, TouchEndHandler,
TouchCancelHandler {
private final HasAllTouchHandlers source;
private final int minDistance;
private final int threshold;
private int touchCount;
private enum State {
INVALID, READY, FINDER_DOWN, FOUND_DIRECTION
}
private State state;
private SwipeEvent.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(HasAllTouchHandlers 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(HasAllTouchHandlers 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(HasAllTouchHandlers 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;
((HasAllTouchHandlers) source).addTouchStartHandler(this);
((HasAllTouchHandlers) source).addTouchMoveHandler(this);
((HasAllTouchHandlers) source).addTouchEndHandler(this);
((HasAllTouchHandlers) source).addTouchCancelHandler(this);
}
@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:
if (Math.abs(touch.getPageX() - x) >= threshold) {
state = State.FOUND_DIRECTION;
direction = touch.getPageX() - x > 0 ? SwipeEvent.DIRECTION.LEFT_TO_RIGHT : SwipeEvent
.DIRECTION.RIGHT_TO_LEFT;
SwipeStartEvent swipeStartEvent =
new SwipeStartEvent(TouchCopy.copy(touch), touch.getPageX() - x, direction);
getEventPropagator().fireEvent(swipeStartEvent);
} else {
if (Math.abs(touch.getPageY() - y) >= threshold) {
state = State.FOUND_DIRECTION;
direction = touch.getPageY() - y > 0 ? SwipeEvent.DIRECTION.TOP_TO_BOTTOM : SwipeEvent.DIRECTION.BOTTOM_TO_TOP;
SwipeStartEvent swipeStartEvent =
new SwipeStartEvent(TouchCopy.copy(touch), touch.getPageY() - y, direction);
getEventPropagator().fireEvent(swipeStartEvent);
}
}
break;
case FOUND_DIRECTION:
switch (direction) {
case TOP_TO_BOTTOM:
case BOTTOM_TO_TOP:
lastDistance = Math.abs(touch.getPageY() - y);
getEventPropagator().fireEvent(
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(
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(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;
}
private EventBus getEventPropagator() {
return EventBus.getInstance();
}
}