package magic.ui.widget.duel.animation; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import javax.swing.SwingUtilities; import magic.data.GeneralConfig; import magic.model.MagicPlayer; import magic.ui.MagicImages; import magic.ui.duel.viewerinfo.CardViewerInfo; import org.pushingpixels.trident.Timeline; import org.pushingpixels.trident.Timeline.TimelineState; import org.pushingpixels.trident.TimelineScenario; import org.pushingpixels.trident.callback.TimelineCallbackAdapter; import org.pushingpixels.trident.ease.Spline; abstract class CardAnimation extends MagicAnimation { protected static final int GROW_DURATION = 800; // milliseconds protected static final int FLIP_DURATION = 400; // milliseconds protected static final int SHRINK_DURATION = 400; // milliseconds protected static final Color SHADOW_COLOR = new Color(0, 0, 0, 100); private Timeline growTimeline; private Timeline flipTimeline; private Timeline shrinkTimeline; private Timeline viewTimeline; private TimelineScenario.RendezvousSequence scenario; private volatile float flipPosition = 1f; private final GameLayoutInfo layoutInfo; private final int playerIndex; private ImageScaler frontImageScaler; private ImageScaler backImageScaler; private Dimension previewSize; private Rectangle imageRect = new Rectangle(); private final MagicPlayer player; private final CardViewerInfo cardInfo; // abstract methods protected abstract Rectangle getStart(); protected abstract Rectangle getEnd(); CardAnimation(MagicPlayer aPlayer, CardViewerInfo cardInfo, GameLayoutInfo layoutInfo) { this.player = aPlayer; this.cardInfo = cardInfo; this.playerIndex = aPlayer.getIndex(); this.layoutInfo = layoutInfo; } protected MagicPlayer getPlayer() { return player; } protected CardViewerInfo getCardInfo() { return cardInfo; } private void drawCardShadow(Graphics g) { final int SHADOW = 6; g.setColor(SHADOW_COLOR); g.fillRect(imageRect.x + SHADOW, imageRect.y + SHADOW, frontImageScaler.getImage().getWidth(null), frontImageScaler.getImage().getHeight(null) ); } private boolean isRunning(Timeline t) { return t != null && (t.getState() == TimelineState.READY || t.getState() == TimelineState.PLAYING_FORWARD); } private boolean isStaticArrowOn() { return MagicAnimations.isOn(AnimationFx.STATIC_ARROW) && MagicAnimations.isOff(AnimationFx.ELASTIC_ARROW) && isRunning(viewTimeline); } private boolean isElasticArrowOn() { return (MagicAnimations.isOn(AnimationFx.ELASTIC_ARROW) && isRunning(growTimeline)) || (MagicAnimations.isOn(AnimationFx.STATIC_ARROW) && isRunning(viewTimeline)); } private boolean isCardShadowOn() { return MagicAnimations.isOn(AnimationFx.CARD_SHADOW) && isRunning(viewTimeline); } private void drawCardImage(Graphics g) { if (flipPosition < 1f) { final int w = Math.abs((int)(Math.cos(flipPosition * Math.PI) * imageRect.width)); g.drawImage( flipPosition > 0.5f ? frontImageScaler.getImage() : backImageScaler.getImage(), imageRect.x + ((imageRect.width - w) / 2), imageRect.y, w, imageRect.height, getCanvas() ); } else { g.drawImage( frontImageScaler.getImage(), imageRect.x, imageRect.y, getCanvas() ); } } @Override protected void render(Graphics g) { if (imageRect.isEmpty()) return; if (isElasticArrowOn()) { ArrowBuilder.drawArrow(g, getStart(), imageRect); } else if (isStaticArrowOn()) { ArrowBuilder.drawArrow(g, getStart(), getPreviewRectangle()); } if (isCardShadowOn()) { drawCardShadow(g); } drawCardImage(g); } private Dimension getCardPreviewSize(Image image) { final Dimension prefSize = MagicImages.getPreferredImageSize(image); final Dimension container = layoutInfo.getGamePanelSize(); if (container.height < prefSize.height) { final int newWidth = (int)((container.height / (double)prefSize.height) * prefSize.width); return new Dimension(newWidth, container.height); } else { return prefSize; } } /** * Returns the dimensions and position of the full size card image. */ private Rectangle getPreviewRectangle() { final int x = (layoutInfo.getGamePanelSize().width - previewSize.width) / 2; final int y = (layoutInfo.getGamePanelSize().height - previewSize.height) / 2; return new Rectangle(new Point(x, y), previewSize); } /** * This is called by a timeline (do not delete!). */ public void setGrowRectangle(final Rectangle rect) { assert !SwingUtilities.isEventDispatchThread(); imageRect = rect; frontImageScaler.setSize(rect.getSize(), growTimeline.getTimelinePosition() > 0.96f); getCanvas().repaint(); } private Timeline getGrowTimeline() { final Timeline timeline = new Timeline(this); timeline.addPropertyToInterpolate("GrowRectangle", getStart(), getPreviewRectangle()); timeline.setDuration(GROW_DURATION); timeline.setEase(new Spline(0.8f)); return timeline; } /** * This is called by a timeline (do not delete!). */ public void setFlipPosition(final float value) { assert !SwingUtilities.isEventDispatchThread(); flipPosition = value; } private Timeline getFlipTimeline() { if (MagicAnimations.isOff(AnimationFx.FLIP_CARD)) { return null; } final Timeline timeline = new Timeline(this); timeline.addPropertyToInterpolate("FlipPosition", 0.0f, 1.0f); timeline.setDuration(FLIP_DURATION); timeline.setInitialDelay(GROW_DURATION - FLIP_DURATION); flipPosition = 0f; return timeline; } /** * This is called by a timeline (do not delete!). */ public void setShrinkRectangle(final Rectangle rect) { assert !SwingUtilities.isEventDispatchThread(); imageRect = rect; frontImageScaler.setLQSize(rect.getSize()); getCanvas().repaint(); } private Timeline getShrinkTimeline() { final Timeline timeline = new Timeline(this); timeline.addPropertyToInterpolate("ShrinkRectangle", getPreviewRectangle(), getEnd()); timeline.setDuration(SHRINK_DURATION); timeline.setEase(new Spline(0.8f)); timeline.addCallback(new TimelineCallbackAdapter() { @Override public void onTimelineStateChanged(Timeline.TimelineState oldState, Timeline.TimelineState newState, float durationFraction, float timelinePosition) { if (newState == Timeline.TimelineState.DONE) { scenario.cancel(); } } }); return timeline; } /** * Amount of time in millisecs that full-size card preview is displayed. */ private int getViewDuration() { return cardInfo.isLand() ? GeneralConfig.getInstance().getLandPreviewDuration() : GeneralConfig.getInstance().getNonLandPreviewDuration(); } private Timeline getViewTimeline() { final Timeline timeline = new Timeline(); timeline.setDuration(getViewDuration()); timeline.addCallback(new TimelineCallbackAdapter() { @Override public void onTimelineStateChanged(Timeline.TimelineState oldState, Timeline.TimelineState newState, float durationFraction, float timelinePosition) { if (newState == Timeline.TimelineState.PLAYING_FORWARD) { // ensures arrow is painted correctly. getCanvas().repaint(); } } }); return timeline; } private void playTimelineScenario() { growTimeline = getGrowTimeline(); flipTimeline = getFlipTimeline(); viewTimeline = getViewTimeline(); shrinkTimeline = getShrinkTimeline(); scenario = new TimelineScenario.RendezvousSequence(); scenario.addScenarioActor(growTimeline); if (flipTimeline != null) { scenario.addScenarioActor(flipTimeline); } scenario.rendezvous(); scenario.addScenarioActor(viewTimeline); scenario.rendezvous(); scenario.addScenarioActor(shrinkTimeline); scenario.addCallback(() -> { SwingUtilities.invokeLater(() -> { imageRect = new Rectangle(); getCanvas().setVisible(false); }); isRunning.set(false); }); scenario.play(); } @Override protected void play() { assert SwingUtilities.isEventDispatchThread(); isRunning.set(true); frontImageScaler = new ImageScaler(cardInfo.getImage()); backImageScaler = new ImageScaler(cardInfo.getBackFaceImage()); this.previewSize = getCardPreviewSize(frontImageScaler.getImage()); playTimelineScenario(); } @Override protected void cancel() { if (scenario != null && scenario.getState() == TimelineScenario.TimelineScenarioState.PLAYING) { scenario.cancel(); } } @Override protected void doCancelAction() { if (viewTimeline.getState() == Timeline.TimelineState.PLAYING_FORWARD) { viewTimeline.cancel(); shrinkTimeline.play(); } } protected int getPlayerIndex() { return playerIndex; } protected GameLayoutInfo getLayoutInfo() { return layoutInfo; } }