diff --git a/forge-gui/src/main/java/forge/gui/deckchooser/FDeckViewer.java b/forge-gui/src/main/java/forge/gui/deckchooser/FDeckViewer.java index b1a4734e7fd..c1a81337293 100644 --- a/forge-gui/src/main/java/forge/gui/deckchooser/FDeckViewer.java +++ b/forge-gui/src/main/java/forge/gui/deckchooser/FDeckViewer.java @@ -25,6 +25,7 @@ import forge.gui.toolbox.FButton; import forge.gui.toolbox.FOptionPane; import forge.gui.toolbox.itemmanager.CardManager; import forge.gui.toolbox.itemmanager.ItemManagerContainer; +import forge.gui.toolbox.itemmanager.views.GroupDef; import forge.gui.toolbox.itemmanager.views.ItemColumn; import forge.gui.toolbox.itemmanager.views.SColumnUtil; import forge.gui.toolbox.itemmanager.views.ColumnDef; @@ -132,7 +133,7 @@ public class FDeckViewer extends FDialog { ItemColumn qtyColumn = new ItemColumn(ColumnDef.QUANTITY); qtyColumn.setIndex(0); columns.put(ColumnDef.QUANTITY, qtyColumn); - this.cardManager.setup(columns); + this.cardManager.setup(columns, GroupDef.CREATURE_SPELL_LAND, ColumnDef.CMC); } private void changeSection() { diff --git a/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorCommander.java b/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorCommander.java index 3639b793403..3fc30793a1b 100644 --- a/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorCommander.java +++ b/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorCommander.java @@ -38,6 +38,7 @@ import forge.gui.framework.FScreen; import forge.gui.toolbox.itemmanager.CardManager; import forge.gui.toolbox.itemmanager.SItemManagerUtil; import forge.gui.toolbox.itemmanager.views.ColumnDef; +import forge.gui.toolbox.itemmanager.views.GroupDef; import forge.gui.toolbox.itemmanager.views.SColumnUtil; import forge.gui.toolbox.itemmanager.views.ItemColumn; import forge.item.PaperCard; @@ -164,7 +165,7 @@ public final class CEditorCommander extends ACEditorBase { lstCatalogCols.remove(ColumnDef.QUANTITY); this.getCatalogManager().setup(lstCatalogCols); - this.getDeckManager().setup(SColumnUtil.getDeckDefaultColumns()); + this.getDeckManager().setup(SColumnUtil.getDeckDefaultColumns(), GroupDef.CREATURE_SPELL_LAND, ColumnDef.CMC); SItemManagerUtil.resetUI(this); diff --git a/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java b/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java index 22130330da9..96aa097d8ff 100644 --- a/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java +++ b/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java @@ -35,6 +35,7 @@ import forge.gui.framework.FScreen; import forge.gui.toolbox.itemmanager.CardManager; import forge.gui.toolbox.itemmanager.SItemManagerUtil; import forge.gui.toolbox.itemmanager.views.ColumnDef; +import forge.gui.toolbox.itemmanager.views.GroupDef; import forge.gui.toolbox.itemmanager.views.SColumnUtil; import forge.gui.toolbox.itemmanager.views.ItemColumn; import forge.item.PaperCard; @@ -340,7 +341,7 @@ public final class CEditorConstructed extends ACEditorBase { lstCatalogCols.remove(ColumnDef.QUANTITY); this.getCatalogManager().setup(lstCatalogCols); - this.getDeckManager().setup(SColumnUtil.getDeckDefaultColumns()); + this.getDeckManager().setup(SColumnUtil.getDeckDefaultColumns(), GroupDef.CREATURE_SPELL_LAND, ColumnDef.CMC); SItemManagerUtil.resetUI(this); diff --git a/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorDraftingProcess.java b/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorDraftingProcess.java index cccc1caaea0..ccd4c5f5f51 100644 --- a/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorDraftingProcess.java +++ b/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorDraftingProcess.java @@ -33,6 +33,8 @@ import forge.gui.framework.FScreen; import forge.gui.home.sanctioned.CSubmenuDraft; import forge.gui.toolbox.FOptionPane; import forge.gui.toolbox.itemmanager.CardManager; +import forge.gui.toolbox.itemmanager.views.ColumnDef; +import forge.gui.toolbox.itemmanager.views.GroupDef; import forge.gui.toolbox.itemmanager.views.SColumnUtil; import forge.item.PaperCard; import forge.limited.BoosterDraft; @@ -261,7 +263,7 @@ public class CEditorDraftingProcess extends ACEditorBase { @Override public void update() { this.getCatalogManager().setup(SColumnUtil.getCatalogDefaultColumns()); - this.getDeckManager().setup(SColumnUtil.getDeckDefaultColumns()); + this.getDeckManager().setup(SColumnUtil.getDeckDefaultColumns(), GroupDef.CREATURE_SPELL_LAND, ColumnDef.CMC); ccAddLabel = this.getBtnAdd().getText(); this.getBtnAdd().setText("Choose Card"); diff --git a/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorLimited.java b/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorLimited.java index 2ca959205a8..f64c61efcdf 100644 --- a/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorLimited.java +++ b/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorLimited.java @@ -34,6 +34,8 @@ import forge.gui.home.sanctioned.CSubmenuDraft; import forge.gui.home.sanctioned.CSubmenuSealed; import forge.gui.toolbox.itemmanager.CardManager; import forge.gui.toolbox.itemmanager.SItemManagerUtil; +import forge.gui.toolbox.itemmanager.views.ColumnDef; +import forge.gui.toolbox.itemmanager.views.GroupDef; import forge.gui.toolbox.itemmanager.views.SColumnUtil; import forge.item.PaperCard; import forge.util.storage.IStorage; @@ -161,7 +163,7 @@ public final class CEditorLimited extends ACEditorBase { @Override public void update() { this.getCatalogManager().setup(SColumnUtil.getCatalogDefaultColumns()); - this.getDeckManager().setup(SColumnUtil.getDeckDefaultColumns()); + this.getDeckManager().setup(SColumnUtil.getDeckDefaultColumns(), GroupDef.CREATURE_SPELL_LAND, ColumnDef.CMC); SItemManagerUtil.resetUI(this); diff --git a/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorQuest.java b/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorQuest.java index a10ccff2ed2..18166f2493d 100644 --- a/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorQuest.java +++ b/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorQuest.java @@ -40,6 +40,7 @@ import forge.gui.home.quest.CSubmenuQuestDecks; import forge.gui.toolbox.itemmanager.CardManager; import forge.gui.toolbox.itemmanager.SItemManagerUtil; import forge.gui.toolbox.itemmanager.views.ColumnDef; +import forge.gui.toolbox.itemmanager.views.GroupDef; import forge.gui.toolbox.itemmanager.views.SColumnUtil; import forge.gui.toolbox.itemmanager.views.ItemColumn; import forge.item.PaperCard; @@ -255,7 +256,7 @@ public final class CEditorQuest extends ACEditorBase { columnsDeck.put(ColumnDef.DECKS, new ItemColumn(ColumnDef.DECKS, this.fnDeckCompare, this.fnDeckGet)); this.getCatalogManager().setup(columnsCatalog); - this.getDeckManager().setup(columnsDeck); + this.getDeckManager().setup(columnsDeck, GroupDef.CREATURE_SPELL_LAND, ColumnDef.CMC); SItemManagerUtil.resetUI(this); diff --git a/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorVariant.java b/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorVariant.java index 3dced4f5378..5e7e802bb69 100644 --- a/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorVariant.java +++ b/forge-gui/src/main/java/forge/gui/deckeditor/controllers/CEditorVariant.java @@ -35,6 +35,7 @@ import forge.gui.framework.FScreen; import forge.gui.toolbox.itemmanager.CardManager; import forge.gui.toolbox.itemmanager.SItemManagerUtil; import forge.gui.toolbox.itemmanager.views.ColumnDef; +import forge.gui.toolbox.itemmanager.views.GroupDef; import forge.gui.toolbox.itemmanager.views.SColumnUtil; import forge.gui.toolbox.itemmanager.views.ItemColumn; import forge.item.PaperCard; @@ -163,7 +164,7 @@ public final class CEditorVariant extends ACEditorBase { lstCatalogCols.remove(ColumnDef.QUANTITY); this.getCatalogManager().setup(lstCatalogCols); - this.getDeckManager().setup(SColumnUtil.getDeckDefaultColumns()); + this.getDeckManager().setup(SColumnUtil.getDeckDefaultColumns(), GroupDef.CREATURE_SPELL_LAND, ColumnDef.CMC); SItemManagerUtil.resetUI(this); diff --git a/forge-gui/src/main/java/forge/gui/toolbox/FSkin.java b/forge-gui/src/main/java/forge/gui/toolbox/FSkin.java index 365868164d3..edac80a5827 100644 --- a/forge-gui/src/main/java/forge/gui/toolbox/FSkin.java +++ b/forge-gui/src/main/java/forge/gui/toolbox/FSkin.java @@ -1240,6 +1240,10 @@ public enum FSkin { return SkinFont.get(Font.ITALIC, size); } + public static void setGraphicsFont(Graphics g, SkinFont skinFont) { + g.setFont(skinFont.font); + } + public static class SkinFont { private static Font baseFont; private static Map fonts = new HashMap(); diff --git a/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/ItemManager.java b/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/ItemManager.java index 02839aabef1..abf3752cafa 100644 --- a/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/ItemManager.java +++ b/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/ItemManager.java @@ -58,6 +58,7 @@ import forge.gui.toolbox.LayoutHelper; import forge.gui.toolbox.FSkin.Colors; import forge.gui.toolbox.itemmanager.filters.ItemFilter; import forge.gui.toolbox.itemmanager.views.ColumnDef; +import forge.gui.toolbox.itemmanager.views.GroupDef; import forge.gui.toolbox.itemmanager.views.ImageView; import forge.gui.toolbox.itemmanager.views.ItemColumn; import forge.gui.toolbox.itemmanager.views.ItemListView; @@ -120,6 +121,7 @@ public abstract class ItemManager extends JPanel { private final List> views = new ArrayList>(); private final ItemListView listView; + private final ImageView imageView; private ItemView currentView; private boolean initialized; protected boolean lockFiltering; @@ -135,12 +137,13 @@ public abstract class ItemManager extends JPanel { this.genericType = genericType0; this.wantUnique = wantUnique0; this.model = new ItemManagerModel(genericType0); + this.listView = new ItemListView(this, this.model); - this.listView.setAllowMultipleSelections(false); - this.currentView = this.listView; + this.imageView = new ImageView(this, this.model); this.views.add(this.listView); - this.views.add(new ImageView(this, this.model)); + this.views.add(this.imageView); + this.currentView = this.listView; } /** @@ -283,7 +286,12 @@ public abstract class ItemManager extends JPanel { } public void setup(final Map cols) { + this.setup(cols, null, null); + } + public void setup(final Map cols, GroupDef groupBy, ColumnDef pileBy) { this.listView.setup(cols); + this.imageView.setGroupBy(groupBy); + this.imageView.setPileBy(pileBy); } public void setViewIndex(int index) { @@ -291,15 +299,21 @@ public abstract class ItemManager extends JPanel { ItemView view = this.views.get(index); if (this.currentView == view) { return; } - final int selectedIndexBefore = this.currentView.getSelectedIndex(); - final Iterable selectedItemsBefore = this.currentView.getSelectedItems(); + final int backupIndexToSelect = this.currentView.getSelectedIndex(); + final Iterable itemsToSelect; //only retain selected items if not single selection of first item + if (backupIndexToSelect > 0 || this.getSelectionCount() > 1) { + itemsToSelect = this.currentView.getSelectedItems(); + } + else { + itemsToSelect = null; + } this.currentView.getButton().setSelected(false); this.remove(this.currentView.getScroller()); this.currentView = view; this.currentView.getButton().setSelected(true); - this.currentView.refresh(selectedItemsBefore, selectedIndexBefore); + this.currentView.refresh(itemsToSelect, backupIndexToSelect); this.add(currentView.getScroller()); this.revalidate(); diff --git a/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/views/ImageView.java b/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/views/ImageView.java index b77b9dc819b..fb6f44ee73b 100644 --- a/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/views/ImageView.java +++ b/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/views/ImageView.java @@ -2,6 +2,7 @@ package forge.gui.toolbox.itemmanager.views; import java.awt.Color; import java.awt.Dimension; +import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; @@ -32,11 +33,12 @@ import forge.gui.toolbox.FSkin.SkinImage; import forge.gui.toolbox.itemmanager.ItemManager; import forge.gui.toolbox.itemmanager.ItemManagerModel; import forge.item.InventoryItem; -import forge.view.arcane.CardArea; import forge.view.arcane.CardPanel; public class ImageView extends ItemView { + private static final int PADDING = 5; private static final float GAP_SCALE_FACTOR = 0.04f; + private static final int GROUP_HEADER_HEIGHT = 19; private final CardViewDisplay display; private List selectedIndices = new ArrayList(); @@ -44,19 +46,40 @@ public class ImageView extends ItemView { private boolean allowMultipleSelections; private ColumnDef pileBy = null; private GroupDef groupBy = null; + private Point hoverPoint; + private Point hoverScrollPos; + private ItemInfo hoveredItem; + private ArrayList orderedItems = new ArrayList(); + private ArrayList groups = new ArrayList(); public ImageView(ItemManager itemManager0, ItemManagerModel model0) { super(itemManager0, model0); - this.display = new CardViewDisplay(); - this.display.addMouseListener(new FMouseAdapter() { + display = new CardViewDisplay(); + display.addMouseListener(new FMouseAdapter() { @Override public void onLeftMouseDown(MouseEvent e) { - selectItem(e); + if (!selectItem(e)) { + //if didn't click on item, see if clicked on group header + if (groupBy != null) { + Point point = e.getPoint(); + for (Group group : groups) { + if (group.getBounds().contains(point)) { + if (point.y < group.getTop() + GROUP_HEADER_HEIGHT) { + group.isCollapsed = !group.isCollapsed; + updateLayout(); + } + break; + } + } + } + } } @Override public void onLeftDoubleClick(MouseEvent e) { - itemManager.activateSelectedItems(); + if (hoveredItem != null && hoveredItem.selected) { + itemManager.activateSelectedItems(); + } } @Override @@ -65,23 +88,23 @@ public class ImageView extends ItemView { itemManager.showContextMenu(e); } - private void selectItem(MouseEvent e) { + private boolean selectItem(MouseEvent e) { focus(); - ItemInfo item = display.getItemAtPoint(e.getPoint()); - if (item == null) { return; } + ItemInfo item = getItemAtPoint(e.getPoint()); + if (item == null) { return false; } if (item.selected) { //toggle selection off item if Control down and left mouse down, otherwise do nothing if (e.getButton() != 1) { - return; + return true; } if (e.isControlDown() && allowMultipleSelections) { item.selected = false; selectedIndices.remove(item.index); onSelectionChange(); item.scrollIntoView(); - return; + return true; } } if (!allowMultipleSelections || (!e.isControlDown() && !e.isShiftDown())) { @@ -91,11 +114,12 @@ public class ImageView extends ItemView { item.selected = true; onSelectionChange(); item.scrollIntoView(); + return true; } @Override public void onMouseExit(MouseEvent e) { - if (display.updateHoveredItem(null, null)) { + if (updateHoveredItem(null, null)) { display.repaint(); } } @@ -103,25 +127,209 @@ public class ImageView extends ItemView { display.addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseMoved(MouseEvent e) { - FScrollPane scroller = ImageView.this.getScroller(); + FScrollPane scroller = getScroller(); Point hoverScrollPos = new Point(scroller.getHorizontalScrollBar().getValue(), scroller.getVerticalScrollBar().getValue()); - if (display.updateHoveredItem(e.getPoint(), hoverScrollPos)) { + if (updateHoveredItem(e.getPoint(), hoverScrollPos)) { display.repaint(); } } }); } - @Override - protected void onResize() { - if (this.pileBy == null) { - display.refresh(); //need to refresh to adjust wrapping of items + public GroupDef getGroupBy() { + return groupBy; + } + public void setGroupBy(GroupDef groupBy0) { + groupBy = groupBy0; + } + + public ColumnDef getPileBy() { + return pileBy; + } + public void setPileBy(ColumnDef pileBy0) { + pileBy = pileBy0; + if (pileBy == null) { + getScroller().setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + } + else { + getScroller().setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); } } + @Override + protected void onResize() { + updateLayout(); //need to update layout to adjust wrapping of items + } + @Override protected void onRefresh() { - display.refresh(); + groups.clear(); + + Group otherItems; + if (groupBy == null) { //use single group with all items if not grouping + otherItems = new Group(""); + groups.add(otherItems); + } + else { + otherItems = null; + for (String groupName : groupBy.getGroups()) { + groups.add(new Group(groupName)); + } + } + + for (Entry itemEntry : model.getOrderedList()) { + T item = itemEntry.getKey(); + int qty = itemEntry.getValue(); + int groupIndex = groupBy == null ? -1 : groupBy.getItemGroupIndex(item); + + for (int i = 0; i < qty; i++) { + if (groupIndex >= 0) { + groups.get(groupIndex).add(new ItemInfo(item)); + } + else { + if (otherItems == null) { + otherItems = new Group("Other"); + groups.add(otherItems); + } + otherItems.add(new ItemInfo(item)); + } + } + } + + updateLayout(); + } + + private void updateLayout() { + orderedItems.clear(); + + int x, groupY; + int y = PADDING; + int groupX = PADDING; + int itemAreaWidth = getVisibleSize().width; + int groupWidth = itemAreaWidth - 2 * groupX; + int pileX = groupBy == null ? groupX : 2 * groupX + 1; + int pileWidth = itemAreaWidth - 2 * pileX; + + int itemIndex = 0; + int itemWidth = 50 * imageScaleFactor; + int gap = Math.round(itemWidth * GAP_SCALE_FACTOR); + int dx = itemWidth + gap; + int itemsPerRow = (pileWidth + gap) / dx; + if (itemsPerRow == 0) { + itemsPerRow = 1; + itemWidth = pileWidth; + } + int itemHeight = Math.round(itemWidth * CardPanel.ASPECT_RATIO); + int dy = itemHeight + gap; + + for (Group group : groups) { + group.piles.clear(); + + groupY = y; + if (groupBy != null) { + y += GROUP_HEADER_HEIGHT + PADDING; //leave room for group header + if (group.isCollapsed || group.items.isEmpty()) { + group.setBounds(groupX, groupY, groupWidth, GROUP_HEADER_HEIGHT); + continue; + } + } + else if (group.items.isEmpty()) { + group.setBounds(groupX, groupY, groupWidth, 0); + continue; + } + + Pile pile = new Pile(); //use a pile for each row + x = pileX; + + for (ItemInfo itemInfo : group.items) { + itemInfo.index = itemIndex++; + orderedItems.add(itemInfo); + + if (pile.items.size() == itemsPerRow) { + pile = new Pile(); + x = pileX; + y += dy; + } + + itemInfo.setBounds(x, y, itemWidth, itemHeight); + + if (pile.items.size() == 0) { + pile.setBounds(pileX, y, pileWidth, itemHeight); + group.piles.add(pile); + } + pile.items.add(itemInfo); + x += dx; + } + + y += itemHeight; + if (groupBy != null) { + y += PADDING + 1; //leave room for group footer + } + group.setBounds(groupX, groupY, groupWidth, y - groupY); + y += PADDING; + } + + display.setPreferredSize(new Dimension(itemAreaWidth, y)); + display.revalidate(); + display.repaint(); + } + + private ItemInfo getItemAtPoint(Point p) { + for (int i = groups.size() - 1; i >= 0; i--) { + Group group = groups.get(i); + if (!group.isCollapsed && group.getBounds().contains(p)) { + for (int j = group.piles.size() - 1; j >= 0; j--) { + Pile pile = group.piles.get(j); + if (pile.getBounds().contains(p)) { + for (int k = pile.items.size() - 1; k >= 0; k--) { + ItemInfo item = pile.items.get(k); + if (item.getBounds().contains(p)) { + return item; + } + } + } + } + } + } + return null; + } + + private Dimension getVisibleSize() { + FScrollPane scroller = getScroller(); + Dimension size = getScroller().getSize(); + Insets insets = getScroller().getInsets(); + size = new Dimension(size.width - insets.left - insets.right, + size.height - insets.top - insets.bottom); + if (scroller.getVerticalScrollBarPolicy() != ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER) { + size.width -= scroller.getVerticalScrollBar().getPreferredSize().width; + } + if (scroller.getHorizontalScrollBarPolicy() != ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER) { + size.height -= scroller.getHorizontalScrollBar().getPreferredSize().height; + } + return size; + } + + private boolean updateHoveredItem(Point hoverPoint0, Point hoverScrollPos0) { + hoverPoint = hoverPoint0; + hoverScrollPos = hoverScrollPos0; + + ItemInfo item = null; + FScrollPane scroller = getScroller(); + if (hoverPoint0 != null) { + Point displayPoint = new Point(hoverPoint0); + //account for change in scroll positions since mouse last moved + displayPoint.x += scroller.getHorizontalScrollBar().getValue() - hoverScrollPos0.x; + displayPoint.y += scroller.getVerticalScrollBar().getValue() - hoverScrollPos0.y; + item = getItemAtPoint(displayPoint); + } + + if (hoveredItem == item) { return false; } + hoveredItem = item; + if (item != null) { + CDetail.SINGLETON_INSTANCE.showCard(item.item); + CPicture.SINGLETON_INSTANCE.showImage(item.item); + } + return true; } @Override @@ -131,13 +339,13 @@ public class ImageView extends ItemView { @Override public void setAllowMultipleSelections(boolean allowMultipleSelections0) { - this.allowMultipleSelections = allowMultipleSelections0; + allowMultipleSelections = allowMultipleSelections0; } @Override public T getItemAtIndex(int index) { if (index >= 0 && index < getCount()) { - return display.items.get(index).item; + return orderedItems.get(index).item; } return null; } @@ -145,12 +353,11 @@ public class ImageView extends ItemView { @Override public int getIndexOfItem(T item) { for (int i = getCount() - 1; i >= 0; i--) { - ItemInfo itemInfo = display.items.get(i); - if (itemInfo.item == item) { - return itemInfo.index; + if (orderedItems.get(i).item == item) { + return i; } } - return 0; + return -1; } @Override @@ -165,7 +372,7 @@ public class ImageView extends ItemView { @Override public int getCount() { - return display.items.size(); + return orderedItems.size(); } @Override @@ -175,7 +382,7 @@ public class ImageView extends ItemView { @Override public int getIndexAtPoint(Point p) { - ItemInfo item = display.getItemAtPoint(p); + ItemInfo item = getItemAtPoint(p); if (item != null) { return item.index; } @@ -198,7 +405,7 @@ public class ImageView extends ItemView { @Override public void selectAll() { clearSelection(); - for (Integer i = 0; i < display.items.size(); i++) { + for (Integer i = 0; i < getCount(); i++) { selectedIndices.add(i); } updateSelection(); @@ -224,7 +431,7 @@ public class ImageView extends ItemView { int count = getCount(); for (Integer i : selectedIndices) { if (i < count) { - display.items.get(i).selected = false; + orderedItems.get(i).selected = false; } } selectedIndices.clear(); @@ -232,7 +439,7 @@ public class ImageView extends ItemView { private void updateSelection() { for (Integer i : selectedIndices) { - display.items.get(i).selected = true; + orderedItems.get(i).selected = true; } onSelectionChange(); } @@ -247,7 +454,7 @@ public class ImageView extends ItemView { protected void onScrollSelectionIntoView(JViewport viewport) { if (selectedIndices.isEmpty()) { return; } - ItemInfo itemInfo = display.items.get(selectedIndices.get(0)); + ItemInfo itemInfo = orderedItems.get(selectedIndices.get(0)); itemInfo.scrollIntoView(); } @@ -255,234 +462,165 @@ public class ImageView extends ItemView { private final Rectangle bounds = new Rectangle(); public Rectangle getBounds() { - return this.bounds; + return bounds; } public void setBounds(int x, int y, int width, int height) { - this.bounds.x = x; - this.bounds.y = y; - this.bounds.width = width; - this.bounds.height = height; + bounds.x = x; + bounds.y = y; + bounds.width = width; + bounds.height = height; + } + public int getLeft() { + return bounds.x; + } + public int getTop() { + return bounds.y; + } + public int getRight() { + return bounds.x + bounds.width; + } + public int getBottom() { + return bounds.y + bounds.height; } public void scrollIntoView() { - int x = this.bounds.x - CardArea.GUTTER_X; - int y = this.bounds.y - CardArea.GUTTER_Y; - int width = this.bounds.width + 2 * CardArea.GUTTER_Y; - int height = this.bounds.height + 2 * CardArea.GUTTER_Y; + int x = bounds.x - PADDING; + int y = bounds.y - PADDING; + int width = bounds.width + 2 * PADDING; + int height = bounds.height + 2 * PADDING; display.scrollRectToVisible(new Rectangle(x, y, width, height)); } } private class Group extends DisplayArea { + private final List items = new ArrayList(); private final List piles = new ArrayList(); + private final String name; private boolean isCollapsed; + + public Group(String name0) { + name = name0; + } + + public void add(ItemInfo item) { + items.add(item); + } + + @Override + public String toString() { + return name; + } } private class Pile extends DisplayArea { private final List items = new ArrayList(); } private class ItemInfo extends DisplayArea { private final T item; - private Integer index; + private int index; private boolean selected; - private ItemInfo(T item0, int index0) { - this.item = item0; - this.index = index0; + private ItemInfo(T item0) { + item = item0; } @Override public String toString() { - return this.item.toString(); + return item.toString(); } } @SuppressWarnings("serial") private class CardViewDisplay extends JPanel { - private Point hoverPoint; - private Point hoverScrollPos; - private ItemInfo hoveredItem; - private List items = new ArrayList(); - private List sections = new ArrayList(); - private CardViewDisplay() { - this.setOpaque(false); - this.setFocusable(true); - } - - private void refresh() { - int index = 0; - this.items.clear(); - for (Entry itemEntry : model.getOrderedList()) { - for (int i = 0; i < itemEntry.getValue(); i++) { - this.items.add(new ItemInfo(itemEntry.getKey(), index++)); - } - } - this.refreshSections(); - } - - private void refreshSections() { - this.sections.clear(); - - if (!this.items.isEmpty()) { - if (ImageView.this.pileBy == null) { - buildSpreadsheet(); - } - else { - buildPiles(); - } - } - - this.revalidate(); - this.repaint(); - } - - private ItemInfo getItemAtPoint(Point p) { - for (int i = this.sections.size() - 1; i >= 0; i--) { - Group group = this.sections.get(i); - if (!group.isCollapsed && group.getBounds().contains(p)) { - for (int j = group.piles.size() - 1; j >= 0; j--) { - Pile pile = group.piles.get(j); - if (pile.getBounds().contains(p)) { - for (int k = pile.items.size() - 1; k >= 0; k--) { - ItemInfo item = pile.items.get(k); - if (item.getBounds().contains(p)) { - return item; - } - } - } - } - } - } - return null; - } - - private Dimension getVisibleSize() { - FScrollPane scroller = ImageView.this.getScroller(); - Dimension size = ImageView.this.getScroller().getSize(); - Insets insets = ImageView.this.getScroller().getInsets(); - size = new Dimension(size.width - insets.left - insets.right, - size.height - insets.top - insets.bottom); - if (scroller.getVerticalScrollBarPolicy() != ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER) { - size.width -= scroller.getVerticalScrollBar().getWidth(); - } - if (scroller.getHorizontalScrollBarPolicy() != ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER) { - size.height -= scroller.getHorizontalScrollBar().getHeight(); - } - return size; - } - - private void buildSpreadsheet() { - ImageView.this.getScroller().setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - - Group group = new Group(); - - final int itemAreaWidth = getVisibleSize().width; - int itemWidth = 50 * imageScaleFactor; - int gap = Math.round(itemWidth * GAP_SCALE_FACTOR); - int dx = itemWidth + gap; - int itemsPerRow = (itemAreaWidth - 2 * CardArea.GUTTER_X + gap) / dx; - if (itemsPerRow == 0) { - itemsPerRow = 1; - itemWidth = itemAreaWidth - 2 * CardArea.GUTTER_X; - } - int itemHeight = Math.round(itemWidth * CardPanel.ASPECT_RATIO); - int dy = itemHeight + gap; - - Pile pile = new Pile(); //use a pile for each row - int x = CardArea.GUTTER_X; - int y = CardArea.GUTTER_Y; - - for (ItemInfo itemInfo : this.items) { - if (pile.items.size() == itemsPerRow) { - pile = new Pile(); - x = CardArea.GUTTER_X; - y += dy; - } - - itemInfo.setBounds(x, y, itemWidth, itemHeight); - - if (pile.items.size() == 0) { - pile.setBounds(0, y, itemAreaWidth, dy); - group.piles.add(pile); - } - pile.items.add(itemInfo); - x += dx; - } - - group.setBounds(0, 0, itemAreaWidth, y + itemHeight + CardArea.GUTTER_Y); - this.setPreferredSize(group.getBounds().getSize()); - - this.sections.add(group); - } - - private void buildPiles() { - - } - - private boolean updateHoveredItem(Point hoverPoint0, Point hoverScrollPos0) { - this.hoverPoint = hoverPoint0; - this.hoverScrollPos = hoverScrollPos0; - - ItemInfo item = null; - FScrollPane scroller = ImageView.this.getScroller(); - if (hoverPoint0 != null) { - Point displayPoint = new Point(hoverPoint0); - //account for change in scroll positions since mouse last moved - displayPoint.x += scroller.getHorizontalScrollBar().getValue() - hoverScrollPos0.x; - displayPoint.y += scroller.getVerticalScrollBar().getValue() - hoverScrollPos0.y; - item = this.getItemAtPoint(displayPoint); - } - - if (this.hoveredItem == item) { return false; } - this.hoveredItem = item; - if (item != null) { - CDetail.SINGLETON_INSTANCE.showCard(item.item); - CPicture.SINGLETON_INSTANCE.showImage(item.item); - } - return true; + setOpaque(false); + setFocusable(true); } @Override public final void paintComponent(final Graphics g) { - if (this.items.isEmpty()) { return; } - - updateHoveredItem(this.hoverPoint, this.hoverScrollPos); //ensure hovered item up to date + updateHoveredItem(hoverPoint, hoverScrollPos); //ensure hovered item up to date final Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - int sectionIdx = 0, pileIdx = 0; - final int scrollTop = ImageView.this.getScroller().getVerticalScrollBar().getValue(); - final int scrollBottom = scrollTop + getVisibleSize().height; - if (ImageView.this.pileBy == null) { - pileIdx = scrollTop / this.sections.get(0).piles.get(0).getBounds().height; - } - else { - - } - for (; sectionIdx < this.sections.size(); sectionIdx++, pileIdx = 0) { - Group group = this.sections.get(sectionIdx); - if (group.getBounds().y >= scrollBottom) { + final Dimension visibleSize = getVisibleSize(); + final int visibleTop = getScroller().getVerticalScrollBar().getValue(); + final int visibleBottom = visibleTop + visibleSize.height; + final int visibleLeft = getScroller().getHorizontalScrollBar().getValue(); + final int visibleRight = visibleLeft + visibleSize.width; + + FSkin.setGraphicsFont(g2d, ItemListView.ROW_FONT); + FontMetrics fm = g2d.getFontMetrics(); + int fontOffsetY = (GROUP_HEADER_HEIGHT - fm.getHeight()) / 2 + fm.getAscent(); + + for (Group group : groups) { + if (group.getBottom() < visibleTop) { + continue; + } + if (group.getTop() >= visibleBottom) { break; } - if (this.sections.size() > 1) { - //TODO: Draw group name/border - if (group.isCollapsed) { + if (groupBy != null) { + Rectangle bounds = group.getBounds(); + FSkin.setGraphicsColor(g2d, ItemListView.HEADER_BACK_COLOR); + g2d.fillRect(bounds.x, bounds.y, bounds.width, GROUP_HEADER_HEIGHT - 1); + FSkin.setGraphicsColor(g2d, ItemListView.FORE_COLOR); + g2d.drawString(group.name + " (" + group.items.size() + ")", bounds.x + PADDING, bounds.y + fontOffsetY); + if (!group.items.isEmpty()) { //draw expand/collapse glyph as long as group isn't empty + int offset = GROUP_HEADER_HEIGHT / 4; + int x1 = bounds.x + bounds.width - PADDING; + int x2 = x1 - offset; + int x3 = x2 - offset; + int y2 = bounds.y + GROUP_HEADER_HEIGHT / 2; + if (!group.isCollapsed) { + offset *= -1; + y2++; + } + int y1 = y2 - offset; + g2d.drawLine(x1, y1, x2, y2); + g2d.drawLine(x2, y2, x3, y1); + if (group.isCollapsed) { + offset++; + } + else { + offset--; + } + y1 += offset; + y2 += offset; + g2d.drawLine(x1, y1, x2, y2); + g2d.drawLine(x2, y2, x3, y1); + } + FSkin.setGraphicsColor(g2d, ItemListView.GRID_COLOR); + g2d.drawRect(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1); + if (group.isCollapsed || group.items.isEmpty()) { continue; } + int y = bounds.y + GROUP_HEADER_HEIGHT - 1; //draw bottom border of header + g2d.drawLine(bounds.x, y, bounds.x + bounds.width - 1, y); } - for (; pileIdx < group.piles.size(); pileIdx++) { - Pile pile = group.piles.get(pileIdx); - if (pile.getBounds().y >= scrollBottom) { + else if (group.items.isEmpty()) { + continue; + } + for (Pile pile : group.piles) { + if (pile.getBottom() < visibleTop || pile.getRight() < visibleLeft) { + continue; + } + if (pile.getTop() >= visibleBottom || pile.getLeft() >= visibleRight) { break; } for (ItemInfo itemInfo : pile.items) { - if (itemInfo != this.hoveredItem) { //save hovered item for last + if (itemInfo.getBottom() < visibleTop || itemInfo.getRight() < visibleLeft) { + continue; + } + if (itemInfo.getTop() >= visibleBottom || itemInfo.getLeft() >= visibleRight) { + break; + } + if (itemInfo != hoveredItem) { //save hovered item for last drawItemImage(g2d, itemInfo); } } } } - if (this.hoveredItem != null) { //draw hovered item on top - drawItemImage(g2d, this.hoveredItem); + if (hoveredItem != null) { //draw hovered item on top + drawItemImage(g2d, hoveredItem); } } @@ -499,7 +637,7 @@ public class ImageView extends ItemView { bounds.width + 2 * selBorderSize, bounds.height + 2 * selBorderSize, cornerSize + selBorderSize, cornerSize + selBorderSize); } - else if (itemInfo == this.hoveredItem) { + else if (itemInfo == hoveredItem) { int hoverBorderSize = Math.max(1, selBorderSize / 2); g.setColor(Color.green); g.fillRoundRect(bounds.x - hoverBorderSize, bounds.y - hoverBorderSize, @@ -517,7 +655,7 @@ public class ImageView extends ItemView { else { g.setColor(Color.white); Shape clip = g.getClip(); - g.setClip(bounds.x, bounds.y, bounds.width, bounds.height); + g.setClip(bounds); g.drawString(itemInfo.item.getName(), bounds.x + 10, bounds.y + 20); g.setClip(clip); } diff --git a/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/views/ItemListView.java b/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/views/ItemListView.java index 57b23df82e0..860ce97f5de 100644 --- a/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/views/ItemListView.java +++ b/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/views/ItemListView.java @@ -78,14 +78,14 @@ import forge.item.InventoryItem; @SuppressWarnings("serial") public final class ItemListView extends ItemView { static final SkinColor BACK_COLOR = FSkin.getColor(FSkin.Colors.CLR_ZEBRA); - private static final SkinColor FORE_COLOR = FSkin.getColor(FSkin.Colors.CLR_TEXT); + static final SkinColor FORE_COLOR = FSkin.getColor(FSkin.Colors.CLR_TEXT); private static final SkinColor SEL_ACTIVE_COLOR = FSkin.getColor(FSkin.Colors.CLR_ACTIVE); private static final SkinColor SEL_INACTIVE_COLOR = FSkin.getColor(FSkin.Colors.CLR_INACTIVE); - private static final SkinColor HEADER_BACK_COLOR = BACK_COLOR.getContrastColor(-10); + static final SkinColor HEADER_BACK_COLOR = BACK_COLOR.getContrastColor(-10); static final SkinColor ALT_ROW_COLOR = BACK_COLOR.getContrastColor(-20); - private static final SkinColor GRID_COLOR = BACK_COLOR.getContrastColor(20); + static final SkinColor GRID_COLOR = BACK_COLOR.getContrastColor(20); private static final SkinBorder HEADER_BORDER = new FSkin.CompoundSkinBorder(new FSkin.MatteSkinBorder(0, 0, 1, 1, GRID_COLOR), new EmptyBorder(0, 1, 0, 0)); - private static final SkinFont ROW_FONT = FSkin.getFont(12); + static final SkinFont ROW_FONT = FSkin.getFont(12); private static final int ROW_HEIGHT = 19; private final ItemTable table = new ItemTable(); @@ -104,6 +104,7 @@ public final class ItemListView extends ItemView { public ItemListView(ItemManager itemManager0, ItemManagerModel model0) { super(itemManager0, model0); this.tableModel = new ItemTableModel(model0); + this.setAllowMultipleSelections(false); // use different selection highlight colors for focused vs. unfocused tables this.table.addMouseListener(new FMouseAdapter() { diff --git a/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/views/ItemView.java b/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/views/ItemView.java index b6c6b0b303b..6b26b090d40 100644 --- a/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/views/ItemView.java +++ b/forge-gui/src/main/java/forge/gui/toolbox/itemmanager/views/ItemView.java @@ -33,6 +33,7 @@ import org.apache.commons.lang3.StringUtils; import forge.gui.toolbox.FLabel; import forge.gui.toolbox.FScrollPane; import forge.gui.toolbox.FSkin; +import forge.gui.toolbox.FSkin.SkinColor; import forge.gui.toolbox.FSkin.SkinImage; import forge.gui.toolbox.ToolTipListener; import forge.gui.toolbox.itemmanager.ItemManager; @@ -40,6 +41,8 @@ import forge.gui.toolbox.itemmanager.ItemManagerModel; import forge.item.InventoryItem; public abstract class ItemView { + private static final SkinColor BORDER_COLOR = FSkin.getColor(FSkin.Colors.CLR_TEXT); + protected final ItemManager itemManager; protected final ItemManagerModel model; private final FScrollPane scroller; @@ -51,7 +54,7 @@ public abstract class ItemView { this.itemManager = itemManager0; this.model = model0; this.scroller = new FScrollPane(false); - this.scroller.setBorder(new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_TEXT))); + this.scroller.setBorder(new FSkin.LineSkinBorder(BORDER_COLOR)); this.button = new FLabel.Builder().hoverable().selectable(true) .icon(getIcon()).iconScaleAuto(false) .tooltip(getCaption()).build(); @@ -114,7 +117,8 @@ public abstract class ItemView { protected abstract void onRefresh(); private void fixSelection(final Iterable itemsToSelect, final int backupIndexToSelect) { if (itemsToSelect == null) { - setSelectedIndex(0); //select first item if no items to select + setSelectedIndex(0, false); //select first item if no items to select + getScroller().getVerticalScrollBar().setValue(0); //ensure scrolled to top } else { if (!setSelectedItems(itemsToSelect)) {