* 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,10 +1,11 @@
package org.mage.card.arcane;
import mage.cards.MageCard;
import javax.swing.*;
import java.awt.*;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.*;
import mage.cards.MagePermanent;
public abstract class Animation {
@ -18,7 +19,7 @@ public abstract class Animation {
private static CardPanel enlargedAnimationPanel;
private static final Object enlargeLock = new Object();
private TimerTask timerTask;
private final TimerTask timerTask;
private FrameTimer frameTimer;
private long elapsed;
@ -115,53 +116,59 @@ public abstract class Animation {
}
}
public static void tapCardToggle(final CardPanel panel, final MagePermanent parent, final boolean tapped, final boolean flipped) {
public static void tapCardToggle(final CardPanel source, final boolean tapped, final boolean flipped) {
CardPanel mainPanel = source;
MageCard parentPanel = mainPanel.getTopPanelRef();
new Animation(300) {
@Override
protected void start() {
parent.onBeginAnimation();
parentPanel.onBeginAnimation();
}
@Override
protected void update(float percentage) {
if (tapped) {
panel.setTappedAngle(CardPanel.TAPPED_ANGLE * percentage);
mainPanel.setTappedAngle(CardPanel.TAPPED_ANGLE * percentage);
// reverse movement if untapping
if (!panel.isTapped()) {
panel.setTappedAngle(CardPanel.TAPPED_ANGLE - panel.getTappedAngle());
if (!mainPanel.isTapped()) {
mainPanel.setTappedAngle(CardPanel.TAPPED_ANGLE - mainPanel.getTappedAngle());
}
}
if (flipped) {
panel.setFlippedAngle(CardPanel.FLIPPED_ANGLE * percentage);
if (!panel.isFlipped()) {
panel.setFlippedAngle(CardPanel.FLIPPED_ANGLE - panel.getFlippedAngle());
mainPanel.setFlippedAngle(CardPanel.FLIPPED_ANGLE * percentage);
if (!mainPanel.isFlipped()) {
mainPanel.setFlippedAngle(CardPanel.FLIPPED_ANGLE - mainPanel.getFlippedAngle());
}
}
panel.repaint();
parentPanel.repaint();
}
@Override
protected void end() {
if (tapped) {
panel.setTappedAngle(panel.isTapped() ? CardPanel.TAPPED_ANGLE : 0);
mainPanel.setTappedAngle(mainPanel.isTapped() ? CardPanel.TAPPED_ANGLE : 0);
}
if (flipped) {
panel.setFlippedAngle(panel.isFlipped() ? CardPanel.FLIPPED_ANGLE : 0);
mainPanel.setFlippedAngle(mainPanel.isFlipped() ? CardPanel.FLIPPED_ANGLE : 0);
}
parent.onEndAnimation();
parent.repaint();
parentPanel.onEndAnimation();
parentPanel.repaint();
}
};
}
public static void transformCard(final CardPanel panel, final MagePermanent parent, final boolean transformed) {
public static void transformCard(final CardPanel source) {
CardPanel mainPanel = source;
MageCard parentPanel = mainPanel.getTopPanelRef();
new Animation(600) {
private boolean state = false;
@Override
protected void start() {
parent.onBeginAnimation();
parentPanel.onBeginAnimation();
}
@Override
@ -169,48 +176,51 @@ public abstract class Animation {
double p = percentage * 2;
if (percentage > 0.5) {
if (!state) {
parent.toggleTransformed();
parentPanel.toggleTransformed();
}
state = true;
p = (p - 0.5) * 2;
}
if (!state) {
panel.transformAngle = Math.max(0.01, 1 - p);
mainPanel.transformAngle = Math.max(0.01, 1 - p);
} else {
panel.transformAngle = Math.max(0.01, p - 1);
mainPanel.transformAngle = Math.max(0.01, p - 1);
}
panel.repaint();
parentPanel.repaint();
}
@Override
protected void end() {
if (!state) {
parent.toggleTransformed();
parentPanel.toggleTransformed();
}
state = true;
panel.transformAngle = 1;
mainPanel.transformAngle = 1;
parent.onEndAnimation();
parent.repaint();
parentPanel.onEndAnimation();
parentPanel.repaint();
}
};
}
public static void moveCardToPlay(final int startX, final int startY, final int startWidth, final int endX, final int endY,
final int endWidth, final CardPanel animationPanel, final CardPanel placeholder, final JLayeredPane layeredPane,
final int speed) {
final int endWidth, final CardPanel cardToAnimate, final CardPanel placeholder, final JLayeredPane layeredPane,
final int speed) {
CardPanel cardPanel = (CardPanel) cardToAnimate.getMainPanel();
MageCard mainPanel = cardToAnimate.getTopPanelRef();
UI.invokeLater(() -> {
final int startHeight = Math.round(startWidth * CardPanel.ASPECT_RATIO);
final int endHeight = Math.round(endWidth * CardPanel.ASPECT_RATIO);
final float a = 2f;
final float sqrta = (float) Math.sqrt(1 / a);
animationPanel.setCardBounds(startX, startY, startWidth, startHeight);
animationPanel.setAnimationPanel(true);
Container parent = animationPanel.getParent();
mainPanel.setCardBounds(startX, startY, startWidth, startHeight);
cardPanel.setAnimationPanel(true);
Container parent = mainPanel.getParent();
if (parent != null && !parent.equals(layeredPane)) {
layeredPane.add(animationPanel);
layeredPane.setLayer(animationPanel, JLayeredPane.MODAL_LAYER);
layeredPane.add(mainPanel);
layeredPane.setLayer(mainPanel, JLayeredPane.MODAL_LAYER);
}
new Animation(700) {
@ -241,7 +251,7 @@ public abstract class Animation {
}
currentX -= currentWidth / 2;
currentY -= currentHeight / 2;
animationPanel.setCardBounds(currentX, currentY, currentWidth, currentHeight);
mainPanel.setCardBounds(currentX, currentY, currentWidth, currentHeight);
}
@Override
@ -249,11 +259,11 @@ public abstract class Animation {
EventQueue.invokeLater(() -> {
if (placeholder != null) {
placeholder.setDisplayEnabled(true);
placeholder.transferResources(animationPanel);
placeholder.transferResources(cardPanel);
}
animationPanel.setVisible(false);
animationPanel.repaint();
layeredPane.remove(animationPanel);
mainPanel.setVisible(false);
mainPanel.repaint();
layeredPane.remove(mainPanel);
});
}
};
@ -261,18 +271,21 @@ public abstract class Animation {
}
public static void moveCard(final int startX, final int startY, final int startWidth, final int endX, final int endY,
final int endWidth, final CardPanel animationPanel, final CardPanel placeholder, final JLayeredPane layeredPane,
final int speed) {
final int endWidth, final MageCard cardToAnimate, final CardPanel placeholder, final JLayeredPane layeredPane,
final int speed) {
CardPanel cardPanel = (CardPanel) cardToAnimate.getMainPanel();
MageCard mainPanel = cardToAnimate.getTopPanelRef();
UI.invokeLater(() -> {
final int startHeight = Math.round(startWidth * CardPanel.ASPECT_RATIO);
final int endHeight = Math.round(endWidth * CardPanel.ASPECT_RATIO);
animationPanel.setCardBounds(startX, startY, startWidth, startHeight);
animationPanel.setAnimationPanel(true);
Container parent = animationPanel.getParent();
mainPanel.setCardBounds(startX, startY, startWidth, startHeight);
cardPanel.setAnimationPanel(true);
Container parent = mainPanel.getParent();
if (parent != null && !parent.equals(layeredPane)) {
layeredPane.add(animationPanel);
layeredPane.setLayer(animationPanel, JLayeredPane.MODAL_LAYER);
layeredPane.add(mainPanel);
layeredPane.setLayer(mainPanel, JLayeredPane.MODAL_LAYER);
}
new Animation(speed) {
@ -282,7 +295,7 @@ public abstract class Animation {
int currentY = startY + Math.round((endY - startY) * percentage);
int currentWidth = startWidth + Math.round((endWidth - startWidth) * percentage);
int currentHeight = startHeight + Math.round((endHeight - startHeight) * percentage);
animationPanel.setCardBounds(currentX, currentY, currentWidth, currentHeight);
mainPanel.setCardBounds(currentX, currentY, currentWidth, currentHeight);
}
@Override
@ -290,11 +303,11 @@ public abstract class Animation {
EventQueue.invokeLater(() -> {
if (placeholder != null) {
placeholder.setDisplayEnabled(true);
placeholder.transferResources(animationPanel);
placeholder.transferResources(cardPanel);
}
animationPanel.setVisible(false);
animationPanel.repaint();
layeredPane.remove(animationPanel);
mainPanel.setVisible(false);
mainPanel.repaint();
layeredPane.remove(mainPanel);
});
}
};
@ -327,7 +340,7 @@ public abstract class Animation {
protected void update(float percentage) {
int currentWidth = startWidth + Math.round((endWidth - startWidth) * percentage);
int currentHeight = startHeight + Math.round((endHeight - startHeight) * percentage);
Point startPos = SwingUtilities.convertPoint(overPanel.getParent(), overPanel.getCardLocation(), layeredPane);
Point startPos = SwingUtilities.convertPoint(overPanel.getParent(), overPanel.getCardLocation().getCardPoint(), layeredPane);
int centerX = startPos.x + Math.round(endWidth / 2f);
int centerY = startPos.y + Math.round(endHeight / 2f);
int currentX = Math.max(0, centerX - Math.round(currentWidth / 2f));
@ -353,7 +366,7 @@ public abstract class Animation {
}
}
public static void showCard(final MagePermanent card, int count) {
public static void showCard(final MageCard card, int count) {
if (count == 0) {
return;
}
@ -376,7 +389,7 @@ public abstract class Animation {
};
}
public static void hideCard(final MagePermanent card, int count) {
public static void hideCard(final MageCard card, int count) {
if (count == 0) {
return;
}

View file

@ -1,16 +1,13 @@
package org.mage.card.arcane;
import mage.cards.MagePermanent;
import mage.cards.TextPopup;
import mage.cards.*;
import mage.cards.action.ActionCallback;
import mage.cards.action.TransferData;
import mage.client.plugins.adapters.MageActionCallback;
import mage.client.plugins.impl.Plugins;
import mage.client.util.GUISizeHelper;
import mage.client.util.audio.AudioManager;
import mage.constants.CardType;
import mage.constants.EnlargeMode;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.*;
import mage.view.AbilityView;
import mage.view.CardView;
import mage.view.PermanentView;
@ -24,19 +21,22 @@ import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.TimerTask;
import java.util.UUID;
/**
* Main class for drawing Mage card object.
*
* @author arcane, nantuko, noxx
* WARNING, if you want to catch mouse events then use cardEventSource and related code. You can't use outer listeners.
*
* @author arcane, nantuko, noxx, JayDi85
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public abstract class CardPanel extends MagePermanent implements MouseListener, MouseMotionListener, MouseWheelListener, ComponentListener {
public abstract class CardPanel extends MagePermanent implements ComponentListener, MouseListener, MouseMotionListener, MouseWheelListener {
private static final long serialVersionUID = -3272134219262184410L;
private static final Logger LOGGER = Logger.getLogger(CardPanel.class);
private static final Logger logger = Logger.getLogger(CardPanel.class);
public static final double TAPPED_ANGLE = Math.PI / 2;
public static final double FLIPPED_ANGLE = Math.PI;
@ -57,7 +57,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
private double tappedAngle = 0;
private double flippedAngle = 0;
private final List<MagePermanent> links = new ArrayList<>();
private final List<MageCard> links = new ArrayList<>();
public final JPanel buttonPanel;
private JButton dayNightButton;
@ -65,7 +65,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
private boolean displayEnabled = true;
private boolean isAnimationPanel;
private int cardXOffset, cardYOffset, cardWidth, cardHeight;
private int cardWidth, cardHeight;
private int symbolWidth;
private boolean isSelected;
@ -82,7 +82,12 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
private boolean isPermanent;
private boolean hasSickness;
private String zone;
private Zone zone;
// mouse clicks
private int mouseClicksCount = 0;
private java.util.Timer mouseResetTimer = null;
static private final int MOUSE_DOUBLE_CLICK_RESET_MS = 200;
// Permanent and card renders are different (another sizes and positions of panel, tapped, etc -- that's weird)
// Some card view components support only permanents (BattlefieldPanel), but another support only cards (CardArea)
@ -95,7 +100,8 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
private boolean transformed;
private boolean animationInProgress = false;
private JPanel cardArea;
private Container cardContainer;
private MageCard topPanel;
// default offset, e.g. for battlefield
private int yCardCaptionOffsetPercent = 8; // card caption offset (use for moving card caption view center, below mana icons -- for more good UI)
@ -104,6 +110,8 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
private JPopupMenu popupMenu;
public CardPanel(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, final boolean foil, Dimension dimension, boolean needFullPermanentRender) {
// warning, it can be used under MageLayer so make all rotates or other card manipulation as parent
// Store away params
this.setGameCard(newGameCard);
this.callback = callback;
@ -154,7 +162,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
if (animationInProgress || isTapped() || isPermanent) {
return;
}
Animation.transformCard(CardPanel.this, CardPanel.this, true);
Animation.transformCard(this);
});
// Add it
@ -183,6 +191,8 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
setOpaque(false);
// JPanel event listeners
// all listeneres to process mouse and another events
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
@ -201,7 +211,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
@Override
public void doLayout() {
// Position transform and show source buttons
buttonPanel.setLocation(cardXOffset, cardYOffset);
buttonPanel.setLocation(0, 0);
buttonPanel.setSize(cardWidth, cardHeight);
int x = cardWidth / 20;
int y = cardHeight / 10;
@ -255,12 +265,12 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
public abstract void transferResources(CardPanel panel);
@Override
public void setZone(String zone) {
public void setZone(Zone zone) {
this.zone = zone;
}
@Override
public String getZone() {
public Zone getZone() {
return zone;
}
@ -290,15 +300,21 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
}
@Override
public List<MagePermanent> getLinks() {
public List<MageCard> getLinks() {
return links;
}
@Override
public MageCardSpace getOuterSpace() {
return MageCardSpace.empty;
}
@Override
public void setChoosable(boolean isChoosable) {
this.isChoosable = isChoosable;
}
@Override
public boolean isChoosable() {
return this.isChoosable;
}
@ -312,8 +328,18 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
}
@Override
public void setCardAreaRef(JPanel cardArea) {
this.cardArea = cardArea;
public void setCardContainerRef(Container cardContainer) {
this.cardContainer = cardContainer;
}
@Override
public void setTopPanelRef(MageCard topPanel) {
this.topPanel = topPanel;
}
@Override
public MageCard getTopPanelRef() {
return this.topPanel;
}
public void setShowCastingCost(boolean showCastingCost) {
@ -333,25 +359,9 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
@Override
public void paint(Graphics g) {
if (!displayEnabled) {
return;
}
if (!isValid()) {
super.validate();
}
Graphics2D g2d = (Graphics2D) g;
if (transformAngle < 1) {
float edgeOffset = (cardWidth + cardXOffset) / 2f;
g2d.translate(edgeOffset * (1 - transformAngle), 0);
g2d.scale(transformAngle, 1);
}
if (getTappedAngle() + getFlippedAngle() > 0) {
g2d = (Graphics2D) g2d.create();
float edgeOffset = cardWidth / 2f;
double angle = getTappedAngle() + (Math.abs(getFlippedAngle() - FLIPPED_ANGLE) < 0.001 ? 0 : getFlippedAngle());
g2d.rotate(angle, cardXOffset + edgeOffset, cardYOffset + cardHeight - edgeOffset);
}
super.paint(g2d);
// card rotating implemented by top layer panel
// TODO: is CardPanel can be used without MageLayer?
super.paint(g);
}
@Override
@ -373,70 +383,34 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
@Override
public void setCardBounds(int x, int y, int cardWidth, int cardHeight) {
if (cardWidth == this.cardWidth && cardHeight == this.cardHeight) {
setBounds(x - cardXOffset, y - cardYOffset, getWidth(), getHeight());
return;
}
this.cardWidth = cardWidth;
this.symbolWidth = cardWidth / 7;
this.cardHeight = cardHeight;
if (this.isPermanent && needFullPermanentRender) {
int rotCenterX = Math.round(cardWidth / 2f);
int rotCenterY = cardHeight - rotCenterX;
int rotCenterToTopCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_TOP_CORNER);
int rotCenterToBottomCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_BOTTOM_CORNER);
int xOffset = getXOffset(cardWidth);
int yOffset = getYOffset(cardWidth, cardHeight);
cardXOffset = -xOffset;
cardYOffset = -yOffset;
int width = -xOffset + rotCenterX + rotCenterToTopCorner;
int height = -yOffset + rotCenterY + rotCenterToBottomCorner;
setBounds(x + xOffset, y + yOffset, width, height);
// coords changed
//noinspection deprecation
setBounds(x, y, getWidth(), getHeight());
} else {
cardXOffset = 0;
cardYOffset = 0;
int width = cardXOffset * 2 + cardWidth;
int height = cardYOffset * 2 + cardHeight;
setBounds(x - cardXOffset, y - cardYOffset, width, height);
// coords + sizes changed
this.cardWidth = cardWidth;
this.symbolWidth = cardWidth / 7;
this.cardHeight = cardHeight;
// no needs in size settings here - all outer/draw spaces calcs by top parent panel
//noinspection deprecation
setBounds(x, y, cardWidth, cardHeight);
}
}
public int getXOffset(int cardWidth) {
if (this.isPermanent && needFullPermanentRender) {
int rotCenterX = Math.round(cardWidth / 2f);
int rotCenterToBottomCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_BOTTOM_CORNER);
int xOffset = rotCenterX - rotCenterToBottomCorner;
return xOffset;
} else {
return cardXOffset;
}
}
public final int getYOffset(int cardWidth, int cardHeight) {
if (this.isPermanent && needFullPermanentRender) {
int rotCenterX = Math.round(cardWidth / 2f);
int rotCenterY = cardHeight - rotCenterX;
int rotCenterToTopCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_TOP_CORNER);
int yOffset = rotCenterY - rotCenterToTopCorner;
return yOffset;
} else {
return cardYOffset;
}
}
public final int getCardX() {
return getX() + cardXOffset;
return getX() + this.getOuterSpace().getLeft();
}
public final int getCardY() {
return getY() + cardYOffset;
return getY() + this.getOuterSpace().getTop();
}
@Override
public final int getCardWidth() {
return cardWidth;
}
@Override
public final int getCardHeight() {
return cardHeight;
}
@ -445,13 +419,6 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
return symbolWidth;
}
public final Point getCardLocation() {
Point p = getLocation();
p.x += cardXOffset;
p.y += cardYOffset;
return p;
}
public final CardView getCard() {
return this.getGameCard();
}
@ -466,12 +433,38 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
return alpha;
}
public final int getCardXOffset() {
return cardXOffset;
}
@Override
public MageCardAnimationSettings getAnimationSettings(int offsetX, int offsetY, float cardBoundWidth, float cardBoundHeight) {
// card panel can be rotated after tap so send drawning settings to rotate parent panel too
MageCardAnimationSettings settings = new MageCardAnimationSettings();
public final int getCardYOffset() {
return cardYOffset;
// display
settings.withVisible(this.displayEnabled);
// TODO: remove cardXOffset and cardYOffset
// animate tap
if (getTappedAngle() + getFlippedAngle() > 0) {
// Rectangle rotation to keep bottom left corner
// Algorithm logic:
// 1. Take the start and the final figure positions (example: vertical and horizontal)
// 2. Find share figure between start/end positions;
// 3. Find center of the share figure;
// 4. Rotate from that center.
// Rotate center schema: https://user-images.githubusercontent.com/8344157/104398558-6981b500-5568-11eb-9e97-5c16926d481b.png
double angle = getTappedAngle() + (Math.abs(getFlippedAngle() - FLIPPED_ANGLE) < 0.001 ? 0 : getFlippedAngle());
float edgeOffset = cardBoundWidth / 2f;
settings.withRotate(angle, offsetX + edgeOffset, offsetY + cardBoundHeight - edgeOffset);
}
// animate transform (shrink/flip animation)
if (transformAngle < 1) {
float edgeOffset = cardBoundWidth / 2f;
settings.withTranslate((offsetX + edgeOffset) * (1 - transformAngle), 0);
settings.withScale(transformAngle, 1);
}
return settings;
}
@Override
@ -542,14 +535,14 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
boolean needsTapping = isTapped() != ((PermanentView) card).isTapped();
boolean needsFlipping = isFlipped() != ((PermanentView) card).isFlipped();
if (needsTapping || needsFlipping) {
Animation.tapCardToggle(this, this, needsTapping, needsFlipping);
Animation.tapCardToggle(this, needsTapping, needsFlipping);
}
if (needsTapping && ((PermanentView) card).isTapped()) {
AudioManager.playTapPermanent();
}
boolean needsTranforming = isTransformed() != card.isTransformed();
if (needsTranforming) {
Animation.transformCard(this, this, card.isTransformed());
Animation.transformCard(this);
}
}
@ -589,27 +582,6 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
}
}
@Override
public boolean contains(int x, int y) {
return containsThis(x, y, true);
}
public boolean containsThis(int x, int y, boolean root) {
Point component = getLocation();
int cx = getCardX() - component.x;
int cy = getCardY() - component.y;
int cw = cardWidth;
int ch = cardHeight;
if (isTapped()) {
cy = ch - cw + cx;
ch = cw;
cw = cardHeight;
}
return x >= cx && x <= cx + cw && y >= cy && y <= cy + ch;
}
@Override
public CardView getOriginal() {
return this.getGameCard();
@ -617,6 +589,43 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
@Override
public void mouseClicked(MouseEvent e) {
data.setComponent(this);
data.setCard(this.getGameCard());
data.setGameId(this.gameId);
// popup menu processing
if (e.isPopupTrigger() || SwingUtilities.isRightMouseButton(e)) {
callback.popupMenuCard(e, data);
return;
}
// double clicks processing, see https://stackoverflow.com/questions/4051659/identifying-double-click-in-java
// logic: run timer to reset clicks counter
mouseClicksCount = e.getClickCount();
if (mouseClicksCount > 1) {
// forced to double click
if (mouseResetTimer != null) {
mouseResetTimer.cancel();
}
callback.mouseClicked(e, data, true);
} else {
// can be single or double click, start the reset timer
if (mouseResetTimer != null) {
mouseResetTimer.cancel();
}
mouseResetTimer = new java.util.Timer("mouseResetTimer", false);
mouseResetTimer.schedule(new TimerTask() {
@Override
public void run() {
if (mouseClicksCount == 1) {
callback.mouseClicked(e, data, false);
} else if (mouseClicksCount > 1) {
callback.mouseClicked(e, data, true);
}
mouseClicksCount = 0;
}
}, MOUSE_DOUBLE_CLICK_RESET_MS);
}
}
@Override
@ -626,7 +635,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
}
if (!tooltipShowing) {
synchronized (this) {
if (!tooltipShowing) {
if (!tooltipShowing) { // TODO: remove tooltip showing to callback processing code, not here
TransferData transferData = getTransferDataForMouseEntered();
if (this.isShowing()) {
tooltipShowing = true;
@ -637,31 +646,15 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
}
}
@Override
public void mouseDragged(MouseEvent e) {
data.setComponent(this);
callback.mouseDragged(e, data);
}
@Override
public void mouseMoved(MouseEvent e) {
if (getGameCard().hideInfo()) {
return;
}
data.setComponent(this);
callback.mouseMoved(e, data);
}
@Override
@Override
public void mouseExited(MouseEvent e) {
if (getGameCard().hideInfo()) {
return;
}
if (tooltipShowing) {
synchronized (this) {
if (tooltipShowing) {
tooltipShowing = false;
tooltipShowing = false; // TODO: same, move code for callback processing
data.setComponent(this);
data.setCard(this.getGameCard());
data.setPopupText(tooltipText);
@ -681,21 +674,55 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
@Override
public void mouseReleased(MouseEvent e) {
data.setComponent(this);
data.setCard(this.getGameCard());
data.setGameId(this.gameId);
callback.mouseReleased(e, data);
}
@Override
public void mouseDragged(MouseEvent e) {
data.setComponent(this);
data.setCard(this.getGameCard());
data.setGameId(this.gameId);
callback.mouseDragged(e, data);
}
@Override
public void mouseMoved(MouseEvent e) {
if (getGameCard().hideInfo()) {
return;
}
data.setComponent(this);
data.setCard(this.getGameCard());
data.setGameId(this.gameId);
callback.mouseMoved(e, data);
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (getGameCard().hideInfo()) {
return;
}
data.setComponent(this);
data.setCard(this.getGameCard());
data.setGameId(this.gameId);
callback.mouseWheelMoved(e, data);
}
/**
* Prepares data to be sent to action callback on client side.
*
* @return
*/
private TransferData getTransferDataForMouseEntered() {
MageCard cardPanel = this.getTopPanelRef();
data.setComponent(this);
data.setCard(this.getGameCard());
data.setPopupText(tooltipText);
data.setGameId(this.gameId);
data.setLocationOnScreen(data.getComponent().getLocationOnScreen()); // we need this for popup
data.setPopupOffsetX(isTapped() ? cardHeight + cardXOffset + POPUP_X_GAP : cardWidth + cardXOffset + POPUP_X_GAP);
data.setLocationOnScreen(cardPanel.getCardLocationOnScreen().getCardPoint()); // we need this for popup
data.setPopupOffsetX(isTapped() ? cardHeight + POPUP_X_GAP : cardWidth + POPUP_X_GAP);
data.setPopupOffsetY(40);
return data;
}
@ -760,7 +787,9 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
public void update(PermanentView card) {
this.hasSickness = card.hasSummoningSickness();
this.showCopySourceButton.setVisible(card.isCopy());
update((CardView) card);
// must update from top layer (e.g. card icons)
this.getTopPanelRef().update(card);
}
@Override
@ -771,12 +800,6 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
throw new IllegalStateException("Is not permanent.");
}
@Override
public void updateCallback(ActionCallback callback, UUID gameId) {
this.callback = callback;
this.gameId = gameId;
}
public void setTransformed(boolean transformed) {
this.transformed = transformed;
}
@ -798,7 +821,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
dayNightButton.setIcon(new ImageIcon(night));
}
if (this.getGameCard().getSecondCardFace() == null) {
LOGGER.error("no second side for card to transform!");
logger.error("no second side for card to transform!");
return;
}
if (!isPermanent) { // use only for custom transformation (when pressing day-night button)
@ -829,16 +852,8 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (getGameCard().hideInfo()) {
return;
}
data.setComponent(this);
callback.mouseWheelMoved(e, data);
}
public JPanel getCardArea() {
return cardArea;
public Container getCardContainer() {
return cardContainer;
}
@Override
@ -922,4 +937,32 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
public void setFlippedAngle(double flippedAngle) {
this.flippedAngle = flippedAngle;
}
@Override
public boolean contains(int x, int y) {
// if you need a mouse related features in the tapped state then implement contains here (see MageLayer for info)
// example: you want a working button
//return super.contains(x, y);
// Swing uses relative coords here (0,0 is component's top left corner)
MageCardLocation needLocation = this.getCardLocation();
Rectangle normalRect = new Rectangle(
0,
0,
needLocation.getCardWidth(),
needLocation.getCardHeight()
);
Rectangle animatedRect = MageLayer.animateCoords(this, normalRect);
return animatedRect.contains(x, y);
}
@Override
public Font getFont() {
Font res = super.getFont();
if (res == null) {
// workaround: sometimes the card panels haven't default font
res = GUISizeHelper.getCardFont();
}
return res;
}
}

View file

@ -1,8 +1,3 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.mage.card.arcane;
/**

View file

@ -1,6 +1,7 @@
package org.mage.card.arcane;
import mage.MageInt;
import mage.cards.MageCardLocation;
import mage.cards.action.ActionCallback;
import mage.client.constants.Constants;
import mage.client.dialog.PreferencesDialog;
@ -11,11 +12,11 @@ import mage.components.ImagePanel;
import mage.components.ImagePanelStyle;
import mage.constants.AbilityType;
import mage.constants.SubType;
import mage.util.DebugUtil;
import mage.view.CardView;
import mage.view.CounterView;
import mage.view.PermanentView;
import mage.view.StackAbilityView;
import org.apache.log4j.Logger;
import org.jdesktop.swingx.graphics.GraphicsUtilities;
import org.mage.plugins.card.images.ImageCache;
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
@ -27,18 +28,14 @@ import java.util.StringTokenizer;
import java.util.UUID;
/**
* Class for drawing the mage card object by using a form based JComponent
* approach
* Render mode: IMAGE
*
* @author arcane, nantuko, noxx, stravant, JayDi85
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class CardPanelComponentImpl extends CardPanel {
public class CardPanelRenderModeImage extends CardPanel {
private static final long serialVersionUID = -3272134219262184411L;
private static final Logger LOGGER = Logger.getLogger(CardPanelComponentImpl.class);
private static final int WIDTH_LIMIT = 90; // card width limit to create smaller counter
private static final float ROUNDED_CORNER_SIZE = 0.1f;
@ -47,10 +44,6 @@ public class CardPanelComponentImpl extends CardPanel {
private static final int TEXT_GLOW_SIZE = 6;
private static final float TEXT_GLOW_INTENSITY = 3f;
// size to show icons and text (help to see full size card without text)
private static final int CARD_MIN_SIZE_FOR_ICONS = 60;
private static final int CARD_MAX_SIZE_FOR_ICONS = 200;
// text min size for image render mode
private static final int CARD_TITLE_FONT_MIN_SIZE = 13;
private static final int CARD_PT_FONT_MIN_SIZE = 17;
@ -58,9 +51,10 @@ public class CardPanelComponentImpl extends CardPanel {
public final ScaledImagePanel imagePanel;
private ImagePanel overlayPanel;
private JPanel iconPanel;
private JButton typeButton;
private JPanel ptPanel;
// triggered/activated/permanent icon
private JPanel typeIconPanel;
private JButton typeIconButton;
private final JPanel ptPanel;
private JPanel counterPanel;
private JLabel loyaltyCounterLabel;
@ -83,7 +77,7 @@ public class CardPanelComponentImpl extends CardPanel {
private boolean hasImage = false;
private boolean displayTitleAnyway;
private boolean displayFullImagePath;
private final boolean displayFullImagePath;
private final static SoftValuesLoadingCache<Key, BufferedImage> IMAGE_CACHE;
@ -95,12 +89,12 @@ public class CardPanelComponentImpl extends CardPanel {
this.overlayPanel = overlayPanel;
}
public JPanel getIconPanel() {
return iconPanel;
public JPanel getTypeIconPanel() {
return typeIconPanel;
}
public void setIconPanel(JPanel iconPanel) {
this.iconPanel = iconPanel;
public void setTypeIconPanel(JPanel typeIconPanel) {
this.typeIconPanel = typeIconPanel;
}
public JPanel getCounterPanel() {
@ -113,6 +107,7 @@ public class CardPanelComponentImpl extends CardPanel {
static class Key {
final Insets border;
final int width;
final int height;
final int cardWidth;
@ -126,7 +121,8 @@ public class CardPanelComponentImpl extends CardPanel {
final boolean canAttack;
final boolean canBlock;
public Key(int width, int height, int cardWidth, int cardHeight, int cardXOffset, int cardYOffset, boolean hasImage, boolean isSelected, boolean isChoosable, boolean isPlayable, boolean canAttack, boolean canBlock) {
public Key(Insets border, int width, int height, int cardWidth, int cardHeight, int cardXOffset, int cardYOffset, boolean hasImage, boolean isSelected, boolean isChoosable, boolean isPlayable, boolean canAttack, boolean canBlock) {
this.border = border;
this.width = width;
this.height = height;
this.cardWidth = cardWidth;
@ -144,6 +140,10 @@ public class CardPanelComponentImpl extends CardPanel {
@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;
@ -171,6 +171,18 @@ public class CardPanelComponentImpl extends CardPanel {
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;
}
@ -209,7 +221,7 @@ public class CardPanelComponentImpl extends CardPanel {
}
static {
IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(CardPanelComponentImpl::createImage));
IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(CardPanelRenderModeImage::createImage));
}
static private boolean canShowCardIcons(int cardFullWidth, boolean cardHasImage) {
@ -220,27 +232,29 @@ public class CardPanelComponentImpl extends CardPanel {
}
private static class CardSizes {
// from bigger to smaller
Rectangle rectFull;
Rectangle rectSelection;
Rectangle rectBorder;
Rectangle rectCard;
CardSizes(int offsetX, int offsetY, int fullWidth, int fullHeight) {
CardSizes(Insets border, int offsetX, int offsetY, int fullWidth, int fullHeight) {
int realBorderSizeX = Math.round(fullWidth * BLACK_BORDER_SIZE);
int realBorderSizeY = Math.round(fullWidth * BLACK_BORDER_SIZE);
int realSelectionSizeX = Math.round(fullWidth * SELECTION_BORDER_SIZE);
int realSelectionSizeY = Math.round(fullWidth * SELECTION_BORDER_SIZE);
int realBorderSizeX = Math.max(1, Math.round(fullWidth * BLACK_BORDER_SIZE));
int realBorderSizeY = Math.max(1, Math.round(fullHeight * BLACK_BORDER_SIZE));
int realSelectionSizeX = Math.max(1, Math.round(fullWidth * SELECTION_BORDER_SIZE));
int realSelectionSizeY = Math.max(1, Math.round(fullHeight * SELECTION_BORDER_SIZE));
// card full size = select border + black border + real card
rectFull = new Rectangle(offsetX, offsetY, fullWidth, fullHeight);
rectFull = new Rectangle(offsetX + border.left, offsetY + border.top, fullWidth - border.left - border.right, fullHeight - border.top - border.bottom);
rectSelection = new Rectangle(rectFull.x, rectFull.y, rectFull.width, rectFull.height);
rectBorder = new Rectangle(rectSelection.x + realSelectionSizeX, rectSelection.y + realSelectionSizeY, rectSelection.width - 2 * realSelectionSizeX, rectSelection.height - 2 * realSelectionSizeY);
rectCard = new Rectangle(rectBorder.x + realBorderSizeX, rectBorder.y + realBorderSizeY, rectBorder.width - 2 * realBorderSizeX, rectBorder.height - 2 * realBorderSizeY);
}
}
public CardPanelComponentImpl(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, final boolean foil, Dimension dimension, boolean needFullPermanentRender) {
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);
@ -290,8 +304,6 @@ public class CardPanelComponentImpl extends CardPanel {
// Title Text
titleText = new GlowText();
setTitle(getGameCard());
// int fontSize = (int) cardHeight / 11;
// titleText.setFont(getFont().deriveFont(Font.BOLD, fontSize));
titleText.setForeground(Color.white);
titleText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY);
titleText.setWrap(true);
@ -326,7 +338,9 @@ public class CardPanelComponentImpl extends CardPanel {
// Imagel panel
imagePanel = new ScaledImagePanel();
imagePanel.setBorder(BorderFactory.createLineBorder(Color.white));
if (DebugUtil.GUI_RENDER_IMAGE_DRAW_IMAGE_BORDER) {
imagePanel.setBorder(BorderFactory.createLineBorder(Color.white));
}
add(imagePanel);
// Do we need to load?
@ -338,21 +352,21 @@ public class CardPanelComponentImpl extends CardPanel {
}
private void setTypeIcon(BufferedImage bufferedImage, String toolTipText) {
setIconPanel(new JPanel());
getIconPanel().setLayout(null);
getIconPanel().setOpaque(false);
add(getIconPanel());
setTypeIconPanel(new JPanel());
getTypeIconPanel().setLayout(null);
getTypeIconPanel().setOpaque(false);
add(getTypeIconPanel());
typeButton = new JButton("");
typeButton.setLocation(2, 2);
typeButton.setSize(25, 25);
typeIconButton = new JButton("");
typeIconButton.setLocation(2, 2);
typeIconButton.setSize(25, 25);
getIconPanel().setVisible(true);
typeButton.setIcon(new ImageIcon(bufferedImage));
getTypeIconPanel().setVisible(true);
typeIconButton.setIcon(new ImageIcon(bufferedImage));
if (toolTipText != null) {
typeButton.setToolTipText(toolTipText);
typeIconButton.setToolTipText(toolTipText);
}
getIconPanel().add(typeButton);
getTypeIconPanel().add(typeIconButton);
}
@Override
@ -388,8 +402,8 @@ public class CardPanelComponentImpl extends CardPanel {
@Override
public void transferResources(final CardPanel panelAbstract) {
if (panelAbstract instanceof CardPanelComponentImpl) {
CardPanelComponentImpl panel = (CardPanelComponentImpl) panelAbstract;
if (panelAbstract instanceof CardPanelRenderModeImage) {
CardPanelRenderModeImage panel = (CardPanelRenderModeImage) panelAbstract;
synchronized (panel.imagePanel) {
if (panel.imagePanel.hasImage()) {
setImage(panel.imagePanel.getSrcImage());
@ -416,27 +430,35 @@ public class CardPanelComponentImpl extends CardPanel {
g2d.setComposite(composite);
}
// draw background (selected/chooseable/playable)
MageCardLocation cardLocation = getCardLocation();
g2d.drawImage(
IMAGE_CACHE.getOrThrow(
new Key(getWidth(), getHeight(), getCardWidth(), getCardHeight(), getCardXOffset(), getCardYOffset(),
new Key(getInsets(),
cardLocation.getCardWidth(), cardLocation.getCardHeight(),
cardLocation.getCardWidth(), cardLocation.getCardHeight(),
0,
0,
hasImage, isSelected(), isChoosable(), getGameCard().isPlayable(), getGameCard().isCanAttack(),
getGameCard().isCanBlock())),
0, 0, null);
0, 0, cardLocation.getCardWidth(), cardLocation.getCardHeight(), null);
g2d.dispose();
}
private static BufferedImage createImage(Key key) {
int cardWidth = key.cardWidth;
int cardHeight = key.cardHeight;
// draw background image with selection
Insets componentBorder = key.border;
int renderWidth = key.cardWidth;
int renderHeight = key.cardHeight;
int cardXOffset = key.cardXOffset;
int cardYOffset = key.cardYOffset;
BufferedImage image = GraphicsUtilities.createCompatibleTranslucentImage(key.width, key.height);
BufferedImage image = GraphicsUtilities.createCompatibleTranslucentImage(renderWidth, renderHeight);
Graphics2D g2d = image.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// card full size = select border + black border + real card
CardSizes sizes = new CardSizes(cardXOffset, cardYOffset, cardWidth, cardHeight);
CardSizes sizes = new CardSizes(componentBorder, cardXOffset, cardYOffset, renderWidth, renderHeight);
// corners for selection and for border
int cornerSizeSelection = Math.max(4, Math.round(sizes.rectSelection.width * ROUNDED_CORNER_SIZE));
@ -446,6 +468,7 @@ public class CardPanelComponentImpl extends CardPanel {
// draw selection
if (key.isSelected) {
// TODO: add themes color support
g2d.setColor(Color.green);
g2d.fillRoundRect(sizes.rectSelection.x + 1, sizes.rectSelection.y + 1, sizes.rectSelection.width - 2, sizes.rectSelection.height - 2, cornerSizeSelection, cornerSizeSelection);
} else if (key.isChoosable) {
@ -456,7 +479,7 @@ public class CardPanelComponentImpl extends CardPanel {
g2d.fillRoundRect(sizes.rectSelection.x, sizes.rectSelection.y, sizes.rectSelection.width, sizes.rectSelection.height, cornerSizeSelection, cornerSizeSelection);
}
// draw attack or block border (?inner part of selection?)
// draw attack or block border (?inner part of a selection?)
if (key.canAttack || key.canBlock) {
g2d.setColor(new Color(255, 50, 50, 230));
g2d.fillRoundRect(sizes.rectSelection.x + 1, sizes.rectSelection.y + 1, sizes.rectSelection.width - 2, sizes.rectSelection.height - 2, cornerSizeSelection, cornerSizeSelection);
@ -473,13 +496,6 @@ public class CardPanelComponentImpl extends CardPanel {
}
// draw real card by component (see imagePanel and other layout's items)
//TODO:uncomment
/*
if (gameCard.isAttacking()) {
g2d.setColor(new Color(200,10,10,200));
g2d.fillRoundRect(cardXOffset+1, cardYOffset+1, cardWidth-2, cardHeight-2, cornerSize, cornerSize);
}*/
g2d.dispose();
return image;
@ -489,10 +505,8 @@ public class CardPanelComponentImpl extends CardPanel {
protected void paintChildren(Graphics g) {
super.paintChildren(g);
CardSizes realCard = new CardSizes(getCardXOffset(), getCardYOffset(), getCardWidth(), getCardHeight());
/*
// draw recs for debug
// debug draw recs
// full card
g.setColor(new Color(255, 0, 0));
@ -522,8 +536,10 @@ public class CardPanelComponentImpl extends CardPanel {
int manaMarginRight = Math.round(22f / 672f * getCardWidth());
int manaMarginTop = Math.round(24f / 936f * getCardHeight());
int manaX = getCardXOffset() + getCardWidth() - manaMarginRight - manaWidth;
int manaY = getCardYOffset() + manaMarginTop;
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);
}
@ -547,12 +563,12 @@ public class CardPanelComponentImpl extends CardPanel {
public void doLayout() {
super.doLayout();
int cardWidth = getCardWidth();
int cardHeight = getCardHeight();
int cardXOffset = getCardXOffset();
int cardYOffset = getCardYOffset();
int cardWidth = getCardLocation().getCardWidth(); // must use current panel sizes to scale real image
int cardHeight = getCardLocation().getCardHeight();
int cardXOffset = 0;
int cardYOffset = 0;
CardSizes sizes = new CardSizes(cardXOffset, cardYOffset, cardWidth, cardHeight);
CardSizes sizes = new CardSizes(getInsets(), cardXOffset, cardYOffset, cardWidth, cardHeight);
// origin card without selection
Rectangle realCardSize = sizes.rectBorder;
@ -566,10 +582,11 @@ public class CardPanelComponentImpl extends CardPanel {
getOverlayPanel().setVisible(false);
}
if (getIconPanel() != null) {
getIconPanel().setLocation(realCardSize.x, realCardSize.y);
getIconPanel().setSize(realCardSize.width, realCardSize.height);
if (getTypeIconPanel() != null) {
getTypeIconPanel().setLocation(realCardSize.x, realCardSize.y);
getTypeIconPanel().setSize(realCardSize.width, realCardSize.height);
}
if (getCounterPanel() != null) {
getCounterPanel().setLocation(realCardSize.x, realCardSize.y);
getCounterPanel().setSize(realCardSize.width, realCardSize.height);
@ -591,10 +608,6 @@ public class CardPanelComponentImpl extends CardPanel {
// TITLE
//old version - text hide on small fonts, why?
//int fontHeight = Math.round(cardHeight * (26f / 672));
//boolean showText = (!isAnimationPanel() && fontHeight < 12);
boolean showText = !isAnimationPanel() && canShowCardIcons(cardWidth, hasImage);
titleText.setVisible(showText);
ptText1.setVisible(showText && !ptText1.getText().isEmpty());
@ -609,7 +622,7 @@ public class CardPanelComponentImpl extends CardPanel {
// margins from card black border to text, not need? text show up good without margins
int titleMarginLeft = 0; //Math.round(28f / 672f * cardWidth);
int titleMarginRight = 0;
int titleMarginTop = 0 + Math.round(getCardCaptionTopOffset() / 100f * cardHeight);//Math.round(28f / 936f * cardHeight);
int titleMarginTop = Math.round(getCardCaptionTopOffset() / 100f * cardHeight);//Math.round(28f / 936f * cardHeight);
int titleMarginBottom = 0;
titleText.setBounds(
imagePanel.getX() + titleMarginLeft,
@ -778,11 +791,7 @@ public class CardPanelComponentImpl extends CardPanel {
setTitle(getGameCard());
// Summoning Sickness overlay
if (hasSickness() && getGameCard().isCreature() && isPermanent()) {
getOverlayPanel().setVisible(true);
} else {
getOverlayPanel().setVisible(false);
}
getOverlayPanel().setVisible(hasSickness() && getGameCard().isCreature() && isPermanent());
// Update counters panel
if (getCounterPanel() != null) {
@ -852,7 +861,6 @@ public class CardPanelComponentImpl extends CardPanel {
otherCounterLabel.setVisible(false);
getCounterPanel().setVisible(false);
}
}
private static ImageIcon getCounterImageWithAmount(int amount, BufferedImage image, int cardWidth) {

View file

@ -11,7 +11,6 @@ import mage.view.CardView;
import mage.view.CounterView;
import mage.view.PermanentView;
import mage.view.StackAbilityView;
import org.apache.log4j.Logger;
import org.jdesktop.swingx.graphics.GraphicsUtilities;
import org.mage.plugins.card.images.ImageCache;
@ -21,9 +20,10 @@ import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class CardPanelRenderImpl extends CardPanel {
private static final Logger LOGGER = Logger.getLogger(CardPanelRenderImpl.class);
/**
* Render mode: MTGO
*/
public class CardPanelRenderModeMTGO extends CardPanel {
private static boolean cardViewEquals(CardView a, CardView b) {
if (a == b) {
@ -200,7 +200,7 @@ public class CardPanelRenderImpl extends CardPanel {
final ImageKey other = (ImageKey) object;
// Compare
if ((artImage != null) != (other.artImage != null)) {
if ((artImage == null) == (other.artImage != null)) {
return false;
}
if (width != other.width) {
@ -241,8 +241,8 @@ public class CardPanelRenderImpl extends CardPanel {
private BufferedImage cardImage;
private CardRenderer cardRenderer;
public CardPanelRenderImpl(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback,
final boolean foil, Dimension dimension, boolean needFullPermanentRender) {
public CardPanelRenderModeMTGO(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);
@ -255,8 +255,8 @@ public class CardPanelRenderImpl extends CardPanel {
@Override
public void transferResources(CardPanel panel) {
if (panel instanceof CardPanelRenderImpl) {
CardPanelRenderImpl impl = (CardPanelRenderImpl) panel;
if (panel instanceof CardPanelRenderModeMTGO) {
CardPanelRenderModeMTGO impl = (CardPanelRenderModeMTGO) panel;
// Use the art image and current rendered image from the card
artImage = impl.artImage;
@ -285,16 +285,15 @@ public class CardPanelRenderImpl extends CardPanel {
}
// And draw the image we now have
g.drawImage(cardImage, getCardXOffset(), getCardYOffset(), null);
int cardOffsetX = 0;
int cardOffsetY = 0;
g.drawImage(cardImage, cardOffsetX, cardOffsetY, null);
}
/**
* Create an appropriate card renderer for the
*/
/**
* Render the card to a new BufferedImage at it's current dimensions
*
* @return
* @return image
*/
private BufferedImage renderCard() {
int cardWidth = getCardWidth();
@ -411,6 +410,7 @@ public class CardPanelRenderImpl extends CardPanel {
}
private BufferedImage getFaceDownImage() {
// TODO: add download default images
if (isPermanent()) {
if (((PermanentView) getGameCard()).isMorphed()) {
return ImageCache.getMorphImage();

View file

@ -4,7 +4,7 @@ import mage.cards.ArtRect;
import mage.view.CardView;
/**
* Created by StravantUser on 2017-03-30.
* @author StravantUser
*/
public class CardRendererFactory {

View file

@ -1,10 +1,16 @@
package org.mage.card.arcane;
import mage.MageInt;
import mage.util.DebugUtil;
import mage.view.CardView;
import mage.view.PermanentView;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.LineMetrics;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Iterator;
@ -253,4 +259,90 @@ public final class CardRendererUtils {
return defaultColor;
}
/**
* Reduce rect by percent (add empty space from all sides and keep rect position)
* Example usage: reduce rect to fit auto-size text
*
* @param rect
* @param reduceFactor
* @return
*/
public static Rectangle reduceRect(Rectangle rect, float reduceFactor) {
float newWidth = rect.width * reduceFactor;
float newHeight = rect.height * reduceFactor;
int offsetX = Math.round((rect.width - newWidth) / 2f);
int offsetY = Math.round((rect.height - newHeight) / 2f);
return new Rectangle(rect.x + offsetX, rect.y + offsetY, Math.round(newWidth), Math.round(newHeight));
}
/**
* Draw a String centered in the middle of a rectangle.
*
* @param g2d The graphics instance
* @param text The string to draw
* @param rect The rectangle to center the text in
* @param font
* @param isAutoScaleFont if the text is too big then it will scale a font to fit it in the rect
*/
public static void drawCenteredText(Graphics2D g2d, String text, Rectangle rect, Font font, boolean isAutoScaleFont) {
if (DebugUtil.GUI_RENDER_CENTERED_TEXT_DRAW_DEBUG_LINES) {
g2d.drawLine(rect.x, rect.y + rect.height / 2, rect.x + rect.width, rect.y + rect.height / 2);
g2d.drawLine(rect.x + rect.width / 2, rect.y, rect.x + rect.width / 2, rect.y + rect.height);
}
// https://stackoverflow.com/a/23730104/1276632
Font affectedFont = font;
if (isAutoScaleFont) {
affectedFont = scaleFont(g2d, text, rect, font);
}
g2d.setFont(affectedFont);
FontRenderContext frc = g2d.getFontRenderContext();
GlyphVector gv = affectedFont.createGlyphVector(frc, text);
Rectangle2D box = gv.getVisualBounds();
float offsetX = (float) (((rect.getWidth() - box.getWidth()) / 2d) + (-box.getX()));
float offsetY = (float) (((rect.getHeight() - box.getHeight()) / 2d) + (-box.getY()));
g2d.drawString(text, rect.x + offsetX, rect.y + offsetY);
}
/**
* Auto scale font to fit current text inside the rect (e.g. decrease font size for too big text)
*
* @param g2d graphics context
* @param text text to draw
* @param font base font
* @param rect the bounds for fitting the string
* @return a scaled font
*/
private static Font scaleFont(Graphics2D g2d, String text, Rectangle rect, Font font) {
// https://stackoverflow.com/a/876266/1276632
FontRenderContext frc = g2d.getFontRenderContext();
double needWidth = rect.getWidth();
double needHeight = rect.getHeight();
float fontMinSize = 1f;
float fontMaxSize = 1000f;
Font scaledFont = font;
float scaledFontSize = scaledFont.getSize();
while (fontMaxSize - fontMinSize > 1f) {
scaledFont = scaledFont.deriveFont(scaledFontSize);
TextLayout layout = new TextLayout(text, scaledFont, frc);
float currentWidth = layout.getVisibleAdvance();
LineMetrics metrics = scaledFont.getLineMetrics(text, frc);
float currentHeight = metrics.getHeight();
if ((currentWidth > needWidth) || (currentHeight > needHeight)) {
fontMaxSize = scaledFontSize;
} else {
fontMinSize = scaledFontSize;
}
scaledFontSize = (fontMinSize + fontMaxSize) / 2f;
}
return scaledFont.deriveFont((float) Math.floor(scaledFontSize));
}
}

View file

@ -0,0 +1,624 @@
package org.mage.card.arcane;
import mage.abilities.icon.CardIcon;
import mage.abilities.icon.CardIconCategory;
import mage.abilities.icon.CardIconRenderSettings;
import mage.abilities.icon.system.PlayableCountIcon;
import mage.cards.MageCard;
import mage.cards.MageCardAnimationSettings;
import mage.cards.MageCardLocation;
import mage.cards.MageCardSpace;
import mage.client.cards.CardIconsPanel;
import mage.client.cards.CardIconsPanelFactory;
import mage.constants.Zone;
import mage.util.DebugUtil;
import mage.view.CardView;
import org.apache.log4j.Logger;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Additional layer for mage cards (example: layer with card icons).
* One MageCard can have multiple layers in the future.
* <p>
* WARNING, all added listeners goes to card's main panel (mouse, events, etc)
*
* @author JayDi85
*/
public class MageLayer extends MageCard {
private static final Logger logger = Logger.getLogger(MageLayer.class);
JLayeredPane mainContainer;
JPanel mainLayerCard;
JPanel mainLayerIcons;
JPanel mainLayerDebug = null;
MageCard mainPanel;
// empty spaces to control real card size in the center
JPanel spaceLeft;
JPanel spaceRight;
JPanel spaceTop;
JPanel spaceBottom;
// drawing spaces, you must ignore it in animations and other calcs
MageCardSpace lastOuterSpace = MageCardSpace.empty;
// card icons
CardIconRenderSettings iconsRender;
List<CardIconsPanel> iconsPanels = new ArrayList<>(); // for calcs only
CardIconsPanel iconsDebugPanel;
CardIconsPanel iconsAbilitiesPanel;
CardIconsPanel iconsPlayablePanel;
public MageLayer(MageCard mainPanel, CardIconRenderSettings iconsRender) {
this.mainPanel = mainPanel;
this.mainPanel.setTopPanelRef(this);
// component structure (border layout):
// - main container: JLayeredPane (center)
// * layer with card: custom size, border layout
// - main panel + spaces
// * layer with icons: custom size
// * layer with debug drawing
// component
this.setLayout(new BorderLayout());
this.setOpaque(false);
// main container
this.mainContainer = new JLayeredPane();
this.mainContainer.setOpaque(false);
this.add(this.mainContainer, BorderLayout.CENTER);
// card layer
this.mainLayerCard = new JPanel(new BorderLayout());
this.mainLayerCard.setOpaque(false);
this.mainContainer.add(this.mainLayerCard, (Integer) 0);
// main panel + spaces
this.mainLayerCard.add(this.mainPanel, BorderLayout.CENTER);
this.initEmptySpaces();
// icons layer
this.mainLayerIcons = new JPanel(null);
this.mainLayerIcons.setOpaque(false);
this.mainContainer.add(this.mainLayerIcons, (Integer) 10);
// debug layer
if (DebugUtil.GUI_CARD_DRAW_MOUSE_CONTAINS_BOUNDS) {
this.mainLayerDebug = new JPanel(null);
this.mainLayerDebug.setOpaque(false);
this.mainLayerDebug.setBorder(BorderFactory.createLineBorder(Color.MAGENTA));
this.mainContainer.add(this.mainLayerDebug, (Integer) 20);
}
// init icons panels, real icons and sizes will added by setCardBounds and setSizes
this.iconsRender = iconsRender;
this.initCardIconsPanels();
// Warning, you must ignore outer/draw spaces, use getCardLocation to find a real component and card size/position
// If you inherits a MageLayer then you must implements contains(int x, int y) for correct mouse events
if (DebugUtil.GUI_CARD_DRAW_OUTER_BORDER) {
this.setBorder(BorderFactory.createLineBorder(Color.red));
}
if (DebugUtil.GUI_CARD_DRAW_INNER_BORDER) {
this.mainPanel.setBorder(BorderFactory.createLineBorder(Color.green));
}
}
private void initEmptySpaces() {
this.spaceLeft = new JPanel(null);
this.spaceLeft.setOpaque(false);
this.mainLayerCard.add(this.spaceLeft, BorderLayout.WEST);
//
this.spaceRight = new JPanel(null);
this.spaceRight.setOpaque(false);
this.mainLayerCard.add(this.spaceRight, BorderLayout.EAST);
//
this.spaceTop = new JPanel(null);
this.spaceTop.setOpaque(false);
this.mainLayerCard.add(this.spaceTop, BorderLayout.NORTH);
//
this.spaceBottom = new JPanel(null);
this.spaceBottom.setOpaque(false);
this.mainLayerCard.add(this.spaceBottom, BorderLayout.SOUTH);
}
private void initCardIconsPanels() {
this.iconsDebugPanel = null;
this.iconsAbilitiesPanel = null;
this.iconsPlayablePanel = null;
if (this.iconsRender.isDebugMode()) {
// DEBUG MODE -- only one debug panel
this.iconsDebugPanel = CardIconsPanelFactory.createDebugPanel(this.iconsRender);
this.iconsPanels.add(this.iconsDebugPanel);
} else {
// NORMAL mode -- multiple panels
this.iconsAbilitiesPanel = CardIconsPanelFactory.createAbilitiesPanel();
this.iconsPanels.add(this.iconsAbilitiesPanel);
this.iconsPlayablePanel = CardIconsPanelFactory.createPlayablePanel();
this.iconsPanels.add(this.iconsPlayablePanel);
}
this.iconsPanels.forEach(panel -> this.mainLayerIcons.add(panel));
}
private void setEmptySpaces(int left, int right, int top, int bottom) {
this.setEmptySpaces(new MageCardSpace(left, right, top, bottom));
}
private void setEmptySpaces(MageCardSpace space) {
Border border = space.getDebugColor() == null ? null : BorderFactory.createLineBorder(space.getDebugColor());
this.spaceLeft.setPreferredSize(new Dimension(space.getLeft(), 0));
this.spaceLeft.setBorder(border);
//
this.spaceRight.setPreferredSize(new Dimension(space.getRight(), 0));
this.spaceRight.setBorder(border);
//
this.spaceTop.setPreferredSize(new Dimension(0, space.getTop()));
this.spaceTop.setBorder(border);
//
this.spaceBottom.setPreferredSize(new Dimension(0, space.getBottom()));
this.spaceBottom.setBorder(border);
}
@Override
public MageCard getMainPanel() {
return mainPanel.getMainPanel();
}
@Override
public void onBeginAnimation() {
mainPanel.onBeginAnimation();
}
@Override
public void onEndAnimation() {
mainPanel.onEndAnimation();
}
@Override
public boolean isTapped() {
return mainPanel.isTapped();
}
@Override
public boolean isFlipped() {
return mainPanel.isFlipped();
}
@Override
public void setAlpha(float transparency) {
mainPanel.setAlpha(transparency);
}
@Override
public float getAlpha() {
return mainPanel.getAlpha();
}
@Override
public CardView getOriginal() {
return mainPanel.getOriginal();
}
@Override
public void setCardCaptionTopOffset(int yOffsetPercent) {
mainPanel.setCardCaptionTopOffset(yOffsetPercent);
}
/**
* Scale inner card to draw additional icons or something (example: card icons in outer space)
*
* @param renderWidth
* @param renderHeight
* @return
*/
private MageCardSpace getAdditionalSpaces(int renderWidth, int renderHeight) {
return new MageCardSpace(0, 0, Math.round(renderHeight * 0f), 0);
}
@Override
public void setCardBounds(int x, int y, int width, int height) {
// base idea: child layers should not know about parent layer
//
// render logic:
// * scale the current layer to fit additional elemenst like icons
// * draw child layer with new sizes
//
// animation logic (maybe it can be change in the future):
// * animation implemented as g2d graphic context scale in Paint() method
// * all layers and elements must be moved as one object
// * only the main panel (child) can do a calcs for the animation (so send parent sizes to recalc it)
// if (this.getTopPanelRef() == this && this.getOriginal().getName().equals("Kathari Remnant")) { // for debug only
if (this.getTopPanelRef() == this) { // TODO: is it support multi layer drawing?
// scale inner card and create space for additional drawing like icons
MageCardSpace innerSpace = getAdditionalSpaces(width, height);
// extra space for animation and other drawing
// WTF, I'm tired with render calcs, so make BIG draw spaces for any needs
MageCardSpace outerSpace = new MageCardSpace(width * 2, width * 2, height * 2, height * 2);
//MageCardSpace outerSpace = new MageCardSpace(50, 30, 150, 20);
this.lastOuterSpace = outerSpace;
// construct new spaces (outer + inner)
MageCardSpace fullSpace = MageCardSpace.combine(innerSpace, outerSpace).withDebugColor(innerSpace.getDebugColor());
this.setEmptySpaces(fullSpace);
//noinspection deprecation - it's ok to use inner setBounds here
this.setBounds(x - outerSpace.getLeft(), y - outerSpace.getTop(), width + outerSpace.getWidth(), height + outerSpace.getHeight());
mainPanel.setCardBounds(x + innerSpace.getLeft(), y + innerSpace.getTop(), width - innerSpace.getWidth(), height - innerSpace.getHeight());
} else {
this.setEmptySpaces(0, 0, 0, 0);
//noinspection deprecation - it's ok to use inner setBounds here
this.setBounds(x, y, width, height);
mainPanel.setCardBounds(x, y, width, height);
}
MageCardLocation location = this.getCardLocation();
// panel sizes
this.mainLayerCard.setBounds(0, 0, location.getComponentWidth(), location.getComponentHeight());
this.mainLayerIcons.setBounds(0, 0, location.getComponentWidth(), location.getComponentHeight());
// icons sizes
Rectangle cardSize = new Rectangle(location.getCardRelativeX(), location.getCardRelativeY(), location.getCardWidth(), location.getCardHeight());
iconsPanels.forEach(panel -> {
panel.updateSizes(cardSize);
});
}
@Override
public void update(CardView card) {
// icons update
updateCardIcons(card);
// card update
mainPanel.update(card);
}
private void updateCardIcons(CardView card) {
Map<CardIconsPanel, List<CardIcon>> newIcons = new HashMap<>();
this.iconsPanels.forEach(panel -> newIcons.put(panel, new ArrayList<>()));
List<CardIcon> allIcons = new ArrayList<>();
// main icons
allIcons.addAll(card.getCardIcons());
// playable icons
if (card.getPlayableStats().getPlayableImportantAmount() > 0) {
allIcons.add(new PlayableCountIcon(card.getPlayableStats()));
}
// create panels
allIcons.forEach(cardIcon -> {
CardIconCategory category = cardIcon.getIconType().getCategory();
// debug must take all icons
if (iconsDebugPanel != null) {
newIcons.get(iconsDebugPanel).add(cardIcon);
}
if (iconsPlayablePanel != null && category == CardIconCategory.PLAYABLE_COUNT) {
newIcons.get(iconsPlayablePanel).add(cardIcon);
}
if (iconsAbilitiesPanel != null && category == CardIconCategory.ABILITY) {
newIcons.get(iconsAbilitiesPanel).add(cardIcon);
}
});
this.iconsPanels.forEach(panel -> panel.updateIcons(newIcons.get(panel)));
}
@Override
public void updateArtImage() {
mainPanel.updateArtImage();
}
@Override
public Image getImage() {
return mainPanel.getImage();
}
@Override
public void setZone(Zone zone) {
mainPanel.setZone(zone);
}
@Override
public Zone getZone() {
return mainPanel.getZone();
}
@Override
public void toggleTransformed() {
mainPanel.toggleTransformed();
}
@Override
public boolean isTransformed() {
return mainPanel.isTransformed();
}
@Override
public void showCardTitle() {
mainPanel.showCardTitle();
}
@Override
public void setSelected(boolean selected) {
mainPanel.setSelected(selected);
}
@Override
public void setCardContainerRef(Container cardContainer) {
mainPanel.setCardContainerRef(cardContainer);
}
@Override
public void setTopPanelRef(MageCard mageCard) {
mainPanel.setTopPanelRef(mageCard);
}
@Override
public MageCard getTopPanelRef() {
return mainPanel.getTopPanelRef();
}
@Override
public Container getCardContainer() {
return mainPanel.getCardContainer();
}
@Override
public void setChoosable(boolean isChoosable) {
mainPanel.setChoosable(isChoosable);
}
@Override
public boolean isChoosable() {
return mainPanel.isChoosable();
}
@Override
public void setPopupMenu(JPopupMenu popupMenu) {
mainPanel.setPopupMenu(popupMenu);
}
@Override
public JPopupMenu getPopupMenu() {
return mainPanel.getPopupMenu();
}
@Override
public void cleanUp() {
mainPanel.cleanUp();
}
@Override
public int getCardWidth() {
return mainPanel.getCardWidth();
}
@Override
public int getCardHeight() {
return mainPanel.getCardHeight();
}
@Override
public MageCardAnimationSettings getAnimationSettings(int offsetX, int offsetY, float cardBoundWidth, float cardBoundHeight) {
return mainPanel.getAnimationSettings(offsetX, offsetY, cardBoundWidth, cardBoundHeight);
}
@Override
public List<MageCard> getLinks() {
return mainPanel.getLinks();
}
@Override
public MageCardSpace getOuterSpace() {
return this.lastOuterSpace;
}
@Override
public MageCardLocation getCardLocation() {
// TODO: is it support multi layers?
if (this.getTopPanelRef() == this) {
//noinspection deprecation (it's ok to call native getLocation here)
return new MageCardLocation(this.getLocation(), this.getOuterSpace(), this.getBounds());
} else {
return super.getCardLocation();
}
}
@Override
public void setCardLocation(int x, int y) {
// TODO: is it support multi layers?
if (this.getTopPanelRef() == this) {
// see setCardBounds for more coords cals
//noinspection deprecation - it's ok to use inner setLocation here
this.setLocation(x - lastOuterSpace.getLeft(), y - lastOuterSpace.getTop());
} else {
this.getTopPanelRef().setCardLocation(x, y);
}
}
@Override
public MageCardLocation getCardLocationOnScreen() {
// TODO: is it support multi layers?
if (this.getTopPanelRef() == this) {
//noinspection deprecation - it's ok to use inner getLocation here
return new MageCardLocation(this.getLocationOnScreen(), this.getOuterSpace(), this.getBounds());
} else {
return super.getCardLocationOnScreen();
}
}
// ADDITIONAL METHODS FROM real components (e.g. set bounds or other things)
// TODO: move it to interface for require?
@Override
public int hashCode() {
return mainPanel.hashCode();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public synchronized void addMouseListener(MouseListener l) {
//super.addMouseListener(l);
mainPanel.addMouseListener(l);
}
@Override
public synchronized void removeMouseListener(MouseListener l) {
//super.removeMouseListener(l);
mainPanel.removeMouseListener(l);
}
@Override
public synchronized void addMouseMotionListener(MouseMotionListener l) {
//super.addMouseMotionListener(l);
mainPanel.addMouseMotionListener(l);
}
@Override
public synchronized void removeMouseMotionListener(MouseMotionListener l) {
//super.removeMouseMotionListener(l);
mainPanel.removeMouseMotionListener(l);
}
@Override
public synchronized void addMouseWheelListener(MouseWheelListener l) {
//super.addMouseWheelListener(l);
mainPanel.addMouseWheelListener(l);
}
@Override
public synchronized void removeMouseWheelListener(MouseWheelListener l) {
//super.removeMouseWheelListener(l);
mainPanel.removeMouseWheelListener(l);
}
@Override
public void paint(Graphics g) {
// inner card panel can decide about transform/scale settings (example: tapped),
// so the top parent layer must be scaled too
// real card can be put on any parent layer's position (see outer spaces), so the render logic here:
// * find real card sizes (without outer spaces)
// * calc new sizes for the animation/rotation
// * apply outer spaces to calculated sizes
// TODO: is it support multi layers?
int offsetX = this.getOuterSpace().getLeft();
int offsetY = this.getOuterSpace().getTop();
int extraWidth = this.getOuterSpace().getWidth();
int extraHeight = this.getOuterSpace().getHeight();
Rectangle componentRect = this.getCardLocation().getComponentBounds();
MageCardAnimationSettings settings = getAnimationSettings(
offsetX,
offsetY,
componentRect.width - extraWidth,
componentRect.height - extraHeight
);
if (!settings.isVisible()) {
return;
}
if (!isValid() || !mainPanel.isValid()) {
mainPanel.validate();
super.validate();
}
Graphics2D g2d = (Graphics2D) g;
settings.doTransforms(g2d);
super.paint(g);
}
@Override
public boolean contains(int x, int y) {
// TODO: is it work with multi layer?
// Mouse coords checking to find a child component under the mouse (example: show card hint on mouse over or button click)
// Swing uses relative coords here (0,0 is component's top left corner)
// WARNING, when you fail a parent coord check then all childs goes to ignore (example: top layer miss check,
// then no card panel get it and no card hints on mouse over)
MageCardLocation needLocation = this.getCardLocation();
// TODO: added contains support for icons hint
// implement idea: use custom "contains" methods for all components structure: from top layer to icon label
// another implement idea: save last AffineTransforms from paint method, translate it to current component and check coords (most accurate method)
// extra size for icons (workaround to fix tooltips over icons)
Rectangle iconsOffset = new Rectangle(0, 0);
if (this.iconsPanels.stream().anyMatch(Component::isVisible)) {
CardIconsPanel samplePanel = this.iconsPanels.stream().findFirst().get();
iconsOffset.x = -samplePanel.getHalfSize();
iconsOffset.y = -samplePanel.getHalfSize();
iconsOffset.height = samplePanel.getHalfSize();
iconsOffset.width = samplePanel.getHalfSize();
}
Rectangle normalRect = new Rectangle(
needLocation.getCardRelativeX() + iconsOffset.x,
needLocation.getCardRelativeY() + iconsOffset.y,
needLocation.getCardWidth() + iconsOffset.width,
needLocation.getCardHeight() + iconsOffset.height
);
Rectangle animatedRect = animateCoords(this, normalRect);
// debug draw just for color info, real draw will be transformed/animated with card, so you can look at draw rect
if (DebugUtil.GUI_CARD_DRAW_MOUSE_CONTAINS_BOUNDS) {
this.mainLayerDebug.setBounds(animatedRect.x, animatedRect.y, animatedRect.width, animatedRect.height);
if (animatedRect.contains(x, y)) {
this.mainLayerDebug.setBorder(BorderFactory.createLineBorder(Color.green));
} else {
this.mainLayerDebug.setBorder(BorderFactory.createLineBorder(Color.MAGENTA));
}
}
return animatedRect.contains(x, y);
}
public static Rectangle animateCoords(MageCard card, Rectangle normalRect) {
int needX = normalRect.x;
int needY = normalRect.y;
int needW = normalRect.width;
int needH = normalRect.height;
int cx = needX;
int cy = needY;
int cw = needW;
int ch = needH;
if (card.isTapped()) {
// TODO: add rotate support for non 90 angles in the future
// rotate by 90 only
// example before:
// * coord: 50, 150
// * size: 126 x 176
// example after:
// * coord: 50, 150 + 176 - 126
// * size: 176 x 126
cx = needX;
cy = needY + needH - needW;
cw = needH;
ch = needW;
}
return new Rectangle(cx, cy, cw, ch);
}
}

View file

@ -1,23 +1,5 @@
package org.mage.card.arcane;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageProducer;
import java.awt.image.RGBImageFilter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import javax.imageio.ImageIO;
import javax.swing.*;
import mage.abilities.hint.HintUtils;
import mage.cards.repository.CardInfo;
import mage.cards.repository.ExpansionRepository;
@ -30,23 +12,30 @@ import mage.client.util.ImageHelper;
import mage.client.util.gui.BufferedImageBuilder;
import mage.client.util.gui.GuiDisplayUtil;
import mage.constants.Rarity;
import mage.utils.StreamUtils;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.TranscodingHints;
import org.apache.batik.transcoder.image.ImageTranscoder;
import org.apache.batik.util.SVGConstants;
import org.apache.log4j.Logger;
import org.mage.plugins.card.utils.CardImageUtils;
import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
public final class ManaSymbols {
private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class);
private static final Map<Integer, Map<String, BufferedImage>> manaImages = new HashMap<>();
private static final Logger logger = Logger.getLogger(ManaSymbols.class);
private static final String CSS_FILE_NAME = "mana-svg-settings.css";
private static final String CSS_ADDITIONAL_SETTINGS = "";
private static final Map<Integer, Map<String, BufferedImage>> manaImages = new HashMap<>();
private static final Map<String, Map<Rarity, Image>> setImages = new ConcurrentHashMap<>();
private static final Set<String> onlyMythics = new HashSet<>();
@ -81,55 +70,20 @@ public final class ManaSymbols {
private static final Pattern REPLACE_SYMBOLS_PATTERN = Pattern.compile("\\{([^}/]*)/?([^}]*)\\}");
private static final String[] symbols = new String[]{
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
"B", "BG", "BR", "BP", "2B",
"G", "GU", "GW", "GP", "2G",
"R", "RG", "RW", "RP", "2R",
"S", "T", "Q",
"U", "UB", "UR", "UP", "2U",
"W", "WB", "WU", "WP", "2W",
"X", "C", "E"};
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
"B", "BG", "BR", "BP", "2B",
"G", "GU", "GW", "GP", "2G",
"R", "RG", "RW", "RP", "2R",
"S", "T", "Q",
"U", "UB", "UR", "UP", "2U",
"W", "WB", "WU", "WP", "2W",
"X", "C", "E"};
private static final JLabel labelRender = new JLabel(); // render mana text
private static String getSvgPathToCss() {
return getImagesDir() + File.separator + "temp" + File.separator + "batic-svg-settings.css";
}
private static void prepareSvg(Boolean forceToCreateCss) {
File f = new File(getSvgPathToCss());
if (forceToCreateCss || !f.exists()) {
// Rendering hints can't be set programatically, so
// we override defaults with a temporary stylesheet.
// These defaults emphasize quality and precision, and
// are more similar to the defaults of other SVG viewers.
// SVG documents can still override these defaults.
String css = "svg {"
+ "shape-rendering: geometricPrecision;"
+ "text-rendering: geometricPrecision;"
+ "color-rendering: optimizeQuality;"
+ "image-rendering: optimizeQuality;"
+ "}";
FileWriter w = null;
try {
f.getParentFile().mkdirs();
f.createNewFile();
w = new FileWriter(f);
w.write(css);
} catch (Throwable e) {
LOGGER.error("Can't create css file for svg", e);
} finally {
StreamUtils.closeQuietly(w);
}
}
}
public static void loadImages() {
LOGGER.info("Loading symbols...");
logger.info("Loading symbols...");
// TODO: delete files rename jpg->gif (it was for backward compatibility for one of the old version?)
renameSymbols(getResourceSymbolsPath(ResourceSymbolSize.SMALL));
@ -138,8 +92,8 @@ public final class ManaSymbols {
//renameSymbols(getSymbolsPath(ResourceSymbolSize.SVG)); // not need
// TODO: remove medium sets files to "medium" folder like symbols above?
// prepare svg settings
prepareSvg(true);
// prepare svg's css settings
SvgUtils.prepareCss(CSS_FILE_NAME, CSS_ADDITIONAL_SETTINGS, true);
// preload symbol images
loadSymbolImages(15);
@ -164,7 +118,7 @@ public final class ManaSymbols {
ImageIO.write(image, "png", newFile);
}
} catch (Exception e) {
LOGGER.warn("Can't generate png image for symbol:" + symbol);
logger.warn("Can't generate png image for symbol:" + symbol);
}
}
}
@ -173,7 +127,7 @@ public final class ManaSymbols {
java.util.List<String> setCodes = ExpansionRepository.instance.getSetCodes();
if (setCodes == null) {
// the cards db file is probaly not included in the client. It will be created after the first connect to a server.
LOGGER.warn("No db information for sets found. Connect to a server to create database file on client side. Then try to restart the client.");
logger.warn("No db information for sets found. Connect to a server to create database file on client side. Then try to restart the client.");
return;
}
for (String set : setCodes) {
@ -270,136 +224,16 @@ public final class ManaSymbols {
}
}
public static BufferedImage loadSVG(File svgFile, int resizeToWidth, int resizeToHeight, boolean useShadow) throws IOException {
// debug: disable shadow gen, need to test it
useShadow = false;
// load SVG image
// base loader code: https://stackoverflow.com/questions/11435671/how-to-get-a-buffererimage-from-a-svg
// resize code: https://vibranttechie.wordpress.com/2015/05/15/svg-loading-to-javafx-stage-and-auto-scaling-when-stage-resize/
if (useShadow && ((resizeToWidth <= 0) || (resizeToHeight <= 0))) {
throw new IllegalArgumentException("Must use non zero sizes for shadow.");
}
final BufferedImage[] imagePointer = new BufferedImage[1];
// css settings for svg
prepareSvg(false);
File cssFile = new File(getSvgPathToCss());
TranscodingHints transcoderHints = new TranscodingHints();
// resize
int shadowX = 0;
int shadowY = 0;
if (useShadow) {
// shadow size (16px image: 1px left, 2px bottom)
shadowX = 1 * Math.round(1f / 16f * resizeToWidth);
shadowY = 2 * Math.round(1f / 16f * resizeToHeight);
resizeToWidth = resizeToWidth - shadowX;
resizeToHeight = resizeToHeight - shadowY;
}
if (resizeToWidth > 0) {
transcoderHints.put(ImageTranscoder.KEY_WIDTH, (float) resizeToWidth); //your image width
}
if (resizeToHeight > 0) {
transcoderHints.put(ImageTranscoder.KEY_HEIGHT, (float) resizeToHeight); //your image height
}
transcoderHints.put(ImageTranscoder.KEY_XML_PARSER_VALIDATING, Boolean.FALSE);
transcoderHints.put(ImageTranscoder.KEY_DOM_IMPLEMENTATION,
SVGDOMImplementation.getDOMImplementation());
transcoderHints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI,
SVGConstants.SVG_NAMESPACE_URI);
transcoderHints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT, "svg");
transcoderHints.put(ImageTranscoder.KEY_USER_STYLESHEET_URI, cssFile.toURI().toString());
try {
TranscoderInput input = new TranscoderInput(new FileInputStream(svgFile));
ImageTranscoder t = new ImageTranscoder() {
@Override
public BufferedImage createImage(int w, int h) {
return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
}
@Override
public void writeImage(BufferedImage image, TranscoderOutput out)
throws TranscoderException {
imagePointer[0] = image;
}
};
t.setTranscodingHints(transcoderHints);
t.transcode(input, null);
} catch (Exception e) {
throw new IOException("Couldn't convert svg file: " + svgFile + " , reason: " + e.getMessage());
}
BufferedImage originImage = imagePointer[0];
if (useShadow && (originImage.getWidth() > 0)) {
// draw shadow
// origin image was reduces in sizes to fit shadow
// see https://stackoverflow.com/a/40833715/1276632
// a filter which converts all colors except 0 to black
ImageProducer prod = new FilteredImageSource(originImage.getSource(), new RGBImageFilter() {
@Override
public int filterRGB(int x, int y, int rgb) {
if (rgb == 0) {
return 0;
} else {
return 0xff000000;
}
}
});
// create whe black image
Image shadow = Toolkit.getDefaultToolkit().createImage(prod);
// result
BufferedImage result = new BufferedImage(originImage.getWidth() + shadowX, originImage.getHeight() + shadowY, originImage.getType());
Graphics2D g = (Graphics2D) result.getGraphics();
// draw shadow with offset (left bottom)
g.drawImage(shadow, -1 * shadowX, shadowY, null);
// draw original image
g.drawImage(originImage, 0, 0, null);
return result;
} else {
// return origin image without shadow
return originImage;
}
/*
BufferedImage base = GraphicsUtilities.createCompatibleTranslucentImage(w, h);
Graphics2D g2 = base.createGraphics();
g2.setColor(Color.WHITE);
g2.fillRoundRect(0, 0, image.getWidth(), image.getHeight(), 10, 10);
g2.dispose();
ShadowRenderer renderer = new ShadowRenderer(shadowSize, 0.5f,
Color.GRAY);
return renderer.createShadow(base);
*/
//imagePointer[0];
}
public static File getSymbolFileNameAsSVG(String symbol) {
return new File(getResourceSymbolsPath(ResourceSymbolSize.SVG) + symbol + ".svg");
}
private static BufferedImage loadSymbolAsSVG(String symbol, int resizeToWidth, int resizeToHeight) {
File sourceFile = getSymbolFileNameAsSVG(symbol);
return loadSymbolAsSVG(sourceFile, resizeToWidth, resizeToHeight);
}
private static BufferedImage loadSymbolAsSVG(File sourceFile, int resizeToWidth, int resizeToHeight) {
private static BufferedImage loadSymbolAsSVG(InputStream svgFile, String svgInfo, int resizeToWidth, int resizeToHeight) {
try {
// no need to resize svg (lib already do it on load)
return loadSVG(sourceFile, resizeToWidth, resizeToHeight, true);
return SvgUtils.loadSVG(svgFile, svgInfo, CSS_FILE_NAME, CSS_ADDITIONAL_SETTINGS, resizeToWidth, resizeToHeight, true);
} catch (Exception e) {
LOGGER.error("Can't load svg symbol: " + sourceFile.getPath() + " , reason: " + e.getMessage());
logger.error("Can't load svg symbol: " + svgInfo + " , reason: " + e.getMessage());
return null;
}
}
@ -441,7 +275,7 @@ public final class ManaSymbols {
}
}
} catch (IOException e) {
LOGGER.error("Can't load gif symbol: " + sourceFile.getPath());
logger.error("Can't load gif symbol: " + sourceFile.getPath());
return null;
}
@ -463,9 +297,16 @@ public final class ManaSymbols {
File file;
// svg
file = getSymbolFileNameAsSVG(symbol);
if (file.exists()) {
image = loadSymbolAsSVG(file, size, size);
if (SvgUtils.haveSvgSupport()) {
file = getSymbolFileNameAsSVG(symbol);
if (file.exists()) {
try {
InputStream fileStream = new FileInputStream(file);
image = loadSymbolAsSVG(fileStream, file.getPath(), size, size);
} catch (FileNotFoundException e) {
// it's ok to hide error
}
}
}
// gif
@ -501,7 +342,7 @@ public final class ManaSymbols {
}
if (!errorInfo.isEmpty()) {
LOGGER.warn("Symbols can't be load for size " + size + ": " + errorInfo);
logger.warn("Symbols can't be load for size " + size + ": " + errorInfo);
}
manaImages.put(size, sizedSymbols);
@ -527,7 +368,7 @@ public final class ManaSymbols {
}
});
} catch (IOException e) {
LOGGER.error("Couldn't rename mana symbols on " + path, e);
logger.error("Couldn't rename mana symbols on " + path, e);
}
}
@ -604,7 +445,7 @@ public final class ManaSymbols {
loadSymbolImages(symbolWidth);
}
// TODO: replace with jlabel render (look at table rendere)?
// TODO: replace with jlabel render (look at table renderer)?
/*
// NEW version with component draw
@ -677,7 +518,6 @@ public final class ManaSymbols {
labelRender.setVerticalAlignment(SwingConstants.CENTER);
labelRender.setForeground(symbolsTextColor);
labelRender.setHorizontalAlignment(SwingConstants.CENTER);
//labelRender.setBorder(new LineBorder(new Color(125, 250, 250), 1)); // debug draw
// fix font size for mana text
// work for labels WITHOUT borders
@ -727,6 +567,7 @@ public final class ManaSymbols {
CHAT,
DIALOG,
TOOLTIP,
CARD_ICON_HINT
}
private static String filePathToUrl(String path) {
@ -739,6 +580,13 @@ public final class ManaSymbols {
}
}
/**
* Replace images/icons code by real html links. Uses in many places.
*
* @param value
* @param type
* @return
*/
public static synchronized String replaceSymbolsWithHTML(String value, Type type) {
// mana cost to HTML images (urls to files)
@ -757,6 +605,7 @@ public final class ManaSymbols {
case TOOLTIP:
symbolSize = GUISizeHelper.symbolTooltipSize;
break;
case CARD_ICON_HINT:
default:
symbolSize = 11;
break;
@ -786,7 +635,7 @@ public final class ManaSymbols {
replaced = replaced.replace(CardInfo.SPLIT_MANA_SEPARATOR_FULL, CardInfo.SPLIT_MANA_SEPARATOR_RENDER);
replaced = REPLACE_SYMBOLS_PATTERN.matcher(replaced).replaceAll(
"<img src='" + filePathToUrl(htmlImagesPath) + "$1$2" + ".png' alt='$1$2' width="
+ symbolSize + " height=" + symbolSize + '>');
+ symbolSize + " height=" + symbolSize + '>');
// replace hint icons
if (replaced.contains(HintUtils.HINT_ICON_GOOD)) {

View file

@ -16,7 +16,7 @@ import java.util.StringTokenizer;
public final class ManaSymbolsCellRenderer extends DefaultTableCellRenderer {
// base panel to render
private JPanel renderPanel = new JPanel();
private final JPanel renderPanel = new JPanel();
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
@ -48,7 +48,6 @@ public final class ManaSymbolsCellRenderer extends DefaultTableCellRenderer {
JLabel symbolLabel = new JLabel();
symbolLabel.setFont(GUISizeHelper.tableFont);
symbolLabel.setBorder(new EmptyBorder(0, symbolHorizontalMargin, 0, 0));
//symbolLabel.setBorder(new LineBorder(new Color(150, 150, 150))); // debug draw
BufferedImage image = ManaSymbols.getSizedManaSymbol(symbol, symbolWidth);
if (image != null) {

View file

@ -13,7 +13,7 @@ import java.util.ArrayList;
import java.util.List;
/**
* Created by StravantUser on 2017-03-30.
* @author StravantUser
*/
public class ModernSplitCardRenderer extends ModernCardRenderer {
@ -29,7 +29,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
List<TextboxRule> keywords = new ArrayList<>();
}
private static ArrayList<CardType> ONLY_LAND_TYPE = new ArrayList<CardType>() {
private static final ArrayList<CardType> ONLY_LAND_TYPE = new ArrayList<CardType>() {
{
add(CardType.LAND);
}

View file

@ -1,20 +1,22 @@
package org.mage.card.arcane;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
import mage.client.util.TransformedImageCache;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
public class ScaledImagePanel extends JPanel {
private static final long serialVersionUID = -1523279873208605664L;
private volatile BufferedImage srcImage;
public ScaledImagePanel () {
public ScaledImagePanel() {
super(false);
setOpaque(false);
}
public void clearImage () {
public void clearImage() {
srcImage = null;
repaint();
}
@ -23,17 +25,24 @@ public class ScaledImagePanel extends JPanel {
this.srcImage = srcImage;
}
public boolean hasImage () {
public boolean hasImage() {
return srcImage != null;
}
@Override
public void paint (Graphics g) {
public void paint(Graphics g) {
if (srcImage == null) {
return;
}
g.drawImage(TransformedImageCache.getResizedImage(srcImage, getWidth(), getHeight()), 0, 0, null);
Insets border = getInsets();
int x = border.left;
int y = border.top;
int width = getWidth() - border.left - border.right;
int height = getHeight() - border.top - border.bottom;
g.drawImage(TransformedImageCache.getResizedImage(srcImage, width, height), x, y, width, height, null);
super.paint(g);
}
public BufferedImage getSrcImage() {

View file

@ -0,0 +1,230 @@
package org.mage.card.arcane;
import mage.abilities.icon.abilities.FlyingAbilityIcon;
import mage.utils.StreamUtils;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.TranscodingHints;
import org.apache.batik.transcoder.image.ImageTranscoder;
import org.apache.batik.util.SVGConstants;
import org.apache.log4j.Logger;
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageProducer;
import java.awt.image.RGBImageFilter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir;
/**
* @author JayDi85
*/
public class SvgUtils {
private static final Logger logger = Logger.getLogger(SvgUtils.class);
private static boolean haveSvgSupport = false;
// basic css settings for good svg rendering quality, other default settings can be defined by additional param
private static final String CSS_BASE_SETTINGS = ""
+ "shape-rendering: geometricPrecision;"
+ "text-rendering: geometricPrecision;"
+ "color-rendering: optimizeQuality;"
+ "image-rendering: optimizeQuality;";
private static String getSvgTempFolder() {
return getImagesDir() + File.separator + "temp";
}
public static String getSvgTempFile(String fileName) {
return getSvgTempFolder() + File.separator + fileName;
}
public static void prepareCss(String cssFileName, String cssAdditionalSettings, Boolean forceToCreateCss) {
// css must be created all the time, so ignore svg check here
//if (!SvgUtils.haveSvgSupport())
File cssFile = new File(SvgUtils.getSvgTempFile(cssFileName));
if (forceToCreateCss || !cssFile.exists()) {
// Rendering hints can't be set programatically, so
// we override defaults with a temporary stylesheet.
// These defaults emphasize quality and precision, and
// are more similar to the defaults of other SVG viewers.
// SVG documents can still override these defaults.
String css = "svg {"
+ CSS_BASE_SETTINGS
+ cssAdditionalSettings
+ "}";
FileWriter w = null;
try {
cssFile.getParentFile().mkdirs();
cssFile.createNewFile();
w = new FileWriter(cssFile);
w.write(css);
} catch (Throwable e) {
logger.error("Can't create css file for svg: " + cssFile.toPath().toAbsolutePath().toString(), e);
} finally {
StreamUtils.closeQuietly(w);
}
}
}
/**
* Load svg file content as image
*
* @param svgFile content (can be resource or file)
* @param svgInfo info to show in logs or errors
* @param cssFileName css settings
* @param cssAdditionalSettings additional css settings (warning, if you change additional settings then css file must be re-created)
* @param resizeToWidth image size
* @param resizeToHeight image size
* @param useShadow draw image with shadow (not implemented)
* @return can return null on error (some linux systems can have compatibility problem with different java/svg libs)
* @throws IOException
*/
public static BufferedImage loadSVG(InputStream svgFile, String svgInfo,
String cssFileName, String cssAdditionalSettings,
int resizeToWidth, int resizeToHeight, boolean useShadow) throws IOException {
if (svgFile == null) {
throw new IllegalArgumentException("Empty svg data or unknown file");
}
// load SVG image
// base loader code: https://stackoverflow.com/questions/11435671/how-to-get-a-buffererimage-from-a-svg
// resize code: https://vibranttechie.wordpress.com/2015/05/15/svg-loading-to-javafx-stage-and-auto-scaling-when-stage-resize/
useShadow = false; // TODO: implement shadow drawing
if (useShadow && ((resizeToWidth <= 0) || (resizeToHeight <= 0))) {
throw new IllegalArgumentException("Must use non zero sizes for shadow");
}
final BufferedImage[] imagePointer = new BufferedImage[1];
// css settings for svg
SvgUtils.prepareCss(cssFileName, cssAdditionalSettings, false);
File cssFile = new File(SvgUtils.getSvgTempFile(cssFileName));
TranscodingHints transcoderHints = new TranscodingHints();
// resize
int shadowX = 0;
int shadowY = 0;
if (useShadow) {
// shadow size (16px image: 1px left, 2px bottom)
shadowX = 1 * Math.round(1f / 16f * resizeToWidth);
shadowY = 2 * Math.round(1f / 16f * resizeToHeight);
resizeToWidth = resizeToWidth - shadowX;
resizeToHeight = resizeToHeight - shadowY;
}
if (resizeToWidth > 0) {
transcoderHints.put(ImageTranscoder.KEY_WIDTH, (float) resizeToWidth); //your image width
}
if (resizeToHeight > 0) {
transcoderHints.put(ImageTranscoder.KEY_HEIGHT, (float) resizeToHeight); //your image height
}
transcoderHints.put(ImageTranscoder.KEY_XML_PARSER_VALIDATING, Boolean.FALSE);
transcoderHints.put(ImageTranscoder.KEY_DOM_IMPLEMENTATION,
SVGDOMImplementation.getDOMImplementation());
transcoderHints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI,
SVGConstants.SVG_NAMESPACE_URI);
transcoderHints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT, "svg");
transcoderHints.put(ImageTranscoder.KEY_USER_STYLESHEET_URI, cssFile.toURI().toString());
try {
TranscoderInput input = new TranscoderInput(svgFile);
ImageTranscoder t = new ImageTranscoder() {
@Override
public BufferedImage createImage(int w, int h) {
return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
}
@Override
public void writeImage(BufferedImage image, TranscoderOutput out) {
imagePointer[0] = image;
}
};
t.setTranscodingHints(transcoderHints);
t.transcode(input, null);
} catch (Exception e) {
throw new IOException("Can't load svg file: " + svgInfo + " , reason: " + e.getMessage());
}
BufferedImage originImage = imagePointer[0];
if (useShadow && (originImage.getWidth() > 0)) {
// draw shadow
// origin image was reduces in sizes to fit shadow
// see https://stackoverflow.com/a/40833715/1276632
// a filter which converts all colors except 0 to black
ImageProducer prod = new FilteredImageSource(originImage.getSource(), new RGBImageFilter() {
@Override
public int filterRGB(int x, int y, int rgb) {
if (rgb == 0) {
return 0;
} else {
return 0xff000000;
}
}
});
// create whe black image
Image shadow = Toolkit.getDefaultToolkit().createImage(prod);
// result
BufferedImage result = new BufferedImage(originImage.getWidth() + shadowX, originImage.getHeight() + shadowY, originImage.getType());
Graphics2D g = (Graphics2D) result.getGraphics();
// draw shadow with offset (left bottom)
g.drawImage(shadow, -1 * shadowX, shadowY, null);
// draw original image
g.drawImage(originImage, 0, 0, null);
return result;
} else {
// return origin image without shadow
return originImage;
}
/*
BufferedImage base = GraphicsUtilities.createCompatibleTranslucentImage(w, h);
Graphics2D g2 = base.createGraphics();
g2.setColor(Color.WHITE);
g2.fillRoundRect(0, 0, image.getWidth(), image.getHeight(), 10, 10);
g2.dispose();
ShadowRenderer renderer = new ShadowRenderer(shadowSize, 0.5f,
Color.GRAY);
return renderer.createShadow(base);
*/
//imagePointer[0];
}
/**
* Check if the current system support svg (some linux systems can have compatibility problems due to different java/svg libs)
* <p>
* Call it on app's start
*
* @return true on support, also save result for haveSvgSupport
*/
public static boolean checkSvgSupport() {
// usa sample icon for svg support testing
// direct call, no needs in cache
BufferedImage sampleImage = ImageManagerImpl.instance.getCardIcon(FlyingAbilityIcon.instance.getIconType().getResourceName(), 32);
haveSvgSupport = (sampleImage != null && sampleImage.getWidth() > 0);
if (!haveSvgSupport) {
logger.warn("WARNING, your system doesn't support svg images, so card icons will be disabled. Please, make a bug report in the github.");
}
return haveSvgSupport;
}
public static boolean haveSvgSupport() {
return haveSvgSupport;
}
}

View file

@ -17,7 +17,7 @@ import java.util.regex.Pattern;
*/
public final class TextboxRuleParser {
private static final Logger LOGGER = Logger.getLogger(CardPanel.class);
private static final Logger LOGGER = Logger.getLogger(TextboxRuleParser.class);
private static final Pattern BasicManaAbility = Pattern.compile("\\{T\\}: Add \\{(\\w)\\}\\.");
private static final Pattern LevelAbilityPattern = Pattern.compile("Level (\\d+)-?(\\d*)(\\+?)");

View file

@ -1,5 +1,6 @@
package org.mage.plugins.card;
import mage.cards.MageCard;
import mage.cards.MagePermanent;
import mage.cards.action.ActionCallback;
import mage.client.util.GUISizeHelper;
@ -45,8 +46,8 @@ public class CardPluginImpl implements CardPlugin {
private static final Logger LOGGER = Logger.getLogger(CardPluginImpl.class);
private static final int GUTTER_Y = 15;
private static final int GUTTER_X = 5;
private static final int GUTTER_Y = 15; // top offset before cards
private static final int GUTTER_X = 15; // left offset before cards
static final float EXTRA_CARD_SPACING_X = 0.04f;
private static final float CARD_SPACING_Y = 0.03f;
private static final float STACK_SPACING_X = 0.07f;
@ -54,9 +55,10 @@ public class CardPluginImpl implements CardPlugin {
private static final float ATTACHMENT_SPACING_Y = 0.13f;
private static final int landStackMax = 5;
// private int cardWidthMin = 50, cardWidthMax = Constants.CARD_SIZE_FULL.width;
private int cardWidthMin = (int) GUISizeHelper.battlefieldCardMinDimension.getWidth();
private int cardWidthMax = (int) GUISizeHelper.battlefieldCardMaxDimension.getWidth();
// card width increment for auto-size searching (bigger value - faster draw speed on screen size, but not as accurate)
private static final int CARD_WIDTH_AUTO_FIT_INCREMENT = 10;
private static final boolean stackVertical = false;
@ -98,12 +100,12 @@ public class CardPluginImpl implements CardPlugin {
* Temporary card rendering shim. Split card rendering isn't implemented
* yet, so use old component based rendering for the split cards.
*/
private CardPanel makePanel(CardView view, UUID gameId, boolean loadImage, ActionCallback callback, boolean isFoil, Dimension dimension, int renderMode, boolean needFullPermanentRender) {
private CardPanel makeCardPanel(CardView view, UUID gameId, boolean loadImage, ActionCallback callback, boolean isFoil, Dimension dimension, int renderMode, boolean needFullPermanentRender) {
switch (renderMode) {
case 0:
return new CardPanelRenderImpl(view, gameId, loadImage, callback, isFoil, dimension, needFullPermanentRender);
return new CardPanelRenderModeMTGO(view, gameId, loadImage, callback, isFoil, dimension, needFullPermanentRender);
case 1:
return new CardPanelComponentImpl(view, gameId, loadImage, callback, isFoil, dimension, needFullPermanentRender);
return new CardPanelRenderModeImage(view, gameId, loadImage, callback, isFoil, dimension, needFullPermanentRender);
default:
throw new IllegalStateException("Unknown render mode " + renderMode);
@ -111,43 +113,45 @@ public class CardPluginImpl implements CardPlugin {
}
@Override
public MagePermanent getMagePermanent(PermanentView permanent, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage, int renderMode, boolean needFullPermanentRender) {
CardPanel cardPanel = makePanel(permanent, gameId, loadImage, callback, false, dimension, renderMode, needFullPermanentRender);
public MageCard getMagePermanent(PermanentView permanent, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage, int renderMode, boolean needFullPermanentRender) {
CardPanel cardPanel = makeCardPanel(permanent, gameId, loadImage, callback, false, dimension, renderMode, needFullPermanentRender);
cardPanel.setShowCastingCost(true);
return cardPanel;
}
@Override
public MagePermanent getMageCard(CardView cardView, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage, int renderMode, boolean needFullPermanentRender) {
CardPanel cardPanel = makePanel(cardView, gameId, loadImage, callback, false, dimension, renderMode, needFullPermanentRender);
public MageCard getMageCard(CardView cardView, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage, int renderMode, boolean needFullPermanentRender) {
CardPanel cardPanel = makeCardPanel(cardView, gameId, loadImage, callback, false, dimension, renderMode, needFullPermanentRender);
cardPanel.setShowCastingCost(true);
return cardPanel;
}
@Override
public int sortPermanents(Map<String, JComponent> ui, Map<UUID, MagePermanent> permanents, boolean nonPermanentsOwnRow, boolean topPanel) {
//TODO: add caching
public int sortPermanents(Map<String, JComponent> ui, Map<UUID, MageCard> cards, boolean nonPermanentsOwnRow, boolean topPanel) {
//requires to find out is position have been changed that includes:
//adding/removing permanents, type change
// must return new height, so battlefield scrolls can be enabled on too big sizes
if (ui == null) {
throw new RuntimeException("Error: no components");
}
JComponent component = ui.get("battlefieldPanel");
if (component == null) {
throw new RuntimeException("Error: battlefieldPanel is missing");
throw new RuntimeException("No battlefield ui for layout");
}
JLayeredPane battlefieldPanel = (JLayeredPane) component;
JComponent jPanel = ui.get("jPanel");
JLayeredPane battlefieldPanel = (JLayeredPane) ui.get("battlefieldPanel");
JComponent cardsPanel = ui.get("jPanel");
JScrollPane scrollPane = (JScrollPane) ui.get("scrollPane");
if (battlefieldPanel == null || cardsPanel == null || scrollPane == null) {
throw new RuntimeException("No battlefield components for layout");
}
Row rowAllLands = new Row();
outerLoop:
//
for (MagePermanent permanent : permanents.values()) {
if (!permanent.isLand() || permanent.isCreature()) {
for (MageCard card : cards.values()) {
MagePermanent perm = (MagePermanent) card.getMainPanel(); // all cards must be MagePermanent on battlefield
if (!perm.isLand() || perm.isCreature()) {
continue;
}
@ -155,35 +159,36 @@ public class CardPluginImpl implements CardPlugin {
// Find already added lands with the same name.
for (int i = 0, n = rowAllLands.size(); i < n; i++) {
// stack contains main card panel, but for any size/order manipulation you must use top layer panel
Stack stack = rowAllLands.get(i);
MagePermanent firstPanel = stack.get(0);
if (firstPanel.getOriginal().getName().equals(permanent.getOriginal().getName())) {
MagePermanent firstPanelPerm = stack.get(0);
if (firstPanelPerm.getOriginal().getName().equals(perm.getOriginal().getName())) {
if (!empty(firstPanel.getOriginalPermanent().getAttachments())) {
if (!empty(firstPanelPerm.getOriginalPermanent().getAttachments())) {
// Put this land to the left of lands with the same name and attachments.
insertIndex = i;
break;
}
List<CounterView> counters = firstPanel.getOriginalPermanent().getCounters();
List<CounterView> counters = firstPanelPerm.getOriginalPermanent().getCounters();
if (counters != null && !counters.isEmpty()) {
// don't put to first panel if it has counters
insertIndex = i;
break;
}
if (!empty(permanent.getOriginalPermanent().getAttachments()) || stack.size() == landStackMax) {
if (!empty(perm.getOriginalPermanent().getAttachments()) || stack.size() == landStackMax) {
// If this land has attachments or the stack is full, put it to the right.
insertIndex = i + 1;
continue;
}
counters = permanent.getOriginalPermanent().getCounters();
counters = perm.getOriginalPermanent().getCounters();
if (counters != null && !counters.isEmpty()) {
// if a land has counter, put it to the right
insertIndex = i + 1;
continue;
}
// Add to stack.
stack.add(0, permanent);
stack.add(0, perm);
continue outerLoop;
}
if (insertIndex != -1) {
@ -193,22 +198,22 @@ public class CardPluginImpl implements CardPlugin {
Stack stack = new Stack();
if (permanent.getOriginalPermanent().getAttachments() != null
&& !permanent.getOriginalPermanent().getAttachments().isEmpty()
&& !permanent.getOriginalPermanent().isAttachedTo()) {
if (perm.getOriginalPermanent().getAttachments() != null
&& !perm.getOriginalPermanent().getAttachments().isEmpty()
&& !perm.getOriginalPermanent().isAttachedTo()) {
// get the number of all attachements and sub attachments
AttachmentLayoutInfos ali = calculateNeededNumberOfVerticalColumns(0, permanents, permanent);
AttachmentLayoutInfos ali = calculateNeededNumberOfVerticalColumns(0, cards, card);
stack.setMaxAttachedCount(ali.getAttachments());
stack.setAttachmentColumns(ali.getColumns());
}
stack.add(permanent);
stack.add(perm);
rowAllLands.add(insertIndex == -1 ? rowAllLands.size() : insertIndex, stack);
}
Row rowAllCreatures = new Row(permanents, RowType.creature);
Row rowAllOthers = new Row(permanents, RowType.other);
Row rowAllAttached = new Row(permanents, RowType.attached);
Row rowAllCreatures = new Row(cards, RowType.creature);
Row rowAllOthers = new Row(cards, RowType.other);
Row rowAllAttached = new Row(cards, RowType.attached);
boolean othersOnTheRight = true;
if (nonPermanentsOwnRow) {
@ -217,6 +222,7 @@ public class CardPluginImpl implements CardPlugin {
rowAllOthers.clear();
}
// try to auto-fit cards
cardWidth = cardWidthMax;
Rectangle rect = battlefieldPanel.getVisibleRect();
playAreaWidth = rect.width;
@ -226,7 +232,7 @@ public class CardPluginImpl implements CardPlugin {
// calculate values based on the card size that is changing with every iteration
cardHeight = Math.round(cardWidth * CardPanel.ASPECT_RATIO);
extraCardSpacingX = Math.round(cardWidth * EXTRA_CARD_SPACING_X);
cardSpacingX = cardHeight - cardWidth + extraCardSpacingX;
cardSpacingX = cardHeight - cardWidth + extraCardSpacingX; // need space for tap animation (horizontal position)
cardSpacingY = Math.round(cardHeight * CARD_SPACING_Y);
stackSpacingX = stackVertical ? 0 : Math.round(cardWidth * STACK_SPACING_X);
stackSpacingY = Math.round(cardHeight * STACK_SPACING_Y);
@ -248,7 +254,6 @@ public class CardPluginImpl implements CardPlugin {
addOthersIndex = rows.size();
wrap(lands, rows, rows.size());
wrap(others, rows, rows.size());
}
// Store the current rows and others.
@ -277,9 +282,7 @@ public class CardPluginImpl implements CardPlugin {
if (creatures.isEmpty() && lands.isEmpty() && others.isEmpty()) {
break;
}
//FIXME: -1 is too slow. why not binary search?
cardWidth -= 3;
cardWidth -= CARD_WIDTH_AUTO_FIT_INCREMENT;
}
// Get size of all the rows.
@ -297,7 +300,7 @@ public class CardPluginImpl implements CardPlugin {
maxRowWidth = Math.max(maxRowWidth, x);
}
// Position all card panels.
// Position all card panels
y = GUTTER_Y;
for (Row row : rows) {
int rowBottom = 0;
@ -312,21 +315,21 @@ public class CardPluginImpl implements CardPlugin {
}
}
for (int panelIndex = 0, panelCount = stack.size(); panelIndex < panelCount; panelIndex++) {
MagePermanent panel = stack.get(panelIndex);
MagePermanent panelPerm = stack.get(panelIndex); // it's original card panel, but you must change top layer
int stackPosition = panelCount - panelIndex - 1;
if (jPanel != null) {
jPanel.setComponentZOrder(panel, panelIndex);
if (cardsPanel != null) {
cardsPanel.setComponentZOrder(panelPerm.getTopPanelRef(), panelIndex);
}
int panelX = x + (stackPosition * stackSpacingX);
int panelY = y + (stackPosition * stackSpacingY);
try {
// may cause:
// java.lang.IllegalArgumentException: illegal component position 26 should be less then 26
battlefieldPanel.moveToFront(panel);
battlefieldPanel.moveToFront(panelPerm.getTopPanelRef());
} catch (Exception e) {
e.printStackTrace();
}
panel.setCardBounds(panelX, panelY, cardWidth, cardHeight);
panelPerm.getTopPanelRef().setCardBounds(panelX, panelY, cardWidth, cardHeight);
}
rowBottom = Math.max(rowBottom, y + stack.getHeight());
x += stack.getWidth();
@ -337,11 +340,14 @@ public class CardPluginImpl implements CardPlugin {
// we need this only for defining card size
// attached permanents will be handled separately
for (Stack stack : rowAllAttached) {
for (MagePermanent panel : stack) {
panel.setCardBounds(0, 0, cardWidth, cardHeight);
for (MagePermanent panelPerm : stack) {
panelPerm.getTopPanelRef().setCardBounds(0, 0, cardWidth, cardHeight);
}
}
// scrollbars speed
scrollPane.getVerticalScrollBar().setUnitIncrement(GUISizeHelper.getCardsScrollbarUnitInc(cardHeight));
return y;
}
@ -351,7 +357,7 @@ public class CardPluginImpl implements CardPlugin {
private int wrap(Row sourceRow, List<Row> rows, int insertIndex) {
// The cards are sure to fit (with vertical scrolling) at the minimum card width.
boolean allowHeightOverflow = cardWidth == cardWidthMin;
boolean allowHeightOverflow = (cardWidth <= cardWidthMin);
Row currentRow = new Row();
for (int i = 0, n = sourceRow.size() - 1; i <= n; i++) {
@ -413,15 +419,17 @@ public class CardPluginImpl implements CardPlugin {
return height - cardSpacingY + GUTTER_Y * 2;
}
private AttachmentLayoutInfos calculateNeededNumberOfVerticalColumns(int currentCol, Map<UUID, MagePermanent> permanents, MagePermanent permanentWithAttachments) {
private AttachmentLayoutInfos calculateNeededNumberOfVerticalColumns(int currentCol, Map<UUID, MageCard> cards, MageCard cardWithAttachments) {
int maxCol = ++currentCol;
int attachments = 0;
for (UUID attachmentId : permanentWithAttachments.getOriginalPermanent().getAttachments()) {
MagePermanent attachedPermanent = permanents.get(attachmentId);
if (attachedPermanent != null) {
MagePermanent permWithAttachments = (MagePermanent) cardWithAttachments.getMainPanel();
for (UUID attachmentId : permWithAttachments.getOriginalPermanent().getAttachments()) {
MageCard attachedCard = cards.get(attachmentId);
if (attachedCard != null) {
attachments++;
if (attachedPermanent.getOriginalPermanent().getAttachments() != null && !attachedPermanent.getOriginalPermanent().getAttachments().isEmpty()) {
AttachmentLayoutInfos attachmentLayoutInfos = calculateNeededNumberOfVerticalColumns(currentCol, permanents, attachedPermanent);
MagePermanent attachedPerm = (MagePermanent) attachedCard.getMainPanel();
if (attachedPerm.getOriginalPermanent().getAttachments() != null && !attachedPerm.getOriginalPermanent().getAttachments().isEmpty()) {
AttachmentLayoutInfos attachmentLayoutInfos = calculateNeededNumberOfVerticalColumns(currentCol, cards, attachedCard);
if (attachmentLayoutInfos.getColumns() > maxCol) {
maxCol = attachmentLayoutInfos.getColumns();
attachments += attachmentLayoutInfos.getAttachments();
@ -435,16 +443,16 @@ public class CardPluginImpl implements CardPlugin {
private enum RowType {
land, creature, other, attached;
public boolean isType(MagePermanent card) {
public boolean isType(MagePermanent permanent) {
switch (this) {
case land:
return card.isLand();
return permanent.isLand();
case creature:
return card.isCreature();
return permanent.isCreature();
case other:
return !card.isLand() && !card.isCreature();
return !permanent.isLand() && !permanent.isCreature();
case attached:
return card.getOriginalPermanent().isAttachedToPermanent();
return permanent.getOriginalPermanent().isAttachedToPermanent();
default:
throw new RuntimeException("Unhandled type: " + this);
}
@ -459,24 +467,26 @@ public class CardPluginImpl implements CardPlugin {
super(16);
}
public Row(Map<UUID, MagePermanent> permanents, RowType type) {
public Row(Map<UUID, MageCard> cards, RowType type) {
this();
addAll(permanents, type);
addAll(cards, type);
}
private void addAll(Map<UUID, MagePermanent> permanents, RowType type) {
for (MagePermanent permanent : permanents.values()) {
if (!type.isType(permanent)) {
private void addAll(Map<UUID, MageCard> cards, RowType type) {
for (MageCard card : cards.values()) {
MagePermanent perm = (MagePermanent) card.getMainPanel();
if (!type.isType(perm)) {
continue;
}
// all attached permanents are grouped separately later
if (type != RowType.attached && RowType.attached.isType(permanent)) {
if (type != RowType.attached && RowType.attached.isType(perm)) {
continue;
}
Stack stack = new Stack();
stack.add(permanent);
if (permanent.getOriginalPermanent().getAttachments() != null) {
AttachmentLayoutInfos ali = calculateNeededNumberOfVerticalColumns(0, permanents, permanent);
stack.add(perm);
if (perm.getOriginalPermanent().getAttachments() != null) {
AttachmentLayoutInfos ali = calculateNeededNumberOfVerticalColumns(0, cards, card);
stack.setMaxAttachedCount(ali.getAttachments());
stack.setAttachmentColumns(ali.getColumns());
}
@ -666,7 +676,7 @@ public class CardPluginImpl implements CardPlugin {
}
@Override
public void onAddCard(MagePermanent card, int count) {
public void onAddCard(MageCard card, int count) {
if (card != null) {
Animation.showCard(card, count > 0 ? count : 1);
try {
@ -680,7 +690,7 @@ public class CardPluginImpl implements CardPlugin {
}
@Override
public void onRemoveCard(MagePermanent card, int count) {
public void onRemoveCard(MageCard card, int count) {
if (card != null) {
Animation.hideCard(card, count > 0 ? count : 1);
try {

View file

@ -1,9 +1,3 @@
/**
* AbstractBoundBean.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans;

View file

@ -1,9 +1,3 @@
/**
* BoundBean.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans;

View file

@ -1,8 +1,3 @@
/**
* EventListenerList.java
*
* Created on 08.04.2010
*/
package org.mage.plugins.card.dl.beans;
import com.google.common.base.Function;

View file

@ -1,14 +1,5 @@
/**
* PropertyChangeSupport.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans;
/**
* The class PropertyChangeSupport.
*

View file

@ -1,9 +1,3 @@
/**
* ListenableCollections.java
*
* Created on 25.04.2010
*/
package org.mage.plugins.card.dl.beans.collections;

View file

@ -1,9 +1,3 @@
/**
* AbstractProperties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;
import java.util.ArrayList;

View file

@ -1,9 +1,3 @@
/**
* AbstractProperty.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;

View file

@ -1,9 +1,3 @@
/**
* CompoundProperties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;

View file

@ -1,9 +1,3 @@
/**
* Properties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;

View file

@ -1,9 +1,3 @@
/**
* Property.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;

View file

@ -1,9 +1,3 @@
/**
* BoundProperties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties.basic;

View file

@ -1,9 +1,3 @@
/**
* BasicProperty.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.basic;

View file

@ -1,9 +1,3 @@
/**
* BoundProperties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;

View file

@ -1,9 +1,3 @@
/**
* BasicProperty.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;

View file

@ -1,17 +1,8 @@
/**
* PropertyChangeListListener.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;
import org.mage.plugins.card.dl.beans.PropertyChangeSupport;
import org.mage.plugins.card.dl.beans.collections.ListenableCollections.ListListener;
/**
* The class PropertyChangeListListener.
*

View file

@ -1,9 +1,3 @@
/**
* PropertyChangeMapListener.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;

View file

@ -1,9 +1,3 @@
/**
* PropertyChangeSetListener.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;

View file

@ -1,19 +1,11 @@
/**
* AbstractLaternaBean.java
* <p>
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl.lm;
import org.apache.log4j.Logger;
import org.mage.plugins.card.dl.beans.AbstractBoundBean;
import org.mage.plugins.card.dl.beans.EventListenerList;
import org.mage.plugins.card.dl.beans.properties.Properties;
import org.mage.plugins.card.dl.beans.properties.bound.BoundProperties;
/**
* The class AbstractLaternaBean.
*

View file

@ -1,9 +1,3 @@
/**
* GathererSymbols.java
* <p>
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl.sources;
import mage.client.constants.Constants;

View file

@ -1,8 +1,3 @@
/**
* GathererSymbols.java
*
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl.sources;
import com.google.common.collect.AbstractIterator;

View file

@ -13,6 +13,7 @@ import net.java.truevfs.access.TFileOutputStream;
import org.apache.log4j.Logger;
import org.mage.plugins.card.dl.sources.DirectLinksForDownload;
import org.mage.plugins.card.utils.CardImageUtils;
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
import javax.imageio.ImageIO;
import java.awt.*;
@ -45,14 +46,18 @@ public final class ImageCache {
private static final SoftValuesLoadingCache<String, BufferedImage> IMAGE_CACHE;
private static final SoftValuesLoadingCache<String, BufferedImage> FACE_IMAGE_CACHE;
private static final SoftValuesLoadingCache<String, BufferedImage> CARD_ICONS_CACHE;
/**
* Common pattern for keys. See ImageCache.getKey for structure info
*/
private static final Pattern KEY_PATTERN = Pattern.compile("(.*)#(.*)#(.*)#(.*)#(.*)#(.*)");
private static final Pattern CARD_ICON_KEY_PATTERN = Pattern.compile("(.*)#(.*)");
static {
// softValues() = Specifies that each value (not key) stored in the map should be wrapped in a SoftReference (by default, strong references are used). Softly-referenced objects will be garbage-collected in a globally least-recently-used manner, in response to memory demand.
// softValues() = Specifies that each value (not key) stored in the map should be wrapped in a SoftReference
// (by default, strong references are used). Softly-referenced objects will be garbage-collected in a
// globally least-recently-used manner, in response to memory demand.
IMAGE_CACHE = SoftValuesLoadingCache.from(new Function<String, BufferedImage>() {
@Override
public BufferedImage apply(String key) {
@ -219,10 +224,33 @@ public final class ImageCache {
}
}
});
CARD_ICONS_CACHE = SoftValuesLoadingCache.from(key -> {
try {
Matcher m = CARD_ICON_KEY_PATTERN.matcher(key);
if (m.matches()) {
int cardSize = Integer.parseInt(m.group(1));
String resourceName = m.group(2);
BufferedImage image = ImageManagerImpl.instance.getCardIcon(resourceName, cardSize);
return image;
} else {
throw new RuntimeException("Wrong card icons image key format: " + key);
}
} catch (Exception ex) {
if (ex instanceof ComputationException) {
throw (ComputationException) ex;
} else {
throw new ComputationException(ex);
}
}
});
}
public static void clearCache() {
IMAGE_CACHE.invalidateAll();
FACE_IMAGE_CACHE.invalidateAll();
CARD_ICONS_CACHE.invalidateAll();
}
public static String getFilePath(CardView card, int width) {
@ -335,7 +363,7 @@ public final class ImageCache {
BufferedImage cornerImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
// corner
float ROUNDED_CORNER_SIZE = 0.11f; // see CardPanelComponentImpl
float ROUNDED_CORNER_SIZE = 0.11f; // see CardPanelRenderModeImage
int cornerSizeBorder = Math.max(4, Math.round(image.getWidth() * ROUNDED_CORNER_SIZE));
// corner mask
@ -394,13 +422,14 @@ public final class ImageCache {
return getImage(getKey(card, card.getName(), ""));
}
// public static BufferedImage getImageFaceOriginal(CardView card) {
// return getFaceImage(getFaceKey(card, card.getName(), card.getExpansionSetCode()));
// }
public static BufferedImage getImageOriginalAlternateName(CardView card) {
return getImage(getKey(card, card.getAlternateName(), ""));
}
public static BufferedImage getCardIconImage(String resourceName, int iconSize) {
return getCardIconImage(getCardIconKey(resourceName, iconSize));
}
/**
* Returns the Image corresponding to the key
*/
@ -432,6 +461,18 @@ public final class ImageCache {
}
}
private static BufferedImage getCardIconImage(String key) {
try {
return CARD_ICONS_CACHE.getOrNull(key);
} catch (ComputationException ex) {
if (ex.getCause() instanceof NullPointerException) {
return null;
}
LOGGER.error(ex, ex);
return null;
}
}
/**
* Returns the Image corresponding to the key only if it already exists in
* the cache.
@ -461,13 +502,9 @@ public final class ImageCache {
return name + '#' + set + "####";
}
// /**
// * Returns the map key for the flip image of a card, without any suffixes for the image size.
// */
// private static String getKeyAlternateName(CardView card, String alternateName) {
// return alternateName + "#" + card.getExpansionSetCode() + "#" +card.getType()+ "#" + card.getCardNumber() + "#"
// + (card.getTokenSetCode() == null ? "":card.getTokenSetCode());
// }
private static String getCardIconKey(String resourceName, int size) {
return size + "#" + resourceName;
}
/**
* Load image from file

View file

@ -1,50 +1,83 @@
package org.mage.plugins.card.utils;
import java.awt.*;
import java.awt.image.BufferedImage;
public interface ImageManager {
Image getAppImage();
Image getAppSmallImage();
Image getAppFlashedImage();
Image getSicknessImage();
Image getDayImage();
Image getNightImage();
Image getTokenIconImage();
Image getTriggeredAbilityImage();
Image getActivatedAbilityImage();
Image getLookedAtImage();
Image getRevealedImage();
Image getExileImage();
Image getCopyInformIconImage();
Image getCounterImageViolet();
Image getCounterImageRed();
Image getCounterImageGreen();
Image getCounterImageGrey();
Image getDlgAcceptButtonImage();
Image getDlgActiveAcceptButtonImage();
Image getDlgCancelButtonImage();
Image getDlgActiveCancelButtonImage();
Image getDlgPrevButtonImage();
Image getDlgActivePrevButtonImage();
Image getDlgNextButtonImage();
Image getDlgActiveNextButtonImage();
Image getSwitchHandsButtonImage();
Image getStopWatchButtonImage();
Image getConcedeButtonImage();
Image getCancelSkipButtonImage();
Image getSkipNextTurnButtonImage();
Image getSkipEndTurnButtonImage();
Image getSkipMainButtonImage();
Image getSkipStackButtonImage();
Image getSkipEndStepBeforeYourTurnButtonImage();
Image getSkipYourNextTurnButtonImage();
Image getToggleRecordMacroButtonImage();
BufferedImage getCardIcon(String resourceName, int size);
Image getPhaseImage(String phase);
}

View file

@ -1,36 +1,34 @@
package org.mage.plugins.card.utils.impl;
import java.awt.Color;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.CropImageFilter;
import java.awt.image.FilteredImageSource;
import java.awt.image.WritableRaster;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.gui.BufferedImageBuilder;
import org.apache.log4j.Logger;
import org.mage.card.arcane.SvgUtils;
import org.mage.plugins.card.utils.ImageManager;
import org.mage.plugins.card.utils.Transparency;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.*;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.imageio.ImageIO;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.gui.BufferedImageBuilder;
import org.mage.plugins.card.utils.ImageManager;
import org.mage.plugins.card.utils.Transparency;
public enum ImageManagerImpl implements ImageManager {
instance;
private static final Logger logger = Logger.getLogger(ImageManagerImpl.class);
ImageManagerImpl() {
init();
}
public void init() {
String[] phases = {"Untap", "Upkeep", "Draw", "Main1",
"Combat_Start", "Combat_Attack", "Combat_Block", "Combat_Damage", "Combat_End",
"Main2", "Cleanup", "Next_Turn"};
"Combat_Start", "Combat_Attack", "Combat_Block", "Combat_Damage", "Combat_End",
"Main2", "Cleanup", "Next_Turn"};
phasesImages = new HashMap<>();
for (String name : phases) {
Image image = getImageFromResource(
@ -224,6 +222,7 @@ public enum ImageManagerImpl implements ImageManager {
return imageDlgAcceptButton;
}
@Override
public Image getDlgActiveAcceptButtonImage() {
if (imageDlgActiveAcceptButton == null) {
@ -363,6 +362,22 @@ public enum ImageManagerImpl implements ImageManager {
return imageToggleRecordMacroButton;
}
@Override
public BufferedImage getCardIcon(String resourceName, int size) {
// icon must be same, but color can be changed by themes
InputStream data = ImageManager.class.getResourceAsStream(PreferencesDialog.getCurrentTheme().getCardIconsResourcePath(resourceName));
try {
// no need to resize svg (lib already do it on load)
return SvgUtils.loadSVG(data, "card icon = " + resourceName,
PreferencesDialog.getCurrentTheme().getCardIconsCssFile(),
PreferencesDialog.getCurrentTheme().getCardIconsCssSettings(),
size, size, false);
} catch (Exception e) {
logger.error("Can't load card icon: " + resourceName + " , reason: " + e.getMessage(), e);
return null;
}
}
protected static Image getImageFromResourceTransparent(String path, Color mask, Rectangle rec) {
BufferedImage image;
Image imageCardTransparent;