* GUI: added card popup info in choose dialog (example: choose dungeon, #8012);

* GUI: added texts popup info in choose dialog (example: choose from any list);
This commit is contained in:
Oleg Agafonov 2021-08-14 09:18:50 +04:00
parent b73f10a0ab
commit d587cc9151
12 changed files with 322 additions and 36 deletions

View file

@ -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)
* <p>
* 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);
}
}

View file

@ -12,7 +12,7 @@ import java.util.Set;
import java.util.UUID; 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 * @author mw, noxx
*/ */

View file

@ -6,12 +6,19 @@ import java.util.*;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
import mage.choices.Choice; import mage.choices.Choice;
import mage.choices.ChoiceHintType;
import mage.client.MageFrame; import mage.client.MageFrame;
import mage.client.cards.BigCard;
import mage.client.cards.VirtualCardInfo;
import mage.client.util.gui.MageDialogState; 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 * @author JayDi85
*/ */
@ -19,25 +26,25 @@ public class PickChoiceDialog extends MageDialog {
Choice choice; Choice choice;
// popup card info
int lastModelIndex = -1;
VirtualCardInfo cardInfo = new VirtualCardInfo();
BigCard bigCard;
UUID gameId;
java.util.List<KeyValueItem> allItems = new ArrayList<>(); java.util.List<KeyValueItem> allItems = new ArrayList<>();
DefaultListModel<KeyValueItem> dataModel = new DefaultListModel<>(); DefaultListModel<KeyValueItem> dataModel = new DefaultListModel<>();
final private static String HTML_HEADERS_TEMPLATE = "<html><div style='text-align: center;'>%s</div></html>"; final private static String HTML_HEADERS_TEMPLATE = "<html><div style='text-align: center;'>%s</div></html>";
public void showDialog(Choice choice) {
showDialog(choice, null, null, null);
}
public void showDialog(Choice choice, String startSelectionValue) { 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) { public void showDialog(Choice choice, String startSelectionValue, UUID objectId, MageDialogState mageDialogState, BigCard bigCard) {
showDialog(choice, objectId, mageDialogState, null);
}
public void showDialog(Choice choice, UUID objectId, MageDialogState mageDialogState, String startSelectionValue) {
this.choice = choice; this.choice = choice;
this.bigCard = bigCard;
this.gameId = objectId;
setLabelText(this.labelMessage, choice.getMessage()); setLabelText(this.labelMessage, choice.getMessage());
setLabelText(this.labelSubMessage, choice.getSubMessage()); setLabelText(this.labelSubMessage, choice.getSubMessage());
@ -54,20 +61,20 @@ public class PickChoiceDialog extends MageDialog {
this.allItems.clear(); this.allItems.clear();
if (choice.isKeyChoice()) { if (choice.isKeyChoice()) {
for (Map.Entry<String, String> entry : choice.getKeyChoices().entrySet()) { for (Map.Entry<String, String> 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 { } else {
for (String value : choice.getChoices()) { for (String value : choice.getChoices()) {
this.allItems.add(new KeyValueItem(value, value)); this.allItems.add(new KeyValueItem(value, value, choice.getHintType()));
} }
} }
// sorting // sorting
if (choice.isSortEnabled()) { if (choice.isSortEnabled()) {
this.allItems.sort((o1, o2) -> { this.allItems.sort((o1, o2) -> {
Integer n1 = choice.getSortData().get(o1.Key); Integer n1 = choice.getSortData().get(o1.getKey());
Integer n2 = choice.getSortData().get(o2.Key); Integer n2 = choice.getSortData().get(o2.getKey());
return n1.compareTo(n2); 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() { listChoices.addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) { if (e.getClickCount() == 2) {
doChoose(); 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 // listeners for ESC close
@ -163,11 +192,11 @@ public class PickChoiceDialog extends MageDialog {
loadData(); loadData();
// start selection // start selection
if ((startSelectionValue != null)) { if (startSelectionValue != null) {
int selectIndex = -1; int selectIndex = -1;
for (int i = 0; i < this.listChoices.getModel().getSize(); i++) { for (int i = 0; i < this.listChoices.getModel().getSize(); i++) {
KeyValueItem listItem = (KeyValueItem) this.listChoices.getModel().getElementAt(i); KeyValueItem listItem = (KeyValueItem) this.listChoices.getModel().getElementAt(i);
if (listItem.Key.equals(startSelectionValue)) { if (listItem.getKey().equals(startSelectionValue)) {
selectIndex = i; selectIndex = i;
break; break;
} }
@ -182,10 +211,77 @@ public class PickChoiceDialog extends MageDialog {
this.setVisible(true); 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) { public void setWindowSize(int width, int height) {
this.setSize(new Dimension(width, height)); this.setSize(new Dimension(width, height));
} }
@Override
public void hideDialog() {
choiceHintHide();
super.hideDialog();
}
private void loadData() { private void loadData() {
// load data to datamodel after filter or on startup // load data to datamodel after filter or on startup
String filter = choice.getSearchText(); String filter = choice.getSearchText();
@ -196,7 +292,7 @@ public class PickChoiceDialog extends MageDialog {
this.dataModel.clear(); this.dataModel.clear();
for (KeyValueItem item : this.allItems) { 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); this.dataModel.addElement(item);
} }
} }
@ -284,27 +380,33 @@ public class PickChoiceDialog extends MageDialog {
} }
} }
class KeyValueItem { static class KeyValueItem {
private final String Key; protected final String key;
private final String Value; protected final String value;
protected final ChoiceHintType hint;
public KeyValueItem(String value, String label) { public KeyValueItem(String key, String value, ChoiceHintType hint) {
this.Key = value; this.key = key;
this.Value = label; this.value = value;
this.hint = hint;
} }
public String getKey() { public String getKey() {
return this.Key; return this.key;
} }
public String getValue() { public String getValue() {
return this.Value; return this.value;
}
public ChoiceHintType getHint() {
return this.hint;
} }
@Override @Override
public String toString() { public String toString() {
return this.Value; return this.value;
} }
} }

View file

@ -1746,7 +1746,7 @@ public final class GamePanel extends javax.swing.JPanel {
hideAll(); hideAll();
// TODO: remember last choices and search incremental for same events? // TODO: remember last choices and search incremental for same events?
PickChoiceDialog pickChoice = new PickChoiceDialog(); 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) // special mode adds # to the answer (server side code must process that prefix, see replacementEffectChoice)
String specialPrefix = choice.isChosenSpecial() ? "#" : ""; String specialPrefix = choice.isChosenSpecial() ? "#" : "";

View file

@ -132,6 +132,7 @@ public class MageActionCallback implements ActionCallback {
@Override @Override
public void mouseEntered(MouseEvent e, final TransferData data) { public void mouseEntered(MouseEvent e, final TransferData data) {
// MouseEvent can be null for custom hints calls, e.g. from choose dialog
this.popupData = data; this.popupData = data;
handleMouseMoveOverNewCard(data); handleMouseMoveOverNewCard(data);
} }
@ -288,10 +289,13 @@ public class MageActionCallback implements ActionCallback {
@Override @Override
public void mouseMoved(MouseEvent e, TransferData data) { public void mouseMoved(MouseEvent e, TransferData data) {
// MouseEvent can be null for custom hints calls, e.g. from choose dialog
if (!Plugins.instance.isCardPluginLoaded()) { if (!Plugins.instance.isCardPluginLoaded()) {
return; return;
} }
if (!popupData.getCard().equals(data.getCard())) { if (this.popupData == null
|| !popupData.getCard().equals(data.getCard())) {
this.popupData = data; this.popupData = data;
handleMouseMoveOverNewCard(data); handleMouseMoveOverNewCard(data);
} }
@ -336,6 +340,7 @@ public class MageActionCallback implements ActionCallback {
@Override @Override
public void mouseExited(MouseEvent e, final TransferData data) { public void mouseExited(MouseEvent e, final TransferData data) {
// MouseEvent can be null for custom hints calls, e.g. from choose dialog
if (data != null) { if (data != null) {
hideAll(data.getGameId()); hideAll(data.getGameId());
} else { } else {
@ -452,6 +457,10 @@ public class MageActionCallback implements ActionCallback {
hideTooltipPopup(); hideTooltipPopup();
cancelTimeout(); cancelTimeout();
Component parentComponent = SwingUtilities.getRoot(cardPanel); 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(); Point parentPoint = parentComponent.getLocationOnScreen();
if (data.getLocationOnScreen() == null) { if (data.getLocationOnScreen() == null) {

View file

@ -162,6 +162,14 @@ public final class GUISizeHelper {
enlargedImageHeight = 25 * PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GUI_ENLARGED_IMAGE_SIZE, 20); 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) { public static void changePopupMenuFont(JPopupMenu popupMenu) {
for (Component comp : popupMenu.getComponents()) { for (Component comp : popupMenu.getComponents()) {
if (comp instanceof JMenuItem) { if (comp instanceof JMenuItem) {

View file

@ -44,8 +44,8 @@ public class CardInfoPaneImpl extends JEditorPane implements CardInfoPane {
} }
private void setGUISize() { private void setGUISize() {
addWidth = 20 * GUISizeHelper.cardTooltipFontSize - 50; addWidth = GUISizeHelper.getTooltipCardWidth();
addHeight = 12 * GUISizeHelper.cardTooltipFontSize - 20; addHeight = GUISizeHelper.getTooltipCardHeight();
setSize = true; setSize = true;
} }

View file

@ -9,6 +9,7 @@ import java.util.UUID;
/** /**
* Data for main card panel events like mouse moves or clicks * Data for main card panel events like mouse moves or clicks
*
*/ */
public class TransferData { public class TransferData {

View file

@ -39,6 +39,8 @@ public interface Choice extends Serializable, Copyable<Choice> {
String getSpecialHint(); String getSpecialHint();
ChoiceHintType getHintType();
// string choice // string choice
void setChoices(Set<String> choices); void setChoices(Set<String> choices);

View file

@ -0,0 +1,13 @@
package mage.choices;
/**
* For GUI: popup hint in choose dialog
*
* @author JayDi85
*/
public enum ChoiceHintType {
TEXT,
CARD,
CARD_DUNGEON
}

View file

@ -21,6 +21,7 @@ public class ChoiceImpl implements Choice {
protected String subMessage; protected String subMessage;
protected boolean searchEnabled = true; // enable for all windows by default protected boolean searchEnabled = true; // enable for all windows by default
protected String searchText; protected String searchText;
protected ChoiceHintType hintType;
// special button with #-answer // special button with #-answer
// warning, only for human's GUI, not AI // warning, only for human's GUI, not AI
@ -34,7 +35,12 @@ public class ChoiceImpl implements Choice {
} }
public ChoiceImpl(boolean required) { public ChoiceImpl(boolean required) {
this(required, ChoiceHintType.TEXT);
}
public ChoiceImpl(boolean required, ChoiceHintType hintType) {
this.required = required; this.required = required;
this.hintType = hintType;
} }
public ChoiceImpl(final ChoiceImpl choice) { public ChoiceImpl(final ChoiceImpl choice) {
@ -46,6 +52,7 @@ public class ChoiceImpl implements Choice {
this.subMessage = choice.subMessage; this.subMessage = choice.subMessage;
this.searchEnabled = choice.searchEnabled; this.searchEnabled = choice.searchEnabled;
this.searchText = choice.searchText; this.searchText = choice.searchText;
this.hintType = choice.hintType;
this.choices.addAll(choice.choices); this.choices.addAll(choice.choices);
this.choiceKey = choice.choiceKey; 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 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() { public String getSpecialHint() {
return this.specialHint; return this.specialHint;
} }
@Override
public ChoiceHintType getHintType() {
return this.hintType;
}
} }

View file

@ -14,6 +14,7 @@ import mage.abilities.effects.Effect;
import mage.abilities.text.TextPart; import mage.abilities.text.TextPart;
import mage.cards.FrameStyle; import mage.cards.FrameStyle;
import mage.choices.Choice; import mage.choices.Choice;
import mage.choices.ChoiceHintType;
import mage.choices.ChoiceImpl; import mage.choices.ChoiceImpl;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
@ -130,11 +131,15 @@ public class Dungeon implements CommandObject {
public static Dungeon selectDungeon(UUID playerId, Game game) { public static Dungeon selectDungeon(UUID playerId, Game game) {
Player player = game.getPlayer(playerId); 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.setMessage("Choose a dungeon to venture into");
choice.setChoices(dungeonNames); choice.setChoices(dungeonNames);
player.choose(Outcome.Neutral, choice, game); 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": case "Tomb of Annihilation":
return new TombOfAnnihilation(); return new TombOfAnnihilation();
case "Lost Mine of Phandelver": case "Lost Mine of Phandelver":