package jrummikub.view.impl; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.Stroke; import java.awt.geom.GeneralPath; import java.awt.geom.Rectangle2D; import jrummikub.model.Position; import jrummikub.model.Stone; import jrummikub.model.StoneColor; /** * The StonePainter paints stones and converts between pixel and grid * coordinates */ class StonePainter { private static final float ASPECT_RATIO = 0.75f; private static final float DEFAULT_WIDTH = 40; private static final float TEXT_POS = 0.275f; private static final float FACE_WIDTH = 0.475f; private static final float CIRCLE_POS = 0.725f; private static final float CIRCLE_WIDTH = 0.45f; private static final Color BACKGROUND_COLOR = new Color(0.9f, 0.9f, 0.6f); private static final Color SELECTED_COLOR = BACKGROUND_COLOR.darker(); private static final float BRIGHTER_SCALE = 1.15f; /** * The width of one pixel in the scale of 1.0 */ public static final float WIDTH_SCALE = 1 / DEFAULT_WIDTH; /** * The height of one pixel in the scale of 1.0 */ public static final float HEIGHT_SCALE = ASPECT_RATIO / DEFAULT_WIDTH; private float scale; private static int even(float f) { return 2 * (int) (f / 2); } private static Color brighter(Color color) { int r = (int) (color.getRed() * BRIGHTER_SCALE); int g = (int) (color.getRed() * BRIGHTER_SCALE); int b = (int) (color.getRed() * BRIGHTER_SCALE); return new Color(r > 255 ? 255 : r, g > 255 ? 255 : g, b > 255 ? 255 : b); } private static Color getColor(StoneColor color) { switch (color) { case BLACK: return new Color(0.15f, 0.15f, 0.15f); case BLUE: return new Color(0.0f, 0.0f, 1.0f); case ORANGE: return new Color(1.0f, 0.6f, 0.0f); case RED: return new Color(0.9f, 0.0f, 0.25f); } return null; } /** * Sets the new grid scale * * @param scale * the new scale */ public void setScale(float scale) { this.scale = scale; } /** * @return the current grid scale */ public float getScale() { return scale; } /** * @param x * x position in screen coordinates * @param y * y position in screen coordinates * @return position in grid coordinates */ public Position calculatePosition(int x, int y) { float width = getStoneWidth(); float height = getStoneHeight(); return new Position(x / width, y / height); } /** * @return the width of a stone in the current scale in pixels */ public int getStoneWidth() { return even(DEFAULT_WIDTH * scale); } /** * @return the height of a stone in the current scale in pixels */ public int getStoneHeight() { return (int) (DEFAULT_WIDTH * scale / ASPECT_RATIO); } /** * @param scale * the scaling factor for the grid coordinates */ StonePainter(float scale) { this.scale = scale; } private void paintStoneBackground(Graphics2D g, Rectangle r, Color background) { // Paint background g.setColor(background); g.fillRect(r.x, r.y, r.width, r.height); // Paint bevel border g.setColor(brighter(brighter(background))); g.fillRect(r.x, r.y, 1, r.height); g.setColor(brighter(background)); g.fillRect(r.x + 1, r.y + 1, 1, r.height - 2); g.setColor(brighter(brighter(background))); g.fillRect(r.x, r.y, r.width, 1); g.setColor(brighter(background)); g.fillRect(r.x + 1, r.y + 1, r.width - 2, 1); g.setColor(background.darker().darker()); g.fillRect(r.x + r.width - 1, r.y, 1, r.height); g.setColor(background.darker()); g.fillRect(r.x + r.width - 2, r.y + 1, 1, r.height - 2); g.setColor(background.darker().darker()); g.fillRect(r.x, r.y + r.height - 1, r.width, 1); g.setColor(background.darker()); g.fillRect(r.x + 1, r.y + r.height - 2, r.width - 2, 1); } private void paintJokerFace(Graphics2D g, Rectangle r) { Stroke oldStroke = g.getStroke(); g.setStroke(new BasicStroke(2)); g.drawOval(r.x, r.y, r.width, r.height); g.setStroke(new BasicStroke(1)); GeneralPath path = new GeneralPath(); // nose path.moveTo(r.x + 0.5f * r.width, r.y + 0.45f * r.height); path.lineTo(r.x + 0.53f * r.width, r.y + 0.6f * r.height); path.lineTo(r.x + 0.47f * r.width, r.y + 0.6f * r.height); path.closePath(); g.fill(path); path.reset(); // mouth, left path.moveTo(r.x + 0.23f * r.width, r.y + 0.75f * r.width); path.lineTo(r.x + 0.27f * r.width, r.y + 0.65f * r.width); // mouth, middle path.moveTo(r.x + 0.25f * r.width, r.y + 0.7f * r.width); path.lineTo(r.x + 0.5f * r.width, r.y + 0.8f * r.width); path.lineTo(r.x + 0.75f * r.width, r.y + 0.7f * r.width); // mouth, right path.moveTo(r.x + 0.77f * r.width, r.y + 0.75f * r.width); path.lineTo(r.x + 0.73f * r.width, r.y + 0.65f * r.width); g.draw(path); path.reset(); // left eye path.moveTo(r.x + 0.3f * r.width, r.y + 0.41f * r.height); path.lineTo(r.x + 0.375f * r.width, r.y + 0.375f * r.height); path.lineTo(r.x + 0.3f * r.width, r.y + 0.34f * r.height); path.lineTo(r.x + 0.225f * r.width, r.y + 0.375f * r.height); path.closePath(); g.draw(path); path.reset(); // right eye path.moveTo(r.x + 0.7f * r.width, r.y + 0.41f * r.height); path.lineTo(r.x + 0.625f * r.width, r.y + 0.375f * r.height); path.lineTo(r.x + 0.7f * r.width, r.y + 0.34f * r.height); path.lineTo(r.x + 0.775f * r.width, r.y + 0.375f * r.height); path.closePath(); g.draw(path); g.setStroke(oldStroke); } private void paintJoker(Graphics2D g, Rectangle r, Color color) { int faceSize = even(FACE_WIDTH * r.width); int pos = r.y + (int) (TEXT_POS * r.height); g.setColor(color); paintJokerFace(g, new Rectangle(r.x + r.width / 2 - faceSize / 2, pos - faceSize / 2, faceSize, faceSize)); } private void paintStoneValue(Graphics2D g, Rectangle r, Color color, int v) { int pos = r.y + (int) (TEXT_POS * r.height); g.setFont(new Font("SansSerif", Font.BOLD, r.height / 4)); FontMetrics fm = g.getFontMetrics(); String value = Integer.toString(v); Rectangle2D stringRect = fm.getStringBounds(value, g); g.setColor(color.darker()); g.drawString(value, (int) (r.x + r.width / 2 - stringRect.getWidth() / 2) + 1, pos + (fm.getAscent() - fm.getDescent()) / 2 + 1); g.setColor(color); g.drawString(value, (int) (r.x + r.width / 2 - stringRect.getWidth() / 2), pos + (fm.getAscent() - fm.getDescent()) / 2); } private void paintCircle(Graphics2D g, Rectangle r, Color background) { int size = even(r.width * CIRCLE_WIDTH); int pos = r.y + (int) (CIRCLE_POS * r.height); // Paint circle g.setColor(background.darker()); g.drawArc(r.x + r.width / 2 - size / 2, pos - size / 2, size, size, 50, 170); g.setColor(brighter(background)); g.drawArc((int) (r.x + r.width / 2 - size / 2), pos - size / 2, size, size, -130, 170); } /** * Paints a stone * * @param g * the graphics context to paint the stone on * @param stone * the stone to paint * @param p * the position of the stone * @param selected * if selected is true the stone will be painted darker */ public void paintStone(Graphics2D g, Stone stone, Position p, boolean selected) { Color background = selected ? SELECTED_COLOR : BACKGROUND_COLOR; int width = getStoneWidth(); int height = getStoneHeight(); Rectangle rect = new Rectangle((int) (p.getX() * width), (int) (p.getY() * height), width, height); paintStoneBackground(g, rect, background); Color color = getColor(stone.getColor()); if (selected) color = color.darker(); if (stone.isJoker()) { paintJoker(g, rect, color); } else { paintStoneValue(g, rect, color, stone.getValue()); } paintCircle(g, rect, background); } }