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