git-svn-id: svn://sunsvr01.isp.uni-luebeck.de/swproj13/trunk@390 72836036-5685-4462-b002-a69064685172
372 lines
11 KiB
Java
372 lines
11 KiB
Java
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.RenderingHints;
|
|
import java.awt.Stroke;
|
|
import java.awt.geom.GeneralPath;
|
|
import java.awt.geom.Rectangle2D;
|
|
import java.awt.image.BufferedImage;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
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 double ASPECT_RATIO = 0.75f;
|
|
private static final double DEFAULT_WIDTH = 40;
|
|
private static final double TEXT_POS = 0.275f;
|
|
private static final double FACE_WIDTH = 0.475f;
|
|
private static final double CIRCLE_POS = 0.725f;
|
|
private static final double CIRCLE_WIDTH = 0.45f;
|
|
|
|
private static final Color BACKGROUND_COLOR = new Color(0.9f, 0.9f, 0.6f);
|
|
|
|
private static final double BRIGHTER_SCALE = 1.15f;
|
|
private static final double HOVER_RATIO = 0.7f;
|
|
|
|
private Map<StoneColor, Map<Integer, BufferedImage>> defaultStones;
|
|
private Map<StoneColor, Map<Integer, BufferedImage>> selectedStones;
|
|
private Map<StoneColor, Map<Integer, BufferedImage>> hoveredStones;
|
|
private Map<StoneColor, Map<Integer, BufferedImage>> hoveredSelectedStones;
|
|
|
|
/**
|
|
* The width of one pixel in the scale of 1.0
|
|
*/
|
|
public static final double WIDTH_SCALE = 1 / DEFAULT_WIDTH;
|
|
/**
|
|
* The height of one pixel in the scale of 1.0
|
|
*/
|
|
public static final double HEIGHT_SCALE = ASPECT_RATIO / DEFAULT_WIDTH;
|
|
|
|
private double scale;
|
|
|
|
private static int even(double f) {
|
|
return 2 * (int) (f / 2);
|
|
}
|
|
|
|
private static Color brighter(Color color) {
|
|
int r = (int) (color.getRed() * BRIGHTER_SCALE);
|
|
int g = (int) (color.getGreen() * BRIGHTER_SCALE);
|
|
int b = (int) (color.getBlue() * BRIGHTER_SCALE);
|
|
|
|
return new Color(r > 255 ? 255 : r, g > 255 ? 255 : g, b > 255 ? 255 : b);
|
|
}
|
|
|
|
private static Color hover(Color color) {
|
|
int r = (int) (color.getRed() * HOVER_RATIO + 255 * (1 - HOVER_RATIO));
|
|
int g = (int) (color.getGreen() * HOVER_RATIO + 255 * (1 - HOVER_RATIO));
|
|
int b = (int) (color.getBlue() * HOVER_RATIO + 255 * (1 - HOVER_RATIO));
|
|
|
|
return new Color(r > 255 ? 255 : r, g > 255 ? 255 : g, b > 255 ? 255 : b);
|
|
}
|
|
|
|
public static Color getColor(StoneColor color) {
|
|
switch (color) {
|
|
case BLACK:
|
|
return new Color(0.0f, 0.0f, 0.0f);
|
|
case BLUE:
|
|
return new Color(0.0f, 0.0f, 1.0f);
|
|
case ORANGE:
|
|
return new Color(1.0f, 0.4f, 0.0f);
|
|
case RED:
|
|
return new Color(0.9f, 0.0f, 0.25f);
|
|
case AQUA:
|
|
return new Color(0.0f, 0.85f, 0.75f);
|
|
case GREEN:
|
|
return new Color(0.0f, 0.65f, 0.0f);
|
|
case VIOLET:
|
|
return new Color(0.75f, 0.325f, 0.75f);
|
|
case GRAY:
|
|
return new Color(0.5f, 0.5f, 0.5f);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Sets the new grid scale
|
|
*
|
|
* @param scale
|
|
* the new scale
|
|
*/
|
|
public void setScale(double scale) {
|
|
this.scale = scale;
|
|
|
|
if (this.scale == 0) {
|
|
this.scale = 1;
|
|
}
|
|
|
|
resetPrepaint();
|
|
}
|
|
|
|
/**
|
|
* @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) {
|
|
double width = getStoneWidth();
|
|
double 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 Math.max(even(DEFAULT_WIDTH * scale), 1);
|
|
}
|
|
|
|
/**
|
|
* @return the height of a stone in the current scale in pixels
|
|
*/
|
|
public int getStoneHeight() {
|
|
return Math.max((int) (DEFAULT_WIDTH * scale / ASPECT_RATIO), 1);
|
|
}
|
|
|
|
private BufferedImage prepaintStone(Color fg, Color bg, int value) {
|
|
Rectangle r = new Rectangle(0, 0, getStoneWidth(), getStoneHeight());
|
|
BufferedImage img = new BufferedImage(r.width, r.height,
|
|
BufferedImage.TYPE_INT_RGB);
|
|
Graphics2D g = img.createGraphics();
|
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
|
RenderingHints.VALUE_ANTIALIAS_ON);
|
|
|
|
paintStoneBackground(g, r, bg);
|
|
|
|
if (value == 0) {
|
|
paintJoker(g, r, fg);
|
|
} else {
|
|
paintStoneValue(g, r, fg, value);
|
|
}
|
|
|
|
paintCircle(g, r, bg);
|
|
|
|
return img;
|
|
}
|
|
|
|
private BufferedImage getStoneImage(StoneColor color, int value,
|
|
boolean selected, boolean hovered) {
|
|
Map<StoneColor, Map<Integer, BufferedImage>> stoneMap;
|
|
|
|
if (selected) {
|
|
if (hovered) {
|
|
stoneMap = hoveredSelectedStones;
|
|
} else {
|
|
stoneMap = selectedStones;
|
|
}
|
|
} else {
|
|
if (hovered) {
|
|
stoneMap = hoveredStones;
|
|
} else {
|
|
stoneMap = defaultStones;
|
|
}
|
|
}
|
|
|
|
BufferedImage image = stoneMap.get(color).get(value);
|
|
|
|
if (image == null) {
|
|
Color background = BACKGROUND_COLOR;
|
|
Color foreground = getColor(color);
|
|
if (selected) {
|
|
background = background.darker();
|
|
foreground = foreground.darker();
|
|
}
|
|
if (hovered) {
|
|
background = hover(background);
|
|
foreground = hover(foreground);
|
|
}
|
|
|
|
image = prepaintStone(foreground, background, value);
|
|
|
|
stoneMap.get(color).put(value, image);
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
private void resetPrepaint() {
|
|
defaultStones = new HashMap<StoneColor, Map<Integer, BufferedImage>>();
|
|
selectedStones = new HashMap<StoneColor, Map<Integer, BufferedImage>>();
|
|
hoveredStones = new HashMap<StoneColor, Map<Integer, BufferedImage>>();
|
|
hoveredSelectedStones = new HashMap<StoneColor, Map<Integer, BufferedImage>>();
|
|
|
|
for (StoneColor color : StoneColor.values()) {
|
|
defaultStones.put(color, new HashMap<Integer, BufferedImage>());
|
|
selectedStones.put(color, new HashMap<Integer, BufferedImage>());
|
|
hoveredStones.put(color, new HashMap<Integer, BufferedImage>());
|
|
hoveredSelectedStones.put(color, new HashMap<Integer, BufferedImage>());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param scale
|
|
* the scaling factor for the grid coordinates
|
|
*/
|
|
StonePainter(double scale) {
|
|
setScale(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 = (v > 0) ? Integer.toString(v) : Character
|
|
.toString((char) (-v));
|
|
Rectangle2D stringRect = fm.getStringBounds(value, g);
|
|
|
|
if (scale > 1) {
|
|
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
|
|
* @param hovered
|
|
* if hovered is true the stone will be painted brighter
|
|
*/
|
|
public void paintStone(Graphics2D g, Stone stone, Position p,
|
|
boolean selected, boolean hovered) {
|
|
int width = getStoneWidth();
|
|
int height = getStoneHeight();
|
|
int x = (int) Math.round(p.getX() * width), y = (int) Math.round(p.getY()
|
|
* height);
|
|
|
|
if (stone.isJoker()) {
|
|
g.drawImage(getStoneImage(stone.getColor(), 0, selected, hovered), x, y,
|
|
null);
|
|
} else {
|
|
g.drawImage(
|
|
getStoneImage(stone.getColor(), stone.getValue(), selected, hovered),
|
|
x, y, null);
|
|
}
|
|
}
|
|
}
|