mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
Support sorting cards into piles
This commit is contained in:
@@ -16,6 +16,7 @@ import java.awt.image.BufferedImage;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
@@ -39,10 +40,12 @@ import forge.view.arcane.CardPanel;
|
|||||||
public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||||
private static final int PADDING = 5;
|
private static final int PADDING = 5;
|
||||||
private static final float GAP_SCALE_FACTOR = 0.04f;
|
private static final float GAP_SCALE_FACTOR = 0.04f;
|
||||||
|
private static final float PILE_SPACING_Y = 0.1f;
|
||||||
private static final int GROUP_HEADER_HEIGHT = 19;
|
private static final int GROUP_HEADER_HEIGHT = 19;
|
||||||
|
|
||||||
private final CardViewDisplay display;
|
private final CardViewDisplay display;
|
||||||
private List<Integer> selectedIndices = new ArrayList<Integer>();
|
private List<Integer> selectedIndices = new ArrayList<Integer>();
|
||||||
|
private int itemsPerRow;
|
||||||
private int imageScaleFactor = 3;
|
private int imageScaleFactor = 3;
|
||||||
private boolean allowMultipleSelections;
|
private boolean allowMultipleSelections;
|
||||||
private ColumnDef pileBy = null;
|
private ColumnDef pileBy = null;
|
||||||
@@ -69,7 +72,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
if (!group.items.isEmpty() && point.y < group.getTop() + GROUP_HEADER_HEIGHT) {
|
if (!group.items.isEmpty() && point.y < group.getTop() + GROUP_HEADER_HEIGHT) {
|
||||||
group.isCollapsed = !group.isCollapsed;
|
group.isCollapsed = !group.isCollapsed;
|
||||||
clearSelection(); //must clear selection since indices and visible items will be changing
|
clearSelection(); //must clear selection since indices and visible items will be changing
|
||||||
updateLayout();
|
updateLayout(false);
|
||||||
collapsedChanged = true;
|
collapsedChanged = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -194,7 +197,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResize() {
|
protected void onResize() {
|
||||||
updateLayout(); //need to update layout to adjust wrapping of items
|
updateLayout(false); //need to update layout to adjust wrapping of items
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -235,34 +238,48 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
groups.remove(groups.size() - 1); //remove Other group if empty
|
groups.remove(groups.size() - 1); //remove Other group if empty
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLayout();
|
updateLayout(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLayout() {
|
private void updateLayout(boolean forRefresh) {
|
||||||
orderedItems.clear();
|
int x, groupY, pileY, pileHeight, maxPileHeight;
|
||||||
|
|
||||||
int x, groupY;
|
|
||||||
int y = PADDING;
|
int y = PADDING;
|
||||||
int groupX = PADDING;
|
int groupX = groupBy == null ? 0 : PADDING;
|
||||||
int itemAreaWidth = getVisibleSize().width;
|
int itemAreaWidth = getVisibleSize().width;
|
||||||
int groupWidth = itemAreaWidth - 2 * groupX;
|
int groupWidth = itemAreaWidth - 2 * groupX;
|
||||||
int pileX = groupBy == null ? groupX : 2 * groupX + 1;
|
int pileX = groupBy == null ? PADDING : 2 * PADDING + 1;
|
||||||
int pileWidth = itemAreaWidth - 2 * pileX;
|
int pileWidth = itemAreaWidth - 2 * pileX;
|
||||||
|
|
||||||
int itemIndex = 0;
|
|
||||||
int itemWidth = 50 * imageScaleFactor;
|
int itemWidth = 50 * imageScaleFactor;
|
||||||
int gap = Math.round(itemWidth * GAP_SCALE_FACTOR);
|
int gap = Math.round(itemWidth * GAP_SCALE_FACTOR);
|
||||||
int dx = itemWidth + gap;
|
int dx = itemWidth + gap;
|
||||||
int itemsPerRow = (pileWidth + gap) / dx;
|
itemsPerRow = (pileWidth + gap) / dx;
|
||||||
if (itemsPerRow == 0) {
|
if (itemsPerRow == 0) {
|
||||||
itemsPerRow = 1;
|
itemsPerRow = 1;
|
||||||
itemWidth = pileWidth;
|
itemWidth = pileWidth;
|
||||||
}
|
}
|
||||||
int itemHeight = Math.round(itemWidth * CardPanel.ASPECT_RATIO);
|
int itemHeight = Math.round(itemWidth * CardPanel.ASPECT_RATIO);
|
||||||
int dy = itemHeight + gap;
|
int dy = pileBy == null ? itemHeight + gap : Math.round(itemHeight * PILE_SPACING_Y);
|
||||||
|
|
||||||
for (Group group : groups) {
|
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);
|
||||||
|
TreeMap<Comparable<?>, Pile> piles = new TreeMap<Comparable<?>, Pile>();
|
||||||
|
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.clear();
|
||||||
|
for (Pile pile : piles.values()) {
|
||||||
|
group.piles.add(pile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
groupY = y;
|
groupY = y;
|
||||||
if (groupBy != null) {
|
if (groupBy != null) {
|
||||||
@@ -277,13 +294,13 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pile pile = new Pile(); //use a pile for each row
|
if (pileBy == null) {
|
||||||
|
//if not piling by anything, wrap items using a pile for each row
|
||||||
|
group.piles.clear();
|
||||||
|
Pile pile = new Pile();
|
||||||
x = pileX;
|
x = pileX;
|
||||||
|
|
||||||
for (ItemInfo itemInfo : group.items) {
|
for (ItemInfo itemInfo : group.items) {
|
||||||
itemInfo.index = itemIndex++;
|
|
||||||
orderedItems.add(itemInfo);
|
|
||||||
|
|
||||||
if (pile.items.size() == itemsPerRow) {
|
if (pile.items.size() == itemsPerRow) {
|
||||||
pile = new Pile();
|
pile = new Pile();
|
||||||
x = pileX;
|
x = pileX;
|
||||||
@@ -299,8 +316,28 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
pile.items.add(itemInfo);
|
pile.items.add(itemInfo);
|
||||||
x += dx;
|
x += dx;
|
||||||
}
|
}
|
||||||
|
|
||||||
y += itemHeight;
|
y += itemHeight;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
x = pileX - group.scrollValue;
|
||||||
|
pileY = y;
|
||||||
|
maxPileHeight = 0;
|
||||||
|
for (Pile pile : group.piles) {
|
||||||
|
y = pileY;
|
||||||
|
for (ItemInfo itemInfo : pile.items) {
|
||||||
|
itemInfo.setBounds(x, y, itemWidth, itemHeight);
|
||||||
|
y += dy;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
if (groupBy != null) {
|
if (groupBy != null) {
|
||||||
y += PADDING + 1; //leave room for group footer
|
y += PADDING + 1; //leave room for group footer
|
||||||
}
|
}
|
||||||
@@ -308,6 +345,21 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
y += PADDING;
|
y += PADDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
display.setPreferredSize(new Dimension(itemAreaWidth, y));
|
display.setPreferredSize(new Dimension(itemAreaWidth, y));
|
||||||
display.revalidate();
|
display.revalidate();
|
||||||
display.repaintSelf();
|
display.repaintSelf();
|
||||||
@@ -339,12 +391,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
Insets insets = getScroller().getInsets();
|
Insets insets = getScroller().getInsets();
|
||||||
size = new Dimension(size.width - insets.left - insets.right,
|
size = new Dimension(size.width - insets.left - insets.right,
|
||||||
size.height - insets.top - insets.bottom);
|
size.height - insets.top - insets.bottom);
|
||||||
if (scroller.getVerticalScrollBarPolicy() != ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER) {
|
|
||||||
size.width -= scroller.getVerticalScrollBar().getPreferredSize().width;
|
size.width -= scroller.getVerticalScrollBar().getPreferredSize().width;
|
||||||
}
|
|
||||||
if (scroller.getHorizontalScrollBarPolicy() != ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER) {
|
|
||||||
size.height -= scroller.getHorizontalScrollBar().getPreferredSize().height;
|
|
||||||
}
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,7 +449,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
if (group.isCollapsed) {
|
if (group.isCollapsed) {
|
||||||
group.isCollapsed = false;
|
group.isCollapsed = false;
|
||||||
clearSelection(); //must clear selection since indices and visible items will be changing
|
clearSelection(); //must clear selection since indices and visible items will be changing
|
||||||
updateLayout();
|
updateLayout(false);
|
||||||
}
|
}
|
||||||
return itemInfo.index;
|
return itemInfo.index;
|
||||||
}
|
}
|
||||||
@@ -546,6 +593,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
private final List<Pile> piles = new ArrayList<Pile>();
|
private final List<Pile> piles = new ArrayList<Pile>();
|
||||||
private final String name;
|
private final String name;
|
||||||
private boolean isCollapsed;
|
private boolean isCollapsed;
|
||||||
|
private int scrollValue = 0;
|
||||||
|
|
||||||
public Group(String name0) {
|
public Group(String name0) {
|
||||||
name = name0;
|
name = name0;
|
||||||
@@ -563,7 +611,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
private class Pile extends DisplayArea {
|
private class Pile extends DisplayArea {
|
||||||
private final List<ItemInfo> items = new ArrayList<ItemInfo>();
|
private final List<ItemInfo> items = new ArrayList<ItemInfo>();
|
||||||
}
|
}
|
||||||
private class ItemInfo extends DisplayArea {
|
private class ItemInfo extends DisplayArea implements Entry<InventoryItem, Integer> {
|
||||||
private final T item;
|
private final T item;
|
||||||
private int index;
|
private int index;
|
||||||
private boolean selected;
|
private boolean selected;
|
||||||
@@ -576,6 +624,21 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return item.toString();
|
return item.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InventoryItem getKey() {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer setValue(Integer value) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
@@ -600,13 +663,16 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
final Dimension visibleSize = getVisibleSize();
|
final Dimension visibleSize = getVisibleSize();
|
||||||
final int visibleTop = getScroller().getVerticalScrollBar().getValue();
|
final int visibleTop = getScroller().getVerticalScrollBar().getValue();
|
||||||
final int visibleBottom = visibleTop + visibleSize.height;
|
final int visibleBottom = visibleTop + visibleSize.height;
|
||||||
final int visibleLeft = getScroller().getHorizontalScrollBar().getValue();
|
Rectangle bounds = groups.get(0).getBounds();
|
||||||
final int visibleRight = visibleLeft + visibleSize.width;
|
final int visibleLeft = bounds.x + 1; //use first group left and width as defined visible horizontal bounds
|
||||||
|
final int visibleRight = bounds.x + bounds.width - 2;
|
||||||
|
|
||||||
FSkin.setGraphicsFont(g2d, ItemListView.ROW_FONT);
|
FSkin.setGraphicsFont(g2d, ItemListView.ROW_FONT);
|
||||||
FontMetrics fm = g2d.getFontMetrics();
|
FontMetrics fm = g2d.getFontMetrics();
|
||||||
int fontOffsetY = (GROUP_HEADER_HEIGHT - fm.getHeight()) / 2 + fm.getAscent();
|
int fontOffsetY = (GROUP_HEADER_HEIGHT - fm.getHeight()) / 2 + fm.getAscent();
|
||||||
|
|
||||||
|
Rectangle baseClip = g.getClipBounds();
|
||||||
|
|
||||||
for (Group group : groups) {
|
for (Group group : groups) {
|
||||||
if (group.getBottom() < visibleTop) {
|
if (group.getBottom() < visibleTop) {
|
||||||
continue;
|
continue;
|
||||||
@@ -614,8 +680,9 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
if (group.getTop() >= visibleBottom) {
|
if (group.getTop() >= visibleBottom) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
bounds = group.getBounds();
|
||||||
if (groupBy != null) {
|
if (groupBy != null) {
|
||||||
Rectangle bounds = group.getBounds();
|
g2d.setClip(baseClip.intersection(bounds)); //set clip region for header
|
||||||
FSkin.setGraphicsColor(g2d, ItemListView.HEADER_BACK_COLOR);
|
FSkin.setGraphicsColor(g2d, ItemListView.HEADER_BACK_COLOR);
|
||||||
g2d.fillRect(bounds.x, bounds.y, bounds.width, GROUP_HEADER_HEIGHT - 1);
|
g2d.fillRect(bounds.x, bounds.y, bounds.width, GROUP_HEADER_HEIGHT - 1);
|
||||||
FSkin.setGraphicsColor(g2d, ItemListView.FORE_COLOR);
|
FSkin.setGraphicsColor(g2d, ItemListView.FORE_COLOR);
|
||||||
@@ -655,6 +722,13 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
else if (group.items.isEmpty()) {
|
else if (group.items.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//set clip region for piles if needed to prevent appearing them left or right of group borders
|
||||||
|
if (pileBy != null) {
|
||||||
|
g2d.setClip(baseClip.intersection(new Rectangle(bounds.x + 1, baseClip.y, bounds.width - 2, baseClip.height)));
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemInfo skippedItem = null;
|
||||||
for (Pile pile : group.piles) {
|
for (Pile pile : group.piles) {
|
||||||
if (pile.getBottom() < visibleTop || pile.getRight() < visibleLeft) {
|
if (pile.getBottom() < visibleTop || pile.getRight() < visibleLeft) {
|
||||||
continue;
|
continue;
|
||||||
@@ -663,23 +737,28 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
for (ItemInfo itemInfo : pile.items) {
|
for (ItemInfo itemInfo : pile.items) {
|
||||||
if (itemInfo.getBottom() < visibleTop || itemInfo.getRight() < visibleLeft) {
|
if (itemInfo.getBottom() < visibleTop) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (itemInfo.getTop() >= visibleBottom || itemInfo.getLeft() >= visibleRight) {
|
if (itemInfo.getTop() >= visibleBottom) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (itemInfo != hoveredItem) { //save hovered item for last
|
if (itemInfo != hoveredItem) { //save hovered item for last
|
||||||
drawItemImage(g2d, itemInfo);
|
drawItemImage(g2d, itemInfo);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
skippedItem = itemInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hoveredItem != null) { //draw hovered item on top
|
if (skippedItem != null) { //draw hovered item on top
|
||||||
drawItemImage(g2d, hoveredItem);
|
drawItemImage(g2d, skippedItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g2d.setClip(baseClip);
|
||||||
|
}
|
||||||
|
|
||||||
private void drawItemImage(Graphics2D g, ItemInfo itemInfo) {
|
private void drawItemImage(Graphics2D g, ItemInfo itemInfo) {
|
||||||
Rectangle bounds = itemInfo.getBounds();
|
Rectangle bounds = itemInfo.getBounds();
|
||||||
final int itemWidth = bounds.width;
|
final int itemWidth = bounds.width;
|
||||||
|
|||||||
Reference in New Issue
Block a user