diff options
Diffstat (limited to 'core/src/ch/asynk/gdx/boardgame')
22 files changed, 1496 insertions, 0 deletions
diff --git a/core/src/ch/asynk/gdx/boardgame/Assets.java b/core/src/ch/asynk/gdx/boardgame/Assets.java new file mode 100644 index 0000000..3402430 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/Assets.java @@ -0,0 +1,30 @@ +package ch.asynk.gdx.boardgame; + +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/boardgame/Board.java b/core/src/ch/asynk/gdx/boardgame/Board.java new file mode 100644 index 0000000..b1d82d0 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/Board.java @@ -0,0 +1,9 @@ +package ch.asynk.gdx.boardgame; + +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/boardgame/Camera.java b/core/src/ch/asynk/gdx/boardgame/Camera.java new file mode 100644 index 0000000..5140538 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/Camera.java @@ -0,0 +1,223 @@ +package ch.asynk.gdx.boardgame; + +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.width = (viewport.width - (2 * hud.x)); + hud.height = (viewport.height - (2 * hud.y)); + } else { + hud.x = 0; + hud.y = 0; + hud.width = screenWidth; + hud.height = screenHeight; + } + + // 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 applyScreenViewport() + { + HdpiUtils.glViewport(0, 0, screenWidth, screenHeight); + } + + public void applyBoardViewport() + { + HdpiUtils.glViewport((int)viewport.x, (int)viewport.y, (int)viewport.width, (int)viewport.height); + } + + public void applyHudViewport() + { + if (hudInBoard) + applyBoardViewport(); + else + applyScreenViewport(); + } + + 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 ? viewport : hud); + 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/boardgame/Drawable.java b/core/src/ch/asynk/gdx/boardgame/Drawable.java new file mode 100644 index 0000000..618d6a2 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/Drawable.java @@ -0,0 +1,19 @@ +package ch.asynk.gdx.boardgame; + +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/boardgame/Touchable.java b/core/src/ch/asynk/gdx/boardgame/Touchable.java new file mode 100644 index 0000000..0fd99d0 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/Touchable.java @@ -0,0 +1,6 @@ +package ch.asynk.gdx.boardgame; + +public interface Touchable +{ + public boolean touch(float x, float y); +} diff --git a/core/src/ch/asynk/gdx/boardgame/board/BoardFactory.java b/core/src/ch/asynk/gdx/boardgame/board/BoardFactory.java new file mode 100644 index 0000000..8dc73b6 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/board/BoardFactory.java @@ -0,0 +1,42 @@ +package ch.asynk.gdx.boardgame.board; + +import ch.asynk.gdx.boardgame.Board; + +public class BoardFactory +{ + public enum BoardType + { + HEX, SQUARE, TRIANGLE + } + + 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); + case TRIANGLE: + return new TriangleBoard(side, x0, y0, boardOrientation); + default: + throw new RuntimeException( String.format("%s board type is not implemented yet.", boardType) ); + } + } +} diff --git a/core/src/ch/asynk/gdx/boardgame/board/HexBoard.java b/core/src/ch/asynk/gdx/boardgame/board/HexBoard.java new file mode 100644 index 0000000..d5b2a26 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/board/HexBoard.java @@ -0,0 +1,136 @@ +package ch.asynk.gdx.boardgame.board; + +import com.badlogic.gdx.math.Vector2; + +import ch.asynk.gdx.boardgame.Board; + +public class HexBoard implements Board +{ + private final float side; // length of the side of the hex + private final float x0; // bottom left x offset + private final float y0; // bottom left y offset + private final BoardFactory.BoardOrientation orientation; + + private final float w; // side to side orthogonal distance + private final float dw; // half hex : w/2 + private final float dh; // hex top : s/2 + private final float h; // square height : s + dh + private final 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 < 0f) 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 < 0f) 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/boardgame/board/SquareBoard.java b/core/src/ch/asynk/gdx/boardgame/board/SquareBoard.java new file mode 100644 index 0000000..de2ab4c --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/board/SquareBoard.java @@ -0,0 +1,39 @@ +package ch.asynk.gdx.boardgame.board; + +import com.badlogic.gdx.math.Vector2; + +import ch.asynk.gdx.boardgame.Board; + +public class SquareBoard implements Board +{ + private final float side; // length of the side of a square + private final float x0; // bottom left x offset + private final 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) + { + float dx = x - this.x0; + float dy = y - this.y0; + int col = (int) (dx / this.side); + int row = (int) (dy / this.side); + if (dx < 0) col -=1; + if (dy < 0) row -=1; + + v.set(col, row); + } +} diff --git a/core/src/ch/asynk/gdx/boardgame/board/TriangleBoard.java b/core/src/ch/asynk/gdx/boardgame/board/TriangleBoard.java new file mode 100644 index 0000000..e3c9434 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/board/TriangleBoard.java @@ -0,0 +1,108 @@ +package ch.asynk.gdx.boardgame.board; + +import com.badlogic.gdx.math.Vector2; + +import ch.asynk.gdx.boardgame.Board; + +public class TriangleBoard implements Board +{ + private final float side; // length of the side of the equilateral triangle + private final float x0; // bottom left x offset + private final float y0; // bottom left y offset + private final BoardFactory.BoardOrientation orientation; + + private final float d; // side / 2 + private final float h; // height of the triangle + private final float m; // h / d + private final float h13; // 1/3 height of the triangle + private final float h23; // 2/3 height of the triangle + private final float h43; // 4/3 height of the triangle + + public TriangleBoard(float side, float x0, float y0, BoardFactory.BoardOrientation boardOrientation) + { + this.side = side; + this.x0 = x0; + this.y0 = y0; + this.orientation = boardOrientation; + + this.d = side / 2f; + this.h = side * 0.866f; + this.m = this.h / this.d; + this.h13 = this.h * 0.33333f; + this.h23 = this.h * 0.66666f; + this.h43 = this.h * 1.33333f; + } + + @Override public void centerOf(int x, int y, Vector2 v) + { + float cx = this.x0; + float cy = this.y0; + + if (this.orientation == BoardFactory.BoardOrientation.VERTICAL) { + cy += (y * this.d); + cx += ((x * this.h) + (((x + y) % 2 == 0) ? this.h23 : this.h13)); + } else { + cx += (this.d + (x * this.d)); + cy += ((y * this.h) + (((x + y) % 2 == 0) ? this.h13 : this.h23)); + } + + v.set(cx, cy); + } + + @Override public void toBoard(float x, float y, Vector2 v) + { + boolean vert = (this.orientation == BoardFactory.BoardOrientation.VERTICAL); + + float dx = x - this.x0; + float dy = y - this.y0; + float cx = (vert ? this.h : this.d); + float cy = (vert ? this.d : this.h); + + int col = (int) (dx / cx); + int row = (int) (dy / cy); + if (dx < 0) col -=1; + if (dy < 0) row -=1; + dx -= (col * cx); + dy -= (row * cy); + + if (vert) { + if (col % 2 == 0) { + if (row % 2 == 0) { + if (dy > (dx / this.m)) + row += 1; + } else { + if (dy + (dx / this.m) > d ) + row += 1; + } + } else { + if (row % 2 == 0) { + if (dy + (dx / this.m) > d ) + row += 1; + } else { + if (dy > (dx / this.m)) + row += 1; + } + } + } else { + if (row % 2 == 0) { + if (col % 2 == 0) { + if (dy > (dx * this.m)) + col -= 1; + } else { + if (dy + (dx * this.m) < h ) + col -= 1; + } + } else { + if (col % 2 == 0) { + if (dy + (dx * this.m) < h ) + col -= 1; + } else { + if (dy > (dx * this.m)) + col -= 1; + } + } + } + + v.set(col, row); + } +} diff --git a/core/src/ch/asynk/gdx/boardgame/ui/Alignment.java b/core/src/ch/asynk/gdx/boardgame/ui/Alignment.java new file mode 100644 index 0000000..67da089 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/ui/Alignment.java @@ -0,0 +1,168 @@ +package ch.asynk.gdx.boardgame.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/boardgame/ui/Assembly.java b/core/src/ch/asynk/gdx/boardgame/ui/Assembly.java new file mode 100644 index 0000000..c603383 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/ui/Assembly.java @@ -0,0 +1,63 @@ +package ch.asynk.gdx.boardgame.ui; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.gdx.boardgame.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 taint() + { + children.forEach( c -> c.taint() ); + } + + @Override public void draw(Batch batch) + { + if (tainted) computeGeometry(); + 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/boardgame/ui/Button.java b/core/src/ch/asynk/gdx/boardgame/ui/Button.java new file mode 100644 index 0000000..9bc1149 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/ui/Button.java @@ -0,0 +1,67 @@ +package ch.asynk.gdx.boardgame.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; // for label alignment; + + public Button(BitmapFont font, NinePatch patch) + { + this(font, patch, 0); + } + + public Button(BitmapFont font, NinePatch patch, float padding) + { + this(font, patch, padding, 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) + { + label.write(text); + this.tainted = true; // might impact Button's geometry + } + + public void setLabelAlignment(Alignment alignment) + { + label.setAlignment(alignment); + } + + @Override public void computeGeometry() + { + float dd = 2 * (padding + spacing); + label.computeGeometry(); // update dimensions + rect.width = label.getWidth() + dd; + rect.height = label.getHeight() + dd; + super.computeGeometry(); + label.computeGeometry(); // update position + } + + @Override public void draw(Batch batch) + { + if (!visible) return; + if (tainted) computeGeometry(); + 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/boardgame/ui/Element.java b/core/src/ch/asynk/gdx/boardgame/ui/Element.java new file mode 100644 index 0000000..25f38eb --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/ui/Element.java @@ -0,0 +1,116 @@ +package ch.asynk.gdx.boardgame.ui; + +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.math.Rectangle; + +import ch.asynk.gdx.boardgame.Drawable; +import ch.asynk.gdx.boardgame.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; // outer drawing coordinates + protected float x, y; // given position + protected boolean tainted; // geometry must be computed + + protected Element() + { + this.blocked = false; + this.visible = true; + this.padding = 0; + this.parent = null; + this.alignment = alignment.RELATIVE; + this.rect = new Rectangle(0, 0, 0, 0); + this.x = this.y = 0; + this.tainted = true; + } + + @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); + } + + public void taint() + { + this.tainted = true; + } + + @Override public void setPosition(float x, float y, float w, float h) + { + this.x = x; + this.y = y; + this.rect.width = w; + this.rect.height = h; + this.tainted = true; + // rect.(x,y) will be set in computeGeometry + } + + public void setParent(Element parent) + { + this.parent = parent; + this.tainted = true; + } + + public void setPadding(float padding) + { + this.padding = padding; + this.tainted = true; + } + + public void setAlignment(Alignment alignment) + { + this.alignment = alignment; + this.tainted = true; + } + + 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 computeGeometry() + { + 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); + } + this.tainted = false; + // System.err.println(String.format("%s : %s", this, rect)); + } +} diff --git a/core/src/ch/asynk/gdx/boardgame/ui/Label.java b/core/src/ch/asynk/gdx/boardgame/ui/Label.java new file mode 100644 index 0000000..32ae814 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/ui/Label.java @@ -0,0 +1,61 @@ +package ch.asynk.gdx.boardgame.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; + this.layout.setText(font, (text == null) ? "" : text); + this.tainted = true; + } + + @Override protected void computeGeometry() + { + this.rect.width = (layout.width + (2 * padding)); + this.rect.height = (layout.height + (2 * padding)); + super.computeGeometry(); + fx = getInnerX(); + fy = getInnerY() + layout.height; + } + + @Override public void draw(Batch batch) + { + if (!visible) return; + if (tainted) computeGeometry(); + font.draw(batch, layout, fx, fy); + } +} diff --git a/core/src/ch/asynk/gdx/boardgame/ui/Menu.java b/core/src/ch/asynk/gdx/boardgame/ui/Menu.java new file mode 100644 index 0000000..aa62483 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/ui/Menu.java @@ -0,0 +1,126 @@ +package ch.asynk.gdx.boardgame.ui; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.NinePatch; +import com.badlogic.gdx.graphics.g2d.BitmapFont; + +public class Menu extends Patch +{ + private Label title; + private Label[] entries; + private Integer touchedItem; + + private int entriesOffset; // horizontal offset + private int titleSpacing; // between title and entries + private int entriesSpacing; // between entries + + public Menu(BitmapFont font, NinePatch patch, String title, String[] entries) + { + super(patch); + this.touchedItem = null; + setTitle(font, title); + setEntries(font, entries); + } + + public void setTitle(BitmapFont font, String title) + { + this.title = new Label(font); + this.title.write(title); + this.tainted = true; + } + + public void setEntries(BitmapFont font, String[] entries) + { + this.entries = new Label[entries.length]; + for (int i = 0; i < entries.length; i++) { + Label l = new Label(font); + l.write(entries[i]); + this.entries[i] = l; + } + this.tainted = true; + } + + public void setLabelsOffset(int entriesOffset) + { + this.entriesOffset = entriesOffset; + this.tainted = true; + } + + public void setPaddings(int titlePadding, int labelPadding) + { + this.title.setPadding(titlePadding); + for (Label label : entries) { + label.setPadding(labelPadding); + } + this.tainted = true; + } + + public void setSpacings(int titleSpacing, int entriesSpacing) + { + this.titleSpacing = titleSpacing; + this.entriesSpacing = entriesSpacing; + this.tainted = true; + } + + @Override public void computeGeometry() + { + title.computeGeometry(); + float h = title.getHeight(); + float w = title.getWidth(); + for (Label label : entries) { + label.computeGeometry(); + h += label.getHeight(); + float t = label.getWidth() + entriesOffset; + if (t > w) + w = t; + } + h += (titleSpacing + (entriesSpacing * (entries.length - 1)) + (2 * padding)); + w += (2 * padding); + + rect.width = w; + rect.height = h; + super.computeGeometry(); + + float x = getInnerX(); + float y = getInnerY(); + + // setPosition() will trigger computeGeometry() from within draw() + for (int i = entries.length - 1; i >= 0; i--) { + entries[i].setPosition(x + entriesOffset, y); + y += entries[i].getHeight() + entriesSpacing; + } + y -= entriesSpacing; + y += titleSpacing; + title.setPosition(x, y); + } + + public Integer touched() + { + return touchedItem; + } + + @Override public boolean touch(float x, float y) + { + touchedItem = null; + if (super.touch(x, y)) { + for (int i = 0; i < entries.length; i++) { + if (entries[i].touch(x, y)) { + touchedItem = i; + return true; + } + } + } + return false; + } + + @Override public void draw(Batch batch) + { + if (!visible) return; + if (tainted) computeGeometry(); + super.draw(batch); + title.draw(batch); + for (Label label : entries) { + label.draw(batch); + } + } +} diff --git a/core/src/ch/asynk/gdx/boardgame/ui/Patch.java b/core/src/ch/asynk/gdx/boardgame/ui/Patch.java new file mode 100644 index 0000000..df2cc06 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/ui/Patch.java @@ -0,0 +1,23 @@ +package ch.asynk.gdx.boardgame.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; + if (tainted) computeGeometry(); + patch.draw(batch, getX(), getY(), getWidth(), getHeight()); + } +} diff --git a/core/src/ch/asynk/gdx/boardgame/ui/Root.java b/core/src/ch/asynk/gdx/boardgame/ui/Root.java new file mode 100644 index 0000000..2353c41 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/ui/Root.java @@ -0,0 +1,28 @@ +package ch.asynk.gdx.boardgame.ui; + +import com.badlogic.gdx.math.Rectangle; + +public class Root extends Assembly +{ + public Root(int c) + { + super(c); + this.alignment = Alignment.ABSOLUTE; + } + + public void resize(Rectangle r) + { + resize(r.x, r.y, r.width, r.height); + } + + public void resize(float width, float height) + { + resize(getX(), getY(), width, height); + } + + public void resize(float x, float y, float width, float height) + { + setPosition(x, y, width, height); + taint(); + } +} diff --git a/core/src/ch/asynk/gdx/boardgame/util/Collection.java b/core/src/ch/asynk/gdx/boardgame/util/Collection.java new file mode 100644 index 0000000..1d7d5e4 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/util/Collection.java @@ -0,0 +1,28 @@ +package ch.asynk.gdx.boardgame.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/boardgame/util/IterableArray.java b/core/src/ch/asynk/gdx/boardgame/util/IterableArray.java new file mode 100644 index 0000000..ba57de1 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/util/IterableArray.java @@ -0,0 +1,143 @@ +package ch.asynk.gdx.boardgame.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/boardgame/util/IterableQueue.java b/core/src/ch/asynk/gdx/boardgame/util/IterableQueue.java new file mode 100644 index 0000000..16ab442 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/util/IterableQueue.java @@ -0,0 +1,19 @@ +package ch.asynk.gdx.boardgame.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/boardgame/util/IterableSet.java b/core/src/ch/asynk/gdx/boardgame/util/IterableSet.java new file mode 100644 index 0000000..4132771 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/util/IterableSet.java @@ -0,0 +1,16 @@ +package ch.asynk.gdx.boardgame.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/boardgame/util/IterableStack.java b/core/src/ch/asynk/gdx/boardgame/util/IterableStack.java new file mode 100644 index 0000000..7400718 --- /dev/null +++ b/core/src/ch/asynk/gdx/boardgame/util/IterableStack.java @@ -0,0 +1,26 @@ +package ch.asynk.gdx.boardgame.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); + } +} |