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; 18 19 import com.badlogic.gdx.Application.ApplicationType; 20 import com.badlogic.gdx.Gdx; 21 import com.badlogic.gdx.Graphics; 22 import com.badlogic.gdx.Input; 23 import com.badlogic.gdx.InputAdapter; 24 import com.badlogic.gdx.InputMultiplexer; 25 import com.badlogic.gdx.graphics.Camera; 26 import com.badlogic.gdx.graphics.Color; 27 import com.badlogic.gdx.graphics.GL20; 28 import com.badlogic.gdx.graphics.OrthographicCamera; 29 import com.badlogic.gdx.graphics.g2d.Batch; 30 import com.badlogic.gdx.graphics.g2d.SpriteBatch; 31 import com.badlogic.gdx.graphics.glutils.ShapeRenderer; 32 import com.badlogic.gdx.math.Matrix4; 33 import com.badlogic.gdx.math.Rectangle; 34 import com.badlogic.gdx.math.Vector2; 35 import com.badlogic.gdx.scenes.scene2d.InputEvent.Type; 36 import com.badlogic.gdx.scenes.scene2d.ui.Table; 37 import com.badlogic.gdx.scenes.scene2d.ui.Table.Debug; 38 import com.badlogic.gdx.scenes.scene2d.utils.FocusListener.FocusEvent; 39 import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack; 40 import com.badlogic.gdx.utils.Array; 41 import com.badlogic.gdx.utils.Disposable; 42 import com.badlogic.gdx.utils.Pool.Poolable; 43 import com.badlogic.gdx.utils.Pools; 44 import com.badlogic.gdx.utils.Scaling; 45 import com.badlogic.gdx.utils.SnapshotArray; 46 import com.badlogic.gdx.utils.viewport.ScalingViewport; 47 import com.badlogic.gdx.utils.viewport.Viewport; 48 49 /** A 2D scene graph containing hierarchies of {@link Actor actors}. Stage handles the viewport and distributes input events. 50 * <p> 51 * {@link #setViewport(Viewport)} controls the coordinates used within the stage and sets up the camera used to convert between 52 * stage coordinates and screen coordinates. 53 * <p> 54 * A stage must receive input events so it can distribute them to actors. This is typically done by passing the stage to 55 * {@link Input#setInputProcessor(com.badlogic.gdx.InputProcessor) Gdx.input.setInputProcessor}. An {@link InputMultiplexer} may be 56 * used to handle input events before or after the stage does. If an actor handles an event by returning true from the input 57 * method, then the stage's input method will also return true, causing subsequent InputProcessors to not receive the event. 58 * <p> 59 * The Stage and its constituents (like Actors and Listeners) are not thread-safe and should only be updated and queried from a 60 * single thread (presumably the main render thread). Methods should be reentrant, so you can update Actors and Stages from within 61 * callbacks and handlers. 62 * @author mzechner 63 * @author Nathan Sweet */ 64 public class Stage extends InputAdapter implements Disposable { 65 /** True if any actor has ever had debug enabled. */ 66 static boolean debug; 67 68 private Viewport viewport; 69 private final Batch batch; 70 private boolean ownsBatch; 71 private final Group root; 72 private final Vector2 tempCoords = new Vector2(); 73 private final Actor[] pointerOverActors = new Actor[20]; 74 private final boolean[] pointerTouched = new boolean[20]; 75 private final int[] pointerScreenX = new int[20]; 76 private final int[] pointerScreenY = new int[20]; 77 private int mouseScreenX, mouseScreenY; 78 private Actor mouseOverActor; 79 private Actor keyboardFocus, scrollFocus; 80 private final SnapshotArray<TouchFocus> touchFocuses = new SnapshotArray(true, 4, TouchFocus.class); 81 private boolean actionsRequestRendering = true; 82 83 private ShapeRenderer debugShapes; 84 private boolean debugInvisible, debugAll, debugUnderMouse, debugParentUnderMouse; 85 private Debug debugTableUnderMouse = Debug.none; 86 private final Color debugColor = new Color(0, 1, 0, 0.85f); 87 88 /** Creates a stage with a {@link ScalingViewport} set to {@link Scaling#stretch}. The stage will use its own {@link Batch} 89 * which will be disposed when the stage is disposed. */ 90 public Stage () { 91 this(new ScalingViewport(Scaling.stretch, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), new OrthographicCamera()), 92 new SpriteBatch()); 93 ownsBatch = true; 94 } 95 96 /** Creates a stage with the specified viewport. The stage will use its own {@link Batch} which will be disposed when the stage 97 * is disposed. */ 98 public Stage (Viewport viewport) { 99 this(viewport, new SpriteBatch()); 100 ownsBatch = true; 101 } 102 103 /** Creates a stage with the specified viewport and batch. This can be used to avoid creating a new batch (which can be somewhat 104 * slow) if multiple stages are used during an application's life time. 105 * @param batch Will not be disposed if {@link #dispose()} is called, handle disposal yourself. */ 106 public Stage (Viewport viewport, Batch batch) { 107 if (viewport == null) throw new IllegalArgumentException("viewport cannot be null."); 108 if (batch == null) throw new IllegalArgumentException("batch cannot be null."); 109 this.viewport = viewport; 110 this.batch = batch; 111 112 root = new Group(); 113 root.setStage(this); 114 115 viewport.update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true); 116 } 117 118 public void draw () { 119 Camera camera = viewport.getCamera(); 120 camera.update(); 121 122 if (!root.isVisible()) return; 123 124 Batch batch = this.batch; 125 if (batch != null) { 126 batch.setProjectionMatrix(camera.combined); 127 batch.begin(); 128 root.draw(batch, 1); 129 batch.end(); 130 } 131 132 if (debug) drawDebug(); 133 } 134 135 private void drawDebug () { 136 if (debugShapes == null) { 137 debugShapes = new ShapeRenderer(); 138 debugShapes.setAutoShapeType(true); 139 } 140 141 if (debugUnderMouse || debugParentUnderMouse || debugTableUnderMouse != Debug.none) { 142 screenToStageCoordinates(tempCoords.set(Gdx.input.getX(), Gdx.input.getY())); 143 Actor actor = hit(tempCoords.x, tempCoords.y, true); 144 if (actor == null) return; 145 146 if (debugParentUnderMouse && actor.parent != null) actor = actor.parent; 147 148 if (debugTableUnderMouse == Debug.none) 149 actor.setDebug(true); 150 else { 151 while (actor != null) { 152 if (actor instanceof Table) break; 153 actor = actor.parent; 154 } 155 if (actor == null) return; 156 ((Table)actor).debug(debugTableUnderMouse); 157 } 158 159 if (debugAll && actor instanceof Group) ((Group)actor).debugAll(); 160 161 disableDebug(root, actor); 162 } else { 163 if (debugAll) root.debugAll(); 164 } 165 166 Gdx.gl.glEnable(GL20.GL_BLEND); 167 debugShapes.setProjectionMatrix(viewport.getCamera().combined); 168 debugShapes.begin(); 169 root.drawDebug(debugShapes); 170 debugShapes.end(); 171 } 172 173 /** Disables debug on all actors recursively except the specified actor and any children. */ 174 private void disableDebug (Actor actor, Actor except) { 175 if (actor == except) return; 176 actor.setDebug(false); 177 if (actor instanceof Group) { 178 SnapshotArray<Actor> children = ((Group)actor).children; 179 for (int i = 0, n = children.size; i < n; i++) 180 disableDebug(children.get(i), except); 181 } 182 } 183 184 /** Calls {@link #act(float)} with {@link Graphics#getDeltaTime()}, limited to a minimum of 30fps. */ 185 public void act () { 186 act(Math.min(Gdx.graphics.getDeltaTime(), 1 / 30f)); 187 } 188 189 /** Calls the {@link Actor#act(float)} method on each actor in the stage. Typically called each frame. This method also fires 190 * enter and exit events. 191 * @param delta Time in seconds since the last frame. */ 192 public void act (float delta) { 193 // Update over actors. Done in act() because actors may change position, which can fire enter/exit without an input event. 194 for (int pointer = 0, n = pointerOverActors.length; pointer < n; pointer++) { 195 Actor overLast = pointerOverActors[pointer]; 196 // Check if pointer is gone. 197 if (!pointerTouched[pointer]) { 198 if (overLast != null) { 199 pointerOverActors[pointer] = null; 200 screenToStageCoordinates(tempCoords.set(pointerScreenX[pointer], pointerScreenY[pointer])); 201 // Exit over last. 202 InputEvent event = Pools.obtain(InputEvent.class); 203 event.setType(InputEvent.Type.exit); 204 event.setStage(this); 205 event.setStageX(tempCoords.x); 206 event.setStageY(tempCoords.y); 207 event.setRelatedActor(overLast); 208 event.setPointer(pointer); 209 overLast.fire(event); 210 Pools.free(event); 211 } 212 continue; 213 } 214 // Update over actor for the pointer. 215 pointerOverActors[pointer] = fireEnterAndExit(overLast, pointerScreenX[pointer], pointerScreenY[pointer], pointer); 216 } 217 // Update over actor for the mouse on the desktop. 218 ApplicationType type = Gdx.app.getType(); 219 if (type == ApplicationType.Desktop || type == ApplicationType.Applet || type == ApplicationType.WebGL) 220 mouseOverActor = fireEnterAndExit(mouseOverActor, mouseScreenX, mouseScreenY, -1); 221 222 root.act(delta); 223 } 224 225 private Actor fireEnterAndExit (Actor overLast, int screenX, int screenY, int pointer) { 226 // Find the actor under the point. 227 screenToStageCoordinates(tempCoords.set(screenX, screenY)); 228 Actor over = hit(tempCoords.x, tempCoords.y, true); 229 if (over == overLast) return overLast; 230 231 // Exit overLast. 232 if (overLast != null) { 233 InputEvent event = Pools.obtain(InputEvent.class); 234 event.setStage(this); 235 event.setStageX(tempCoords.x); 236 event.setStageY(tempCoords.y); 237 event.setPointer(pointer); 238 event.setType(InputEvent.Type.exit); 239 event.setRelatedActor(over); 240 overLast.fire(event); 241 Pools.free(event); 242 } 243 // Enter over. 244 if (over != null) { 245 InputEvent event = Pools.obtain(InputEvent.class); 246 event.setStage(this); 247 event.setStageX(tempCoords.x); 248 event.setStageY(tempCoords.y); 249 event.setPointer(pointer); 250 event.setType(InputEvent.Type.enter); 251 event.setRelatedActor(overLast); 252 over.fire(event); 253 Pools.free(event); 254 } 255 return over; 256 } 257 258 /** Applies a touch down event to the stage and returns true if an actor in the scene {@link Event#handle() handled} the event. */ 259 public boolean touchDown (int screenX, int screenY, int pointer, int button) { 260 if (screenX < viewport.getScreenX() || screenX >= viewport.getScreenX() + viewport.getScreenWidth()) return false; 261 if (Gdx.graphics.getHeight() - screenY < viewport.getScreenY() 262 || Gdx.graphics.getHeight() - screenY >= viewport.getScreenY() + viewport.getScreenHeight()) return false; 263 264 pointerTouched[pointer] = true; 265 pointerScreenX[pointer] = screenX; 266 pointerScreenY[pointer] = screenY; 267 268 screenToStageCoordinates(tempCoords.set(screenX, screenY)); 269 270 InputEvent event = Pools.obtain(InputEvent.class); 271 event.setType(Type.touchDown); 272 event.setStage(this); 273 event.setStageX(tempCoords.x); 274 event.setStageY(tempCoords.y); 275 event.setPointer(pointer); 276 event.setButton(button); 277 278 Actor target = hit(tempCoords.x, tempCoords.y, true); 279 if (target == null) { 280 if (root.getTouchable() == Touchable.enabled) root.fire(event); 281 } else { 282 target.fire(event); 283 } 284 285 boolean handled = event.isHandled(); 286 Pools.free(event); 287 return handled; 288 } 289 290 /** Applies a touch moved event to the stage and returns true if an actor in the scene {@link Event#handle() handled} the event. 291 * Only {@link InputListener listeners} that returned true for touchDown will receive this event. */ 292 public boolean touchDragged (int screenX, int screenY, int pointer) { 293 pointerScreenX[pointer] = screenX; 294 pointerScreenY[pointer] = screenY; 295 mouseScreenX = screenX; 296 mouseScreenY = screenY; 297 298 if (touchFocuses.size == 0) return false; 299 300 screenToStageCoordinates(tempCoords.set(screenX, screenY)); 301 302 InputEvent event = Pools.obtain(InputEvent.class); 303 event.setType(Type.touchDragged); 304 event.setStage(this); 305 event.setStageX(tempCoords.x); 306 event.setStageY(tempCoords.y); 307 event.setPointer(pointer); 308 309 SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses; 310 TouchFocus[] focuses = touchFocuses.begin(); 311 for (int i = 0, n = touchFocuses.size; i < n; i++) { 312 TouchFocus focus = focuses[i]; 313 if (focus.pointer != pointer) continue; 314 if (!touchFocuses.contains(focus, true)) continue; // Touch focus already gone. 315 event.setTarget(focus.target); 316 event.setListenerActor(focus.listenerActor); 317 if (focus.listener.handle(event)) event.handle(); 318 } 319 touchFocuses.end(); 320 321 boolean handled = event.isHandled(); 322 Pools.free(event); 323 return handled; 324 } 325 326 /** Applies a touch up event to the stage and returns true if an actor in the scene {@link Event#handle() handled} the event. 327 * Only {@link InputListener listeners} that returned true for touchDown will receive this event. */ 328 public boolean touchUp (int screenX, int screenY, int pointer, int button) { 329 pointerTouched[pointer] = false; 330 pointerScreenX[pointer] = screenX; 331 pointerScreenY[pointer] = screenY; 332 333 if (touchFocuses.size == 0) return false; 334 335 screenToStageCoordinates(tempCoords.set(screenX, screenY)); 336 337 InputEvent event = Pools.obtain(InputEvent.class); 338 event.setType(Type.touchUp); 339 event.setStage(this); 340 event.setStageX(tempCoords.x); 341 event.setStageY(tempCoords.y); 342 event.setPointer(pointer); 343 event.setButton(button); 344 345 SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses; 346 TouchFocus[] focuses = touchFocuses.begin(); 347 for (int i = 0, n = touchFocuses.size; i < n; i++) { 348 TouchFocus focus = focuses[i]; 349 if (focus.pointer != pointer || focus.button != button) continue; 350 if (!touchFocuses.removeValue(focus, true)) continue; // Touch focus already gone. 351 event.setTarget(focus.target); 352 event.setListenerActor(focus.listenerActor); 353 if (focus.listener.handle(event)) event.handle(); 354 Pools.free(focus); 355 } 356 touchFocuses.end(); 357 358 boolean handled = event.isHandled(); 359 Pools.free(event); 360 return handled; 361 } 362 363 /** Applies a mouse moved event to the stage and returns true if an actor in the scene {@link Event#handle() handled} the event. 364 * This event only occurs on the desktop. */ 365 public boolean mouseMoved (int screenX, int screenY) { 366 if (screenX < viewport.getScreenX() || screenX >= viewport.getScreenX() + viewport.getScreenWidth()) return false; 367 if (Gdx.graphics.getHeight() - screenY < viewport.getScreenY() 368 || Gdx.graphics.getHeight() - screenY >= viewport.getScreenY() + viewport.getScreenHeight()) return false; 369 370 mouseScreenX = screenX; 371 mouseScreenY = screenY; 372 373 screenToStageCoordinates(tempCoords.set(screenX, screenY)); 374 375 InputEvent event = Pools.obtain(InputEvent.class); 376 event.setStage(this); 377 event.setType(Type.mouseMoved); 378 event.setStageX(tempCoords.x); 379 event.setStageY(tempCoords.y); 380 381 Actor target = hit(tempCoords.x, tempCoords.y, true); 382 if (target == null) target = root; 383 384 target.fire(event); 385 boolean handled = event.isHandled(); 386 Pools.free(event); 387 return handled; 388 } 389 390 /** Applies a mouse scroll event to the stage and returns true if an actor in the scene {@link Event#handle() handled} the 391 * event. This event only occurs on the desktop. */ 392 public boolean scrolled (int amount) { 393 Actor target = scrollFocus == null ? root : scrollFocus; 394 395 screenToStageCoordinates(tempCoords.set(mouseScreenX, mouseScreenY)); 396 397 InputEvent event = Pools.obtain(InputEvent.class); 398 event.setStage(this); 399 event.setType(InputEvent.Type.scrolled); 400 event.setScrollAmount(amount); 401 event.setStageX(tempCoords.x); 402 event.setStageY(tempCoords.y); 403 target.fire(event); 404 boolean handled = event.isHandled(); 405 Pools.free(event); 406 return handled; 407 } 408 409 /** Applies a key down event to the actor that has {@link Stage#setKeyboardFocus(Actor) keyboard focus}, if any, and returns 410 * true if the event was {@link Event#handle() handled}. */ 411 public boolean keyDown (int keyCode) { 412 Actor target = keyboardFocus == null ? root : keyboardFocus; 413 InputEvent event = Pools.obtain(InputEvent.class); 414 event.setStage(this); 415 event.setType(InputEvent.Type.keyDown); 416 event.setKeyCode(keyCode); 417 target.fire(event); 418 boolean handled = event.isHandled(); 419 Pools.free(event); 420 return handled; 421 } 422 423 /** Applies a key up event to the actor that has {@link Stage#setKeyboardFocus(Actor) keyboard focus}, if any, and returns true 424 * if the event was {@link Event#handle() handled}. */ 425 public boolean keyUp (int keyCode) { 426 Actor target = keyboardFocus == null ? root : keyboardFocus; 427 InputEvent event = Pools.obtain(InputEvent.class); 428 event.setStage(this); 429 event.setType(InputEvent.Type.keyUp); 430 event.setKeyCode(keyCode); 431 target.fire(event); 432 boolean handled = event.isHandled(); 433 Pools.free(event); 434 return handled; 435 } 436 437 /** Applies a key typed event to the actor that has {@link Stage#setKeyboardFocus(Actor) keyboard focus}, if any, and returns 438 * true if the event was {@link Event#handle() handled}. */ 439 public boolean keyTyped (char character) { 440 Actor target = keyboardFocus == null ? root : keyboardFocus; 441 InputEvent event = Pools.obtain(InputEvent.class); 442 event.setStage(this); 443 event.setType(InputEvent.Type.keyTyped); 444 event.setCharacter(character); 445 target.fire(event); 446 boolean handled = event.isHandled(); 447 Pools.free(event); 448 return handled; 449 } 450 451 /** Adds the listener to be notified for all touchDragged and touchUp events for the specified pointer and button. The actor 452 * will be used as the {@link Event#getListenerActor() listener actor} and {@link Event#getTarget() target}. */ 453 public void addTouchFocus (EventListener listener, Actor listenerActor, Actor target, int pointer, int button) { 454 TouchFocus focus = Pools.obtain(TouchFocus.class); 455 focus.listenerActor = listenerActor; 456 focus.target = target; 457 focus.listener = listener; 458 focus.pointer = pointer; 459 focus.button = button; 460 touchFocuses.add(focus); 461 } 462 463 /** Removes the listener from being notified for all touchDragged and touchUp events for the specified pointer and button. Note 464 * the listener may never receive a touchUp event if this method is used. */ 465 public void removeTouchFocus (EventListener listener, Actor listenerActor, Actor target, int pointer, int button) { 466 SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses; 467 for (int i = touchFocuses.size - 1; i >= 0; i--) { 468 TouchFocus focus = touchFocuses.get(i); 469 if (focus.listener == listener && focus.listenerActor == listenerActor && focus.target == target 470 && focus.pointer == pointer && focus.button == button) { 471 touchFocuses.removeIndex(i); 472 Pools.free(focus); 473 } 474 } 475 } 476 477 /** Cancels touch focus for the specified actor. 478 * @see #cancelTouchFocus() */ 479 public void cancelTouchFocus (Actor actor) { 480 InputEvent event = Pools.obtain(InputEvent.class); 481 event.setStage(this); 482 event.setType(InputEvent.Type.touchUp); 483 event.setStageX(Integer.MIN_VALUE); 484 event.setStageY(Integer.MIN_VALUE); 485 486 // Cancel all current touch focuses for the specified listener, allowing for concurrent modification, and never cancel the 487 // same focus twice. 488 SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses; 489 TouchFocus[] items = touchFocuses.begin(); 490 for (int i = 0, n = touchFocuses.size; i < n; i++) { 491 TouchFocus focus = items[i]; 492 if (focus.listenerActor != actor) continue; 493 if (!touchFocuses.removeValue(focus, true)) continue; // Touch focus already gone. 494 event.setTarget(focus.target); 495 event.setListenerActor(focus.listenerActor); 496 event.setPointer(focus.pointer); 497 event.setButton(focus.button); 498 focus.listener.handle(event); 499 // Cannot return TouchFocus to pool, as it may still be in use (eg if cancelTouchFocus is called from touchDragged). 500 } 501 touchFocuses.end(); 502 503 Pools.free(event); 504 } 505 506 /** Sends a touchUp event to all listeners that are registered to receive touchDragged and touchUp events and removes their 507 * touch focus. This method removes all touch focus listeners, but sends a touchUp event so that the state of the listeners 508 * remains consistent (listeners typically expect to receive touchUp eventually). The location of the touchUp is 509 * {@link Integer#MIN_VALUE}. Listeners can use {@link InputEvent#isTouchFocusCancel()} to ignore this event if needed. */ 510 public void cancelTouchFocus () { 511 cancelTouchFocusExcept(null, null); 512 } 513 514 /** Cancels touch focus for all listeners except the specified listener. 515 * @see #cancelTouchFocus() */ 516 public void cancelTouchFocusExcept (EventListener exceptListener, Actor exceptActor) { 517 InputEvent event = Pools.obtain(InputEvent.class); 518 event.setStage(this); 519 event.setType(InputEvent.Type.touchUp); 520 event.setStageX(Integer.MIN_VALUE); 521 event.setStageY(Integer.MIN_VALUE); 522 523 // Cancel all current touch focuses except for the specified listener, allowing for concurrent modification, and never 524 // cancel the same focus twice. 525 SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses; 526 TouchFocus[] items = touchFocuses.begin(); 527 for (int i = 0, n = touchFocuses.size; i < n; i++) { 528 TouchFocus focus = items[i]; 529 if (focus.listener == exceptListener && focus.listenerActor == exceptActor) continue; 530 if (!touchFocuses.removeValue(focus, true)) continue; // Touch focus already gone. 531 event.setTarget(focus.target); 532 event.setListenerActor(focus.listenerActor); 533 event.setPointer(focus.pointer); 534 event.setButton(focus.button); 535 focus.listener.handle(event); 536 // Cannot return TouchFocus to pool, as it may still be in use (eg if cancelTouchFocus is called from touchDragged). 537 } 538 touchFocuses.end(); 539 540 Pools.free(event); 541 } 542 543 /** Adds an actor to the root of the stage. 544 * @see Group#addActor(Actor) */ 545 public void addActor (Actor actor) { 546 root.addActor(actor); 547 } 548 549 /** Adds an action to the root of the stage. 550 * @see Group#addAction(Action) */ 551 public void addAction (Action action) { 552 root.addAction(action); 553 } 554 555 /** Returns the root's child actors. 556 * @see Group#getChildren() */ 557 public Array<Actor> getActors () { 558 return root.children; 559 } 560 561 /** Adds a listener to the root. 562 * @see Actor#addListener(EventListener) */ 563 public boolean addListener (EventListener listener) { 564 return root.addListener(listener); 565 } 566 567 /** Removes a listener from the root. 568 * @see Actor#removeListener(EventListener) */ 569 public boolean removeListener (EventListener listener) { 570 return root.removeListener(listener); 571 } 572 573 /** Adds a capture listener to the root. 574 * @see Actor#addCaptureListener(EventListener) */ 575 public boolean addCaptureListener (EventListener listener) { 576 return root.addCaptureListener(listener); 577 } 578 579 /** Removes a listener from the root. 580 * @see Actor#removeCaptureListener(EventListener) */ 581 public boolean removeCaptureListener (EventListener listener) { 582 return root.removeCaptureListener(listener); 583 } 584 585 /** Removes the root's children, actions, and listeners. */ 586 public void clear () { 587 unfocusAll(); 588 root.clear(); 589 } 590 591 /** Removes the touch, keyboard, and scroll focused actors. */ 592 public void unfocusAll () { 593 scrollFocus = null; 594 keyboardFocus = null; 595 cancelTouchFocus(); 596 } 597 598 /** Removes the touch, keyboard, and scroll focus for the specified actor and any descendants. */ 599 public void unfocus (Actor actor) { 600 cancelTouchFocus(actor); 601 if (scrollFocus != null && scrollFocus.isDescendantOf(actor)) scrollFocus = null; 602 if (keyboardFocus != null && keyboardFocus.isDescendantOf(actor)) keyboardFocus = null; 603 } 604 605 /** Sets the actor that will receive key events. 606 * @param actor May be null. */ 607 public void setKeyboardFocus (Actor actor) { 608 if (keyboardFocus == actor) return; 609 FocusEvent event = Pools.obtain(FocusEvent.class); 610 event.setStage(this); 611 event.setType(FocusEvent.Type.keyboard); 612 Actor oldKeyboardFocus = keyboardFocus; 613 if (oldKeyboardFocus != null) { 614 event.setFocused(false); 615 event.setRelatedActor(actor); 616 oldKeyboardFocus.fire(event); 617 } 618 if (!event.isCancelled()) { 619 keyboardFocus = actor; 620 if (actor != null) { 621 event.setFocused(true); 622 event.setRelatedActor(oldKeyboardFocus); 623 actor.fire(event); 624 if (event.isCancelled()) setKeyboardFocus(oldKeyboardFocus); 625 } 626 } 627 Pools.free(event); 628 } 629 630 /** Gets the actor that will receive key events. 631 * @return May be null. */ 632 public Actor getKeyboardFocus () { 633 return keyboardFocus; 634 } 635 636 /** Sets the actor that will receive scroll events. 637 * @param actor May be null. */ 638 public void setScrollFocus (Actor actor) { 639 if (scrollFocus == actor) return; 640 FocusEvent event = Pools.obtain(FocusEvent.class); 641 event.setStage(this); 642 event.setType(FocusEvent.Type.scroll); 643 Actor oldScrollFocus = scrollFocus; 644 if (oldScrollFocus != null) { 645 event.setFocused(false); 646 event.setRelatedActor(actor); 647 oldScrollFocus.fire(event); 648 } 649 if (!event.isCancelled()) { 650 scrollFocus = actor; 651 if (actor != null) { 652 event.setFocused(true); 653 event.setRelatedActor(oldScrollFocus); 654 actor.fire(event); 655 if (event.isCancelled()) setScrollFocus(oldScrollFocus); 656 } 657 } 658 Pools.free(event); 659 } 660 661 /** Gets the actor that will receive scroll events. 662 * @return May be null. */ 663 public Actor getScrollFocus () { 664 return scrollFocus; 665 } 666 667 public Batch getBatch () { 668 return batch; 669 } 670 671 public Viewport getViewport () { 672 return viewport; 673 } 674 675 public void setViewport (Viewport viewport) { 676 this.viewport = viewport; 677 } 678 679 /** The viewport's world width. */ 680 public float getWidth () { 681 return viewport.getWorldWidth(); 682 } 683 684 /** The viewport's world height. */ 685 public float getHeight () { 686 return viewport.getWorldHeight(); 687 } 688 689 /** The viewport's camera. */ 690 public Camera getCamera () { 691 return viewport.getCamera(); 692 } 693 694 /** Returns the root group which holds all actors in the stage. */ 695 public Group getRoot () { 696 return root; 697 } 698 699 /** Returns the {@link Actor} at the specified location in stage coordinates. Hit testing is performed in the order the actors 700 * were inserted into the stage, last inserted actors being tested first. To get stage coordinates from screen coordinates, use 701 * {@link #screenToStageCoordinates(Vector2)}. 702 * @param touchable If true, the hit detection will respect the {@link Actor#setTouchable(Touchable) touchability}. 703 * @return May be null if no actor was hit. */ 704 public Actor hit (float stageX, float stageY, boolean touchable) { 705 root.parentToLocalCoordinates(tempCoords.set(stageX, stageY)); 706 return root.hit(tempCoords.x, tempCoords.y, touchable); 707 } 708 709 /** Transforms the screen coordinates to stage coordinates. 710 * @param screenCoords Input screen coordinates and output for resulting stage coordinates. */ 711 public Vector2 screenToStageCoordinates (Vector2 screenCoords) { 712 viewport.unproject(screenCoords); 713 return screenCoords; 714 } 715 716 /** Transforms the stage coordinates to screen coordinates. 717 * @param stageCoords Input stage coordinates and output for resulting screen coordinates. */ 718 public Vector2 stageToScreenCoordinates (Vector2 stageCoords) { 719 viewport.project(stageCoords); 720 stageCoords.y = viewport.getScreenHeight() - stageCoords.y; 721 return stageCoords; 722 } 723 724 /** Transforms the coordinates to screen coordinates. The coordinates can be anywhere in the stage since the transform matrix 725 * describes how to convert them. The transform matrix is typically obtained from {@link Batch#getTransformMatrix()} during 726 * {@link Actor#draw(Batch, float)}. 727 * @see Actor#localToStageCoordinates(Vector2) */ 728 public Vector2 toScreenCoordinates (Vector2 coords, Matrix4 transformMatrix) { 729 return viewport.toScreenCoordinates(coords, transformMatrix); 730 } 731 732 /** Calculates window scissor coordinates from local coordinates using the batch's current transformation matrix. 733 * @see ScissorStack#calculateScissors(Camera, float, float, float, float, Matrix4, Rectangle, Rectangle) */ 734 public void calculateScissors (Rectangle localRect, Rectangle scissorRect) { 735 viewport.calculateScissors(batch.getTransformMatrix(), localRect, scissorRect); 736 Matrix4 transformMatrix; 737 if (debugShapes != null && debugShapes.isDrawing()) 738 transformMatrix = debugShapes.getTransformMatrix(); 739 else 740 transformMatrix = batch.getTransformMatrix(); 741 viewport.calculateScissors(transformMatrix, localRect, scissorRect); 742 } 743 744 /** If true, any actions executed during a call to {@link #act()}) will result in a call to {@link Graphics#requestRendering()}. 745 * Widgets that animate or otherwise require additional rendering may check this setting before calling 746 * {@link Graphics#requestRendering()}. Default is true. */ 747 public void setActionsRequestRendering (boolean actionsRequestRendering) { 748 this.actionsRequestRendering = actionsRequestRendering; 749 } 750 751 public boolean getActionsRequestRendering () { 752 return actionsRequestRendering; 753 } 754 755 /** The default color that can be used by actors to draw debug lines. */ 756 public Color getDebugColor () { 757 return debugColor; 758 } 759 760 /** If true, debug lines are shown for actors even when {@link Actor#isVisible()} is false. */ 761 public void setDebugInvisible (boolean debugInvisible) { 762 this.debugInvisible = debugInvisible; 763 } 764 765 /** If true, debug lines are shown for all actors. */ 766 public void setDebugAll (boolean debugAll) { 767 if (this.debugAll == debugAll) return; 768 this.debugAll = debugAll; 769 if (debugAll) 770 debug = true; 771 else 772 root.setDebug(false, true); 773 } 774 775 /** If true, debug is enabled only for the actor under the mouse. Can be combined with {@link #setDebugAll(boolean)}. */ 776 public void setDebugUnderMouse (boolean debugUnderMouse) { 777 if (this.debugUnderMouse == debugUnderMouse) return; 778 this.debugUnderMouse = debugUnderMouse; 779 if (debugUnderMouse) 780 debug = true; 781 else 782 root.setDebug(false, true); 783 } 784 785 /** If true, debug is enabled only for the parent of the actor under the mouse. Can be combined with 786 * {@link #setDebugAll(boolean)}. */ 787 public void setDebugParentUnderMouse (boolean debugParentUnderMouse) { 788 if (this.debugParentUnderMouse == debugParentUnderMouse) return; 789 this.debugParentUnderMouse = debugParentUnderMouse; 790 if (debugParentUnderMouse) 791 debug = true; 792 else 793 root.setDebug(false, true); 794 } 795 796 /** If not {@link Debug#none}, debug is enabled only for the first ascendant of the actor under the mouse that is a table. Can 797 * be combined with {@link #setDebugAll(boolean)}. 798 * @param debugTableUnderMouse May be null for {@link Debug#none}. */ 799 public void setDebugTableUnderMouse (Debug debugTableUnderMouse) { 800 if (debugTableUnderMouse == null) debugTableUnderMouse = Debug.none; 801 if (this.debugTableUnderMouse == debugTableUnderMouse) return; 802 this.debugTableUnderMouse = debugTableUnderMouse; 803 if (debugTableUnderMouse != Debug.none) 804 debug = true; 805 else 806 root.setDebug(false, true); 807 } 808 809 /** If true, debug is enabled only for the first ascendant of the actor under the mouse that is a table. Can be combined with 810 * {@link #setDebugAll(boolean)}. */ 811 public void setDebugTableUnderMouse (boolean debugTableUnderMouse) { 812 setDebugTableUnderMouse(debugTableUnderMouse ? Debug.all : Debug.none); 813 } 814 815 public void dispose () { 816 clear(); 817 if (ownsBatch) batch.dispose(); 818 } 819 820 /** Internal class for managing touch focus. Public only for GWT. 821 * @author Nathan Sweet */ 822 public static final class TouchFocus implements Poolable { 823 EventListener listener; 824 Actor listenerActor, target; 825 int pointer, button; 826 827 public void reset () { 828 listenerActor = null; 829 listener = null; 830 target = null; 831 } 832 } 833 } 834