/***********************************************************************
* mt4j Copyright (c) 2008 - 2009 C.Ruff, Fraunhofer-Gesellschaft All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
***********************************************************************/
package org.mt4j.input.inputProcessors.componentProcessors.tapProcessor;
import java.util.ArrayList;
import org.mt4j.components.MTCanvas;
import org.mt4j.components.interfaces.IMTComponent3D;
import org.mt4j.input.inputData.InputCursor;
import org.mt4j.input.inputData.MTFingerInputEvt;
import org.mt4j.input.inputProcessors.IInputProcessor;
import org.mt4j.input.inputProcessors.MTGestureEvent;
import org.mt4j.input.inputProcessors.componentProcessors.AbstractComponentProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor;
import org.mt4j.util.math.Tools3D;
import org.mt4j.util.math.Vector3D;
import processing.core.PApplet;
/**
* The Class TapProcessor. Tap multitouch gesture. Triggered on a component
* that is tapped with a finger.
* Fires TapEvent gesture events.
* @author Christopher Ruff
*/
public class TapProcessor extends AbstractCursorProcessor {
/** The applet. */
private PApplet applet;
/** The max finger up dist. */
private float maxFingerUpDist;
/** The un used cursors. */
private ArrayList<InputCursor> unUsedCursors;
/** The locked cursors. */
private ArrayList<InputCursor> lockedCursors;
/** The button down screen pos. */
private Vector3D buttonDownScreenPos;
/** The enable double tap. */
private boolean enableDoubleTap;
/** The time last tap. */
private long timeLastTap;
/** The double tap time. */
private int doubleTapTime = 300;
/**
* Instantiates a new tap processor.
*
* @param pa the pa
*/
public TapProcessor(PApplet pa) {
this(pa, 18.0f);
}
/**
* Instantiates a new tap processor.
*
* @param pa the pa
* @param maxFingerUpDistance the max finger up distance
*/
public TapProcessor(PApplet pa, float maxFingerUpDistance) {
this(pa, maxFingerUpDistance, false, 300);
}
/**
* Instantiates a new tap processor.
*
* @param pa the pa
* @param maxFingerUpDistance the max finger up distance
* @param enableDoubleTap the enable double tap
*/
public TapProcessor(PApplet pa, float maxFingerUpDistance, boolean enableDoubleTap){
this(pa, maxFingerUpDistance, enableDoubleTap, 300);
}
/**
* Instantiates a new tap processor.
*
* @param pa the pa
* @param maxFingerUpDistance the max finger up distance
* @param enableDoubleTap the enable double tap
* @param doubleTapTime the double tap time
*/
public TapProcessor(PApplet pa, float maxFingerUpDistance, boolean enableDoubleTap, int doubleTapTime){
super();
this.applet = pa;
this.maxFingerUpDist = maxFingerUpDistance;
this.unUsedCursors = new ArrayList<InputCursor>();
this.lockedCursors = new ArrayList<InputCursor>();
this.setLockPriority(1);
this.setDebug(false);
this.enableDoubleTap = enableDoubleTap;
this.doubleTapTime = doubleTapTime;
this.timeLastTap = -1;
//System.out.println("Double click default time:" + Toolkit.getDefaultToolkit().getDesktopProperty("awt.multiClickInterval"));
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor#cursorStarted(org.mt4j.input.inputData.InputCursor, org.mt4j.input.inputData.MTFingerInputEvt)
*/
@Override
public void cursorStarted(InputCursor m, MTFingerInputEvt positionEvent) {
IMTComponent3D comp = positionEvent.getTargetComponent();
if (lockedCursors.size() >= 1){ //We assume that the gesture is already in progress and add this new cursor to the unUsedList
unUsedCursors.add(m);
}else{
if (unUsedCursors.size() == 0){ //Only start gesture if no other finger on the component yet
if (this.canLock(m)){//See if we can obtain a lock on this cursor (depends on the priority)
this.getLock(m);
logger.debug(this.getName() + " successfully locked cursor (id:" + m.getId() + ")");
lockedCursors.add(m);
buttonDownScreenPos = new Vector3D(m.getCurrentEvent().getPosX(), m.getCurrentEvent().getPosY(), 0);
this.fireGestureEvent(new TapEvent(this, MTGestureEvent.GESTURE_DETECTED, comp, m, buttonDownScreenPos, TapEvent.BUTTON_DOWN));
}else{
unUsedCursors.add(m);
}
}else{
unUsedCursors.add(m);
}
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor#cursorUpdated(org.mt4j.input.inputData.InputCursor, org.mt4j.input.inputData.MTFingerInputEvt)
*/
@Override
public void cursorUpdated(InputCursor m, MTFingerInputEvt positionEvent) {
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor#cursorEnded(org.mt4j.input.inputData.InputCursor, org.mt4j.input.inputData.MTFingerInputEvt)
*/
@Override
public void cursorEnded(InputCursor m, MTFingerInputEvt positionEvent) {
IMTComponent3D comp = positionEvent.getTargetComponent();
logger.debug(this.getName() + " INPUT_ENDED RECIEVED - CURSOR: " + m.getId());
if (lockedCursors.contains(m)){ //cursor was a actual gesture cursor
lockedCursors.remove(m);
if (unUsedCursors.size() > 0){ //check if there are other cursors on the component, we could use
InputCursor otherCursor = unUsedCursors.get(0); //TODO cycle through all available unUsedCursors and try to lock one, maybe the first one is lock but another isnt!
if (this.canLock(otherCursor)){ //Check if we have the priority to use this cursor
this.getLock(otherCursor);
unUsedCursors.remove(otherCursor);
lockedCursors.add(otherCursor);
//TODO fire started? maybe not.. do we have to?
}else{
this.endGesture(m, comp);
}
}else{
this.endGesture(m, comp);
}
this.unLock(m); //FIXME TEST
}else{ //cursor was not used here
if (unUsedCursors.contains(m)){
unUsedCursors.remove(m);
}
}
}
/**
* End gesture.
*
* @param m the m
* @param comp the comp
*/
private void endGesture(InputCursor m, IMTComponent3D comp){
//Default where for the event if no intersections are found
Vector3D buttonUpScreenPos = new Vector3D(m.getCurrentEvent().getPosX(), m.getCurrentEvent().getPosY(), 0);
//If component is detached from tree, destroyed etc
if (comp.getViewingCamera() == null){
this.fireGestureEvent(new TapEvent(this, MTGestureEvent.GESTURE_ENDED, comp, m, buttonUpScreenPos, TapEvent.BUTTON_CLICKED));
return;
}
Vector3D intersection = comp.getIntersectionGlobal(Tools3D.getCameraPickRay(applet, comp, m.getCurrentEvent().getPosX(), m.getCurrentEvent().getPosY()));
//logger.debug("Distance between buttondownScreenPos: " + buttonDownScreenPos + " and upScrPos: " + buttonUpScreenPos + " is: " + Vector3D.distance(buttonDownScreenPos, buttonUpScreenPos));
//Check if at finger_Up the cursor is still on that object or if the cursor has moved too much
if ((intersection != null || comp instanceof MTCanvas)
&&
Vector3D.distance(buttonDownScreenPos, buttonUpScreenPos) <= this.maxFingerUpDist
){
//We have a valid TAP!
if (this.isEnableDoubleTap()){
//Check if it was a double tap by comparing the now time to the time of the last valid tap
long now = m.getCurrentEvent().getWhen();
if (this.timeLastTap != -1 && (now - this.timeLastTap) <= this.getDoubleTapTime()){
//Its a Double tap
this.timeLastTap = -1;
this.fireGestureEvent(new TapEvent(this, MTGestureEvent.GESTURE_ENDED, comp, m, buttonUpScreenPos, TapEvent.BUTTON_DOUBLE_CLICKED));
}else{
this.timeLastTap = now;
this.fireGestureEvent(new TapEvent(this, MTGestureEvent.GESTURE_ENDED, comp, m, buttonUpScreenPos, TapEvent.BUTTON_CLICKED));
}
}else{
this.fireGestureEvent(new TapEvent(this, MTGestureEvent.GESTURE_ENDED, comp, m, buttonUpScreenPos, TapEvent.BUTTON_CLICKED));
}
}else{
//logger.debug("FINGER UP NOT ON SAME OBJ!");
this.fireGestureEvent(new TapEvent(this, MTGestureEvent.GESTURE_ENDED, comp, m, buttonUpScreenPos, TapEvent.BUTTON_UP));
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor#cursorLocked(org.mt4j.input.inputData.InputCursor, org.mt4j.input.inputProcessors.IInputProcessor)
*/
@Override
public void cursorLocked(InputCursor m, IInputProcessor lockingProcessor) {
if (lockingProcessor instanceof AbstractComponentProcessor){
logger.debug(this.getName() + " Recieved CURSOR LOCKED by (" + ((AbstractComponentProcessor)lockingProcessor).getName() + ") - cursor ID: " + m.getId());
}else{
logger.debug(this.getName() + " Recieved CURSOR LOCKED by higher priority signal - cursor ID: " + m.getId());
}
if (lockedCursors.contains(m)){ //cursor was in use here
lockedCursors.remove(m);
unUsedCursors.add(m);
logger.debug(this.getName() + " cursor:" + m.getId() + " CURSOR LOCKED. Was an active cursor in this gesture!");
//FIXME TEST -> dont allow resuming of gesture if the cursor was locked by a higher priority gesture
//-> fire gesture ended
this.fireGestureEvent(new TapEvent(this, MTGestureEvent.GESTURE_ENDED, m.getCurrentEvent().getTargetComponent(), m, new Vector3D(m.getCurrentEvent().getPosX(), m.getCurrentEvent().getPosY()), TapEvent.BUTTON_UP));
}else{ //Else is just for debug
if (unUsedCursors.contains(m)){
logger.debug(this.getName() + " CURSOR LOCKED. But it was NOT an active cursor in this gesture!");
}
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor#cursorUnlocked(org.mt4j.input.inputData.InputCursor)
*/
@Override
public void cursorUnlocked(InputCursor m) {
logger.debug(this.getName() + " Recieved UNLOCKED signal for cursor ID: " + m.getId());
if (lockedCursors.size() >= 1){ //we dont need the unlocked cursor, gesture still in progress
logger.debug(this.getName() + " still in progress - we dont need the unlocked cursor" );
return;
}
/*
//FIXME TEST -> dont allow resuming of tap if the tap cursor was locked by a higher priority gesture
//-> dont resume gesture here
if (unUsedCursors.contains(m)){
if (this.canLock(m)){
this.getLock(m);
unUsedCursors.remove(m);
lockedCursors.add(m);
//TODO fire started? maybe not.. do we have to?
logger.debug(this.getName() + " can resume its gesture with cursor: " + m.getId());
}
}
*/
}
/**
* Gets the max finger up dist.
*
* @return the max finger up dist
*/
public float getMaxFingerUpDist() {
return maxFingerUpDist;
}
/**
* Sets the maximum allowed distance of the position
* of the finger_down event and the finger_up event
* that fires a click event (in screen pixels).
* <br>This ensures that a click event is only raised
* if the finger didnt move too far during the click.
*
* @param maxFingerUpDist the max finger up dist
*/
public void setMaxFingerUpDist(float maxFingerUpDist) {
this.maxFingerUpDist = maxFingerUpDist;
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractComponentProcessor#getName()
*/
@Override
public String getName() {
return "Tap Processor";
}
/**
* Checks if is enable double tap.
*
* @return true, if is enable double tap
*/
public boolean isEnableDoubleTap() {
return this.enableDoubleTap;
}
/**
* Sets the enable double tap.
*
* @param enableDoubleTap the new enable double tap
*/
public void setEnableDoubleTap(boolean enableDoubleTap) {
this.enableDoubleTap = enableDoubleTap;
}
/**
* Gets the double tap time.
*
* @return the double tap time
*/
public int getDoubleTapTime() {
return this.doubleTapTime;
}
/**
* Sets the double tap time.
*
* @param doubleTapTime the new double tap time
*/
public void setDoubleTapTime(int doubleTapTime) {
this.doubleTapTime = doubleTapTime;
}
}