Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

import com.google.common.primitives.Ints;

Expand All @@ -18,8 +19,10 @@
import forge.model.FModel;
import forge.screens.match.CMatchUI;
import forge.screens.match.VAutoYields;
import forge.screens.match.views.VField;
import forge.screens.match.controllers.CDock.ArcState;
import forge.toolbox.FSkin.SkinIcon;
import forge.toolbox.FSkin.SkinnedCheckBoxMenuItem;
import forge.toolbox.FSkin.SkinnedMenu;
import forge.toolbox.FSkin.SkinnedMenuItem;
import forge.toolbox.FSkin.SkinnedRadioButtonMenuItem;
Expand Down Expand Up @@ -50,6 +53,8 @@ public JMenu getMenu() {
menu.addSeparator();
menu.add(getMenuItem_TargetingArcs());
menu.add(new CardOverlaysMenu(matchUI).getMenu());
menu.add(getSubmenu_StackGroupPermanents());
menu.add(getMenuItem_TokensSeparateRow());
menu.add(getMenuItem_AutoYields());
menu.addSeparator();
menu.add(getMenuItem_ViewDeckList());
Expand Down Expand Up @@ -195,4 +200,58 @@ private SkinnedMenuItem getMenuItem_ViewDeckList() {
private ActionListener getViewDeckListAction() {
return e -> matchUI.viewDeckList();
}

private SkinnedMenu getSubmenu_StackGroupPermanents() {
final Localizer localizer = Localizer.getInstance();
final SkinnedMenu submenu = new SkinnedMenu(localizer.getMessage("cbpStackGroupPermanents"));
final ButtonGroup group = new ButtonGroup();
final String current = prefs.getPref(FPref.UI_GROUP_PERMANENTS);

final String[] options = {"Default", "Stack Creatures", "Group Creatures/Tokens", "Group All Permanents"};
final String[] tooltips = {
"Creatures are never grouped or stacked. Identical lands and tokens are stacked (up to 5). Identical artifacts and enchantments are stacked (up to 4). Stacking fans cards out so each copy is partially visible.",
"Same as Default, but creatures are also stacked (up to 4).",
"Group identical creatures and tokens into a single compact pile with a count badge.",
"Group all identical permanents into a single compact pile with a count badge."
};
for (int i = 0; i < options.length; i++) {
SkinnedRadioButtonMenuItem item = new SkinnedRadioButtonMenuItem(options[i]);
item.setToolTipText(tooltips[i]);
item.setSelected(options[i].equals(current));
item.addActionListener(getGroupPermanentsAction(options[i]));
group.add(item);
submenu.add(item);
}
return submenu;
}

private SkinnedCheckBoxMenuItem getMenuItem_TokensSeparateRow() {
final Localizer localizer = Localizer.getInstance();
SkinnedCheckBoxMenuItem menuItem = new SkinnedCheckBoxMenuItem(localizer.getMessage("cbpTokensSeparateRow"));
menuItem.setToolTipText("Show tokens in their own row instead of mixed with creatures.");
menuItem.setState(prefs.getPrefBoolean(FPref.UI_TOKENS_IN_SEPARATE_ROW));
menuItem.addActionListener(e -> {
final boolean enabled = !prefs.getPrefBoolean(FPref.UI_TOKENS_IN_SEPARATE_ROW);
prefs.setPref(FPref.UI_TOKENS_IN_SEPARATE_ROW, enabled);
prefs.save();
SwingUtilities.invokeLater(() -> {
for (final VField f : matchUI.getFieldViews()) {
f.getTabletop().doLayout();
}
});
});
return menuItem;
}

private ActionListener getGroupPermanentsAction(final String value) {
return e -> {
prefs.setPref(FPref.UI_GROUP_PERMANENTS, value);
prefs.save();
SwingUtilities.invokeLater(() -> {
for (final VField f : matchUI.getFieldViews()) {
f.getTabletop().doLayout();
}
});
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ public MouseTriggerEvent(final MouseEvent event) {
this.y = event.getY();
}

public MouseTriggerEvent(final int button, final int x, final int y) {
this.button = button;
this.x = x;
this.y = y;
}

@Override
public int getButton() {
return button;
Expand Down
72 changes: 72 additions & 0 deletions forge-gui-desktop/src/main/java/forge/view/arcane/CardPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
private boolean isSelected;
private boolean hasFlash;
private CachedCardImage cachedImage;
private int groupCount;
private Font badgeFont;
private int badgeFontCardWidth; // cardWidth when badgeFont was last computed

private static final Color BADGE_BG_COLOR = new Color(0, 0, 0, 180);
private static Font smallCounterFont;
private static Font largeCounterFont;

Expand Down Expand Up @@ -250,6 +254,13 @@ public final void setDisplayEnabled(final boolean displayEnabled0) {
displayEnabled = displayEnabled0;
}

public int getGroupCount() {
return groupCount;
}
public void setGroupCount(int count) {
this.groupCount = count;
}

public final void setAnimationPanel(final boolean isAnimationPanel0) {
isAnimationPanel = isAnimationPanel0;
}
Expand Down Expand Up @@ -385,6 +396,9 @@ protected final void paintChildren(final Graphics g) {

}
displayIconOverlay(g, canShow);
if (groupCount >= 2) {
drawGroupCountBadge(g);
}
if (canShow) {
drawFoilEffect(g, card, cardXOffset, cardYOffset,
cardWidth, cardHeight, Math.round(cardWidth * BLACK_BORDER_SIZE));
Expand Down Expand Up @@ -496,6 +510,64 @@ private void displayCardNameOverlay(final boolean isVisible, final Dimension img
titleText.setVisible(isVisible);
}

private void drawGroupCountBadge(final Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

if (badgeFont == null || badgeFontCardWidth != cardWidth) {
badgeFont = new Font("Dialog", Font.BOLD, Math.max(10, cardWidth / 5));
badgeFontCardWidth = cardWidth;
}

String text = "\u00D7" + groupCount;
FontMetrics fm = g2d.getFontMetrics(badgeFont);

int textWidth = fm.stringWidth(text);
int textHeight = fm.getAscent();
int padX = Math.max(4, cardWidth / 20);
int padY = Math.max(2, cardHeight / 30);
int badgeWidth = textWidth + padX * 2;
int badgeHeight = textHeight + padY * 2;
int badgeX = cardXOffset + 2;
int badgeY = cardYOffset + 2;
int cornerRadius = Math.max(4, cardWidth / 16);

g2d.setColor(BADGE_BG_COLOR);
g2d.fillRoundRect(badgeX, badgeY, badgeWidth, badgeHeight, cornerRadius, cornerRadius);

g2d.setColor(Color.WHITE);
g2d.setFont(badgeFont);
g2d.drawString(text, badgeX + padX, badgeY + padY + textHeight);
}

public boolean isBadgeHit(int mouseX, int mouseY) {
if (groupCount < 2) {
return false;
}
// Badge is drawn at (cardXOffset+2, cardYOffset+2) in the card's local
// coordinate space. Mouse coordinates are container-relative. When the
// card is tapped, the graphics are rotated but mouse events are not, so
// we must inverse-rotate the mouse point into the card's local frame.
int localX = mouseX - getX();
int localY = mouseY - getY();
if (tappedAngle > 0) {
float pivotX = cardXOffset + cardWidth / 2f;
float pivotY = cardYOffset + cardHeight - cardWidth / 2f;
double cos = Math.cos(-tappedAngle);
double sin = Math.sin(-tappedAngle);
float dx = localX - pivotX;
float dy = localY - pivotY;
localX = (int) Math.round(cos * dx - sin * dy + pivotX);
localY = (int) Math.round(sin * dx + cos * dy + pivotY);
}
int badgeX = cardXOffset + 2;
int badgeY = cardYOffset + 2;
int badgeWidth = Math.max(30, cardWidth / 3);
int badgeHeight = Math.max(20, cardHeight / 6);
return localX >= badgeX && localX <= badgeX + badgeWidth
&& localY >= badgeY && localY <= badgeY + badgeHeight;
}

private void displayIconOverlay(final Graphics g, final boolean canShow) {
if (canShow && showCardManaCostOverlay() && cardWidth < 200) {
final boolean showSplitMana = card.isSplitCard() && card.getZone() != ZoneType.Battlefield;
Expand Down
Loading