Skip to content
Open
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 @@ -619,8 +619,8 @@ private void initializeGraveyardOrderingComboBox() {

private void initializeStackGroupPermanentsComboBox() {
final Localizer localizer = Localizer.getInstance();
final String[] keys = {"default", "stack", "group_creatures", "group_all"};
final String[] labelKeys = {"lblGroupDefault", "lblGroupStack", "lblGroupCreatures", "lblGroupAll"};
final String[] keys = {"default", "stack", "group_tokens", "group_creatures", "group_all"};
final String[] labelKeys = {"lblGroupDefault", "lblGroupStack", "lblGroupTokens", "lblGroupCreatures", "lblGroupAll"};
final Map<String, String> mapping = new LinkedHashMap<>();
final String[] labels = new String[keys.length];
for (int i = 0; i < keys.length; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ private SkinnedMenu getSubmenu_StackGroupPermanents() {
final ButtonGroup group = new ButtonGroup();
final String current = prefs.getPref(FPref.UI_GROUP_PERMANENTS);

final String[] keys = {"default", "stack", "group_creatures", "group_all"};
final String[] labelKeys = {"lblGroupDefault", "lblGroupStack", "lblGroupCreatures", "lblGroupAll"};
final String[] tooltipKeys = {"nlGroupDefault", "nlGroupStack", "nlGroupCreatures", "nlGroupAll"};
final String[] keys = {"default", "stack", "group_tokens", "group_creatures", "group_all"};
final String[] labelKeys = {"lblGroupDefault", "lblGroupStack", "lblGroupTokens", "lblGroupCreatures", "lblGroupAll"};
final String[] tooltipKeys = {"nlGroupDefault", "nlGroupStack", "nlGroupTokens", "nlGroupCreatures", "nlGroupAll"};
for (int i = 0; i < keys.length; i++) {
final SkinnedRadioButtonMenuItem item = MenuUtil.createStayOpenSkinnedRadioButton(localizer.getMessage(labelKeys[i]));
item.setToolTipText(localizer.getMessage(tooltipKeys[i]));
Expand Down
25 changes: 24 additions & 1 deletion forge-gui-desktop/src/main/java/forge/view/arcane/CardPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,29 @@ private void displayCardNameOverlay(final boolean isVisible, final Dimension img
titleText.setVisible(isVisible);
}

private static String formatGroupCount(int count) {
if (count < 1000) {
return String.valueOf(count);
}
if (count < 1000000) {
return formatLargeValue(count / 1000.0) + "k";
}
if (count < 1000000000) {
return formatLargeValue(count / 1000000.0) + "M";
}
return formatLargeValue(count / 1000000000.0) + "B";
}

private static String formatLargeValue(double val) {
if (val >= 100) {
return String.format(java.util.Locale.ENGLISH, "%.0f", val);
}
if (val >= 10) {
return String.format(java.util.Locale.ENGLISH, "%.1f", val).replace(".0", "");
}
return String.format(java.util.Locale.ENGLISH, "%.2f", val).replace(".00", "").replace(".0", "");
}

private void drawGroupCountBadge(final Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Expand All @@ -571,7 +594,7 @@ private void drawGroupCountBadge(final Graphics g) {
badgeFontCardWidth = cardWidth;
}

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

int textWidth = fm.stringWidth(text);
Expand Down
10 changes: 6 additions & 4 deletions forge-gui-desktop/src/main/java/forge/view/arcane/PlayArea.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen

private boolean makeTokenRow = true;
private boolean stackCreatures = false;
private boolean groupTokens;
private boolean groupTokensAndCreatures;
private boolean groupAll;
private boolean grouping;
Expand All @@ -113,9 +114,10 @@ public PlayArea(final CMatchUI matchUI, final FScrollPane scrollPane, final bool
private void updateGroupScope() {
String groupScope = FModel.getPreferences().getPref(FPref.UI_GROUP_PERMANENTS);
this.stackCreatures = "stack".equals(groupScope);
this.groupTokens = "group_tokens".equals(groupScope) || "group_creatures".equals(groupScope) || "group_all".equals(groupScope);
this.groupTokensAndCreatures = "group_creatures".equals(groupScope) || "group_all".equals(groupScope);
this.groupAll = "group_all".equals(groupScope);
this.grouping = groupTokensAndCreatures || groupAll;
this.grouping = groupTokens || groupTokensAndCreatures || groupAll;
int prefDepth = FModel.getPreferences().getPrefInt(FPref.UI_MAX_STACK_DEPTH);
this.maxStackDepth = Math.max(MIN_STACK_DEPTH, Math.min(MAX_STACK_DEPTH, prefDepth));
}
Expand All @@ -134,11 +136,11 @@ private CardStackRow collectAllTokens(List<CardPanel> remainingPanels) {
&& card.isSick() == first.isSick()
&& card.hasSamePT(first)
&& card.getText().equals(first.getText())
&& (!groupTokensAndCreatures || card.isTapped() == first.isTapped())
&& (!groupTokensAndCreatures || card.getDamage() == first.getDamage());
&& (!groupTokens || card.isTapped() == first.isTapped())
&& (!groupTokens || card.getDamage() == first.getDamage());
return collectStacked(remainingPanels, RowType.Token,
base.and(this::compatibleUnderCombatSeparation),
maxStackDepth, groupTokensAndCreatures);
maxStackDepth, groupTokens);
}

private CardStackRow collectAllCreatures(List<CardPanel> remainingPanels) {
Expand Down
51 changes: 50 additions & 1 deletion forge-gui-mobile/src/forge/card/CardRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -725,12 +725,20 @@ public static void drawCard(Graphics g, CardView card, float x, float y, float w
}

public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos) {
drawCardWithOverlays(g, card, x, y, w, h, pos, false, false, false);
drawCardWithOverlays(g, card, x, y, w, h, pos, false, false, false, 0);
}

public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, int groupCount) {
drawCardWithOverlays(g, card, x, y, w, h, pos, false, false, false, groupCount);
}

static float markersHeight = 0f;

public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean stackview, boolean showAltState, boolean isChoiceList) {
drawCardWithOverlays(g, card, x, y, w, h, pos, stackview, showAltState, isChoiceList, 0);
}

public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean stackview, boolean showAltState, boolean isChoiceList, int groupCount) {
boolean canShow = MatchController.instance.mayView(card);
float oldAlpha = g.getfloatAlphaComposite();
boolean unselectable = !MatchController.instance.isSelectable(card) && MatchController.instance.isSelecting();
Expand All @@ -747,6 +755,10 @@ public static void drawCardWithOverlays(Graphics g, CardView card, float x, floa
w -= 2 * padding;
h -= 2 * padding;

if (groupCount >= 2) {
drawGroupCountBadge(g, groupCount, cx, cy, cw, ch);
}

// TODO: A hacky workaround is currently used to make the game not leak the color information for Morph cards.
final CardStateView details = showAltState ? card.getAlternateState() : isChoiceList && card.isSplitCard() ? card.getLeftSplitState() : card.getCurrentState();
final boolean isFaceDown = card.isFaceDown();
Expand Down Expand Up @@ -1200,4 +1212,41 @@ public void dispose() {
}
});
}

private static void drawGroupCountBadge(Graphics g, int count, float x, float y, float w, float h) {
String text = "×" + formatGroupCount(count);
FSkinFont font = FSkinFont.forHeight(h * 0.15f);
float textWidth = font.getBounds(text).width;
float textHeight = font.getCapHeight();
float padX = w / 20f;
float padY = h / 30f;
float badgeWidth = textWidth + padX * 2;
float badgeHeight = textHeight + padY * 2;

g.fillRect(new Color(0, 0, 0, 0.7f), x + 2, y + 2, badgeWidth, badgeHeight);
g.drawText(text, font, Color.WHITE, x + 2 + padX, y + 2 + padY, textWidth, textHeight, false, Align.left, false);
}

private static String formatGroupCount(int count) {
if (count < 1000) {
return String.valueOf(count);
}
if (count < 1000000) {
return formatLargeValue(count / 1000.0) + "k";
}
if (count < 1000000000) {
return formatLargeValue(count / 1000000.0) + "M";
}
return formatLargeValue(count / 1000000000.0) + "B";
}

private static String formatLargeValue(double val) {
if (val >= 100) {
return String.format(java.util.Locale.ENGLISH, "%.0f", val);
}
if (val >= 10) {
return String.format(java.util.Locale.ENGLISH, "%.1f", val).replace(".0", "");
}
return String.format(java.util.Locale.ENGLISH, "%.2f", val).replace(".00", "").replace(".0", "");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ public void clear() {
}
}

protected boolean isGroupingEnabled(CardAreaPanel cardPanel) {
return false;
}

private int addCards(CardAreaPanel cardPanel, float x, float y, float cardWidth, float cardHeight) {
int totalCount = 0;
List<CardAreaPanel> attachedPanels = cardPanel.getAttachedPanels();
Expand All @@ -174,9 +178,20 @@ private int addCards(CardAreaPanel cardPanel, float x, float y, float cardWidth,
cardPanel.setBounds(x, y, cardWidth, cardHeight);

if (cardPanel.getNextPanelInStack() != null) { //add next panel in stack if needed
x += cardWidth * getCardStackOffset();
float offset = getCardStackOffset();
if (isGroupingEnabled(cardPanel)) {
offset = 0;
}
x += cardWidth * offset;
totalCount += addCards(cardPanel.getNextPanelInStack(), x, y, cardWidth, cardHeight);
}

if (cardPanel.getPrevPanelInStack() == null) {
cardPanel.setGroupCount(totalCount + 1);
} else {
cardPanel.setGroupCount(0);
}

return totalCount + 1;
}

Expand All @@ -201,7 +216,11 @@ protected ScrollBounds layoutAndGetScrollBounds(float visibleWidth, float visibl
for (CardAreaPanel cardPanel : new ArrayList<>(cardPanels.get())) {
if (cardPanel != null) {
int count = addCards(cardPanel, x, y, cardWidth, cardHeight);
x += cardWidth + (count - 1) * cardWidth * getCardStackOffset();
float offset = getCardStackOffset();
if (isGroupingEnabled(cardPanel)) {
offset = 0;
}
x += cardWidth + (count - 1) * cardWidth * offset;
}
}

Expand Down
15 changes: 15 additions & 0 deletions forge-gui-mobile/src/forge/screens/match/views/VField.java
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,21 @@ private FieldRow() {
setVisible(true); //make visible by default unlike other display areas
}

@Override
protected boolean isGroupingEnabled(CardAreaPanel cardPanel) {
String groupScope = FModel.getPreferences().getPref(ForgePreferences.FPref.UI_GROUP_PERMANENTS);
if ("group_all".equals(groupScope)) {
return true;
}
if ("group_creatures".equals(groupScope)) {
return cardPanel.getCard().isCreature() || cardPanel.getCard().isToken();
}
if ("group_tokens".equals(groupScope)) {
return cardPanel.getCard().isToken();
}
return false;
}

@Override
protected float getCardWidth(float cardHeight) {
return cardHeight; //allow cards room to tap
Expand Down
12 changes: 11 additions & 1 deletion forge-gui-mobile/src/forge/toolbox/FCardPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,16 @@ public void draw(Graphics g) {
}
}

private int groupCount;

public int getGroupCount() {
return groupCount;
}

public void setGroupCount(int groupCount0) {
groupCount = groupCount0;
}

private void rotateTransform(Graphics g, float x, float y, float w, float h, float edgeOffset, boolean animate) {
if (tapped) {
g.startRotateTransform(x + edgeOffset, y + h - edgeOffset, getTappedAngle());
Expand All @@ -169,7 +179,7 @@ private void rotateTransform(Graphics g, float x, float y, float w, float h, flo
transformAnimation.start();
transformAnimation.drawCard(g, card, x, y, w, h);
} else {
CardRenderer.drawCardWithOverlays(g, card, x, y, w, h, getStackPosition());
CardRenderer.drawCardWithOverlays(g, card, x, y, w, h, getStackPosition(), groupCount);
if (Forge.hasGamepad() && isHovered())
g.drawRect(3f, Color.LIME, x, y, w, h);
}
Expand Down
2 changes: 2 additions & 0 deletions forge-gui/res/languages/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,12 @@ cbpStackGroupPermanents=Stack/Group Permanents
cbpTokensSeparateRow=Tokens in Separate Row
lblGroupDefault=Default
lblGroupStack=Stack Creatures
lblGroupTokens=Group Tokens
lblGroupCreatures=Group Creatures/Tokens
lblGroupAll=Group All Permanents
nlGroupDefault=Creatures are never grouped or stacked. Identical lands, tokens, artifacts, and enchantments are stacked. Stacking fans cards out so each copy is partially visible.
nlGroupStack=Same as Default, but creatures are also stacked.
nlGroupTokens=Group identical tokens into a single compact pile with a count badge. Creatures are fanned out.
nlGroupCreatures=Group identical creatures and tokens into a single compact pile with a count badge.
nlGroupAll=Group all identical permanents into a single compact pile with a count badge.
nlTokensSeparateRow=Show tokens in their own row instead of mixed with creatures.
Expand Down
16 changes: 16 additions & 0 deletions forge-gui/res/languages/es-ES.properties
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,22 @@ nlHideReminderText=Ocultar el texto del recordatorio en el panel de Detalle de l
nlCardTextUseSansSerif=Renderiza las imágenes de las cartas utilizando la fuente Sans-serif para el texto de las cartas. (Requiere reiniciar)
nlCardTextHideReminder=Cuando se renderizan las imágenes de las cartas, se omite la renderización del texto de recordatorio.
nlOpenPacksIndiv=Al abrir packs de cartas (Fat Packs) y cajas de sobres, los sobres se abrirán y se mostrarán de uno en uno.
cbpStackGroupPermanents=Apilar/Agrupar permanentes
cbpTokensSeparateRow=Fichas en fila separada
lblGroupDefault=Por defecto
lblGroupStack=Apilar criaturas
lblGroupTokens=Agrupar fichas
lblGroupCreatures=Agrupar criaturas/fichas
lblGroupAll=Agrupar todos los permanentes
nlGroupDefault=Las criaturas nunca se agrupan ni se apilan. Las tierras, fichas, artefactos y encantamientos idénticos se apilan. El apilamiento despliega las cartas para que cada copia sea parcialmente visible.
nlGroupStack=Igual que por defecto, pero las criaturas también se apilan.
nlGroupTokens=Agrupa las fichas idénticas en una sola pila compacta con un contador. Las criaturas se despliegan individualmente.
nlGroupCreatures=Agrupa las criaturas y fichas idénticas en una sola pila compacta con un contador.
nlGroupAll=Agrupa todos los permanentes idénticos en una sola pila compacta con un contador.
nlTokensSeparateRow=Muestra las fichas en su propia fila en lugar de mezcladas con las criaturas.
nlGroupPermanents=Controla cómo se apilan o agrupan los permanentes idénticos en el campo de batalla.
cbpMaxStackDepth=Profundidad máxima de pila
nlMaxStackDepth=Máximo de cartas por pila o grupo en el campo de batalla. En los modos Por defecto y Apilar criaturas, las cartas idénticas adicionales forman una nueva pila junto a la original. En los modos de Agrupar, los extras se ocultan detrás de la carta superior con el contador.
nlTokensInSeparateRow=Muestra las fichas en una fila separada en el campo de batalla debajo de las criaturas que no son fichas.
nlStackCreatures=Apila criaturas idénticas en el campo de batalla, como tierras, artefactos y encantamientos.
nlSeparateCombatStacks=Separa las criaturas apiladas mientras atacan o bloquean para que las flechas de combate sean más fáciles de seguir.
Expand Down