forked from External/mage
* 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:
parent
df98cc3e62
commit
a1da5ef437
304 changed files with 7266 additions and 5093 deletions
371
Mage.Client/src/main/java/mage/client/cards/CardIconsPanel.java
Normal file
371
Mage.Client/src/main/java/mage/client/cards/CardIconsPanel.java
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
package mage.client.cards;
|
||||
|
||||
import mage.abilities.icon.*;
|
||||
import mage.abilities.icon.system.CombinedCountIcon;
|
||||
import mage.client.components.StretchIcon;
|
||||
import mage.client.dialog.PreferencesDialog;
|
||||
import mage.client.util.GUISizeHelper;
|
||||
import mage.util.DebugUtil;
|
||||
import org.mage.card.arcane.CardRendererUtils;
|
||||
import org.mage.card.arcane.ManaSymbols;
|
||||
import org.mage.card.arcane.SvgUtils;
|
||||
import org.mage.plugins.card.images.ImageCache;
|
||||
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* GUI panel to drawning icons (one of the card's side)
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class CardIconsPanel extends JPanel {
|
||||
|
||||
private static final CardIconPosition DEFAULT_POSITION = CardIconPosition.LEFT;
|
||||
private static final CardIconOrder DEFAULT_ORDER = CardIconOrder.START;
|
||||
private static final int DEFAULT_MAX_VISIBLE_COUNT = 5;
|
||||
private static final int DEFAULT_ICON_SIZE_PERCENT = 30;
|
||||
|
||||
private static final int MINIMUM_ICON_SIZE = 32; // TODO: not working?
|
||||
private static final int KEEP_ICON_IN_CARD_INSIDE_PERCENT = 70; // example: 66% - 2/3 keep inside and 1/3 keep outside
|
||||
private static final int MAXIMUM_CARD_WIDTH_FOR_ICONS_SMALL_MODE = 100; // enable icons small mode for too small cards (combine ability icons to one);
|
||||
|
||||
private final CardIconPosition position;
|
||||
private final CardIconOrder order;
|
||||
private final int iconSizePercent; // icons size, related to card's width
|
||||
private final List<CardIcon> icons;
|
||||
private final int cellsMaxCount; // split card side to cells, can be 1, 3, 5, 7 (x left + 1x center + x right)
|
||||
private final int cellsVisibleCount; // if it contains too much elements then replace it by combined element (example: cells x7, visible x3)
|
||||
private final int cellsOffset; // how many cells must be offset from the start and the end. Example: 0 - nothing, 1 - 1x from left and 1x from right
|
||||
private int iconsGap = 3; // gap between icons in the cells (aplies from left and right sides)
|
||||
private int halfSize = 0; // offset for icons from the card's border
|
||||
private Font font = null;
|
||||
|
||||
// auto-calced for small mode, see calcSizes
|
||||
private int calcedCellsMaxCount = 1;
|
||||
private int calcedCellsVisibleCount = 1;
|
||||
private int calcedSizeSizePercent = 30;
|
||||
private int calcedCellsOffset = 1;
|
||||
private CardIconPosition calcedPosition;
|
||||
private CardIconOrder calcedOrder;
|
||||
|
||||
public CardIconsPanel(CardIconRenderSettings render) {
|
||||
this(render.getCustomPosition() != null ? render.getCustomPosition() : DEFAULT_POSITION,
|
||||
render.getCustomOrder() != null ? render.getCustomOrder() : DEFAULT_ORDER,
|
||||
render.getCustomMaxVisibleCount() > 0 ? render.getCustomMaxVisibleCount() : DEFAULT_MAX_VISIBLE_COUNT,
|
||||
render.getCustomIconSizePercent() > 0 ? render.getCustomIconSizePercent() : DEFAULT_ICON_SIZE_PERCENT
|
||||
);
|
||||
}
|
||||
|
||||
public CardIconsPanel(CardIconPosition position, CardIconOrder order, int cellsVisibleCount, int iconSizePercent) {
|
||||
this(position, order, cellsVisibleCount, iconSizePercent, new ArrayList<>(), new Rectangle(100, 100));
|
||||
}
|
||||
|
||||
public CardIconsPanel(CardIconPosition position, CardIconOrder order, int cellsVisibleCount, int iconSizePercent, List<CardIcon> icons, Rectangle startingCardSize) {
|
||||
super(null);
|
||||
this.position = position != null ? position : DEFAULT_POSITION;
|
||||
this.iconSizePercent = iconSizePercent;
|
||||
this.icons = icons;
|
||||
|
||||
// corners have only one icon with center order
|
||||
if (this.position.getMaxIconsAmount() == 1) {
|
||||
this.order = CardIconOrder.START;
|
||||
this.cellsOffset = 0;
|
||||
this.cellsMaxCount = 1;
|
||||
} else {
|
||||
this.order = order != null ? order : DEFAULT_ORDER;
|
||||
this.cellsOffset = 1;
|
||||
this.cellsMaxCount = 7;
|
||||
}
|
||||
int maxIcons = Math.max(1, Math.min(this.cellsMaxCount, cellsVisibleCount)); // must be in [1..cells];
|
||||
this.cellsVisibleCount = Math.min(maxIcons, this.position.getMaxIconsAmount());
|
||||
|
||||
this.setVisible(false);
|
||||
this.setOpaque(false);
|
||||
if (DebugUtil.GUI_CARD_ICONS_DRAW_PANEL_BORDER) {
|
||||
this.setBorder(BorderFactory.createLineBorder(Color.red));
|
||||
}
|
||||
|
||||
this.updateSizes(startingCardSize);
|
||||
}
|
||||
|
||||
public void updateSizes(Rectangle cardSize) {
|
||||
this.calcSizes(cardSize);
|
||||
|
||||
// panel uses GridLayout with gaps, grid is static size, so the sizes structure:
|
||||
// [gap + icon + gap + icon + ... gap]
|
||||
|
||||
// corner icons must be same sizes as max possible on left/right (for a more beautiful look)
|
||||
int panelFullSize = this.halfSize * 2 + cardSize.height;
|
||||
int panelIconSize = (panelFullSize - (7 + 1) * this.iconsGap) / 7;
|
||||
int cornerHalfSize = Math.min(panelIconSize, this.halfSize * 2) / 2; // real icons can be limited by height or width
|
||||
|
||||
// move panel to the inner (for a more beautiful look)
|
||||
// 2/3 keep inside and 1/3 keep outside
|
||||
// panels already centered by halfSize, so use "- this.halfSize"
|
||||
int panelOffset = Math.round(this.halfSize * 2 * KEEP_ICON_IN_CARD_INSIDE_PERCENT / 100f) - this.halfSize;
|
||||
|
||||
Rectangle panelRect;
|
||||
Point panelTranslate;
|
||||
switch (this.calcedPosition) {
|
||||
case TOP:
|
||||
panelRect = new Rectangle(cardSize.x - this.halfSize, cardSize.y - this.halfSize, cardSize.width + this.halfSize * 2, this.halfSize * 2);
|
||||
panelTranslate = new Point(0, panelOffset);
|
||||
this.setLayout(new GridLayout(1, this.calcedCellsMaxCount, iconsGap, 0));
|
||||
break;
|
||||
case LEFT:
|
||||
panelRect = new Rectangle(cardSize.x - this.halfSize, cardSize.y - this.halfSize, this.halfSize * 2, cardSize.height + this.halfSize * 2);
|
||||
panelTranslate = new Point(panelOffset, 0);
|
||||
this.setLayout(new GridLayout(this.calcedCellsMaxCount, 1, 0, iconsGap));
|
||||
break;
|
||||
case RIGHT:
|
||||
panelRect = new Rectangle(cardSize.x + cardSize.width - this.halfSize, cardSize.y - this.halfSize, this.halfSize * 2, cardSize.height + this.halfSize * 2);
|
||||
panelTranslate = new Point(-panelOffset, 0);
|
||||
this.setLayout(new GridLayout(this.calcedCellsMaxCount, 1, 0, iconsGap));
|
||||
break;
|
||||
case BOTTOM:
|
||||
panelRect = new Rectangle(cardSize.x - this.halfSize, cardSize.y + cardSize.height - this.halfSize, cardSize.width + this.halfSize * 2, this.halfSize * 2);
|
||||
panelTranslate = new Point(0, -panelOffset);
|
||||
this.setLayout(new GridLayout(1, this.calcedCellsMaxCount, iconsGap, 0));
|
||||
break;
|
||||
case CORNER_TOP_LEFT:
|
||||
panelRect = new Rectangle(cardSize.x - cornerHalfSize, cardSize.y - cornerHalfSize, cornerHalfSize * 2, cornerHalfSize * 2);
|
||||
panelTranslate = new Point(panelOffset, panelOffset);
|
||||
this.setLayout(new GridLayout(1, 1, 0, 0));
|
||||
break;
|
||||
case CORNER_TOP_RIGHT:
|
||||
panelRect = new Rectangle(cardSize.x + cardSize.width - cornerHalfSize, cardSize.y - cornerHalfSize, cornerHalfSize * 2, cornerHalfSize * 2);
|
||||
panelTranslate = new Point(-panelOffset, panelOffset);
|
||||
this.setLayout(new GridLayout(1, 1, 0, 0));
|
||||
break;
|
||||
case CORNER_BOTTOM_LEFT:
|
||||
panelRect = new Rectangle(cardSize.x - cornerHalfSize, cardSize.y + cardSize.height - cornerHalfSize, cornerHalfSize * 2, cornerHalfSize * 2);
|
||||
panelTranslate = new Point(panelOffset, -panelOffset);
|
||||
this.setLayout(new GridLayout(1, 1, 0, 0));
|
||||
break;
|
||||
case CORNER_BOTTOM_RIGHT:
|
||||
panelRect = new Rectangle(cardSize.x + cardSize.width - cornerHalfSize, cardSize.y + cardSize.height - cornerHalfSize, cornerHalfSize * 2, cornerHalfSize * 2);
|
||||
panelTranslate = new Point(-panelOffset, -panelOffset);
|
||||
this.setLayout(new GridLayout(1, 1, 0, 0));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Card icons do not support position " + this.calcedPosition);
|
||||
}
|
||||
panelRect.translate(panelTranslate.x, panelTranslate.y);
|
||||
this.setBounds(panelRect);
|
||||
|
||||
// reload icons for new size
|
||||
this.updateIcons();
|
||||
}
|
||||
|
||||
public void updateIcons() {
|
||||
updateIcons(null);
|
||||
}
|
||||
|
||||
public void updateIcons(List<CardIcon> newIcons) {
|
||||
this.removeAll();
|
||||
if (newIcons != null) {
|
||||
this.icons.clear();
|
||||
this.icons.addAll(newIcons);
|
||||
}
|
||||
|
||||
// auto-hide panel on empty icons
|
||||
if (this.icons.isEmpty()) {
|
||||
this.setVisible(false);
|
||||
return;
|
||||
} else {
|
||||
this.setVisible(true);
|
||||
}
|
||||
|
||||
int actualMaxVisibleCount = Math.min(this.calcedCellsVisibleCount, this.calcedCellsMaxCount - this.calcedCellsOffset * 2); // preserve offset cells
|
||||
|
||||
List<Component> visibleComponents = new ArrayList<>();
|
||||
List<Component> combinedComponents = new ArrayList<>();
|
||||
List<Component> orderedComponents = new ArrayList<>();
|
||||
|
||||
// structure:
|
||||
// * icons panel - control the icons size and offsets in card;
|
||||
// * grid panel - control the icons order and position (put empty panel for empty space)
|
||||
// * grid's cell - control one icon
|
||||
// * label - stretched icon image that occupy all cell's space
|
||||
Map<Component, CardIcon> cardLinks = new HashMap<>();
|
||||
this.icons.stream()
|
||||
.sorted(CardIconComparator.instance)
|
||||
.forEach(icon -> {
|
||||
Component iconComponent = createIconComponent(icon);
|
||||
if (iconComponent != null) {
|
||||
visibleComponents.add(iconComponent);
|
||||
cardLinks.put(iconComponent, icon);
|
||||
}
|
||||
});
|
||||
|
||||
// OPTIMIZE visible components (if card contains too much icons then combine it in one "...")
|
||||
if (visibleComponents.size() > actualMaxVisibleCount) {
|
||||
while (visibleComponents.size() > actualMaxVisibleCount - 1) {
|
||||
// combined must contains minimum 2 elements
|
||||
combinedComponents.add(visibleComponents.remove(visibleComponents.size() - 1));
|
||||
}
|
||||
String combinedHint = combinedComponents
|
||||
.stream()
|
||||
.map(cardLinks::get)
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(CardIconComparator.instance)
|
||||
.map(CardIcon::getCombinedInfo)
|
||||
.collect(Collectors.joining("<br>"));
|
||||
CardIcon combinedIcon = new CombinedCountIcon(combinedComponents.size(), combinedHint);
|
||||
Component combinedComponent = createIconComponent(combinedIcon);
|
||||
if (combinedComponent != null) {
|
||||
visibleComponents.add(combinedComponent);
|
||||
}
|
||||
}
|
||||
|
||||
// add offsets to the start of the list
|
||||
if (this.calcedOrder == CardIconOrder.START || this.calcedOrder == CardIconOrder.END) {
|
||||
for (int i = 0; i < this.calcedCellsOffset; i++) {
|
||||
JPanel panel = new JPanel(null);
|
||||
panel.setOpaque(false);
|
||||
visibleComponents.add(0, panel);
|
||||
}
|
||||
}
|
||||
|
||||
// fill components list to max (grid can't put elements to cells, so must fill all)
|
||||
while (visibleComponents.size() < this.calcedCellsMaxCount) {
|
||||
JPanel panel = new JPanel(null);
|
||||
panel.setOpaque(false);
|
||||
visibleComponents.add(panel);
|
||||
}
|
||||
|
||||
// ORDER visible components
|
||||
// icons sort order example with CENTER order:
|
||||
// 1: [1]
|
||||
// 3: [2 1 3]
|
||||
// 5: [4 2 1 3 5]
|
||||
// 7: [6 4 2 1 3 5 7]
|
||||
//
|
||||
// icons sort order example with START order (END order is same but reversed):
|
||||
// 1: [1]
|
||||
// 3: [1 2 3]
|
||||
// 5: [1 2 3 4 5]
|
||||
// 7: [1 2 3 4 5 6 7]
|
||||
if (this.calcedOrder == CardIconOrder.CENTER) {
|
||||
// CENTER
|
||||
if (this.calcedCellsMaxCount == 1) {
|
||||
Arrays.asList(1).forEach(i -> orderedComponents.add(visibleComponents.get(i - 1)));
|
||||
} else if (this.calcedCellsMaxCount == 3) {
|
||||
Arrays.asList(2, 1, 3).forEach(i -> orderedComponents.add(visibleComponents.get(i - 1)));
|
||||
} else if (this.calcedCellsMaxCount == 5) {
|
||||
Arrays.asList(4, 2, 1, 3, 5).forEach(i -> orderedComponents.add(visibleComponents.get(i - 1)));
|
||||
} else if (this.calcedCellsMaxCount == 7) {
|
||||
Arrays.asList(6, 4, 2, 1, 3, 5, 7).forEach(i -> orderedComponents.add(visibleComponents.get(i - 1)));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Card icons do not support max size as " + this.calcedCellsMaxCount);
|
||||
}
|
||||
} else if (this.calcedOrder == CardIconOrder.START) {
|
||||
// START
|
||||
orderedComponents.addAll(visibleComponents);
|
||||
} else if (this.calcedOrder == CardIconOrder.END) {
|
||||
// END
|
||||
orderedComponents.addAll(visibleComponents);
|
||||
Collections.reverse(orderedComponents);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Card icons do not support order type " + this.calcedOrder);
|
||||
}
|
||||
|
||||
// ADD real components to the grid
|
||||
orderedComponents.forEach(this::add);
|
||||
}
|
||||
|
||||
private Component createIconComponent(CardIcon icon) {
|
||||
if (!SvgUtils.haveSvgSupport()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// direct call
|
||||
//BufferedImage iconImage = ImageManagerImpl.instance.getCardIcon(icon.getIconType().getResourceName(), this.halfSize * 2);
|
||||
|
||||
// cached call
|
||||
BufferedImage iconImageCached = ImageCache.getCardIconImage(icon.getIconType().getResourceName(), this.halfSize * 2);
|
||||
|
||||
if (iconImageCached != null && this.font != null) {
|
||||
BufferedImage iconImageWithText = ImageManagerImpl.deepCopy(iconImageCached); // must copy cached value before modify
|
||||
|
||||
// text
|
||||
JLabel label = new JLabel();
|
||||
label.setToolTipText("<html>" + ManaSymbols.replaceSymbolsWithHTML(icon.getHint(), ManaSymbols.Type.CARD_ICON_HINT));
|
||||
if (!icon.getText().isEmpty()) {
|
||||
Graphics2D g2d = iconImageWithText.createGraphics();
|
||||
g2d.setColor(PreferencesDialog.getCurrentTheme().getCardIconsTextColor());
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
Rectangle rect = CardRendererUtils.reduceRect(new Rectangle(0, 0, iconImageWithText.getWidth(), iconImageWithText.getHeight()), 0.8f);
|
||||
CardRendererUtils.drawCenteredText(g2d, icon.getText(), rect, this.font, true);
|
||||
g2d.dispose();
|
||||
}
|
||||
|
||||
// the stretch icon can occupy all space (full grid's cell)
|
||||
StretchIcon s = new StretchIcon(iconImageWithText, true);
|
||||
label.setIcon(s);
|
||||
label.setIconTextGap(0);
|
||||
if (DebugUtil.GUI_CARD_ICONS_DRAW_ICON_BORDER) {
|
||||
label.setBorder(BorderFactory.createLineBorder(Color.green));
|
||||
}
|
||||
return label;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void calcSizes(Rectangle cardSize) {
|
||||
// small mode takes 20% of card's sizes diff
|
||||
boolean smallMode = false;
|
||||
int maxW = GUISizeHelper.battlefieldCardMaxDimension.width;
|
||||
int minW = GUISizeHelper.battlefieldCardMinDimension.width;
|
||||
if (minW > maxW) {
|
||||
// on wrong settings
|
||||
maxW = GUISizeHelper.battlefieldCardMinDimension.width;
|
||||
minW = GUISizeHelper.battlefieldCardMaxDimension.width;
|
||||
}
|
||||
// cardSize.width < 120 - disable small mode on too big cards
|
||||
if (cardSize.width < MAXIMUM_CARD_WIDTH_FOR_ICONS_SMALL_MODE && (cardSize.width < minW + (maxW - minW) * 0.2f)) {
|
||||
smallMode = true;
|
||||
}
|
||||
|
||||
// auto-sizeable icons (smaller for small card, normal for big)
|
||||
this.calcedSizeSizePercent = this.iconSizePercent;
|
||||
if (smallMode) {
|
||||
this.calcedSizeSizePercent = Math.round(this.calcedSizeSizePercent * 1.5f);
|
||||
}
|
||||
|
||||
// auto-amount for icons (less for small, normal for big)
|
||||
this.calcedCellsMaxCount = this.cellsMaxCount;
|
||||
this.calcedCellsVisibleCount = this.cellsVisibleCount;
|
||||
this.calcedCellsOffset = this.cellsOffset;
|
||||
this.calcedPosition = this.position;
|
||||
this.calcedOrder = this.order;
|
||||
if (smallMode) {
|
||||
this.calcedCellsMaxCount = Math.min(5, this.calcedCellsMaxCount);
|
||||
this.calcedCellsVisibleCount = Math.min(1, this.calcedCellsVisibleCount);
|
||||
this.calcedCellsOffset = Math.min(1, this.calcedCellsOffset);
|
||||
// change order of multi-icons and ignore corners (make icons it centered)
|
||||
if (this.calcedPosition.getMaxIconsAmount() > 1) {
|
||||
this.calcedOrder = CardIconOrder.CENTER;
|
||||
}
|
||||
}
|
||||
|
||||
// REAL SIZES
|
||||
|
||||
// auto-sizeable gaps (use test render form to find best values)
|
||||
this.iconsGap = Math.floorDiv(cardSize.width, 100) * 2;
|
||||
|
||||
// icons intersect the card like mtg arena
|
||||
this.halfSize = Math.max(MINIMUM_ICON_SIZE / 2, Math.round(cardSize.width / 100.0f * this.calcedSizeSizePercent / 2.0f));
|
||||
this.font = new Font("Arial", Font.PLAIN + Font.BOLD, Math.round(this.halfSize * 1.5f));
|
||||
}
|
||||
|
||||
public int getHalfSize() {
|
||||
return halfSize;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue