diff --git a/resources/magic/data/icons/b_exile_zone.png b/resources/magic/data/icons/b_exile_zone.png new file mode 100644 index 0000000000..958b92e05c Binary files /dev/null and b/resources/magic/data/icons/b_exile_zone.png differ diff --git a/resources/magic/data/icons/b_graveyard_zone.png b/resources/magic/data/icons/b_graveyard_zone.png new file mode 100644 index 0000000000..9d8b21b2a9 Binary files /dev/null and b/resources/magic/data/icons/b_graveyard_zone.png differ diff --git a/resources/magic/data/icons/b_hand_zone.png b/resources/magic/data/icons/b_hand_zone.png new file mode 100644 index 0000000000..a590a4c732 Binary files /dev/null and b/resources/magic/data/icons/b_hand_zone.png differ diff --git a/resources/magic/data/icons/b_library_zone.png b/resources/magic/data/icons/b_library_zone.png new file mode 100644 index 0000000000..756691ae9b Binary files /dev/null and b/resources/magic/data/icons/b_library_zone.png differ diff --git a/src/magic/data/MagicIcon.java b/src/magic/data/MagicIcon.java index efb48cdf93..8f1ce128cd 100644 --- a/src/magic/data/MagicIcon.java +++ b/src/magic/data/MagicIcon.java @@ -66,10 +66,14 @@ public enum MagicIcon { POISON("poison.png"), HAND("hand.gif"), HAND2("hand2.png"), + HAND_ZONE("b_hand_zone.png"), LIBRARY2("library2.gif"), + LIBRARY_ZONE("b_library_zone.png"), GRAVEYARD("graveyard.gif"), GRAVEYARD2("graveyard2.gif"), + GRAVEYARD_ZONE("b_graveyard_zone.png"), EXILE("exile.png"), + EXILE_ZONE("b_exile_zone.png"), DIFFICULTY("difficulty.png"), CANNOTTAP("cannottap.png"), SLEEP("sleep.gif"), diff --git a/src/magic/model/MagicPlayer.java b/src/magic/model/MagicPlayer.java index 9b4a7667fa..7e14d0203c 100644 --- a/src/magic/model/MagicPlayer.java +++ b/src/magic/model/MagicPlayer.java @@ -1,5 +1,10 @@ package magic.model; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; import magic.model.action.MagicLoseGameAction; import magic.model.choice.MagicBuilderManaCost; import magic.model.event.MagicActivationPriority; @@ -8,15 +13,10 @@ import magic.model.event.MagicSourceManaActivation; import magic.model.mstatic.MagicLayer; import magic.model.mstatic.MagicPermanentStatic; import magic.model.mstatic.MagicStatic; +import magic.model.player.AiPlayer; import magic.model.target.MagicTarget; -import magic.model.target.MagicTargetType; import magic.model.target.MagicTargetFilter; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; +import magic.model.target.MagicTargetType; public class MagicPlayer extends MagicObjectImpl implements MagicTarget, MagicMappable { @@ -802,4 +802,8 @@ public class MagicPlayer extends MagicObjectImpl implements MagicTarget, MagicMa public boolean isHuman() { return !getPlayerDefinition().isArtificial(); } + + public boolean isAiPlayerProfile() { + return getPlayerDefinition().getPlayerProfile() instanceof AiPlayer; + } } diff --git a/src/magic/model/MagicPlayerZone.java b/src/magic/model/MagicPlayerZone.java new file mode 100644 index 0000000000..aa1167992b --- /dev/null +++ b/src/magic/model/MagicPlayerZone.java @@ -0,0 +1,8 @@ +package magic.model; + +public enum MagicPlayerZone { + HAND, + LIBRARY, + GRAVEYARD, + EXILE; +} diff --git a/src/magic/ui/GraphicsUtilities.java b/src/magic/ui/GraphicsUtilities.java index 536ec9915a..c21ce2428d 100644 --- a/src/magic/ui/GraphicsUtilities.java +++ b/src/magic/ui/GraphicsUtilities.java @@ -1,49 +1,17 @@ -/* - * $Id$ - * - * Dual-licensed under LGPL (Sun and Romain Guy) and BSD (Romain Guy). - * - * Copyright 2005 Sun Microsystems, Inc., 4150 Network Circle, - * Santa Clara, California 95054, U.S.A. All rights reserved. - * - * Copyright (c) 2006 Romain Guy - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - package magic.ui; +import java.awt.AlphaComposite; import java.awt.Color; -import magic.ui.MagicStyle; import java.awt.Component; +import java.awt.Composite; import java.awt.Cursor; import java.awt.Dimension; import java.awt.GradientPaint; import java.awt.Graphics2D; +import java.awt.Graphics; import java.awt.GraphicsConfiguration; -import java.awt.GraphicsDevice; import java.awt.GraphicsDevice.WindowTranslucency; +import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Paint; import java.awt.Rectangle; @@ -55,11 +23,12 @@ import java.io.IOException; import java.nio.file.Path; import javax.imageio.ImageIO; import javax.swing.BorderFactory; +import javax.swing.ImageIcon; import javax.swing.JComponent; import magic.data.GeneralConfig; import magic.ui.theme.Theme; -import magic.utility.MagicFileSystem; import magic.utility.MagicFileSystem.DataPath; +import magic.utility.MagicFileSystem; /** *

GraphicsUtilities contains a set of tools to perform @@ -257,6 +226,58 @@ final public class GraphicsUtilities { } else { return CardImagesProvider.SMALL_SCREEN_IMAGE_SIZE; } - } + } + + public static BufferedImage getConvertedIcon(final ImageIcon icon) { + final BufferedImage bi = + GraphicsUtilities.getCompatibleBufferedImage( + icon.getIconWidth(), icon.getIconHeight(), Transparency.TRANSLUCENT); + final Graphics g = bi.createGraphics(); + // paint the Icon to the BufferedImage. + icon.paintIcon(null, g, 0, 0); + g.dispose(); + return bi; + } + + public static void drawStringWithOutline(final Graphics g, final String str, int x, int y, Color fillColor, Color outlineColor) { + g.setColor(outlineColor); + for (int i = 1; i <= 1; i++) { + g.drawString(str, x+i, y); + g.drawString(str, x-i, y); + g.drawString(str, x, y+i); + g.drawString(str, x, y-i); + } + g.setColor(fillColor); + g.drawString(str,x,y); + } + + public static void drawStringWithOutline(final Graphics g, final String str, int x, int y) { + drawStringWithOutline(g, str, x, y, Color.WHITE, Color.BLACK); + } + + public static void clearImage(final BufferedImage image) { + final Graphics2D g2d = image.createGraphics(); + final Composite composite = g2d.getComposite(); + g2d.setComposite(AlphaComposite.Clear); + g2d.fillRect(0, 0, image.getWidth(), image.getHeight()); + g2d.setComposite(composite); + g2d.dispose(); + } + + public static BufferedImage getGreyScaleImage(final BufferedImage colorImage) { + + final BufferedImage greyscaleImage = new BufferedImage( + colorImage.getWidth(), + colorImage.getHeight(), + BufferedImage.TYPE_BYTE_GRAY + ); + + final Graphics g = greyscaleImage.createGraphics(); + g.drawImage(colorImage, 0, 0, null); + g.dispose(); + + return greyscaleImage; + } + } diff --git a/src/magic/ui/SwingGameController.java b/src/magic/ui/SwingGameController.java index 191c269e01..50be6def72 100644 --- a/src/magic/ui/SwingGameController.java +++ b/src/magic/ui/SwingGameController.java @@ -1,30 +1,5 @@ package magic.ui; -import magic.exception.UndoClickedException; -import magic.ui.duel.DuelPanel; -import magic.utility.MagicSystem; -import magic.ai.MagicAI; -import magic.data.GeneralConfig; -import magic.data.SoundEffects; -import magic.model.ILogBookListener; -import magic.model.MagicCardDefinition; -import magic.model.MagicCardList; -import magic.model.MagicGame; -import magic.model.MagicLogBookEvent; -import magic.model.MagicPlayer; -import magic.model.MagicSource; -import magic.model.phase.MagicPhaseType; -import magic.model.event.MagicEvent; -import magic.model.event.MagicEventAction; -import magic.model.event.MagicPriorityEvent; -import magic.model.target.MagicTarget; -import magic.model.target.MagicTargetNone; -import magic.ui.duel.viewer.ChoiceViewer; -import magic.ui.duel.viewer.UserActionPanel; - -import javax.swing.JComponent; -import javax.swing.SwingUtilities; - import java.awt.Dimension; import java.awt.KeyEventDispatcher; import java.awt.KeyboardFocusManager; @@ -43,20 +18,41 @@ import java.util.concurrent.Callable; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import javax.swing.JComponent; +import javax.swing.SwingUtilities; +import magic.ai.MagicAI; import magic.data.DuelConfig; +import magic.data.GeneralConfig; import magic.data.MagicIcon; +import magic.data.SoundEffects; import magic.exception.InvalidDeckException; +import magic.exception.UndoClickedException; import magic.game.state.GameState; -import magic.game.state.GameStateSnapshot; import magic.game.state.GameStateFileWriter; +import magic.game.state.GameStateSnapshot; +import magic.model.ILogBookListener; import magic.model.IUIGameController; +import magic.model.MagicCardDefinition; +import magic.model.MagicCardList; import magic.model.MagicColor; +import magic.model.MagicGame; +import magic.model.MagicLogBookEvent; import magic.model.MagicManaCost; import magic.model.MagicObject; +import magic.model.MagicPlayer; +import magic.model.MagicPlayerZone; +import magic.model.MagicSource; import magic.model.MagicSubType; import magic.model.choice.MagicPlayChoiceResult; +import magic.model.event.MagicEvent; +import magic.model.event.MagicEventAction; +import magic.model.event.MagicPriorityEvent; import magic.model.phase.MagicMainPhase; +import magic.model.phase.MagicPhaseType; +import magic.model.target.MagicTarget; +import magic.model.target.MagicTargetNone; import magic.ui.card.AnnotatedCardPanel; +import magic.ui.duel.DuelPanel; import magic.ui.duel.choice.ColorChoicePanel; import magic.ui.duel.choice.ManaCostXChoicePanel; import magic.ui.duel.choice.MayChoicePanel; @@ -64,10 +60,15 @@ import magic.ui.duel.choice.ModeChoicePanel; import magic.ui.duel.choice.MulliganChoicePanel; import magic.ui.duel.choice.MultiKickerChoicePanel; import magic.ui.duel.choice.PlayChoicePanel; +import magic.ui.duel.player.IZoneButtonListener; +import magic.ui.duel.viewer.ChoiceViewer; +import magic.ui.duel.viewer.PlayerViewerInfo; +import magic.ui.duel.viewer.UserActionPanel; import magic.ui.duel.viewer.ViewerInfo; import magic.ui.screen.MulliganScreen; +import magic.utility.MagicSystem; -public class SwingGameController implements IUIGameController, ILogBookListener { +public class SwingGameController implements IUIGameController, ILogBookListener, IZoneButtonListener { private static final GeneralConfig CONFIG = GeneralConfig.getInstance(); @@ -198,6 +199,7 @@ public class SwingGameController implements IUIGameController, ILogBookListener public void switchKeyPressed() { game.setVisiblePlayer(game.getVisiblePlayer().getOpponent()); + gamePanel.switchPlayers(); getViewerInfo().update(game); gamePanel.updateView(); } @@ -933,4 +935,17 @@ public class SwingGameController implements IUIGameController, ILogBookListener return choicePanel.getResult(); } + @Override + public void playerZoneSelected(PlayerViewerInfo playerInfo, MagicPlayerZone zone) { + gamePanel.setActivePlayerZone(playerInfo, zone); + } + + @Override + public void playerZoneSelectedClicked(PlayerViewerInfo playerInfo, MagicPlayerZone zone) { + gamePanel.setFullScreenActivePlayerZone(playerInfo, zone); + } + + public void setPlayerZone(PlayerViewerInfo playerInfo, MagicPlayerZone zone) { + System.err.printf("TODO SwingGameController.setPlayerZone : %s, %s\n", playerInfo.name, zone); + } } diff --git a/src/magic/ui/duel/BattlefieldPanel.java b/src/magic/ui/duel/BattlefieldPanel.java index 7406df074b..64c141e33f 100644 --- a/src/magic/ui/duel/BattlefieldPanel.java +++ b/src/magic/ui/duel/BattlefieldPanel.java @@ -1,10 +1,12 @@ package magic.ui.duel; -import magic.ui.duel.animation.PlayCardAnimation; import javax.swing.JPanel; import magic.model.MagicCardList; +import magic.model.MagicPlayerZone; import magic.model.event.MagicEvent; +import magic.ui.duel.animation.PlayCardAnimation; import magic.ui.duel.resolution.ResolutionProfileResult; +import magic.ui.duel.viewer.PlayerViewerInfo; import magic.ui.duel.viewer.StackViewer; @SuppressWarnings("serial") @@ -26,4 +28,7 @@ public abstract class BattlefieldPanel extends JPanel { public abstract StackViewer getStackViewer(); + public abstract void setActivePlayerZone(PlayerViewerInfo playerInfo, MagicPlayerZone zone); + + public abstract void setFullScreenActivePlayerZone(PlayerViewerInfo playerInfo, MagicPlayerZone zone); } diff --git a/src/magic/ui/duel/BattlefieldTextOverlay.java b/src/magic/ui/duel/BattlefieldTextOverlay.java new file mode 100644 index 0000000000..928ca7b51a --- /dev/null +++ b/src/magic/ui/duel/BattlefieldTextOverlay.java @@ -0,0 +1,58 @@ +package magic.ui.duel; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Transparency; +import java.awt.image.BufferedImage; +import magic.ui.GraphicsUtilities; + + +public class BattlefieldTextOverlay { + + private static final Font ZONE_FONT = new Font("Dialog", Font.BOLD, 12); + + private BufferedImage textOverlayImage = null; + private String zoneName = ""; + private boolean refreshOverlay = true; + + public BufferedImage getOverlayImage(final int battlefieldWidth, final int batttlefieldHeight) { + + final boolean createNewImage = + textOverlayImage == null || + textOverlayImage.getHeight() != batttlefieldHeight || + textOverlayImage.getWidth() != battlefieldWidth; + + if (createNewImage) { + textOverlayImage = GraphicsUtilities.getCompatibleBufferedImage( + battlefieldWidth, batttlefieldHeight, Transparency.BITMASK + ); + } + + if (refreshOverlay) { + refreshOverlay = false; + GraphicsUtilities.clearImage(textOverlayImage); + final Graphics2D g2d = textOverlayImage.createGraphics(); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + drawZoneName(g2d); + g2d.dispose(); + } + + return textOverlayImage; + } + + private void drawZoneName(final Graphics2D g2d) { + g2d.setFont(ZONE_FONT); + final int textWidth = g2d.getFontMetrics(ZONE_FONT).stringWidth(zoneName); + final int textX = (textOverlayImage.getWidth() / 2) - (textWidth / 2); + final int textY = textOverlayImage.getHeight() - 4; + GraphicsUtilities.drawStringWithOutline(g2d, zoneName, textX, textY, Color.YELLOW, Color.DARK_GRAY); + } + + public void setPlayerZoneName(final String text) { + this.zoneName = text; + refreshOverlay = true; + } + +} diff --git a/src/magic/ui/duel/CounterOverlay.java b/src/magic/ui/duel/CounterOverlay.java new file mode 100644 index 0000000000..2e9051208a --- /dev/null +++ b/src/magic/ui/duel/CounterOverlay.java @@ -0,0 +1,103 @@ +package magic.ui.duel; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Transparency; +import java.awt.geom.Ellipse2D; +import java.awt.image.BufferedImage; +import magic.ui.GraphicsUtilities; + +public class CounterOverlay { + + private static final Font VALUE_FONT = new Font("Dialog", Font.BOLD, 14); + + private final BufferedImage image; + private int counterValue = 0; + private Color counterColor; + + public CounterOverlay(final int width, final int height, final Color color) { + this.counterColor = color; + image = GraphicsUtilities.getCompatibleBufferedImage(width, height, Transparency.TRANSLUCENT); + drawCounter(); + } + + public BufferedImage getCounterImage() { + return image; + } + + private void drawCounter() { + GraphicsUtilities.clearImage(image); + final Graphics2D g2d = image.createGraphics(); +// drawBorder(g2d); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + drawCounterShape(g2d); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + drawCounterValue(g2d); + g2d.dispose(); + } + + private void drawCounterShape(final Graphics2D g2d) { + drawFillShape(g2d); +// drawOutlineShape(g2d); + } + + private void setShapeFrame(final Ellipse2D shape, final float strokeWidth) { + final float strokeAdj = strokeWidth / 2.0f; + final float origin = strokeAdj; + final float sizeAdj = strokeWidth + 1.0f; + final float shapeWidth = (float)image.getWidth() - sizeAdj; + final float shapeHeight = (float)image.getHeight() - sizeAdj; + shape.setFrame(origin, origin, shapeWidth, shapeHeight); + } + + private void drawFillShape(final Graphics2D g2d) { + final float strokeWidth = 1.0f; + final Ellipse2D shape = new Ellipse2D.Float(); + setShapeFrame(shape, strokeWidth); + g2d.setStroke(new BasicStroke(strokeWidth)); + g2d.setColor(counterColor); + g2d.fill(shape); + } + + private void drawOutlineShape(final Graphics2D g2d) { + final float strokeWidth = 2.1f; // for some reason the circle appears asymmetrical when 2.0f. + final Ellipse2D shape = new Ellipse2D.Float(); + setShapeFrame(shape, strokeWidth); + g2d.setStroke(new BasicStroke(strokeWidth)); + g2d.setColor(counterColor); + g2d.draw(shape); + } + + private void drawCounterValue(final Graphics2D g2d) { + g2d.setFont(VALUE_FONT); +// FontRenderContext frc = g2d.getFontRenderContext(); +// final int textHeight = Math.round(VALUE_FONT.getLineMetrics(text, frc).getHeight()); + final String text = Integer.toString(counterValue); + final FontMetrics metrics = g2d.getFontMetrics(VALUE_FONT); + final int textWidth = metrics.stringWidth(text); + final int textX = image.getWidth(null) / 2 - textWidth / 2; + final int textY = image.getHeight(null) / 2 + 4; + GraphicsUtilities.drawStringWithOutline(g2d, text, textX, textY); + } + + public void setCounterValue(final int value) { + if (this.counterValue != value) { + this.counterValue = value; + drawCounter(); + } + } + + /** + * useful for debugging purposes. + */ + private void drawBorder(final Graphics2D g2d) { + g2d.setStroke(new BasicStroke(1)); + g2d.setColor(new Color(255, 0, 0, 200)); + g2d.fillRect(0, 0, image.getWidth()-1, image.getHeight()-1); + } + +} diff --git a/src/magic/ui/duel/DuelPanel.java b/src/magic/ui/duel/DuelPanel.java index 5545596db5..7b092eeb45 100644 --- a/src/magic/ui/duel/DuelPanel.java +++ b/src/magic/ui/duel/DuelPanel.java @@ -17,6 +17,7 @@ import magic.data.GeneralConfig; import magic.exception.InvalidDeckException; import magic.model.MagicCardList; import magic.model.MagicGame; +import magic.model.MagicPlayerZone; import magic.model.event.MagicEvent; import magic.ui.SwingGameController; import magic.ui.MagicFrame; @@ -28,6 +29,7 @@ import magic.ui.duel.resolution.DefaultResolutionProfile; import magic.ui.duel.resolution.ResolutionProfileResult; import magic.ui.duel.resolution.ResolutionProfiles; import magic.ui.duel.viewer.LogBookViewer; +import magic.ui.duel.viewer.PlayerViewerInfo; import magic.ui.widget.ZoneBackgroundLabel; import net.miginfocom.swing.MigLayout; @@ -226,7 +228,6 @@ public final class DuelPanel extends JPanel { final Dimension size = getSize(); result = ResolutionProfiles.calculate(size); backgroundLabel.setZones(result); - sidebarPanel.resizeComponents(result); battlefieldPanel.resizeComponents(result); setGamePanelLayout(); // defer until all pending events on the EDT have been processed. @@ -289,7 +290,6 @@ public final class DuelPanel extends JPanel { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - sidebarPanel.setStartEndTurnState(); sidebarPanel.getGameStatusPanel().showNewTurnNotification(game); } }); @@ -312,11 +312,21 @@ public final class DuelPanel extends JPanel { public void showEndGameMessage() { dialogPanel.showEndGameMessage(controller); - sidebarPanel.setStartEndTurnState(); } public JPanel getDialogPanel() { return dialogPanel; } + public void setActivePlayerZone(PlayerViewerInfo playerInfo, MagicPlayerZone zone) { + battlefieldPanel.setActivePlayerZone(playerInfo, zone); + } + + public void setFullScreenActivePlayerZone(PlayerViewerInfo playerInfo, MagicPlayerZone zone) { + battlefieldPanel.setFullScreenActivePlayerZone(playerInfo, zone); + } + + public void switchPlayers() { + sidebarPanel.switchPlayers(); + } } diff --git a/src/magic/ui/duel/DuelSideBarPanel.java b/src/magic/ui/duel/DuelSideBarPanel.java index e0551f70e6..e77c776a84 100644 --- a/src/magic/ui/duel/DuelSideBarPanel.java +++ b/src/magic/ui/duel/DuelSideBarPanel.java @@ -1,12 +1,10 @@ package magic.ui.duel; -import magic.ui.duel.viewer.PlayerViewer; import javax.swing.JPanel; import magic.data.GeneralConfig; import magic.ui.SwingGameController; +import magic.ui.duel.player.GamePlayerPanel; import magic.ui.duel.resolution.DefaultResolutionProfile; -import magic.ui.duel.resolution.ResolutionProfileResult; -import magic.ui.duel.resolution.ResolutionProfileType; import magic.ui.duel.viewer.GameStatusPanel; import magic.ui.duel.viewer.LogBookViewer; import magic.ui.duel.viewer.LogStackViewer; @@ -19,17 +17,20 @@ public class DuelSideBarPanel extends JPanel { private static final GeneralConfig CONFIG = GeneralConfig.getInstance(); - private final PlayerViewer opponentViewer; - private final PlayerViewer playerViewer; + private final GamePlayerPanel opponentViewer; + private final GamePlayerPanel playerViewer; private final LogStackViewer logStackViewer; private final LogBookViewer logBookViewer; private final GameStatusPanel gameStatusPanel; + private final SwingGameController controller; + private boolean isSwitchedPlayers = false; DuelSideBarPanel(final SwingGameController controller, final StackViewer imageStackViewer) { + this.controller = controller; setOpaque(false); // - opponentViewer = new PlayerViewer(controller, true); - playerViewer = new PlayerViewer(controller, false); + opponentViewer = new GamePlayerPanel(controller, controller.getViewerInfo().getPlayerInfo(true)); + playerViewer = new GamePlayerPanel(controller, controller.getViewerInfo().getPlayerInfo(false)); logBookViewer = new LogBookViewer(controller.getGame().getLogBook()); logBookViewer.setVisible(!CONFIG.isLogViewerDisabled()); logStackViewer = new LogStackViewer(logBookViewer, imageStackViewer); @@ -54,31 +55,25 @@ public class DuelSideBarPanel extends JPanel { removeAll(); setLayout(new MigLayout("insets " + insets + ", gap 0 10, flowy")); - add(opponentViewer, "w " + maxWidth + "!, h " + DefaultResolutionProfile.PLAYER_VIEWER_HEIGHT_SMALL + "!"); + add(isSwitchedPlayers ? playerViewer : opponentViewer, "w " + maxWidth + "!, h " + DefaultResolutionProfile.PLAYER_VIEWER_HEIGHT_SMALL + "!"); add(logStackViewer, "w " + maxWidth + "!, h 100%"); add(gameStatusPanel, "w " + maxWidth + "!, h " + DefaultResolutionProfile.GAME_VIEWER_HEIGHT + "!"); - add(playerViewer, "w " + maxWidth + "!, h " + DefaultResolutionProfile.PLAYER_VIEWER_HEIGHT_SMALL + "!"); + add(isSwitchedPlayers ? opponentViewer : playerViewer, "w " + maxWidth + "!, h " + DefaultResolutionProfile.PLAYER_VIEWER_HEIGHT_SMALL + "!"); logStackViewer.setLogStackLayout(); } void doUpdate() { - opponentViewer.update(); - playerViewer.update(); + opponentViewer.updateDisplay(controller.getViewerInfo().getPlayerInfo(!isSwitchedPlayers)); + playerViewer.updateDisplay(controller.getViewerInfo().getPlayerInfo(isSwitchedPlayers)); gameStatusPanel.update(); } - void resizeComponents(final ResolutionProfileResult result) { - opponentViewer.setSmall(result.getFlag(ResolutionProfileType.GamePlayerViewerSmall)); - playerViewer.setBounds(result.getBoundary(ResolutionProfileType.GamePlayerViewer)); - playerViewer.setSmall(result.getFlag(ResolutionProfileType.GamePlayerViewerSmall)); - gameStatusPanel.setBounds(result.getBoundary(ResolutionProfileType.GameStatusPanel)); - } - - void setStartEndTurnState() { - opponentViewer.getAvatarPanel().setSelected(false); - playerViewer.getAvatarPanel().setSelected(false); + void switchPlayers() { + isSwitchedPlayers = !isSwitchedPlayers; + doSetLayout(); + revalidate(); } } diff --git a/src/magic/ui/duel/ImageModeBattlefieldPanel.java b/src/magic/ui/duel/ImageModeBattlefieldPanel.java index 0c35859bdb..b3343d204d 100644 --- a/src/magic/ui/duel/ImageModeBattlefieldPanel.java +++ b/src/magic/ui/duel/ImageModeBattlefieldPanel.java @@ -1,12 +1,17 @@ package magic.ui.duel; +import java.awt.Graphics; +import java.awt.Image; import magic.ui.duel.animation.PlayCardAnimation; import java.awt.Point; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import javax.swing.JComponent; import javax.swing.SwingUtilities; import magic.model.MagicCardDefinition; import magic.model.MagicCardList; import magic.model.MagicPlayer; +import magic.model.MagicPlayerZone; import magic.model.event.MagicEvent; import magic.ui.SwingGameController; import magic.ui.duel.resolution.ResolutionProfileResult; @@ -15,6 +20,7 @@ import magic.ui.duel.viewer.ImageBattlefieldViewer; import magic.ui.duel.viewer.ImageCardListViewer; import magic.ui.duel.viewer.ImageCombatViewer; import magic.ui.duel.viewer.ImageHandGraveyardExileViewer; +import magic.ui.duel.viewer.PlayerViewerInfo; import magic.ui.duel.viewer.StackViewer; @SuppressWarnings("serial") @@ -22,6 +28,8 @@ public class ImageModeBattlefieldPanel extends BattlefieldPanel { private PlayCardAnimation animationEvent = null; + private final BattlefieldTextOverlay textOverlay = new BattlefieldTextOverlay(); + private final ImageHandGraveyardExileViewer imageHandGraveyardViewer; private final ImageBattlefieldViewer imagePlayerPermanentViewer; private final ImageBattlefieldViewer imageOpponentPermanentViewer; @@ -37,6 +45,16 @@ public class ImageModeBattlefieldPanel extends BattlefieldPanel { imageCombatViewer = new ImageCombatViewer(controller); imageStackViewer = new StackViewer(controller, true); // + imageHandGraveyardViewer.addPropertyChangeListener( + ImageHandGraveyardExileViewer.CP_PLAYER_ZONE, + new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + textOverlay.setPlayerZoneName((String) evt.getNewValue()); + repaint(); + } + }); + // setLayout(null); add(imageStackViewer); add(imageHandGraveyardViewer); @@ -137,5 +155,22 @@ public class ImageModeBattlefieldPanel extends BattlefieldPanel { animationEvent = event; } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + final Image overlayImage = textOverlay.getOverlayImage(getWidth(), getHeight()); + g.drawImage(overlayImage, 0, 0, this); + } + + @Override + public void setActivePlayerZone(PlayerViewerInfo playerInfo, MagicPlayerZone zone) { + imageHandGraveyardViewer.setActivePlayerZone(playerInfo, zone); + } + + @Override + public void setFullScreenActivePlayerZone(PlayerViewerInfo playerInfo, MagicPlayerZone zone) { + imageHandGraveyardViewer.setFullScreenActivePlayerZone(playerInfo, zone); + } } diff --git a/src/magic/ui/duel/TextModeBattlefieldPanel.java b/src/magic/ui/duel/TextModeBattlefieldPanel.java index f0cc9a214e..12b2a76501 100644 --- a/src/magic/ui/duel/TextModeBattlefieldPanel.java +++ b/src/magic/ui/duel/TextModeBattlefieldPanel.java @@ -1,13 +1,15 @@ package magic.ui.duel; -import magic.ui.duel.animation.PlayCardAnimation; import magic.model.MagicCardList; +import magic.model.MagicPlayerZone; import magic.model.event.MagicEvent; import magic.ui.SwingGameController; +import magic.ui.duel.animation.PlayCardAnimation; import magic.ui.duel.resolution.ResolutionProfileResult; import magic.ui.duel.resolution.ResolutionProfileType; import magic.ui.duel.viewer.BattlefieldViewer; import magic.ui.duel.viewer.HandGraveyardExileViewer; +import magic.ui.duel.viewer.PlayerViewerInfo; import magic.ui.duel.viewer.StackCombatViewer; import magic.ui.duel.viewer.StackViewer; @@ -81,4 +83,13 @@ public class TextModeBattlefieldPanel extends BattlefieldPanel { // not implemented } + @Override + public void setActivePlayerZone(PlayerViewerInfo playerInfo, MagicPlayerZone zone) { + // not applicable in text mode, do nothing. + } + + @Override + public void setFullScreenActivePlayerZone(PlayerViewerInfo playerInfo, MagicPlayerZone zone) { + // not applicable in text mode, do nothing. + } } diff --git a/src/magic/ui/duel/player/GamePlayerPanel.java b/src/magic/ui/duel/player/GamePlayerPanel.java new file mode 100644 index 0000000000..1fd5633e8c --- /dev/null +++ b/src/magic/ui/duel/player/GamePlayerPanel.java @@ -0,0 +1,88 @@ +package magic.ui.duel.player; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.util.Set; +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import magic.model.player.AiPlayer; +import magic.ui.SwingGameController; +import magic.ui.duel.viewer.ChoiceViewer; +import magic.ui.duel.viewer.PlayerViewerInfo; +import magic.ui.theme.ThemeFactory; +import magic.ui.widget.PanelButton; +import magic.ui.widget.TexturedPanel; +import net.miginfocom.swing.MigLayout; + +@SuppressWarnings("serial") +public class GamePlayerPanel extends TexturedPanel implements ChoiceViewer { + + private PlayerViewerInfo playerInfo; + private PlayerZoneButtonsPanel zoneButtonsPanel; + private PlayerImagePanel avatarPanel; + private final PanelButton avatarButton; + + public GamePlayerPanel(final SwingGameController controller, final PlayerViewerInfo playerInfo) { + + this.playerInfo = playerInfo; + + setOpaque(true); + setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.BLACK)); + + setPreferredSize(new Dimension(0, 80)); + setMinimumSize(getPreferredSize()); + + zoneButtonsPanel = new PlayerZoneButtonsPanel(playerInfo); + + assert controller instanceof IZoneButtonListener; + zoneButtonsPanel.addZoneButtonListener(controller); + + avatarPanel = new PlayerImagePanel(playerInfo, controller.getGame()); + + avatarButton = new PanelButton() { + @Override + public Color getValidColor() { + return ThemeFactory.getInstance().getCurrentTheme().getChoiceColor(); + } + @Override + public void mouseClicked() { + controller.processClick(playerInfo.player); + } + }; + avatarButton.setComponent(avatarPanel); + + setLayout(new MigLayout("flowy, insets 0, gap 4 1, wrap 2")); + add(avatarButton, "w 80!, h 80!, spany 2"); + add(getPlayerLabel(), "gaptop 3"); + add(zoneButtonsPanel, "w 100%, h 100%"); + + if (controller != null) { + controller.registerChoiceViewer(this); + } + + } + + private JLabel getPlayerLabel() { + final StringBuffer sb = new StringBuffer(playerInfo.name); + if (playerInfo.player.isAiPlayerProfile()) { + final AiPlayer aiPlayer = (AiPlayer)playerInfo.player.getPlayerDefinition().getPlayerProfile(); + sb.append(", level ").append(aiPlayer.getAiLevel()).append(" AI (").append(aiPlayer.getAiType().name()).append(")"); + } + final JLabel lbl = new JLabel(sb.toString()); + lbl.setFont(new Font("dialog", Font.PLAIN, 9)); + return lbl; + } + + @Override + public void showValidChoices(Set validChoices) { + avatarButton.setValid(!validChoices.isEmpty() ? validChoices.contains(playerInfo.player) : false); + } + + public void updateDisplay(final PlayerViewerInfo playerInfo) { + this.playerInfo = playerInfo; + avatarPanel.updateDisplay(playerInfo); + zoneButtonsPanel.updateDisplay(playerInfo); + } + +} diff --git a/src/magic/ui/duel/player/IZoneButtonListener.java b/src/magic/ui/duel/player/IZoneButtonListener.java new file mode 100644 index 0000000000..98133af07e --- /dev/null +++ b/src/magic/ui/duel/player/IZoneButtonListener.java @@ -0,0 +1,9 @@ +package magic.ui.duel.player; + +import magic.model.MagicPlayerZone; +import magic.ui.duel.viewer.PlayerViewerInfo; + +public interface IZoneButtonListener { + void playerZoneSelected(PlayerViewerInfo playerInfo, MagicPlayerZone zone); + void playerZoneSelectedClicked(PlayerViewerInfo playerInfo, MagicPlayerZone zone); +} diff --git a/src/magic/ui/duel/player/PlayerImagePanel.java b/src/magic/ui/duel/player/PlayerImagePanel.java new file mode 100644 index 0000000000..6937577cdc --- /dev/null +++ b/src/magic/ui/duel/player/PlayerImagePanel.java @@ -0,0 +1,121 @@ +package magic.ui.duel.player; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import javax.swing.ImageIcon; +import javax.swing.JPanel; +import magic.model.MagicGame; +import magic.ui.GraphicsUtilities; +import magic.ui.IconImages; +import magic.ui.duel.CounterOverlay; +import magic.ui.duel.viewer.PlayerViewerInfo; + +@SuppressWarnings("serial") +public class PlayerImagePanel extends JPanel { + + private static final Font HEALTH_FONT = new Font("Dialog", Font.BOLD, 20); + + private final MagicGame game; + private final CounterOverlay poisonCounter; + private final CounterOverlay damageCounter; + private final BufferedImage activeImage; + private final BufferedImage inactiveImage; + private PlayerViewerInfo playerInfo; + + public PlayerImagePanel(final PlayerViewerInfo player, final MagicGame game) { + this.playerInfo = player; + this.game = game; + activeImage = getPlayerAvatarImage(); + inactiveImage = GraphicsUtilities.getGreyScaleImage(activeImage); + poisonCounter = new CounterOverlay(20, 20, Color.GREEN); + damageCounter = new CounterOverlay(20, 20, Color.CYAN); + } + + private BufferedImage getPlayerAvatarImage() { + final ImageIcon icon = IconImages.getIconSize3(this.playerInfo.player.getPlayerDefinition()); + return GraphicsUtilities.scale(GraphicsUtilities.getConvertedIcon(icon), 74, 74); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + final Graphics2D g2d = (Graphics2D) g; + + if (playerInfo.player == game.getTurnPlayer()) { + g2d.drawImage(activeImage, 0, 0, this); +// drawPlayerHighlight(g2d); + } else { + g2d.drawImage(inactiveImage, 0, 0, this); + } + + // counters + drawPoisonCountersOverlay(g2d); + drawShieldDamageOverlay(g2d); + // health + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + drawHealthValueOverlay(g2d, 0, 0, activeImage); + } + + private void drawPlayerHighlight(final Graphics2D g2d) { + g2d.setStroke(new BasicStroke(6.0f)); + g2d.setColor(new Color(255, 255, 0, 220)); + g2d.drawRect(0, 0, getWidth(), getHeight()); + } + + private void drawHealthValueOverlay(final Graphics2D g2d, final int x, final int y, final Image image) { + g2d.setFont(HEALTH_FONT); + final String text = Integer.toString(playerInfo.life); + final int textX = x + 4; + final int textY = y + image.getHeight(null) - 6; + GraphicsUtilities.drawStringWithOutline(g2d, text, textX, textY); + } + + public void updateDisplay(final PlayerViewerInfo playerInfo) { + this.playerInfo = playerInfo; + repaint(); + } + + private void drawPoisonCountersOverlay(final Graphics2D g2d) { + if (playerInfo.poison > 0) { + poisonCounter.setCounterValue(playerInfo.poison); + final BufferedImage counterImage = poisonCounter.getCounterImage(); + g2d.drawImage( + counterImage, + getWidth() - counterImage.getWidth() - 4, + getHeight() - counterImage.getHeight() - 4, + null); + } + } + + private void drawShieldDamageOverlay(final Graphics2D g2d) { + if (playerInfo.preventDamage > 0) { + damageCounter.setCounterValue(playerInfo.preventDamage); + final BufferedImage counterImage = damageCounter.getCounterImage(); + g2d.drawImage( + counterImage, + getWidth() - counterImage.getWidth() - 4, + 4, + null); + } + + } + +// private void drawHealthGauge(final Graphics2D g2d) { +// g2d.setStroke(new BasicStroke(1)); +// g2d.setColor(Color.BLACK); +// final int w = 10; +// g2d.drawRect(avatarX-w, avatarY, w, avatarImage.getHeight(null) - 1); +// final Paint borderColor = new GradientPaint(avatarX-w, avatarY, Color.GREEN, avatarX-w, avatarY + avatarImage.getHeight(null), Color.RED, false); +// g2d.setPaint(borderColor); +// g2d.fillRect(avatarX-w+1, avatarY+1, w-1, avatarImage.getHeight(null) - 2); +// } + + +} diff --git a/src/magic/ui/duel/player/PlayerZoneButtonsPanel.java b/src/magic/ui/duel/player/PlayerZoneButtonsPanel.java new file mode 100644 index 0000000000..b6ae9004fb --- /dev/null +++ b/src/magic/ui/duel/player/PlayerZoneButtonsPanel.java @@ -0,0 +1,158 @@ +package magic.ui.duel.player; + +import java.awt.Cursor; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; +import javax.swing.ButtonGroup; +import javax.swing.JPanel; +import javax.swing.JToggleButton; +import javax.swing.SwingUtilities; +import magic.data.MagicIcon; +import magic.model.MagicPlayerZone; +import magic.ui.duel.viewer.PlayerViewerInfo; +import net.miginfocom.swing.MigLayout; + +@SuppressWarnings("serial") +public class PlayerZoneButtonsPanel extends JPanel { + + private static ButtonGroup buttonGroup = new ButtonGroup(); + + private final ZoneToggleButton libraryZoneButton; + private final ZoneToggleButton handZoneButton; + private final ZoneToggleButton graveyardZoneButton; + private final ZoneToggleButton exileZoneButton; + private final List zoneButtonlisteners = new ArrayList<>(); + private PlayerViewerInfo playerInfo; + + public PlayerZoneButtonsPanel(final PlayerViewerInfo playerInfo) { + + this.playerInfo = playerInfo; + + setLayout(new MigLayout("insets 0 2 0 0")); + + libraryZoneButton = getZoneToggleButton( + MagicPlayerZone.LIBRARY, MagicIcon.LIBRARY_ZONE, playerInfo.library.size(), "Library", false, true); + handZoneButton = getZoneToggleButton( + MagicPlayerZone.HAND, MagicIcon.HAND_ZONE, playerInfo.hand.size(), "Hand", false, true); + graveyardZoneButton = getZoneToggleButton( + MagicPlayerZone.GRAVEYARD, MagicIcon.GRAVEYARD_ZONE, playerInfo.graveyard.size(), "Graveyard", false, true); + exileZoneButton = getZoneToggleButton( + MagicPlayerZone.EXILE, MagicIcon.EXILE_ZONE, playerInfo.exile.size(), "Exile", false, true); + + add(libraryZoneButton); + add(handZoneButton); + add(graveyardZoneButton); + add(exileZoneButton); +// add(new JLabel(IconImages.getIcon(MagicIcon.WAITING))); + + if (buttonGroup.getButtonCount() > 0) { + buttonGroup.getElements().nextElement().setSelected(true); + } + + setOpaque(false); + + } + + public synchronized void addZoneButtonListener(final IZoneButtonListener listener) { + zoneButtonlisteners.add(listener); + } + + public synchronized void removeZoneButtonListener(final IZoneButtonListener listener) { + zoneButtonlisteners.remove(listener); + } + + private ZoneToggleButton getZoneToggleButton( + final MagicPlayerZone zone, + final MagicIcon icon, + final int cardCount, + final String tooltip, + final boolean isActive, + final boolean isAnimated) { + + final ZoneToggleButton btn = new ZoneToggleButton(zone, icon, cardCount, isActive, isAnimated); + btn.setToolTipText(tooltip); + if (isActive) { + buttonGroup.add(btn); + } else { + btn.setEnabled(false); + } +// btn.addChangeListener(new ChangeListener() { +// @Override +// public void stateChanged(ChangeEvent ev) { +// final JToggleButton btn = (JToggleButton) ev.getSource(); +// System.out.println("isSelected=" + btn.isSelected()); +// } +// }); + btn.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent ev) { + if (ev.getStateChange() == ItemEvent.SELECTED) { + final ZoneToggleButton b = (ZoneToggleButton) ev.getSource(); + notifyToggleButtonSelected(b.getPlayerZone()); + } + } + }); + btn.addMouseListener(new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent ev) { + final JToggleButton btn = (JToggleButton) ev.getSource(); + if (btn.isEnabled()) { + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + } + @Override + public void mouseExited(MouseEvent e) { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + @Override + public void mousePressed(MouseEvent ev) { + final ZoneToggleButton b = (ZoneToggleButton) ev.getSource(); + if (b.isSelected()) { + notifyToggleButtonSelectedClicked(b.getPlayerZone()); + } + } + }); + return btn; + } + + private void notifyToggleButtonSelectedClicked(final MagicPlayerZone zone) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + for (final IZoneButtonListener listener : zoneButtonlisteners) { + listener.playerZoneSelectedClicked(playerInfo, zone); + } + } + }); + } + + private void notifyToggleButtonSelected(final MagicPlayerZone zone) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + for (final IZoneButtonListener listener : zoneButtonlisteners) { + listener.playerZoneSelected(playerInfo, zone); + } + } + }); + } + + void updateDisplay(final PlayerViewerInfo playerInfo) { + this.playerInfo = playerInfo; + if (playerInfo != null) { + handZoneButton.setNumberOfCardsInZone(playerInfo.hand.size()); + libraryZoneButton.setNumberOfCardsInZone(playerInfo.library.size()); + graveyardZoneButton.setNumberOfCardsInZone(playerInfo.graveyard.size()); + exileZoneButton.setNumberOfCardsInZone(playerInfo.exile.size()); + } + } + + public static void clearButtonGroup() { + buttonGroup = new ButtonGroup(); + } + +} diff --git a/src/magic/ui/duel/player/ZoneToggleButton.java b/src/magic/ui/duel/player/ZoneToggleButton.java new file mode 100644 index 0000000000..10ff347d92 --- /dev/null +++ b/src/magic/ui/duel/player/ZoneToggleButton.java @@ -0,0 +1,174 @@ +package magic.ui.duel.player; + +import java.awt.BasicStroke; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.RenderingHints; +import java.awt.Transparency; +import java.awt.font.FontRenderContext; +import java.awt.image.BufferedImage; +import javax.swing.JToggleButton; +import magic.data.MagicIcon; +import magic.model.MagicPlayerZone; +import magic.ui.GraphicsUtilities; +import magic.ui.IconImages; +import magic.ui.MagicStyle; +import org.pushingpixels.trident.Timeline; +import org.pushingpixels.trident.callback.TimelineCallback; + + +@SuppressWarnings("serial") +public class ZoneToggleButton extends JToggleButton implements TimelineCallback { + + enum ValueStyle { + NORMAL, + OUTLINE + } + + private static final Font ZONE_FONT = new Font("Dialog", Font.BOLD, 14); + + private BufferedImage zoneIconImage = null; + private final MagicIcon magicIcon; + private String cardCountString = "0"; + private final ValueStyle valueStyle; + private final MagicPlayerZone playerZone; + private Timeline timeline1; + private int imageOffset = 0; + private boolean animateOnChange = false; + + public int getImageOffset() { + return imageOffset; + } + + public void setImageOffset(int value) { + this.imageOffset = value * 2; + repaint(); + } + + // CTR + private ZoneToggleButton( + final MagicPlayerZone playerZone, + final MagicIcon icon, + final int cardCount, + final ValueStyle valueStyle, + final boolean isActive, + final boolean isAnimated) { + + this.playerZone = playerZone; + this.magicIcon = icon; + this.valueStyle = valueStyle; + this.animateOnChange = false; + setEnabled(isActive); + setNumberOfCardsInZone(cardCount); + setMinimumSize(new Dimension(40, 60)); + } + // CTR + ZoneToggleButton( + final MagicPlayerZone playerZone, + final MagicIcon icon, + final int cardCount, + final boolean isActive, + final boolean isAnimated) { + + this(playerZone, icon, cardCount, ValueStyle.NORMAL, isActive, isAnimated); + + } + + public MagicPlayerZone getPlayerZone() { + return playerZone; + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + final Image image = getZoneIconAsImage(); + final int x = getWidth() / 2 - image.getWidth(null) / 2; + + if (animateOnChange) { + g.drawImage(image, x, 4, x+32, 4+32, 0+imageOffset, 0+imageOffset, 32-imageOffset, 32-imageOffset, null); + } else { + g.drawImage(image, x, 4, null); + } + + drawZoneValueOverlay((Graphics2D)g, cardCountString, x, getHeight(), image); + + if (isSelected()) { + drawSelectedBorder(g); + } + } + + private void drawSelectedBorder(Graphics g) { + final Graphics2D g2d = (Graphics2D) g; + g2d.setStroke(new BasicStroke(4.0f)); + g2d.setColor(MagicStyle.HIGHLIGHT_COLOR); + g2d.drawRect(0, 0, getWidth(), getHeight()); + + } + + private BufferedImage getZoneIconAsImage() { + if (zoneIconImage == null) { + zoneIconImage = GraphicsUtilities.getCompatibleBufferedImage(32, 32, Transparency.TRANSLUCENT); + Graphics2D g2d = (Graphics2D) zoneIconImage.getGraphics(); + final Image iconImage = GraphicsUtilities.getConvertedIcon(IconImages.getIcon(magicIcon)); + g2d.drawImage(iconImage, 0, 0, this); + g2d.dispose(); + } + return zoneIconImage; + } + + private void drawZoneValueOverlay(Graphics2D g2d, String text, int x, int y, final Image iconImage) { + g2d.setFont(ZONE_FONT); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + FontRenderContext frc = g2d.getFontRenderContext(); + final int textHeight = Math.round(ZONE_FONT.getLineMetrics(text, frc).getHeight()); + final FontMetrics metrics = g2d.getFontMetrics(ZONE_FONT); + int textWidth = metrics.stringWidth(text); + int textX = x + ((iconImage.getWidth(null) / 2) - (textWidth / 2)); + int textY = y - 6; + if (valueStyle == ValueStyle.OUTLINE) { + GraphicsUtilities.drawStringWithOutline(g2d, text, textX, textY); + } else { + g2d.drawString(text, textX, textY); + } + } + + final void setNumberOfCardsInZone(final int cardCount) { + final boolean isModified = !Integer.toString(cardCount).equals(cardCountString); + cardCountString = Integer.toString(cardCount); + if (isModified) { + if (animateOnChange) { + doAlertAnimation(); + } else { + repaint(); + } + } + } + + public void doAlertAnimation() { + timeline1 = new Timeline(); + timeline1.addCallback(this); + timeline1.setDuration(200); +// timeline1.setEase(new Spline(0.8f)); + timeline1.addPropertyToInterpolate( + Timeline.property("imageOffset").on(this).from(0).to(4)); + timeline1.playLoop(2, Timeline.RepeatBehavior.REVERSE); + } + + @Override + public void onTimelineStateChanged(Timeline.TimelineState oldState, Timeline.TimelineState newState, float durationFraction, float timelinePosition) { + // do nothing. + } + + @Override + public void onTimelinePulse(float durationFraction, float timelinePosition) { + // do nothing. + } + + + +} diff --git a/src/magic/ui/duel/viewer/ImageHandGraveyardExileViewer.java b/src/magic/ui/duel/viewer/ImageHandGraveyardExileViewer.java index 8b103394c0..938358b419 100644 --- a/src/magic/ui/duel/viewer/ImageHandGraveyardExileViewer.java +++ b/src/magic/ui/duel/viewer/ImageHandGraveyardExileViewer.java @@ -11,40 +11,41 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import java.awt.BorderLayout; +import magic.model.MagicPlayerZone; import magic.ui.ScreenController; import magic.ui.MagicStyle; +@SuppressWarnings("serial") public class ImageHandGraveyardExileViewer extends JPanel implements ChangeListener { - private static final long serialVersionUID = 1L; + // fired when contents of player zone is changed + public static final String CP_PLAYER_ZONE = "activeZoneName"; - private final ViewerInfo viewerInfo; + private final SwingGameController controller; private final TabSelector tabSelector; private final ImageCardListViewer cardListViewer; private final MagicCardList other = new MagicCardList(); private JToggleButton selectedTab = null; public ImageHandGraveyardExileViewer(final SwingGameController controller) { - - viewerInfo = controller.getViewerInfo(); + this.controller = controller; + setOpaque(false); - setLayout(new BorderLayout(6,0)); + setLayout(new BorderLayout(6, 0)); - final String playerName=viewerInfo.getPlayerInfo(false).name; - final String opponentName=viewerInfo.getPlayerInfo(true).name; + final Theme theme = MagicStyle.getTheme(); + tabSelector = new TabSelector(this, true); + tabSelector.addTab(theme.getIcon(Theme.ICON_SMALL_HAND), getHandZoneName(getUserPlayer())); + tabSelector.addTab(theme.getIcon(Theme.ICON_SMALL_GRAVEYARD), getGraveyardZoneName(getUserPlayer())); + tabSelector.addTab(theme.getIcon(Theme.ICON_SMALL_GRAVEYARD), getGraveyardZoneName(getAiPlayer())); + tabSelector.addTab(theme.getIcon(Theme.ICON_SMALL_EXILE), getExileZoneName(getUserPlayer())); + tabSelector.addTab(theme.getIcon(Theme.ICON_SMALL_EXILE), getExileZoneName(getAiPlayer())); + tabSelector.addTab(theme.getIcon(Theme.ICON_SMALL_HAND), "Other : " + getUserPlayer().name); + add(tabSelector, BorderLayout.WEST); - tabSelector=new TabSelector(this,true); - tabSelector.addTab(MagicStyle.getTheme().getIcon(Theme.ICON_SMALL_HAND),"Hand : "+playerName); - tabSelector.addTab(MagicStyle.getTheme().getIcon(Theme.ICON_SMALL_GRAVEYARD),"Graveyard : "+playerName); - tabSelector.addTab(MagicStyle.getTheme().getIcon(Theme.ICON_SMALL_GRAVEYARD),"Graveyard : "+opponentName); - tabSelector.addTab(MagicStyle.getTheme().getIcon(Theme.ICON_SMALL_EXILE),"Exile : "+playerName); - tabSelector.addTab(MagicStyle.getTheme().getIcon(Theme.ICON_SMALL_EXILE),"Exile : "+opponentName); - tabSelector.addTab(MagicStyle.getTheme().getIcon(Theme.ICON_SMALL_HAND),"Other : "+playerName); - add(tabSelector,BorderLayout.WEST); - - cardListViewer=new ImageCardListViewer(controller); - add(cardListViewer,BorderLayout.CENTER); + cardListViewer = new ImageCardListViewer(controller); + add(cardListViewer, BorderLayout.CENTER); } public void setSelectedTab(final int selectedTab) { @@ -64,55 +65,73 @@ public class ImageHandGraveyardExileViewer extends JPanel implements ChangeListe private void showCards( final MagicCardList cards, - final boolean useCardZoneScreen, + final boolean showFullScreen, final String cardZoneTitle, - final boolean aShowInfo) { - if (useCardZoneScreen) { - useCardZoneScreen(cards, cardZoneTitle); + final boolean showCardIcons) { + if (showFullScreen) { + showFullScreenZone(cards, cardZoneTitle); } else { - cardListViewer.setCardList(cards, aShowInfo); + cardListViewer.setCardList(cards, showCardIcons); + firePropertyChange(CP_PLAYER_ZONE, null, cardZoneTitle); } } - private void update(final boolean useCardZoneScreen) { - if (cardListViewer!=null) { + private void update(final boolean showFullScreen) { + if (cardListViewer != null) { switch (tabSelector.getSelectedTab()) { case 0: showCards( - viewerInfo.getPlayerInfo(false).hand, - useCardZoneScreen, "Player Hand", true); + getUserPlayer().hand, + showFullScreen, getHandZoneName(getUserPlayer()), true); + controller.setPlayerZone(getUserPlayer(), MagicPlayerZone.HAND); break; case 1: showCards( - viewerInfo.getPlayerInfo(false).graveyard, - useCardZoneScreen, "Player Graveyard", false); + getUserPlayer().graveyard, + showFullScreen, getGraveyardZoneName(getUserPlayer()), false); + controller.setPlayerZone(getUserPlayer(), MagicPlayerZone.GRAVEYARD); break; case 2: showCards( - viewerInfo.getPlayerInfo(true).graveyard, - useCardZoneScreen, "Computer Graveyard", false); + getAiPlayer().graveyard, + showFullScreen, getGraveyardZoneName(getAiPlayer()), false); + controller.setPlayerZone(getAiPlayer(), MagicPlayerZone.GRAVEYARD); break; case 3: showCards( - viewerInfo.getPlayerInfo(false).exile, - useCardZoneScreen, "Player Exile", false); + getUserPlayer().exile, + showFullScreen, getExileZoneName(getUserPlayer()), false); + controller.setPlayerZone(getUserPlayer(), MagicPlayerZone.EXILE); break; case 4: showCards( - viewerInfo.getPlayerInfo(true).exile, - useCardZoneScreen, "Computer Exile", false); + getAiPlayer().exile, + showFullScreen, getExileZoneName(getAiPlayer()), false); + controller.setPlayerZone(getAiPlayer(), MagicPlayerZone.EXILE); break; case 5: showCards( other, - useCardZoneScreen, "Other", false); + showFullScreen, "Other", false); break; } repaint(); } } - private void useCardZoneScreen(final MagicCardList aCardList, final String zoneName) { + private String getHandZoneName(final PlayerViewerInfo player) { + return player.isAi ? player.name + " Hand" : ""; + } + + private String getGraveyardZoneName(final PlayerViewerInfo player) { + return player.name + " Graveyard"; + } + + private String getExileZoneName(final PlayerViewerInfo player) { + return player.name + " Exile"; + } + + private void showFullScreenZone(final MagicCardList aCardList, final String zoneName) { ScreenController.showCardZoneScreen(aCardList, zoneName, false); } @@ -125,4 +144,79 @@ public class ImageHandGraveyardExileViewer extends JPanel implements ChangeListe public ImageCardListViewer getCardListViewer() { return cardListViewer; } + + public void setActivePlayerZone(PlayerViewerInfo playerInfo, MagicPlayerZone zone) { + tabSelector.setIsUserClick(false); + if (playerInfo.isAi) { + switch (zone) { + case HAND: // TODO + break; + case GRAVEYARD: + setSelectedTab(2); + break; + case EXILE: setSelectedTab(4); + break; + } + } else { + switch (zone) { + case HAND: + setSelectedTab(0); + break; + case GRAVEYARD: + setSelectedTab(1); + break; + case EXILE: + setSelectedTab(3); + break; + } + } + } + + public void setFullScreenActivePlayerZone(PlayerViewerInfo playerInfo, MagicPlayerZone zone) { + tabSelector.setIsUserClick(true); + if (playerInfo.isAi) { + switch (zone) { + case HAND: // TODO + break; + case GRAVEYARD: + setSelectedTab(2); + break; + case EXILE: setSelectedTab(4); + break; + } + } else { + switch (zone) { + case HAND: + setSelectedTab(0); + break; + case GRAVEYARD: + setSelectedTab(1); + break; + case EXILE: + setSelectedTab(3); + break; + } + } + } + + /** + * Returns the latest instance of PlayerViewInfo. + *

+ * Do not retain a reference toPlayerViewerInfo since a new instance + * is created whenever ViewerInfo is updated. + */ + private PlayerViewerInfo getUserPlayer() { + return controller.getViewerInfo().getPlayerInfo(false); + } + + /** + * Returns the latest instance of PlayerViewInfo. + *

+ * Do not retain a reference toPlayerViewerInfo since a new instance + * is created whenever ViewerInfo is updated. + */ + private PlayerViewerInfo getAiPlayer() { + return controller.getViewerInfo().getPlayerInfo(true); + } + } diff --git a/src/magic/ui/duel/viewer/PlayerViewerInfo.java b/src/magic/ui/duel/viewer/PlayerViewerInfo.java index eca365b8d6..cf4dadd661 100644 --- a/src/magic/ui/duel/viewer/PlayerViewerInfo.java +++ b/src/magic/ui/duel/viewer/PlayerViewerInfo.java @@ -23,6 +23,7 @@ public class PlayerViewerInfo { public final MagicCardList exile; public final MagicCardList library; public final List permanents; + public final boolean isAi; public PlayerViewerInfo(final MagicGame game,final MagicPlayer player) { this.player=player; @@ -36,6 +37,7 @@ public class PlayerViewerInfo { graveyard=new MagicCardList(player.getGraveyard()); exile=new MagicCardList(player.getExile()); library=new MagicCardList(player.getLibrary()); + isAi = player.getPlayerDefinition().isArtificial(); permanents=new ArrayList(); for (final MagicPermanent permanent : player.getPermanents()) { permanents.add(new PermanentViewerInfo(game,permanent)); diff --git a/src/magic/ui/widget/TabSelector.java b/src/magic/ui/widget/TabSelector.java index 312d282735..deb577d136 100644 --- a/src/magic/ui/widget/TabSelector.java +++ b/src/magic/ui/widget/TabSelector.java @@ -1,12 +1,5 @@ package magic.ui.widget; -import javax.swing.BoxLayout; -import javax.swing.ImageIcon; -import javax.swing.JPanel; -import javax.swing.JToggleButton; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; @@ -15,11 +8,16 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JPanel; +import javax.swing.JToggleButton; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +@SuppressWarnings("serial") public class TabSelector extends JPanel implements ActionListener { - private static final long serialVersionUID = 1L; - private static final Dimension HORIZONTAL_BUTTON_DIMENSION=new Dimension(28,20); private static final Dimension VERTICAL_BUTTON_DIMENSION=new Dimension(24,24); @@ -53,7 +51,7 @@ public class TabSelector extends JPanel implements ActionListener { buttonDimension=HORIZONTAL_BUTTON_DIMENSION; } - buttons=new ArrayList(); + buttons=new ArrayList<>(); } @@ -91,18 +89,12 @@ public class TabSelector extends JPanel implements ActionListener { } } -// public void addTab(final ImageIcon icon) { -// -// addTab(icon,null); -// } - private void showTab(final JToggleButton selectedButton) { - showTab(selectedButton, false); + showTab(selectedButton, isUserClick); } private void showTab(final JToggleButton selectedButton, final boolean userClick) { this.isUserClick = userClick; for (final JToggleButton button : buttons) { - button.setSelected(button==selectedButton); } @@ -119,4 +111,8 @@ public class TabSelector extends JPanel implements ActionListener { return isUserClick; } + public void setIsUserClick(final boolean b) { + this.isUserClick = b; + } + }