From 720a4457fdd04251e26f1609f25cd04828b84962 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 3 Sep 2016 10:52:14 +0200 Subject: [PATCH] * Fixed hybrid mana symbol display for characteristic-based card rendering. Removed not used import statements. --- .../java/org/mage/card/arcane/CardPanel.java | 80 +- .../mage/card/arcane/CardPanelRenderImpl.java | 795 +++++++++--------- .../org/mage/card/arcane/CardRenderer.java | 756 ++++++++--------- .../java/org/mage/card/arcane/GlowText.java | 29 +- .../mage/card/arcane/ModernCardRenderer.java | 537 ++++++------ .../mage/card/arcane/TextboxLevelRule.java | 56 +- .../mage/card/arcane/TextboxLoyaltyRule.java | 66 +- .../org/mage/card/arcane/TextboxRule.java | 192 ++--- .../mage/card/arcane/TextboxRuleParser.java | 502 +++++------ 9 files changed, 1490 insertions(+), 1523 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java index 72f4afc1cd..23520e6a83 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java @@ -2,7 +2,6 @@ package org.mage.card.arcane; import java.awt.Color; import java.awt.Dimension; -import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; @@ -20,8 +19,6 @@ import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import javax.swing.Box; -import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JPanel; @@ -30,7 +27,6 @@ import mage.cards.MagePermanent; import mage.cards.TextPopup; import mage.cards.action.ActionCallback; import mage.cards.action.TransferData; -import mage.client.components.layout.RelativeLayout; import mage.client.plugins.adapters.MageActionCallback; import mage.client.plugins.impl.Plugins; import mage.client.util.audio.AudioManager; @@ -75,7 +71,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, public double flippedAngle = 0; private final List links = new ArrayList<>(); - + public JPanel buttonPanel; private JButton dayNightButton; private JButton showCopySourceButton; @@ -118,7 +114,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, this.gameCard = newGameCard; this.callback = callback; this.gameId = gameId; - + // Gather info about the card this.isPermanent = this.gameCard instanceof PermanentView; if (isPermanent) { @@ -127,14 +123,14 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, // Set to requested size this.setCardBounds(0, 0, dimension.width, dimension.height); - + // Create button panel for Transform and Show Source (copied cards) buttonPanel = new JPanel(); buttonPanel.setLayout(null); buttonPanel.setOpaque(false); buttonPanel.setVisible(true); add(buttonPanel); - + // Both card rendering implementations have a transform button if (this.gameCard.canTransform()) { // Create the day night button @@ -155,7 +151,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, Animation.transformCard(CardPanel.this, CardPanel.this, true); } }); - + // Add it buttonPanel.add(dayNightButton); } @@ -199,14 +195,14 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; } - + @Override public void doLayout() { // Position transform and show source buttons buttonPanel.setLocation(cardXOffset, cardYOffset); buttonPanel.setSize(cardWidth, cardHeight); - int x = cardWidth/20; - int y = cardHeight/10; + int x = cardWidth / 20; + int y = cardHeight / 10; if (dayNightButton != null) { dayNightButton.setLocation(x, y); y += 25; @@ -216,9 +212,9 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, showCopySourceButton.setLocation(x, y); } } - + public final void initialDraw() { - // Kick off + // Kick off if (gameCard.isTransformed()) { // this calls updateImage toggleTransformed(); @@ -246,8 +242,8 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, this.callback = null; this.data = null; } - - // Copy the graphical resources of another CardPanel over to this one, + + // Copy the graphical resources of another CardPanel over to this one, // if possible (may not be possible if they have different implementations) // Used when cards are moving between zones public abstract void transferResources(CardPanel panel); @@ -273,7 +269,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, public void setAnimationPanel(boolean isAnimationPanel) { this.isAnimationPanel = isAnimationPanel; } - + public boolean isAnimationPanel() { return this.isAnimationPanel; } @@ -282,7 +278,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, public void setSelected(boolean isSelected) { this.isSelected = isSelected; } - + public boolean isSelected() { return this.isSelected; } @@ -291,16 +287,16 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, public List getLinks() { return links; } - + @Override public void setChoosable(boolean isChoosable) { this.isChoosable = isChoosable; } - + public boolean isChoosable() { return this.isChoosable; } - + public boolean hasSickness() { return this.hasSickness; } @@ -308,7 +304,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, public boolean isPermanent() { return this.isPermanent; } - + @Override public void setCardAreaRef(JPanel cardArea) { this.cardArea = cardArea; @@ -317,13 +313,13 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, public void setShowCastingCost(boolean showCastingCost) { this.showCastingCost = showCastingCost; } - + public boolean getShowCastingCost() { return this.showCastingCost; } - + /** - * Overridden by different card rendering styles + * Overridden by different card rendering styles */ protected abstract void paintCard(Graphics2D g); @@ -356,7 +352,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, // Deferr to subclasses paintCard(g2d); - + // Done, dispose of the context g2d.dispose(); } @@ -372,7 +368,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, setBounds(x - cardXOffset, y - cardYOffset, getWidth(), getHeight()); return; } - + this.cardWidth = cardWidth; this.symbolWidth = cardWidth / 7; this.cardHeight = cardHeight; @@ -436,7 +432,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, public final int getCardHeight() { return cardHeight; } - + public final int getSymbolWidth() { return symbolWidth; } @@ -510,15 +506,15 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, } /** - * Inheriting classes should implement update(CardView card) by - * using this. However, they should ALSO call repaint() after the superclass - * call to this function, that can't be done here as the overriders may need - * to do things both before and after this call before repainting. + * Inheriting classes should implement update(CardView card) by using this. + * However, they should ALSO call repaint() after the superclass call to + * this function, that can't be done here as the overriders may need to do + * things both before and after this call before repainting. */ @Override public void update(CardView card) { this.updateCard = card; - + // Animation update if (isPermanent && (card instanceof PermanentView)) { boolean needsTapping = isTapped() != ((PermanentView) card).isTapped(); @@ -538,11 +534,11 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, // Update panel attributes this.isChoosable = card.isChoosable(); this.isSelected = card.isSelected(); - + // Update art? - boolean mustUpdateArt = - (!gameCard.getName().equals(card.getName())) || - (gameCard.isFaceDown() != card.isFaceDown()); + boolean mustUpdateArt + = (!gameCard.getName().equals(card.getName())) + || (gameCard.isFaceDown() != card.isFaceDown()); // Set the new card this.gameCard = card; @@ -550,12 +546,12 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, // Update tooltip text String cardType = getType(card); tooltipText.setText(getText(cardType, card)); - + // Update the image if (mustUpdateArt) { updateArtImage(); } - + // Update transform circle if (card.canTransform()) { BufferedImage transformIcon; @@ -742,13 +738,13 @@ 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); + update((CardView) card); } @Override public PermanentView getOriginalPermanent() { if (isPermanent) { - return (PermanentView)this.gameCard; + return (PermanentView) this.gameCard; } throw new IllegalStateException("Is not permanent."); } @@ -835,7 +831,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, public void setTextOffset(int yOffset) { yTextOffset = yOffset; } - + public int getTextOffset() { return yTextOffset; } diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java index 900bff8f42..e84d2aec7c 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java @@ -1,398 +1,397 @@ -package org.mage.card.arcane; - -import com.google.common.base.Function; -import com.google.common.collect.MapMaker; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.RenderingHints; -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.Map; -import java.util.UUID; -import mage.cards.action.ActionCallback; -import mage.client.util.ImageCaches; -import mage.constants.CardType; -import mage.view.CardView; -import mage.view.CounterView; -import mage.view.PermanentView; -import mage.view.StackAbilityView; -import net.java.truevfs.access.TFile; -import org.apache.log4j.Logger; -import org.jdesktop.swingx.graphics.GraphicsUtilities; -import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL; -import org.mage.plugins.card.dl.sources.DirectLinksForDownload; -import org.mage.plugins.card.images.ImageCache; - -public class CardPanelRenderImpl extends CardPanel { - - private static final Logger LOGGER = Logger.getLogger(CardPanelRenderImpl.class); - - private static boolean cardViewEquals(CardView a, CardView b) { - if (a == b) { - return true; - } - if (a.getClass() != b.getClass()) { - return false; - } - if (!a.getName().equals(b.getName())) { - return false; - } - if (!a.getPower().equals(b.getPower())) { - return false; - } - if (!a.getToughness().equals(b.getToughness())) { - return false; - } - if (!a.getLoyalty().equals(b.getLoyalty())) { - return false; - } - if (0 != a.getColor().compareTo(b.getColor())) { - return false; - } - if (!a.getCardTypes().equals(b.getCardTypes())) { - return false; - } - if (!a.getSubTypes().equals(b.getSubTypes())) { - return false; - } - if (!a.getSuperTypes().equals(b.getSuperTypes())) { - return false; - } - if (!a.getManaCost().equals(b.getManaCost())) { - return false; - } - if (!a.getRules().equals(b.getRules())) { - return false; - } - if (!a.getExpansionSetCode().equals(b.getExpansionSetCode())) { - return false; - } - if (a.getCounters() == null) { - if (b.getCounters() != null) { - return false; - } - } else if (!a.getCounters().equals(b.getCounters())) { - return false; - } - if (a.isFaceDown() != b.isFaceDown()) { - return false; - } - if ((a instanceof PermanentView)) { - PermanentView aa = (PermanentView)a; - PermanentView bb = (PermanentView)b; - if (aa.hasSummoningSickness() != bb.hasSummoningSickness()) { - // Note: b must be a permanentview too as we aleady checked that classes - // are the same for a and b - return false; - } - if (aa.getDamage() != bb.getDamage()) { - return false; - } - } - return true; - } - - class ImageKey { - final BufferedImage artImage; - final int width; - final int height; - final boolean isChoosable; - final boolean isSelected; - final CardView view; - final int hashCode; - - public ImageKey(CardView view, BufferedImage artImage, int width, int height, boolean isChoosable, boolean isSelected) { - this.view = view; - this.artImage = artImage; - this.width = width; - this.height = height; - this.isChoosable = isChoosable; - this.isSelected = isSelected; - this.hashCode = hashCodeImpl(); - } - - private int hashCodeImpl() { - StringBuilder sb = new StringBuilder(); - sb.append((char)(artImage != null ? 1 : 0)); - sb.append((char)width); - sb.append((char)height); - sb.append((char)(isSelected ? 1 : 0)); - sb.append((char)(isChoosable ? 1 : 0)); - sb.append((char)(this.view.isPlayable() ? 1 : 0)); - sb.append((char)(this.view.isCanAttack() ? 1 : 0)); - sb.append((char)(this.view.isFaceDown() ? 1 : 0)); - if (this.view instanceof PermanentView) { - sb.append((char)(((PermanentView)this.view).hasSummoningSickness() ? 1 : 0)); - sb.append((char)(((PermanentView)this.view).getDamage())); - } - sb.append(this.view.getName()); - sb.append(this.view.getPower()); - sb.append(this.view.getToughness()); - sb.append(this.view.getLoyalty()); - sb.append(this.view.getColor().toString()); - sb.append(this.view.getExpansionSetCode()); - for (CardType type: this.view.getCardTypes()) { - sb.append((char)type.ordinal()); - } - for (String s: this.view.getSuperTypes()) { - sb.append(s); - } - for (String s: this.view.getSubTypes()) { - sb.append(s); - } - for (String s: this.view.getManaCost()) { - sb.append(s); - } - for (String s: this.view.getRules()) { - sb.append(s); - } - if (this.view.getCounters() != null) { - for (CounterView v: this.view.getCounters()) { - sb.append(v.getName()).append(v.getCount()); - } - } - return sb.toString().hashCode(); - } - - @Override - public int hashCode() { - return hashCode; - } - - @Override - public boolean equals(Object object) { - // Initial checks - if (this == object) { - return true; - } - if (object == null) { - return false; - } - if (!(object instanceof ImageKey)) { - return false; - } - final ImageKey other = (ImageKey)object; - - // Compare - if ((artImage != null) != (other.artImage != null)) { - return false; - } - if (width != other.width) { - return false; - } - if (height != other.height) { - return false; - } - if (isChoosable != other.isChoosable) { - return false; - } - if (isSelected != other.isSelected) { - return false; - } - return cardViewEquals(view, other.view); - } - } - - // Map of generated images - private final static Map IMAGE_CACHE = new MapMaker().softValues().makeMap(); - - // The art image for the card, loaded in from the disk - private BufferedImage artImage; - - // The rendered card image, with or without the art image loaded yet - // = null while invalid - private BufferedImage cardImage; - private CardRenderer cardRenderer; - - public CardPanelRenderImpl(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, final boolean foil, Dimension dimension) { - // Call to super - super(newGameCard, gameId, loadImage, callback, foil, dimension); - - // Renderer - cardRenderer = new ModernCardRenderer(gameCard, isTransformed()); - - // Draw the parts - initialDraw(); - } - - @Override - public void transferResources(CardPanel panel) { - if (panel instanceof CardPanelRenderImpl) { - CardPanelRenderImpl impl = (CardPanelRenderImpl)panel; - - // Use the art image and current rendered image from the card - artImage = impl.artImage; - cardRenderer.setArtImage(artImage); - cardImage = impl.cardImage; - } - } - - @Override - protected void paintCard(Graphics2D g) { - // Render the card if we don't have an image ready to use - if (cardImage == null) { - // Try to get card image from cache based on our card characteristics - ImageKey key = - new ImageKey(gameCard, artImage, - getCardWidth(), getCardHeight(), - isChoosable(), isSelected()); - cardImage = IMAGE_CACHE.get(key); - - // No cached copy exists? Render one and cache it - if (cardImage == null) { - cardImage = renderCard(); - IMAGE_CACHE.put(key, cardImage); - } - } - - // And draw the image we now have - g.drawImage(cardImage, getCardXOffset(), getCardYOffset(), null); - } - - /** - * Render the card to a new BufferedImage at it's current dimensions - * @return - */ - private BufferedImage renderCard() { - int cardWidth = getCardWidth(); - int cardHeight = getCardHeight(); - - // Create image to render to - BufferedImage image = - GraphicsUtilities.createCompatibleTranslucentImage(cardWidth, cardHeight); - Graphics2D g2d = image.createGraphics(); - - // Render with Antialialsing - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - // Attributes - CardPanelAttributes attribs = - new CardPanelAttributes(cardWidth, cardHeight, isChoosable(), isSelected()); - - // Draw card itself - cardRenderer.draw(g2d, attribs); - - // Done - g2d.dispose(); - return image; - } - - private int updateArtImageStamp; - @Override - public void updateArtImage() { - // Invalidate - artImage = null; - cardImage = null; - cardRenderer.setArtImage(null); - - // Stop animation - tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; - flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; - - // Schedule a repaint - repaint(); - - // See if the image is already loaded - //artImage = ImageCache.tryGetImage(gameCard, getCardWidth(), getCardHeight()); - //this.cardRenderer.setArtImage(artImage); - - // Submit a task to draw with the card art when it arrives - if (artImage == null) { - final int stamp = ++updateArtImageStamp; - Util.threadPool.submit(new Runnable() { - @Override - public void run() { - try { - final BufferedImage srcImage; - if (gameCard.isFaceDown()) { - // Nothing to do - srcImage = null; - } else if (getCardWidth() > THUMBNAIL_SIZE_FULL.width) { - srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight()); - } else { - srcImage = ImageCache.getThumbnail(gameCard); - } - UI.invokeLater(new Runnable() { - @Override - public void run() { - if (stamp == updateArtImageStamp) { - artImage = srcImage; - cardRenderer.setArtImage(srcImage); - if (srcImage != null) { - // Invalidate and repaint - cardImage = null; - repaint(); - } - } - } - }); - } catch (Exception e) { - e.printStackTrace(); - } catch (Error err) { - err.printStackTrace(); - } - } - }); - } - } - - @Override - public void update(CardView card) { - // Update super - super.update(card); - - // Update renderer - cardImage = null; - cardRenderer = new ModernCardRenderer(gameCard, isTransformed()); - cardRenderer.setArtImage(artImage); - - // Repaint - repaint(); - } - - @Override - public void setCardBounds(int x, int y, int cardWidth, int cardHeight) { - int oldCardWidth = getCardWidth(); - int oldCardHeight = getCardHeight(); - - super.setCardBounds(x, y, cardWidth, cardHeight); - - // Rerender if card size changed - if (getCardWidth() != oldCardWidth || getCardHeight() != oldCardHeight) { - cardImage = null; - } - } - - private BufferedImage getFaceDownImage() { - if (isPermanent()) { - if (((PermanentView) gameCard).isMorphed()) { - return ImageCache.getMorphImage(); - } else { - return ImageCache.getManifestImage(); - } - } else if (this.gameCard instanceof StackAbilityView) { - return ImageCache.getMorphImage(); - } else { - return ImageCache.loadImage(new TFile(DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename)); - } - } - - @Override - public Image getImage() { - if (artImage != null) { - if (gameCard.isFaceDown()) { - return getFaceDownImage(); - } else { - return ImageCache.getImageOriginal(gameCard); - } - } - return null; - } - - @Override - public void showCardTitle() { - // Nothing to do, rendered cards always have a title - } -} \ No newline at end of file +package org.mage.card.arcane; + +import com.google.common.collect.MapMaker; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.Map; +import java.util.UUID; +import mage.cards.action.ActionCallback; +import mage.constants.CardType; +import mage.view.CardView; +import mage.view.CounterView; +import mage.view.PermanentView; +import mage.view.StackAbilityView; +import net.java.truevfs.access.TFile; +import org.apache.log4j.Logger; +import org.jdesktop.swingx.graphics.GraphicsUtilities; +import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL; +import org.mage.plugins.card.dl.sources.DirectLinksForDownload; +import org.mage.plugins.card.images.ImageCache; + +public class CardPanelRenderImpl extends CardPanel { + + private static final Logger LOGGER = Logger.getLogger(CardPanelRenderImpl.class); + + private static boolean cardViewEquals(CardView a, CardView b) { + if (a == b) { + return true; + } + if (a.getClass() != b.getClass()) { + return false; + } + if (!a.getName().equals(b.getName())) { + return false; + } + if (!a.getPower().equals(b.getPower())) { + return false; + } + if (!a.getToughness().equals(b.getToughness())) { + return false; + } + if (!a.getLoyalty().equals(b.getLoyalty())) { + return false; + } + if (0 != a.getColor().compareTo(b.getColor())) { + return false; + } + if (!a.getCardTypes().equals(b.getCardTypes())) { + return false; + } + if (!a.getSubTypes().equals(b.getSubTypes())) { + return false; + } + if (!a.getSuperTypes().equals(b.getSuperTypes())) { + return false; + } + if (!a.getManaCost().equals(b.getManaCost())) { + return false; + } + if (!a.getRules().equals(b.getRules())) { + return false; + } + if (!a.getExpansionSetCode().equals(b.getExpansionSetCode())) { + return false; + } + if (a.getCounters() == null) { + if (b.getCounters() != null) { + return false; + } + } else if (!a.getCounters().equals(b.getCounters())) { + return false; + } + if (a.isFaceDown() != b.isFaceDown()) { + return false; + } + if ((a instanceof PermanentView)) { + PermanentView aa = (PermanentView) a; + PermanentView bb = (PermanentView) b; + if (aa.hasSummoningSickness() != bb.hasSummoningSickness()) { + // Note: b must be a permanentview too as we aleady checked that classes + // are the same for a and b + return false; + } + if (aa.getDamage() != bb.getDamage()) { + return false; + } + } + return true; + } + + class ImageKey { + + final BufferedImage artImage; + final int width; + final int height; + final boolean isChoosable; + final boolean isSelected; + final CardView view; + final int hashCode; + + public ImageKey(CardView view, BufferedImage artImage, int width, int height, boolean isChoosable, boolean isSelected) { + this.view = view; + this.artImage = artImage; + this.width = width; + this.height = height; + this.isChoosable = isChoosable; + this.isSelected = isSelected; + this.hashCode = hashCodeImpl(); + } + + private int hashCodeImpl() { + StringBuilder sb = new StringBuilder(); + sb.append((char) (artImage != null ? 1 : 0)); + sb.append((char) width); + sb.append((char) height); + sb.append((char) (isSelected ? 1 : 0)); + sb.append((char) (isChoosable ? 1 : 0)); + sb.append((char) (this.view.isPlayable() ? 1 : 0)); + sb.append((char) (this.view.isCanAttack() ? 1 : 0)); + sb.append((char) (this.view.isFaceDown() ? 1 : 0)); + if (this.view instanceof PermanentView) { + sb.append((char) (((PermanentView) this.view).hasSummoningSickness() ? 1 : 0)); + sb.append((char) (((PermanentView) this.view).getDamage())); + } + sb.append(this.view.getName()); + sb.append(this.view.getPower()); + sb.append(this.view.getToughness()); + sb.append(this.view.getLoyalty()); + sb.append(this.view.getColor().toString()); + sb.append(this.view.getExpansionSetCode()); + for (CardType type : this.view.getCardTypes()) { + sb.append((char) type.ordinal()); + } + for (String s : this.view.getSuperTypes()) { + sb.append(s); + } + for (String s : this.view.getSubTypes()) { + sb.append(s); + } + for (String s : this.view.getManaCost()) { + sb.append(s); + } + for (String s : this.view.getRules()) { + sb.append(s); + } + if (this.view.getCounters() != null) { + for (CounterView v : this.view.getCounters()) { + sb.append(v.getName()).append(v.getCount()); + } + } + return sb.toString().hashCode(); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object object) { + // Initial checks + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (!(object instanceof ImageKey)) { + return false; + } + final ImageKey other = (ImageKey) object; + + // Compare + if ((artImage != null) != (other.artImage != null)) { + return false; + } + if (width != other.width) { + return false; + } + if (height != other.height) { + return false; + } + if (isChoosable != other.isChoosable) { + return false; + } + if (isSelected != other.isSelected) { + return false; + } + return cardViewEquals(view, other.view); + } + } + + // Map of generated images + private final static Map IMAGE_CACHE = new MapMaker().softValues().makeMap(); + + // The art image for the card, loaded in from the disk + private BufferedImage artImage; + + // The rendered card image, with or without the art image loaded yet + // = null while invalid + private BufferedImage cardImage; + private CardRenderer cardRenderer; + + public CardPanelRenderImpl(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, final boolean foil, Dimension dimension) { + // Call to super + super(newGameCard, gameId, loadImage, callback, foil, dimension); + + // Renderer + cardRenderer = new ModernCardRenderer(gameCard, isTransformed()); + + // Draw the parts + initialDraw(); + } + + @Override + public void transferResources(CardPanel panel) { + if (panel instanceof CardPanelRenderImpl) { + CardPanelRenderImpl impl = (CardPanelRenderImpl) panel; + + // Use the art image and current rendered image from the card + artImage = impl.artImage; + cardRenderer.setArtImage(artImage); + cardImage = impl.cardImage; + } + } + + @Override + protected void paintCard(Graphics2D g) { + // Render the card if we don't have an image ready to use + if (cardImage == null) { + // Try to get card image from cache based on our card characteristics + ImageKey key + = new ImageKey(gameCard, artImage, + getCardWidth(), getCardHeight(), + isChoosable(), isSelected()); + cardImage = IMAGE_CACHE.get(key); + + // No cached copy exists? Render one and cache it + if (cardImage == null) { + cardImage = renderCard(); + IMAGE_CACHE.put(key, cardImage); + } + } + + // And draw the image we now have + g.drawImage(cardImage, getCardXOffset(), getCardYOffset(), null); + } + + /** + * Render the card to a new BufferedImage at it's current dimensions + * + * @return + */ + private BufferedImage renderCard() { + int cardWidth = getCardWidth(); + int cardHeight = getCardHeight(); + + // Create image to render to + BufferedImage image + = GraphicsUtilities.createCompatibleTranslucentImage(cardWidth, cardHeight); + Graphics2D g2d = image.createGraphics(); + + // Render with Antialialsing + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // Attributes + CardPanelAttributes attribs + = new CardPanelAttributes(cardWidth, cardHeight, isChoosable(), isSelected()); + + // Draw card itself + cardRenderer.draw(g2d, attribs); + + // Done + g2d.dispose(); + return image; + } + + private int updateArtImageStamp; + + @Override + public void updateArtImage() { + // Invalidate + artImage = null; + cardImage = null; + cardRenderer.setArtImage(null); + + // Stop animation + tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; + flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; + + // Schedule a repaint + repaint(); + + // See if the image is already loaded + //artImage = ImageCache.tryGetImage(gameCard, getCardWidth(), getCardHeight()); + //this.cardRenderer.setArtImage(artImage); + // Submit a task to draw with the card art when it arrives + if (artImage == null) { + final int stamp = ++updateArtImageStamp; + Util.threadPool.submit(new Runnable() { + @Override + public void run() { + try { + final BufferedImage srcImage; + if (gameCard.isFaceDown()) { + // Nothing to do + srcImage = null; + } else if (getCardWidth() > THUMBNAIL_SIZE_FULL.width) { + srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight()); + } else { + srcImage = ImageCache.getThumbnail(gameCard); + } + UI.invokeLater(new Runnable() { + @Override + public void run() { + if (stamp == updateArtImageStamp) { + artImage = srcImage; + cardRenderer.setArtImage(srcImage); + if (srcImage != null) { + // Invalidate and repaint + cardImage = null; + repaint(); + } + } + } + }); + } catch (Exception e) { + e.printStackTrace(); + } catch (Error err) { + err.printStackTrace(); + } + } + }); + } + } + + @Override + public void update(CardView card) { + // Update super + super.update(card); + + // Update renderer + cardImage = null; + cardRenderer = new ModernCardRenderer(gameCard, isTransformed()); + cardRenderer.setArtImage(artImage); + + // Repaint + repaint(); + } + + @Override + public void setCardBounds(int x, int y, int cardWidth, int cardHeight) { + int oldCardWidth = getCardWidth(); + int oldCardHeight = getCardHeight(); + + super.setCardBounds(x, y, cardWidth, cardHeight); + + // Rerender if card size changed + if (getCardWidth() != oldCardWidth || getCardHeight() != oldCardHeight) { + cardImage = null; + } + } + + private BufferedImage getFaceDownImage() { + if (isPermanent()) { + if (((PermanentView) gameCard).isMorphed()) { + return ImageCache.getMorphImage(); + } else { + return ImageCache.getManifestImage(); + } + } else if (this.gameCard instanceof StackAbilityView) { + return ImageCache.getMorphImage(); + } else { + return ImageCache.loadImage(new TFile(DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename)); + } + } + + @Override + public Image getImage() { + if (artImage != null) { + if (gameCard.isFaceDown()) { + return getFaceDownImage(); + } else { + return ImageCache.getImageOriginal(gameCard); + } + } + return null; + } + + @Override + public void showCardTitle() { + // Nothing to do, rendered cards always have a title + } +} diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java index b70ab56e72..53b6aa0741 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java @@ -1,382 +1,374 @@ -/* - * 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; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.Paint; -import java.awt.Polygon; -import java.awt.Rectangle; -import java.awt.image.BufferedImage; -import java.text.AttributedString; -import java.util.ArrayList; -import java.util.List; -import mage.client.dialog.PreferencesDialog; -import mage.constants.AbilityType; -import mage.constants.CardType; -import mage.constants.Rarity; -import mage.counters.Counter; -import mage.utils.CardUtil; -import mage.view.CardView; -import mage.view.CounterView; -import mage.view.PermanentView; -import org.apache.log4j.Logger; - -/** - * @author stravant@gmail.com - * - * Common base class for card renderers for each card frame / card type. - * - * Follows the template method pattern to implement a new renderer, implement - * the following methods (they are called in the following order): - * - * * drawBorder() - * Draws the outermost border of the card, white border or black border - * - * * drawBackground() - * Draws the background texture / color of the card - * - * * drawArt() - * Draws the card's art - * - * * drawFrame() - * Draws the card frame (over the art and background) - * - * * drawOverlays() - * Draws summoning sickness and possible other overlays - * - * * drawCounters() - * Draws counters on the card, such as +1/+1 and -1/-1 counters - * - * Predefined methods that the implementations can use: - * - * * drawRules(font, bounding box) - * - * * drawNameLine(font, bounding box) - * - * * drawTypeLine(font, bounding box) - * - */ -public abstract class CardRenderer { - private static final Logger LOGGER = Logger.getLogger(CardPanel.class); - - /////////////////////////////////////////////////////////////////////////// - // Common layout metrics between all cards - - // The card to be rendered - protected final CardView cardView; - - // Is the card transformed? - protected final boolean isTransformed; - - // The card image - protected BufferedImage artImage; - - /////////////////////////////////////////////////////////////////////////// - // Common layout metrics between all cards - - // Polygons for counters - private static final Polygon PLUS_COUNTER_POLY = new Polygon(new int[]{ - 0, 5, 10, 10, 5, 0 - }, new int[]{ - 3, 0, 3, 10, 9, 10 - }, 6); - private static final Polygon MINUS_COUNTER_POLY = new Polygon(new int[]{ - 0, 5, 10, 10, 5, 0 - }, new int[]{ - 0, 1, 0, 7, 10, 7 - }, 6); - private static final Polygon TIME_COUNTER_POLY = new Polygon(new int[]{ - 0, 10, 8, 10, 0, 2 - }, new int[]{ - 0, 0, 5, 10, 10, 5 - }, 6); - private static final Polygon OTHER_COUNTER_POLY = new Polygon(new int[]{ - 1, 9, 9, 1 - }, new int[]{ - 1, 1, 9, 9 - }, 4); - - // Paint for a card back - public static Paint BG_TEXTURE_CARDBACK = new Color(153, 102, 51); - - // The size of the card - protected int cardWidth; - protected int cardHeight; - - // Is it selectable / selected - protected boolean isChoosable; - protected boolean isSelected; - - // Radius of the corners of the cards - protected static float CORNER_RADIUS_FRAC = 0.1f; //x cardWidth - protected static int CORNER_RADIUS_MIN = 3; - protected int cornerRadius; - - // The inset of the actual card from the black / white border around it - protected static float BORDER_WIDTH_FRAC = 0.03f; //x cardWidth - protected static float BORDER_WIDTH_MIN = 2; - protected int borderWidth; - - // The parsed text of the card - protected ArrayList textboxRules = new ArrayList<>(); - protected ArrayList textboxKeywords = new ArrayList<>(); - - // The Construtor - // The constructor should prepare all of the things that it can - // without knowing the dimensions that the card will be rendered at. - // Then, the CardRenderer can be called on multiple times to render the - // card at various sizes (for instance, during animation) - public CardRenderer(CardView card, boolean isTransformed) { - // Set base parameters - this.cardView = card; - this.isTransformed = isTransformed; - - // Translate the textbox text - for (String rule: card.getRules()) { - // Kill reminder text - if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_REMINDER_TEXT, "false").equals("false")) { - rule = CardRendererUtils.killReminderText(rule).trim(); - } - if (!rule.isEmpty()) { - TextboxRule tbRule = TextboxRuleParser.parse(card, rule); - if (tbRule.type == TextboxRuleType.SIMPLE_KEYWORD) { - textboxKeywords.add(tbRule); - } else if (tbRule.text.isEmpty()) { - // Nothing to do, rule is empty - } else { - textboxRules.add(tbRule); - } - } - } - } - - // Layout operation - // Calculate common layout metrics that will be used by several - // of the operations in the template method. - protected void layout(int cardWidth, int cardHeight) { - // Store the dimensions for the template methods to use - this.cardWidth = cardWidth; - this.cardHeight = cardHeight; - - // Corner radius and border width - cornerRadius = (int)Math.max( - CORNER_RADIUS_MIN, - CORNER_RADIUS_FRAC * cardWidth); - - borderWidth = (int)Math.max( - BORDER_WIDTH_MIN, - BORDER_WIDTH_FRAC * cardWidth); - } - - // The Draw Method - // The draw method takes the information caculated by the constructor - // and uses it to draw to a concrete size of card and graphics. - public void draw(Graphics2D g, CardPanelAttributes attribs) { - // Pre template method layout, to calculate shared layout info - layout(attribs.cardWidth, attribs.cardHeight); - isSelected = attribs.isSelected; - isChoosable = attribs.isChoosable; - - // Call the template methods - drawBorder(g); - drawBackground(g); - drawArt(g); - drawFrame(g); - if (!cardView.isAbility()) { - drawOverlays(g); - drawCounters(g); - } - } - - // Template methods to be implemented by sub classes - // For instance, for the Modern vs Old border card frames - protected abstract void drawBorder(Graphics2D g); - protected abstract void drawBackground(Graphics2D g); - protected abstract void drawArt(Graphics2D g); - protected abstract void drawFrame(Graphics2D g); - - // Template methods that are possible to override, but unlikely to be - // overridden. - - // Draw the card back - protected void drawCardBack(Graphics2D g) { - g.setPaint(BG_TEXTURE_CARDBACK); - g.fillRect(borderWidth, borderWidth, - cardWidth - 2*borderWidth, cardHeight - 2*borderWidth); - } - - // Draw summoning sickness overlay, and possibly other overlays - protected void drawOverlays(Graphics2D g) { - if (CardUtil.isCreature(cardView) && cardView instanceof PermanentView) { - if (((PermanentView)cardView).hasSummoningSickness()) { - int x1 = (int)(0.2*cardWidth); - int x2 = (int)(0.8*cardWidth); - int y1 = (int)(0.2*cardHeight); - int y2 = (int)(0.8*cardHeight); - int xPoints[] = { - x1, x2, x1, x2 - }; - int yPoints[] = { - y1, y1, y2, y2 - }; - g.setColor(new Color(255, 255, 255, 200)); - g.setStroke(new BasicStroke(7)); - g.drawPolygon(xPoints, yPoints, 4); - g.setColor(new Color(0, 0, 0, 200)); - g.setStroke(new BasicStroke(5)); - g.drawPolygon(xPoints, yPoints, 4); - g.setStroke(new BasicStroke(1)); - int[] xPoints2 = { - x1, x2, cardWidth/2 - }; - int[] yPoints2 = { - y1, y1, cardHeight/2 - }; - g.setColor(new Color(0, 0, 0, 100)); - g.fillPolygon(xPoints2, yPoints2, 3); - } - } - } - - // Draw +1/+1 and other counters - protected void drawCounters(Graphics2D g) { - int xPos = (int)(0.65*cardWidth); - int yPos = (int)(0.15*cardHeight); - if (cardView.getCounters() != null) { - for (CounterView v: cardView.getCounters()) { - // Don't render loyalty, we do that in the bottom corner - if (!v.getName().equals("loyalty")) { - Polygon p; - if (v.getName().equals("+1/+1")) { - p = PLUS_COUNTER_POLY; - } else if (v.getName().equals("-1/-1")) { - p = MINUS_COUNTER_POLY; - } else if (v.getName().equals("time")) { - p = TIME_COUNTER_POLY; - } else { - p = OTHER_COUNTER_POLY; - } - double scale = (0.1*0.25*cardWidth); - Graphics2D g2 = (Graphics2D)g.create(); - g2.translate(xPos, yPos); - g2.scale(scale, scale); - g2.setColor(Color.white); - g2.fillPolygon(p); - g2.setColor(Color.black); - g2.drawPolygon(p); - g2.setFont(new Font("Arial", Font.BOLD, 7)); - String cstr = "" + v.getCount(); - int strW = g2.getFontMetrics().stringWidth(cstr); - g2.drawString(cstr, 5 - strW/2, 8); - g2.dispose(); - yPos += ((int)(0.30*cardWidth)); - } - } - } - } - - // Draw an expansion symbol, right justified, in a given region - // Return the width of the drawn symbol - protected int drawExpansionSymbol(Graphics2D g, int x, int y, int w, int h) { - // Draw the expansion symbol - Image setSymbol = ManaSymbols.getSetSymbolImage(cardView.getExpansionSetCode(), cardView.getRarity().getCode()); - int setSymbolWidth; - if (setSymbol == null) { - // Don't draw anything when we don't have a set symbol - return 0; - /* - // Just draw the as a code - String code = cardView.getExpansionSetCode(); - code = (code != null) ? code.toUpperCase() : ""; - FontMetrics metrics = g.getFontMetrics(); - setSymbolWidth = metrics.stringWidth(code); - if (cardView.getRarity() == Rarity.COMMON) { - g.setColor(Color.white); - } else { - g.setColor(Color.black); - } - g.fillRoundRect( - x + w - setSymbolWidth - 1, y + 2, - setSymbolWidth+2, h - 5, - 5, 5); - g.setColor(getRarityColor()); - g.drawString(code, x + w - setSymbolWidth, y + h - 3); - */ - } else { - // Draw the set symbol - int height = setSymbol.getHeight(null); - int scale = 1; - if (height != -1) { - while (height > h+2) { - scale *= 2; - height /= 2; - } - } - setSymbolWidth = setSymbol.getWidth(null) / scale; - g.drawImage(setSymbol, - x + w - setSymbolWidth, y + (h - height)/2, - setSymbolWidth, height, - null); - } - return setSymbolWidth; - } - private Color getRarityColor() { - switch (cardView.getRarity()) { - case RARE: - return new Color(255, 191, 0); - case UNCOMMON: - return new Color(192, 192, 192); - case MYTHIC: - return new Color(213, 51, 11); - case SPECIAL: - return new Color(204, 0, 255); - case BONUS: - return new Color(129, 228, 228); - case COMMON: - default: - return Color.black; - } - } - - // Get a string representing the type line - protected String getCardTypeLine() { - if (cardView.isAbility()) { - if (AbilityType.TRIGGERED.equals(cardView.getAbilityType())) { - return "Triggered Ability"; - } else if (AbilityType.ACTIVATED.equals(cardView.getAbilityType())) { - return "Activated Ability"; - } else { - return "??? Ability"; - } - } else { - StringBuilder sbType = new StringBuilder(); - for (String superType : cardView.getSuperTypes()) { - sbType.append(superType).append(" "); - } - for (CardType cardType : cardView.getCardTypes()) { - sbType.append(cardType.toString()).append(" "); - } - if (cardView.getSubTypes().size() > 0) { - sbType.append("- "); - for (String subType : cardView.getSubTypes()) { - sbType.append(subType).append(" "); - } - } - return sbType.toString(); - } - } - - // Set the card art image (CardPanel will give it to us when it - // is loaded and ready) - public void setArtImage(Image image) { - artImage = CardRendererUtils.toBufferedImage(image); - } -} +/* + * 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; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Paint; +import java.awt.Polygon; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import mage.client.dialog.PreferencesDialog; +import mage.constants.AbilityType; +import mage.constants.CardType; +import mage.utils.CardUtil; +import mage.view.CardView; +import mage.view.CounterView; +import mage.view.PermanentView; +import org.apache.log4j.Logger; + +/** + * @author stravant@gmail.com + * + * Common base class for card renderers for each card frame / card type. + * + * Follows the template method pattern to implement a new renderer, implement + * the following methods (they are called in the following order): + * + * * drawBorder() Draws the outermost border of the card, white border or black + * border + * + * * drawBackground() Draws the background texture / color of the card + * + * * drawArt() Draws the card's art + * + * * drawFrame() Draws the card frame (over the art and background) + * + * * drawOverlays() Draws summoning sickness and possible other overlays + * + * * drawCounters() Draws counters on the card, such as +1/+1 and -1/-1 + * counters + * + * Predefined methods that the implementations can use: + * + * * drawRules(font, bounding box) + * + * * drawNameLine(font, bounding box) + * + * * drawTypeLine(font, bounding box) + * + */ +public abstract class CardRenderer { + + private static final Logger LOGGER = Logger.getLogger(CardPanel.class); + + /////////////////////////////////////////////////////////////////////////// + // Common layout metrics between all cards + // The card to be rendered + protected final CardView cardView; + + // Is the card transformed? + protected final boolean isTransformed; + + // The card image + protected BufferedImage artImage; + + /////////////////////////////////////////////////////////////////////////// + // Common layout metrics between all cards + // Polygons for counters + private static final Polygon PLUS_COUNTER_POLY = new Polygon(new int[]{ + 0, 5, 10, 10, 5, 0 + }, new int[]{ + 3, 0, 3, 10, 9, 10 + }, 6); + private static final Polygon MINUS_COUNTER_POLY = new Polygon(new int[]{ + 0, 5, 10, 10, 5, 0 + }, new int[]{ + 0, 1, 0, 7, 10, 7 + }, 6); + private static final Polygon TIME_COUNTER_POLY = new Polygon(new int[]{ + 0, 10, 8, 10, 0, 2 + }, new int[]{ + 0, 0, 5, 10, 10, 5 + }, 6); + private static final Polygon OTHER_COUNTER_POLY = new Polygon(new int[]{ + 1, 9, 9, 1 + }, new int[]{ + 1, 1, 9, 9 + }, 4); + + // Paint for a card back + public static Paint BG_TEXTURE_CARDBACK = new Color(153, 102, 51); + + // The size of the card + protected int cardWidth; + protected int cardHeight; + + // Is it selectable / selected + protected boolean isChoosable; + protected boolean isSelected; + + // Radius of the corners of the cards + protected static float CORNER_RADIUS_FRAC = 0.1f; //x cardWidth + protected static int CORNER_RADIUS_MIN = 3; + protected int cornerRadius; + + // The inset of the actual card from the black / white border around it + protected static float BORDER_WIDTH_FRAC = 0.03f; //x cardWidth + protected static float BORDER_WIDTH_MIN = 2; + protected int borderWidth; + + // The parsed text of the card + protected ArrayList textboxRules = new ArrayList<>(); + protected ArrayList textboxKeywords = new ArrayList<>(); + + // The Construtor + // The constructor should prepare all of the things that it can + // without knowing the dimensions that the card will be rendered at. + // Then, the CardRenderer can be called on multiple times to render the + // card at various sizes (for instance, during animation) + public CardRenderer(CardView card, boolean isTransformed) { + // Set base parameters + this.cardView = card; + this.isTransformed = isTransformed; + + // Translate the textbox text + for (String rule : card.getRules()) { + // Kill reminder text + if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_REMINDER_TEXT, "false").equals("false")) { + rule = CardRendererUtils.killReminderText(rule).trim(); + } + if (!rule.isEmpty()) { + TextboxRule tbRule = TextboxRuleParser.parse(card, rule); + if (tbRule.type == TextboxRuleType.SIMPLE_KEYWORD) { + textboxKeywords.add(tbRule); + } else if (tbRule.text.isEmpty()) { + // Nothing to do, rule is empty + } else { + textboxRules.add(tbRule); + } + } + } + } + + // Layout operation + // Calculate common layout metrics that will be used by several + // of the operations in the template method. + protected void layout(int cardWidth, int cardHeight) { + // Store the dimensions for the template methods to use + this.cardWidth = cardWidth; + this.cardHeight = cardHeight; + + // Corner radius and border width + cornerRadius = (int) Math.max( + CORNER_RADIUS_MIN, + CORNER_RADIUS_FRAC * cardWidth); + + borderWidth = (int) Math.max( + BORDER_WIDTH_MIN, + BORDER_WIDTH_FRAC * cardWidth); + } + + // The Draw Method + // The draw method takes the information caculated by the constructor + // and uses it to draw to a concrete size of card and graphics. + public void draw(Graphics2D g, CardPanelAttributes attribs) { + // Pre template method layout, to calculate shared layout info + layout(attribs.cardWidth, attribs.cardHeight); + isSelected = attribs.isSelected; + isChoosable = attribs.isChoosable; + + // Call the template methods + drawBorder(g); + drawBackground(g); + drawArt(g); + drawFrame(g); + if (!cardView.isAbility()) { + drawOverlays(g); + drawCounters(g); + } + } + + // Template methods to be implemented by sub classes + // For instance, for the Modern vs Old border card frames + protected abstract void drawBorder(Graphics2D g); + + protected abstract void drawBackground(Graphics2D g); + + protected abstract void drawArt(Graphics2D g); + + protected abstract void drawFrame(Graphics2D g); + + // Template methods that are possible to override, but unlikely to be + // overridden. + // Draw the card back + protected void drawCardBack(Graphics2D g) { + g.setPaint(BG_TEXTURE_CARDBACK); + g.fillRect(borderWidth, borderWidth, + cardWidth - 2 * borderWidth, cardHeight - 2 * borderWidth); + } + + // Draw summoning sickness overlay, and possibly other overlays + protected void drawOverlays(Graphics2D g) { + if (CardUtil.isCreature(cardView) && cardView instanceof PermanentView) { + if (((PermanentView) cardView).hasSummoningSickness()) { + int x1 = (int) (0.2 * cardWidth); + int x2 = (int) (0.8 * cardWidth); + int y1 = (int) (0.2 * cardHeight); + int y2 = (int) (0.8 * cardHeight); + int xPoints[] = { + x1, x2, x1, x2 + }; + int yPoints[] = { + y1, y1, y2, y2 + }; + g.setColor(new Color(255, 255, 255, 200)); + g.setStroke(new BasicStroke(7)); + g.drawPolygon(xPoints, yPoints, 4); + g.setColor(new Color(0, 0, 0, 200)); + g.setStroke(new BasicStroke(5)); + g.drawPolygon(xPoints, yPoints, 4); + g.setStroke(new BasicStroke(1)); + int[] xPoints2 = { + x1, x2, cardWidth / 2 + }; + int[] yPoints2 = { + y1, y1, cardHeight / 2 + }; + g.setColor(new Color(0, 0, 0, 100)); + g.fillPolygon(xPoints2, yPoints2, 3); + } + } + } + + // Draw +1/+1 and other counters + protected void drawCounters(Graphics2D g) { + int xPos = (int) (0.65 * cardWidth); + int yPos = (int) (0.15 * cardHeight); + if (cardView.getCounters() != null) { + for (CounterView v : cardView.getCounters()) { + // Don't render loyalty, we do that in the bottom corner + if (!v.getName().equals("loyalty")) { + Polygon p; + if (v.getName().equals("+1/+1")) { + p = PLUS_COUNTER_POLY; + } else if (v.getName().equals("-1/-1")) { + p = MINUS_COUNTER_POLY; + } else if (v.getName().equals("time")) { + p = TIME_COUNTER_POLY; + } else { + p = OTHER_COUNTER_POLY; + } + double scale = (0.1 * 0.25 * cardWidth); + Graphics2D g2 = (Graphics2D) g.create(); + g2.translate(xPos, yPos); + g2.scale(scale, scale); + g2.setColor(Color.white); + g2.fillPolygon(p); + g2.setColor(Color.black); + g2.drawPolygon(p); + g2.setFont(new Font("Arial", Font.BOLD, 7)); + String cstr = "" + v.getCount(); + int strW = g2.getFontMetrics().stringWidth(cstr); + g2.drawString(cstr, 5 - strW / 2, 8); + g2.dispose(); + yPos += ((int) (0.30 * cardWidth)); + } + } + } + } + + // Draw an expansion symbol, right justified, in a given region + // Return the width of the drawn symbol + protected int drawExpansionSymbol(Graphics2D g, int x, int y, int w, int h) { + // Draw the expansion symbol + Image setSymbol = ManaSymbols.getSetSymbolImage(cardView.getExpansionSetCode(), cardView.getRarity().getCode()); + int setSymbolWidth; + if (setSymbol == null) { + // Don't draw anything when we don't have a set symbol + return 0; + /* + // Just draw the as a code + String code = cardView.getExpansionSetCode(); + code = (code != null) ? code.toUpperCase() : ""; + FontMetrics metrics = g.getFontMetrics(); + setSymbolWidth = metrics.stringWidth(code); + if (cardView.getRarity() == Rarity.COMMON) { + g.setColor(Color.white); + } else { + g.setColor(Color.black); + } + g.fillRoundRect( + x + w - setSymbolWidth - 1, y + 2, + setSymbolWidth+2, h - 5, + 5, 5); + g.setColor(getRarityColor()); + g.drawString(code, x + w - setSymbolWidth, y + h - 3); + */ + } else { + // Draw the set symbol + int height = setSymbol.getHeight(null); + int scale = 1; + if (height != -1) { + while (height > h + 2) { + scale *= 2; + height /= 2; + } + } + setSymbolWidth = setSymbol.getWidth(null) / scale; + g.drawImage(setSymbol, + x + w - setSymbolWidth, y + (h - height) / 2, + setSymbolWidth, height, + null); + } + return setSymbolWidth; + } + + private Color getRarityColor() { + switch (cardView.getRarity()) { + case RARE: + return new Color(255, 191, 0); + case UNCOMMON: + return new Color(192, 192, 192); + case MYTHIC: + return new Color(213, 51, 11); + case SPECIAL: + return new Color(204, 0, 255); + case BONUS: + return new Color(129, 228, 228); + case COMMON: + default: + return Color.black; + } + } + + // Get a string representing the type line + protected String getCardTypeLine() { + if (cardView.isAbility()) { + if (AbilityType.TRIGGERED.equals(cardView.getAbilityType())) { + return "Triggered Ability"; + } else if (AbilityType.ACTIVATED.equals(cardView.getAbilityType())) { + return "Activated Ability"; + } else { + return "??? Ability"; + } + } else { + StringBuilder sbType = new StringBuilder(); + for (String superType : cardView.getSuperTypes()) { + sbType.append(superType).append(" "); + } + for (CardType cardType : cardView.getCardTypes()) { + sbType.append(cardType.toString()).append(" "); + } + if (cardView.getSubTypes().size() > 0) { + sbType.append("- "); + for (String subType : cardView.getSubTypes()) { + sbType.append(subType).append(" "); + } + } + return sbType.toString(); + } + } + + // Set the card art image (CardPanel will give it to us when it + // is loaded and ready) + public void setArtImage(Image image) { + artImage = CardRendererUtils.toBufferedImage(image); + } +} diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/GlowText.java b/Mage.Client/src/main/java/org/mage/card/arcane/GlowText.java index 5a32f6b872..526eec50cb 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/GlowText.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/GlowText.java @@ -2,20 +2,12 @@ package org.mage.card.arcane; import com.google.common.base.Function; import com.google.common.collect.MapMaker; -import javax.swing.*; import java.awt.*; import java.awt.font.FontRenderContext; import java.awt.font.LineBreakMeasurer; import java.awt.font.TextAttribute; import java.awt.font.TextLayout; import java.awt.image.BufferedImage; -import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.text.AttributedCharacterIterator; import java.text.AttributedString; @@ -23,10 +15,12 @@ import java.text.BreakIterator; import java.util.Locale; import java.util.Map; import java.util.Objects; +import javax.swing.*; import mage.client.util.ImageCaches; import org.jdesktop.swingx.graphics.GraphicsUtilities; public class GlowText extends JLabel { + private static final long serialVersionUID = 1827677946939348001L; private int glowSize; @SuppressWarnings("unused") @@ -36,12 +30,12 @@ public class GlowText extends JLabel { private int lineCount = 0; private static Map IMAGE_CACHE; - private final static class Key - { + private final static class Key { + final int width; final int height; final String text; - final Map fontAttributes; + final Map fontAttributes; final Color color; final int glowSize; final float glowIntensity; @@ -53,8 +47,9 @@ public class GlowText extends JLabel { Font getFont() { Font res = this.originalFont.get(); - if(res == null) + if (res == null) { res = Font.getFont(this.fontAttributes); + } return res; } @@ -138,18 +133,18 @@ public class GlowText extends JLabel { })); } - public void setGlow (Color glowColor, int size, float intensity) { + public void setGlow(Color glowColor, int size, float intensity) { this.glowColor = glowColor; this.glowSize = size; this.glowIntensity = intensity; } - public void setWrap (boolean wrap) { + public void setWrap(boolean wrap) { this.wrap = wrap; } @Override - public Dimension getPreferredSize () { + public Dimension getPreferredSize() { Dimension size = super.getPreferredSize(); size.width += glowSize; size.height += glowSize / 2; @@ -157,7 +152,7 @@ public class GlowText extends JLabel { } @Override - public void paint (Graphics g) { + public void paint(Graphics g) { if (getText().length() == 0) { return; } @@ -165,7 +160,7 @@ public class GlowText extends JLabel { g.drawImage(IMAGE_CACHE.get(new Key(getWidth(), getHeight(), getText(), getFont(), getForeground(), glowSize, glowIntensity, glowColor, wrap)), 0, 0, null); } - private static BufferedImage createImage (Key key) { + private static BufferedImage createImage(Key key) { Dimension size = new Dimension(key.width, key.height); BufferedImage image = GraphicsUtilities.createCompatibleTranslucentImage(size.width, size.height); Graphics2D g2d = image.createGraphics(); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java index 542b26267c..7e0a1d9d27 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java @@ -9,21 +9,17 @@ import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.FontFormatException; -import java.awt.GradientPaint; import java.awt.Graphics2D; -import java.awt.Image; import java.awt.LinearGradientPaint; import java.awt.Paint; import java.awt.Polygon; import java.awt.Rectangle; -import java.awt.Shape; import java.awt.TexturePaint; import java.awt.font.FontRenderContext; import java.awt.font.LineBreakMeasurer; import java.awt.font.TextAttribute; import java.awt.font.TextLayout; import java.awt.font.TextMeasurer; -import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.io.IOException; @@ -40,27 +36,19 @@ import mage.client.dialog.PreferencesDialog; import mage.constants.CardType; import mage.view.CardView; import mage.view.PermanentView; -import net.java.balloontip.styles.RoundedBalloonStyle; import org.apache.log4j.Logger; -import org.mage.card.arcane.CardRenderer; -import org.mage.card.arcane.CardRendererUtils; -import org.mage.card.arcane.ManaSymbols; -import org.mage.card.arcane.TextboxLoyaltyRule; -import org.mage.card.arcane.TextboxRule; -import org.mage.card.arcane.TextboxRuleType; -import sun.security.pkcs11.P11TlsKeyMaterialGenerator; -/* +/* private void cardRendererBasedRender(Graphics2D g) { // Prepare for draw g.translate(cardXOffset, cardYOffset); int cardWidth = this.cardWidth - cardXOffset; int cardHeight = this.cardHeight - cardYOffset; - + // AA on g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - + // Renderer CardRenderer render = new ModernCardRenderer(gameCard, transformed); Image img = imagePanel.getSrcImage(); @@ -70,28 +58,28 @@ import sun.security.pkcs11.P11TlsKeyMaterialGenerator; render.draw(g, cardWidth, cardHeight); } */ - /** * @author stravant@gmail.com - * + * * Base rendering class for new border cards */ public class ModernCardRenderer extends CardRenderer { - private static Logger LOGGER = Logger.getLogger(ModernCardRenderer.class); - + + private final static Logger LOGGER = Logger.getLogger(ModernCardRenderer.class); + /////////////////////////////////////////////////////////////////////////// // Textures for modern frame cards - private static TexturePaint loadBackgroundTexture(String name) { URL url = ModernCardRenderer.class.getResource("/cardrender/background_texture_" + name + ".png"); ImageIcon icon = new ImageIcon(url); BufferedImage img = CardRendererUtils.toBufferedImage(icon.getImage()); return new TexturePaint(img, new Rectangle(0, 0, img.getWidth(), img.getHeight())); } + private static Font loadFont(String name) { try { return Font.createFont( - Font.TRUETYPE_FONT, + Font.TRUETYPE_FONT, ModernCardRenderer.class.getResourceAsStream("/cardrender/" + name + ".ttf")); } catch (IOException e) { LOGGER.info("Failed to load font `" + name + "`, couldn't find resource."); @@ -101,16 +89,16 @@ public class ModernCardRenderer extends CardRenderer { return new Font("Arial", Font.PLAIN, 1); } public static Font BASE_BELEREN_FONT = loadFont("beleren-bold"); - - public static Paint BG_TEXTURE_WHITE = loadBackgroundTexture("white"); - public static Paint BG_TEXTURE_BLUE = loadBackgroundTexture("blue"); - public static Paint BG_TEXTURE_BLACK = loadBackgroundTexture("black"); - public static Paint BG_TEXTURE_RED = loadBackgroundTexture("red"); - public static Paint BG_TEXTURE_GREEN = loadBackgroundTexture("green"); - public static Paint BG_TEXTURE_GOLD = loadBackgroundTexture("gold"); + + public static Paint BG_TEXTURE_WHITE = loadBackgroundTexture("white"); + public static Paint BG_TEXTURE_BLUE = loadBackgroundTexture("blue"); + public static Paint BG_TEXTURE_BLACK = loadBackgroundTexture("black"); + public static Paint BG_TEXTURE_RED = loadBackgroundTexture("red"); + public static Paint BG_TEXTURE_GREEN = loadBackgroundTexture("green"); + public static Paint BG_TEXTURE_GOLD = loadBackgroundTexture("gold"); public static Paint BG_TEXTURE_ARTIFACT = loadBackgroundTexture("artifact"); - public static Paint BG_TEXTURE_LAND = loadBackgroundTexture("land"); - + public static Paint BG_TEXTURE_LAND = loadBackgroundTexture("land"); + public static Color BORDER_WHITE = new Color(216, 203, 188); public static Color BORDER_BLUE = new Color(20, 121, 175); public static Color BORDER_BLACK = new Color(45, 45, 35); @@ -119,7 +107,7 @@ public class ModernCardRenderer extends CardRenderer { public static Color BORDER_GOLD = new Color(255, 228, 124); public static Color BORDER_COLORLESS = new Color(238, 242, 242); public static Color BORDER_LAND = new Color(190, 173, 115); - + public static Color BOX_WHITE = new Color(244, 245, 239); public static Color BOX_BLUE = new Color(201, 223, 237); public static Color BOX_BLACK = new Color(204, 194, 192); @@ -128,7 +116,7 @@ public class ModernCardRenderer extends CardRenderer { public static Color BOX_GOLD = new Color(223, 195, 136); public static Color BOX_COLORLESS = new Color(220, 228, 232); public static Color BOX_LAND = new Color(220, 215, 213); - + public static Color BOX_WHITE_NIGHT = new Color(169, 160, 145); public static Color BOX_BLUE_NIGHT = new Color(46, 133, 176); public static Color BOX_BLACK_NIGHT = new Color(95, 90, 89); @@ -136,45 +124,43 @@ public class ModernCardRenderer extends CardRenderer { public static Color BOX_GREEN_NIGHT = new Color(31, 100, 44); public static Color BOX_GOLD_NIGHT = new Color(171, 134, 70); public static Color BOX_COLORLESS_NIGHT = new Color(118, 147, 158); - + public static Color TEXTBOX_WHITE = new Color(252, 249, 244, 244); - public static Color TEXTBOX_BLUE = new Color(229, 238, 247, 244); + public static Color TEXTBOX_BLUE = new Color(229, 238, 247, 244); public static Color TEXTBOX_BLACK = new Color(241, 241, 240, 244); - public static Color TEXTBOX_RED = new Color(243, 224, 217, 244); + public static Color TEXTBOX_RED = new Color(243, 224, 217, 244); public static Color TEXTBOX_GREEN = new Color(217, 232, 223, 244); - public static Color TEXTBOX_GOLD = new Color(240, 234, 209, 244); + public static Color TEXTBOX_GOLD = new Color(240, 234, 209, 244); public static Color TEXTBOX_COLORLESS = new Color(219, 229, 233, 244); public static Color TEXTBOX_LAND = new Color(218, 214, 212, 244); - + public static Color ERROR_COLOR = new Color(255, 0, 255); - - + /////////////////////////////////////////////////////////////////////////// // Layout metrics for modern border cards - // How far the main box, art, and name / type line are inset from the // card border. That is, the width of background texture that shows around // the edge of the card. protected int contentInset; - + // Helper: The total inset from card edge to rules box etc. // = borderWidth + contentInset protected int totalContentInset; - + // Width of the content region of the card // = cardWidth - 2 x totalContentInset protected int contentWidth; - + // How tall the name / type lines and P/T box are protected static float BOX_HEIGHT_FRAC = 0.065f; // x cardHeight protected static int BOX_HEIGHT_MIN = 16; protected int boxHeight; - + // How far down the card is the type line placed? protected static float TYPE_LINE_Y_FRAC = 0.57f; // x cardHeight protected static float TYPE_LINE_Y_FRAC_TOKEN = 0.70f; protected int typeLineY; - + // How large is the box text, and how far is it down the boxes protected int boxTextHeight; protected int boxTextOffset; @@ -184,61 +170,61 @@ public class ModernCardRenderer extends CardRenderer { protected int ptTextHeight; protected int ptTextOffset; protected Font ptTextFont; - + // Processed mana cost string protected String manaCostString; - + public ModernCardRenderer(CardView card, boolean isTransformed) { // Pass off to parent super(card, isTransformed); - + // Mana cost string manaCostString = ManaSymbols.getStringManaCost(cardView.getManaCost()); } - + @Override protected void layout(int cardWidth, int cardHeight) { // Pass to parent super.layout(cardWidth, cardHeight); - + // Content inset, just equal to border width contentInset = borderWidth; - + // Total content inset helper totalContentInset = borderWidth + contentInset; - + // Content width - contentWidth = cardWidth - 2*totalContentInset; - + contentWidth = cardWidth - 2 * totalContentInset; + // Box height - boxHeight = (int)Math.max( + boxHeight = (int) Math.max( BOX_HEIGHT_MIN, BOX_HEIGHT_FRAC * cardHeight); - + // Type line at if (cardView.isToken()) { - typeLineY = (int)(TYPE_LINE_Y_FRAC_TOKEN * cardHeight); + typeLineY = (int) (TYPE_LINE_Y_FRAC_TOKEN * cardHeight); } else { - typeLineY = (int)(TYPE_LINE_Y_FRAC * cardHeight); + typeLineY = (int) (TYPE_LINE_Y_FRAC * cardHeight); } - + // Box text height boxTextHeight = getTextHeightForBoxHeight(boxHeight); - boxTextOffset = (boxHeight - boxTextHeight)/2; + boxTextOffset = (boxHeight - boxTextHeight) / 2; boxTextFont = BASE_BELEREN_FONT.deriveFont(Font.PLAIN, boxTextHeight); - + // Box text height ptTextHeight = getPTTextHeightForLineHeight(boxHeight); - ptTextOffset = (boxHeight - ptTextHeight)/2; + ptTextOffset = (boxHeight - ptTextHeight) / 2; ptTextFont = BASE_BELEREN_FONT.deriveFont(Font.PLAIN, ptTextHeight); } - + @Override protected void drawBorder(Graphics2D g) { // Draw border as one rounded rectangle g.setColor(Color.black); g.fillRoundRect(0, 0, cardWidth, cardHeight, cornerRadius, cornerRadius); - + // Selection Borders Color borderColor; if (isSelected) { @@ -247,17 +233,17 @@ public class ModernCardRenderer extends CardRenderer { borderColor = new Color(250, 250, 0, 230); } else if (cardView.isPlayable()) { borderColor = new Color(153, 102, 204, 200); - } else if (cardView instanceof PermanentView && ((PermanentView)cardView).isCanAttack()) { + } else if (cardView instanceof PermanentView && ((PermanentView) cardView).isCanAttack()) { borderColor = new Color(0, 0, 255, 230); } else { borderColor = null; } if (borderColor != null) { float hwidth = borderWidth / 2.0f; - Graphics2D g2 = (Graphics2D)g.create(); + Graphics2D g2 = (Graphics2D) g.create(); g2.setColor(borderColor); g2.setStroke(new BasicStroke(borderWidth)); - RoundRectangle2D.Float rect + RoundRectangle2D.Float rect = new RoundRectangle2D.Float( hwidth, hwidth, cardWidth - borderWidth, cardHeight - borderWidth, @@ -266,11 +252,11 @@ public class ModernCardRenderer extends CardRenderer { g2.dispose(); } } - + @Override protected void drawBackground(Graphics2D g) { // Draw background, in 3 parts - + if (cardView.isFaceDown()) { // Just draw a brown rectangle drawCardBack(g); @@ -280,94 +266,94 @@ public class ModernCardRenderer extends CardRenderer { // Draw main part (most of card) g.fillRoundRect( - borderWidth, borderWidth, - cardWidth - borderWidth*2, cardHeight - borderWidth*4 - cornerRadius*2, + borderWidth, borderWidth, + cardWidth - borderWidth * 2, cardHeight - borderWidth * 4 - cornerRadius * 2, cornerRadius - 1, cornerRadius - 1); // Draw the M15 rounded "swoosh" at the bottom g.fillRoundRect( - borderWidth, cardHeight - borderWidth*4 - cornerRadius*4, - cardWidth - borderWidth*2, cornerRadius*4, - cornerRadius*2, cornerRadius*2); + borderWidth, cardHeight - borderWidth * 4 - cornerRadius * 4, + cardWidth - borderWidth * 2, cornerRadius * 4, + cornerRadius * 2, cornerRadius * 2); // Draw the cutout into the "swoosh" for the textbox to lie over g.fillRect( - borderWidth + contentInset, cardHeight - borderWidth*5, - cardWidth - borderWidth*2 - contentInset*2, borderWidth*2); + borderWidth + contentInset, cardHeight - borderWidth * 5, + cardWidth - borderWidth * 2 - contentInset * 2, borderWidth * 2); } } - + @Override protected void drawArt(Graphics2D g) { if (artImage != null && !cardView.isFaceDown()) { int imgWidth = artImage.getWidth(); int imgHeight = artImage.getHeight(); - BufferedImage subImg = - artImage.getSubimage( - (int)(.079*imgWidth), (int)(.11*imgHeight), - (int)(.84*imgWidth), (int)(.42*imgHeight)); + BufferedImage subImg + = artImage.getSubimage( + (int) (.079 * imgWidth), (int) (.11 * imgHeight), + (int) (.84 * imgWidth), (int) (.42 * imgHeight)); g.drawImage(subImg, - totalContentInset+1, totalContentInset+boxHeight, - contentWidth - 2, typeLineY - totalContentInset - boxHeight, + totalContentInset + 1, totalContentInset + boxHeight, + contentWidth - 2, typeLineY - totalContentInset - boxHeight, null); } } - + @Override protected void drawFrame(Graphics2D g) { // Get the card colors to base the frame on ObjectColor frameColors = getFrameObjectColor(); - + // Get the border paint Color boxColor = getBoxColor(frameColors, cardView.getCardTypes(), isTransformed); Paint textboxPaint = getTextboxPaint(frameColors, cardView.getCardTypes(), cardWidth); Paint borderPaint = getBorderPaint(frameColors, cardView.getCardTypes(), cardWidth); - + // Draw the main card content border g.setPaint(borderPaint); g.drawRect( totalContentInset, totalContentInset, - contentWidth - 1, cardHeight - borderWidth*3 - totalContentInset - 1); - + contentWidth - 1, cardHeight - borderWidth * 3 - totalContentInset - 1); + // Draw the textbox fill g.setPaint(textboxPaint); g.fillRect( totalContentInset + 1, typeLineY, - contentWidth - 2, cardHeight - borderWidth*3 - typeLineY - 1); - + contentWidth - 2, cardHeight - borderWidth * 3 - typeLineY - 1); + // If it's a planeswalker, extend the textbox left border by some if (cardView.getCardTypes().contains(CardType.PLANESWALKER)) { g.setPaint(borderPaint); g.fillRect( totalContentInset, typeLineY + boxHeight, - cardWidth/16, cardHeight - typeLineY - boxHeight - borderWidth*3); + cardWidth / 16, cardHeight - typeLineY - boxHeight - borderWidth * 3); } - + // Draw a shadow highlight at the right edge of the content frame g.setColor(new Color(0, 0, 0, 100)); g.fillRect( totalContentInset - 1, totalContentInset, - 1, cardHeight - borderWidth*3 - totalContentInset - 1); - + 1, cardHeight - borderWidth * 3 - totalContentInset - 1); + // Draw a shadow highlight separating the card art and rest of frame g.drawRect( totalContentInset + 1, totalContentInset + boxHeight, contentWidth - 3, typeLineY - totalContentInset - boxHeight - 1); - + // Draw the name line box - CardRendererUtils.drawRoundedBox(g, - borderWidth, totalContentInset, - cardWidth - 2*borderWidth, boxHeight, - contentInset, + CardRendererUtils.drawRoundedBox(g, + borderWidth, totalContentInset, + cardWidth - 2 * borderWidth, boxHeight, + contentInset, borderPaint, boxColor); - + // Draw the type line box - CardRendererUtils.drawRoundedBox(g, - borderWidth, typeLineY, - cardWidth - 2*borderWidth, boxHeight, - contentInset, - borderPaint, boxColor); - + CardRendererUtils.drawRoundedBox(g, + borderWidth, typeLineY, + cardWidth - 2 * borderWidth, boxHeight, + contentInset, + borderPaint, boxColor); + // Draw a small separator between the type line and box, and shadow // at the left of the texbox, and above the name line g.setColor(new Color(0, 0, 0, 150)); @@ -379,30 +365,30 @@ public class ModernCardRenderer extends CardRenderer { contentWidth - 2, 1); g.fillRect( cardWidth - totalContentInset - 1, typeLineY + boxHeight, - 1, cardHeight - borderWidth*3 - typeLineY - boxHeight); - + 1, cardHeight - borderWidth * 3 - typeLineY - boxHeight); + // Draw the transform circle int nameOffset = drawTransformationCircle(g, borderPaint); - + // Draw the name line - drawNameLine(g, + drawNameLine(g, totalContentInset + nameOffset, totalContentInset, contentWidth - nameOffset, boxHeight); - + // Draw the type line drawTypeLine(g, totalContentInset, typeLineY, contentWidth, boxHeight); - + // Draw the textbox rules drawRulesText(g, totalContentInset + 2, typeLineY + boxHeight + 2, - contentWidth - 4, cardHeight - typeLineY - boxHeight - 4 - borderWidth*3); - + contentWidth - 4, cardHeight - typeLineY - boxHeight - 4 - borderWidth * 3); + // Draw the bottom right stuff drawBottomRight(g, borderPaint, boxColor); } - + // Draw the name line protected void drawNameLine(Graphics2D g, int x, int y, int w, int h) { // Width of the mana symbols @@ -412,16 +398,16 @@ public class ModernCardRenderer extends CardRenderer { } else { manaCostWidth = CardRendererUtils.getManaCostWidth(manaCostString, boxTextHeight); } - + // Available width for name. Add a little bit of slop so that one character // can partially go underneath the mana cost int availableWidth = w - manaCostWidth + 2; - + // Draw the name String nameStr; if (cardView.isFaceDown()) { - if (cardView instanceof PermanentView && ((PermanentView)cardView).isManifested()) { - nameStr = "Manifest: " + cardView.getName(); + if (cardView instanceof PermanentView && ((PermanentView) cardView).isManifested()) { + nameStr = "Manifest: " + cardView.getName(); } else { nameStr = "Morph: " + cardView.getName(); } @@ -434,13 +420,13 @@ public class ModernCardRenderer extends CardRenderer { TextLayout layout = measure.getLayout(0, measure.getLineBreakIndex(0, availableWidth)); g.setColor(getBoxTextColor()); layout.draw(g, x, y + boxTextOffset + boxTextHeight - 1); - + // Draw the mana symbols if (!cardView.isAbility() && !cardView.isFaceDown()) { ManaSymbols.draw(g, manaCostString, x + w - manaCostWidth, y + boxTextOffset, boxTextHeight); } } - + // Draw the type line (color indicator, types, and expansion symbol) protected void drawTypeLine(Graphics2D g, int x, int y, int w, int h) { // Draw expansion symbol @@ -454,108 +440,106 @@ public class ModernCardRenderer extends CardRenderer { } else { expansionSymbolWidth = 0; } - + // Draw type line text int availableWidth = w - expansionSymbolWidth + 1; String types = getCardTypeLine(); g.setFont(boxTextFont); - + // Replace "Legendary" in type line if there's not enough space if (g.getFontMetrics().stringWidth(types) > availableWidth) { types = types.replace("Legendary", "L."); } - + if (!types.isEmpty()) { AttributedString str = new AttributedString(types); str.addAttribute(TextAttribute.FONT, boxTextFont); TextMeasurer measure = new TextMeasurer(str.getIterator(), g.getFontRenderContext()); TextLayout layout = measure.getLayout(0, measure.getLineBreakIndex(0, availableWidth)); g.setColor(getBoxTextColor()); - layout.draw(g, x, y + boxTextOffset + boxTextHeight - 1); + layout.draw(g, x, y + boxTextOffset + boxTextHeight - 1); } } - + // Draw the P/T and/or Loyalty boxes protected void drawBottomRight(Graphics2D g, Paint borderPaint, Color fill) { // No bottom right for abilities if (cardView.isAbility()) { return; } - + // Where to start drawing the things - int curY = cardHeight - (int)(0.03f*cardHeight); - + int curY = cardHeight - (int) (0.03f * cardHeight); + // Width of the boxes - int partWidth = (int)Math.max(30, 0.20f*cardWidth); - + int partWidth = (int) Math.max(30, 0.20f * cardWidth); + // Is it a creature? if (cardView.getCardTypes().contains(CardType.CREATURE)) { int x = cardWidth - borderWidth - partWidth; - + // Draw PT box - CardRendererUtils.drawRoundedBox(g, + CardRendererUtils.drawRoundedBox(g, x, curY - boxHeight, partWidth, boxHeight, contentInset, borderPaint, fill); - + // Draw shadow line top g.setColor(new Color(0, 0, 0, 150)); g.fillRect( x + contentInset, curY - boxHeight - 1, - partWidth - 2*contentInset, 1); - + partWidth - 2 * contentInset, 1); + // Draw text g.setColor(getBoxTextColor()); g.setFont(ptTextFont); String ptText = cardView.getPower() + "/" + cardView.getToughness(); int ptTextWidth = g.getFontMetrics().stringWidth(ptText); - g.drawString(ptText, - x + (partWidth - ptTextWidth)/2, curY - ptTextOffset - 1); - + g.drawString(ptText, + x + (partWidth - ptTextWidth) / 2, curY - ptTextOffset - 1); + // Does it have damage on it? - if ((cardView instanceof PermanentView) && ((PermanentView)cardView).getDamage() > 0) { + if ((cardView instanceof PermanentView) && ((PermanentView) cardView).getDamage() > 0) { // Show marked damage - + } - + curY -= boxHeight; } - + // Is it a walker? (But don't draw the box if it's a non-permanent view // of a walker without a starting loyalty (EG: Arlin Kord's flipped side). if (cardView.getCardTypes().contains(CardType.PLANESWALKER) && (cardView instanceof PermanentView || !cardView.getStartingLoyalty().equals("0"))) { // Draw the PW loyalty box int w = partWidth; - int h = partWidth/2; + int h = partWidth / 2; int x = cardWidth - partWidth - borderWidth; int y = curY - h; - + Polygon symbol = new Polygon( - new int[]{ - x + w/2, - (int)(x + w*0.9), - x + w, - (int)(x + w*0.6), - x + w/2, - (int)(x + w*0.4), - x, - (int)(x + w*0.1), - }, - new int[]{ - y + h, - (int)(y + 0.8*h), - y, - (int)(y - 0.2*h), - y, - (int)(y - 0.2*h), - y, - (int)(y + 0.8*h), - }, - 8); - + new int[]{ + x + w / 2, + (int) (x + w * 0.9), + x + w, + (int) (x + w * 0.6), + x + w / 2, + (int) (x + w * 0.4), + x, + (int) (x + w * 0.1),}, + new int[]{ + y + h, + (int) (y + 0.8 * h), + y, + (int) (y - 0.2 * h), + y, + (int) (y - 0.2 * h), + y, + (int) (y + 0.8 * h),}, + 8); + // Draw + stroke g.setColor(Color.black); g.fillPolygon(symbol); @@ -563,7 +547,7 @@ public class ModernCardRenderer extends CardRenderer { g.setStroke(new BasicStroke(2)); g.drawPolygon(symbol); g.setStroke(new BasicStroke(1)); - + // Loyalty number String loyalty; if (cardView instanceof PermanentView) { @@ -571,38 +555,39 @@ public class ModernCardRenderer extends CardRenderer { } else { loyalty = cardView.getStartingLoyalty(); } - + g.setFont(ptTextFont); g.setColor(Color.white); int loyaltyWidth = g.getFontMetrics().stringWidth(loyalty); - g.drawString(loyalty, x + (w - loyaltyWidth)/2, y + ptTextHeight + (h - ptTextHeight)/2); - + g.drawString(loyalty, x + (w - loyaltyWidth) / 2, y + ptTextHeight + (h - ptTextHeight) / 2); + // Advance - curY -= (int)(1.2*y); + curY -= (int) (1.2 * y); } - + // does it have damage on it? - if ((cardView instanceof PermanentView) && ((PermanentView)cardView).getDamage() > 0) { + if ((cardView instanceof PermanentView) && ((PermanentView) cardView).getDamage() > 0) { int x = cardWidth - partWidth - borderWidth; int y = curY - boxHeight; - String damage = "" + ((PermanentView)cardView).getDamage(); + String damage = "" + ((PermanentView) cardView).getDamage(); g.setFont(ptTextFont); int txWidth = g.getFontMetrics().stringWidth(damage); g.setColor(Color.red); g.fillRect(x, y, partWidth, boxHeight); g.setColor(Color.white); g.drawRect(x, y, partWidth, boxHeight); - g.drawString(damage, x + (partWidth - txWidth)/2, curY - 1); + g.drawString(damage, x + (partWidth - txWidth) / 2, curY - 1); } } - + // Draw the card's textbox in a given rect protected boolean loyaltyAbilityColorToggle = false; - protected void drawRulesText(Graphics2D g, int x, int y, int w, int h) { + + protected void drawRulesText(Graphics2D g, int x, int y, int w, int h) { // Initial font size to try to render at Font font = new Font("Arial", Font.PLAIN, 12); Font fontItalic = new Font("Arial", Font.ITALIC, 12); - + // Handle the keyword rules boolean hasKeywords = !textboxKeywords.isEmpty(); String keywordRulesString = getKeywordRulesString(); @@ -610,7 +595,7 @@ public class ModernCardRenderer extends CardRenderer { if (hasKeywords) { keywordRulesAttributed.addAttribute(TextAttribute.FONT, font); } - + // Get the total height List attributedRules = new ArrayList<>(); boolean useSmallFont = false; @@ -619,7 +604,7 @@ public class ModernCardRenderer extends CardRenderer { if (hasKeywords) { remaining -= drawSingleRule(g, keywordRulesAttributed, null, 0, 0, w, remaining, false); } - for (TextboxRule rule: textboxRules) { + for (TextboxRule rule : textboxRules) { AttributedString attributed = rule.generateAttributedString(font, fontItalic); attributedRules.add(attributed); remaining -= drawSingleRule(g, attributed, rule, 0, 0, w, remaining, false); @@ -629,7 +614,7 @@ public class ModernCardRenderer extends CardRenderer { } } } - + // If there wasn't enough room, try using a smaller font if (useSmallFont) { font = new Font("Arial", Font.PLAIN, 9); @@ -638,20 +623,20 @@ public class ModernCardRenderer extends CardRenderer { keywordRulesAttributed = new AttributedString(keywordRulesString); keywordRulesAttributed.addAttribute(TextAttribute.FONT, font); } - + // Clear out the attributed rules and reatribute them with the new font size attributedRules.clear(); - for (TextboxRule rule: textboxRules) { + for (TextboxRule rule : textboxRules) { AttributedString attributed = rule.generateAttributedString(font, fontItalic); attributedRules.add(attributed); } - + // Get the new spacing for the small text remaining = h; if (hasKeywords) { remaining -= drawSingleRule(g, keywordRulesAttributed, null, 0, 0, w, remaining, false); } - for (TextboxRule rule: textboxRules) { + for (TextboxRule rule : textboxRules) { AttributedString attributed = rule.generateAttributedString(font, fontItalic); attributedRules.add(attributed); remaining -= drawSingleRule(g, attributed, rule, 0, 0, w, remaining, false); @@ -661,18 +646,18 @@ public class ModernCardRenderer extends CardRenderer { } } } - + // Do we have room for additional spacing between the parts of text? // If so, calculate the spacing based on how much space was left over int spacing; if (remaining <= 0) { spacing = 0; } else { - spacing = (int)(remaining / (hasKeywords ? - (textboxRules.size() + 2) : - (textboxRules.size() + 1))); + spacing = (int) (remaining / (hasKeywords + ? (textboxRules.size() + 2) + : (textboxRules.size() + 1))); } - + // Do the actual draw loyaltyAbilityColorToggle = false; g.setColor(Color.black); @@ -693,7 +678,7 @@ public class ModernCardRenderer extends CardRenderer { } } } - + // Get the first line of the textbox, the keyword string private String getKeywordRulesString() { StringBuilder builder = new StringBuilder(); @@ -705,18 +690,18 @@ public class ModernCardRenderer extends CardRenderer { } return builder.toString(); } - + // Draw a single rule and returns the amount vertically advanced by, but // only if doDraw is true. If doDraw is false, just returns the vertical // advance if the rule were to be drawn. - private int drawSingleRule(Graphics2D g, AttributedString text, TextboxRule rule, int x, int y, int w, int h, boolean doDraw) { + private int drawSingleRule(Graphics2D g, AttributedString text, TextboxRule rule, int x, int y, int w, int h, boolean doDraw) { // Inset, in case we are a leveler or loyalty ability int inset = 0; if (rule != null && rule.type == TextboxRuleType.LOYALTY) { - inset = cardWidth/12; + inset = cardWidth / 12; } int availWidth = w - inset; - + FontRenderContext frc = g.getFontRenderContext(); AttributedCharacterIterator textIter = text.getIterator(); LineBreakMeasurer measure = new LineBreakMeasurer(textIter, frc); @@ -731,7 +716,7 @@ public class ModernCardRenderer extends CardRenderer { break; } } - + // Get the text layout TextLayout layout = measure.nextLayout(availWidth, newLineCheck.getIndex(), false); float ascent = layout.getAscent(); @@ -746,74 +731,68 @@ public class ModernCardRenderer extends CardRenderer { } yPos += layout.getDescent() + layout.getLeading() - 2; } - + // Advance - int advance = ((int)Math.ceil(yPos)) - y; - + int advance = ((int) Math.ceil(yPos)) - y; + // Is it a loyalty ability? if (rule != null && rule.type == TextboxRuleType.LOYALTY) { - TextboxLoyaltyRule loyaltyRule = (TextboxLoyaltyRule)rule; + TextboxLoyaltyRule loyaltyRule = (TextboxLoyaltyRule) rule; Polygon symbol; int symbolWidth = (x + inset) - borderWidth - 4; - int symbolHeight = (int)(0.7f*symbolWidth); + int symbolHeight = (int) (0.7f * symbolWidth); if (symbolHeight > advance) { advance = symbolHeight; } int symbolX = x - borderWidth; - int symbolY = y + (advance - symbolHeight)/2; + int symbolY = y + (advance - symbolHeight) / 2; if (doDraw) { if (loyaltyRule.loyaltyChange < 0 || loyaltyRule.loyaltyChange == TextboxLoyaltyRule.MINUS_X) { symbol = new Polygon( - new int[]{ - symbolX, - symbolX + symbolWidth, - symbolX + symbolWidth, - symbolX + symbolWidth/2, - symbolX, - }, - new int[]{ - symbolY, - symbolY, - symbolY + symbolHeight - 3, - symbolY + symbolHeight + 3, - symbolY + symbolHeight - 3, - }, - 5); + new int[]{ + symbolX, + symbolX + symbolWidth, + symbolX + symbolWidth, + symbolX + symbolWidth / 2, + symbolX,}, + new int[]{ + symbolY, + symbolY, + symbolY + symbolHeight - 3, + symbolY + symbolHeight + 3, + symbolY + symbolHeight - 3,}, + 5); } else if (loyaltyRule.loyaltyChange > 0) { symbol = new Polygon( - new int[]{ - symbolX, - symbolX + symbolWidth/2, - symbolX + symbolWidth, - symbolX + symbolWidth, - symbolX, - }, - new int[]{ - symbolY + 3, - symbolY - 3, - symbolY + 3, - symbolY + symbolHeight, - symbolY + symbolHeight, - }, - 5); + new int[]{ + symbolX, + symbolX + symbolWidth / 2, + symbolX + symbolWidth, + symbolX + symbolWidth, + symbolX,}, + new int[]{ + symbolY + 3, + symbolY - 3, + symbolY + 3, + symbolY + symbolHeight, + symbolY + symbolHeight,}, + 5); } else { symbol = new Polygon( - new int[]{ - symbolX, - symbolX + symbolWidth, - symbolX + symbolWidth, - symbolX, - }, - new int[]{ - symbolY, - symbolY, - symbolY + symbolHeight, - symbolY + symbolHeight, - }, - 4); + new int[]{ + symbolX, + symbolX + symbolWidth, + symbolX + symbolWidth, + symbolX,}, + new int[]{ + symbolY, + symbolY, + symbolY + symbolHeight, + symbolY + symbolHeight,}, + 4); } g.setColor(new Color(0, 0, 0, 128)); - g.fillRect(x+2, y+advance+1, w-2, 1); + g.fillRect(x + 2, y + advance + 1, w - 2, 1); g.setColor(Color.black); g.fillPolygon(symbol); g.setColor(new Color(200, 200, 200)); @@ -824,26 +803,28 @@ public class ModernCardRenderer extends CardRenderer { g.setFont(boxTextFont); String loyaltyString = loyaltyRule.getChangeString(); int textWidth = g.getFontMetrics().stringWidth(loyaltyString); - g.drawString(loyaltyString, - symbolX + (symbolWidth - textWidth)/2, - symbolY + symbolHeight - (symbolHeight - boxTextHeight)/2); + g.drawString(loyaltyString, + symbolX + (symbolWidth - textWidth) / 2, + symbolY + symbolHeight - (symbolHeight - boxTextHeight) / 2); advance += 3; loyaltyAbilityColorToggle = !loyaltyAbilityColorToggle; } } - + return advance; } - - // Draw the transformation circle if there is one, and return the + + // Draw the transformation circle if there is one, and return the // horizontal width taken up into the content space by it. protected boolean isNightCard() { return isTransformed; } + protected boolean isTransformCard() { return cardView.canTransform() || isTransformed; } + protected int drawTransformationCircle(Graphics2D g, Paint borderPaint) { int transformCircleOffset = 0; if (isTransformCard()) { @@ -851,32 +832,32 @@ public class ModernCardRenderer extends CardRenderer { g.setPaint(borderPaint); g.drawOval(borderWidth, totalContentInset, boxHeight - 1, boxHeight - 1); g.setColor(Color.black); - g.fillOval(borderWidth+1, totalContentInset+1, boxHeight-2, boxHeight-2); + g.fillOval(borderWidth + 1, totalContentInset + 1, boxHeight - 2, boxHeight - 2); g.setColor(Color.white); if (isNightCard()) { - g.fillArc(borderWidth+3, totalContentInset+3, boxHeight-6, boxHeight-6, 90, 270); + g.fillArc(borderWidth + 3, totalContentInset + 3, boxHeight - 6, boxHeight - 6, 90, 270); g.setColor(Color.black); - g.fillArc(borderWidth+3+3, totalContentInset+3, boxHeight-6-3, boxHeight-6, 90, 270); + g.fillArc(borderWidth + 3 + 3, totalContentInset + 3, boxHeight - 6 - 3, boxHeight - 6, 90, 270); } else { - g.fillOval(borderWidth+3, totalContentInset+3, boxHeight-6, boxHeight-6); + g.fillOval(borderWidth + 3, totalContentInset + 3, boxHeight - 6, boxHeight - 6); } } return transformCircleOffset; } - + // Get the text height for a given box height protected static int getTextHeightForBoxHeight(int h) { if (h < 15) { - return h-3; + return h - 3; } else { - return (int)Math.ceil(.6*h); - } + return (int) Math.ceil(.6 * h); + } } - + protected static int getPTTextHeightForLineHeight(int h) { return h - 4; } - + // Determine the color of the name / type line text protected Color getBoxTextColor() { if (isNightCard()) { @@ -887,13 +868,13 @@ public class ModernCardRenderer extends CardRenderer { return Color.black; } } - + // Determine the colors to base the frame on protected ObjectColor getFrameObjectColor() { // TODO: Take into account devoid, land frame colors, etc return cardView.getColor().union(cardView.getFrameColor()); } - + // Determine which background paint to use from a set of colors // and the current card. protected static Paint getBackgroundPaint(ObjectColor colors, Collection types) { @@ -918,7 +899,7 @@ public class ModernCardRenderer extends CardRenderer { return new Color(71, 86, 101); } } - + // Get the box color for the given colors protected Color getBoxColor(ObjectColor colors, Collection types, boolean isNightCard) { if (cardView.isAbility()) { @@ -950,7 +931,7 @@ public class ModernCardRenderer extends CardRenderer { return ERROR_COLOR; } } - + // Get the border color for a single color protected static Color getBorderColor(ObjectColor color) { if (color.isWhite()) { @@ -967,13 +948,13 @@ public class ModernCardRenderer extends CardRenderer { return ERROR_COLOR; } } - + // Determine the border paint to use, based on an ObjectColors protected static Paint getBorderPaint(ObjectColor colors, Collection types, int width) { if (colors.isMulticolored()) { if (colors.getColorCount() == 2) { List twoColors = colors.getColors(); - + // Two-color frames look better if we use a whiter white // than the normal white frame color for them, as the normal // white border color is very close to the gold background @@ -989,11 +970,11 @@ public class ModernCardRenderer extends CardRenderer { } else { color2 = getBorderColor(twoColors.get(1)); } - + // Special case for two colors, gradient paint return new LinearGradientPaint( - 0, 0, width, 0, - new float[]{0.4f, 0.6f}, + 0, 0, width, 0, + new float[]{0.4f, 0.6f}, new Color[]{color1, color2}); } else { return BORDER_GOLD; @@ -1008,7 +989,7 @@ public class ModernCardRenderer extends CardRenderer { return getBorderColor(colors); } } - + // Determine the textbox color for a single color protected static Color getTextboxColor(ObjectColor color) { if (color.isWhite()) { @@ -1022,20 +1003,20 @@ public class ModernCardRenderer extends CardRenderer { } else if (color.isGreen()) { return TEXTBOX_GREEN; } else { - return ERROR_COLOR; + return ERROR_COLOR; } } - + // Determine the border paint to use, based on an ObjectColors protected static Paint getTextboxPaint(ObjectColor colors, Collection types, int width) { if (colors.isMulticolored()) { if (colors.getColorCount() == 2) { List twoColors = colors.getColors(); - + // Special case for two colors, gradient paint return new LinearGradientPaint( - 0, 0, width, 0, - new float[]{0.4f, 0.6f}, + 0, 0, width, 0, + new float[]{0.4f, 0.6f}, new Color[]{ getTextboxColor(twoColors.get(0)), getTextboxColor(twoColors.get(1)) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/TextboxLevelRule.java b/Mage.Client/src/main/java/org/mage/card/arcane/TextboxLevelRule.java index 7a5388e29e..207e2dee90 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/TextboxLevelRule.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/TextboxLevelRule.java @@ -1,28 +1,28 @@ -/* - * 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; - -import java.text.AttributedString; -import java.util.List; - -/** - * @author StravantUser - * - * Level rule associated with leveler cards - */ -public class TextboxLevelRule extends TextboxRule { - // The levels that this rule applies to - public int levelFrom; - public int levelTo; - - public static int AND_HIGHER = 100; - - public TextboxLevelRule(String text, List regions, int levelFrom, int levelTo) { - super(text, regions, TextboxRuleType.LEVEL); - this.levelFrom = levelFrom; - this.levelTo = levelTo; - } -} +/* + * 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; + +import java.util.List; + +/** + * @author StravantUser + * + * Level rule associated with leveler cards + */ +public class TextboxLevelRule extends TextboxRule { + + // The levels that this rule applies to + public int levelFrom; + public int levelTo; + + public static int AND_HIGHER = 100; + + public TextboxLevelRule(String text, List regions, int levelFrom, int levelTo) { + super(text, regions, TextboxRuleType.LEVEL); + this.levelFrom = levelFrom; + this.levelTo = levelTo; + } +} diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/TextboxLoyaltyRule.java b/Mage.Client/src/main/java/org/mage/card/arcane/TextboxLoyaltyRule.java index a60858e088..50d1ae633a 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/TextboxLoyaltyRule.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/TextboxLoyaltyRule.java @@ -1,33 +1,33 @@ -/* - * 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; - -import java.text.AttributedString; -import java.util.List; - -/** - * @author StravantUser - */ -public class TextboxLoyaltyRule extends TextboxRule { - public int loyaltyChange; - - public static int MINUS_X = 100; - - public String getChangeString() { - if (loyaltyChange == MINUS_X) { - return "-X"; - } else if (loyaltyChange > 0) { - return "+" + loyaltyChange; - } else { - return "" + loyaltyChange; - } - } - - public TextboxLoyaltyRule(String text, List regions, int loyaltyChange) { - super(text, regions, TextboxRuleType.LOYALTY); - this.loyaltyChange = loyaltyChange; - } -} +/* + * 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; + +import java.util.List; + +/** + * @author StravantUser + */ +public class TextboxLoyaltyRule extends TextboxRule { + + public int loyaltyChange; + + public static int MINUS_X = 100; + + public String getChangeString() { + if (loyaltyChange == MINUS_X) { + return "-X"; + } else if (loyaltyChange > 0) { + return "+" + loyaltyChange; + } else { + return "" + loyaltyChange; + } + } + + public TextboxLoyaltyRule(String text, List regions, int loyaltyChange) { + super(text, regions, TextboxRuleType.LOYALTY); + this.loyaltyChange = loyaltyChange; + } +} diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/TextboxRule.java b/Mage.Client/src/main/java/org/mage/card/arcane/TextboxRule.java index 660b8587e2..063685782c 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/TextboxRule.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/TextboxRule.java @@ -1,94 +1,98 @@ -/* - * 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; - -import java.awt.Font; -import java.awt.Image; -import java.awt.font.GraphicAttribute; -import java.awt.font.ImageGraphicAttribute; -import java.awt.font.TextAttribute; -import java.text.AttributedString; -import java.util.List; - -/** - * @author stravant@gmail.com - * - * Class describing parsed & translated rules in the text box of a card, - * ready to be rendered. - */ -public class TextboxRule { - // An attributed region in the text, which can be applied to an - // attributed string. - public interface AttributeRegion { - public void applyToAttributedString(AttributedString str, Font normal, Font italic); - } - - // A region of italics, or bold text in a - public static class ItalicRegion implements AttributeRegion { - ItalicRegion(int start, int end) { - this.start = start; - this.end = end; - } - private final int start; - private final int end; - - @Override - public void applyToAttributedString(AttributedString str, Font normal, Font italic) { - if (end > start+1) { - str.addAttribute(TextAttribute.FONT, italic, start, end); - } - } - } - - // A special symbol embedded at some point in a string - public static class EmbeddedSymbol implements AttributeRegion { - EmbeddedSymbol(String symbol, int location) { - this.symbol = symbol; - this.location = location; - } - private final String symbol; - private final int location; - - @Override - public void applyToAttributedString(AttributedString str, Font normal, Font italic) { - Image symbolImage = ManaSymbols.getSizedManaSymbol(symbol, normal.getSize()); - if (symbolImage != null) { - ImageGraphicAttribute imgAttr = - new ImageGraphicAttribute(symbolImage, GraphicAttribute.BOTTOM_ALIGNMENT); - str.addAttribute(TextAttribute.CHAR_REPLACEMENT, imgAttr, location, location+1); - } - } - } - - public String text; - public TextboxRuleType type; - - private List regions; - - protected TextboxRule(String text, List regions, TextboxRuleType type) { - this.text = text; - this.type = type; - this.regions = regions; - } - - public TextboxRule(String text, List regions) { - this(text, regions, TextboxRuleType.NORMAL); - } - - public AttributedString generateAttributedString(Font normal, Font italic) { - // Build the final attributed text using the regions - // Do it in reverse order for proper handling of regions where - // there are multiple attributes stacked (EG: bold + italic) - AttributedString attributedRule = new AttributedString(text); - if (text.length() != 0) { - attributedRule.addAttribute(TextAttribute.FONT, normal); - for (int i = regions.size()-1; i >= 0; --i) { - regions.get(i).applyToAttributedString(attributedRule, normal, italic); - } - } - return attributedRule; - } -} +/* + * 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; + +import java.awt.Font; +import java.awt.Image; +import java.awt.font.GraphicAttribute; +import java.awt.font.ImageGraphicAttribute; +import java.awt.font.TextAttribute; +import java.text.AttributedString; +import java.util.List; + +/** + * @author stravant@gmail.com + * + * Class describing parsed & translated rules in the text box of a card, ready + * to be rendered. + */ +public class TextboxRule { + + // An attributed region in the text, which can be applied to an + // attributed string. + public interface AttributeRegion { + + public void applyToAttributedString(AttributedString str, Font normal, Font italic); + } + + // A region of italics, or bold text in a + public static class ItalicRegion implements AttributeRegion { + + ItalicRegion(int start, int end) { + this.start = start; + this.end = end; + } + private final int start; + private final int end; + + @Override + public void applyToAttributedString(AttributedString str, Font normal, Font italic) { + if (end > start + 1) { + str.addAttribute(TextAttribute.FONT, italic, start, end); + } + } + } + + // A special symbol embedded at some point in a string + public static class EmbeddedSymbol implements AttributeRegion { + + EmbeddedSymbol(String symbol, int location) { + this.symbol = symbol; + this.location = location; + } + private final String symbol; + private final int location; + + @Override + public void applyToAttributedString(AttributedString str, Font normal, Font italic) { + Image symbolImage = ManaSymbols.getSizedManaSymbol(symbol.replace("/", ""), normal.getSize()); + if (symbolImage != null) { + ImageGraphicAttribute imgAttr + = new ImageGraphicAttribute(symbolImage, GraphicAttribute.BOTTOM_ALIGNMENT); + str.addAttribute(TextAttribute.CHAR_REPLACEMENT, imgAttr, location, location + 1); + } + } + } + + public String text; + public TextboxRuleType type; + + private List regions; + + protected TextboxRule(String text, List regions, TextboxRuleType type) { + this.text = text; + this.type = type; + this.regions = regions; + } + + public TextboxRule(String text, List regions) { + this(text, regions, TextboxRuleType.NORMAL); + } + + public AttributedString generateAttributedString(Font normal, Font italic) { + // Build the final attributed text using the regions + // Do it in reverse order for proper handling of regions where + // there are multiple attributes stacked (EG: bold + italic) + AttributedString attributedRule = new AttributedString(text); + if (text.length() != 0) { + attributedRule.addAttribute(TextAttribute.FONT, normal); + for (int i = regions.size() - 1; i >= 0; --i) { + regions.get(i).applyToAttributedString(attributedRule, normal, italic); + } + } + return attributedRule; + } +} diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/TextboxRuleParser.java b/Mage.Client/src/main/java/org/mage/card/arcane/TextboxRuleParser.java index 1cb66a1477..021e150ad6 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/TextboxRuleParser.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/TextboxRuleParser.java @@ -1,251 +1,251 @@ -/* - * 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; - -import java.awt.Font; -import java.awt.Image; -import java.awt.font.GraphicAttribute; -import java.awt.font.ImageGraphicAttribute; -import java.awt.font.TextAttribute; -import java.text.AttributedString; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import mage.client.dialog.PreferencesDialog; -import mage.view.CardView; -import org.apache.log4j.Logger; - -/** - * - * @author StravantUser - */ -public class TextboxRuleParser { - private static final Logger LOGGER = Logger.getLogger(CardPanel.class); - - private static final Pattern LevelAbilityPattern = Pattern.compile("Level (\\d+)-?(\\d*)(\\+?)"); - private static final Pattern LoyaltyAbilityPattern = Pattern.compile("^(\\+|\\-)(\\d+|X): "); - private static final Pattern SimpleKeywordPattern = Pattern.compile("^(\\w+( \\w+)?)\\s*(\\([^\\)]*\\))?\\s*$"); - - // Parse a given rule (given as a string) into a TextboxRule, replacing - // symbol annotations, italics, etc, parsing out information such as - // if the ability is a loyalty ability, and returning an TextboxRule - // representing that information, which can be used to render the rule in - // the textbox of a card. - public static TextboxRule parse(CardView source, String rule) { - // List of regions to apply - ArrayList regions = new ArrayList<>(); - - // Leveler / loyalty - boolean isLeveler = false; - int levelFrom = 0; - int levelTo = 0; - - boolean isLoyalty = false; - int loyaltyChange = 0; - - // Parse the attributedString contents - int index = 0; - int outputIndex = 0; - - // Is it a simple keyword ability? - { - Matcher simpleKeywordMatch = SimpleKeywordPattern.matcher(rule); - if (simpleKeywordMatch.find()) { - return new TextboxKeywordRule(simpleKeywordMatch.group(1), regions); - } - } - - // Check if it's a loyalty ability. Must be right at the start of the rule - { - Matcher loyaltyMatch = LoyaltyAbilityPattern.matcher(rule); - if (loyaltyMatch.find()) { - // Get the loyalty change - if (loyaltyMatch.group(2).equals("X")) { - loyaltyChange = TextboxLoyaltyRule.MINUS_X; - } else { - loyaltyChange = Integer.parseInt(loyaltyMatch.group(2)); - if (loyaltyMatch.group(1).equals("-")) { - loyaltyChange = -loyaltyChange; - } - } - isLoyalty = true; - - // Go past the match - index = loyaltyMatch.group().length(); - } - } - - Deque openingStack = new ArrayDeque<>(); - StringBuilder build = new StringBuilder(); - while (index < rule.length()) { - int initialIndex = index; - char ch = rule.charAt(index); - if (ch == '{') { - // Handling for `{this}` - int closeIndex = rule.indexOf('}', index); - if (closeIndex == -1) { - // Malformed input, nothing to do - ++index; - ++outputIndex; - build.append(ch); - } else { - String contents = rule.substring(index+1, closeIndex); - if (contents.equals("this") || contents.equals("source")) { - // Replace {this} with the card's name - String cardName = source.getName(); - build.append(cardName); - index += contents.length() + 2; - outputIndex += cardName.length(); - } else { - Image symbol = ManaSymbols.getSizedManaSymbol(contents, 10); - if (symbol != null) { - // Mana or other inline symbol - build.append('#'); - regions.add(new TextboxRule.EmbeddedSymbol(contents, outputIndex)); - ++outputIndex; - index = closeIndex+1; - } else { - // Bad entry - build.append('{'); - build.append(contents); - build.append('}'); - index = closeIndex+1; - outputIndex += (contents.length() + 2); - } - } - } - - } else if (ch == '&') { - // Handling for `—` - if (rule.startsWith("—", index)) { - build.append('—'); - index += 7; - ++outputIndex; - } else if (rule.startsWith("&bull", index)) { - build.append('•'); - index += 5; - ++outputIndex; - } else { - LOGGER.error("Bad &...; sequence `" + rule.substring(index+1, index+10) + "` in rule."); - build.append('&'); - ++index; - ++outputIndex; - } - - - } else if (ch == '<') { - // Handling for `` and `
` - int closeIndex = rule.indexOf('>', index); - if (closeIndex != -1) { - // Is a tag - String tag = rule.substring(index+1, closeIndex); - if (tag.charAt(tag.length()-1) == '/') { - // Pure closing tag (like
) - if (tag.equals("br/")) { - build.append('\n'); - ++outputIndex; - } else { - // Unknown - build.append('<').append(tag).append('>'); - outputIndex += (tag.length() + 2); - } - } else if (tag.charAt(0) == '/') { - // Opening index for the tag - int openingIndex; - if (openingStack.isEmpty()) { - // Malformed input, just make an empty interval - openingIndex = outputIndex; - } else { - openingIndex = openingStack.pop(); - } - - // What tag is it? - if (tag.equals("/i")) { - // Italics - regions.add(new TextboxRule.ItalicRegion(openingIndex, outputIndex)); - } else if (tag.equals("/b")) { - // Bold, see if it's a level ability - String content = build.substring(openingIndex); - - Matcher levelMatch = LevelAbilityPattern.matcher(content); - if (levelMatch.find()) { - try { - levelFrom = Integer.parseInt(levelMatch.group(1)); - if (!levelMatch.group(2).equals("")) { - levelTo = Integer.parseInt(levelMatch.group(2)); - } - if (!levelMatch.group(3).equals("")) { - levelTo = TextboxLevelRule.AND_HIGHER; - } - isLeveler = true; - } catch (Exception e) { - LOGGER.error("Bad leveler levels in rule `" + rule + "`."); - } - } - } else { - // Unknown - build.append('<').append(tag).append('>'); - outputIndex += (tag.length() + 2); - } - } else { - // Is it a
tag special case? [Why can't it have a closing `/`... =( ] - if (tag.equals("br")) { - build.append('\n'); - ++outputIndex; - } else { - // Opening tag - openingStack.push(outputIndex); - } - } - // Skip characters - index = closeIndex+1; - } else { - // Malformed tag - build.append('<'); - ++outputIndex; - ++index; - } - - } else { - // Normal character - ++index; - ++outputIndex; - build.append(ch); - } - if (outputIndex != build.length()) { - // Somehow our parsing code output symbols but didn't update the output index correspondingly - LOGGER.error("The human is dead; mismatch! Failed on rule: `" + rule + "` due to not updating outputIndex properly."); - - // Bail out - build = new StringBuilder(rule); - regions.clear(); - break; - } - if (index == initialIndex) { - // Somehow our parsing failed to consume the - LOGGER.error("Failed on rule `" + rule + "` due to not consuming a character."); - - // Bail out - build = new StringBuilder(rule); - regions.clear(); - break; - } - } - - // Build and return the rule - rule = build.toString(); - if (isLoyalty) { - return new TextboxLoyaltyRule(rule, regions, loyaltyChange); - } else if (isLeveler) { - return new TextboxLevelRule(rule, regions, levelFrom, levelTo); - } else { - return new TextboxRule(rule, regions); - } - } -} +/* + * 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; + +import java.awt.Image; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import mage.view.CardView; +import org.apache.log4j.Logger; + +/** + * + * @author StravantUser + */ +public class TextboxRuleParser { + + private static final Logger LOGGER = Logger.getLogger(CardPanel.class); + + private static final Pattern LevelAbilityPattern = Pattern.compile("Level (\\d+)-?(\\d*)(\\+?)"); + private static final Pattern LoyaltyAbilityPattern = Pattern.compile("^(\\+|\\-)(\\d+|X): "); + private static final Pattern SimpleKeywordPattern = Pattern.compile("^(\\w+( \\w+)?)\\s*(\\([^\\)]*\\))?\\s*$"); + + // Parse a given rule (given as a string) into a TextboxRule, replacing + // symbol annotations, italics, etc, parsing out information such as + // if the ability is a loyalty ability, and returning an TextboxRule + // representing that information, which can be used to render the rule in + // the textbox of a card. + public static TextboxRule parse(CardView source, String rule) { + // List of regions to apply + ArrayList regions = new ArrayList<>(); + + // Leveler / loyalty + boolean isLeveler = false; + int levelFrom = 0; + int levelTo = 0; + + boolean isLoyalty = false; + int loyaltyChange = 0; + + // Parse the attributedString contents + int index = 0; + int outputIndex = 0; + + // Is it a simple keyword ability? + { + Matcher simpleKeywordMatch = SimpleKeywordPattern.matcher(rule); + if (simpleKeywordMatch.find()) { + return new TextboxKeywordRule(simpleKeywordMatch.group(1), regions); + } + } + + // Check if it's a loyalty ability. Must be right at the start of the rule + { + Matcher loyaltyMatch = LoyaltyAbilityPattern.matcher(rule); + if (loyaltyMatch.find()) { + // Get the loyalty change + if (loyaltyMatch.group(2).equals("X")) { + loyaltyChange = TextboxLoyaltyRule.MINUS_X; + } else { + loyaltyChange = Integer.parseInt(loyaltyMatch.group(2)); + if (loyaltyMatch.group(1).equals("-")) { + loyaltyChange = -loyaltyChange; + } + } + isLoyalty = true; + + // Go past the match + index = loyaltyMatch.group().length(); + } + } + + Deque openingStack = new ArrayDeque<>(); + StringBuilder build = new StringBuilder(); + while (index < rule.length()) { + int initialIndex = index; + char ch = rule.charAt(index); + switch (ch) { + case '{': { + // Handling for `{this}` + int closeIndex = rule.indexOf('}', index); + if (closeIndex == -1) { + // Malformed input, nothing to do + ++index; + ++outputIndex; + build.append(ch); + } else { + String contents = rule.substring(index + 1, closeIndex); + if (contents.equals("this") || contents.equals("source")) { + // Replace {this} with the card's name + String cardName = source.getName(); + build.append(cardName); + index += contents.length() + 2; + outputIndex += cardName.length(); + } else { + Image symbol = ManaSymbols.getSizedManaSymbol(contents.replace("/", ""), 10); + if (symbol != null) { + // Mana or other inline symbol + build.append('#'); + regions.add(new TextboxRule.EmbeddedSymbol(contents, outputIndex)); + ++outputIndex; + index = closeIndex + 1; + } else { + // Bad entry + build.append('{'); + build.append(contents); + build.append('}'); + index = closeIndex + 1; + outputIndex += (contents.length() + 2); + } + } + } + break; + } + case '&': + // Handling for `—` + if (rule.startsWith("—", index)) { + build.append('—'); + index += 7; + ++outputIndex; + } else if (rule.startsWith("&bull", index)) { + build.append('•'); + index += 5; + ++outputIndex; + } else { + LOGGER.error("Bad &...; sequence `" + rule.substring(index + 1, index + 10) + "` in rule."); + build.append('&'); + ++index; + ++outputIndex; + } + break; + case '<': { + // Handling for `` and `
` + int closeIndex = rule.indexOf('>', index); + if (closeIndex != -1) { + // Is a tag + String tag = rule.substring(index + 1, closeIndex); + if (tag.charAt(tag.length() - 1) == '/') { + // Pure closing tag (like
) + if (tag.equals("br/")) { + build.append('\n'); + ++outputIndex; + } else { + // Unknown + build.append('<').append(tag).append('>'); + outputIndex += (tag.length() + 2); + } + } else if (tag.charAt(0) == '/') { + // Opening index for the tag + int openingIndex; + if (openingStack.isEmpty()) { + // Malformed input, just make an empty interval + openingIndex = outputIndex; + } else { + openingIndex = openingStack.pop(); + } + + // What tag is it? + switch (tag) { + case "/i": + // Italics + regions.add(new TextboxRule.ItalicRegion(openingIndex, outputIndex)); + break; + case "/b": + // Bold, see if it's a level ability + String content = build.substring(openingIndex); + Matcher levelMatch = LevelAbilityPattern.matcher(content); + if (levelMatch.find()) { + try { + levelFrom = Integer.parseInt(levelMatch.group(1)); + if (!levelMatch.group(2).equals("")) { + levelTo = Integer.parseInt(levelMatch.group(2)); + } + if (!levelMatch.group(3).equals("")) { + levelTo = TextboxLevelRule.AND_HIGHER; + } + isLeveler = true; + } catch (Exception e) { + LOGGER.error("Bad leveler levels in rule `" + rule + "`."); + } + } + break; + default: + // Unknown + build.append('<').append(tag).append('>'); + outputIndex += (tag.length() + 2); + break; + } + } else // Is it a
tag special case? [Why can't it have a closing `/`... =( ] + { + if (tag.equals("br")) { + build.append('\n'); + ++outputIndex; + } else { + // Opening tag + openingStack.push(outputIndex); + } + } + // Skip characters + index = closeIndex + 1; + } else { + // Malformed tag + build.append('<'); + ++outputIndex; + ++index; + } + break; + } + default: + // Normal character + ++index; + ++outputIndex; + build.append(ch); + break; + } + if (outputIndex != build.length()) { + // Somehow our parsing code output symbols but didn't update the output index correspondingly + LOGGER.error("The human is dead; mismatch! Failed on rule: `" + rule + "` due to not updating outputIndex properly."); + + // Bail out + build = new StringBuilder(rule); + regions.clear(); + break; + } + if (index == initialIndex) { + // Somehow our parsing failed to consume the + LOGGER.error("Failed on rule `" + rule + "` due to not consuming a character."); + + // Bail out + build = new StringBuilder(rule); + regions.clear(); + break; + } + } + + // Build and return the rule + rule = build.toString(); + if (isLoyalty) { + return new TextboxLoyaltyRule(rule, regions, loyaltyChange); + } else if (isLeveler) { + return new TextboxLevelRule(rule, regions, levelFrom, levelTo); + } else { + return new TextboxRule(rule, regions); + } + } +}