package org.mage.card.arcane;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.swing.BorderFactory;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import mage.Constants.CardType;
import mage.cards.MagePermanent;
import mage.cards.TextPopup;
import mage.cards.action.ActionCallback;
import mage.cards.action.TransferData;
import mage.components.ImagePanel;
import mage.utils.CardUtil;
import mage.view.AbilityView;
import mage.view.CardView;
import mage.view.PermanentView;
import mage.view.StackAbilityView;
import org.apache.log4j.Logger;
import org.mage.card.arcane.ScaledImagePanel.MultipassType;
import org.mage.card.arcane.ScaledImagePanel.ScalingType;
import org.mage.plugins.card.images.ImageCache;
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
@SuppressWarnings({"unchecked","rawtypes"})
public class CardPanel extends MagePermanent implements MouseListener, MouseMotionListener {
private static final long serialVersionUID = -3272134219262184410L;
private static final Logger log = Logger.getLogger(CardPanel.class);
static public final double TAPPED_ANGLE = Math.PI / 2;
static public final float ASPECT_RATIO = 3.5f / 2.5f;
static public final int POPUP_X_GAP = 1; // prevent popup window from blinking
//static public final float ASPECT_RATIO = 1.0f;
static public CardPanel dragAnimationPanel;
public static final Rectangle CARD_SIZE_FULL = new Rectangle(101, 149);
static private final float ROUNDED_CORNER_SIZE = 0.1f;
//static private final float SELECTED_BORDER_SIZE = 0.01f;
static private final float BLACK_BORDER_SIZE = 0.03f;
static private final int TEXT_GLOW_SIZE = 6;
static private final float TEXT_GLOW_INTENSITY = 3f;
static private final float rotCenterToTopCorner = 1.0295630140987000315797369464196f;
static private final float rotCenterToBottomCorner = 0.7071067811865475244008443621048f;
public CardView gameCard;
public PermanentView gamePermanent;
public CardPanel attachedToPanel;
public List<CardPanel> attachedPanels = new ArrayList();
public double tappedAngle = 0;
public ScaledImagePanel imagePanel;
public ImagePanel overlayPanel;
private GlowText titleText;
private GlowText ptText;
private List<CardPanel> imageLoadListeners = new ArrayList(2);
private boolean displayEnabled = true;
private boolean isAnimationPanel;
private int cardXOffset, cardYOffset, cardWidth, cardHeight;
private boolean isSelected;
private boolean showCastingCost;
private boolean hasImage = false;
private float alpha = 1.0f;
private ActionCallback callback;
protected boolean popupShowing;
protected TextPopup popupText = new TextPopup();
protected UUID gameId;
private TransferData data = new TransferData();
private boolean isPermanent;
private boolean hasSickness;
public CardPanel(CardView newGameCard, UUID gameId, boolean loadImage, ActionCallback callback) {
this.gameCard = newGameCard;
this.callback = callback;
this.gameId = gameId;
this.isPermanent = this.gameCard instanceof PermanentView;
if (isPermanent) {
this.gamePermanent = (PermanentView) this.gameCard;
this.hasSickness = this.gamePermanent.hasSummoningSickness();
}
//for container debug (don't remove)
//setBorder(BorderFactory.createLineBorder(Color.green));
setBackground(Color.black);
setOpaque(false);
addMouseListener(this);
addMouseMotionListener(this);
titleText = new GlowText();
setText(gameCard);
titleText.setFont(getFont().deriveFont(Font.BOLD, 13f));
titleText.setForeground(Color.white);
titleText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY);
titleText.setWrap(true);
add(titleText);
ptText = new GlowText();
if (CardUtil.isCreature(gameCard)) {
ptText.setText(gameCard.getPower() + "/" + gameCard.getToughness());
} else if (CardUtil.isPlaneswalker(gameCard)) {
ptText.setText(gameCard.getLoyalty());
}
ptText.setFont(getFont().deriveFont(Font.BOLD, 13f));
ptText.setForeground(Color.white);
ptText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY);
add(ptText);
BufferedImage sickness = ImageManagerImpl.getInstance().getSicknessImage();
overlayPanel = new ImagePanel(sickness, ImagePanel.SCALED);
overlayPanel.setOpaque(false);
add(overlayPanel);
imagePanel = new ScaledImagePanel();
imagePanel.setBorder(BorderFactory.createLineBorder(Color.white));
add(imagePanel);
imagePanel.setScaleLarger(true);
imagePanel.setScalingType(ScalingType.nearestNeighbor);
imagePanel.setScalingBlur(true);
imagePanel.setScalingMultiPassType(MultipassType.none);
String cardType = getType(newGameCard);
popupText.setText(getText(cardType, newGameCard));
if (!loadImage) return;
Util.threadPool.submit(new Runnable() {
public void run () {
try {
tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0;
BufferedImage srcImage = ImageCache.getImageOriginal(gameCard);
srcImage = ImageCache.getNormalSizeImage(srcImage);
if (srcImage != null) {
hasImage = true;
setText(gameCard);
setImage(srcImage, srcImage);
} else {
log.warn("image wasn't found, card=" + gameCard.getName() + ", set=" + gameCard.getExpansionSetCode() + ", cid=" + gameCard.getCardNumber());
}
} catch (Exception e) {
e.printStackTrace();
} catch (Error err) {
err.printStackTrace();
}
}
});
}
private void setText(CardView card) {
if (hasImage) {
titleText.setText("");
} else {
titleText.setText(card.getName());
}
}
private void setImage (Image srcImage, Image srcImageBlurred) {
synchronized (imagePanel) {
imagePanel.setImage(srcImage, srcImageBlurred);
repaint();
for (CardPanel cardPanel : imageLoadListeners) {
cardPanel.setImage(srcImage, srcImageBlurred);
cardPanel.repaint();
}
imageLoadListeners.clear();
}
layout();
}
public void setImage (final CardPanel panel) {
synchronized (panel.imagePanel) {
if (panel.imagePanel.hasImage())
setImage(panel.imagePanel.srcImage, panel.imagePanel.srcImageBlurred);
else
panel.imageLoadListeners.add(this);
}
}
public void setScalingType (ScalingType scalingType) {
imagePanel.setScalingType(scalingType);
}
public void setDisplayEnabled (boolean displayEnabled) {
this.displayEnabled = displayEnabled;
}
public boolean isDisplayEnabled () {
return displayEnabled;
}
public void setAnimationPanel (boolean isAnimationPanel) {
this.isAnimationPanel = isAnimationPanel;
}
public void setSelected (boolean isSelected) {
this.isSelected = isSelected;
repaint();
}
public void setAttacking (boolean isAttacking) {
//TODO:uncomment
//this.gameCard.setAttacking(isAttacking);
repaint();
}
public boolean getSelected() {
return this.isSelected;
}
public void setShowCastingCost (boolean showCastingCost) {
this.showCastingCost = showCastingCost;
}
public void paint (Graphics g) {
if (!displayEnabled) return;
if (!isValid()) super.validate();
Graphics2D g2d = (Graphics2D)g;
if (tappedAngle > 0) {
g2d = (Graphics2D)g2d.create();
float edgeOffset = cardWidth / 2f;
g2d.rotate(tappedAngle, cardXOffset + edgeOffset, cardYOffset + cardHeight - edgeOffset);
}
super.paint(g2d);
}
protected void paintComponent (Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (alpha != 1.0f) {
AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha);
g2d.setComposite(composite);
}
//TODO:uncomment
if (!hasImage /*&& gameCard.getTableID() > 0*/) {
g2d.setColor(new Color(30,200,200,120));
} else {
g2d.setColor(new Color(0,0,0,200));
}
//for debug repainting
//g2d.setColor(new Color(MyRandom.random.nextInt(255),MyRandom.random.nextInt(255),MyRandom.random.nextInt(255),150));
int cornerSize = Math.max(4, Math.round(cardWidth * ROUNDED_CORNER_SIZE));
g2d.fillRoundRect(cardXOffset, cardYOffset, cardWidth, cardHeight, cornerSize, cornerSize);
if (isSelected) {
//g2d.setColor(new Color(0,250,0,200));
g2d.setColor(new Color(200,120,40,200));
g2d.fillRoundRect(cardXOffset+1, cardYOffset+1, cardWidth-2, cardHeight-2, cornerSize, cornerSize);
}
//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);
}*/
/*if (isSelected) {
g2d.setColor(Color.green);
int offset = gameCard.isTapped() ? 1 : 0;
for (int i = 1, n = Math.max(1, Math.round(cardWidth * SELECTED_BORDER_SIZE)); i <= n; i++)
g2d.drawRoundRect(cardXOffset - i, cardYOffset - i + offset, cardWidth + i * 2 - 1, cardHeight + i * 2 - 1,
cornerSize, cornerSize);
}*/
//for debugging
// REMOVEME
/*
Point component = getLocation();
int cx = getCardX() + component.x;
int cy = getCardY() + component.y;
int cw = getCardWidth();
int ch = getCardHeight();
g2d.setColor(Color.white);
g2d.drawRect(getCardX() - component.x, getCardY() - component.y, cw, ch);
*/
}
protected void paintChildren (Graphics g) {
super.paintChildren(g);
if (showCastingCost && !isAnimationPanel && cardWidth < 200 && cardWidth > 60) {
String manaCost = ManaSymbols.getStringManaCost(gameCard.getManaCost());
int width = ManaSymbols.getWidth(manaCost);
if (hasImage) {
ManaSymbols.draw(g, manaCost, cardXOffset + cardWidth - width - 5, cardYOffset + 5);
} else {
ManaSymbols.draw(g, manaCost, cardXOffset + 8, cardHeight - 9);
}
}
}
public void layout() {
int borderSize = Math.round(cardWidth * BLACK_BORDER_SIZE);
imagePanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize);
imagePanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2);
if (hasSickness && CardUtil.isCreature(gameCard) && isPermanent) {
overlayPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize);
overlayPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2);
} else {
overlayPanel.setVisible(false);
}
int fontHeight = Math.round(cardHeight * (27f / 680));
boolean showText = (!isAnimationPanel && fontHeight < 12);
titleText.setVisible(showText);
ptText.setVisible(showText);
int titleX = Math.round(cardWidth * (20f / 480));
int titleY = Math.round(cardHeight * (9f / 680));
titleText.setBounds(cardXOffset + titleX, cardYOffset + titleY, cardWidth - titleX, cardHeight);
Dimension ptSize = ptText.getPreferredSize();
ptText.setSize(ptSize.width, ptSize.height);
int ptX = Math.round(cardWidth * (420f / 480)) - ptSize.width / 2;
int ptY = Math.round(cardHeight * (675f / 680)) - ptSize.height;
int offsetX = Math.round((CARD_SIZE_FULL.width - cardWidth) / 10.0f);
ptText.setLocation(cardXOffset + ptX - TEXT_GLOW_SIZE / 2 - offsetX, cardYOffset + ptY - TEXT_GLOW_SIZE / 2);
if (isAnimationPanel || cardWidth < 200)
imagePanel.setScalingType(ScalingType.nearestNeighbor);
else
imagePanel.setScalingType(ScalingType.bilinear);
}
public String toString () {
return gameCard.toString();
}
@Override
public void setCardBounds (int x, int y, int width, int height) {
cardWidth = width;
cardHeight = height;
int rotCenterX = Math.round(width / 2f);
int rotCenterY = height - rotCenterX;
int rotCenterToTopCorner = Math.round(width * CardPanel.rotCenterToTopCorner);
int rotCenterToBottomCorner = Math.round(width * CardPanel.rotCenterToBottomCorner);
int xOffset = rotCenterX - rotCenterToBottomCorner;
int yOffset = rotCenterY - rotCenterToTopCorner;
cardXOffset = -xOffset;
cardYOffset = -yOffset;
width = -xOffset + rotCenterX + rotCenterToTopCorner;
height = -yOffset + rotCenterY + rotCenterToBottomCorner;
setBounds(x + xOffset, y + yOffset, width, height);
}
public void repaint () {
Rectangle b = getBounds();
JRootPane rootPane = SwingUtilities.getRootPane(this);
if (rootPane == null) return;
Point p = SwingUtilities.convertPoint(getParent(), b.x, b.y, rootPane);
rootPane.repaint(p.x, p.y, b.width, b.height);
}
public int getCardX () {
return getX() + cardXOffset;
}
public int getCardY () {
return getY() + cardYOffset;
}
public int getCardWidth () {
return cardWidth;
}
public int getCardHeight () {
return cardHeight;
}
public Point getCardLocation () {
Point p = getLocation();
p.x += cardXOffset;
p.y += cardYOffset;
return p;
}
public CardView getCard() {
return this.gameCard;
}
@Override
public void setAlpha(float alpha) {
this.alpha = alpha;
}
public float getAlpha() {
return alpha;
}
public int getCardXOffset() {
return cardXOffset;
}
public int getCardYOffset() {
return cardYOffset;
}
public void updateImage() {
if (!hasImage) {
Util.threadPool.submit(new Runnable() {
public void run () {
//TODO: BufferedImage srcImage = ImageCache.getImageOriginal(gameCard);
//BufferedImage srcImage = null;
//tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0;
/*if (srcImage != null) {
hasImage = true;
setText(gameCard);
setImage(srcImage, srcImage);
}*/
}
});
}
}
@Override
public List<MagePermanent> getLinks() {
List<MagePermanent> list = new ArrayList<MagePermanent>();
if (attachedPanels == null) return list;
for (MagePermanent p : attachedPanels) {
list.add(p);
}
return list;
}
@Override
public boolean isTapped() {
if (isPermanent) {
return ((PermanentView)gameCard).isTapped();
}
return false;
}
@Override
public void onBeginAnimation() {
}
@Override
public void onEndAnimation() {
}
@Override
public void update(CardView card) {
if (isPermanent) {
if (isTapped() != ((PermanentView)card).isTapped()) {
Animation.tapCardToggle(this, this);
}
}
if (CardUtil.isCreature(card) && CardUtil.isPlaneswalker(card)) {
ptText.setText(card.getPower() + "/" + card.getToughness() + " (" + card.getLoyalty() + ")");
} else if (CardUtil.isCreature(card)) {
ptText.setText(card.getPower() + "/" + card.getToughness());
} else if (CardUtil.isPlaneswalker(card)) {
ptText.setText(card.getLoyalty());
} else {
ptText.setText("");
}
setText(card);
this.gameCard = card;
String cardType = getType(card);
popupText.setText(getText(cardType, card));
if (hasSickness && CardUtil.isCreature(gameCard) && isPermanent) {
overlayPanel.setVisible(true);
} else {
overlayPanel.setVisible(false);
}
repaint();
}
@Override
public boolean contains(int x, int y) {
if (containsThis(x, y, true)) return true;
/*
* if (attachedCount > 0) { for (MWCardImpl card :
* mwAttachedCards.keySet()) { if (card.contains(x, y)) return true; } }
*/
return false;
}
public boolean containsThis(int x, int y, boolean root) {
//log.info("x="+x+", y="+y);
Point component = getLocation();
//int dy = component.y;
//if (root) dy = 0;
int cx = getCardX() - component.x;
int cy = getCardY() - component.y;
int cw = getCardWidth();
int ch = getCardHeight();
if (isTapped()) {
cy = ch - cw + cx /*+ attachedDy*attachedCount*/;
ch = cw;
cw = getCardHeight();
}
//int dx = drawIcons ? 19 : 0;
//int dx = 0;
if (x >= cx && x <= cx + cw && y >= cy && y <= cy + ch) {
//log.info("!cx="+cx+", cy="+cy+", dx="+cw +", ch="+ch);
//log.info(getOriginal().getId());
return true;
} else {
//log.info("cx="+cx+", cy="+cy+", dx="+cw +", ch="+ch);
}
return false;
}
@Override
public CardView getOriginal() {
return this.gameCard;
}
@Override
public Image getImage() {
return ImageCache.getImageOriginal(gameCard);
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
if (!popupShowing) {
synchronized (this) {
if (!popupShowing) {
popupShowing = true;
callback.mouseEntered(e, getTransferDataForMouseEntered());
}
}
}
}
@Override
public void mouseDragged(MouseEvent e) {}
@Override
public void mouseMoved(MouseEvent e) {
data.component = this;
callback.mouseMoved(e, data);
}
@Override
public void mouseExited(MouseEvent e) {
if(getMousePosition(true) != null) return;
if (popupShowing) {
synchronized (this) {
if (popupShowing) {
popupShowing = false;
data.component = this;
data.card = this.gameCard;
data.popupText = popupText;
callback.mouseExited(e, data);
}
}
}
}
@Override
public void mousePressed(MouseEvent e) {
data.component = this;
data.card = this.gameCard;
data.gameId = this.gameId;
callback.mousePressed(e, data);
}
@Override
public void mouseReleased(MouseEvent e) {}
/**
* Prepares data to be sent to action callback on client side.
*
* @return
*/
private TransferData getTransferDataForMouseEntered() {
data.component = this;
data.card = this.gameCard;
data.popupText = popupText;
data.popupOffsetX = isTapped() ? cardHeight + cardXOffset + POPUP_X_GAP : cardWidth + cardXOffset + POPUP_X_GAP;
data.popupOffsetY = 40;
data.locationOnScreen = this.getLocationOnScreen();
return data;
}
protected String getType(CardView card) {
StringBuilder sbType = new StringBuilder();
for (String superType: card.getSuperTypes()) {
sbType.append(superType).append(" ");
}
for (mage.Constants.CardType cardType: card.getCardTypes()) {
sbType.append(cardType.toString()).append(" ");
}
if (card.getSubTypes().size() > 0) {
sbType.append("- ");
for (String subType: card.getSubTypes()) {
sbType.append(subType).append(" ");
}
}
return sbType.toString();
}
protected String getText(String cardType, CardView card) {
StringBuilder sb = new StringBuilder();
if (card instanceof StackAbilityView || card instanceof AbilityView) {
for (String rule: card.getRules()) {
sb.append("\n").append(rule);
}
}
else {
sb.append(card.getName());
if (card.getManaCost().size() > 0) {
sb.append("\n").append(card.getManaCost());
}
sb.append("\n").append(cardType);
if (card.getColor().hasColor()) {
sb.append("\n").append(card.getColor().toString());
}
if (card.getCardTypes().contains(CardType.CREATURE)) {
sb.append("\n").append(card.getPower()).append("/").append(card.getToughness());
}
else if (card.getCardTypes().contains(CardType.PLANESWALKER)) {
sb.append("\n").append(card.getLoyalty());
}
for (String rule: card.getRules()) {
sb.append("\n").append(rule);
}
if (card.getExpansionSetCode() != null && card.getExpansionSetCode().length() > 0) {
sb.append("\n").append(card.getCardNumber()).append(" - ");
//sb.append(Sets.getInstance().get(card.getExpansionSetCode()).getName()).append(" - ");
sb.append(card.getExpansionSetCode()).append(" - ");
sb.append(card.getRarity().toString());
}
}
// sb.append("\n").append(card.getId());
return sb.toString();
}
@Override
public void update(PermanentView card) {
update((CardView)card);
this.hasSickness = card.hasSummoningSickness();
}
@Override
public PermanentView getOriginalPermanent() {
throw new IllegalStateException("Is not permanent.");
}
}