/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* 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.badlogic.gdx.scenes.scene2d.utils;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Buttons;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.utils.TimeUtils;
/**
* Detects mouse over, mouse or finger touch presses, and clicks on an actor. A touch must go down over the actor and is
* considered pressed as long as it is over the actor or within the {@link #setTapSquareSize(float) tap square}. This
* behavior makes it easier to press buttons on a touch interface when the initial touch happens near the edge of the
* actor. Double clicks can be detected using {@link #getTapCount()}. Any touch (not just the first) will trigger this
* listener. While pressed, other touch downs are ignored.
*
* @author Nathan Sweet
*/
public class ClickListener extends InputListener {
private float tapSquareSize = 14, touchDownX = -1, touchDownY = -1;
private int pressedPointer = -1;
private int button;
private boolean pressed, over, cancelled;
private long tapCountInterval = (long) (0.4f * 1000000000l);
private int tapCount;
private long lastTapTime;
public ClickListener() {
}
public ClickListener(int button) {
this.button = button;
}
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
if (pressed)
return false;
if (pointer == 0 && this.button != -1 && button != this.button)
return false;
pressed = true;
pressedPointer = pointer;
touchDownX = x;
touchDownY = y;
return true;
}
public void touchDragged(InputEvent event, float x, float y, int pointer) {
if (pointer != pressedPointer || cancelled)
return;
pressed = isOver(event.getListenerActor(), x, y);
if (pressed && pointer == 0 && button != -1 && !Gdx.input.isButtonPressed(button))
pressed = false;
if (!pressed) {
// Once outside the tap square, don't use the tap square anymore.
invalidateTapSquare();
}
}
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
if (pointer == pressedPointer) {
if (!cancelled) {
boolean validClick = isOver(event.getListenerActor(), x, y);
if (validClick && pointer == 0 && this.button != -1 && button != this.button)
validClick = false;
if (validClick) {
long time = TimeUtils.nanoTime();
if (time - lastTapTime > tapCountInterval)
tapCount = 0;
tapCount++;
lastTapTime = time;
clicked(event, x, y);
}
}
pressed = false;
pressedPointer = -1;
cancelled = false;
}
}
public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) {
if (pointer == -1 && !cancelled)
over = true;
}
public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
if (pointer == -1 && !cancelled)
over = false;
}
/** If a touch down is being monitored, the drag and touch up events are ignored until the next touch up. */
public void cancel() {
if (pressedPointer == -1)
return;
cancelled = true;
over = false;
pressed = false;
}
public void clicked(InputEvent event, float x, float y) {
}
public void dragStart(InputEvent event, float x, float y, int pointer) {
}
public void drag(InputEvent event, float x, float y, int pointer) {
}
public void dragStop(InputEvent event, float x, float y, int pointer) {
}
/** Returns true if the specified position is over the specified actor or within the tap square. */
public boolean isOver(Actor actor, float x, float y) {
Actor hit = actor.hit(x, y, true);
if (hit == null || !hit.isDescendantOf(actor))
return inTapSquare(x, y);
return true;
}
public boolean inTapSquare(float x, float y) {
if (touchDownX == -1 && touchDownY == -1)
return false;
return Math.abs(x - touchDownX) < tapSquareSize && Math.abs(y - touchDownY) < tapSquareSize;
}
/** The tap square will not longer be used for the current touch. */
public void invalidateTapSquare() {
touchDownX = -1;
touchDownY = -1;
}
/** Returns true if a touch is over the actor or within the tap square. */
public boolean isPressed() {
return pressed;
}
/** Returns true if the mouse or touch is over the actor or pressed and within the tap square. */
public boolean isOver() {
return over || pressed;
}
public void setTapSquareSize(float halfTapSquareSize) {
tapSquareSize = halfTapSquareSize;
}
public float getTapSquareSize() {
return tapSquareSize;
}
/**
* @param tapCountInterval
* time in seconds that must pass for two touch down/up sequences to be detected as consecutive taps.
*/
public void setTapCountInterval(float tapCountInterval) {
this.tapCountInterval = (long) (tapCountInterval * 1000000000l);
}
/** Returns the number of taps within the tap count interval for the most recent click event. */
public int getTapCount() {
return tapCount;
}
public float getTouchDownX() {
return touchDownX;
}
public float getTouchDownY() {
return touchDownY;
}
public int getButton() {
return button;
}
/**
* Sets the button to listen for, all other buttons are ignored. Default is {@link Buttons#LEFT}. Use -1 for any
* button.
*/
public void setButton(int button) {
this.button = button;
}
}