1 /*******************************************************************************
\r
2 * Copyright 2011 See AUTHORS file.
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
15 ******************************************************************************/
\r
17 package com.badlogic.gdx.scenes.scene2d;
\r
19 import com.badlogic.gdx.graphics.Color;
\r
20 import com.badlogic.gdx.graphics.g2d.SpriteBatch;
\r
21 import com.badlogic.gdx.math.MathUtils;
\r
22 import com.badlogic.gdx.math.Rectangle;
\r
23 import com.badlogic.gdx.math.Vector2;
\r
24 import com.badlogic.gdx.scenes.scene2d.InputEvent.Type;
\r
25 import com.badlogic.gdx.scenes.scene2d.actions.Actions;
\r
26 import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener;
\r
27 import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
\r
28 import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
\r
29 import com.badlogic.gdx.utils.Array;
\r
30 import com.badlogic.gdx.utils.DelayedRemovalArray;
\r
31 import com.badlogic.gdx.utils.GdxRuntimeException;
\r
32 import com.badlogic.gdx.utils.Pools;
\r
34 /** 2D scene graph node. An actor has a position, rectangular size, origin, scale, rotation, Z index, and color. The position
\r
35 * corresponds to the unrotated, unscaled bottom left corner of the actor. The position is relative to the actor's parent. The
\r
36 * origin is relative to the position and is used for scale and rotation.
\r
38 * An actor has a list of in-progress {@link Action actions} that are applied to the actor (over time). These are generally used to
\r
39 * change the presentation of the actor (moving it, resizing it, etc). See {@link #act(float)} and {@link Action}.
\r
41 * An actor has two kinds of listeners associated with it: "capture" and regular. The listeners are notified of events the actor
\r
42 * or its children receive. The capture listeners are designed to allow a parent or container actor to hide events from child
\r
43 * actors. The regular listeners are designed to allow an actor to respond to events that have been delivered. See {@link #fire}
\r
46 * An {@link InputListener} can receive all the basic input events, and more complex listeners (like {@link ClickListener} and
\r
47 * {@link ActorGestureListener}) can listen for and combine primitive events and recognize complex interactions like multi-click
\r
51 * @author Nathan Sweet */
\r
52 public class Actor {
\r
53 private Stage stage;
\r
54 private Group parent;
\r
55 private final DelayedRemovalArray<EventListener> listeners = new DelayedRemovalArray(0);
\r
56 private final DelayedRemovalArray<EventListener> captureListeners = new DelayedRemovalArray(0);
\r
57 private final Array<Action> actions = new Array(0);
\r
59 private String name;
\r
60 private Touchable touchable = Touchable.enabled;
\r
61 private boolean visible = true;
\r
63 private float width, height;
\r
64 private float originX, originY;
\r
65 private float scaleX = 1, scaleY = 1;
\r
66 private float rotation;
\r
67 private final Color color = new Color(1, 1, 1, 1);
\r
69 /** Draws the actor. The SpriteBatch is configured to draw in the parent's coordinate system.
\r
70 * {@link SpriteBatch#draw(com.badlogic.gdx.graphics.g2d.TextureRegion, float, float, float, float, float, float, float, float, float)
\r
71 * This draw method} is convenient to draw a rotated and scaled TextureRegion. {@link SpriteBatch#begin()} has already been
\r
72 * called on the SpriteBatch. If {@link SpriteBatch#end()} is called to draw without the SpriteBatch then
\r
73 * {@link SpriteBatch#begin()} must be called before the method returns.
\r
75 * The default implementation does nothing.
\r
76 * @param parentAlpha Should be multiplied with the actor's alpha, allowing a parent's alpha to affect all children. */
\r
77 public void draw (SpriteBatch batch, float parentAlpha) {
\r
80 /** Updates the actor based on time. Typically this is called each frame by {@link Stage#act(float)}.
\r
82 * The default implementation calls {@link Action#act(float)} on each action and removes actions that are complete.
\r
83 * @param delta Time in seconds since the last frame. */
\r
84 public void act (float delta) {
\r
85 for (int i = 0, n = actions.size; i < n; i++) {
\r
86 Action action = actions.get(i);
\r
87 if (action.act(delta)) {
\r
88 actions.removeIndex(i);
\r
89 action.setActor(null);
\r
96 /** Sets this actor as the event {@link Event#setTarget(Actor) target} and propagates the event to this actor and ancestor
\r
97 * actors as necessary. If this actor is not in the stage, the stage must be set before calling this method.
\r
99 * Events are fired in 2 phases.
\r
101 * <li>The first phase (the "capture" phase) notifies listeners on each actor starting at the root and propagating downward to
\r
102 * (and including) this actor.</li>
\r
103 * <li>The second phase notifies listeners on each actor starting at this actor and, if {@link Event#getBubbles()} is true,
\r
104 * propagating upward to the root.</li>
\r
106 * If the event is {@link Event#stop() stopped} at any time, it will not propagate to the next actor.
\r
107 * @return true if the event was {@link Event#cancel() cancelled}. */
\r
108 public boolean fire (Event event) {
\r
109 if (event.getStage() == null) event.setStage(getStage());
\r
110 event.setTarget(this);
\r
112 // Collect ancestors so event propagation is unaffected by hierarchy changes.
\r
113 Array<Group> ancestors = Pools.obtain(Array.class);
\r
114 Group parent = getParent();
\r
115 while (parent != null) {
\r
116 ancestors.add(parent);
\r
117 parent = parent.getParent();
\r
121 // Notify all parent capture listeners, starting at the root. Ancestors may stop an event before children receive it.
\r
122 for (int i = ancestors.size - 1; i >= 0; i--) {
\r
123 Group currentTarget = ancestors.get(i);
\r
124 currentTarget.notify(event, true);
\r
125 if (event.isStopped()) return event.isCancelled();
\r
128 // Notify the target capture listeners.
\r
129 notify(event, true);
\r
130 if (event.isStopped()) return event.isCancelled();
\r
132 // Notify the target listeners.
\r
133 notify(event, false);
\r
134 if (!event.getBubbles()) return event.isCancelled();
\r
135 if (event.isStopped()) return event.isCancelled();
\r
137 // Notify all parent listeners, starting at the target. Children may stop an event before ancestors receive it.
\r
138 for (int i = 0, n = ancestors.size; i < n; i++) {
\r
139 ancestors.get(i).notify(event, false);
\r
140 if (event.isStopped()) return event.isCancelled();
\r
143 return event.isCancelled();
\r
146 Pools.free(ancestors);
\r
150 /** Notifies this actor's listeners of the event. The event is not propagated to any parents. Before notifying the listeners,
\r
151 * this actor is set as the {@link Event#getListenerActor() listener actor}. The event {@link Event#setTarget(Actor) target}
\r
152 * must be set before calling this method. If this actor is not in the stage, the stage must be set before calling this method.
\r
153 * @param capture If true, the capture listeners will be notified instead of the regular listeners.
\r
154 * @return true of the event was {@link Event#cancel() cancelled}. */
\r
155 public boolean notify (Event event, boolean capture) {
\r
156 if (event.getTarget() == null) throw new IllegalArgumentException("The event target cannot be null.");
\r
158 DelayedRemovalArray<EventListener> listeners = capture ? captureListeners : this.listeners;
\r
159 if (listeners.size == 0) return event.isCancelled();
\r
161 event.setListenerActor(this);
\r
162 event.setCapture(capture);
\r
163 if (event.getStage() == null) event.setStage(stage);
\r
166 for (int i = 0, n = listeners.size; i < n; i++) {
\r
167 EventListener listener = listeners.get(i);
\r
168 if (listener.handle(event)) {
\r
170 if (event instanceof InputEvent) {
\r
171 InputEvent inputEvent = (InputEvent)event;
\r
172 if (inputEvent.getType() == Type.touchDown) {
\r
173 event.getStage().addTouchFocus(listener, this, inputEvent.getTarget(), inputEvent.getPointer(),
\r
174 inputEvent.getButton());
\r
181 return event.isCancelled();
\r
184 /** Returns the deepest actor that contains the specified point and is {@link #getTouchable() touchable} and
\r
185 * {@link #isVisible() visible}, or null if no actor was hit. The point is specified in the actor's local coordinate system (0,0
\r
186 * is the bottom left of the actor and width,height is the upper right).
\r
188 * This method is used to delegate touchDown, mouse, and enter/exit events. If this method returns null, those events will not
\r
189 * occur on this Actor.
\r
191 * The default implementation returns this actor if the point is within this actor's bounds.
\r
193 * @param touchable If true, the hit detection will respect the {@link #setTouchable(Touchable) touchability}.
\r
194 * @see Touchable */
\r
195 public Actor hit (float x, float y, boolean touchable) {
\r
196 if (touchable && this.touchable != Touchable.enabled) return null;
\r
197 return x >= 0 && x < width && y >= 0 && y < height ? this : null;
\r
200 /** Removes this actor from its parent, if it has a parent.
\r
201 * @see Group#removeActor(Actor) */
\r
202 public boolean remove () {
\r
203 if (parent != null) return parent.removeActor(this);
\r
207 /** Add a listener to receive events that {@link #hit(float, float, boolean) hit} this actor. See {@link #fire(Event)}.
\r
209 * @see InputListener
\r
210 * @see ClickListener */
\r
211 public boolean addListener (EventListener listener) {
\r
212 if (!listeners.contains(listener, true)) {
\r
213 listeners.add(listener);
\r
219 public boolean removeListener (EventListener listener) {
\r
220 return listeners.removeValue(listener, true);
\r
223 public Array<EventListener> getListeners () {
\r
227 /** Adds a listener that is only notified during the capture phase.
\r
228 * @see #fire(Event) */
\r
229 public boolean addCaptureListener (EventListener listener) {
\r
230 if (!captureListeners.contains(listener, true)) captureListeners.add(listener);
\r
234 public boolean removeCaptureListener (EventListener listener) {
\r
235 return captureListeners.removeValue(listener, true);
\r
238 public Array<EventListener> getCaptureListeners () {
\r
239 return captureListeners;
\r
242 public void addAction (Action action) {
\r
243 action.setActor(this);
\r
244 actions.add(action);
\r
247 public void removeAction (Action action) {
\r
248 if (actions.removeValue(action, true)) action.setActor(null);
\r
251 public Array<Action> getActions () {
\r
255 /** Removes all actions on this actor. */
\r
256 public void clearActions () {
\r
257 for (int i = actions.size - 1; i >= 0; i--)
\r
258 actions.get(i).setActor(null);
\r
262 /** Removes all listeners on this actor. */
\r
263 public void clearListeners () {
\r
265 captureListeners.clear();
\r
268 /** Removes all actions and listeners on this actor. */
\r
269 public void clear () {
\r
274 /** Returns the stage that this actor is currently in, or null if not in a stage. */
\r
275 public Stage getStage () {
\r
279 /** Called by the framework when this actor or any parent is added to a group that is in the stage.
\r
280 * @param stage May be null if the actor or any parent is no longer in a stage. */
\r
281 protected void setStage (Stage stage) {
\r
282 this.stage = stage;
\r
285 /** Returns true if this actor is the same as or is the descendant of the specified actor. */
\r
286 public boolean isDescendantOf (Actor actor) {
\r
287 if (actor == null) throw new IllegalArgumentException("actor cannot be null.");
\r
288 Actor parent = this;
\r
290 if (parent == null) return false;
\r
291 if (parent == actor) return true;
\r
292 parent = parent.getParent();
\r
296 /** Returns true if this actor is the same as or is the ascendant of the specified actor. */
\r
297 public boolean isAscendantOf (Actor actor) {
\r
298 if (actor == null) throw new IllegalArgumentException("actor cannot be null.");
\r
300 if (actor == null) return false;
\r
301 if (actor == this) return true;
\r
302 actor = actor.getParent();
\r
306 /** Returns true if the actor's parent is not null. */
\r
307 public boolean hasParent () {
\r
308 return parent != null;
\r
311 /** Returns the parent actor, or null if not in a stage. */
\r
312 public Group getParent () {
\r
316 /** Called by the framework when an actor is added to or removed from a group.
\r
317 * @param parent May be null if the actor has been removed from the parent. */
\r
318 protected void setParent (Group parent) {
\r
319 this.parent = parent;
\r
322 public Touchable getTouchable () {
\r
326 /** Determines how touch events are distributed to this actor. Default is {@link Touchable#enabled}. */
\r
327 public void setTouchable (Touchable touchable) {
\r
328 this.touchable = touchable;
\r
331 public boolean isVisible () {
\r
335 /** If false, the actor will not be drawn and will not receive touch events. Default is true. */
\r
336 public void setVisible (boolean visible) {
\r
337 this.visible = visible;
\r
340 public float getX () {
\r
344 public void setX (float x) {
\r
348 public float getY () {
\r
352 public void setY (float y) {
\r
356 /** Sets the x and y. */
\r
357 public void setPosition (float x, float y) {
\r
362 public void translate (float x, float y) {
\r
367 public float getWidth () {
\r
371 public void setWidth (float width) {
\r
372 this.width = width;
\r
375 public float getHeight () {
\r
379 public void setHeight (float height) {
\r
380 this.height = height;
\r
383 /** Returns y plus height. */
\r
384 public float getTop () {
\r
385 return getY() + getHeight();
\r
388 /** Returns x plus width. */
\r
389 public float getRight () {
\r
390 return getX() + getWidth();
\r
393 /** Sets the width and height. */
\r
394 public void setSize (float width, float height) {
\r
399 /** Adds the specified size to the current size. */
\r
400 public void size (float size) {
\r
401 setWidth(width + size);
\r
402 setHeight(height + size);
\r
405 /** Adds the specified size to the current size. */
\r
406 public void size (float width, float height) {
\r
407 setWidth(this.width + width);
\r
408 setHeight(this.height + height);
\r
411 /** Set bounds the x, y, width, and height. */
\r
412 public void setBounds (float x, float y, float width, float height) {
\r
419 public float getOriginX () {
\r
423 public void setOriginX (float originX) {
\r
424 this.originX = originX;
\r
427 public float getOriginY () {
\r
431 public void setOriginY (float originY) {
\r
432 this.originY = originY;
\r
435 /** Sets the originx and originy. */
\r
436 public void setOrigin (float originX, float originY) {
\r
437 setOriginX(originX);
\r
438 setOriginY(originY);
\r
441 public float getScaleX () {
\r
445 public void setScaleX (float scaleX) {
\r
446 this.scaleX = scaleX;
\r
449 public float getScaleY () {
\r
453 public void setScaleY (float scaleY) {
\r
454 this.scaleY = scaleY;
\r
457 /** Sets the scalex and scaley. */
\r
458 public void setScale (float scale) {
\r
463 /** Sets the scalex and scaley. */
\r
464 public void setScale (float scaleX, float scaleY) {
\r
469 /** Adds the specified scale to the current scale. */
\r
470 public void scale (float scale) {
\r
471 setScaleX(scaleX + scale);
\r
472 setScaleY(scaleY + scale);
\r
475 /** Adds the specified scale to the current scale. */
\r
476 public void scale (float scaleX, float scaleY) {
\r
477 setScaleX(this.scaleX + scaleX);
\r
478 setScaleY(this.scaleY + scaleY);
\r
481 public float getRotation () {
\r
485 public void setRotation (float degrees) {
\r
486 this.rotation = degrees;
\r
489 /** Adds the specified rotation to the current rotation. */
\r
490 public void rotate (float amountInDegrees) {
\r
491 setRotation(rotation + amountInDegrees);
\r
494 public void setColor (Color color) {
\r
495 this.color.set(color);
\r
498 public void setColor (float r, float g, float b, float a) {
\r
499 color.set(r, g, b, a);
\r
502 /** Returns the color the actor will be tinted when drawn. The returned instance can be modified to change the color. */
\r
503 public Color getColor () {
\r
507 public String getName () {
\r
511 /** Sets a name for easier identification of the actor in application code.
\r
512 * @see Group#findActor(String) */
\r
513 public void setName (String name) {
\r
517 /** Changes the z-order for this actor so it is in front of all siblings. */
\r
518 public void toFront () {
\r
519 setZIndex(Integer.MAX_VALUE);
\r
522 /** Changes the z-order for this actor so it is in back of all siblings. */
\r
523 public void toBack () {
\r
527 /** Sets the z-index of this actor. The z-index is the index into the parent's {@link Group#getChildren() children}, where a
\r
528 * lower index is below a higher index. Setting a z-index higher than the number of children will move the child to the front.
\r
529 * Setting a z-index less than zero is invalid. */
\r
530 public void setZIndex (int index) {
\r
531 if (index < 0) throw new IllegalArgumentException("ZIndex cannot be < 0.");
\r
532 Group parent = getParent();
\r
533 if (parent == null) return;
\r
534 Array<Actor> children = parent.getChildren();
\r
535 if (children.size == 1) return;
\r
536 if (!children.removeValue(this, true)) return;
\r
537 if (index >= children.size)
\r
538 children.add(this);
\r
540 children.insert(index, this);
\r
543 /** Returns the z-index of this actor.
\r
544 * @see #setZIndex(int) */
\r
545 public int getZIndex () {
\r
546 Group parent = getParent();
\r
547 if (parent == null) return -1;
\r
548 return parent.getChildren().indexOf(this, true);
\r
551 /** Calls {@link #clipBegin(float, float, float, float)} to clip this actor's bounds. */
\r
552 public boolean clipBegin () {
\r
553 return clipBegin(getX(), getY(), getWidth(), getHeight());
\r
556 /** Clips the specified screen aligned rectangle, specified relative to the transform matrix of the stage's SpriteBatch. The
\r
557 * transform matrix and the stage's camera must not have rotational components. Calling this method must be followed by a call
\r
558 * to {@link #clipEnd()} if true is returned.
\r
559 * @return false if the clipping area is zero and no drawing should occur.
\r
560 * @see ScissorStack */
\r
561 public boolean clipBegin (float x, float y, float width, float height) {
\r
562 Rectangle tableBounds = Rectangle.tmp;
\r
565 tableBounds.width = width;
\r
566 tableBounds.height = height;
\r
567 Stage stage = getStage();
\r
568 Rectangle scissorBounds = Pools.obtain(Rectangle.class);
\r
569 ScissorStack.calculateScissors(stage.getCamera(), stage.getSpriteBatch().getTransformMatrix(), tableBounds, scissorBounds);
\r
570 if (ScissorStack.pushScissors(scissorBounds)) return true;
\r
571 Pools.free(scissorBounds);
\r
575 /** Ends clipping begun by {@link #clipBegin(float, float, float, float)}. */
\r
576 public void clipEnd () {
\r
577 Pools.free(ScissorStack.popScissors());
\r
580 /** Transforms the specified point in screen coordinates to the actor's local coordinate system. */
\r
581 public Vector2 screenToLocalCoordinates (Vector2 screenCoords) {
\r
582 Stage stage = getStage();
\r
583 if (stage == null) return screenCoords;
\r
584 return stageToLocalCoordinates(stage.screenToStageCoordinates(screenCoords));
\r
587 /** Transforms the specified point in the stage's coordinates to the actor's local coordinate system. */
\r
588 public Vector2 stageToLocalCoordinates (Vector2 stageCoords) {
\r
589 if (parent == null) return stageCoords;
\r
590 parent.stageToLocalCoordinates(stageCoords);
\r
591 parentToLocalCoordinates(stageCoords);
\r
592 return stageCoords;
\r
595 /** Transforms the specified point in the actor's coordinates to be in the stage's coordinates. Note this method will ONLY work
\r
596 * for screen aligned, unrotated, unscaled actors! */
\r
597 public Vector2 localToStageCoordinates (Vector2 localCoords) {
\r
598 Actor actor = this;
\r
599 while (actor != null) {
\r
600 if (actor.getRotation() != 0 || actor.getScaleX() != 1 || actor.getScaleY() != 1)
\r
601 throw new GdxRuntimeException("Only unrotated and unscaled actors may use this method.");
\r
602 localCoords.x += actor.getX();
\r
603 localCoords.y += actor.getY();
\r
604 actor = actor.getParent();
\r
606 return localCoords;
\r
609 /** Transforms the specified point in the actor's coordinates to be in the parent's coordinates. Note this method will ONLY work
\r
610 * for screen aligned, unrotated, unscaled actors! */
\r
611 public Vector2 localToParentCoordinates (Vector2 localCoords) {
\r
612 if (getRotation() != 0 || getScaleX() != 1 || getScaleY() != 1)
\r
613 throw new GdxRuntimeException("Only unrotated and unscaled actors may use this method.");
\r
614 localCoords.x += getX();
\r
615 localCoords.y += getY();
\r
616 return localCoords;
\r
619 /** Converts coordinates for this actor to those of a parent actor. The ascendant does not need to be a direct parent. */
\r
620 public Vector2 localToAscendantCoordinates (Actor ascendant, Vector2 localCoords) {
\r
621 Actor actor = this;
\r
622 while (actor.getParent() != null) {
\r
623 actor.localToParentCoordinates(localCoords);
\r
624 actor = actor.getParent();
\r
625 if (actor == ascendant) break;
\r
627 return localCoords;
\r
630 /** Converts the coordinates given in the parent's coordinate system to this actor's coordinate system. */
\r
631 public Vector2 parentToLocalCoordinates (Vector2 parentCoords) {
\r
632 final float rotation = getRotation();
\r
633 final float scaleX = getScaleX();
\r
634 final float scaleY = getScaleY();
\r
635 final float childX = getX();
\r
636 final float childY = getY();
\r
638 if (rotation == 0) {
\r
639 if (scaleX == 1 && scaleY == 1) {
\r
640 parentCoords.x -= childX;
\r
641 parentCoords.y -= childY;
\r
643 final float originX = getOriginX();
\r
644 final float originY = getOriginY();
\r
645 if (originX == 0 && originY == 0) {
\r
646 parentCoords.x = (parentCoords.x - childX) / scaleX;
\r
647 parentCoords.y = (parentCoords.y - childY) / scaleY;
\r
649 parentCoords.x = (parentCoords.x - childX - originX) / scaleX + originX;
\r
650 parentCoords.y = (parentCoords.y - childY - originY) / scaleY + originY;
\r
654 final float cos = (float)Math.cos(rotation * MathUtils.degreesToRadians);
\r
655 final float sin = (float)Math.sin(rotation * MathUtils.degreesToRadians);
\r
657 final float originX = getOriginX();
\r
658 final float originY = getOriginY();
\r
660 if (scaleX == 1 && scaleY == 1) {
\r
661 if (originX == 0 && originY == 0) {
\r
662 float tox = parentCoords.x - childX;
\r
663 float toy = parentCoords.y - childY;
\r
665 parentCoords.x = tox * cos + toy * sin;
\r
666 parentCoords.y = tox * -sin + toy * cos;
\r
668 final float worldOriginX = childX + originX;
\r
669 final float worldOriginY = childY + originY;
\r
670 final float fx = -originX;
\r
671 final float fy = -originY;
\r
673 final float x1 = cos * fx - sin * fy + worldOriginX;
\r
674 final float y1 = sin * fx + cos * fy + worldOriginY;
\r
676 final float tox = parentCoords.x - x1;
\r
677 final float toy = parentCoords.y - y1;
\r
679 parentCoords.x = tox * cos + toy * sin;
\r
680 parentCoords.y = tox * -sin + toy * cos;
\r
683 if (originX == 0 && originY == 0) {
\r
684 final float tox = parentCoords.x - childX;
\r
685 final float toy = parentCoords.y - childY;
\r
687 parentCoords.x = (tox * cos + toy * sin) / scaleX;
\r
688 parentCoords.y = (tox * -sin + toy * cos) / scaleY;
\r
690 final float worldOriginX = childX + originX;
\r
691 final float worldOriginY = childY + originY;
\r
692 final float fx = -originX * scaleX;
\r
693 final float fy = -originY * scaleY;
\r
695 final float x1 = cos * fx - sin * fy + worldOriginX;
\r
696 final float y1 = sin * fx + cos * fy + worldOriginY;
\r
698 final float tox = parentCoords.x - x1;
\r
699 final float toy = parentCoords.y - y1;
\r
701 parentCoords.x = (tox * cos + toy * sin) / scaleX;
\r
702 parentCoords.y = (tox * -sin + toy * cos) / scaleY;
\r
706 return parentCoords;
\r
709 public String toString () {
\r
710 String name = this.name;
\r
711 if (name == null) {
\r
712 name = getClass().getName();
\r
713 int dotIndex = name.lastIndexOf('.');
\r
714 if (dotIndex != -1) name = name.substring(dotIndex + 1);
\r