Files
forge/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java
Anthony Calosa d88edc4e31 [Mobile] Update Deck AI Indicator
- AI Indicator (green) means all cards on the main deck are AI playable
2022-03-03 15:31:28 +08:00

1114 lines
44 KiB
Java

package forge.itemmanager.views;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Align;
import forge.Forge;
import forge.Forge.KeyInputAdapter;
import forge.Graphics;
import forge.ImageKeys;
import forge.assets.*;
import forge.assets.FSkinColor.Colors;
import forge.card.*;
import forge.card.CardRenderer.CardStackPosition;
import forge.deck.*;
import forge.deck.io.DeckPreferences;
import forge.game.card.CardView;
import forge.gamemodes.limited.CardRanker;
import forge.gamemodes.planarconquest.ConquestCommander;
import forge.item.InventoryItem;
import forge.item.PaperCard;
import forge.itemmanager.*;
import forge.itemmanager.filters.ItemFilter;
import forge.localinstance.properties.ForgePreferences;
import forge.model.FModel;
import forge.toolbox.*;
import forge.toolbox.FEvent.FEventHandler;
import forge.util.ImageUtil;
import forge.util.Localizer;
import forge.util.TextUtil;
import forge.util.Utils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
public class ImageView<T extends InventoryItem> extends ItemView<T> {
private static final float PADDING = Utils.scale(5);
private static final float PILE_SPACING_Y = 0.1f;
private static final FSkinFont LABEL_FONT = FSkinFont.get(12);
private static final FSkinColor GROUP_HEADER_FORE_COLOR = FSkinColor.get(Colors.CLR_TEXT);
private static final FSkinColor GROUP_HEADER_LINE_COLOR = GROUP_HEADER_FORE_COLOR.alphaColor(0.5f);
private static final FSkinFont GROUP_HEADER_FONT = LABEL_FONT;
private static final float GROUP_HEADER_HEIGHT = Utils.scale(19);
private static final float GROUP_HEADER_GLYPH_WIDTH = Utils.scale(6);
private static final float GROUP_HEADER_LINE_THICKNESS = Utils.scale(1);
private static final float SEL_BORDER_SIZE = Utils.scale(1);
private static final int MIN_COLUMN_COUNT = Forge.isLandscapeMode() ? 2 : 1;
private static final int MAX_COLUMN_COUNT = 10;
private final List<Integer> selectedIndices = new ArrayList<>();
private int columnCount = 4;
private float scrollHeight = 0;
private ColumnDef pileBy = null;
private GroupDef groupBy = null;
private ItemInfo focalItem;
private boolean updatingLayout;
private float totalZoomAmount;
private final List<ItemInfo> orderedItems = new ArrayList<>();
private final List<Group> groups = new ArrayList<>();
private class ExpandCollapseButton extends FLabel {
private boolean isAllCollapsed;
private ExpandCollapseButton() {
super(new FLabel.ButtonBuilder());
setCommand(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
if (groupBy == null || model.getItems().isEmpty()) { return; }
boolean collapsed = !isAllCollapsed;
for (Group group : groups) {
group.isCollapsed = collapsed;
}
updateIsAllCollapsed();
clearSelection(); //must clear selection since indices and visible items will be changing
updateLayout(false);
}
});
}
private void updateIsAllCollapsed() {
boolean isAllCollapsed0 = true;
for (Group group : groups) {
if (!group.isCollapsed) {
isAllCollapsed0 = false;
break;
}
}
isAllCollapsed = isAllCollapsed0;
}
@Override
protected void drawContent(Graphics g, float w, float h, final boolean pressed) {
float lineThickness = Utils.scale(1);
float offset = 2 * lineThickness;
float squareSize = Math.round(w / 2 - offset);
if (squareSize % 2 == 1) {
squareSize++; //needs to be even number for this to look right
}
float x = Math.round((w - squareSize) / 2 - offset);
float y = Math.round((h - squareSize) / 2 - offset);
if (pressed) {
y += lineThickness;
}
else {
x -= lineThickness;
}
for (int i = 0; i < 2; i++) {
g.drawLine(lineThickness, GROUP_HEADER_FORE_COLOR, x, y, x + squareSize, y);
g.drawLine(lineThickness, GROUP_HEADER_FORE_COLOR, x + squareSize, y, x + squareSize, y + offset);
g.drawLine(lineThickness, GROUP_HEADER_FORE_COLOR, x, y, x, y + squareSize);
g.drawLine(lineThickness, GROUP_HEADER_FORE_COLOR, x, y + squareSize, x + offset, y + squareSize);
x += offset;
y += offset;
}
g.drawLine(lineThickness, GROUP_HEADER_FORE_COLOR, x, y, x + squareSize, y);
g.drawLine(lineThickness, GROUP_HEADER_FORE_COLOR, x + squareSize, y, x + squareSize, y + squareSize);
g.drawLine(lineThickness, GROUP_HEADER_FORE_COLOR, x, y, x, y + squareSize);
g.drawLine(lineThickness, GROUP_HEADER_FORE_COLOR, x, y + squareSize, x + squareSize, y + squareSize);
g.drawLine(lineThickness, GROUP_HEADER_FORE_COLOR, x + offset + 1, y + squareSize / 2, x + squareSize - offset, y + squareSize / 2);
if (isAllCollapsed) {
g.drawLine(lineThickness, GROUP_HEADER_FORE_COLOR, x + squareSize / 2, y + offset, x + squareSize / 2, y + squareSize - offset - 1);
}
}
}
private final ExpandCollapseButton btnExpandCollapseAll = new ExpandCollapseButton();
private final FComboBox<Object> cbGroupByOptions = new FComboBox<>(Localizer.getInstance().getMessage("lblGroups") + " ");
private final FComboBox<Object> cbPileByOptions = new FComboBox<>(Localizer.getInstance().getMessage("lblPiles") + " ");
public ImageView(ItemManager<T> itemManager0, ItemManagerModel<T> model0) {
super(itemManager0, model0);
SItemManagerUtil.populateImageViewOptions(itemManager0, cbGroupByOptions, cbPileByOptions);
cbGroupByOptions.setChangedHandler(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
if (cbGroupByOptions.getSelectedIndex() > 0) {
setGroupBy((GroupDef) cbGroupByOptions.getSelectedItem());
}
else {
setGroupBy(null);
}
}
});
cbPileByOptions.setChangedHandler(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
if (cbPileByOptions.getSelectedIndex() > 0) {
setPileBy((ColumnDef) cbPileByOptions.getSelectedItem());
}
else {
setPileBy(null);
}
}
});
cbGroupByOptions.setFont(LABEL_FONT);
cbPileByOptions.setFont(LABEL_FONT);
getPnlOptions().add(btnExpandCollapseAll);
getPnlOptions().add(cbGroupByOptions);
getPnlOptions().add(cbPileByOptions);
Group group = new Group(""); //add default group
groups.add(group);
getScroller().add(group);
}
@Override
public void setup(ItemManagerConfig config, Map<ColumnDef, ItemColumn> colOverrides) {
setGroupBy(config.getGroupBy(), true);
setPileBy(config.getPileBy(), true);
setColumnCount(config.getImageColumnCount(), true);
}
public GroupDef getGroupBy() {
return groupBy;
}
public void setGroupBy(GroupDef groupBy0) {
setGroupBy(groupBy0, false);
}
private void setGroupBy(GroupDef groupBy0, boolean forSetup) {
if (groupBy == groupBy0) { return; }
groupBy = groupBy0;
if (groupBy == null) {
cbGroupByOptions.setSelectedIndex(0);
}
else {
cbGroupByOptions.setSelectedItem(groupBy);
}
groups.clear();
if (groupBy == null) {
groups.add(new Group(""));
btnExpandCollapseAll.updateIsAllCollapsed();
}
else {
for (String groupName : groupBy.getGroups()) {
groups.add(new Group(groupName));
}
//collapse all groups by default if all previous groups were collapsed
if (btnExpandCollapseAll.isAllCollapsed) {
for (Group group : groups) {
group.isCollapsed = true;
}
}
}
getScroller().clear();
for (Group group : groups) {
getScroller().add(group);
}
if (!forSetup) {
if (itemManager.getConfig() != null) {
itemManager.getConfig().setGroupBy(groupBy);
}
refresh(null, -1, 0);
}
}
public ColumnDef getPileBy() {
return pileBy;
}
public void setPileBy(ColumnDef pileBy0) {
setPileBy(pileBy0, false);
}
private void setPileBy(ColumnDef pileBy0, boolean forSetup) {
if (pileBy == pileBy0) { return; }
pileBy = pileBy0;
if (pileBy == null) {
cbPileByOptions.setSelectedIndex(0);
}
else {
cbPileByOptions.setSelectedItem(pileBy);
}
if (!forSetup) {
if (itemManager.getConfig() != null) {
itemManager.getConfig().setPileBy(pileBy);
}
refresh(null, -1, 0);
}
}
@Override
protected void fixSelection(final Iterable<T> itemsToSelect, final int backupIndexToSelect, final float scrollValueToRestore) {
clearSelection(); //just clear selection instead of fixing selection this way
setScrollValue(scrollValueToRestore); //ensure scroll value restored
}
public int getColumnCount() {
return columnCount;
}
public void setColumnCount(int columnCount0) {
setColumnCount(columnCount0, false);
}
private void setColumnCount(int columnCount0, boolean forSetup) {
if (columnCount0 < MIN_COLUMN_COUNT) {
columnCount0 = MIN_COLUMN_COUNT;
}
else if (columnCount0 > MAX_COLUMN_COUNT) {
columnCount0 = MAX_COLUMN_COUNT;
}
if (columnCount == columnCount0) { return; }
columnCount = columnCount0;
if (!forSetup) {
if (itemManager.getConfig() != null) {
itemManager.getConfig().setImageColumnCount(columnCount);
}
//determine item to retain scroll position of following column count change
ItemInfo focalItem0 = getFocalItem();
if (focalItem0 == null) {
updateLayout(false);
return;
}
float offsetTop = focalItem0.getTop() - getScrollValue();
updateLayout(false);
setScrollValue(focalItem0.getTop() - offsetTop);
focalItem = focalItem0; //cache focal item so consecutive column count changes use the same item
}
}
private ItemInfo getFocalItem() {
if (focalItem != null) { //use cached focalItem if one
return focalItem;
}
//if not item hovered, use first fully visible item as focal point
final float visibleTop = getScrollValue();
for (Group group : groups) {
if (group.getBottom() < visibleTop) {
continue;
}
for (Pile pile : group.piles) {
if (group.getBottom() < visibleTop) {
continue;
}
for (ItemInfo item : pile.items) {
if (item.getTop() >= visibleTop) {
return item;
}
}
}
}
if (orderedItems.isEmpty()) {
return null;
}
return orderedItems.get(0);
}
@Override
protected void onResize(float visibleWidth, float visibleHeight) {
updateLayout(false); //need to update layout to adjust wrapping of items
}
@Override
protected void onRefresh() {
Group otherItems = groupBy == null ? groups.get(0) : null;
for (Group group : groups) {
group.items.clear();
}
clearSelection();
for (Entry<T, Integer> itemEntry : model.getOrderedList()) {
T item = itemEntry.getKey();
int qty = itemEntry.getValue();
int groupIndex = groupBy == null ? -1 : groupBy.getItemGroupIndex(item);
Group group;
if (groupIndex >= 0) {
group = groups.get(groupIndex);
}
else {
if (otherItems == null) {
//reuse existing Other group if possible
if (groups.size() > groupBy.getGroups().length) {
otherItems = groups.get(groups.size() - 1);
}
else {
otherItems = new Group(Localizer.getInstance().getMessage("lblOther"));
otherItems.isCollapsed = btnExpandCollapseAll.isAllCollapsed;
groups.add(otherItems);
}
}
group = otherItems;
}
for (int i = 0; i < qty; i++) {
group.add(new ItemInfo(item, group));
}
}
if (otherItems == null && groups.size() > groupBy.getGroups().length) {
groups.remove(groups.size() - 1); //remove Other group if empty
btnExpandCollapseAll.updateIsAllCollapsed();
}
updateLayout(true);
}
@Override
protected void layoutOptionsPanel(float width, float height) {
float padding = ItemFilter.PADDING;
float x = 0;
float h = FTextField.getDefaultHeight(ItemFilter.DEFAULT_FONT);
float y = padding;
btnExpandCollapseAll.setBounds(x, y, h, h);
x += h + padding;
float pileByWidth = itemManager.getPileByWidth();
float groupByWidth = width - x - padding - pileByWidth;
cbGroupByOptions.setBounds(x, y, groupByWidth, h);
x += groupByWidth + padding;
cbPileByOptions.setBounds(x, y, pileByWidth, h);
}
private void updateLayout(boolean forRefresh) {
if (updatingLayout) { return; } //prevent infinite loop
updatingLayout = true;
focalItem = null; //clear cached focalItem when layout changes
float x, groupY, pileY, pileHeight, maxPileHeight;
float y = PADDING;
float groupX = PADDING;
float itemAreaWidth = getScroller().getWidth();
float groupWidth = itemAreaWidth - 2 * groupX;
float gap = (MAX_COLUMN_COUNT - columnCount) / 2 + Utils.scale(2); //more items per row == less gap between them
float itemWidth = (groupWidth + gap) / columnCount - gap;
if (pileBy != null) {
//if showing piles, make smaller so part of the next card is visible so it's obvious if scrolling is needed
itemWidth *= (1 - 0.2f / columnCount);
}
float itemHeight = itemWidth * FCardPanel.ASPECT_RATIO;
float dx = itemWidth + gap;
float dy = pileBy == null ? itemHeight + gap : itemHeight * PILE_SPACING_Y;
for (int i = 0; i < groups.size(); i++) {
Group group = groups.get(i);
if (forRefresh && pileBy != null) { //refresh piles if needed
//use TreeMap to build pile set so iterating below sorts on key
ColumnDef groupPileBy = groupBy == null ? pileBy : groupBy.getGroupPileBy(i, pileBy);
Map<Comparable<?>, Pile> piles = new TreeMap<>();
for (ItemInfo itemInfo : group.items) {
Comparable<?> key = groupPileBy.fnSort.apply(itemInfo);
if (!piles.containsKey(key)) {
piles.put(key, new Pile());
}
piles.get(key).items.add(itemInfo);
}
group.piles.clear();
group.piles.addAll(piles.values());
}
groupY = y;
if (group.items.isEmpty()) {
group.setBounds(groupX, groupY, groupWidth, 0);
group.scrollWidth = groupWidth;
continue;
}
if (groupBy != null) {
y += GROUP_HEADER_HEIGHT + PADDING; //leave room for group header
if (group.isCollapsed) {
group.setBounds(groupX, groupY, groupWidth, GROUP_HEADER_HEIGHT);
group.scrollWidth = groupWidth;
continue;
}
}
if (pileBy == null) {
//if not piling by anything, wrap items using a pile for each row
group.piles.clear();
Pile pile = new Pile();
x = 0;
for (ItemInfo itemInfo : group.items) {
itemInfo.pos = CardStackPosition.Top;
if (pile.items.size() == columnCount) {
pile = new Pile();
x = 0;
y += dy;
}
itemInfo.setBounds(x, y, itemWidth, itemHeight);
if (pile.items.size() == 0) {
pile.setBounds(0, y, groupWidth, itemHeight);
group.piles.add(pile);
}
pile.items.add(itemInfo);
x += dx;
}
y += itemHeight;
group.scrollWidth = groupWidth;
}
else {
x = 0;
pileY = y;
maxPileHeight = 0;
for (int j = 0; j < group.piles.size(); j++) {
Pile pile = group.piles.get(j);
y = pileY;
for (ItemInfo itemInfo : pile.items) {
itemInfo.pos = CardStackPosition.BehindVert;
itemInfo.setBounds(x, y, itemWidth, itemHeight);
y += dy;
}
pile.items.get(pile.items.size() - 1).pos = CardStackPosition.Top;
pileHeight = y + itemHeight - dy - pileY;
if (pileHeight > maxPileHeight) {
maxPileHeight = pileHeight;
}
pile.setBounds(x, pileY, itemWidth, pileHeight);
x += dx;
}
y = pileY + maxPileHeight; //update y for setting group height below
group.scrollWidth = Math.max(x - gap, groupWidth);
}
group.setBounds(groupX, groupY, groupWidth, y - groupY);
y += PADDING;
}
scrollHeight = y;
if (forRefresh) { //refresh ordered items if needed
int index = 0;
orderedItems.clear();
for (Group group : groups) {
if (group.isCollapsed || group.items.isEmpty()) { continue; }
for (Pile pile : group.piles) {
for (ItemInfo itemInfo : pile.items) {
itemInfo.index = index++;
orderedItems.add(itemInfo);
}
}
}
}
getScroller().revalidate();
updatingLayout = false;
}
@Override
protected float getScrollHeight() {
return scrollHeight;
}
@Override
protected boolean tap(float x, float y, int count) {
ItemInfo item = getItemAtPoint(x, y);
if (count == 1) {
selectItem(item);
itemManager.showMenu(false);
}
else if (count == 2) {
if (item != null && item.selected) {
if (!(item.getKey() instanceof DeckProxy))
itemManager.activateSelectedItems();
}
}
return true;
}
@Override
protected boolean zoom(float x, float y, float amount) {
totalZoomAmount += amount;
float columnZoomAmount = 2 * Utils.AVG_FINGER_WIDTH;
while (totalZoomAmount >= columnZoomAmount) {
setColumnCount(getColumnCount() - 1);
totalZoomAmount -= columnZoomAmount;
}
while (totalZoomAmount <= -columnZoomAmount) {
setColumnCount(getColumnCount() + 1);
totalZoomAmount += columnZoomAmount;
}
return true;
}
private ItemInfo getItemAtPoint(float x, float y) {
//check selected items first since they appear on top
for (int i = selectedIndices.size() - 1; i >= 0; i--) {
int currentIndex=selectedIndices.get(i);
if(currentIndex<0||orderedItems.size()<=currentIndex)
continue;
ItemInfo item = orderedItems.get(currentIndex);
float relX = x + item.group.getScrollLeft() - item.group.getLeft();
float relY = y + getScrollValue();
if (item.contains(relX, relY)) {
return item;
}
}
for (int i = groups.size() - 1; i >= 0; i--) {
Group group = groups.get(i);
if (!group.isCollapsed) {
for (int j = group.piles.size() - 1; j >= 0; j--) {
float relX = x + group.getScrollLeft() - group.getLeft();
float relY = y + getScrollValue();
Pile pile = group.piles.get(j);
if (pile.contains(relX, relY)) {
for (int k = pile.items.size() - 1; k >= 0; k--) {
ItemInfo item = pile.items.get(k);
if (item.contains(relX, relY)) {
return item;
}
}
}
}
}
}
return null;
}
@Override
public T getItemAtIndex(int index) {
if (index >= 0 && index < getCount()) {
return orderedItems.get(index).item;
}
return null;
}
@Override
public int getIndexOfItem(T item) {
for (Group group : groups) {
for (ItemInfo itemInfo : group.items) {
if (itemInfo.item == item) {
//if group containing item is collapsed, expand it so the item can be selected and has a valid index
if (group.isCollapsed) {
group.isCollapsed = false;
btnExpandCollapseAll.updateIsAllCollapsed();
clearSelection(); //must clear selection since indices and visible items will be changing
updateLayout(false);
}
return itemInfo.index;
}
}
}
return -1;
}
@Override
public int getSelectedIndex() {
return selectedIndices.isEmpty() ? -1 : selectedIndices.get(0);
}
@Override
public Iterable<Integer> getSelectedIndices() {
return selectedIndices;
}
@Override
public int getCount() {
return orderedItems.size();
}
@Override
public int getSelectionCount() {
return selectedIndices.size();
}
@Override
public int getIndexAtPoint(float x, float y) {
ItemInfo item = getItemAtPoint(x, y);
if (item != null) {
return item.index;
}
return -1;
}
@Override
public FImage getIcon() {
if (itemManager.getGenericType().equals(DeckProxy.class)) {
return FSkinImage.PACK;
}
return FSkinImage.CARD_IMAGE;
}
@Override
public String getCaption() {
return Localizer.getInstance().getMessage("lblImageView");
}
@Override
public void selectAll() {
clearSelection();
for (Integer i = 0; i < getCount(); i++) {
selectedIndices.add(i);
}
updateSelection();
onSelectionChange();
}
@Override
protected void onSetSelectedIndex(int index) {
clearSelection();
selectedIndices.add(index);
updateSelection();
}
@Override
protected void onSetSelectedIndices(Iterable<Integer> indices) {
clearSelection();
for (Integer index : indices) {
selectedIndices.add(index);
}
updateSelection();
}
private void clearSelection() {
int count = getCount();
for (Integer i : selectedIndices) {
if (i < count) {
orderedItems.get(i).selected = false;
}
}
selectedIndices.clear();
}
private void updateSelection() {
for (Integer i : selectedIndices) {
orderedItems.get(i).selected = true;
}
}
private boolean selectItem(ItemInfo item) {
if (item == null) {
if (!KeyInputAdapter.isCtrlKeyDown() && !KeyInputAdapter.isShiftKeyDown()) {
if (minSelections == 0) {
clearSelection();
onSelectionChange();
}
}
return false;
}
if (item.selected) { //unselect item if already selected
if (selectedIndices.size() > minSelections) {
item.selected = false;
selectedIndices.remove((Object)item.index);
onSelectionChange();
item.group.scrollIntoView(item);
}
return true;
}
if (maxSelections <= 1 || (!KeyInputAdapter.isCtrlKeyDown() && !KeyInputAdapter.isShiftKeyDown())) {
clearSelection();
}
if (selectedIndices.size() < maxSelections) {
selectedIndices.add(0, item.index);
item.selected = true;
onSelectionChange();
item.group.scrollIntoView(item);
getScroller().scrollIntoView(item);
}
return true;
}
@Override
public void scrollSelectionIntoView() {
if (selectedIndices.isEmpty()) { return; }
int index=selectedIndices.get(0);
if(index<0||orderedItems.size()<=index) { return ; }
ItemInfo itemInfo = orderedItems.get(index);
getScroller().scrollIntoView(itemInfo);
}
@Override
public Rectangle getSelectionBounds() {
if (selectedIndices.isEmpty()) { return new Rectangle(); }
int index=selectedIndices.get(0);
if(index<0||orderedItems.size()<=index) { return new Rectangle(); }
ItemInfo itemInfo = orderedItems.get(index);
Vector2 relPos = itemInfo.group.getChildRelativePosition(itemInfo);
return new Rectangle(itemInfo.group.screenPos.x + relPos.x - SEL_BORDER_SIZE + itemInfo.group.getLeft(),
itemInfo.group.screenPos.y + relPos.y - SEL_BORDER_SIZE,
itemInfo.getWidth() + 2 * SEL_BORDER_SIZE, itemInfo.getHeight() + 2 * SEL_BORDER_SIZE);
}
private class Group extends FScrollPane {
private final List<ItemInfo> items = new ArrayList<>();
private final List<Pile> piles = new ArrayList<>();
private final String name;
private boolean isCollapsed;
private float scrollWidth;
public Group(String name0) {
name = name0;
}
public void add(ItemInfo item) {
items.add(item);
}
@Override
public String toString() {
return name;
}
@Override
public void draw(Graphics g) {
if (items.isEmpty()) { return; }
if (groupBy != null) {
//draw group name and horizontal line
float x = GROUP_HEADER_GLYPH_WIDTH + PADDING + 1;
float y = 0;
String caption = name + " (" + items.size() + ")";
g.drawText(caption, GROUP_HEADER_FONT, GROUP_HEADER_FORE_COLOR, x, y, getWidth(), GROUP_HEADER_HEIGHT, false, Align.left, true);
x += GROUP_HEADER_FONT.getBounds(caption).width + PADDING;
y += GROUP_HEADER_HEIGHT / 2;
g.drawLine(GROUP_HEADER_LINE_THICKNESS, GROUP_HEADER_LINE_COLOR, x, y, getWidth(), y);
//draw expand/collapse glyph
float offset = GROUP_HEADER_GLYPH_WIDTH / 2 + 1;
x = offset;
if (isCollapsed) {
y += GROUP_HEADER_LINE_THICKNESS;
g.fillTriangle(GROUP_HEADER_LINE_COLOR,
x, y - offset,
x + offset, y,
x, y + offset);
}
else {
g.fillTriangle(GROUP_HEADER_LINE_COLOR,
x - offset + 2, y + offset - 1,
x + offset, y + offset - 1,
x + offset, y - offset + 1);
}
if (isCollapsed) { return; }
float visibleLeft = getScrollLeft();
float visibleRight = visibleLeft + getWidth();
for (Pile pile : piles) {
if (pile.getRight() < visibleLeft) {
continue;
}
if (pile.getLeft() >= visibleRight) {
break;
}
pile.draw(g);
}
return;
}
final float visibleTop = getScrollValue();
final float visibleBottom = visibleTop + getScroller().getHeight();
for (ItemInfo itemInfo : items) {
if (itemInfo.getBottom() < visibleTop) {
continue;
}
if (itemInfo.getTop() >= visibleBottom) {
break;
}
itemInfo.draw(g);
}
}
@Override
protected ScrollBounds layoutAndGetScrollBounds(float visibleWidth, float visibleHeight) {
return new ScrollBounds(scrollWidth, visibleHeight);
}
@Override
public boolean tap(float x, float y, int count) {
ItemInfo item = getItemAtPoint(x + getLeft(), y + getTop());
if (item != null) {
if(item.getKey() instanceof DeckProxy) {
DeckProxy dp = (DeckProxy)item.getKey();
if (count >= 2 && !dp.isGeneratedDeck()) {
//double tap to add to favorites or remove....
if (DeckPreferences.getPrefs(dp).getStarCount() > 0)
DeckPreferences.getPrefs(dp).setStarCount(0);
else
DeckPreferences.getPrefs(dp).setStarCount(1);
updateLayout(false);
}
}
}
if (groupBy != null && !items.isEmpty() && y < GROUP_HEADER_HEIGHT) {
isCollapsed = !isCollapsed;
btnExpandCollapseAll.updateIsAllCollapsed();
clearSelection(); //must clear selection since indices and visible items will be changing
updateLayout(false);
return true;
}
return false;
}
@Override
public boolean longPress(float x, float y) {
ItemInfo item = getItemAtPoint(x + getLeft(), y + getTop());
if (item != null) {
if(item.getKey() instanceof CardThemedDeckGenerator || item.getKey() instanceof CommanderDeckGenerator
|| item.getKey() instanceof ArchetypeDeckGenerator || item.getKey() instanceof DeckProxy){
FDeckViewer.show(((DeckProxy)item.getKey()).getDeck());
return true;
}
CardZoom.show(orderedItems, orderedItems.indexOf(item), itemManager);
return true;
}
return false;
}
//provide special override for this function to account for special ItemInfo positioning logic
@Override
protected Vector2 getChildRelativePosition(FDisplayObject child, float offsetX, float offsetY) {
return new Vector2(child.getLeft() - getScrollLeft() + offsetX - getLeft(), child.getTop() - getScrollValue() + offsetY - getTop());
}
}
private class Pile extends FDisplayObject {
private final List<ItemInfo> items = new ArrayList<>();
@Override
public void draw(Graphics g) {
final float visibleTop = getScrollValue();
final float visibleBottom = visibleTop + getScroller().getHeight();
ItemInfo skippedItem = null;
for (ItemInfo itemInfo : items) {
if (itemInfo.getBottom() < visibleTop) {
continue;
}
if (itemInfo.getTop() >= visibleBottom) {
break;
}
if (itemInfo.selected) {
skippedItem = itemInfo;
}
else {
itemInfo.draw(g);
}
}
if (skippedItem != null) {
CardStackPosition backupPos = skippedItem.pos;
skippedItem.pos = CardStackPosition.Top; //ensure skipped item rendered as if it was on top
skippedItem.draw(g);
skippedItem.pos = backupPos;
}
}
}
private class ItemInfo extends FDisplayObject implements Entry<InventoryItem, Integer> {
private final T item;
private final Group group;
private int index;
private CardStackPosition pos;
private boolean selected;
private final float IMAGE_SIZE = CardRenderer.MANA_SYMBOL_SIZE;
private ItemInfo(T item0, Group group0) {
item = item0;
group = group0;
}
@Override
public String toString() {
return item.toString();
}
@Override
public InventoryItem getKey() {
return item;
}
@Override
public Integer getValue() {
return 1;
}
@Override
public Integer setValue(Integer value) {
return 1;
}
@Override
public void draw(Graphics g) {
final float x = getLeft() - group.getScrollLeft();
final float y = getTop() - group.getTop() - getScrollValue();
final float w = getWidth();
final float h = getHeight();
Texture dpImg = null;
boolean deckSelectMode = false;
if (item instanceof DeckProxy) {
dpImg = ImageCache.getImage(item);
deckSelectMode = true;
}
if (selected) {
if (!deckSelectMode) {
//if round border is enabled, the select highlight is also rounded..
if (Forge.enableUIMask.equals("Full")) {
//fillroundrect has rough/aliased corner
g.fillRoundRect(Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE, w + 2 * SEL_BORDER_SIZE, h + 2 * SEL_BORDER_SIZE, (h - w) / 10);
//drawroundrect has GL_SMOOTH to `smoothen/faux` the aliased corner
g.drawRoundRect(1f, Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE, w + 1.5f * SEL_BORDER_SIZE, h + 1.5f * SEL_BORDER_SIZE, (h - w) / 10);
}
else //default rectangle highlight
g.fillRect(Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE, w + 2 * SEL_BORDER_SIZE, h + 2 * SEL_BORDER_SIZE);
}
}
if (item instanceof PaperCard) {
CardRenderer.drawCard(g, (PaperCard) item, x, y, w, h, pos);
if (itemManager.getShowRanking() && FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_OVERLAY_DRAFT_RANKING)) {
double score = CardRanker.getRawScore((PaperCard) item);
int draftRank = score <= 0 ? 0 : score > 99 ? 99 : (int) Math.round(CardRanker.getRawScore((PaperCard) item));
float rankSize = w/2;
float y2 = y+(rankSize-(rankSize*0.1f));
float x2 = x+rankSize/2;
if (draftRank >= 90) {
g.drawImage(FSkinImage.DRAFTRANK_S, x2, y2+1, rankSize, rankSize);
} else if (draftRank >= 80 && draftRank <= 89 ) {
g.drawImage(FSkinImage.DRAFTRANK_A, x2, y2+1, rankSize, rankSize);
} else if (draftRank >= 60 && draftRank <= 79 ) {
g.drawImage(FSkinImage.DRAFTRANK_B, x2, y2+1, rankSize, rankSize);
} else if (draftRank >= 25 && draftRank <= 59 ) {
g.drawImage(FSkinImage.DRAFTRANK_C, x2, y2+1, rankSize, rankSize);
} else {
g.drawImage(FSkinImage.DRAFTRANK_D, x2, y2+1, rankSize, rankSize);
}
String value = String.valueOf(draftRank);
g.drawText(value, FSkinFont.forHeight(rankSize/4), Color.WHITE, x, y, w, h, true, Align.center, true);
}
} else if (item instanceof ConquestCommander) {
CardRenderer.drawCard(g, ((ConquestCommander) item).getCard(), x, y, w, h, pos);
} else if (deckSelectMode) {
DeckProxy dp = ((DeckProxy) item);
ColorSet deckColor = dp.getColor();
float scale = 0.75f;
if (dpImg != null) {//generated decks have missing info...
if (Forge.enableUIMask.equals("Off")){
if (selected)
g.fillRect(Color.GREEN, x - SEL_BORDER_SIZE, y - SEL_BORDER_SIZE, w + 2 * SEL_BORDER_SIZE, h + 2 * SEL_BORDER_SIZE);
g.drawImage(dpImg, x, y, w, h);
} else {
//commander bg
g.drawImage(FSkin.getDeckbox().get(0), FSkin.getDeckbox().get(0), x, y, w, h, Color.GREEN, selected);
PaperCard paperCard = null;
String imageKey = item.getImageKey(false);
if (imageKey != null) {
if (imageKey.startsWith(ImageKeys.CARD_PREFIX))
paperCard = ImageUtil.getPaperCardFromImageKey(imageKey);
}
if (paperCard != null && Forge.enableUIMask.equals("Art")) {
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(paperCard), false,
x + (w - w * scale) / 2, y + (h - h * scale) / 1.5f, w * scale, h * scale, CardStackPosition.Top, true, false, false, true);
} else {
TextureRegion tr = ImageCache.croppedBorderImage(dpImg);
g.drawImage(tr, x + (w - w * scale) / 2, y + (h - h * scale) / 1.5f, w * scale, h * scale);
}
}
//fake labelname shadow
g.drawText(item.getName(), GROUP_HEADER_FONT, Color.BLACK, (x + PADDING)-1f, (y + PADDING*2)+1f, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false);
//labelname
g.drawText(item.getName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING*2, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false);
} else {
if (!dp.isGeneratedDeck()) {
if (dp.getDeck().isEmpty()) {
g.drawImage(FSkin.getDeckbox().get(2), FSkin.getDeckbox().get(2), x, y-(h*0.25f), w, h, Color.RED, selected);
} else {
//If deck has Commander, use it as cardArt reference
PaperCard paperCard = dp.getDeck().getCommanders().isEmpty() ? dp.getHighestCMCCard() : dp.getDeck().getCommanders().get(0);
FImageComplex cardArt = CardRenderer.getCardArt(paperCard);
//draw the deckbox
if (cardArt == null){
//draw generic box if null or still loading
g.drawImage(FSkin.getDeckbox().get(2), FSkin.getDeckbox().get(2), x, y-(h*0.25f), w, h, Color.GREEN, selected);
} else {
g.drawDeckBox(cardArt, scale, FSkin.getDeckbox().get(1), FSkin.getDeckbox().get(2), x, y, w, h, Color.GREEN, selected);
}
}
} else {
//generic box
g.drawImage(FSkin.getDeckbox().get(2), FSkin.getDeckbox().get(2), x, y-(h*0.25f), w, h, Color.GREEN, selected);
}
if (deckColor != null) {
//deck color identity
float symbolSize = IMAGE_SIZE;
if (Forge.isLandscapeMode()) {
if (columnCount == 4)
symbolSize = IMAGE_SIZE * 1.5f;
else if (columnCount == 3)
symbolSize = IMAGE_SIZE * 2f;
else if (columnCount == 2)
symbolSize = IMAGE_SIZE * 3f;
else if (columnCount == 1)
symbolSize = IMAGE_SIZE * 4f;
} else {
if (columnCount > 2)
symbolSize = IMAGE_SIZE * (0.5f);
}
//vertical mana icons
CardFaceSymbols.drawColorSet(g, deckColor, x +(w-symbolSize), y+(h/8), symbolSize, true);
if(!dp.isGeneratedDeck()) {
if (dp.getDeck().isEmpty()) {
g.drawImage(Forge.hdbuttons ? FSkinImage.HDYIELD : FSkinImage.WARNING, x, y, symbolSize, symbolSize);
} else {
if (Forge.hdbuttons)
g.drawImage(DeckPreferences.getPrefs(dp).getStarCount() > 0 ? FSkinImage.HDSTAR_FILLED : FSkinImage.HDSTAR_OUTLINE, x, y, symbolSize, symbolSize);
else
g.drawImage(DeckPreferences.getPrefs(dp).getStarCount() > 0 ? FSkinImage.STAR_FILLED : FSkinImage.STAR_OUTLINE, x, y, symbolSize, symbolSize);
//AI Icon
g.drawImage(dp.getAI().inMainDeck == 0 ? FSkinImage.AI_ACTIVE : FSkinImage.AI_INACTIVE, x, y+symbolSize, symbolSize, symbolSize);
}
}
}
String deckname = TextUtil.fastReplace(item.getName(),"] #", "]\n#");
//deckname fakeshadow
g.drawText(deckname, GROUP_HEADER_FONT, Color.BLACK, (x + PADDING)-1f, (y + (h/10) + PADDING)+1f, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, true);
//deck name
g.drawText(deckname, GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + (h/10) + PADDING, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, true);
}
} else {
Texture img = ImageCache.getImage(item);
if (img != null) {
g.drawImage(img, x, y, w, h);
} else {
g.fillRect(Color.BLACK, x, y, w, h);
g.drawText(item.getName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false);
}
}
}
}
}