* GUI: new reworked GUI and card render engine, card icons and dozens of other fixes (see full list in related PR);

This commit is contained in:
Oleg Agafonov 2021-01-30 16:38:55 +04:00
parent df98cc3e62
commit a1da5ef437
304 changed files with 7266 additions and 5093 deletions

View file

@ -1,6 +1,7 @@
package mage.client.cards;
import mage.cards.Card;
import mage.abilities.icon.CardIconRenderSettings;
import mage.cards.MageCard;
import mage.cards.decks.DeckCardInfo;
import mage.cards.decks.DeckCardLayout;
@ -11,22 +12,23 @@ import mage.client.constants.Constants;
import mage.client.dialog.PreferencesDialog;
import mage.client.plugins.impl.Plugins;
import mage.client.util.*;
import mage.client.util.Event;
import mage.client.util.comparators.*;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.util.DebugUtil;
import mage.util.RandomUtil;
import mage.view.CardView;
import mage.view.CardsView;
import org.apache.log4j.Logger;
import org.mage.card.arcane.CardRenderer;
import org.mage.card.arcane.ManaSymbols;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.*;
import java.util.List;
import java.util.*;
import java.util.regex.Matcher;
@ -34,12 +36,16 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Created by StravantUser on 2016-09-20.
* @author StravantUser, JayDi85
*/
public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarget {
public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarget, CardEventProducer {
private static final Logger logger = Logger.getLogger(DragCardGrid.class);
private static final String DOUBLE_CLICK_MODE_INFO = "<html>Double click mode: <b>%s</b>";
private static final Logger LOGGER = Logger.getLogger(DragCardGrid.class);
private Constants.DeckEditorMode mode;
Listener<Event> cardListener;
MouseListener countLabelListener; // clicks on the count label
@Override
public Collection<CardView> dragCardList() {
@ -66,7 +72,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
if (card.isSelected()) {
stack.set(i, null);
removeCardView(card);
eventSource.fireEvent(card, ClientEventType.REMOVE_SPECIFIC_CARD);
eventSource.fireEvent(card, ClientEventType.DECK_REMOVE_SPECIFIC_CARD);
}
}
}
@ -88,6 +94,11 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
showDropPosition(e.getX(), e.getY());
}
@Override
public CardEventSource getCardEventSource() {
return this.eventSource;
}
private void showDropPosition(int x, int y) {
// Clamp to region
if (x < 0) {
@ -320,7 +331,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (CardView card : cards) {
card.setSelected(true);
addCardView(card, false);
eventSource.fireEvent(card, ClientEventType.ADD_SPECIFIC_CARD);
eventSource.fireEvent(card, ClientEventType.DECK_ADD_SPECIFIC_CARD);
}
layoutGrid();
repaintGrid();
@ -361,21 +372,33 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
creatureCountLabel.setVisible(false);
landCountLabel.setVisible(false);
cardSizeSliderLabel.setVisible(false);
mouseDoubleClickMode.setVisible(false);
} else {
creatureCountLabel.setVisible(true);
landCountLabel.setVisible(true);
cardSizeSliderLabel.setVisible(true);
mouseDoubleClickMode.setVisible(true);
}
updateCounts();
}
private void updateMouseDoubleClicksInfo(boolean isHotKeyPressed) {
boolean gameMode = isHotKeyPressed
|| mode != Constants.DeckEditorMode.FREE_BUILDING;
String oldText = mouseDoubleClickMode.getText();
String newText = String.format(DOUBLE_CLICK_MODE_INFO, gameMode ? "MOVE" : "DELETE");
if (!oldText.equals(newText)) {
mouseDoubleClickMode.setText(newText);
}
}
public void removeSelection() {
for (List<List<CardView>> gridRow : cardGrid) {
for (List<CardView> stack : gridRow) {
for (int i = 0; i < stack.size(); ++i) {
CardView card = stack.get(i);
if (card.isSelected()) {
eventSource.fireEvent(card, ClientEventType.REMOVE_SPECIFIC_CARD);
eventSource.fireEvent(card, ClientEventType.DECK_REMOVE_SPECIFIC_CARD);
stack.set(i, null);
removeCardView(card);
}
@ -406,13 +429,11 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
public void setDeckEditorMode(Constants.DeckEditorMode mode) {
this.mode = mode;
updateMouseDoubleClicksInfo(false);
}
public enum Sort {
NONE("No Sort", (o1, o2) -> {
// Always equal, sort into the first row
return 0;
}),
NONE("No Sort", new CardViewNoneComparator()),
CARD_TYPE("Card Type", new CardViewCardTypeComparator()),
CMC("Converted Mana Cost", new CardViewCostComparator()),
COLOR("Color", new CardViewColorComparator()),
@ -420,12 +441,12 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
RARITY("Rarity", new CardViewRarityComparator()),
EDH_POWER_LEVEL("EDH Power Level", new CardViewEDHPowerLevelComparator());
Sort(String text, Comparator<CardView> comparator) {
Sort(String text, CardViewComparator comparator) {
this.comparator = comparator;
this.text = text;
}
public Comparator<CardView> getComparator() {
public CardViewComparator getComparator() {
return comparator;
}
@ -433,7 +454,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
return text;
}
private final Comparator<CardView> comparator;
private final CardViewComparator comparator;
private final String text;
}
@ -537,7 +558,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
}
// Constants
public static final int COUNT_LABEL_HEIGHT = 20;
public static final int COUNT_LABEL_HEIGHT = 40; // can contains 1 or 2 lines
public static final int GRID_PADDING = 10;
private static final ImageIcon INSERT_ROW_ICON = new ImageIcon(DragCardGrid.class.getClassLoader().getResource("editor_insert_row.png"));
@ -547,7 +568,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
private final Map<UUID, MageCard> cardViews = new LinkedHashMap<>();
private final List<CardView> allCards = new ArrayList<>();
// Card listeners
// card listeners
private final CardEventSource eventSource = new CardEventSource();
// Last big card
@ -561,6 +582,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
JButton analyseButton;
JButton blingButton;
JButton oldVersionButton;
JLabel mouseDoubleClickMode;
// Popup for toolbar
final JPopupMenu filterPopup;
@ -696,7 +718,6 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
cardSizeSlider.setValue(size);
}
// Constructor
public DragCardGrid() {
// Make sure that the card grid is populated with at least one (empty) stack to begin with
cardGrid = new ArrayList<>();
@ -705,13 +726,55 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
setLayout(new BorderLayout());
setOpaque(false);
// Editting mode
// default game mode (real game mode will be set after all components init)
this.mode = Constants.DeckEditorMode.LIMITED_BUILDING;
// Content
cardContent = new JLayeredPane();
cardContent.setLayout(null);
cardContent.setOpaque(false);
// ENABLE MOUSE CLICKS (cards, menu)
this.cardListener = event -> {
switch(event.getEventType()) {
case CARD_POPUP_MENU:
if (event.getSource() != null) {
// menu for card
CardView card = (CardView) event.getSource();
MouseEvent me = event.getMouseEvent();
if (!card.isSelected()) {
selectCard(card);
}
showCardRightClickMenu(card, me);
}
break;
case CARD_CLICK:
if (event.getSource() != null) {
CardView card = (CardView) event.getSource();
MouseEvent me = event.getMouseEvent();
cardClicked(card, me);
}
break;
}
};
eventSource.addListener(this.cardListener);
// keyboard listener for ALT status update
// it requires GLOBAL listener
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addKeyEventDispatcher(new KeyEventDispatcher() {
@Override
public boolean dispatchKeyEvent(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ALT) {
updateMouseDoubleClicksInfo(e.isAltDown());
}
return false;
}
});
// ENABLE MULTI-CARDS SELECTION
cardContent.addMouseListener(new MouseAdapter() {
private boolean isDragging = false;
@ -739,6 +802,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
updateSelectionDrag(e.getX(), e.getY());
}
});
cardScroll = new JScrollPane(cardContent,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
@ -757,6 +821,13 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
analyseButton = new JButton("M"); // "Mana" button
blingButton = new JButton("B"); // "Bling" button
oldVersionButton = new JButton("O"); // "Old version" button
mouseDoubleClickMode = new JLabel(DOUBLE_CLICK_MODE_INFO);
mouseDoubleClickMode.setToolTipText("<html>Mouse modes for double clicks:"
+ "<br> * &lt;Double Click&gt;: <b>DELETE</b> card from mainboard/sideboard (it works as <b>MOVE</b> in games);"
+ "<br> * &lt;ALT + Double Click&gt;: <b>MOVE</b> card between mainboard/sideboard (default for games);"
+ "<br> * Deck editor: use &lt;ALT + Double Click&gt; on cards list to add card to the sideboard instead mainboard."
);
updateMouseDoubleClicksInfo(false);
// Name and count label
deckNameAndCountLabel = new JLabel();
@ -782,6 +853,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
toolbarInner.add(analyseButton);
toolbarInner.add(blingButton);
toolbarInner.add(oldVersionButton);
toolbarInner.add(mouseDoubleClickMode);
toolbar.add(toolbarInner, BorderLayout.WEST);
JPanel sliderPanel = new JPanel(new GridBagLayout());
sliderPanel.setOpaque(false);
@ -809,12 +881,12 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
insertArrow = new JLabel();
insertArrow.setSize(20, 20);
insertArrow.setVisible(false);
cardContent.add(insertArrow, 1000);
cardContent.add(insertArrow, (Integer) 1000);
// Selection panel
selectionPanel = new SelectionBox();
selectionPanel.setVisible(false);
cardContent.add(selectionPanel, new Integer(1001));
cardContent.add(selectionPanel, (Integer) 1001);
// Load separate creatures setting
separateCreatures = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_DECK_EDITOR_LAST_SEPARATE_CREATURES, "false").equals("true");
@ -965,6 +1037,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
@Override
public void keyPressed(KeyEvent e) {
}
});
@ -975,17 +1048,14 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// Analyse Mana (aka #blue pips, #islands, #white pips, #plains etc.)
analyseButton.setToolTipText("Mana Analyser! Counts coloured/colourless mana costs. Counts land types.");
analyseButton.addActionListener(evt -> analyseDeck());
// Bling button - aka Add in a premium 'JR', 'MBP', 'CS' etc card
blingButton.setToolTipText("Bling your deck! Select the original and added cards by selecting 'Multiples' in the selection options");
blingButton.addActionListener(evt -> blingDeck());
// Old version button - Switch cards to the oldest non-promo printing. In case of multiples in a set, take the lowest card number.
oldVersionButton.setToolTipText("Switch cards to the oldest non-promo printing");
oldVersionButton.addActionListener(evt -> oldVersionDeck());
// Filter popup
@ -1032,11 +1102,11 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
sortMenu.add(separateButton);
menu.add(sortMenu);
// Hook up to card content
// ENABLE popup menu on non card (e.g. show all or sorting)
cardContent.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
if (e.isPopupTrigger() || SwingUtilities.isRightMouseButton(e)) {
for (Sort s : sortMenuItems.keySet()) {
sortMenuItems.get(s).setSelected(cardSort == s);
}
@ -1170,8 +1240,6 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
--col2;
}
// avoids a null ref issue but only deals with symptom of problem. not sure how it gets to this state ever. see issue #3197
// if (selectionDragStartCards == null) return;
int curY = COUNT_LABEL_HEIGHT;
for (int rowIndex = 0; rowIndex < cardGrid.size(); ++rowIndex) {
int stackStartIndex;
@ -1638,7 +1706,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
if (acard.getName().equals(card.getName())) {
CardView pimpedCard = new CardView(acard);
addCardView(pimpedCard, false);
eventSource.fireEvent(pimpedCard, ClientEventType.ADD_SPECIFIC_CARD);
eventSource.fireEvent(pimpedCard, ClientEventType.DECK_ADD_SPECIFIC_CARD);
pimpedCards.put(pimpedCard, 1);
didModify = true;
}
@ -1679,9 +1747,9 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
if (oldestCardInfo != null) {
CardView oldestCardView = new CardView(oldestCardInfo.getMockCard());
this.removeCardView(card);
eventSource.fireEvent(card, ClientEventType.REMOVE_SPECIFIC_CARD);
eventSource.fireEvent(card, ClientEventType.DECK_REMOVE_SPECIFIC_CARD);
this.addCardView(oldestCardView, false);
eventSource.fireEvent(oldestCardView, ClientEventType.ADD_SPECIFIC_CARD);
eventSource.fireEvent(oldestCardView, ClientEventType.DECK_ADD_SPECIFIC_CARD);
newStack.add(oldestCardView);
} else {
newStack.add(card);
@ -1806,7 +1874,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (Map<String, List<CardView>> tracked : trackedCards.values()) {
for (List<CardView> orphans : tracked.values()) {
for (CardView orphan : orphans) {
LOGGER.info("Orphan when setting with layout: ");
logger.info("Orphan when setting with layout: ");
sortIntoGrid(orphan);
}
}
@ -1902,48 +1970,23 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
updateCounts();
// Create the card view
final MageCard cardPanel = Plugins.instance.getMageCard(card, lastBigCard, new Dimension(getCardWidth(), getCardHeight()), null, true, true, PreferencesDialog.getRenderMode(), true);
final MageCard cardPanel = Plugins.instance.getMageCard(card, lastBigCard, new CardIconRenderSettings(), new Dimension(getCardWidth(), getCardHeight()), null, true, true, PreferencesDialog.getRenderMode(), true);
cardPanel.setCardContainerRef(this);
cardPanel.update(card);
// cards bounds set in layoutGrid()
cardPanel.setCardCaptionTopOffset(0);
// Remove mouse wheel listeners so that scrolling works
// Scrolling works on all areas without cards or by using the scroll bar, that's enough
// for (MouseWheelListener l : cardPanel.getMouseWheelListeners()) {
// cardPanel.removeMouseWheelListener(l);
// }
// Add a click listener for selection / drag start
cardPanel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
// Select if not selected
if (!card.isSelected()) {
selectCard(card);
}
// Show menu
showCardRightClickMenu(card, e);
} else if (SwingUtilities.isLeftMouseButton(e)) {
if (e.getClickCount() == 1) {
cardClicked(card, e);
} else if (e.isAltDown()) {
eventSource.fireEvent(card, ClientEventType.ALT_DOUBLE_CLICK);
} else {
eventSource.fireEvent(card, ClientEventType.DOUBLE_CLICK);
}
}
}
});
// Add a motion listener to process drags
// ENABLE DRAG SUPPORT FOR CARDS
// TODO: rewrite mouseDragged in MageActionCallback, so it can support any drags, not hands only
cardPanel.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if (!dragger.isDragging()) {
// If the card isn't already selected, make sure it is
if (!card.isSelected()) {
cardClicked(card, e);
selectCard(card);
}
dragger.beginDrag(cardPanel, e);
dragger.handleDragStart(cardPanel, e);
}
}
});
@ -1954,7 +1997,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
if (duplicated) {
sortIntoGrid(card);
eventSource.fireEvent(card, ClientEventType.ADD_SPECIFIC_CARD);
eventSource.fireEvent(card, ClientEventType.DECK_ADD_SPECIFIC_CARD);
// Update layout
layoutGrid();
repaintGrid();
@ -2059,9 +2102,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// No card in this column?
if (cardInColumn == null) {
// Error, should not have an empty column
LOGGER.error("Empty column! " + currentColumn);
logger.error("Empty column! " + currentColumn);
} else {
int res = cardSort.getComparator().compare(newCard, cardInColumn);
CardViewComparator cardComparator = cardSort.getComparator();
int res = cardComparator.compare(newCard, cardInColumn);
if (res <= 0) {
// Insert into this col, but if less, then we need to create a new col here first
if (res < 0) {
@ -2085,6 +2129,9 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
}
targetRow.get(targetRow.size() - 1).add(newCard);
}
// if it's a new deck then non creature cards can be added to second row, so fix empty rows here
trimGrid();
}
/**
@ -2191,16 +2238,35 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
stackCountLabels.add(new ArrayList<>());
}
if (stackCountLabels.get(rowIndex).size() <= colIndex) {
JLabel countLabel = new JLabel("", SwingConstants.CENTER);
countLabel.setForeground(Color.WHITE);
cardContent.add(countLabel, new Integer(0));
// add new count label for the column
// ENABLE cards auto-selection in the stack
if (this.countLabelListener == null) {
this.countLabelListener = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
JLabel countLabel = (JLabel) e.getComponent();
List<CardView> cards = findCardStackByCountLabel(countLabel);
boolean selected = !cards.isEmpty() && cards.get(0).isSelected();
cards.forEach(card -> {
card.setSelected(!selected);
cardViews.get(card.getId()).update(card);
});
}
};
}
JLabel countLabel = DragCardGrid.createCountLabel(this.countLabelListener);
cardContent.add(countLabel, (Integer) 0);
stackCountLabels.get(rowIndex).add(countLabel);
}
JLabel countLabel = stackCountLabels.get(rowIndex).get(colIndex);
if (stack.isEmpty()) {
countLabel.setVisible(false);
} else {
countLabel.setText(String.valueOf(stack.size()));
String description = cardSort.getComparator().getCategoryName(stack.get(0));
DragCardGrid.updateCountLabel(countLabel, stack.size(), description);
countLabel.setLocation(GRID_PADDING + (cardWidth + GRID_PADDING) * colIndex, currentY - COUNT_LABEL_HEIGHT);
countLabel.setSize(cardWidth, COUNT_LABEL_HEIGHT);
countLabel.setVisible(true);
@ -2231,6 +2297,57 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
//cardContent.setSize(maxWidth, currentY - COUNT_LABEL_HEIGHT + GRID_PADDING);
}
public static JLabel createCountLabel(MouseListener mouseListener) {
JLabel countLabel = new JLabel("", JLabel.CENTER);
countLabel.setForeground(Color.WHITE); // TODO: add theme support
if (mouseListener != null) {
countLabel.addMouseListener(mouseListener);
}
return countLabel;
}
public static void updateCountLabel(JLabel countLabel, int amount, String description) {
// two modes:
// * small: one line with count
// * big: two lines with count and description
String descHtml = ManaSymbols.replaceSymbolsWithHTML(description, ManaSymbols.Type.TABLE);
boolean smallMode = description.isEmpty();
boolean supportCustomClicks = countLabel.getMouseListeners().length > 0
&& !(countLabel.getMouseListeners()[0] instanceof ToolTipManager); // ignore auto-added ToolTipManager for mouse over hints
// border: 1px solid black
// white-space: nowrap;
countLabel.setText("<html>"
+ "<div style='text-align: center;'>"
+ " <div>" + amount + "</div>"
+ (smallMode ? "" : " <div style=''>" + descHtml + "</div>")
+ "</div>"
+ "");
countLabel.setToolTipText("<html>"
+ amount + (smallMode ? "" : " - " + description)
+ (supportCustomClicks ? "<br>Click on the count label to select/unselect cards stack." : "")
);
countLabel.setVerticalAlignment(smallMode ? JLabel.CENTER : JLabel.TOP);
if (DebugUtil.GUI_DECK_EDITOR_DRAW_COUNT_LABEL_BORDER) {
countLabel.setBorder(BorderFactory.createLineBorder(Color.green));
}
}
private List<CardView> findCardStackByCountLabel(JLabel countLabel) {
for (int rowIndex = 0; rowIndex < cardGrid.size(); ++rowIndex) {
List<List<CardView>> gridRow = cardGrid.get(rowIndex);
for (int colIndex = 0; colIndex < gridRow.size(); ++colIndex) {
if (stackCountLabels.size() < rowIndex
|| stackCountLabels.get(rowIndex).size() < colIndex
|| !countLabel.equals(stackCountLabels.get(rowIndex).get(colIndex))) {
continue;
}
return gridRow.get(colIndex);
}
}
return new ArrayList<>();
}
private static void makeButtonPopup(final AbstractButton button, final JPopupMenu popup) {
button.addActionListener(e -> popup.show(button, 0, button.getHeight()));
}