package jrummikub.view.impl; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Point; import java.awt.Shape; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import javax.swing.ImageIcon; import javax.swing.SwingUtilities; import jrummikub.model.Position; import jrummikub.model.Stone; import jrummikub.model.StoneColor; import jrummikub.model.StoneSet; import jrummikub.util.Event1; import jrummikub.util.IListener1; import jrummikub.util.Pair; import jrummikub.view.IStoneCollectionPanel; import jrummikub.view.ITablePanel; import jrummikub.view.impl.StonePainter.StoneState; /** * The implementation of the table */ @SuppressWarnings("serial") class TablePanel extends AbstractStonePanel implements ITablePanel { private final static ImageIcon BACKGROUND = new ImageIcon( HandPanel.class.getResource("/jrummikub/resource/felt.png")); private final static ImageIcon DARK_BACKGROUND = new ImageIcon( HandPanel.class.getResource("/jrummikub/resource/dark_felt.png")); private final static ImageIcon BRIGHT_BACKGROUND = new ImageIcon( HandPanel.class.getResource("/jrummikub/resource/bright_felt.png")); private final static double MIN_VISIBLE_WIDTH = 10; private final static double MIN_VISIBLE_HEIGHT = 5; private final static double HORIZONTAL_MARGIN = 1; private final static double VERTICAL_MARGIN = 0.7f; private final static double CONNECTOR_WIDTH = 0.25f; private final double COLLECTION_RATIO = 0.12f; private final int COLLECTION_GAP = 5; private StoneCollectionPanel stoneCollection; private Iterable> stoneSets = Collections.emptySet(); private List> pauseStoneSets; private Collection invalidStoneSets = Collections.emptyList(); private Collection selectedStones = Collections.emptyList(); private Event1 leftConnectorClickEvent = new Event1(); private Event1 rightConnectorClickEvent = new Event1(); private StoneSet leftHoveredConnector; private StoneSet rightHoveredConnector; private boolean pauseMode = false; @Override public Event1 getLeftConnectorClickEvent() { return leftConnectorClickEvent; } @Override public Event1 getRightConnectorClickEvent() { return rightConnectorClickEvent; } @Override public void setStoneSets(Iterable> stoneSets) { List> stones = new ArrayList>(); for (Pair entry : stoneSets) { double x = entry.getSecond().getX(), y = entry.getSecond().getY(); for (Stone stone : entry.getFirst()) { stones.add(new Pair(stone, new Position(x, y))); x++; } } setStones(stones); this.stoneSets = stoneSets; setScale(); repaint(); } @Override public IStoneCollectionPanel getStoneCollectionPanel() { return stoneCollection; } /** * Sets the currently selected stones * * @param stones * the selected stones */ void setSelectedStones(Collection stones) { selectedStones = stones; stoneCollection.setSelectedStones(stones); repaint(); } void setInvalidStoneSets(Collection sets) { invalidStoneSets = sets; repaint(); } void createPauseStoneSets() { pauseStoneSets = new ArrayList>(); Stone stoneP = new Stone(-'P', StoneColor.BLACK); Stone stonea = new Stone(-'a', StoneColor.ORANGE); Stone stoneu = new Stone(-'u', StoneColor.BLUE); Stone stones = new Stone(-'s', StoneColor.RED); Stone stonee = new Stone(-'e', StoneColor.BLACK); pauseStoneSets.add(new Pair(new StoneSet(Arrays.asList( stoneP, stonea, stoneu, stones, stonee)), new Position(-2.5, 0))); } /** * Creates a new Table instance */ TablePanel() { setLayout(null); createPauseStoneSets(); stoneCollection = new StoneCollectionPanel(); stoneCollection.getOtherClickEvent().add(new IListener1() { @Override public void handle(Point p) { Point p2 = SwingUtilities.convertPoint(stoneCollection, p, TablePanel.this); // theres nothing below here clickAt(p2, 1, false, false, true); } }); add(stoneCollection); addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { rescale(); } }); } private Rectangle2D calculateTableExtent() { double minx = -MIN_VISIBLE_WIDTH / 2, maxx = MIN_VISIBLE_WIDTH / 2; double miny = -MIN_VISIBLE_HEIGHT / 2, maxy = MIN_VISIBLE_HEIGHT / 2; for (Pair entry : (pauseMode ? pauseStoneSets : stoneSets)) { Position p = entry.getSecond(); StoneSet stoneSet = entry.getFirst(); if (p.getX() < minx) minx = p.getX(); if (p.getY() < miny) miny = p.getY(); if (p.getX() + stoneSet.getSize() > maxx) maxx = p.getX() + stoneSet.getSize(); if (p.getY() + 1 > maxy) maxy = p.getY() + 1; } return new Rectangle2D.Double(minx - HORIZONTAL_MARGIN, miny - VERTICAL_MARGIN, maxx - minx + 2 * HORIZONTAL_MARGIN, maxy - miny + 2 * VERTICAL_MARGIN); } private void rescale() { Insets insets = getInsets(); int x = insets.left, y = insets.top, width = getWidth() - insets.left - insets.right, height = getHeight() - insets.top - insets.bottom; int collectionHeight = (int) (height * COLLECTION_RATIO); stoneCollection .setBounds(x, y + height - collectionHeight - COLLECTION_GAP, width, collectionHeight); setScale(); repaint(); } private void setScale() { Insets insets = getInsets(); int width = getWidth() - insets.left - insets.right, height = getHeight() - insets.top - insets.bottom; Rectangle2D extent = calculateTableExtent(); double widthScale = width / (double) extent.getWidth() * StonePainter.WIDTH_SCALE; double heightScale = height * (1 - COLLECTION_RATIO) / (double) extent.getHeight() * StonePainter.HEIGHT_SCALE; getStonePainter().setScale(Math.min(widthScale, heightScale)); } @Override protected boolean handleOtherClickEvent(Position pos) { for (Pair entry : stoneSets) { Position p = entry.getSecond(); StoneSet stoneSet = entry.getFirst(); double x = p.getX(), y = p.getY(); // left connector Rectangle2D rect = new Rectangle2D.Double(x - CONNECTOR_WIDTH, y, CONNECTOR_WIDTH, 1); if (rect.contains(pos.getX(), pos.getY())) { leftConnectorClickEvent.emit(stoneSet); return true; } // right connector rect = new Rectangle2D.Double(x + stoneSet.getSize(), y, CONNECTOR_WIDTH, 1); if (rect.contains(pos.getX(), pos.getY())) { rightConnectorClickEvent.emit(stoneSet); return true; } } return false; } @Override protected void handleOtherMoveEvent(Position pos) { StoneSet oldLeftHoveredConnector = leftHoveredConnector; StoneSet oldRightHoveredConnector = rightHoveredConnector; leftHoveredConnector = null; rightHoveredConnector = null; for (Pair entry : stoneSets) { Position p = entry.getSecond(); StoneSet stoneSet = entry.getFirst(); double x = p.getX(), y = p.getY(); // left connector Rectangle2D rect = new Rectangle2D.Double(x - CONNECTOR_WIDTH, y, CONNECTOR_WIDTH, 1); if (rect.contains(pos.getX(), pos.getY())) { leftHoveredConnector = stoneSet; break; } // right connector rect = new Rectangle2D.Double(x + stoneSet.getSize(), y, CONNECTOR_WIDTH, 1); if (rect.contains(pos.getX(), pos.getY())) { rightHoveredConnector = stoneSet; break; } } if (leftHoveredConnector != oldLeftHoveredConnector || rightHoveredConnector != oldRightHoveredConnector) { repaint(); } } @Override protected Pair getTranslation() { Insets insets = getInsets(); int width = getWidth() - insets.left - insets.right, height = getHeight() - insets.top - insets.bottom; int stoneWidth = getStonePainter().getStoneWidth(), stoneHeight = getStonePainter() .getStoneHeight(); Rectangle2D extent = calculateTableExtent(); return new Pair((int) (width / 2 - extent.getCenterX() * stoneWidth), (int) ((height * (1 - COLLECTION_RATIO)) / 2 - extent.getCenterY() * stoneHeight)); } private void paintStoneSet(Graphics2D g, StoneSet stoneSet, Position pos, Area connectorArea, Area hoveredConnectorArea) { double x = pos.getX(); int width = getStonePainter().getStoneWidth(), height = getStonePainter() .getStoneHeight(); Area leftConnectorArea = (stoneSet == leftHoveredConnector ? hoveredConnectorArea : connectorArea); Area rightConnectorArea = (stoneSet == rightHoveredConnector ? hoveredConnectorArea : connectorArea); // Left connector leftConnectorArea.add(new Area(new Rectangle2D.Double(Math.round(x * width) - (int) width * CONNECTOR_WIDTH + 1, Math.round(pos.getY() * height), (int) (width * CONNECTOR_WIDTH), height))); for (Stone stone : stoneSet) { StoneState state = invalidStoneSets.contains(stoneSet) ? StoneState.INVALID : selectedStones.contains(stone) ? StoneState.SELECTED : StoneState.NORMAL; getStonePainter().paintStone(g, stone, new Position(x, pos.getY()), state, stone == getHoveredStone()); x++; } // Right connector rightConnectorArea.add(new Area(new Rectangle2D.Double(Math .round(x * width), Math.round(pos.getY() * height), (int) (width * CONNECTOR_WIDTH), height))); } @Override protected void paintComponent(Graphics g1) { Graphics2D g = (Graphics2D) g1; for (int x = 0; x < getWidth(); x += BACKGROUND.getIconWidth()) { for (int y = 0; y < getHeight(); y += BACKGROUND.getIconHeight()) { BACKGROUND.paintIcon(this, g, x, y); } } AffineTransform oldTransform = g.getTransform(); Shape oldClip = g.getClip(); Pair translation = getTranslation(); g.translate(translation.getFirst(), translation.getSecond()); Area connectorArea = new Area(); Area hoveredConnectorArea = new Area(); for (Pair entry : (pauseMode ? pauseStoneSets : stoneSets)) { paintStoneSet(g, entry.getFirst(), entry.getSecond(), connectorArea, hoveredConnectorArea); } if (pauseMode) { g.setTransform(oldTransform); return; } g.setClip(connectorArea); g.setTransform(oldTransform); for (int x = 0; x < getWidth(); x += DARK_BACKGROUND.getIconWidth()) { for (int y = 0; y < getHeight(); y += DARK_BACKGROUND.getIconHeight()) { DARK_BACKGROUND.paintIcon(this, g, x, y); } } g.setClip(oldClip); g.translate(translation.getFirst(), translation.getSecond()); g.setClip(hoveredConnectorArea); g.setTransform(oldTransform); for (int x = 0; x < getWidth(); x += BRIGHT_BACKGROUND.getIconWidth()) { for (int y = 0; y < getHeight(); y += BRIGHT_BACKGROUND.getIconHeight()) { BRIGHT_BACKGROUND.paintIcon(this, g, x, y); } } g.setClip(oldClip); } void enablePauseMode(boolean enable) { stoneCollection.setHidden(enable); pauseMode = enable; setScale(); repaint(); } }