OSDN Git Service

Merge pull request #48 from psiegel/master
[mikumikustudio/libgdx-mikumikustudio.git] / gdx / src / com / badlogic / gdx / scenes / scene2d / Actor.java
1 /*******************************************************************************\r
2  * Copyright 2011 See AUTHORS file.\r
3  * \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
7  * \r
8  *   http://www.apache.org/licenses/LICENSE-2.0\r
9  * \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
16 \r
17 package com.badlogic.gdx.scenes.scene2d;\r
18 \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.Vector2;\r
23 import com.badlogic.gdx.scenes.scene2d.InputEvent.Type;\r
24 import com.badlogic.gdx.utils.Array;\r
25 import com.badlogic.gdx.utils.DelayedRemovalArray;\r
26 import com.badlogic.gdx.utils.GdxRuntimeException;\r
27 import com.badlogic.gdx.utils.Pools;\r
28 \r
29 /** 2D scene graph node. An actor has a position, rectangular size, origin, scale, rotation, and color. The position corresponds to\r
30  * the unrotated, unscaled bottom left corner of the actor. The position is relative to the actor's parent. The origin is relative\r
31  * to the position and is used for scale and rotation.\r
32  * <p>\r
33  * An actor also has a list of actions that can manipulate the actor over time, and a list of listeners that are notified of\r
34  * events the actor receives.\r
35  * @author mzechner\r
36  * @author Nathan Sweet */\r
37 public class Actor {\r
38         private Stage stage;\r
39         private Group parent;\r
40         private final DelayedRemovalArray<EventListener> listeners = new DelayedRemovalArray(0);\r
41         private final DelayedRemovalArray<EventListener> captureListeners = new DelayedRemovalArray(0);\r
42         private final Array<Action> actions = new Array(0);\r
43 \r
44         private String name;\r
45         private Touchable touchable = Touchable.enabled;\r
46         private boolean visible = true;\r
47         private float x, y;\r
48         private float width, height;\r
49         private float originX, originY;\r
50         private float scaleX = 1, scaleY = 1;\r
51         private float rotation;\r
52         private final Color color = new Color(1, 1, 1, 1);\r
53 \r
54         /** Draws the actor. The SpriteBatch is configured to draw in the parent's coordinate system.\r
55          * {@link SpriteBatch#draw(com.badlogic.gdx.graphics.g2d.TextureRegion, float, float, float, float, float, float, float, float, float)\r
56          * This draw method} is convenient to draw a rotated and scaled TextureRegion. {@link SpriteBatch#begin()} has already been\r
57          * called on the SpriteBatch. If {@link SpriteBatch#end()} is called to draw without the SpriteBatch then\r
58          * {@link SpriteBatch#begin()} must be called before the method returns.\r
59          * <p>\r
60          * The default implementation does nothing.\r
61          * @param parentAlpha Should be multipied with the actor's alpha, allowing a parent's alpha to affect all children. */\r
62         public void draw (SpriteBatch batch, float parentAlpha) {\r
63         }\r
64 \r
65         /** Updates the actor based on time. Typically this is called each frame by {@link Stage#act(float)}.\r
66          * <p>\r
67          * The default implementation calls {@link Action#act(float)} on each action and removes actions that are complete.\r
68          * @param delta Time in seconds since the last frame. */\r
69         public void act (float delta) {\r
70                 for (int i = 0, n = actions.size; i < n; i++) {\r
71                         Action action = actions.get(i);\r
72                         if (action.act(delta)) {\r
73                                 actions.removeIndex(i);\r
74                                 action.setActor(null);\r
75                                 i--;\r
76                                 n--;\r
77                         }\r
78                 }\r
79         }\r
80 \r
81         /** Sets this actor as the event {@link Event#setTarget(Actor) target} and propagates the event to this actor and ancestor\r
82          * actors as necessary. If this actor is not in the stage, the stage must be set before calling this method.\r
83          * <p>\r
84          * Events are fired in 2 phases. The first phase notifies listeners on each actor starting at the root and propagating downward\r
85          * to (and including) this actor. The second phase notifes listeners on each actor starting at this actor and, if\r
86          * {@link Event#getBubbles()} is true, propagating upward to the root. If the event is {@link Event#stop() stopped} at any time,\r
87          * it will not propagate to the next actor.\r
88          * @return true of the event was {@link Event#cancel() cancelled}. */\r
89         public boolean fire (Event event) {\r
90                 if (event.getStage() == null) event.setStage(getStage());\r
91                 event.setTarget(this);\r
92 \r
93                 // Collect ancestors so event propagation is unaffected by hierarchy changes.\r
94                 Array<Group> ancestors = Pools.obtain(Array.class);\r
95                 Group parent = getParent();\r
96                 while (parent != null) {\r
97                         ancestors.add(parent);\r
98                         parent = parent.getParent();\r
99                 }\r
100 \r
101                 try {\r
102                         // Notify all parent capture listeners, starting at the root. Ancestors may stop an event before children receive it.\r
103                         for (int i = ancestors.size - 1; i >= 0; i--) {\r
104                                 Group currentTarget = ancestors.get(i);\r
105                                 currentTarget.notify(event, true);\r
106                                 if (event.isStopped()) return event.isCancelled();\r
107                         }\r
108 \r
109                         // Notify the target capture listeners.\r
110                         notify(event, true);\r
111                         if (event.isStopped()) return event.isCancelled();\r
112 \r
113                         // Notify the target listeners.\r
114                         notify(event, false);\r
115                         if (!event.getBubbles()) return event.isCancelled();\r
116                         if (event.isStopped()) return event.isCancelled();\r
117 \r
118                         // Notify all parent listeners, starting at the target. Children may stop an event before ancestors receive it.\r
119                         for (int i = 0, n = ancestors.size; i < n; i++) {\r
120                                 ancestors.get(i).notify(event, false);\r
121                                 if (event.isStopped()) return event.isCancelled();\r
122                         }\r
123 \r
124                         return event.isCancelled();\r
125                 } finally {\r
126                         ancestors.clear();\r
127                         Pools.free(ancestors);\r
128                 }\r
129         }\r
130 \r
131         /** Notifies this actor's listeners of the event. The event is not propagated to any parents. Before notifying the listeners,\r
132          * this actor is set as the {@link Event#getListenerActor() listener actor}. The event {@link Event#setTarget(Actor) target}\r
133          * 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
134          * @param capture If true, the capture listeners will be notified instead of the regular listeners.\r
135          * @return true of the event was {@link Event#cancel() cancelled}. */\r
136         public boolean notify (Event event, boolean capture) {\r
137                 if (event.getTarget() == null) throw new IllegalArgumentException("The event target cannot be null.");\r
138 \r
139                 DelayedRemovalArray<EventListener> listeners = capture ? captureListeners : this.listeners;\r
140                 if (listeners.size == 0) return event.isCancelled();\r
141 \r
142                 event.setListenerActor(this);\r
143                 event.setCapture(capture);\r
144                 if (event.getStage() == null) event.setStage(stage);\r
145 \r
146                 listeners.begin();\r
147                 for (int i = 0, n = listeners.size; i < n; i++) {\r
148                         EventListener listener = listeners.get(i);\r
149                         if (listener.handle(event)) {\r
150                                 event.handle();\r
151                                 if (event instanceof InputEvent) {\r
152                                         InputEvent inputEvent = (InputEvent)event;\r
153                                         if (inputEvent.getType() == Type.touchDown) {\r
154                                                 event.getStage().addTouchFocus(listener, this, inputEvent.getTarget(), inputEvent.getPointer(),\r
155                                                         inputEvent.getButton());\r
156                                         }\r
157                                 }\r
158                         }\r
159                 }\r
160                 listeners.end();\r
161 \r
162                 return event.isCancelled();\r
163         }\r
164 \r
165         /** Returns the deepest actor that contains the specified point and is {@link #getTouchable() touchable} and\r
166          * {@link #isVisible() visible}, or null if no actor was hit. The point is specified in the actor's local coordinate system (0,0\r
167          * is the bottom left of the actor and width,height is the upper right).\r
168          * <p>\r
169          * This method is used to delegate touchDown events. If this method returns null, touchDown will not occur.\r
170          * <p>\r
171          * The default implementation returns this actor if the point is within this actor's bounds.\r
172          * @param touchable If true, the hit detection will respect the {@link #setTouchable(Touchable) touchability}.\r
173          * @see Touchable */\r
174         public Actor hit (float x, float y, boolean touchable) {\r
175                 if (touchable && this.touchable != Touchable.enabled) return null;\r
176                 return x >= 0 && x < width && y >= 0 && y < height ? this : null;\r
177         }\r
178 \r
179         /** Removes this actor from its parent, if it has a parent.\r
180          * @see Group#removeActor(Actor) */\r
181         public boolean remove () {\r
182                 if (parent != null) return parent.removeActor(this);\r
183                 return false;\r
184         }\r
185 \r
186         public boolean addListener (EventListener listener) {\r
187                 if (!listeners.contains(listener, true)) {\r
188                         listeners.add(listener);\r
189                         return true;\r
190                 }\r
191                 return false;\r
192         }\r
193 \r
194         public boolean removeListener (EventListener listener) {\r
195                 return listeners.removeValue(listener, true);\r
196         }\r
197 \r
198         public Array<EventListener> getListeners () {\r
199                 return listeners;\r
200         }\r
201 \r
202         /** Adds a listener that is only notified during the capture phase.\r
203          * @see #fire(Event) */\r
204         public boolean addCaptureListener (EventListener listener) {\r
205                 if (!captureListeners.contains(listener, true)) captureListeners.add(listener);\r
206                 return true;\r
207         }\r
208 \r
209         public boolean removeCaptureListener (EventListener listener) {\r
210                 return captureListeners.removeValue(listener, true);\r
211         }\r
212 \r
213         public Array<EventListener> getCaptureListeners () {\r
214                 return captureListeners;\r
215         }\r
216 \r
217         public void addAction (Action action) {\r
218                 action.setActor(this);\r
219                 actions.add(action);\r
220         }\r
221 \r
222         public void removeAction (Action action) {\r
223                 if (actions.removeValue(action, true)) action.setActor(null);\r
224         }\r
225 \r
226         public Array<Action> getActions () {\r
227                 return actions;\r
228         }\r
229 \r
230         /** Removes all actions on this actor. */\r
231         public void clearActions () {\r
232                 for (int i = actions.size - 1; i >= 0; i--)\r
233                         actions.get(i).setActor(null);\r
234                 actions.clear();\r
235         }\r
236 \r
237         /** Returns the stage that this actor is currently in, or null if not in a stage. */\r
238         public Stage getStage () {\r
239                 return stage;\r
240         }\r
241 \r
242         /** Called by the framework when this actor or any parent is added to a group that is in the stage.\r
243          * @param stage May be null if the actor or any parent is no longer in a stage. */\r
244         protected void setStage (Stage stage) {\r
245                 this.stage = stage;\r
246         }\r
247 \r
248         /** Returns true if this actor is the same as or is the descendant of the specified actor. */\r
249         public boolean isDescendantOf (Actor actor) {\r
250                 if (actor == null) throw new IllegalArgumentException("actor cannot be null.");\r
251                 Actor parent = this;\r
252                 while (true) {\r
253                         if (parent == null) return false;\r
254                         if (parent == actor) return true;\r
255                         parent = parent.getParent();\r
256                 }\r
257         }\r
258 \r
259         /** Returns true if this actor is the same as or is the ascendant of the specified actor. */\r
260         public boolean isAscendantOf (Actor actor) {\r
261                 if (actor == null) throw new IllegalArgumentException("actor cannot be null.");\r
262                 while (true) {\r
263                         if (actor == null) return false;\r
264                         if (actor == this) return true;\r
265                         actor = actor.getParent();\r
266                 }\r
267         }\r
268 \r
269         /** Returns true if the actor's parent is not null. */\r
270         public boolean hasParent () {\r
271                 return parent != null;\r
272         }\r
273 \r
274         /** Returns the parent actor, or null if not in a stage. */\r
275         public Group getParent () {\r
276                 return parent;\r
277         }\r
278 \r
279         /** Called by the framework when an actor is added to a group.\r
280          * @param parent May be null if the actor has been removed from the parent. */\r
281         protected void setParent (Group parent) {\r
282                 this.parent = parent;\r
283         }\r
284 \r
285         public Touchable getTouchable () {\r
286                 return touchable;\r
287         }\r
288 \r
289         /** Determines how touch events are distributed to this actor. Default is {@link Touchable#enabled}. */\r
290         public void setTouchable (Touchable touchable) {\r
291                 this.touchable = touchable;\r
292         }\r
293 \r
294         public boolean isVisible () {\r
295                 return visible;\r
296         }\r
297 \r
298         /** If false, the actor will not be drawn and will not receive touch events. Default is true. */\r
299         public void setVisible (boolean visible) {\r
300                 this.visible = visible;\r
301         }\r
302 \r
303         public float getX () {\r
304                 return x;\r
305         }\r
306 \r
307         public void setX (float x) {\r
308                 this.x = x;\r
309         }\r
310 \r
311         public float getY () {\r
312                 return y;\r
313         }\r
314 \r
315         public void setY (float y) {\r
316                 this.y = y;\r
317         }\r
318 \r
319         /** Sets the x and y. */\r
320         public void setPosition (float x, float y) {\r
321                 setX(x);\r
322                 setY(y);\r
323         }\r
324 \r
325         public void translate (float x, float y) {\r
326                 setX(this.x + x);\r
327                 setY(this.y + y);\r
328         }\r
329 \r
330         public float getWidth () {\r
331                 return width;\r
332         }\r
333 \r
334         public void setWidth (float width) {\r
335                 this.width = width;\r
336         }\r
337 \r
338         public float getHeight () {\r
339                 return height;\r
340         }\r
341 \r
342         public void setHeight (float height) {\r
343                 this.height = height;\r
344         }\r
345 \r
346         /** Returns y plus height. */\r
347         public float getTop () {\r
348                 return getY() + getHeight();\r
349         }\r
350 \r
351         /** Returns x plus width. */\r
352         public float getRight () {\r
353                 return getX() + getWidth();\r
354         }\r
355 \r
356         /** Sets the width and height. */\r
357         public void setSize (float width, float height) {\r
358                 setWidth(width);\r
359                 setHeight(height);\r
360         }\r
361 \r
362         /** Adds the specified size to the current size. */\r
363         public void size (float size) {\r
364                 setWidth(width + size);\r
365                 setHeight(height + size);\r
366         }\r
367 \r
368         /** Adds the specified size to the current size. */\r
369         public void size (float width, float height) {\r
370                 setWidth(this.width + width);\r
371                 setHeight(this.height + height);\r
372         }\r
373 \r
374         /** Set bounds the x, y, width, and height. */\r
375         public void setBounds (float x, float y, float width, float height) {\r
376                 setX(x);\r
377                 setY(y);\r
378                 setWidth(width);\r
379                 setHeight(height);\r
380         }\r
381 \r
382         public float getOriginX () {\r
383                 return originX;\r
384         }\r
385 \r
386         public void setOriginX (float originX) {\r
387                 this.originX = originX;\r
388         }\r
389 \r
390         public float getOriginY () {\r
391                 return originY;\r
392         }\r
393 \r
394         public void setOriginY (float originY) {\r
395                 this.originY = originY;\r
396         }\r
397 \r
398         /** Sets the originx and originy. */\r
399         public void setOrigin (float originX, float originY) {\r
400                 setOriginX(originX);\r
401                 setOriginY(originY);\r
402         }\r
403 \r
404         public float getScaleX () {\r
405                 return scaleX;\r
406         }\r
407 \r
408         public void setScaleX (float scaleX) {\r
409                 this.scaleX = scaleX;\r
410         }\r
411 \r
412         public float getScaleY () {\r
413                 return scaleY;\r
414         }\r
415 \r
416         public void setScaleY (float scaleY) {\r
417                 this.scaleY = scaleY;\r
418         }\r
419 \r
420         /** Sets the scalex and scaley. */\r
421         public void setScale (float scale) {\r
422                 setScaleX(scale);\r
423                 setScaleY(scale);\r
424         }\r
425 \r
426         /** Sets the scalex and scaley. */\r
427         public void setScale (float scaleX, float scaleY) {\r
428                 setScaleX(scaleX);\r
429                 setScaleY(scaleY);\r
430         }\r
431 \r
432         /** Adds the specified scale to the current scale. */\r
433         public void scale (float scale) {\r
434                 setScaleX(scaleX + scale);\r
435                 setScaleY(scaleY + scale);\r
436         }\r
437 \r
438         /** Adds the specified scale to the current scale. */\r
439         public void scale (float scaleX, float scaleY) {\r
440                 setScaleX(this.scaleX + scaleX);\r
441                 setScaleY(this.scaleY + scaleY);\r
442         }\r
443 \r
444         public float getRotation () {\r
445                 return rotation;\r
446         }\r
447 \r
448         public void setRotation (float degrees) {\r
449                 this.rotation = degrees;\r
450         }\r
451 \r
452         /** Adds the specified rotation to the current rotation. */\r
453         public void rotate (float amountInDegrees) {\r
454                 setRotation(rotation + amountInDegrees);\r
455         }\r
456 \r
457         public void setColor (Color color) {\r
458                 this.color.set(color);\r
459         }\r
460 \r
461         public void setColor (float r, float g, float b, float a) {\r
462                 color.set(r, g, b, a);\r
463         }\r
464 \r
465         /** Returns the actor's color, which is mutable. */\r
466         public Color getColor () {\r
467                 return color;\r
468         }\r
469 \r
470         public String getName () {\r
471                 return name;\r
472         }\r
473 \r
474         /** Sets a name for easier identification of the actor in application code.\r
475          * @see Group#findActor(String) */\r
476         public void setName (String name) {\r
477                 this.name = name;\r
478         }\r
479 \r
480         /** Changes the z-order for this actor so it is in front of all siblings. */\r
481         public void toFront () {\r
482                 setZIndex(Integer.MAX_VALUE);\r
483         }\r
484 \r
485         /** Changes the z-order for this actor so it is in back of all siblings. */\r
486         public void toBack () {\r
487                 setZIndex(0);\r
488         }\r
489 \r
490         /** Sets the z-index of this actor. The z-index is the index into the parent's {@link Group#getChildren() children}, where a\r
491          * 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
492          * Setting a z-index less than zero is invalid. */\r
493         public void setZIndex (int index) {\r
494                 if (index < 0) throw new IllegalArgumentException("ZIndex cannot be < 0.");\r
495                 Group parent = getParent();\r
496                 if (parent == null) return;\r
497                 Array<Actor> children = parent.getChildren();\r
498                 if (children.size == 1) return;\r
499                 if (!children.removeValue(this, true)) return;\r
500                 if (index >= children.size)\r
501                         children.add(this);\r
502                 else\r
503                         children.insert(index, this);\r
504         }\r
505 \r
506         /** Returns the z-index of this actor.\r
507          * @see #setZIndex(int) */\r
508         public int getZIndex () {\r
509                 Group parent = getParent();\r
510                 if (parent == null) return -1;\r
511                 return parent.getChildren().indexOf(this, true);\r
512         }\r
513 \r
514         /** Transforms the specified point in screen coordinates to the actor's local coordinate system. */\r
515         public Vector2 screenToLocalCoordinates (Vector2 screenCoords) {\r
516                 Stage stage = getStage();\r
517                 if (stage == null) return screenCoords;\r
518                 return stageToLocalCoordinates(stage.screenToStageCoordinates(screenCoords));\r
519         }\r
520 \r
521         /** Transforms the specified point in the stage's coordinates to the actor's local coordinate system. */\r
522         public Vector2 stageToLocalCoordinates (Vector2 stageCoords) {\r
523                 if (parent == null) return stageCoords;\r
524                 parent.stageToLocalCoordinates(stageCoords);\r
525                 parentToLocalCoordinates(stageCoords);\r
526                 return stageCoords;\r
527         }\r
528 \r
529         /** Transforms the specified point in the actor's coordinates to be in the stage's coordinates. Note this method will ONLY work\r
530          * for screen aligned, unrotated, unscaled actors! */\r
531         public Vector2 localToStageCoordinates (Vector2 localCoords) {\r
532                 Actor actor = this;\r
533                 while (actor != null) {\r
534                         if (actor.getRotation() != 0 || actor.getScaleX() != 1 || actor.getScaleY() != 1)\r
535                                 throw new GdxRuntimeException("Only unrotated and unscaled actors may use this method.");\r
536                         localCoords.x += actor.getX();\r
537                         localCoords.y += actor.getY();\r
538                         actor = actor.getParent();\r
539                 }\r
540                 return localCoords;\r
541         }\r
542 \r
543         /** Transforms the specified point in the actor's coordinates to be in the parent's coordinates. Note this method will ONLY work\r
544          * for screen aligned, unrotated, unscaled actors! */\r
545         public Vector2 localToParentCoordinates (Vector2 localCoords) {\r
546                 if (getRotation() != 0 || getScaleX() != 1 || getScaleY() != 1)\r
547                         throw new GdxRuntimeException("Only unrotated and unscaled actors may use this method.");\r
548                 localCoords.x += getX();\r
549                 localCoords.y += getY();\r
550                 return localCoords;\r
551         }\r
552 \r
553         /** Converts coordinates for this actor to those of a parent actor. The ascendant does not need to be a direct parent. */\r
554         public Vector2 localToAscendantCoordinates (Actor ascendant, Vector2 localCoords) {\r
555                 Actor actor = this;\r
556                 while (actor.getParent() != null) {\r
557                         actor.localToParentCoordinates(localCoords);\r
558                         actor = actor.getParent();\r
559                         if (actor == ascendant) break;\r
560                 }\r
561                 return localCoords;\r
562         }\r
563 \r
564         /** Converts the coordinates given in the parent's coordinate system to this actor's coordinate system. */\r
565         public Vector2 parentToLocalCoordinates (Vector2 parentCoords) {\r
566                 final float rotation = getRotation();\r
567                 final float scaleX = getScaleX();\r
568                 final float scaleY = getScaleY();\r
569                 final float childX = getX();\r
570                 final float childY = getY();\r
571 \r
572                 if (rotation == 0) {\r
573                         if (scaleX == 1 && scaleY == 1) {\r
574                                 parentCoords.x -= childX;\r
575                                 parentCoords.y -= childY;\r
576                         } else {\r
577                                 final float originX = getOriginX();\r
578                                 final float originY = getOriginY();\r
579                                 if (originX == 0 && originY == 0) {\r
580                                         parentCoords.x = (parentCoords.x - childX) / scaleX;\r
581                                         parentCoords.y = (parentCoords.y - childY) / scaleY;\r
582                                 } else {\r
583                                         parentCoords.x = (parentCoords.x - childX - originX) / scaleX + originX;\r
584                                         parentCoords.y = (parentCoords.y - childY - originY) / scaleY + originY;\r
585                                 }\r
586                         }\r
587                 } else {\r
588                         final float cos = (float)Math.cos(rotation * MathUtils.degreesToRadians);\r
589                         final float sin = (float)Math.sin(rotation * MathUtils.degreesToRadians);\r
590 \r
591                         final float originX = getOriginX();\r
592                         final float originY = getOriginY();\r
593 \r
594                         if (scaleX == 1 && scaleY == 1) {\r
595                                 if (originX == 0 && originY == 0) {\r
596                                         float tox = parentCoords.x - childX;\r
597                                         float toy = parentCoords.y - childY;\r
598 \r
599                                         parentCoords.x = tox * cos + toy * sin;\r
600                                         parentCoords.y = tox * -sin + toy * cos;\r
601                                 } else {\r
602                                         final float worldOriginX = childX + originX;\r
603                                         final float worldOriginY = childY + originY;\r
604                                         final float fx = -originX;\r
605                                         final float fy = -originY;\r
606 \r
607                                         final float x1 = cos * fx - sin * fy + worldOriginX;\r
608                                         final float y1 = sin * fx + cos * fy + worldOriginY;\r
609 \r
610                                         final float tox = parentCoords.x - x1;\r
611                                         final float toy = parentCoords.y - y1;\r
612 \r
613                                         parentCoords.x = tox * cos + toy * sin;\r
614                                         parentCoords.y = tox * -sin + toy * cos;\r
615                                 }\r
616                         } else {\r
617                                 if (originX == 0 && originY == 0) {\r
618                                         final float tox = parentCoords.x - childX;\r
619                                         final float toy = parentCoords.y - childY;\r
620 \r
621                                         parentCoords.x = (tox * cos + toy * sin) / scaleX;\r
622                                         parentCoords.y = (tox * -sin + toy * cos) / scaleY;\r
623                                 } else {\r
624                                         final float worldOriginX = childX + originX;\r
625                                         final float worldOriginY = childY + originY;\r
626                                         final float fx = -originX * scaleX;\r
627                                         final float fy = -originY * scaleY;\r
628 \r
629                                         final float x1 = cos * fx - sin * fy + worldOriginX;\r
630                                         final float y1 = sin * fx + cos * fy + worldOriginY;\r
631 \r
632                                         final float tox = parentCoords.x - x1;\r
633                                         final float toy = parentCoords.y - y1;\r
634 \r
635                                         parentCoords.x = (tox * cos + toy * sin) / scaleX;\r
636                                         parentCoords.y = (tox * -sin + toy * cos) / scaleY;\r
637                                 }\r
638                         }\r
639                 }\r
640                 return parentCoords;\r
641         }\r
642 \r
643         public String toString () {\r
644                 String name = this.name;\r
645                 if (name == null) {\r
646                         name = getClass().getName();\r
647                         int dotIndex = name.lastIndexOf('.');\r
648                         if (dotIndex != -1) name = name.substring(dotIndex + 1);\r
649                 }\r
650                 return name + " " + x + "," + y + " " + width + "x" + height;\r
651         }\r
652 }\r