package arcane.ui; import arcane.ui.util.CardPanelMouseListener; import forge.Card; import javax.swing.*; import java.awt.*; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** *

PlayArea class.

* * @author Forge * @version $Id$ */ public class PlayArea extends CardPanelContainer implements CardPanelMouseListener { /** Constant serialVersionUID=8333013579724492513L */ private static final long serialVersionUID = 8333013579724492513L; /** Constant GUTTER_Y=5 */ static private final int GUTTER_Y = 5; /** Constant GUTTER_X=5 */ static private final int GUTTER_X = 5; /** Constant EXTRA_CARD_SPACING_X=0.04f */ static final float EXTRA_CARD_SPACING_X = 0.04f; /** Constant CARD_SPACING_Y=0.06f */ static private final float CARD_SPACING_Y = 0.06f; /** Constant STACK_SPACING_X=0.07f */ static private final float STACK_SPACING_X = 0.07f; /** Constant STACK_SPACING_Y=0.07f */ static private final float STACK_SPACING_Y = 0.07f; private int landStackMax = 5; private boolean stackVertical; private boolean mirror; // Computed in layout. private List rows = new ArrayList(); private int cardWidth, cardHeight; private int playAreaWidth, playAreaHeight; private int extraCardSpacingX, cardSpacingX, cardSpacingY; private int stackSpacingX, stackSpacingY; /** *

Constructor for PlayArea.

* * @param scrollPane a {@link javax.swing.JScrollPane} object. * @param mirror a boolean. */ public PlayArea(JScrollPane scrollPane, boolean mirror) { super(scrollPane); setBackground(Color.white); this.mirror = mirror; } /** *

doLayout.

* * @since 1.0.15 */ public void doLayout() { int tokenStackMax = 5; // Collect lands. Row allLands = new Row(); outerLoop: // for (CardPanel panel : cardPanels) { if (!panel.gameCard.isLand()) continue; int insertIndex = -1; // Find lands with the same name. for (int i = 0, n = allLands.size(); i < n; i++) { Stack stack = allLands.get(i); CardPanel firstPanel = stack.get(0); if (firstPanel.gameCard.getName().equals(panel.gameCard.getName()) && !panel.gameCard.isCreature()) { if (!firstPanel.attachedPanels.isEmpty() || firstPanel.gameCard.isEnchanted()) { // Put this land to the left of lands with the same name and attachments. insertIndex = i; break; } if (!panel.attachedPanels.isEmpty() || !panel.gameCard.getCounters().equals(firstPanel.gameCard.getCounters()) || firstPanel.gameCard.isEnchanted() || stack.size() == landStackMax) { // If this land has attachments or the stack is full, put it to the right. insertIndex = i + 1; continue; } // Add to stack. stack.add(0, panel); continue outerLoop; } if (insertIndex != -1) break; } Stack stack = new Stack(); stack.add(panel); allLands.add(insertIndex == -1 ? allLands.size() : insertIndex, stack); } // Collect tokens. Row allTokens = new Row(); outerLoop: // for (CardPanel panel : cardPanels) { if (!panel.gameCard.isToken()) continue; int insertIndex = -1; // Find tokens with the same name. for (int i = 0, n = allTokens.size(); i < n; i++) { Stack stack = allTokens.get(i); CardPanel firstPanel = stack.get(0); if (firstPanel.gameCard.getName().equals(panel.gameCard.getName())) { if (!firstPanel.attachedPanels.isEmpty()) { // Put this token to the left of tokens with the same name and attachments. insertIndex = i; break; } if (!panel.attachedPanels.isEmpty() || !panel.gameCard.getCounters().equals(firstPanel.gameCard.getCounters()) || stack.size() == tokenStackMax) { // If this token has attachments or the stack is full, put it to the right. insertIndex = i + 1; continue; } // Add to stack. stack.add(0, panel); continue outerLoop; } if (insertIndex != -1) break; } Stack stack = new Stack(); stack.add(panel); allTokens.add(insertIndex == -1 ? allTokens.size() : insertIndex, stack); } Row allCreatures = new Row(cardPanels, RowType.creatureNonToken); Row allOthers = new Row(cardPanels, RowType.other); cardWidth = cardWidthMax; Rectangle rect = scrollPane.getVisibleRect(); playAreaWidth = rect.width; playAreaHeight = rect.height; while (true) { rows.clear(); cardHeight = Math.round(cardWidth * CardPanel.ASPECT_RATIO); extraCardSpacingX = Math.round(cardWidth * EXTRA_CARD_SPACING_X); cardSpacingX = cardHeight - cardWidth + extraCardSpacingX; cardSpacingY = Math.round(cardHeight * CARD_SPACING_Y); stackSpacingX = stackVertical ? 0 : (int) Math.round(cardWidth * STACK_SPACING_X); stackSpacingY = Math.round(cardHeight * STACK_SPACING_Y); Row creatures = (Row) allCreatures.clone(); Row tokens = (Row) allTokens.clone(); Row lands = (Row) allLands.clone(); Row others = (Row) allOthers.clone(); int afterFirstRow; if (mirror) { // Wrap all creatures and lands. wrap(lands, rows, -1); afterFirstRow = rows.size(); wrap(tokens, rows, afterFirstRow); wrap(creatures, rows, rows.size()); } else { // Wrap all creatures and lands. wrap(creatures, rows, -1); afterFirstRow = rows.size(); wrap(tokens, rows, afterFirstRow); wrap(lands, rows, rows.size()); } // Store the current rows and others. List storedRows = new ArrayList(rows.size()); for (Row row : rows) storedRows.add((Row) row.clone()); Row storedOthers = (Row) others.clone(); // Fill in all rows with others. for (Row row : rows) fillRow(others, rows, row); // Stop if everything fits, otherwise revert back to the stored values. if (creatures.isEmpty() && tokens.isEmpty() && lands.isEmpty() && others.isEmpty()) break; rows = storedRows; others = storedOthers; // Try to put others on their own row(s) and fill in the rest. wrap(others, rows, afterFirstRow); for (Row row : rows) fillRow(others, rows, row); // If that still doesn't fit, scale down. if (creatures.isEmpty() && tokens.isEmpty() && lands.isEmpty() && others.isEmpty()) break; cardWidth--; } // Get size of all the rows. int x, y = GUTTER_Y; int maxRowWidth = 0; for (Row row : rows) { int rowBottom = 0; x = GUTTER_X; for (int stackIndex = 0, stackCount = row.size(); stackIndex < stackCount; stackIndex++) { Stack stack = row.get(stackIndex); rowBottom = Math.max(rowBottom, y + stack.getHeight()); x += stack.getWidth(); } y = rowBottom; maxRowWidth = Math.max(maxRowWidth, x); } setPreferredSize(new Dimension(maxRowWidth - cardSpacingX, y - cardSpacingY)); revalidate(); // Position all card panels. x = 0; y = GUTTER_Y; for (Row row : rows) { int rowBottom = 0; x = GUTTER_X; for (int stackIndex = 0, stackCount = row.size(); stackIndex < stackCount; stackIndex++) { Stack stack = row.get(stackIndex); // Align others to the right. if (RowType.other.isType(stack.get(0).gameCard)) { x = playAreaWidth - GUTTER_X + extraCardSpacingX; for (int i = stackIndex, n = row.size(); i < n; i++) x -= row.get(i).getWidth(); } for (int panelIndex = 0, panelCount = stack.size(); panelIndex < panelCount; panelIndex++) { CardPanel panel = stack.get(panelIndex); int stackPosition = panelCount - panelIndex - 1; setComponentZOrder(panel, panelIndex); int panelX = x + (stackPosition * stackSpacingX); int panelY = y + (stackPosition * stackSpacingY); panel.setCardBounds(panelX, panelY, cardWidth, cardHeight); } rowBottom = Math.max(rowBottom, y + stack.getHeight()); x += stack.getWidth(); } y = rowBottom; } } /** *

wrap.

* * @param sourceRow a {@link arcane.ui.PlayArea.Row} object. * @param rows a {@link java.util.List} object. * @param insertIndex a int. * @return a int. */ private int wrap(Row sourceRow, List rows, int insertIndex) { // The cards are sure to fit (with vertical scrolling) at the minimum card width. boolean allowHeightOverflow = cardWidth == cardWidthMin; Row currentRow = new Row(); for (int i = 0, n = sourceRow.size() - 1; i <= n; i++) { Stack stack = sourceRow.get(i); // If the row is not empty and this stack doesn't fit, add the row. int rowWidth = currentRow.getWidth(); if (!currentRow.isEmpty() && rowWidth + stack.getWidth() > playAreaWidth) { // Stop processing if the row is too wide or tall. if (!allowHeightOverflow && rowWidth > playAreaWidth) break; if (!allowHeightOverflow && getRowsHeight(rows) + sourceRow.getHeight() > playAreaHeight) break; rows.add(insertIndex == -1 ? rows.size() : insertIndex, currentRow); currentRow = new Row(); } currentRow.add(stack); } // Add the last row if it is not empty and it fits. if (!currentRow.isEmpty()) { int rowWidth = currentRow.getWidth(); if (allowHeightOverflow || rowWidth <= playAreaWidth) { if (allowHeightOverflow || getRowsHeight(rows) + sourceRow.getHeight() <= playAreaHeight) { rows.add(insertIndex == -1 ? rows.size() : insertIndex, currentRow); } } } // Remove the wrapped stacks from the source row. for (Row row : rows) for (Stack stack : row) sourceRow.remove(stack); return insertIndex; } /** *

fillRow.

* * @param sourceRow a {@link arcane.ui.PlayArea.Row} object. * @param rows a {@link java.util.List} object. * @param rows a {@link java.util.List} object. * @param row a {@link arcane.ui.PlayArea.Row} object. */ private void fillRow(Row sourceRow, List rows, Row row) { int rowWidth = row.getWidth(); while (!sourceRow.isEmpty()) { Stack stack = sourceRow.get(0); rowWidth += stack.getWidth(); if (rowWidth > playAreaWidth) break; if (stack.getHeight() > row.getHeight()) { if (getRowsHeight(rows) - row.getHeight() + stack.getHeight() > playAreaHeight) break; } row.add(sourceRow.remove(0)); } } /** *

getRowsHeight.

* * @param rows a {@link java.util.List} object. * @return a int. */ private int getRowsHeight(List rows) { int height = 0; for (Row row : rows) height += row.getHeight(); return height - cardSpacingY + GUTTER_Y * 2; } /** {@inheritDoc} */ public CardPanel getCardPanel(int x, int y) { for (Row row : rows) { for (Stack stack : row) { for (CardPanel panel : stack) { int panelX = panel.getCardX(); int panelY = panel.getCardY(); int panelWidth, panelHeight; if (panel.tapped) { panelWidth = panel.getCardHeight(); panelHeight = panel.getCardWidth(); panelY += panelWidth - panelHeight; } else { panelWidth = panel.getCardWidth(); panelHeight = panel.getCardHeight(); } if (x > panelX && x < panelX + panelWidth) { if (y > panelY && y < panelY + panelHeight) { if (!panel.isDisplayEnabled()) return null; return panel; } } } } } return null; } /** {@inheritDoc} */ public void mouseLeftClicked(CardPanel panel, MouseEvent evt) { if (panel.tappedAngle != 0 && panel.tappedAngle != CardPanel.TAPPED_ANGLE) return; super.mouseLeftClicked(panel, evt); } /** *

Getter for the field landStackMax.

* * @return a int. */ public int getLandStackMax() { return landStackMax; } /** *

Setter for the field landStackMax.

* * @param landStackMax a int. */ public void setLandStackMax(int landStackMax) { this.landStackMax = landStackMax; } /** *

Getter for the field stackVertical.

* * @return a boolean. */ public boolean getStackVertical() { return stackVertical; } /** *

Setter for the field stackVertical.

* * @param stackVertical a boolean. */ public void setStackVertical(boolean stackVertical) { this.stackVertical = stackVertical; } static private enum RowType { land, creature, creatureNonToken, other; public boolean isType(Card card) { switch (this) { case land: return card.isLand(); case creature: return card.isCreature(); case creatureNonToken: return card.isCreature() && !card.isToken(); case other: return !card.isLand() && !card.isCreature(); default: throw new RuntimeException("Unhandled type: " + this); } } } private class Row extends ArrayList { private static final long serialVersionUID = 716489891951011846L; public Row() { super(16); } public Row(List cardPanels, RowType type) { this(); addAll(cardPanels, type); } private void addAll(List cardPanels, RowType type) { for (CardPanel panel : cardPanels) { if (!type.isType(panel.gameCard) || panel.attachedToPanel != null) continue; Stack stack = new Stack(); stack.add(panel); add(stack); } } public boolean addAll(Collection c) { boolean changed = super.addAll(c); c.clear(); return changed; } private int getWidth() { if (isEmpty()) return 0; int width = 0; for (Stack stack : this) width += stack.getWidth(); return width + GUTTER_X * 2 - extraCardSpacingX; } private int getHeight() { if (isEmpty()) return 0; int height = 0; for (Stack stack : this) height = Math.max(height, stack.getHeight()); return height; } } private class Stack extends ArrayList { private static final long serialVersionUID = 3863135156832080368L; public Stack() { super(8); } public boolean add(CardPanel panel) { boolean appended = super.add(panel); for (CardPanel attachedPanel : panel.attachedPanels) add(attachedPanel); return appended; } private int getWidth() { return cardWidth + (size() - 1) * stackSpacingX + cardSpacingX; } private int getHeight() { return cardHeight + (size() - 1) * stackSpacingY + cardSpacingY; } } }