GUI: added card popup support in feedback/choice panel (related to #9609)

This commit is contained in:
Oleg Agafonov 2023-11-27 00:22:39 +04:00
parent 550e97e1e3
commit 1dc040a2c9
3 changed files with 197 additions and 177 deletions

View file

@ -1,158 +1,17 @@
package mage.client.components;
import mage.cards.action.TransferData;
import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import mage.client.MageFrame;
import mage.client.cards.BigCard;
import mage.client.cards.VirtualCardInfo;
import mage.client.dialog.PreferencesDialog;
import mage.client.game.GamePanel;
import mage.game.command.Plane;
import mage.util.CardUtil;
import mage.util.GameLog;
import mage.utils.ThreadUtils;
import mage.view.CardView;
import mage.view.PlaneView;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent.EventType;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
/**
* GUI: chats with html and hints popup support for objects
* GUI: component for display chats and game logs. Support html, mana symbols and popups.
*
* @author nantuko, JayDi85
*/
public class ColorPane extends JEditorPane {
final HTMLEditorKit kit = new HTMLEditorKit();
final HTMLDocument doc = new HTMLDocument();
private static final int CHAT_TOOLTIP_DELAY_MS = 50; // cards popup from chat must be fast all time
// cards popup info
private boolean hyperlinkEnabled = false;
VirtualCardInfo cardInfo = new VirtualCardInfo();
UUID gameId = null;
BigCard bigCard = null;
public class ColorPane extends MageEditorPane {
public ColorPane() {
this.setEditorKit(kit);
this.setDocument(doc);
}
private void addHyperlinkHandlers() {
if (Arrays.stream(getHyperlinkListeners()).findAny().isPresent()) {
throw new IllegalStateException("Wrong code usage: popup links support enabled already");
}
addHyperlinkListener(e -> ThreadUtils.threadPoolPopups.submit(() -> {
if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_TOOLTIPS_DELAY, 300) == 0) {
// if disabled
return;
}
// finds extra data in html element like object_id, alternative_name, etc
Map<String, String> extraData = new HashMap<>();
if (e.getSourceElement() instanceof HTMLDocument.RunElement) {
HTMLDocument.RunElement el = (HTMLDocument.RunElement) e.getSourceElement();
Enumeration attNames = el.getAttributeNames();
while (attNames.hasMoreElements()) {
Object attName = attNames.nextElement();
Object attValue = el.getAttribute(attName);
// custom attributes in SimpleAttributeSet element
if (attValue instanceof SimpleAttributeSet) {
SimpleAttributeSet attReal = (SimpleAttributeSet) attValue;
Enumeration attRealNames = attReal.getAttributeNames();
while (attRealNames.hasMoreElements()) {
Object attRealName = attRealNames.nextElement();
Object attRealValue = attReal.getAttribute(attRealName);
String name = attRealName.toString();
String value = attRealValue.toString();
extraData.put(name, value);
}
}
}
}
// try object first
CardView needCard = null;
GamePanel gamePanel = MageFrame.getGame(this.gameId);
if (gamePanel != null) {
UUID needObjectId = UUID.fromString(extraData.getOrDefault("object_id", ""));
needCard = gamePanel.getLastGameData().findCard(needObjectId);
}
String cardName = e.getDescription().substring(1);
String alternativeName = CardUtil.urlDecode(extraData.getOrDefault("alternative_name", ""));
if (!alternativeName.isEmpty()) {
cardName = alternativeName;
}
if (e.getEventType() == EventType.ENTERED) {
// show real object by priority (workable card hints and actual info)
CardView cardView = needCard;
// if no game object found then show default card
if (cardView == null) {
CardInfo card = CardRepository.instance.findCards(cardName).stream().findFirst().orElse(null);
if (card != null) {
cardView = new CardView(card.getMockCard());
}
}
// plane
if (cardView == null) {
Plane plane = Plane.createPlaneByFullName(cardName);
if (plane != null) {
cardView = new CardView(new PlaneView(plane));
}
}
// TODO: add other objects like dungeon, emblem, commander
if (cardView != null) {
cardInfo.init(cardView, this.bigCard, this.gameId);
cardInfo.setTooltipDelay(CHAT_TOOLTIP_DELAY_MS);
cardInfo.setPopupAutoLocationMode(TransferData.PopupAutoLocationMode.PUT_NEAR_MOUSE_POSITION);
SwingUtilities.invokeLater(() -> {
cardInfo.onMouseEntered(MouseInfo.getPointerInfo().getLocation());
cardInfo.onMouseMoved(MouseInfo.getPointerInfo().getLocation());
});
}
}
if (e.getEventType() == EventType.ACTIVATED) {
SwingUtilities.invokeLater(() -> {
cardInfo.onMouseWheel(MouseInfo.getPointerInfo().getLocation());
});
}
if (e.getEventType() == EventType.EXITED) {
SwingUtilities.invokeLater(() -> {
cardInfo.onMouseExited();
});
}
}));
addMouseListener(new MouseAdapter() {
@Override
public void mouseExited(MouseEvent e) {
cardInfo.onMouseExited();
}
});
}
public void setGameData(UUID gameId, BigCard bigCard) {
this.gameId = gameId;
this.bigCard = bigCard;
super();
}
/**
@ -169,26 +28,6 @@ public class ColorPane extends JEditorPane {
add(jPanel);
}
@Override
public void setText(String text) {
// must use append to enable popup/hyperlinks support
super.setText("");
append(text);
}
public void append(String text) {
try {
if (hyperlinkEnabled) {
text = GameLog.injectPopupSupport(text);
}
kit.insertHTML(doc, doc.getLength(), text, 0, 0, null);
int len = getDocument().getLength();
setCaretPosition(len);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* A little trick to paint black background under the text.
*
@ -208,10 +47,4 @@ public class ColorPane extends JEditorPane {
public void paintComponent(Graphics g) {
super.paintChildren(g);
}
public void enableHyperlinksAndCardPopups() {
hyperlinkEnabled = true;
addHyperlinkHandlers();
}
}

View file

@ -0,0 +1,183 @@
package mage.client.components;
import mage.cards.action.TransferData;
import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import mage.client.MageFrame;
import mage.client.cards.BigCard;
import mage.client.cards.VirtualCardInfo;
import mage.client.dialog.PreferencesDialog;
import mage.client.game.GamePanel;
import mage.game.command.Plane;
import mage.util.CardUtil;
import mage.util.GameLog;
import mage.utils.ThreadUtils;
import mage.view.CardView;
import mage.view.PlaneView;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
/**
* GUI: improved editor pane with html, hyperlinks/popup support
*
* @author JayDi85
*/
public class MageEditorPane extends JEditorPane {
private static final int CHAT_TOOLTIP_DELAY_MS = 50; // cards popup from chat must be fast all time
final HTMLEditorKit kit = new HTMLEditorKit();
final HTMLDocument doc;
MageEditorPane() {
super();
// merge with UI.setHTMLEditorKit
this.setEditorKit(kit);
this.doc = (HTMLDocument) this.getDocument(); // HTMLEditorKit must creates HTMLDocument, os use it here
}
// cards popup info
private boolean hyperlinkEnabled = false;
VirtualCardInfo cardInfo = new VirtualCardInfo();
UUID gameId = null;
BigCard bigCard = null;
public void setGameData(UUID gameId, BigCard bigCard) {
this.gameId = gameId;
this.bigCard = bigCard;
}
private void addHyperlinkHandlers() {
if (Arrays.stream(getHyperlinkListeners()).findAny().isPresent()) {
throw new IllegalStateException("Wrong code usage: popup links support enabled already");
}
addHyperlinkListener(e -> ThreadUtils.threadPoolPopups.submit(() -> {
if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_TOOLTIPS_DELAY, 300) == 0) {
// if disabled
return;
}
// finds extra data in html element like object_id, alternative_name, etc
Map<String, String> extraData = new HashMap<>();
if (e.getSourceElement() instanceof HTMLDocument.RunElement) {
HTMLDocument.RunElement el = (HTMLDocument.RunElement) e.getSourceElement();
Enumeration attNames = el.getAttributeNames();
while (attNames.hasMoreElements()) {
Object attName = attNames.nextElement();
Object attValue = el.getAttribute(attName);
// custom attributes in SimpleAttributeSet element
if (attValue instanceof SimpleAttributeSet) {
SimpleAttributeSet attReal = (SimpleAttributeSet) attValue;
Enumeration attRealNames = attReal.getAttributeNames();
while (attRealNames.hasMoreElements()) {
Object attRealName = attRealNames.nextElement();
Object attRealValue = attReal.getAttribute(attRealName);
String name = attRealName.toString();
String value = attRealValue.toString();
extraData.put(name, value);
}
}
}
}
// try object first
CardView needCard = null;
GamePanel gamePanel = MageFrame.getGame(this.gameId);
if (gamePanel != null) {
UUID needObjectId = UUID.fromString(extraData.getOrDefault("object_id", ""));
needCard = gamePanel.getLastGameData().findCard(needObjectId);
}
String cardName = e.getDescription().substring(1);
String alternativeName = CardUtil.urlDecode(extraData.getOrDefault("alternative_name", ""));
if (!alternativeName.isEmpty()) {
cardName = alternativeName;
}
if (e.getEventType() == HyperlinkEvent.EventType.ENTERED) {
// show real object by priority (workable card hints and actual info)
CardView cardView = needCard;
// if no game object found then show default card
if (cardView == null) {
CardInfo card = CardRepository.instance.findCards(cardName).stream().findFirst().orElse(null);
if (card != null) {
cardView = new CardView(card.getMockCard());
}
}
// plane
if (cardView == null) {
Plane plane = Plane.createPlaneByFullName(cardName);
if (plane != null) {
cardView = new CardView(new PlaneView(plane));
}
}
// TODO: add other objects like dungeon, emblem, commander
if (cardView != null) {
cardInfo.init(cardView, this.bigCard, this.gameId);
cardInfo.setTooltipDelay(CHAT_TOOLTIP_DELAY_MS);
cardInfo.setPopupAutoLocationMode(TransferData.PopupAutoLocationMode.PUT_NEAR_MOUSE_POSITION);
SwingUtilities.invokeLater(() -> {
cardInfo.onMouseEntered(MouseInfo.getPointerInfo().getLocation());
cardInfo.onMouseMoved(MouseInfo.getPointerInfo().getLocation());
});
}
}
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
SwingUtilities.invokeLater(() -> {
cardInfo.onMouseWheel(MouseInfo.getPointerInfo().getLocation());
});
}
if (e.getEventType() == HyperlinkEvent.EventType.EXITED) {
SwingUtilities.invokeLater(() -> {
cardInfo.onMouseExited();
});
}
}));
addMouseListener(new MouseAdapter() {
// TODO: add popup mouse wheel here?
@Override
public void mouseExited(MouseEvent e) {
cardInfo.onMouseExited();
}
});
}
@Override
public void setText(String text) {
// must use append to enable popup/hyperlinks support
super.setText("");
append(text);
}
public void append(String text) {
try {
if (hyperlinkEnabled) {
text = GameLog.injectPopupSupport(text);
}
kit.insertHTML(doc, doc.getLength(), text, 0, 0, null);
int len = getDocument().getLength();
setCaretPosition(len);
} catch (Exception e) {
e.printStackTrace();
}
}
public void enableHyperlinksAndCardPopups() {
hyperlinkEnabled = true;
addHyperlinkHandlers();
}
}

View file

@ -8,19 +8,21 @@ import org.mage.card.arcane.ManaSymbols;
import org.mage.card.arcane.UI;
/**
* Component for displaying text in mage. Supports drawing mana symbols.
* GUI: component for displaying text in feedback panel. Support html, mana symbols and popups.
*
* @author nantuko
*/
public class MageTextArea extends JEditorPane {
public class MageTextArea extends MageEditorPane {
private String currentText;
private int currentPanelWidth;
public MageTextArea() {
UI.setHTMLEditorKit(this);
super();
setEditable(false);
setBackground(new Color(0, 0, 0, 0)); // transparent background
setFocusable(false);
enableHyperlinksAndCardPopups();
}
@Override
@ -32,16 +34,18 @@ public class MageTextArea extends JEditorPane {
if (text == null) {
return;
}
if(text.equals(currentText) && panelWidth == currentPanelWidth)
if (text.equals(currentText) && panelWidth == currentPanelWidth) {
return;
}
currentText = text;
currentPanelWidth = panelWidth;
// prepare text format as header and details texts
final StringBuilder buffer = new StringBuilder(512);
// Dialog is a java logical font family, so it should work on all systems
buffer.append("<html><body style='font-family:Dialog;font-size:");
buffer.append("<body style='font-family:Dialog;font-size:");
buffer.append(GUISizeHelper.gameDialogAreaFontSizeBig);
buffer.append("pt;margin:3px 3px 3px 3px;color: #FFFFFF'><b><center>");
@ -54,11 +58,11 @@ public class MageTextArea extends JEditorPane {
if (!text.isEmpty()) {
buffer.append(basicText);
}
buffer.append("</b></center></body></html>");
SwingUtilities.invokeLater(() -> {
String promptText = buffer.toString();
System.out.println(promptText);
MageTextArea.super.setText(promptText);
// in case the text don't fit in the panel a tooltip with the text is added
if (panelWidth > 0 && MageTextArea.this.getPreferredSize().getWidth() > panelWidth) {