Updated javadocs.
import com.badlogic.gdx.scenes.scene2d.ActorEvent.Type;\r
import com.badlogic.gdx.utils.Array;\r
import com.badlogic.gdx.utils.DelayedRemovalArray;\r
+import com.badlogic.gdx.utils.GdxRuntimeException;\r
import com.badlogic.gdx.utils.Pools;\r
\r
/** 2D scene graph node. An actor has a position, rectangular size, origin, scale, rotation, and color. The position corresponds to\r
children.insert(index, this);\r
}\r
\r
- /** Returns the z-index of this actor. */\r
+ /** Returns the z-index of this actor.\r
+ * @see #setZIndex(int) */\r
public int getZIndex () {\r
Group parent = getParent();\r
if (parent == null) return -1;\r
}\r
\r
/** Transforms the specified point in the actor's coordinates to be in the stage's coordinates. Note this method will ONLY work\r
- * properly for screen aligned, unrotated, unscaled actors! */\r
+ * for screen aligned, unrotated, unscaled actors! */\r
public void localToStageCoordinates (Vector2 localCoords) {\r
- localCoords.x += getX();\r
- localCoords.y += getY();\r
- Group parent = getParent();\r
- while (parent != null) {\r
- localCoords.x += parent.getX();\r
- localCoords.y += parent.getY();\r
- parent = parent.getParent();\r
+ Actor actor = this;\r
+ while (actor != null) {\r
+ if (actor.getRotation() != 0 || actor.getScaleX() != 1 || actor.getScaleY() != 1)\r
+ throw new GdxRuntimeException("Only unrotated and unscaled actors may use this method.");\r
+ localCoords.x += actor.getX();\r
+ localCoords.y += actor.getY();\r
+ actor = actor.getParent();\r
}\r
}\r
\r
/** Converts the coordinates given in the parent's coordinate system to this actor's coordinate system. */\r
public void parentToLocalCoordinates (Vector2 parentCoords) {\r
- float rotation = getRotation();\r
- float scaleX = getScaleX();\r
- float scaleY = getScaleY();\r
- float childX = getX();\r
- float childY = getY();\r
+ final float rotation = getRotation();\r
+ final float scaleX = getScaleX();\r
+ final float scaleY = getScaleY();\r
+ final float childX = getX();\r
+ final float childY = getY();\r
\r
if (rotation == 0) {\r
if (scaleX == 1 && scaleY == 1) {\r
parentCoords.x -= childX;\r
parentCoords.y -= childY;\r
} else {\r
- float originX = getOriginX();\r
- float originY = getOriginY();\r
+ final float originX = getOriginX();\r
+ final float originY = getOriginY();\r
if (originX == 0 && originY == 0) {\r
parentCoords.x = (parentCoords.x - childX) / scaleX;\r
parentCoords.y = (parentCoords.y - childY) / scaleY;\r
final float cos = (float)Math.cos(rotation * MathUtils.degreesToRadians);\r
final float sin = (float)Math.sin(rotation * MathUtils.degreesToRadians);\r
\r
- float originX = getOriginX();\r
- float originY = getOriginY();\r
+ final float originX = getOriginX();\r
+ final float originY = getOriginY();\r
\r
if (scaleX == 1 && scaleY == 1) {\r
if (originX == 0 && originY == 0) {\r
} else {\r
final float worldOriginX = childX + originX;\r
final float worldOriginY = childY + originY;\r
- float fx = -originX;\r
- float fy = -originY;\r
+ final float fx = -originX;\r
+ final float fy = -originY;\r
\r
- float x1 = cos * fx - sin * fy;\r
- float y1 = sin * fx + cos * fy;\r
- x1 += worldOriginX;\r
- y1 += worldOriginY;\r
+ final float x1 = cos * fx - sin * fy + worldOriginX;\r
+ final float y1 = sin * fx + cos * fy + worldOriginY;\r
\r
- float tox = parentCoords.x - x1;\r
- float toy = parentCoords.y - y1;\r
+ final float tox = parentCoords.x - x1;\r
+ final float toy = parentCoords.y - y1;\r
\r
parentCoords.x = tox * cos + toy * sin;\r
parentCoords.y = tox * -sin + toy * cos;\r
}\r
} else {\r
if (originX == 0 && originY == 0) {\r
- float tox = parentCoords.x - childX;\r
- float toy = parentCoords.y - childY;\r
+ final float tox = parentCoords.x - childX;\r
+ final float toy = parentCoords.y - childY;\r
\r
- parentCoords.x = tox * cos + toy * sin;\r
- parentCoords.y = tox * -sin + toy * cos;\r
-\r
- parentCoords.x /= scaleX;\r
- parentCoords.y /= scaleY;\r
+ parentCoords.x = (tox * cos + toy * sin) / scaleX;\r
+ parentCoords.y = (tox * -sin + toy * cos) / scaleY;\r
} else {\r
final float worldOriginX = childX + originX;\r
final float worldOriginY = childY + originY;\r
- float fx = -originX * scaleX;\r
- float fy = -originY * scaleY;\r
+ final float fx = -originX * scaleX;\r
+ final float fy = -originY * scaleY;\r
\r
- float x1 = cos * fx - sin * fy;\r
- float y1 = sin * fx + cos * fy;\r
- x1 += worldOriginX;\r
- y1 += worldOriginY;\r
+ final float x1 = cos * fx - sin * fy + worldOriginX;\r
+ final float y1 = sin * fx + cos * fy + worldOriginY;\r
\r
- float tox = parentCoords.x - x1;\r
- float toy = parentCoords.y - y1;\r
-\r
- parentCoords.x = tox * cos + toy * sin;\r
- parentCoords.y = tox * -sin + toy * cos;\r
+ final float tox = parentCoords.x - x1;\r
+ final float toy = parentCoords.y - y1;\r
\r
- parentCoords.x /= scaleX;\r
- parentCoords.y /= scaleY;\r
+ parentCoords.x = (tox * cos + toy * sin) / scaleX;\r
+ parentCoords.y = (tox * -sin + toy * cos) / scaleY;\r
}\r
}\r
}\r
if (ownsBatch) batch.dispose();\r
}\r
\r
+ /** Internal class for managing touches. Public only for GWT. */\r
public static final class TouchFocus {\r
- static final Pool<TouchFocus> pool = new Pool<TouchFocus>(4, 16) {\r
+ static final Pool<TouchFocus> pool = new Pool(4, 16) {\r
protected TouchFocus newObject () {\r
return new TouchFocus();\r
}\r
Actor actor;\r
EventListener listener;\r
int pointer, button;\r
-\r
- public String toString () {\r
- return actor.toString();\r
- }\r
}\r
}\r
/** A button is a {@link Table} with a checked state and additional {@link ButtonStyle style} fields for pressed, unpressed, and\r
* checked. Each time a button is clicked, the checked state is toggled. Being a table, a button can contain any other actors.\r
* <p>\r
- * {@link ChangeEvent} is fired when the button is clicked.\r
+ * {@link ChangeEvent} is fired when the button is clicked. Cancelling the event will restore the checked button state to what is\r
+ * was previously.\r
* <p>\r
* The preferred size of the button is determined by the background and the button contents.\r
* @author Nathan Sweet */\r
}\r
\r
public Label (CharSequence text, Skin skin, String styleName) {\r
- this(text, skin.get(LabelStyle.class));\r
+ this(text, skin.get(styleName, LabelStyle.class));\r
}\r
\r
/** Creates a label, using a {@link LabelStyle} that has a BitmapFont with the specified name from the skin and the specified\r
\r
/** A list (aka list box) displays textual items and highlights the currently selected item.\r
* <p>\r
+ * {@link ChangeEvent} is fired when the list selection changes.\r
+ * <p>\r
* The preferred size of the list is determined by the text bounds of the items and the size of the {@link ListStyle#selection}.\r
* @author mzechner */\r
public class List extends Widget implements Cullable {\r
/** A select box (aka a drop-down list) allows a user to choose one of a number of values from a list. When inactive, the selected\r
* value is displayed. When activated, it shows the list of values that may be selected.\r
* <p>\r
+ * {@link ChangeEvent} is fired when the selectbox selection changes.\r
+ * <p>\r
* The preferred size of the select box is determined by the maximum text bounds of the items and the size of the\r
* {@link SelectBoxStyle#background}.\r
* @author mzechner\r
import com.badlogic.gdx.scenes.scene2d.ActorEvent;\r
import com.badlogic.gdx.scenes.scene2d.ActorListener;\r
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener.ChangeEvent;\r
+import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;\r
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;\r
\r
// BOZO - Add snapping to the knob.\r
/** A slider is a horizontal indicator that allows a user to set a value. The slider his a range (min, max) and a stepping between\r
* each value the slider represents.\r
* <p>\r
+ * {@link ChangeEvent} is fired when the slider knob is moved. Cancelling the event will move the knob to where it was previously.\r
+ * <p>\r
* The preferred height of a slider is determined by the larger of the knob and background. The preferred width of a slider is\r
* 140, a relatively arbitrary size.\r
* @author mzechner\r
--- /dev/null
+\r
+package com.badlogic.gdx.scenes.scene2d.ui;\r
+\r
+import com.badlogic.gdx.graphics.Color;\r
+import com.badlogic.gdx.graphics.g2d.SpriteBatch;\r
+import com.badlogic.gdx.math.Circle;\r
+import com.badlogic.gdx.math.Vector2;\r
+import com.badlogic.gdx.scenes.scene2d.Actor;\r
+import com.badlogic.gdx.scenes.scene2d.ActorEvent;\r
+import com.badlogic.gdx.scenes.scene2d.ActorListener;\r
+import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener.ChangeEvent;\r
+import com.badlogic.gdx.scenes.scene2d.utils.Drawable;\r
+\r
+/** An on-screen joystick. The movement area of the joystick is circular, centered on the touchpad, and its size determined by the\r
+ * smaller touchpad dimension.\r
+ * <p>\r
+ * The preferred size of the touchpad is determined by the background.\r
+ * <p>\r
+ * {@link ChangeEvent} is fired when the touchpad knob is moved. Cancelling the event will move the knob to where it was\r
+ * previously.\r
+ * @author Josh Street */\r
+public class Touchpad extends Widget {\r
+ private TouchpadStyle style;\r
+ boolean touched;\r
+ private float deadzoneRadius;\r
+ private final Circle padBounds = new Circle(0, 0, 0);\r
+ private final Circle deadzoneBounds = new Circle(0, 0, 0);\r
+ private final Vector2 knobPosition = new Vector2();\r
+ private final Vector2 knobPercent = new Vector2();\r
+\r
+ /** @param deadzoneRadius The distance in pixels from the center of the touchpad required for the knob to be moved. */\r
+ public Touchpad (float deadzoneRadius, Skin skin) {\r
+ this(deadzoneRadius, skin.get(TouchpadStyle.class));\r
+ }\r
+\r
+ /** @param deadzoneRadius The distance in pixels from the center of the touchpad required for the knob to be moved. */\r
+ public Touchpad (float deadzoneRadius, Skin skin, String styleName) {\r
+ this(deadzoneRadius, skin.get(styleName, TouchpadStyle.class));\r
+ }\r
+\r
+ /** @param deadzoneRadius The distance in pixels from the center of the touchpad required for the knob to be moved. */\r
+ public Touchpad (float deadzoneRadius, TouchpadStyle style) {\r
+ if (deadzoneRadius < 0) throw new IllegalArgumentException("deadzoneRadius must be > 0");\r
+ this.deadzoneRadius = deadzoneRadius;\r
+\r
+ knobPosition.set(getWidth() / 2f, getHeight() / 2f);\r
+\r
+ setStyle(style);\r
+ setWidth(getPrefWidth());\r
+ setHeight(getPrefHeight());\r
+\r
+ addListener(new ActorListener() {\r
+ @Override\r
+ public boolean touchDown (ActorEvent event, float x, float y, int pointer, int button) {\r
+ if (touched) return false;\r
+ touched = true;\r
+ calculatePositionAndValue(x, y, false);\r
+ return true;\r
+ }\r
+\r
+ @Override\r
+ public void touchDragged (ActorEvent event, float x, float y, int pointer) {\r
+ calculatePositionAndValue(x, y, false);\r
+ }\r
+\r
+ @Override\r
+ public void touchUp (ActorEvent event, float x, float y, int pointer, int button) {\r
+ touched = false;\r
+ calculatePositionAndValue(x, y, true);\r
+ }\r
+ });\r
+ }\r
+\r
+ void calculatePositionAndValue (float x, float y, boolean isTouchUp) {\r
+ float oldPositionX = knobPosition.x;\r
+ float oldPositionY = knobPosition.y;\r
+ float oldPercentX = knobPercent.x;\r
+ float oldPercentY = knobPercent.y;\r
+ knobPosition.set(getWidth() / 2f, getHeight() / 2f);\r
+ knobPercent.set(0f, 0f);\r
+ if (!isTouchUp) {\r
+ if (!deadzoneBounds.contains(x, y)) {\r
+ knobPercent.set(x - padBounds.x, y - padBounds.y).nor();\r
+ if (padBounds.contains(x, y)) {\r
+ knobPosition.set(x, y);\r
+ } else {\r
+ knobPosition.set(knobPercent).mul(padBounds.radius).add(padBounds.x, padBounds.y);\r
+ }\r
+ }\r
+ }\r
+ if ((oldPercentX != knobPercent.x || oldPercentY != knobPercent.y) && fire(new ChangeEvent())) {\r
+ knobPercent.set(oldPercentX, oldPercentY);\r
+ knobPosition.set(oldPositionX, oldPositionY);\r
+ }\r
+ }\r
+\r
+ public void setStyle (TouchpadStyle style) {\r
+ if (style == null) throw new IllegalArgumentException("style cannot be null");\r
+ this.style = style;\r
+ invalidateHierarchy();\r
+ }\r
+\r
+ /** Returns the touchpad's style. Modifying the returned style may not have an effect until {@link #setStyle(TouchpadStyle)} is\r
+ * called. */\r
+ public TouchpadStyle getStyle () {\r
+ return style;\r
+ }\r
+\r
+ @Override\r
+ public Actor hit (float x, float y) {\r
+ return padBounds.contains(x, y) ? this : null;\r
+ }\r
+\r
+ @Override\r
+ public void layout () {\r
+ // Recalc pad and deadzone bounds\r
+ float radius = Math.min(getWidth(), getHeight()) / 2;\r
+ if (style.knob != null) radius -= Math.max(style.knob.getMinWidth(), style.knob.getMinHeight()) / 2;\r
+ padBounds.set(getWidth() / 2f, getHeight() / 2f, radius);\r
+ deadzoneBounds.set(getWidth() / 2f, getHeight() / 2f, deadzoneRadius);\r
+ // Recalc pad values and knob position\r
+ knobPosition.set(getWidth() / 2f, getHeight() / 2f);\r
+ knobPercent.set(0, 0);\r
+ }\r
+\r
+ @Override\r
+ public void draw (SpriteBatch batch, float parentAlpha) {\r
+ validate();\r
+\r
+ Color c = getColor();\r
+ batch.setColor(c.r, c.g, c.b, c.a * parentAlpha);\r
+\r
+ float x = getX();\r
+ float y = getY();\r
+ float w = getWidth();\r
+ float h = getHeight();\r
+\r
+ final Drawable bg = style.background;\r
+ if (bg != null) bg.draw(batch, x, y, w, h);\r
+\r
+ final Drawable knob = style.knob;\r
+ if (knob != null) {\r
+ x += knobPosition.x - knob.getMinWidth() / 2f;\r
+ y += knobPosition.y - knob.getMinHeight() / 2f;\r
+ knob.draw(batch, x, y, knob.getMinWidth(), knob.getMinHeight());\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public float getPrefWidth () {\r
+ return style.background != null ? style.background.getMinWidth() : 0;\r
+ }\r
+\r
+ @Override\r
+ public float getPrefHeight () {\r
+ return style.background != null ? style.background.getMinHeight() : 0;\r
+ }\r
+\r
+ public boolean isTouched () {\r
+ return touched;\r
+ }\r
+\r
+ /** @param deadzoneRadius The distance in pixels from the center of the touchpad required for the knob to be moved. */\r
+ public void setDeadzone (float deadzoneRadius) {\r
+ if (deadzoneRadius < 0) throw new IllegalArgumentException("deadzoneRadius must be > 0");\r
+ this.deadzoneRadius = deadzoneRadius;\r
+ invalidate();\r
+ }\r
+\r
+ /** Returns the x-position of the knob relative to the center of the widget. The positive direction is right. */\r
+ public float getKnobX () {\r
+ return knobPosition.x;\r
+ }\r
+\r
+ /** Returns the y-position of the knob relative to the center of the widget. The positive direction is up. */\r
+ public float getKnobY () {\r
+ return knobPosition.y;\r
+ }\r
+\r
+ /** Returns the x-position of the knob as a percentage from the center of the touchpad to the edge of the circular movement\r
+ * area. The positive direction is right. */\r
+ public float getKnobPercentX () {\r
+ return knobPercent.x;\r
+ }\r
+\r
+ /** Returns the y-position of the knob as a percentage from the center of the touchpad to the edge of the circular movement\r
+ * area. The positive direction is up. */\r
+ public float getKnobPercentY () {\r
+ return knobPercent.y;\r
+ }\r
+\r
+ /** The style for a {@link Touchpad}.\r
+ * @author Josh Street */\r
+ public static class TouchpadStyle {\r
+ /** Stretched in both directions. Optional. */\r
+ public Drawable background;\r
+\r
+ /** Optional. */\r
+ public Drawable knob;\r
+\r
+ public TouchpadStyle () {\r
+ }\r
+\r
+ public TouchpadStyle (Drawable background, Drawable knob) {\r
+ this.background = background;\r
+ this.knob = knob;\r
+ }\r
+\r
+ public TouchpadStyle (TouchpadStyle style) {\r
+ this.background = style.background;\r
+ this.knob = style.knob;\r
+ }\r
+ }\r
+}\r
},
com.badlogic.gdx.scenes.scene2d.ui.List$ListStyle: {
default: { fontColorUnselected: white, selection: default-rect-pad, fontColorSelected: white, font: default-font }
+},
+com.badlogic.gdx.scenes.scene2d.ui.Touchpad$TouchpadStyle: {
+ default: { background: default-pane, knob: default-round-large }
}
}
\r
Array<Actor> actors = stage.getActors();\r
int len = actors.size;\r
- for (int i = 0; i < len; i++)\r
- if (rotateSprites)\r
- actors.get(i).rotate(Gdx.graphics.getDeltaTime());\r
- else\r
- actors.get(i).setRotation(0);\r
+ if (rotateSprites) {\r
+ for (int i = 0; i < len; i++)\r
+ actors.get(i).rotate(Gdx.graphics.getDeltaTime() * 10);\r
+ }\r
\r
scale += vScale * Gdx.graphics.getDeltaTime();\r
if (scale > 1) {\r
--- /dev/null
+\r
+package com.badlogic.gdx.tests;\r
+\r
+import com.badlogic.gdx.Gdx;\r
+import com.badlogic.gdx.graphics.Color;\r
+import com.badlogic.gdx.graphics.GL10;\r
+import com.badlogic.gdx.graphics.Texture;\r
+import com.badlogic.gdx.graphics.g2d.NinePatch;\r
+import com.badlogic.gdx.graphics.g2d.SpriteBatch;\r
+import com.badlogic.gdx.graphics.g2d.TextureRegion;\r
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;\r
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;\r
+import com.badlogic.gdx.math.MathUtils;\r
+import com.badlogic.gdx.scenes.scene2d.Actor;\r
+import com.badlogic.gdx.scenes.scene2d.ActorEvent;\r
+import com.badlogic.gdx.scenes.scene2d.ActorListener;\r
+import com.badlogic.gdx.scenes.scene2d.Stage;\r
+import com.badlogic.gdx.scenes.scene2d.actions.FloatAction;\r
+import com.badlogic.gdx.scenes.scene2d.ui.Skin;\r
+import com.badlogic.gdx.scenes.scene2d.ui.TextButton;\r
+import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle;\r
+import com.badlogic.gdx.scenes.scene2d.ui.Touchpad;\r
+import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener;\r
+import com.badlogic.gdx.scenes.scene2d.utils.EmptyDrawable;\r
+import com.badlogic.gdx.tests.utils.GdxTest;\r
+\r
+import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;\r
+\r
+public class TouchpadTest extends GdxTest {\r
+ Stage stage;\r
+\r
+ public void create () {\r
+ stage = new Stage();\r
+ Gdx.input.setInputProcessor(stage);\r
+\r
+ Skin skin = new Skin(Gdx.files.internal("data/uiskin.json"));\r
+\r
+ Touchpad touchpad = new Touchpad(20, skin);\r
+ touchpad.setBounds(15, 15, 100, 100);\r
+ stage.addActor(touchpad);\r
+ }\r
+\r
+ public void render () {\r
+ // System.out.println(meow.getValue());\r
+ Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);\r
+ stage.act(Gdx.graphics.getDeltaTime());\r
+ stage.draw();\r
+ }\r
+\r
+ public void resize (int width, int height) {\r
+ stage.setViewport(width, height, true);\r
+ }\r
+\r
+ public boolean needsGL20 () {\r
+ return false;\r
+ }\r
+\r
+ public void dispose () {\r
+ stage.dispose();\r
+ }\r
+}\r
\r
// window.debug();\r
Window window = new Window("Dialog", skin);\r
+ window.setTouchable(false);\r
window.setPosition(0, 0);\r
window.defaults().spaceBottom(10);\r
window.row().fill().expandX();\r
GestureDetectorTest.class, LabelTest.class, BitmapFontTest.class,\r
BlitTest.class, TableTest.class, BobTest.class,\r
ImageScaleTest.class, TableLayoutTest.class,\r
- Box2DTest.class, InterpolationTest.class,\r
+ Box2DTest.class, InterpolationTest.class, TouchpadTest.class,\r
Box2DTestCollection.class, BufferUtilsTest.class, ImageTest.class,\r
CompassTest.class, ComplexActionTest.class, CullTest.class,\r
DeltaTimeTest.class, EdgeDetectionTest.class, ETC1Test.class,\r