summaryrefslogtreecommitdiffstats
path: root/core/src/ch/asynk/gdx/board
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/ch/asynk/gdx/board')
-rw-r--r--core/src/ch/asynk/gdx/board/Assets.java30
-rw-r--r--core/src/ch/asynk/gdx/board/Board.java9
-rw-r--r--core/src/ch/asynk/gdx/board/Camera.java218
-rw-r--r--core/src/ch/asynk/gdx/board/Drawable.java19
-rw-r--r--core/src/ch/asynk/gdx/board/Touchable.java6
-rw-r--r--core/src/ch/asynk/gdx/board/board/BoardFactory.java40
-rw-r--r--core/src/ch/asynk/gdx/board/board/HexBoard.java144
-rw-r--r--core/src/ch/asynk/gdx/board/board/SquareBoard.java35
-rw-r--r--core/src/ch/asynk/gdx/board/ui/Alignment.java168
-rw-r--r--core/src/ch/asynk/gdx/board/ui/Assembly.java62
-rw-r--r--core/src/ch/asynk/gdx/board/ui/Button.java65
-rw-r--r--core/src/ch/asynk/gdx/board/ui/Element.java112
-rw-r--r--core/src/ch/asynk/gdx/board/ui/Label.java62
-rw-r--r--core/src/ch/asynk/gdx/board/ui/Patch.java22
-rw-r--r--core/src/ch/asynk/gdx/board/ui/Root.java16
-rw-r--r--core/src/ch/asynk/gdx/board/util/Collection.java28
-rw-r--r--core/src/ch/asynk/gdx/board/util/IterableArray.java143
-rw-r--r--core/src/ch/asynk/gdx/board/util/IterableQueue.java19
-rw-r--r--core/src/ch/asynk/gdx/board/util/IterableSet.java16
-rw-r--r--core/src/ch/asynk/gdx/board/util/IterableStack.java26
20 files changed, 1240 insertions, 0 deletions
diff --git a/core/src/ch/asynk/gdx/board/Assets.java b/core/src/ch/asynk/gdx/board/Assets.java
new file mode 100644
index 0000000..3678129
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/Assets.java
@@ -0,0 +1,30 @@
+package ch.asynk.gdx.board;
+
+import com.badlogic.gdx.assets.AssetManager;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.NinePatch;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
+public class Assets extends AssetManager
+{
+ public Texture getTexture(String assetName)
+ {
+ return get(assetName, Texture.class);
+ }
+
+ public NinePatch getNinePatch(String assetName, int left, int right, int top, int bottom)
+ {
+ return new NinePatch(get(assetName, Texture.class), left, right, top, bottom);
+ }
+
+ public TextureAtlas getAtlas(String assetName)
+ {
+ return get(assetName, TextureAtlas.class);
+ }
+
+ public BitmapFont getFont(String assetName)
+ {
+ return get(assetName, BitmapFont.class);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/Board.java b/core/src/ch/asynk/gdx/board/Board.java
new file mode 100644
index 0000000..0089ad7
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/Board.java
@@ -0,0 +1,9 @@
+package ch.asynk.gdx.board;
+
+import com.badlogic.gdx.math.Vector2;
+
+public interface Board
+{
+ public void centerOf(int x, int y, Vector2 v);
+ public void toBoard(float x, float y, Vector2 v);
+}
diff --git a/core/src/ch/asynk/gdx/board/Camera.java b/core/src/ch/asynk/gdx/board/Camera.java
new file mode 100644
index 0000000..4c1a1b6
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/Camera.java
@@ -0,0 +1,218 @@
+package ch.asynk.gdx.board;
+
+import com.badlogic.gdx.graphics.glutils.HdpiUtils;
+import com.badlogic.gdx.graphics.OrthographicCamera;
+
+import com.badlogic.gdx.math.Rectangle;
+import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.math.MathUtils;
+import com.badlogic.gdx.math.Matrix4;
+
+public class Camera extends OrthographicCamera
+{
+ private static final float ZEROF = 0.01f;
+
+ private int padding;
+ private float boardWidth;
+ private float boardHeight;
+ private float boardAspectRatio;
+ private float zoomMax;
+ private float zoomMin;
+ private int screenWidth;
+ private int screenHeight;
+ private float widthFactor;
+ private float heightFactor;
+ private Rectangle viewport;
+
+ private Rectangle hud;
+ private Matrix4 hudMatrix;
+ private Matrix4 hudInvProjMatrix;
+ private int hudLeft;
+ private int hudBottom;
+ private boolean hudInBoard;
+
+ public Camera(int padding, float boardWidth, float boardHeight, float zoomMax, float zoomMin, boolean hudInBoard)
+ {
+ super(boardWidth, boardHeight);
+ this.boardWidth = boardWidth;
+ this.boardHeight = boardHeight;
+ this.padding = padding;
+ this.boardAspectRatio = (boardWidth / boardHeight);
+ this.zoomMax = zoomMax;
+ this.zoomMin = zoomMin;
+ this.viewport = new Rectangle();
+
+ this.hudInBoard = hudInBoard;
+ this.hud = new Rectangle();
+ this.hudMatrix = new Matrix4();
+ this.hudInvProjMatrix = new Matrix4();
+ }
+
+ public void setDimension(float boardWidth, float boardHeight)
+ {
+ setToOrtho(false, boardWidth, boardHeight);
+ this.boardWidth = boardWidth;
+ this.boardHeight = boardHeight;
+ this.boardAspectRatio = (boardWidth / boardHeight);
+ }
+
+ public void updateViewport(int screenWidth, int screenHeight)
+ {
+ this.screenWidth = screenWidth;
+ this.screenHeight = screenHeight;
+
+ float screenAvailableWidth = screenWidth - (2 * padding);
+ float screenAvailableHeight = screenHeight - (2 * padding);
+ float screenAspectRatio = (screenWidth / (float) screenHeight);
+ float diff = (boardAspectRatio - screenAspectRatio);
+
+ if (diff <= -ZEROF) {
+ // screen wider than board : use max height, width grows up until max available on zooming
+ viewport.height = screenAvailableHeight;
+ viewport.width = java.lang.Math.min((screenAvailableHeight * boardAspectRatio / zoom), screenAvailableWidth);
+ viewport.x = padding + ((screenAvailableWidth - viewport.width) / 2f);
+ viewport.y = padding;
+ // camera aspect ratio must follow viewport aspect ration
+ viewportHeight = boardHeight;
+ viewportWidth = (viewportHeight * (viewport.width / viewport.height));
+ // hud
+ hud.y = 0;
+ hud.x = (hud.y * viewportWidth / viewportHeight);
+ } else if (diff > ZEROF) {
+ // word is wider than screen : use max width, height grows up until max available on zooming
+ viewport.width = screenAvailableWidth;
+ viewport.height = java.lang.Math.min((screenAvailableWidth / boardAspectRatio / zoom), screenAvailableHeight);
+ viewport.y = padding + ((screenAvailableHeight - viewport.height) / 2f);
+ viewport.x = padding;
+ // camera aspect ratio must follow viewport aspect ration
+ viewportWidth = boardWidth;
+ viewportHeight = (viewportWidth * (viewport.height / viewport.width));
+ // hud
+ hud.x = 0;
+ hud.y = (hud.x * viewportHeight / viewportWidth);
+ }
+
+ if (hudInBoard) {
+ hud.x = 0;
+ hud.y = 0;
+ hud.width = screenWidth;
+ hud.height = screenHeight;
+ } else {
+ hud.width = (viewport.width - (2 * hud.x));
+ hud.height = (viewport.height - (2 * hud.y));
+ }
+
+ // ratio viewport -> camera
+ widthFactor = (viewportWidth / viewport.width);
+ heightFactor = (viewportHeight / viewport.height);
+
+ clampPosition();
+ update(true);
+
+ hudMatrix.setToOrtho2D(hud.x, hud.y, hud.width, hud.height);
+ hudInvProjMatrix.set(hudMatrix);
+ Matrix4.inv(hudInvProjMatrix.val);
+ }
+
+ public void applyMapViewport()
+ {
+ HdpiUtils.glViewport((int)viewport.x, (int)viewport.y, (int)viewport.width, (int)viewport.height);
+ }
+
+ public void applyHudViewport()
+ {
+ if (hudInBoard)
+ HdpiUtils.glViewport(0, 0, screenWidth, screenHeight);
+ else
+ applyMapViewport();
+ }
+
+ public void centerOnWorld()
+ {
+ position.set((boardWidth / 2f), (boardHeight / 2f), 0f);
+ }
+
+ public void zoom(float dz)
+ {
+ zoom += dz;
+ clampZoom();
+ }
+
+ public void clampZoom()
+ {
+ zoom = MathUtils.clamp(zoom, zoomMin, zoomMax);
+ }
+
+ public void translate(float dx, float dy)
+ {
+ float deltaX = (dx * zoom * widthFactor);
+ float deltaY = (dy * zoom * heightFactor);
+ translate(deltaX, -deltaY, 0);
+ clampPosition();
+ update(true);
+ }
+
+ public void clampPosition()
+ {
+ float cameraWidth = (viewportWidth * zoom);
+ float cameraHeight = (viewportHeight * zoom);
+
+ // on each axis, clamp on [ cameraDim/2 ; boardDim - cameraDim/2 ]
+
+ if ((boardWidth - cameraWidth) > ZEROF) {
+ cameraWidth /= 2f;
+ position.x = MathUtils.clamp(position.x, cameraWidth, (boardWidth - cameraWidth));
+ } else {
+ position.x = (boardWidth / 2f);
+ }
+
+ if ((boardHeight - cameraHeight) > ZEROF) {
+ cameraHeight /= 2f;
+ position.y = MathUtils.clamp(position.y, cameraHeight, (boardHeight - cameraHeight));
+ } else {
+ position.y = (boardHeight / 2f);
+ }
+ }
+
+ public void unproject(int x, int y, Vector3 v)
+ {
+ unproject(v.set(x, y, 0), viewport.x, viewport.y, viewport.width, viewport.height);
+ }
+
+ public void unprojectHud(float x, float y, Vector3 v)
+ {
+ Rectangle r = (hudInBoard ? hud : viewport);
+ x = x - r.x;
+ y = screenHeight - y - 1;
+ y = y - r.y;
+ v.x = (2 * x) / r.width - 1;
+ v.y = (2 * y) / r.height - 1;
+ v.z = 2 * v.z - 1;
+ v.prj(hudInvProjMatrix);
+ }
+
+ public int getScreenWidth()
+ {
+ return screenWidth;
+ }
+
+ public int getScreenHeight()
+ {
+ return screenHeight;
+ }
+
+ public Matrix4 getHudMatrix()
+ {
+ return hudMatrix;
+ }
+
+ public Rectangle getViewport()
+ {
+ return viewport;
+ }
+
+ public Rectangle getHud()
+ {
+ return hud;
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/Drawable.java b/core/src/ch/asynk/gdx/board/Drawable.java
new file mode 100644
index 0000000..53559dc
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/Drawable.java
@@ -0,0 +1,19 @@
+package ch.asynk.gdx.board;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+public interface Drawable
+{
+ public float getX();
+ public float getY();
+ public float getWidth();
+ public float getHeight();
+ public float getInnerX();
+ public float getInnerY();
+ public float getInnerWidth();
+ public float getInnerHeight();
+ public void draw(Batch batch);
+ default public void drawDebug(ShapeRenderer debugShapes) { }
+ public void setPosition(float x, float y, float w, float h);
+}
diff --git a/core/src/ch/asynk/gdx/board/Touchable.java b/core/src/ch/asynk/gdx/board/Touchable.java
new file mode 100644
index 0000000..533ec96
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/Touchable.java
@@ -0,0 +1,6 @@
+package ch.asynk.gdx.board;
+
+public interface Touchable
+{
+ public boolean touch(float x, float y);
+}
diff --git a/core/src/ch/asynk/gdx/board/board/BoardFactory.java b/core/src/ch/asynk/gdx/board/board/BoardFactory.java
new file mode 100644
index 0000000..fc30e72
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/board/BoardFactory.java
@@ -0,0 +1,40 @@
+package ch.asynk.gdx.board.board;
+
+import ch.asynk.gdx.board.Board;
+
+public class BoardFactory
+{
+ public enum BoardType
+ {
+ HEX, SQUARE,
+ }
+
+ public enum BoardOrientation
+ {
+ VERTICAL,
+ HORIZONTAL,
+ }
+
+ public static Board getBoard(BoardType boardType, float side)
+ {
+ return getBoard(boardType, side, 0f, 0f, BoardOrientation.VERTICAL);
+ }
+
+ public static Board getBoard(BoardType boardType, float side, float x0, float y0)
+ {
+ return getBoard(boardType, side, x0, y0, BoardOrientation.VERTICAL);
+ }
+
+ public static Board getBoard(BoardType boardType, float side, float x0, float y0, BoardOrientation boardOrientation)
+ {
+ switch(boardType)
+ {
+ case HEX:
+ return new HexBoard(side, x0, y0, boardOrientation);
+ case SQUARE:
+ return new SquareBoard(side, x0, y0);
+ default:
+ throw new RuntimeException( String.format("%s board type is not implemented yet.", boardType) );
+ }
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/board/HexBoard.java b/core/src/ch/asynk/gdx/board/board/HexBoard.java
new file mode 100644
index 0000000..9d56a7c
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/board/HexBoard.java
@@ -0,0 +1,144 @@
+package ch.asynk.gdx.board.board;
+
+import com.badlogic.gdx.math.Vector2;
+
+import ch.asynk.gdx.board.Board;
+
+public class HexBoard implements Board
+{
+ private float side; // length of the side of the hex
+ private float x0; // bottom left x offset
+ private float y0; // bottom left y offset
+ private BoardFactory.BoardOrientation orientation;
+
+ private float w; // side to side orthogonal distance
+ private float dw; // half hex : w/2
+ private float dh; // hex top : s/2
+ private float h; // square height : s + dh
+ private float slope; // dh / dw
+
+ // BoardOrientation.VERTICAL : 2 vertical sides : 2 vertices pointing up and down
+ // coordinates
+ // \
+ // \___
+ // cols are horizontal
+ // rows are at -120°
+ // bottom left is the bottom vertice of the most bottom-left vertical hex side of the map
+ //
+ // BoardOrientation.HORIZONTAL : 2 horizontal sides : 2 vertices pointing left and right
+ // coordinates
+ // |
+ // |
+ // \
+ // \
+ // cols are at +120°
+ // rows are vertical°
+ // bottom left is the left vertice of the most bottom-left horizontal hex side of the map
+
+ public HexBoard(float side, float x0, float y0, BoardFactory.BoardOrientation boardOrientation)
+ {
+ this.side = side;
+ this.x0 = x0;
+ this.y0 = y0;
+ this.orientation = boardOrientation;
+
+ this.w = side * 1.73205f;
+ this.dw = w / 2.0f;
+ this.dh = side / 2.0f;
+ this.h = side + dh;
+ this.slope = dh / dw;
+ }
+
+ @Override public void centerOf(int x, int y, Vector2 v)
+ {
+ float cx = this.x0;
+ float cy = this.y0;
+
+ if (this.orientation == BoardFactory.BoardOrientation.VERTICAL) {
+ cx += (this.dw + (x * this.w) - (y * this.dw));
+ cy += (this.dh + (y * this.h));
+ } else {
+ cx += (this.dh + (x * this.h));
+ cy += (this.dw + (y * this.w) - (x * this.dw));
+ }
+
+ v.set(cx, cy);
+ }
+
+ @Override public void toBoard(float x, float y, Vector2 v)
+ {
+ int col = -1;
+ int row = -1;
+
+ if (this.orientation == BoardFactory.BoardOrientation.VERTICAL) {
+ // compute row
+ float dy = y - this.y0;
+ row = (int) (dy / this.h);
+ if (dy < 0.f) {
+ row -= 1;
+ }
+
+ // compute col
+ float dx = x - this.x0 + (row * this.dw);
+ col = (int) (dx / this.w);
+ if (dx < 0f) {
+ col -= 1;
+ }
+
+ // upper rectangle or hex body
+ if (dy > ((row * this.h) + this.side)) {
+ dy -= ((row * this.h) + this.side);
+ dx -= (col * this.w);
+ // upper left or right rectangle
+ if (dx < this.dw) {
+ if (dy > (dx * this.slope)) {
+ // upper left hex
+ row += 1;
+ }
+ } else {
+ // if (dy > ((2 * this.dh) - (dx * this.slope))) {
+ if (dy > ((this.w - dx) * this.slope)) {
+ // upper right hex
+ row += 1;
+ col += 1;
+ }
+ }
+ }
+ } else {
+ // compute col
+ float dx = x - this.x0;
+ col = (int) (dx / this.h);
+ if (dx < 0.f) {
+ col -= 1;
+ }
+
+ // compute row
+ float dy = y - this.y0 + (col * this.dw);
+ row = (int) (dy / this.w);
+ if (dy < 0f) {
+ row -= 1;
+ }
+
+ // right rectangle or hex body
+ if (dx > ((col * this.h) + this.side)) {
+ dx -= ((col * this.h) + this.side);
+ dy -= (row * this.w);
+ // upper or lower rectangle
+ if (dy > ((this.dw - dx) / this.slope)) {
+ if (dy > ((2 * this.dw) - (dx / this.slope))) {
+ // upper right hex
+ col += 1;
+ row += 1;
+ }
+ } else {
+ if (dy < (dx / this.slope)) {
+ // lower right hex
+ col += 1;
+ }
+ }
+ }
+ }
+
+ v.set(col, row);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/board/SquareBoard.java b/core/src/ch/asynk/gdx/board/board/SquareBoard.java
new file mode 100644
index 0000000..f8da8d3
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/board/SquareBoard.java
@@ -0,0 +1,35 @@
+package ch.asynk.gdx.board.board;
+
+import com.badlogic.gdx.math.Vector2;
+
+import ch.asynk.gdx.board.Board;
+
+public class SquareBoard implements Board
+{
+ private float side; // length of the side of a square
+ private float x0; // bottom left x offset
+ private float y0; // bottom left y offset
+
+ public SquareBoard(float side, float x0, float y0)
+ {
+ this.side = side;
+ this.x0 = x0;
+ this.y0 = y0;
+ }
+
+ @Override public void centerOf(int x, int y, Vector2 v)
+ {
+ float cx = this.x0 + (this.side / 2) + (this.side * x);
+ float cy = this.y0 + (this.side / 2) + (this.side * y);
+
+ v.set(cx, cy);
+ }
+
+ @Override public void toBoard(float x, float y, Vector2 v)
+ {
+ int col = (int) ((x - this.x0) / this.side);
+ int row = (int) ((y - this.y0) / this.side);
+
+ v.set(col, row);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/ui/Alignment.java b/core/src/ch/asynk/gdx/board/ui/Alignment.java
new file mode 100644
index 0000000..cb8f4a2
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/ui/Alignment.java
@@ -0,0 +1,168 @@
+package ch.asynk.gdx.board.ui;
+
+public enum Alignment
+{
+ ABSOLUTE, // Root
+ RELATIVE, // Default
+ TOP_LEFT,
+ TOP_RIGHT,
+ TOP_CENTER,
+ MIDDLE_LEFT,
+ MIDDLE_RIGHT,
+ MIDDLE_CENTER,
+ BOTTOM_LEFT,
+ BOTTOM_RIGHT,
+ BOTTOM_CENTER;
+
+ public Alignment verticalMirror()
+ {
+ switch(this) {
+ case TOP_LEFT:
+ return TOP_RIGHT;
+ case MIDDLE_LEFT:
+ return MIDDLE_RIGHT;
+ case BOTTOM_LEFT:
+ return BOTTOM_RIGHT;
+ case TOP_RIGHT:
+ return TOP_LEFT;
+ case MIDDLE_RIGHT:
+ return MIDDLE_LEFT;
+ case BOTTOM_RIGHT:
+ return BOTTOM_LEFT;
+ }
+ return this;
+ }
+
+ public Alignment horizontalMirror()
+ {
+ switch(this) {
+ case TOP_LEFT:
+ return BOTTOM_LEFT;
+ case TOP_CENTER:
+ return BOTTOM_CENTER;
+ case TOP_RIGHT:
+ return BOTTOM_RIGHT;
+ case BOTTOM_LEFT:
+ return TOP_LEFT;
+ case BOTTOM_CENTER:
+ return TOP_CENTER;
+ case BOTTOM_RIGHT:
+ return TOP_RIGHT;
+ }
+ return this;
+ }
+
+ public boolean isTop()
+ {
+ switch(this) {
+ case TOP_LEFT:
+ case TOP_CENTER:
+ case TOP_RIGHT:
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isMiddle()
+ {
+ boolean r = false;
+ switch(this) {
+ case MIDDLE_LEFT:
+ case MIDDLE_CENTER:
+ case MIDDLE_RIGHT:
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isBottom()
+ {
+ boolean r = false;
+ switch(this) {
+ case BOTTOM_LEFT:
+ case BOTTOM_CENTER:
+ case BOTTOM_RIGHT:
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isLeft()
+ {
+ boolean r = false;
+ switch(this) {
+ case TOP_LEFT:
+ case MIDDLE_LEFT:
+ case BOTTOM_LEFT:
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isRight()
+ {
+ boolean r = false;
+ switch(this) {
+ case TOP_RIGHT:
+ case MIDDLE_RIGHT:
+ case BOTTOM_RIGHT:
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isCenter()
+ {
+ switch(this) {
+ case TOP_CENTER:
+ case MIDDLE_CENTER:
+ case BOTTOM_CENTER:
+ return true;
+ }
+ return false;
+ }
+
+ public float getX(Element element, float width)
+ {
+ float x = element.getInnerX();
+ switch(this) {
+ case TOP_LEFT:
+ case MIDDLE_LEFT:
+ case BOTTOM_LEFT:
+ break;
+ case TOP_CENTER:
+ case MIDDLE_CENTER:
+ case BOTTOM_CENTER:
+ x += ((element.getInnerWidth() - width) / 2);
+ break;
+ case TOP_RIGHT:
+ case MIDDLE_RIGHT:
+ case BOTTOM_RIGHT:
+ x += (element.getInnerWidth() - width);
+ break;
+ }
+ return x;
+ }
+
+ public float getY(Element element, float height)
+ {
+ float y = element.getInnerY();
+ switch(this) {
+ case TOP_LEFT:
+ case TOP_CENTER:
+ case TOP_RIGHT:
+ y += (element.getInnerHeight() - height);
+ break;
+ case MIDDLE_LEFT:
+ case MIDDLE_CENTER:
+ case MIDDLE_RIGHT:
+ y += ((element.getInnerHeight() - height) / 2);
+ break;
+ case BOTTOM_LEFT:
+ case BOTTOM_CENTER:
+ case BOTTOM_RIGHT:
+ break;
+ }
+ return y;
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/ui/Assembly.java b/core/src/ch/asynk/gdx/board/ui/Assembly.java
new file mode 100644
index 0000000..b7d329e
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/ui/Assembly.java
@@ -0,0 +1,62 @@
+package ch.asynk.gdx.board.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.gdx.board.util.IterableSet;
+
+public abstract class Assembly extends Element
+{
+ private IterableSet<Element> children;
+ private Element touched;
+
+ public Assembly(int c)
+ {
+ this.children = new IterableSet<Element>(c);
+ }
+
+ public void add(Element e)
+ {
+ if (children.add(e)) {
+ e.setParent(this);
+ }
+ }
+
+ public void remove(Element e)
+ {
+ if (children.remove(e)) {
+ e.setParent(null);
+ }
+ }
+
+ public Element touched()
+ {
+ return touched;
+ }
+
+ @Override public boolean touch(float x, float y)
+ {
+ for (Element e : children)
+ if (e.touch(x, y)) {
+ touched = e;
+ return true;
+ }
+ touched = null;
+ return false;
+ }
+
+ @Override public void update()
+ {
+ children.forEach( c -> c.update() );
+ }
+
+ @Override public void draw(Batch batch)
+ {
+ children.forEach( c -> c.draw(batch) );
+ }
+
+ @Override public void drawDebug(ShapeRenderer debugShapes)
+ {
+ children.forEach( c -> c.drawDebug(debugShapes) );
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/ui/Button.java b/core/src/ch/asynk/gdx/board/ui/Button.java
new file mode 100644
index 0000000..f5f771a
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/ui/Button.java
@@ -0,0 +1,65 @@
+package ch.asynk.gdx.board.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.NinePatch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+public class Button extends Patch
+{
+ private Label label;
+ private float spacing;
+
+ public Button(BitmapFont font, NinePatch patch)
+ {
+ this(font, patch, 0, 0);
+ }
+
+ public Button(BitmapFont font, NinePatch patch, float padding, float spacing)
+ {
+ super(patch);
+ this.padding = padding;
+ this.spacing = spacing;
+ label = new Label(font);
+ label.setParent(this);
+ label.setAlignment(Alignment.MIDDLE_CENTER);
+ }
+
+ public void write(String text)
+ {
+ write(text, getX(), getY());
+ }
+
+ public void write(String text, float x, float y)
+ {
+ label.write(text, x, y);
+ }
+
+ public void setLabelAlignment(Alignment alignment)
+ {
+ label.setAlignment(alignment);
+ }
+
+ @Override public void update()
+ {
+ label.preUpdate(); // compute width and height
+ rect.width = label.getWidth() + 2 * (padding + spacing);
+ rect.height = label.getHeight() + 2 * (padding + spacing);
+ super.update();
+ label.doUpdate(); // compute x and y
+ label.postUpdate(); // compute fx and fy
+ }
+
+ @Override public void draw(Batch batch)
+ {
+ if (!visible) return;
+ super.draw(batch);
+ label.draw(batch);
+ }
+
+ @Override public void drawDebug(ShapeRenderer debugShapes)
+ {
+ super.drawDebug(debugShapes);
+ label.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/ui/Element.java b/core/src/ch/asynk/gdx/board/ui/Element.java
new file mode 100644
index 0000000..09523b5
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/ui/Element.java
@@ -0,0 +1,112 @@
+package ch.asynk.gdx.board.ui;
+
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+import com.badlogic.gdx.math.Rectangle;
+
+import ch.asynk.gdx.board.Drawable;
+import ch.asynk.gdx.board.Touchable;
+
+public abstract class Element implements Drawable, Touchable
+{
+ public boolean blocked;
+ public boolean visible;
+ protected float padding;
+ protected Element parent;
+ protected Alignment alignment;
+ protected Rectangle rect; // drawing coordinates
+ protected float x, y; // given position
+
+ protected Element()
+ {
+ this.blocked = false;
+ this.visible = true;
+ this.padding = 0;
+ this.rect = new Rectangle(0, 0, 0, 0);
+ this.parent = null;
+ this.alignment = alignment.RELATIVE;
+ this.x = this.y = 0;
+ }
+
+ @Override public final float getX() { return rect.x; }
+ @Override public final float getY() { return rect.y; }
+ @Override public final float getWidth() { return rect.width; }
+ @Override public final float getHeight() { return rect.height; }
+
+ @Override public final float getInnerX() { return rect.x + padding; }
+ @Override public final float getInnerY() { return rect.y + padding; }
+ @Override public final float getInnerWidth() { return rect.width - 2 * padding; }
+ @Override public final float getInnerHeight() { return rect.height - 2 * padding; }
+
+ @Override public void drawDebug(ShapeRenderer debugShapes)
+ {
+ debugShapes.rect(getX(), getY(), getWidth(), getHeight());
+ }
+
+ @Override public boolean touch(float x, float y)
+ {
+ if (blocked || !visible) return false;
+ return rect.contains(x, y);
+ }
+
+ @Override public void setPosition(float x, float y, float w, float h)
+ {
+ this.x = x;
+ this.y = y;
+ rect.width = w;
+ rect.height = h;
+ // rect.set(rec.x, y, w, h);
+ }
+
+ public void setParent(Element parent)
+ {
+ this.parent = parent;
+ }
+
+ public void setPadding(float padding)
+ {
+ this.padding = padding;
+ }
+
+ public void setAlignment(Alignment alignment)
+ {
+ this.alignment = alignment;
+ }
+
+ public final void translate(float dx, float dy)
+ {
+ setPosition(x + dx, y + dy);
+ }
+
+ public final void setPosition(Rectangle r)
+ {
+ setPosition(r.x, r.x, r.width, r.height);
+ }
+
+ public final void setPosition(float x, float y)
+ {
+ setPosition(x, y, rect.width, rect.height);
+ }
+
+ protected void preUpdate() { }
+ protected void postUpdate() { }
+ protected void doUpdate()
+ {
+ if (alignment == Alignment.ABSOLUTE || parent == null) {
+ rect.x = x;
+ rect.y = y;
+ } else if (alignment == Alignment.RELATIVE) {
+ rect.x = x + parent.getInnerX();
+ rect.y = y + parent.getInnerX();
+ } else {
+ rect.x = x + alignment.getX(parent, rect.width);
+ rect.y = y + alignment.getY(parent, rect.height);
+ }
+ }
+
+ public void update()
+ {
+ preUpdate();
+ doUpdate();
+ postUpdate();
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/ui/Label.java b/core/src/ch/asynk/gdx/board/ui/Label.java
new file mode 100644
index 0000000..05dfc8c
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/ui/Label.java
@@ -0,0 +1,62 @@
+package ch.asynk.gdx.board.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.GlyphLayout;
+
+public class Label extends Element
+{
+ private BitmapFont font;
+ private GlyphLayout layout;
+ private float fx;
+ private float fy;
+ private String text;
+
+ public Label(BitmapFont font)
+ {
+ this(font, 0);
+ }
+
+ public Label(BitmapFont font, float padding)
+ {
+ this(font, padding, Alignment.RELATIVE);
+ }
+
+ public Label(BitmapFont font, float padding, Alignment alignment)
+ {
+ super();
+ this.font = font;
+ this.padding = padding;
+ this.alignment = alignment;
+ this.layout = new GlyphLayout();
+ }
+
+ public void write(String text)
+ {
+ write(text, getX(), getY());
+ }
+
+ public void write(String text, float x, float y)
+ {
+ this.text = text;
+ setPosition(x, y);
+ }
+
+ @Override protected void preUpdate()
+ {
+ this.layout.setText(font, (text == null) ? "" : text);
+ super.setPosition(x, y, (layout.width + (2 * padding)), (layout.height + (2 * padding)));
+ }
+
+ @Override protected void postUpdate()
+ {
+ fx = getInnerX();
+ fy = getInnerY() + layout.height;
+ }
+
+ @Override public void draw(Batch batch)
+ {
+ if (!visible) return;
+ font.draw(batch, layout, fx, fy);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/ui/Patch.java b/core/src/ch/asynk/gdx/board/ui/Patch.java
new file mode 100644
index 0000000..aa86c9f
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/ui/Patch.java
@@ -0,0 +1,22 @@
+package ch.asynk.gdx.board.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.NinePatch;
+
+public class Patch extends Element
+{
+ private NinePatch patch;
+
+ public Patch(NinePatch patch)
+ {
+ super();
+ this.patch = patch;
+ setPosition(0, 0, patch.getTotalWidth(), patch.getTotalHeight());
+ }
+
+ @Override public void draw(Batch batch)
+ {
+ if (!visible) return;
+ patch.draw(batch, getX(), getY(), getWidth(), getHeight());
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/ui/Root.java b/core/src/ch/asynk/gdx/board/ui/Root.java
new file mode 100644
index 0000000..95d9ed5
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/ui/Root.java
@@ -0,0 +1,16 @@
+package ch.asynk.gdx.board.ui;
+
+public class Root extends Assembly
+{
+ public Root(int c)
+ {
+ super(c);
+ this.alignment = Alignment.ABSOLUTE;
+ }
+
+ public void resize(float width, float height)
+ {
+ setPosition(0, 0, width, height);
+ update();
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/util/Collection.java b/core/src/ch/asynk/gdx/board/util/Collection.java
new file mode 100644
index 0000000..8aadadf
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/util/Collection.java
@@ -0,0 +1,28 @@
+package ch.asynk.gdx.board.util;
+
+import java.util.Iterator;
+
+public interface Collection<E> extends Iterator, Iterable<E>
+{
+ public int size();
+
+ public boolean isEmpty();
+
+ public void clear();
+
+ public void ensureCapacity(int c);
+
+ public boolean contains(E e);
+
+ public E get(int idx);
+
+ public int indexOf(E e);
+
+ public boolean add(E e);
+
+ public boolean insert(E e, int idx);
+
+ public E remove(int idx);
+
+ public boolean remove(E e);
+}
diff --git a/core/src/ch/asynk/gdx/board/util/IterableArray.java b/core/src/ch/asynk/gdx/board/util/IterableArray.java
new file mode 100644
index 0000000..2dc4481
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/util/IterableArray.java
@@ -0,0 +1,143 @@
+package ch.asynk.gdx.board.util;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+public class IterableArray<E> implements Collection<E>
+{
+ private int idx;
+ private int s;
+ private int c;
+ transient E[] data;
+
+ @SuppressWarnings("unchecked")
+ public IterableArray(int capacity)
+ {
+ this.s = 0;
+ this.c = capacity;
+ this.data = (E[]) new Object[c];
+ }
+
+ @Override public int size()
+ {
+ return s;
+ }
+
+ @Override public boolean isEmpty()
+ {
+ return (s == 0);
+ }
+
+ @Override public void clear()
+ {
+ for (int i = 0; i < s; i++)
+ data[i] = null;
+ s = 0;
+ }
+
+ @Override public void ensureCapacity(int min)
+ {
+ if (c > min) return;
+ c += (c >> 1);
+ if (c < min)
+ c = min;
+ data = Arrays.copyOf(data, c);
+ }
+
+ @Override public boolean contains(E e)
+ {
+ if (e == null) {
+ for (int i = 0; i < s; i++) {
+ if (data[i] == null)
+ return true;
+ }
+ } else {
+ for (int i = 0; i < s; i++) {
+ if (e.equals(data[i]))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override public E get(int i)
+ {
+ return data[i];
+ }
+
+ @Override public int indexOf(E e)
+ {
+ for (int i = 0; i < data.length; i++) {
+ if (data[i] != null && data[i].equals(e))
+ return i;
+ }
+ return -1;
+ }
+
+ @Override public boolean add(E e)
+ {
+ ensureCapacity(s + 1);
+ data[s] = e;
+ s += 1;
+ return true;
+ }
+
+ @Override public boolean insert(E e, int idx)
+ {
+ ensureCapacity(s + 1);
+ System.arraycopy(data, idx, data, idx+1, (s - idx));
+ data[idx] = e;
+ s += 1;
+ return true;
+ }
+
+ @Override public E remove(int i)
+ {
+ E e = data[i];
+ int m = (s - i - 1);
+ if (m > 0)
+ System.arraycopy(data, i+1, data, i, m);
+ data[--s] = null;
+
+ return e;
+ }
+
+ @Override public boolean remove(E e)
+ {
+ for (int i = 0; i < s; i++) {
+ if (e.equals(data[i])) {
+ int m = (s - i - 1);
+ if (m > 0)
+ System.arraycopy(data, i+1, data, i, m);
+ data[--s] = null;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override public Iterator<E> iterator()
+ {
+ this.idx = 0;
+ return (Iterator<E>) this;
+ }
+
+ @Override public boolean hasNext()
+ {
+ return (idx < s);
+ }
+
+ @Override public E next()
+ {
+ E e = get(idx);
+ idx += 1;
+ return e;
+ }
+
+ @Override public void remove()
+ {
+ idx -= 1;
+ remove(idx);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/util/IterableQueue.java b/core/src/ch/asynk/gdx/board/util/IterableQueue.java
new file mode 100644
index 0000000..8a9a74f
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/util/IterableQueue.java
@@ -0,0 +1,19 @@
+package ch.asynk.gdx.board.util;
+
+public class IterableQueue<E> extends IterableArray<E>
+{
+ public IterableQueue(int n)
+ {
+ super(n);
+ }
+
+ public void enqueue(E e)
+ {
+ add(e);
+ }
+
+ public E dequeue()
+ {
+ return remove(0);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/util/IterableSet.java b/core/src/ch/asynk/gdx/board/util/IterableSet.java
new file mode 100644
index 0000000..7e37f4e
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/util/IterableSet.java
@@ -0,0 +1,16 @@
+package ch.asynk.gdx.board.util;
+
+public class IterableSet<E> extends IterableArray<E>
+{
+ public IterableSet(int n)
+ {
+ super(n);
+ }
+
+ @Override public boolean add(E e)
+ {
+ if (contains(e)) return false;
+ super.add(e);
+ return true;
+ }
+}
diff --git a/core/src/ch/asynk/gdx/board/util/IterableStack.java b/core/src/ch/asynk/gdx/board/util/IterableStack.java
new file mode 100644
index 0000000..1365408
--- /dev/null
+++ b/core/src/ch/asynk/gdx/board/util/IterableStack.java
@@ -0,0 +1,26 @@
+package ch.asynk.gdx.board.util;
+
+public class IterableStack<E> extends IterableArray<E>
+{
+ public IterableStack(int n)
+ {
+ super(n);
+ }
+
+ public void push(E e)
+ {
+ add(e);
+ }
+
+ public E pop()
+ {
+ if (size() <= 0) return null;
+ return remove(size() - 1);
+ }
+
+ public E getTop()
+ {
+ if (size() <= 0) return null;
+ return get(size() - 1);
+ }
+}