Refactored CardPanelRenderModes to make them more readable (#9574)

This commit is contained in:
Alex Vasile 2022-09-26 09:48:14 -04:00 committed by GitHub
parent 393ea1e7ba
commit 2ff5cddbce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 516 additions and 663 deletions

View file

@ -26,6 +26,7 @@ import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@ -93,13 +94,13 @@ public enum Plugins implements MagePlugins {
@Override
public MageCard getMagePermanent(PermanentView card, BigCard bigCard, CardIconRenderSettings cardIconRenderSettings, Dimension dimension, UUID gameId, boolean loadImage, int renderMode, boolean needFullPermanentRender) {
MageCard mageCard;
if (cardPlugin != null) {
if (cardPlugin == null) {
throw new IllegalArgumentException("Card's plugin must be loaded");
}
mageActionCallback.refreshSession();
mageActionCallback.setCardPreviewComponent(bigCard);
mageCard = cardPlugin.getMagePermanent(card, dimension, gameId, mageActionCallback, false, !MageFrame.isLite() && loadImage, renderMode, needFullPermanentRender);
} else {
throw new IllegalArgumentException("Card's plugin must be loaded");
}
return createLayeredCard(mageCard, dimension, cardIconRenderSettings);
}
@ -108,15 +109,14 @@ public enum Plugins implements MagePlugins {
// Card icons panels must be put outside of the card like MTG Arena.
// So for compatibility purposes: keep free space for icons and change card dimention
MageCard mageCard;
if (cardPlugin != null) {
if (cardPlugin == null) {
throw new IllegalArgumentException("Card's plugin must be loaded");
}
if (previewable) {
mageActionCallback.refreshSession();
mageActionCallback.setCardPreviewComponent(bigCard);
}
mageCard = cardPlugin.getMageCard(card, dimension, gameId, mageActionCallback, false, !MageFrame.isLite() && loadImage, renderMode, needFullPermanentRender);
} else {
throw new IllegalArgumentException("Card's plugin must be loaded");
}
return createLayeredCard(mageCard, dimension, cardIconRenderSettings);
}

View file

@ -24,6 +24,7 @@ import org.mage.plugins.card.utils.impl.ImageManagerImpl;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Objects;
import java.util.StringTokenizer;
import java.util.UUID;
@ -36,6 +37,8 @@ public class CardPanelRenderModeImage extends CardPanel {
private static final long serialVersionUID = -3272134219262184411L;
private final static SoftValuesLoadingCache<Key, BufferedImage> IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(CardPanelRenderModeImage::createImage));
private static final int WIDTH_LIMIT = 90; // card width limit to create smaller counter
private static final float ROUNDED_CORNER_SIZE = 0.1f;
@ -79,33 +82,9 @@ public class CardPanelRenderModeImage extends CardPanel {
private boolean displayTitleAnyway;
private final boolean displayFullImagePath;
private final static SoftValuesLoadingCache<Key, BufferedImage> IMAGE_CACHE;
private int updateArtImageStamp;
public ImagePanel getOverlayPanel() {
return overlayPanel;
}
public void setOverlayPanel(ImagePanel overlayPanel) {
this.overlayPanel = overlayPanel;
}
public JPanel getTypeIconPanel() {
return typeIconPanel;
}
public void setTypeIconPanel(JPanel typeIconPanel) {
this.typeIconPanel = typeIconPanel;
}
public JPanel getCounterPanel() {
return counterPanel;
}
public void setCounterPanel(JPanel counterPanel) {
this.counterPanel = counterPanel;
}
static class Key {
private static class Key {
final Insets border;
final int width;
@ -137,98 +116,34 @@ public class CardPanelRenderModeImage extends CardPanel {
this.canBlock = canBlock;
}
@Override
public int hashCode() {
int hash = 3;
hash = 19 * hash + this.border.left;
hash = 19 * hash + this.border.right;
hash = 19 * hash + this.border.top;
hash = 19 * hash + this.border.bottom;
hash = 19 * hash + this.width;
hash = 19 * hash + this.height;
hash = 19 * hash + this.cardWidth;
hash = 19 * hash + this.cardHeight;
hash = 19 * hash + this.cardXOffset;
hash = 19 * hash + this.cardYOffset;
hash = 19 * hash + (this.hasImage ? 1 : 0);
hash = 19 * hash + (this.isSelected ? 1 : 0);
hash = 19 * hash + (this.isChoosable ? 1 : 0);
hash = 19 * hash + (this.isPlayable ? 1 : 0);
hash = 19 * hash + (this.canAttack ? 1 : 0);
hash = 19 * hash + (this.canBlock ? 1 : 0);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Key other = (Key) obj;
if (this.border.left != other.border.left) {
return false;
}
if (this.border.right != other.border.right) {
return false;
}
if (this.border.top != other.border.top) {
return false;
}
if (this.border.bottom != other.border.bottom) {
return false;
}
if (this.width != other.width) {
return false;
}
if (this.height != other.height) {
return false;
}
if (this.cardWidth != other.cardWidth) {
return false;
}
if (this.cardHeight != other.cardHeight) {
return false;
}
if (this.cardXOffset != other.cardXOffset) {
return false;
}
if (this.cardYOffset != other.cardYOffset) {
return false;
}
if (this.hasImage != other.hasImage) {
return false;
}
if (this.isSelected != other.isSelected) {
return false;
}
if (this.isChoosable != other.isChoosable) {
return false;
}
if (this.isPlayable != other.isPlayable) {
return false;
}
if (this.canAttack != other.canAttack) {
return false;
}
return this.canBlock == other.canBlock;
}
Key that = (Key) obj;
return Objects.equals(this.border, that.border)
&& this.width == that.width
&& this.height == that.height
&& this.cardWidth == that.cardWidth
&& this.cardHeight == that.cardHeight
&& this.cardXOffset == that.cardXOffset
&& this.cardYOffset == that.cardYOffset
&& this.hasImage == that.hasImage
&& this.isSelected == that.isSelected
&& this.isChoosable == that.isChoosable
&& this.isPlayable == that.isPlayable
&& this.canAttack == that.canAttack
&& this.canBlock == that.canBlock;
}
static {
IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(CardPanelRenderModeImage::createImage));
@Override
public int hashCode() {
return Objects.hash(border, width, height, cardWidth, cardHeight, cardXOffset, cardYOffset, hasImage, isSelected, isChoosable, isPlayable, canAttack, canBlock);
}
static private boolean canShowCardIcons(int cardFullWidth, boolean cardHasImage) {
// cards without images show icons and text always
// TODO: apply "card names on card" setting to icon too?
// TODO: fix card min-max size to hide (compare to settings size, not direct 60 and 200)
return ((cardFullWidth > 60) && (cardFullWidth < 200)) || (!cardHasImage);
}
private static class CardSizes {
@ -254,195 +169,11 @@ public class CardPanelRenderModeImage extends CardPanel {
}
}
public CardPanelRenderModeImage(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, final boolean foil, Dimension dimension, boolean needFullPermanentRender) {
// Call to super
super(newGameCard, gameId, loadImage, callback, foil, dimension, needFullPermanentRender);
// Counter panel
if (!newGameCard.isAbility()) {
// panel to show counters on the card
setCounterPanel(new JPanel());
getCounterPanel().setLayout(null);
getCounterPanel().setOpaque(false);
add(getCounterPanel());
plusCounterLabel = new JLabel("");
plusCounterLabel.setToolTipText("+1/+1");
getCounterPanel().add(plusCounterLabel);
minusCounterLabel = new JLabel("");
minusCounterLabel.setToolTipText("-1/-1");
getCounterPanel().add(minusCounterLabel);
loyaltyCounterLabel = new JLabel("");
loyaltyCounterLabel.setToolTipText("loyalty");
getCounterPanel().add(loyaltyCounterLabel);
otherCounterLabel = new JLabel("");
getCounterPanel().add(otherCounterLabel);
getCounterPanel().setVisible(false);
}
// Ability icon
if (newGameCard.isAbility()) {
if (newGameCard.getAbilityType() == AbilityType.TRIGGERED) {
setTypeIcon(ImageManagerImpl.instance.getTriggeredAbilityImage(), "Triggered Ability");
} else if (newGameCard.getAbilityType() == AbilityType.ACTIVATED) {
setTypeIcon(ImageManagerImpl.instance.getActivatedAbilityImage(), "Activated Ability");
}
}
// Token icon
if (this.getGameCard().isToken()) {
setTypeIcon(ImageManagerImpl.instance.getTokenIconImage(), "Token Permanent");
}
displayTitleAnyway = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_CARD_NAMES, "true").equals("true");
displayFullImagePath = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_FULL_IMAGE_PATH, "false").equals("true");
// Title Text
titleText = new GlowText();
setTitle(getGameCard());
titleText.setForeground(Color.white);
titleText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY);
titleText.setWrap(true);
add(titleText);
// Full path to image text
fullImageText = new JLabel();
fullImageText.setText(fullImagePath);
fullImageText.setForeground(Color.BLACK);
add(fullImageText);
// PT Text
ptPanel = new JPanel();
ptPanel.setOpaque(false);
ptPanel.setLayout(new BoxLayout(ptPanel, BoxLayout.X_AXIS));
ptPanel.add(new Box.Filler(new Dimension(0, 0), new Dimension(0, 0), new Dimension(Integer.MAX_VALUE, 0)));
ptText1 = new GlowText();
ptText2 = new GlowText();
ptText3 = new GlowText();
updatePTTexts(getGameCard());
ptPanel.add(ptText1);
ptPanel.add(ptText2);
ptPanel.add(ptText3);
//
add(ptPanel);
// Sickness overlay
BufferedImage sickness = ImageManagerImpl.instance.getSicknessImage();
setOverlayPanel(new ImagePanel(sickness, ImagePanelStyle.SCALED));
getOverlayPanel().setOpaque(false);
add(getOverlayPanel());
// Imagel panel
imagePanel = new ScaledImagePanel();
if (DebugUtil.GUI_RENDER_IMAGE_DRAW_IMAGE_BORDER) {
imagePanel.setBorder(BorderFactory.createLineBorder(Color.white));
}
add(imagePanel);
// Do we need to load?
if (loadImage) {
initialDraw();
} else {
// Nothing to do
}
}
private void setTypeIcon(BufferedImage bufferedImage, String toolTipText) {
setTypeIconPanel(new JPanel());
getTypeIconPanel().setLayout(null);
getTypeIconPanel().setOpaque(false);
add(getTypeIconPanel());
typeIconButton = new JButton("");
typeIconButton.setLocation(2, 2);
typeIconButton.setSize(25, 25);
getTypeIconPanel().setVisible(true);
typeIconButton.setIcon(new ImageIcon(bufferedImage));
if (toolTipText != null) {
typeIconButton.setToolTipText(toolTipText);
}
getTypeIconPanel().add(typeIconButton);
}
@Override
public void cleanUp() {
super.cleanUp();
this.setCounterPanel(null);
}
private void setTitle(CardView card) {
titleText.setText(!displayTitleAnyway && hasImage ? "" : card.getDisplayName());
}
private void setImage(BufferedImage srcImage) {
synchronized (imagePanel) {
if (srcImage != null) {
imagePanel.setImage(srcImage);
} else {
imagePanel.clearImage();
}
repaint();
}
doLayout();
}
private void setFullPath(String fullImagePath) {
this.fullImagePath = fullImagePath;
this.fullImagePath = this.fullImagePath.replaceAll("\\\\", "\\\\<br>");
this.fullImagePath = this.fullImagePath.replaceAll("/", "/<br>");
this.fullImagePath = "<html>" + this.fullImagePath + "</html>";
fullImageText.setText(!displayFullImagePath ? "" : this.fullImagePath);
doLayout();
}
@Override
public void transferResources(final CardPanel panelAbstract) {
if (panelAbstract instanceof CardPanelRenderModeImage) {
CardPanelRenderModeImage panel = (CardPanelRenderModeImage) panelAbstract;
synchronized (panel.imagePanel) {
if (panel.imagePanel.hasImage()) {
setImage(panel.imagePanel.getSrcImage());
}
}
}
}
@Override
public void setSelected(boolean isSelected) {
super.setSelected(isSelected);
if (isSelected) {
this.titleText.setGlowColor(Color.green);
} else {
this.titleText.setGlowColor(Color.black);
}
}
@Override
protected void paintCard(Graphics2D g2d) {
float alpha = getAlpha();
if (alpha != 1.0f) {
AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha);
g2d.setComposite(composite);
}
// draw background (selected/chooseable/playable)
MageCardLocation cardLocation = getCardLocation();
g2d.drawImage(
IMAGE_CACHE.getOrThrow(
new Key(getInsets(),
cardLocation.getCardWidth(), cardLocation.getCardHeight(),
cardLocation.getCardWidth(), cardLocation.getCardHeight(),
0,
0,
hasImage, isSelected(), isChoosable(), getGameCard().isPlayable(), getGameCard().isCanAttack(),
getGameCard().isCanBlock())),
0, 0, cardLocation.getCardWidth(), cardLocation.getCardHeight(), null);
g2d.dispose();
static private boolean canShowCardIcons(int cardFullWidth, boolean cardHasImage) {
// cards without images show icons and text always
// TODO: apply "card names on card" setting to icon too?
// TODO: fix card min-max size to hide (compare to settings size, not direct 60 and 200)
return ((cardFullWidth > 60) && (cardFullWidth < 200)) || (!cardHasImage);
}
private static BufferedImage createImage(Key key) {
@ -501,62 +232,125 @@ public class CardPanelRenderModeImage extends CardPanel {
return image;
}
private static ImageIcon getCounterImageWithAmount(int amount, BufferedImage image, int cardWidth) {
int factor = cardWidth > WIDTH_LIMIT ? 2 : 1;
int xOffset = amount > 9 ? 2 : 5;
int fontSize = factor == 1 ? amount < 10 ? 12 : amount < 100 ? 10 : amount < 1000 ? 7 : 6
: amount < 10 ? 19 : amount < 100 ? 15 : amount < 1000 ? 12 : amount < 10000 ? 9 : 8;
BufferedImage newImage;
if (cardWidth > WIDTH_LIMIT) {
newImage = ImageManagerImpl.deepCopy(image);
} else {
newImage = ImageHelper.getResizedImage(image, 20, 20);
}
Graphics graphics = newImage.getGraphics();
graphics.setColor(Color.BLACK);
graphics.setFont(new Font("Arial Black", amount > 100 ? Font.PLAIN : Font.BOLD, fontSize));
graphics.drawString(Integer.toString(amount), xOffset * factor, 11 * factor);
return new ImageIcon(newImage);
}
public CardPanelRenderModeImage(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, final boolean foil, Dimension dimension, boolean needFullPermanentRender) {
// Call to super
super(newGameCard, gameId, loadImage, callback, foil, dimension, needFullPermanentRender);
// Counter panel
if (!newGameCard.isAbility()) {
// panel to show counters on the card
counterPanel = new JPanel();
counterPanel.setLayout(null);
counterPanel.setOpaque(false);
add(counterPanel);
plusCounterLabel = new JLabel("");
plusCounterLabel.setToolTipText("+1/+1");
counterPanel.add(plusCounterLabel);
minusCounterLabel = new JLabel("");
minusCounterLabel.setToolTipText("-1/-1");
counterPanel.add(minusCounterLabel);
loyaltyCounterLabel = new JLabel("");
loyaltyCounterLabel.setToolTipText("loyalty");
counterPanel.add(loyaltyCounterLabel);
otherCounterLabel = new JLabel("");
counterPanel.add(otherCounterLabel);
counterPanel.setVisible(false);
}
// Ability icon
if (newGameCard.isAbility()) {
if (newGameCard.getAbilityType() == AbilityType.TRIGGERED) {
setTypeIcon(ImageManagerImpl.instance.getTriggeredAbilityImage(), "Triggered Ability");
} else if (newGameCard.getAbilityType() == AbilityType.ACTIVATED) {
setTypeIcon(ImageManagerImpl.instance.getActivatedAbilityImage(), "Activated Ability");
}
}
// Token icon
if (this.getGameCard().isToken()) {
setTypeIcon(ImageManagerImpl.instance.getTokenIconImage(), "Token Permanent");
}
displayTitleAnyway = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_CARD_NAMES, "true").equals("true");
displayFullImagePath = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_FULL_IMAGE_PATH, "false").equals("true");
// Title Text
titleText = new GlowText();
setTitle(getGameCard());
titleText.setForeground(Color.white);
titleText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY);
titleText.setWrap(true);
add(titleText);
// Full path to image text
fullImageText = new JLabel();
fullImageText.setText(fullImagePath);
fullImageText.setForeground(Color.BLACK);
add(fullImageText);
// PT Text
ptPanel = new JPanel();
ptPanel.setOpaque(false);
ptPanel.setLayout(new BoxLayout(ptPanel, BoxLayout.X_AXIS));
ptPanel.add(new Box.Filler(new Dimension(0, 0), new Dimension(0, 0), new Dimension(Integer.MAX_VALUE, 0)));
ptText1 = new GlowText();
ptText2 = new GlowText();
ptText3 = new GlowText();
updatePTTexts(getGameCard());
ptPanel.add(ptText1);
ptPanel.add(ptText2);
ptPanel.add(ptText3);
//
add(ptPanel);
// Sickness overlay
BufferedImage sickness = ImageManagerImpl.instance.getSicknessImage();
overlayPanel = new ImagePanel(sickness, ImagePanelStyle.SCALED);
overlayPanel.setOpaque(false);
add(overlayPanel);
// Imagel panel
imagePanel = new ScaledImagePanel();
if (DebugUtil.GUI_RENDER_IMAGE_DRAW_IMAGE_BORDER) {
imagePanel.setBorder(BorderFactory.createLineBorder(Color.white));
}
add(imagePanel);
// Do we need to load?
if (loadImage) {
initialDraw();
} else {
// Nothing to do
}
}
@Override
protected void paintChildren(Graphics g) {
super.paintChildren(g);
/*
// debug draw recs
// full card
g.setColor(new Color(255, 0, 0));
g.drawRect(realCard.rectFull.x, realCard.rectFull.y, realCard.rectFull.width, realCard.rectFull.height);
// real card - image
g.setColor(new Color(0, 0, 255));
g.drawRect(imagePanel.getX(), imagePanel.getY(), imagePanel.getBounds().width, imagePanel.getBounds().height);
// caption
g.setColor(new Color(0, 255, 255));
g.drawRect(titleText.getX(), titleText.getY(), titleText.getBounds().width, titleText.getBounds().height);
// life points
g.setColor(new Color(120, 0, 120));
g.drawRect(ptText.getX(), ptText.getY(), ptText.getBounds().width, ptText.getBounds().height);
//*/
if (getShowCastingCost() && !isAnimationPanel() && canShowCardIcons(getCardWidth(), hasImage)) {
int symbolMarginX = 2; // 2 px between icons
String manaCost = ManaSymbols.getClearManaCost(getGameCard().getManaCostStr());
int manaWidth = getManaWidth(manaCost, symbolMarginX);
// right top corner with margin (sizes from any sample card, length from black border to mana icon)
int manaMarginRight = Math.round(22f / 672f * getCardWidth());
int manaMarginTop = Math.round(24f / 936f * getCardHeight());
int cardOffsetX = 0;
int cardOffsetY = 0;
int manaX = cardOffsetX + getCardWidth() - manaMarginRight - manaWidth;
int manaY = cardOffsetY + manaMarginTop;
ManaSymbols.draw(g, manaCost, manaX, manaY, getSymbolWidth(), ModernCardRenderer.MANA_ICONS_TEXT_COLOR, symbolMarginX);
}
}
private int getManaWidth(String manaCost, int symbolMarginX) {
int width = 0;
manaCost = manaCost.replace("\\", "");
StringTokenizer tok = new StringTokenizer(manaCost, " ");
while (tok.hasMoreTokens()) {
tok.nextToken();
if (width != 0) {
width += symbolMarginX;
}
width += getSymbolWidth();
}
return width;
public void cleanUp() {
super.cleanUp();
counterPanel = null;
}
@Override
@ -588,32 +382,32 @@ public class CardPanelRenderModeImage extends CardPanel {
imagePanel.setSize(realCardSize.width, realCardSize.height);
if (hasSickness() && getGameCard().isCreature() && isPermanent()) {
getOverlayPanel().setLocation(realCardSize.x, realCardSize.y);
getOverlayPanel().setSize(realCardSize.width, realCardSize.height);
overlayPanel.setLocation(realCardSize.x, realCardSize.y);
overlayPanel.setSize(realCardSize.width, realCardSize.height);
} else {
getOverlayPanel().setVisible(false);
overlayPanel.setVisible(false);
}
if (getTypeIconPanel() != null) {
getTypeIconPanel().setLocation(realCardSize.x, realCardSize.y);
getTypeIconPanel().setSize(realCardSize.width, realCardSize.height);
if (typeIconPanel != null) {
typeIconPanel.setLocation(realCardSize.x, realCardSize.y);
typeIconPanel.setSize(realCardSize.width, realCardSize.height);
}
if (getCounterPanel() != null) {
getCounterPanel().setLocation(realCardSize.x, realCardSize.y);
getCounterPanel().setSize(realCardSize.width, realCardSize.height);
if (counterPanel != null) {
counterPanel.setLocation(realCardSize.x, realCardSize.y);
counterPanel.setSize(realCardSize.width, realCardSize.height);
int size = cardWidth > WIDTH_LIMIT ? 40 : 20;
minusCounterLabel.setLocation(getCounterPanel().getWidth() - size, getCounterPanel().getHeight() - size * 2);
minusCounterLabel.setLocation(counterPanel.getWidth() - size, counterPanel.getHeight() - size * 2);
minusCounterLabel.setSize(size, size);
plusCounterLabel.setLocation(5, getCounterPanel().getHeight() - size * 2);
plusCounterLabel.setLocation(5, counterPanel.getHeight() - size * 2);
plusCounterLabel.setSize(size, size);
loyaltyCounterLabel.setLocation(getCounterPanel().getWidth() - size, getCounterPanel().getHeight() - size);
loyaltyCounterLabel.setLocation(counterPanel.getWidth() - size, counterPanel.getHeight() - size);
loyaltyCounterLabel.setSize(size, size);
otherCounterLabel.setLocation(5, getCounterPanel().getHeight() - size);
otherCounterLabel.setLocation(5, counterPanel.getHeight() - size);
otherCounterLabel.setSize(size, size);
}
@ -668,41 +462,83 @@ public class CardPanelRenderModeImage extends CardPanel {
}
}
private void prepareGlowFont(GlowText label, int fontSize, MageInt value, boolean drawAsDamaged) {
label.setFont(getFont().deriveFont(Font.BOLD, fontSize));
label.setForeground(CardRendererUtils.getCardTextColor(value, drawAsDamaged, titleText.getForeground(), true));
Dimension ptSize = label.getPreferredSize();
label.setSize(ptSize.width, ptSize.height);
}
private void updatePTTexts(CardView card) {
if (card.isCreature() || card.getSubTypes().contains(SubType.VEHICLE)) {
ptText1.setText(getGameCard().getPower());
ptText2.setText("/");
ptText3.setText(CardRendererUtils.getCardLifeWithDamage(getGameCard()));
} else if (card.isPlanesWalker()) {
ptText1.setText("");
ptText2.setText("");
ptText3.setText(getGameCard().getLoyalty());
@Override
public Image getImage() {
if (this.hasImage) {
if (getGameCard().isFaceDown()) {
return getFaceDownImage();
} else {
ptText1.setText("");
ptText2.setText("");
ptText3.setText("");
return ImageCache.getImageOriginal(getGameCard());
}
ptText1.setForeground(Color.white);
ptText1.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY);
ptText2.setForeground(Color.white);
ptText2.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY);
ptText3.setForeground(Color.white);
ptText3.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY);
}
return null;
}
@Override
public String toString() {
return getGameCard().toString();
protected void paintCard(Graphics2D g2d) {
float alpha = getAlpha();
if (alpha != 1.0f) {
AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha);
g2d.setComposite(composite);
}
// draw background (selected/chooseable/playable)
MageCardLocation cardLocation = getCardLocation();
g2d.drawImage(
IMAGE_CACHE.getOrThrow(
new Key(getInsets(),
cardLocation.getCardWidth(), cardLocation.getCardHeight(),
cardLocation.getCardWidth(), cardLocation.getCardHeight(),
0,
0,
hasImage, isSelected(), isChoosable(), getGameCard().isPlayable(), getGameCard().isCanAttack(),
getGameCard().isCanBlock())),
0, 0, cardLocation.getCardWidth(), cardLocation.getCardHeight(), null);
g2d.dispose();
}
@Override
protected void paintChildren(Graphics g) {
super.paintChildren(g);
/*
// debug draw recs
// full card
g.setColor(new Color(255, 0, 0));
g.drawRect(realCard.rectFull.x, realCard.rectFull.y, realCard.rectFull.width, realCard.rectFull.height);
// real card - image
g.setColor(new Color(0, 0, 255));
g.drawRect(imagePanel.getX(), imagePanel.getY(), imagePanel.getBounds().width, imagePanel.getBounds().height);
// caption
g.setColor(new Color(0, 255, 255));
g.drawRect(titleText.getX(), titleText.getY(), titleText.getBounds().width, titleText.getBounds().height);
// life points
g.setColor(new Color(120, 0, 120));
g.drawRect(ptText.getX(), ptText.getY(), ptText.getBounds().width, ptText.getBounds().height);
//*/
if (getShowCastingCost() && !isAnimationPanel() && canShowCardIcons(getCardWidth(), hasImage)) {
int symbolMarginX = 2; // 2 px between icons
String manaCost = ManaSymbols.getClearManaCost(getGameCard().getManaCostStr());
int manaWidth = getManaWidth(manaCost, symbolMarginX);
// right top corner with margin (sizes from any sample card, length from black border to mana icon)
int manaMarginRight = Math.round(22f / 672f * getCardWidth());
int manaMarginTop = Math.round(24f / 936f * getCardHeight());
int cardOffsetX = 0;
int cardOffsetY = 0;
int manaX = cardOffsetX + getCardWidth() - manaMarginRight - manaWidth;
int manaY = cardOffsetY + manaMarginTop;
ManaSymbols.draw(g, manaCost, manaX, manaY, getSymbolWidth(), ModernCardRenderer.MANA_ICONS_TEXT_COLOR, symbolMarginX);
}
}
@Override
@ -734,9 +570,59 @@ public class CardPanelRenderModeImage extends CardPanel {
}
}
///////////////////////////////////////////////////////////
// Image updating code
private int updateArtImageStamp;
@Override
public void setSelected(boolean isSelected) {
super.setSelected(isSelected);
if (isSelected) {
this.titleText.setGlowColor(Color.green);
} else {
this.titleText.setGlowColor(Color.black);
}
}
@Override
public void showCardTitle() {
displayTitleAnyway = true;
setTitle(getGameCard());
}
@Override
public String toString() {
return getGameCard().toString();
}
@Override
public void transferResources(final CardPanel panelAbstract) {
if (panelAbstract instanceof CardPanelRenderModeImage) {
CardPanelRenderModeImage panel = (CardPanelRenderModeImage) panelAbstract;
synchronized (panel.imagePanel) {
if (panel.imagePanel.hasImage()) {
setImage(panel.imagePanel.getSrcImage());
}
}
}
}
@Override
public void update(CardView card) {
// Super
super.update(card);
// real card to show stores in getGameCard (e.g. after user clicks on night icon -- night card must be rendered)
updatePTTexts(getGameCard());
setTitle(getGameCard());
// Summoning Sickness overlay
overlayPanel.setVisible(hasSickness() && getGameCard().isCreature() && isPermanent());
// Update counters panel
if (counterPanel != null) {
updateCounters(card);
}
// Finally, queue a repaint
repaint();
}
@Override
public void updateArtImage() {
@ -787,32 +673,69 @@ public class CardPanelRenderModeImage extends CardPanel {
}
}
@Override
public void showCardTitle() {
displayTitleAnyway = true;
setTitle(getGameCard());
private int getManaWidth(String manaCost, int symbolMarginX) {
int width = 0;
manaCost = manaCost.replace("\\", "");
StringTokenizer tok = new StringTokenizer(manaCost, " ");
while (tok.hasMoreTokens()) {
tok.nextToken();
if (width != 0) {
width += symbolMarginX;
}
width += getSymbolWidth();
}
return width;
}
@Override
public void update(CardView card) {
// Super
super.update(card);
// real card to show stores in getGameCard (e.g. after user clicks on night icon -- night card must be rendered)
updatePTTexts(getGameCard());
setTitle(getGameCard());
// Summoning Sickness overlay
getOverlayPanel().setVisible(hasSickness() && getGameCard().isCreature() && isPermanent());
// Update counters panel
if (getCounterPanel() != null) {
updateCounters(card);
private void prepareGlowFont(GlowText label, int fontSize, MageInt value, boolean drawAsDamaged) {
label.setFont(getFont().deriveFont(Font.BOLD, fontSize));
label.setForeground(CardRendererUtils.getCardTextColor(value, drawAsDamaged, titleText.getForeground(), true));
Dimension ptSize = label.getPreferredSize();
label.setSize(ptSize.width, ptSize.height);
}
// Finally, queue a repaint
private void setFullPath(String fullImagePath) {
this.fullImagePath = fullImagePath;
this.fullImagePath = this.fullImagePath.replaceAll("\\\\", "\\\\<br>");
this.fullImagePath = this.fullImagePath.replaceAll("/", "/<br>");
this.fullImagePath = "<html>" + this.fullImagePath + "</html>";
fullImageText.setText(!displayFullImagePath ? "" : this.fullImagePath);
doLayout();
}
private void setImage(BufferedImage srcImage) {
synchronized (imagePanel) {
if (srcImage != null) {
imagePanel.setImage(srcImage);
} else {
imagePanel.clearImage();
}
repaint();
}
doLayout();
}
private void setTitle(CardView card) {
titleText.setText(!displayTitleAnyway && hasImage ? "" : card.getDisplayName());
}
private void setTypeIcon(BufferedImage bufferedImage, String toolTipText) {
typeIconPanel = new JPanel();
typeIconPanel.setLayout(null);
typeIconPanel.setOpaque(false);
add(typeIconPanel);
typeIconButton = new JButton("");
typeIconButton.setLocation(2, 2);
typeIconButton.setSize(25, 25);
typeIconPanel.setVisible(true);
typeIconButton.setIcon(new ImageIcon(bufferedImage));
if (toolTipText != null) {
typeIconButton.setToolTipText(toolTipText);
}
typeIconPanel.add(typeIconButton);
}
private void updateCounters(CardView card) {
if (card.getCounters() != null && !card.getCounters().isEmpty()) {
@ -865,43 +788,38 @@ public class CardPanelRenderModeImage extends CardPanel {
}
}
getCounterPanel().setVisible(true);
counterPanel.setVisible(true);
} else {
plusCounterLabel.setVisible(false);
minusCounterLabel.setVisible(false);
loyaltyCounterLabel.setVisible(false);
otherCounterLabel.setVisible(false);
getCounterPanel().setVisible(false);
counterPanel.setVisible(false);
}
}
private static ImageIcon getCounterImageWithAmount(int amount, BufferedImage image, int cardWidth) {
int factor = cardWidth > WIDTH_LIMIT ? 2 : 1;
int xOffset = amount > 9 ? 2 : 5;
int fontSize = factor == 1 ? amount < 10 ? 12 : amount < 100 ? 10 : amount < 1000 ? 7 : 6
: amount < 10 ? 19 : amount < 100 ? 15 : amount < 1000 ? 12 : amount < 10000 ? 9 : 8;
BufferedImage newImage;
if (cardWidth > WIDTH_LIMIT) {
newImage = ImageManagerImpl.deepCopy(image);
private void updatePTTexts(CardView card) {
if (card.isCreature() || card.getSubTypes().contains(SubType.VEHICLE)) {
ptText1.setText(getGameCard().getPower());
ptText2.setText("/");
ptText3.setText(CardRendererUtils.getCardLifeWithDamage(getGameCard()));
} else if (card.isPlanesWalker()) {
ptText1.setText("");
ptText2.setText("");
ptText3.setText(getGameCard().getLoyalty());
} else {
newImage = ImageHelper.getResizedImage(image, 20, 20);
}
Graphics graphics = newImage.getGraphics();
graphics.setColor(Color.BLACK);
graphics.setFont(new Font("Arial Black", amount > 100 ? Font.PLAIN : Font.BOLD, fontSize));
graphics.drawString(Integer.toString(amount), xOffset * factor, 11 * factor);
return new ImageIcon(newImage);
ptText1.setText("");
ptText2.setText("");
ptText3.setText("");
}
@Override
public Image getImage() {
if (this.hasImage) {
if (getGameCard().isFaceDown()) {
return getFaceDownImage();
} else {
return ImageCache.getImageOriginal(getGameCard());
}
}
return null;
ptText1.setForeground(Color.white);
ptText1.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY);
ptText2.setForeground(Color.white);
ptText2.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY);
ptText3.setForeground(Color.white);
ptText3.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY);
}
}

View file

@ -16,6 +16,7 @@ import org.mage.plugins.card.images.ImageCache;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@ -25,94 +26,67 @@ import java.util.concurrent.TimeUnit;
*/
public class CardPanelRenderModeMTGO extends CardPanel {
private static boolean cardViewEquals(CardView a, CardView b) {
// Map of generated images
private final static Cache<ImageKey, BufferedImage> IMAGE_CACHE = CacheBuilder
.newBuilder()
.maximumSize(3000)
.expireAfterAccess(60, TimeUnit.MINUTES)
.softValues()
.build();
// The art image for the card, loaded in from the disk
private BufferedImage artImage;
// The faceart image for the card, loaded in from the disk (based on artid from mtgo)
private BufferedImage faceArtImage;
// Factory to generate card appropriate views
private final CardRendererFactory cardRendererFactory = new CardRendererFactory();
// The rendered card image, with or without the art image loaded yet
// = null while invalid
private BufferedImage cardImage;
private CardRenderer cardRenderer;
private int updateArtImageStamp;
private static boolean cardViewEquals(CardView a, CardView b) { // TODO: This belongs in CardView
if (a == b) {
return true;
}
if (a.getClass() != b.getClass()) {
if (a == null || b == null || a.getClass() != b.getClass()) {
return false;
}
if (!a.getDisplayName().equals(b.getDisplayName())) {
if (!(a.getDisplayName().equals(b.getDisplayName()) // TODO: Original code not checking everything. Why is it only checking these values?
&& a.getPower().equals(b.getPower())
&& a.getToughness().equals(b.getToughness())
&& a.getLoyalty().equals(b.getLoyalty())
&& 0 == a.getColor().compareTo(b.getColor())
&& a.getCardTypes().equals(b.getCardTypes())
&& a.getSubTypes().equals(b.getSubTypes())
&& a.getSuperTypes().equals(b.getSuperTypes())
&& a.getManaCostStr().equals(b.getManaCostStr())
&& a.getRules().equals(b.getRules())
&& Objects.equals(a.getRarity(), b.getRarity())
&& Objects.equals(a.getCardNumber(), b.getCardNumber())
&& Objects.equals(a.getExpansionSetCode(), b.getExpansionSetCode())
&& a.getFrameStyle() == b.getFrameStyle()
&& Objects.equals(a.getCounters(), b.getCounters())
&& a.isFaceDown() == b.isFaceDown())) {
return false;
}
if (!a.getPower().equals(b.getPower())) {
return false;
}
if (!a.getToughness().equals(b.getToughness())) {
return false;
}
if (!a.getLoyalty().equals(b.getLoyalty())) {
return false;
}
if (0 != a.getColor().compareTo(b.getColor())) {
return false;
}
if (!a.getCardTypes().equals(b.getCardTypes())) {
return false;
}
if (!a.getSubTypes().equals(b.getSubTypes())) {
return false;
}
if (!a.getSuperTypes().equals(b.getSuperTypes())) {
return false;
}
if (!a.getManaCostStr().equals(b.getManaCostStr())) {
return false;
}
if (!a.getRules().equals(b.getRules())) {
return false;
}
if (a.getRarity() == null || b.getRarity() == null) {
return false;
}
if (a.getRarity() != b.getRarity()) {
return false;
}
if (a.getCardNumber() != null && !a.getCardNumber().equals(b.getCardNumber())) {
return false;
}
// Expansion set code, with null checking:
// TODO: The null checks should not be necessary, but thanks to Issue #2260
// some tokens / commandobjects will be missing expansion set codes.
String expA = a.getExpansionSetCode();
if (expA == null) {
expA = "";
}
String expB = b.getExpansionSetCode();
if (expB == null) {
expB = "";
}
if (!expA.equals(expB)) {
return false;
}
if (a.getFrameStyle() != b.getFrameStyle()) {
return false;
}
if (a.getCounters() == null) {
if (b.getCounters() != null) {
return false;
}
} else if (!a.getCounters().equals(b.getCounters())) {
return false;
}
if (a.isFaceDown() != b.isFaceDown()) {
return false;
}
if ((a instanceof PermanentView)) {
PermanentView aa = (PermanentView) a;
PermanentView bb = (PermanentView) b;
if (aa.hasSummoningSickness() != bb.hasSummoningSickness()) {
// Note: b must be a permanentview too as we aleady checked that classes
// are the same for a and b
return false;
}
return aa.getDamage() == bb.getDamage();
}
if (!(a instanceof PermanentView)) {
return true;
}
PermanentView aa = (PermanentView) a;
PermanentView bb = (PermanentView) b;
return aa.hasSummoningSickness() == bb.hasSummoningSickness()
&& aa.getDamage() == bb.getDamage();
}
static class ImageKey {
private static class ImageKey {
final BufferedImage artImage;
final int width;
final int height;
@ -133,7 +107,7 @@ public class CardPanelRenderModeMTGO extends CardPanel {
this.hashCode = hashCodeImpl();
}
private int hashCodeImpl() {
private int hashCodeImpl() { // TODO: Why is this using a string builder???
StringBuilder sb = new StringBuilder();
sb.append((char) (artImage != null ? 1 : 0));
sb.append((char) width);
@ -185,60 +159,23 @@ public class CardPanelRenderModeMTGO extends CardPanel {
@Override
public boolean equals(Object object) {
// Initial checks
if (this == object) {
return true;
}
if (object == null) {
if (object == null || this.getClass() != object.getClass()) {
return false;
}
if (!(object instanceof ImageKey)) {
return false;
}
final ImageKey other = (ImageKey) object;
final ImageKey that = (ImageKey) object;
// Compare
if ((artImage == null) == (other.artImage != null)) {
return false;
}
if (width != other.width) {
return false;
}
if (height != other.height) {
return false;
}
if (isChoosable != other.isChoosable) {
return false;
}
if (isSelected != other.isSelected) {
return false;
}
return cardViewEquals(view, other.view);
return (artImage == null) == (that.artImage == null)
&& this.width == that.width
&& this.height == that.height
&& this.isChoosable == that.isChoosable
&& this.isSelected == that.isSelected
&& cardViewEquals(this.view, that.view);
}
}
// Map of generated images
private final static Cache<ImageKey, BufferedImage> IMAGE_CACHE = CacheBuilder
.newBuilder()
.maximumSize(3000)
.expireAfterAccess(60, TimeUnit.MINUTES)
.softValues()
.build();
// The art image for the card, loaded in from the disk
private BufferedImage artImage;
// The faceart image for the card, loaded in from the disk (based on artid from mtgo)
private BufferedImage faceArtImage;
// Factory to generate card appropriate views
private final CardRendererFactory cardRendererFactory = new CardRendererFactory();
// The rendered card image, with or without the art image loaded yet
// = null while invalid
private BufferedImage cardImage;
private CardRenderer cardRenderer;
public CardPanelRenderModeMTGO(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback,
final boolean foil, Dimension dimension, boolean needFullPermanentRender) {
// Call to super
@ -252,16 +189,14 @@ public class CardPanelRenderModeMTGO extends CardPanel {
}
@Override
public void transferResources(CardPanel panel) {
if (panel instanceof CardPanelRenderModeMTGO) {
CardPanelRenderModeMTGO impl = (CardPanelRenderModeMTGO) panel;
// Use the art image and current rendered image from the card
artImage = impl.artImage;
cardRenderer.setArtImage(artImage);
faceArtImage = impl.faceArtImage;
cardRenderer.setFaceArtImage(faceArtImage);
cardImage = impl.cardImage;
public Image getImage() {
if (artImage == null) {
return null;
}
if (getGameCard().isFaceDown()) {
return getFaceDownImage();
} else {
return ImageCache.getImageOriginal(getGameCard());
}
}
@ -288,38 +223,72 @@ public class CardPanelRenderModeMTGO extends CardPanel {
g.drawImage(cardImage, cardOffsetX, cardOffsetY, null);
}
/**
* Render the card to a new BufferedImage at it's current dimensions
*
* @return image
*/
private BufferedImage renderCard() {
int cardWidth = getCardWidth();
int cardHeight = getCardHeight();
@Override
public void setCardBounds(int x, int y, int cardWidth, int cardHeight) {
int oldCardWidth = getCardWidth();
int oldCardHeight = getCardHeight();
// Create image to render to
BufferedImage image
= GraphicsUtilities.createCompatibleTranslucentImage(cardWidth, cardHeight);
Graphics2D g2d = image.createGraphics();
super.setCardBounds(x, y, cardWidth, cardHeight);
// Render with Antialialsing
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
// Draw card itself
cardRenderer.draw(g2d, getAttributes(), image);
// Done
g2d.dispose();
return image;
// Rerender if card size changed
if (getCardWidth() != oldCardWidth || getCardHeight() != oldCardHeight) {
cardImage = null;
}
}
private CardPanelAttributes getAttributes() {
return new CardPanelAttributes(getCardWidth(), getCardHeight(), isChoosable(), isSelected(), isTransformed());
@Override
public void setChoosable(boolean choosable) {
if (choosable != isChoosable()) {
super.setChoosable(choosable);
// Invalidate our render and trigger a repaint
cardImage = null;
repaint();
}
}
private int updateArtImageStamp;
@Override
public void setSelected(boolean selected) {
if (selected != isSelected()) {
super.setSelected(selected);
// Invalidate our render and trigger a repaint
cardImage = null;
repaint();
}
}
@Override
public void showCardTitle() {
// Nothing to do, rendered cards always have a title
}
@Override
public void transferResources(CardPanel panel) {
if (panel instanceof CardPanelRenderModeMTGO) {
CardPanelRenderModeMTGO impl = (CardPanelRenderModeMTGO) panel;
// Use the art image and current rendered image from the card
artImage = impl.artImage;
cardRenderer.setArtImage(artImage);
faceArtImage = impl.faceArtImage;
cardRenderer.setFaceArtImage(faceArtImage);
cardImage = impl.cardImage;
}
}
@Override
public void update(CardView card) {
// Update super
super.update(card);
// Update renderer
cardImage = null;
cardRenderer = cardRendererFactory.create(getGameCard());
cardRenderer.setArtImage(artImage);
cardRenderer.setFaceArtImage(faceArtImage);
// Repaint
repaint();
}
@Override
public void updateArtImage() {
@ -379,32 +348,8 @@ public class CardPanelRenderModeMTGO extends CardPanel {
}
}
@Override
public void update(CardView card) {
// Update super
super.update(card);
// Update renderer
cardImage = null;
cardRenderer = cardRendererFactory.create(getGameCard());
cardRenderer.setArtImage(artImage);
cardRenderer.setFaceArtImage(faceArtImage);
// Repaint
repaint();
}
@Override
public void setCardBounds(int x, int y, int cardWidth, int cardHeight) {
int oldCardWidth = getCardWidth();
int oldCardHeight = getCardHeight();
super.setCardBounds(x, y, cardWidth, cardHeight);
// Rerender if card size changed
if (getCardWidth() != oldCardWidth || getCardHeight() != oldCardHeight) {
cardImage = null;
}
private CardPanelAttributes getAttributes() {
return new CardPanelAttributes(getCardWidth(), getCardHeight(), isChoosable(), isSelected(), isTransformed());
}
private BufferedImage getFaceDownImage() {
@ -422,40 +367,30 @@ public class CardPanelRenderModeMTGO extends CardPanel {
}
}
@Override
public void setSelected(boolean selected) {
if (selected != isSelected()) {
super.setSelected(selected);
// Invalidate our render and trigger a repaint
cardImage = null;
repaint();
}
}
/**
* Render the card to a new BufferedImage at it's current dimensions
*
* @return image
*/
private BufferedImage renderCard() {
int cardWidth = getCardWidth();
int cardHeight = getCardHeight();
@Override
public void setChoosable(boolean choosable) {
if (choosable != isChoosable()) {
super.setChoosable(choosable);
// Invalidate our render and trigger a repaint
cardImage = null;
repaint();
}
}
// Create image to render to
BufferedImage image
= GraphicsUtilities.createCompatibleTranslucentImage(cardWidth, cardHeight);
Graphics2D g2d = image.createGraphics();
@Override
public Image getImage() {
if (artImage != null) {
if (getGameCard().isFaceDown()) {
return getFaceDownImage();
} else {
return ImageCache.getImageOriginal(getGameCard());
}
}
return null;
}
// Render with Antialialsing
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
@Override
public void showCardTitle() {
// Nothing to do, rendered cards always have a title
// Draw card itself
cardRenderer.draw(g2d, getAttributes(), image);
// Done
g2d.dispose();
return image;
}
}