• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright 2011 See AUTHORS file.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  ******************************************************************************/
16 
17 package com.badlogic.gdx.scenes.scene2d.utils;
18 
19 import com.badlogic.gdx.Gdx;
20 import com.badlogic.gdx.Input.Buttons;
21 import com.badlogic.gdx.scenes.scene2d.Actor;
22 import com.badlogic.gdx.scenes.scene2d.InputEvent;
23 import com.badlogic.gdx.scenes.scene2d.InputListener;
24 import com.badlogic.gdx.utils.TimeUtils;
25 
26 /** Detects mouse over, mouse or finger touch presses, and clicks on an actor. A touch must go down over the actor and is
27  * considered pressed as long as it is over the actor or within the {@link #setTapSquareSize(float) tap square}. This behavior
28  * makes it easier to press buttons on a touch interface when the initial touch happens near the edge of the actor. Double clicks
29  * can be detected using {@link #getTapCount()}. Any touch (not just the first) will trigger this listener. While pressed, other
30  * touch downs are ignored.
31  * @author Nathan Sweet */
32 public class ClickListener extends InputListener {
33 	/** Time in seconds {@link #isVisualPressed()} reports true after a press resulting in a click is released. */
34 	static public float visualPressedDuration = 0.1f;
35 
36 	private float tapSquareSize = 14, touchDownX = -1, touchDownY = -1;
37 	private int pressedPointer = -1;
38 	private int pressedButton = -1;
39 	private int button;
40 	private boolean pressed, over, cancelled;
41 	private long visualPressedTime;
42 	private long tapCountInterval = (long)(0.4f * 1000000000l);
43 	private int tapCount;
44 	private long lastTapTime;
45 
46 	/** Create a listener where {@link #clicked(InputEvent, float, float)} is only called for left clicks.
47 	 * @see #ClickListener(int) */
ClickListener()48 	public ClickListener () {
49 	}
50 
51 	/** @see #setButton(int) */
ClickListener(int button)52 	public ClickListener (int button) {
53 		this.button = button;
54 	}
55 
touchDown(InputEvent event, float x, float y, int pointer, int button)56 	public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
57 		if (pressed) return false;
58 		if (pointer == 0 && this.button != -1 && button != this.button) return false;
59 		pressed = true;
60 		pressedPointer = pointer;
61 		pressedButton = button;
62 		touchDownX = x;
63 		touchDownY = y;
64 		visualPressedTime = TimeUtils.millis() + (long)(visualPressedDuration * 1000);
65 		return true;
66 	}
67 
touchDragged(InputEvent event, float x, float y, int pointer)68 	public void touchDragged (InputEvent event, float x, float y, int pointer) {
69 		if (pointer != pressedPointer || cancelled) return;
70 		pressed = isOver(event.getListenerActor(), x, y);
71 		if (pressed && pointer == 0 && button != -1 && !Gdx.input.isButtonPressed(button)) pressed = false;
72 		if (!pressed) {
73 			// Once outside the tap square, don't use the tap square anymore.
74 			invalidateTapSquare();
75 		}
76 	}
77 
touchUp(InputEvent event, float x, float y, int pointer, int button)78 	public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
79 		if (pointer == pressedPointer) {
80 			if (!cancelled) {
81 				boolean touchUpOver = isOver(event.getListenerActor(), x, y);
82 				// Ignore touch up if the wrong mouse button.
83 				if (touchUpOver && pointer == 0 && this.button != -1 && button != this.button) touchUpOver = false;
84 				if (touchUpOver) {
85 					long time = TimeUtils.nanoTime();
86 					if (time - lastTapTime > tapCountInterval) tapCount = 0;
87 					tapCount++;
88 					lastTapTime = time;
89 					clicked(event, x, y);
90 				}
91 			}
92 			pressed = false;
93 			pressedPointer = -1;
94 			pressedButton = -1;
95 			cancelled = false;
96 		}
97 	}
98 
enter(InputEvent event, float x, float y, int pointer, Actor fromActor)99 	public void enter (InputEvent event, float x, float y, int pointer, Actor fromActor) {
100 		if (pointer == -1 && !cancelled) over = true;
101 	}
102 
exit(InputEvent event, float x, float y, int pointer, Actor toActor)103 	public void exit (InputEvent event, float x, float y, int pointer, Actor toActor) {
104 		if (pointer == -1 && !cancelled) over = false;
105 	}
106 
107 	/** If a touch down is being monitored, the drag and touch up events are ignored until the next touch up. */
cancel()108 	public void cancel () {
109 		if (pressedPointer == -1) return;
110 		cancelled = true;
111 		pressed = false;
112 	}
113 
clicked(InputEvent event, float x, float y)114 	public void clicked (InputEvent event, float x, float y) {
115 	}
116 
117 	/** Returns true if the specified position is over the specified actor or within the tap square. */
isOver(Actor actor, float x, float y)118 	public boolean isOver (Actor actor, float x, float y) {
119 		Actor hit = actor.hit(x, y, true);
120 		if (hit == null || !hit.isDescendantOf(actor)) return inTapSquare(x, y);
121 		return true;
122 	}
123 
inTapSquare(float x, float y)124 	public boolean inTapSquare (float x, float y) {
125 		if (touchDownX == -1 && touchDownY == -1) return false;
126 		return Math.abs(x - touchDownX) < tapSquareSize && Math.abs(y - touchDownY) < tapSquareSize;
127 	}
128 
129 	/** Returns true if a touch is within the tap square. */
inTapSquare()130 	public boolean inTapSquare () {
131 		return touchDownX != -1;
132 	}
133 
134 	/** The tap square will not longer be used for the current touch. */
invalidateTapSquare()135 	public void invalidateTapSquare () {
136 		touchDownX = -1;
137 		touchDownY = -1;
138 	}
139 
140 	/** Returns true if a touch is over the actor or within the tap square. */
isPressed()141 	public boolean isPressed () {
142 		return pressed;
143 	}
144 
145 	/** Returns true if a touch is over the actor or within the tap square or has been very recently. This allows the UI to show a
146 	 * press and release that was so fast it occurred within a single frame. */
isVisualPressed()147 	public boolean isVisualPressed () {
148 		if (pressed) return true;
149 		if (visualPressedTime <= 0) return false;
150 		if (visualPressedTime > TimeUtils.millis()) return true;
151 		visualPressedTime = 0;
152 		return false;
153 	}
154 
155 	/** Returns true if the mouse or touch is over the actor or pressed and within the tap square. */
isOver()156 	public boolean isOver () {
157 		return over || pressed;
158 	}
159 
setTapSquareSize(float halfTapSquareSize)160 	public void setTapSquareSize (float halfTapSquareSize) {
161 		tapSquareSize = halfTapSquareSize;
162 	}
163 
getTapSquareSize()164 	public float getTapSquareSize () {
165 		return tapSquareSize;
166 	}
167 
168 	/** @param tapCountInterval time in seconds that must pass for two touch down/up sequences to be detected as consecutive taps. */
setTapCountInterval(float tapCountInterval)169 	public void setTapCountInterval (float tapCountInterval) {
170 		this.tapCountInterval = (long)(tapCountInterval * 1000000000l);
171 	}
172 
173 	/** Returns the number of taps within the tap count interval for the most recent click event. */
getTapCount()174 	public int getTapCount () {
175 		return tapCount;
176 	}
177 
getTouchDownX()178 	public float getTouchDownX () {
179 		return touchDownX;
180 	}
181 
getTouchDownY()182 	public float getTouchDownY () {
183 		return touchDownY;
184 	}
185 
186 	/** The button that initially pressed this button or -1 if the button is not pressed. */
getPressedButton()187 	public int getPressedButton () {
188 		return pressedButton;
189 	}
190 
191 	/** The pointer that initially pressed this button or -1 if the button is not pressed. */
getPressedPointer()192 	public int getPressedPointer () {
193 		return pressedPointer;
194 	}
195 
196 	/** @see #setButton(int) */
getButton()197 	public int getButton () {
198 		return button;
199 	}
200 
201 	/** Sets the button to listen for, all other buttons are ignored. Default is {@link Buttons#LEFT}. Use -1 for any button. */
setButton(int button)202 	public void setButton (int button) {
203 		this.button = button;
204 	}
205 }
206