diff --git a/Mage.Client/src/main/java/mage/client/cards/VirtualCardInfo.java b/Mage.Client/src/main/java/mage/client/cards/VirtualCardInfo.java new file mode 100644 index 0000000000..a01b2dd7e9 --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/cards/VirtualCardInfo.java @@ -0,0 +1,134 @@ +package mage.client.cards; + +import mage.abilities.icon.CardIconRenderSettings; +import mage.cards.MageCard; +import mage.cards.action.TransferData; +import mage.cards.repository.CardInfo; +import mage.cards.repository.CardRepository; +import mage.client.dialog.PreferencesDialog; +import mage.client.plugins.adapters.MageActionCallback; +import mage.client.plugins.impl.Plugins; +import mage.client.util.ClientDefaultSettings; +import mage.view.CardView; + +import java.awt.*; +import java.util.UUID; + +/** + * GUI: virtual card component for popup hint (move mouse over card to show a hint) + *

+ * Use case: you don't have a real card but want to show a popup card hint. + * Howto use: + * - call "init" on new card; + * - call "onMouseXXX" on start, update and close + * + * @author JayDi85 + */ +public class VirtualCardInfo { + + CardView cardView; + MageCard cardComponent; + BigCard bigCard; + MageActionCallback actionCallback; + TransferData data = new TransferData(); + Dimension cardDimension = null; + + public VirtualCardInfo() { + super(); + } + + private void clear() { + this.cardView = null; + this.cardComponent = null; + } + + public void init(String cardName, BigCard bigCard, UUID gameId) { + CardInfo cardInfo = CardRepository.instance.findCards(cardName).stream().findFirst().orElse(null); + if (cardInfo == null) { + clear(); + return; + } + + this.init(new CardView(cardInfo.getCard()), bigCard, gameId); + } + + public void init(CardView cardView, BigCard bigCard, UUID gameId) { + clear(); + + this.bigCard = bigCard != null ? bigCard : new BigCard(); + this.cardDimension = new Dimension(ClientDefaultSettings.dimensions.getFrameWidth(), ClientDefaultSettings.dimensions.getFrameHeight()); + this.actionCallback = (MageActionCallback) Plugins.instance.getActionCallback(); + + this.cardView = cardView; + this.cardComponent = Plugins.instance.getMageCard( + this.cardView, + this.bigCard, + new CardIconRenderSettings(), + this.cardDimension, + null, + true, + true, + PreferencesDialog.getRenderMode(), + true + ); + this.cardComponent.update(cardView); + + data.setComponent(this.cardComponent); + data.setCard(this.cardView); + data.setGameId(gameId); + } + + public boolean prepared() { + return this.cardView != null + && this.cardComponent != null + && this.actionCallback != null; + } + + private void updateLocation(Point point) { + Point newPoint = new Point(point); + if (this.cardComponent != null) { + // offset popup + newPoint.translate(50, 50); + } + data.setLocationOnScreen(newPoint); + } + + public void onMouseEntered() { + onMouseMoved(null); + } + + public void onMouseEntered(Point newLocation) { + if (!prepared()) { + return; + } + + if (newLocation != null) { + updateLocation(newLocation); + } + + this.actionCallback.mouseEntered(null, this.data); + } + + public void onMouseMoved() { + onMouseMoved(null); + } + + public void onMouseMoved(Point newLocation) { + if (!prepared()) { + return; + } + + if (newLocation != null) { + updateLocation(newLocation); + } + + this.actionCallback.mouseMoved(null, this.data); + } + + public void onMouseExited() { + if (!prepared()) { + return; + } + this.actionCallback.mouseExited(null, this.data); + } +} diff --git a/Mage.Client/src/main/java/mage/client/components/ext/dlg/DlgParams.java b/Mage.Client/src/main/java/mage/client/components/ext/dlg/DlgParams.java index 51dc575b29..0dfa74a456 100644 --- a/Mage.Client/src/main/java/mage/client/components/ext/dlg/DlgParams.java +++ b/Mage.Client/src/main/java/mage/client/components/ext/dlg/DlgParams.java @@ -12,7 +12,7 @@ import java.util.Set; import java.util.UUID; /** - * Class is used to save parameters and to send them to dialog. + * GUI: parameters for dialogs, uses to store useful data * * @author mw, noxx */ diff --git a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java index 7e4fd068a6..3ecb24adfd 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java @@ -6,12 +6,19 @@ import java.util.*; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; + import mage.choices.Choice; +import mage.choices.ChoiceHintType; import mage.client.MageFrame; +import mage.client.cards.BigCard; +import mage.client.cards.VirtualCardInfo; import mage.client.util.gui.MageDialogState; +import mage.game.command.Dungeon; +import mage.view.CardView; +import mage.view.DungeonView; /** - * Game GUI: choosing one of the list's item + * GUI: choosing one of the list's item. Uses in game's and non game's GUI like fast search * * @author JayDi85 */ @@ -19,25 +26,25 @@ public class PickChoiceDialog extends MageDialog { Choice choice; + // popup card info + int lastModelIndex = -1; + VirtualCardInfo cardInfo = new VirtualCardInfo(); + BigCard bigCard; + UUID gameId; + java.util.List allItems = new ArrayList<>(); DefaultListModel dataModel = new DefaultListModel<>(); final private static String HTML_HEADERS_TEMPLATE = "

%s
"; - public void showDialog(Choice choice) { - showDialog(choice, null, null, null); - } - public void showDialog(Choice choice, String startSelectionValue) { - showDialog(choice, null, null, startSelectionValue); + showDialog(choice, startSelectionValue, null, null, null); } - public void showDialog(Choice choice, UUID objectId, MageDialogState mageDialogState) { - showDialog(choice, objectId, mageDialogState, null); - } - - public void showDialog(Choice choice, UUID objectId, MageDialogState mageDialogState, String startSelectionValue) { + public void showDialog(Choice choice, String startSelectionValue, UUID objectId, MageDialogState mageDialogState, BigCard bigCard) { this.choice = choice; + this.bigCard = bigCard; + this.gameId = objectId; setLabelText(this.labelMessage, choice.getMessage()); setLabelText(this.labelSubMessage, choice.getSubMessage()); @@ -54,20 +61,20 @@ public class PickChoiceDialog extends MageDialog { this.allItems.clear(); if (choice.isKeyChoice()) { for (Map.Entry entry : choice.getKeyChoices().entrySet()) { - this.allItems.add(new KeyValueItem(entry.getKey(), entry.getValue())); + this.allItems.add(new KeyValueItem(entry.getKey(), entry.getValue(), choice.getHintType())); } } else { for (String value : choice.getChoices()) { - this.allItems.add(new KeyValueItem(value, value)); + this.allItems.add(new KeyValueItem(value, value, choice.getHintType())); } } // sorting if (choice.isSortEnabled()) { this.allItems.sort((o1, o2) -> { - Integer n1 = choice.getSortData().get(o1.Key); - Integer n2 = choice.getSortData().get(o2.Key); - return n1.compareTo(n2); + Integer n1 = choice.getSortData().get(o1.getKey()); + Integer n2 = choice.getSortData().get(o2.getKey()); + return Integer.compare(n1, n2); }); } @@ -123,14 +130,36 @@ public class PickChoiceDialog extends MageDialog { } }); - // listeners double click choose + // listeners double click + // you can't use mouse wheel to switch hint type, cause wheel move a scrollbar listChoices.addMouseListener(new MouseAdapter() { + @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { doChoose(); } } + + @Override + public void mouseExited(MouseEvent e) { + choiceHintHide(); + } + }); + + listChoices.addMouseMotionListener(new MouseMotionAdapter() { + + @Override + public void mouseMoved(MouseEvent e) { + // hint show + JList listSource = (JList) e.getSource(); + int index = listSource.locationToIndex(e.getPoint()); + if (index > -1) { + choiceHintShow(index); + } else { + choiceHintHide(); + } + } }); // listeners for ESC close @@ -163,11 +192,11 @@ public class PickChoiceDialog extends MageDialog { loadData(); // start selection - if ((startSelectionValue != null)) { + if (startSelectionValue != null) { int selectIndex = -1; for (int i = 0; i < this.listChoices.getModel().getSize(); i++) { KeyValueItem listItem = (KeyValueItem) this.listChoices.getModel().getElementAt(i); - if (listItem.Key.equals(startSelectionValue)) { + if (listItem.getKey().equals(startSelectionValue)) { selectIndex = i; break; } @@ -182,10 +211,77 @@ public class PickChoiceDialog extends MageDialog { this.setVisible(true); } + private void choiceHintShow(int modelIndex) { + + switch (choice.getHintType()) { + case CARD: + case CARD_DUNGEON: { + // as popup card + if (lastModelIndex != modelIndex) { + // new card + KeyValueItem item = (KeyValueItem) listChoices.getModel().getElementAt(modelIndex); + String cardName = item.getValue(); + + if (choice.getHintType() == ChoiceHintType.CARD) { + cardInfo.init(cardName, this.bigCard, this.gameId); + } else if (choice.getHintType() == ChoiceHintType.CARD_DUNGEON) { + CardView cardView = new CardView(new DungeonView(Dungeon.createDungeon(cardName))); + cardInfo.init(cardView, this.bigCard, this.gameId); + } + + cardInfo.onMouseEntered(MouseInfo.getPointerInfo().getLocation()); + } else { + // old card + cardInfo.onMouseMoved(MouseInfo.getPointerInfo().getLocation()); + } + lastModelIndex = modelIndex; + break; + } + + default: + case TEXT: { + // as popup text + if (lastModelIndex != modelIndex) { + // new hint + listChoices.setToolTipText(null); + KeyValueItem item = (KeyValueItem) listChoices.getModel().getElementAt(modelIndex); + listChoices.setToolTipText(item.getValue()); + } + lastModelIndex = modelIndex; + break; + } + } + } + + private void choiceHintHide() { + switch (choice.getHintType()) { + case CARD: { + // as popup card + cardInfo.onMouseExited(); + break; + } + + default: + case TEXT: { + // as popup text + listChoices.setToolTipText(null); + break; + } + } + + lastModelIndex = -1; + } + public void setWindowSize(int width, int height) { this.setSize(new Dimension(width, height)); } + @Override + public void hideDialog() { + choiceHintHide(); + super.hideDialog(); + } + private void loadData() { // load data to datamodel after filter or on startup String filter = choice.getSearchText(); @@ -196,7 +292,7 @@ public class PickChoiceDialog extends MageDialog { this.dataModel.clear(); for (KeyValueItem item : this.allItems) { - if (!choice.isSearchEnabled() || item.Value.toLowerCase(Locale.ENGLISH).contains(filter)) { + if (!choice.isSearchEnabled() || item.getValue().toLowerCase(Locale.ENGLISH).contains(filter)) { this.dataModel.addElement(item); } } @@ -284,27 +380,33 @@ public class PickChoiceDialog extends MageDialog { } } - class KeyValueItem { + static class KeyValueItem { - private final String Key; - private final String Value; + protected final String key; + protected final String value; + protected final ChoiceHintType hint; - public KeyValueItem(String value, String label) { - this.Key = value; - this.Value = label; + public KeyValueItem(String key, String value, ChoiceHintType hint) { + this.key = key; + this.value = value; + this.hint = hint; } public String getKey() { - return this.Key; + return this.key; } public String getValue() { - return this.Value; + return this.value; + } + + public ChoiceHintType getHint() { + return this.hint; } @Override public String toString() { - return this.Value; + return this.value; } } diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index 2a99e8369a..d5e1165067 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -1746,7 +1746,7 @@ public final class GamePanel extends javax.swing.JPanel { hideAll(); // TODO: remember last choices and search incremental for same events? PickChoiceDialog pickChoice = new PickChoiceDialog(); - pickChoice.showDialog(choice, objectId, choiceWindowState); + pickChoice.showDialog(choice, null, objectId, choiceWindowState, bigCard); // special mode adds # to the answer (server side code must process that prefix, see replacementEffectChoice) String specialPrefix = choice.isChosenSpecial() ? "#" : ""; diff --git a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java index 78578a8084..685ab57f1c 100644 --- a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java +++ b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java @@ -132,6 +132,7 @@ public class MageActionCallback implements ActionCallback { @Override public void mouseEntered(MouseEvent e, final TransferData data) { + // MouseEvent can be null for custom hints calls, e.g. from choose dialog this.popupData = data; handleMouseMoveOverNewCard(data); } @@ -288,10 +289,13 @@ public class MageActionCallback implements ActionCallback { @Override public void mouseMoved(MouseEvent e, TransferData data) { + // MouseEvent can be null for custom hints calls, e.g. from choose dialog + if (!Plugins.instance.isCardPluginLoaded()) { return; } - if (!popupData.getCard().equals(data.getCard())) { + if (this.popupData == null + || !popupData.getCard().equals(data.getCard())) { this.popupData = data; handleMouseMoveOverNewCard(data); } @@ -336,6 +340,7 @@ public class MageActionCallback implements ActionCallback { @Override public void mouseExited(MouseEvent e, final TransferData data) { + // MouseEvent can be null for custom hints calls, e.g. from choose dialog if (data != null) { hideAll(data.getGameId()); } else { @@ -452,6 +457,10 @@ public class MageActionCallback implements ActionCallback { hideTooltipPopup(); cancelTimeout(); Component parentComponent = SwingUtilities.getRoot(cardPanel); + if (parentComponent == null) { + // virtual card (example: show card popup in non cards panel like PickChoiceDialog) + parentComponent = MageFrame.getDesktop(); + } Point parentPoint = parentComponent.getLocationOnScreen(); if (data.getLocationOnScreen() == null) { diff --git a/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java b/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java index 352b88f610..0e1dec0090 100644 --- a/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java +++ b/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java @@ -162,6 +162,14 @@ public final class GUISizeHelper { enlargedImageHeight = 25 * PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GUI_ENLARGED_IMAGE_SIZE, 20); } + public static int getTooltipCardWidth() { + return 20 * GUISizeHelper.cardTooltipFontSize - 50; + } + + public static int getTooltipCardHeight() { + return 12 * GUISizeHelper.cardTooltipFontSize - 20; + } + public static void changePopupMenuFont(JPopupMenu popupMenu) { for (Component comp : popupMenu.getComponents()) { if (comp instanceof JMenuItem) { diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java b/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java index 4cf531e0d5..43b1273b91 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java @@ -44,8 +44,8 @@ public class CardInfoPaneImpl extends JEditorPane implements CardInfoPane { } private void setGUISize() { - addWidth = 20 * GUISizeHelper.cardTooltipFontSize - 50; - addHeight = 12 * GUISizeHelper.cardTooltipFontSize - 20; + addWidth = GUISizeHelper.getTooltipCardWidth(); + addHeight = GUISizeHelper.getTooltipCardHeight(); setSize = true; } diff --git a/Mage.Common/src/main/java/mage/cards/action/TransferData.java b/Mage.Common/src/main/java/mage/cards/action/TransferData.java index ac1f6c7d99..9f273f182b 100644 --- a/Mage.Common/src/main/java/mage/cards/action/TransferData.java +++ b/Mage.Common/src/main/java/mage/cards/action/TransferData.java @@ -9,6 +9,7 @@ import java.util.UUID; /** * Data for main card panel events like mouse moves or clicks + * */ public class TransferData { diff --git a/Mage/src/main/java/mage/choices/Choice.java b/Mage/src/main/java/mage/choices/Choice.java index 21b1ed6a4f..2323cbf42e 100644 --- a/Mage/src/main/java/mage/choices/Choice.java +++ b/Mage/src/main/java/mage/choices/Choice.java @@ -39,6 +39,8 @@ public interface Choice extends Serializable, Copyable { String getSpecialHint(); + ChoiceHintType getHintType(); + // string choice void setChoices(Set choices); diff --git a/Mage/src/main/java/mage/choices/ChoiceHintType.java b/Mage/src/main/java/mage/choices/ChoiceHintType.java new file mode 100644 index 0000000000..86e7580f4c --- /dev/null +++ b/Mage/src/main/java/mage/choices/ChoiceHintType.java @@ -0,0 +1,13 @@ +package mage.choices; + +/** + * For GUI: popup hint in choose dialog + * + * @author JayDi85 + */ +public enum ChoiceHintType { + + TEXT, + CARD, + CARD_DUNGEON +} diff --git a/Mage/src/main/java/mage/choices/ChoiceImpl.java b/Mage/src/main/java/mage/choices/ChoiceImpl.java index e45fb02d2e..12246f9915 100644 --- a/Mage/src/main/java/mage/choices/ChoiceImpl.java +++ b/Mage/src/main/java/mage/choices/ChoiceImpl.java @@ -21,6 +21,7 @@ public class ChoiceImpl implements Choice { protected String subMessage; protected boolean searchEnabled = true; // enable for all windows by default protected String searchText; + protected ChoiceHintType hintType; // special button with #-answer // warning, only for human's GUI, not AI @@ -34,7 +35,12 @@ public class ChoiceImpl implements Choice { } public ChoiceImpl(boolean required) { + this(required, ChoiceHintType.TEXT); + } + + public ChoiceImpl(boolean required, ChoiceHintType hintType) { this.required = required; + this.hintType = hintType; } public ChoiceImpl(final ChoiceImpl choice) { @@ -46,6 +52,7 @@ public class ChoiceImpl implements Choice { this.subMessage = choice.subMessage; this.searchEnabled = choice.searchEnabled; this.searchText = choice.searchText; + this.hintType = choice.hintType; this.choices.addAll(choice.choices); this.choiceKey = choice.choiceKey; this.keyChoices = choice.keyChoices; // list should never change for the same object so copy by reference TODO: check errors with that, it that ok? Color list is static @@ -302,4 +309,9 @@ public class ChoiceImpl implements Choice { public String getSpecialHint() { return this.specialHint; } + + @Override + public ChoiceHintType getHintType() { + return this.hintType; + } } diff --git a/Mage/src/main/java/mage/game/command/Dungeon.java b/Mage/src/main/java/mage/game/command/Dungeon.java index 2e6e89e44e..eca4f340a1 100644 --- a/Mage/src/main/java/mage/game/command/Dungeon.java +++ b/Mage/src/main/java/mage/game/command/Dungeon.java @@ -14,6 +14,7 @@ import mage.abilities.effects.Effect; import mage.abilities.text.TextPart; import mage.cards.FrameStyle; import mage.choices.Choice; +import mage.choices.ChoiceHintType; import mage.choices.ChoiceImpl; import mage.constants.CardType; import mage.constants.Outcome; @@ -130,11 +131,15 @@ public class Dungeon implements CommandObject { public static Dungeon selectDungeon(UUID playerId, Game game) { Player player = game.getPlayer(playerId); - Choice choice = new ChoiceImpl(true); + Choice choice = new ChoiceImpl(true, ChoiceHintType.CARD_DUNGEON); choice.setMessage("Choose a dungeon to venture into"); choice.setChoices(dungeonNames); player.choose(Outcome.Neutral, choice, game); - switch (choice.getChoice()) { + return createDungeon(choice.getChoice()); + } + + public static Dungeon createDungeon(String name) { + switch (name) { case "Tomb of Annihilation": return new TombOfAnnihilation(); case "Lost Mine of Phandelver":