From c03e9d044223d20cf495367fd71ab31f1cfe6498 Mon Sep 17 00:00:00 2001 From: slprime Date: Sat, 13 Jul 2024 09:33:11 +0300 Subject: [PATCH 1/4] Create Collapsible Items; Presets refactoring --- .../codechicken/nei/CollapsibleItems.java | 364 ++++++ .../codechicken/nei/FormattedTextField.java | 2 +- src/main/java/codechicken/nei/ItemList.java | 55 +- src/main/java/codechicken/nei/ItemPanel.java | 343 +++++- src/main/java/codechicken/nei/ItemSorter.java | 2 +- src/main/java/codechicken/nei/ItemsGrid.java | 14 +- .../java/codechicken/nei/LayoutManager.java | 19 +- .../codechicken/nei/LayoutStyleMinecraft.java | 6 - .../java/codechicken/nei/NEIClientConfig.java | 42 +- .../java/codechicken/nei/PanelWidget.java | 18 +- .../java/codechicken/nei/PresetsList.java | 295 +++++ .../java/codechicken/nei/PresetsWidget.java | 1073 ----------------- .../codechicken/nei/RecipeSearchField.java | 7 +- .../java/codechicken/nei/SearchField.java | 69 +- .../java/codechicken/nei/VisiblityData.java | 7 +- .../codechicken/nei/config/GuiItemSorter.java | 7 +- .../nei/config/preset/CheckboxButton.java | 53 + .../nei/config/preset/GuiPresetList.java | 239 ++++ .../nei/config/preset/GuiPresetSettings.java | 228 ++++ .../nei/config/preset/LeftPanel.java | 327 +++++ .../nei/config/preset/PresetItemsGrid.java | 167 +++ .../nei/config/preset/RightPanel.java | 387 ++++++ .../codechicken/nei/recipe/StackInfo.java | 87 +- .../nei/recipe/TemplateRecipeHandler.java | 4 +- .../assets/nei/cfg/collapsibleitems.cfg | 20 + src/main/resources/assets/nei/lang/en_US.lang | 40 +- src/main/resources/assets/nei/lang/zh_CN.lang | 9 - 27 files changed, 2669 insertions(+), 1215 deletions(-) create mode 100644 src/main/java/codechicken/nei/CollapsibleItems.java create mode 100644 src/main/java/codechicken/nei/PresetsList.java delete mode 100644 src/main/java/codechicken/nei/PresetsWidget.java create mode 100644 src/main/java/codechicken/nei/config/preset/CheckboxButton.java create mode 100644 src/main/java/codechicken/nei/config/preset/GuiPresetList.java create mode 100644 src/main/java/codechicken/nei/config/preset/GuiPresetSettings.java create mode 100644 src/main/java/codechicken/nei/config/preset/LeftPanel.java create mode 100644 src/main/java/codechicken/nei/config/preset/PresetItemsGrid.java create mode 100644 src/main/java/codechicken/nei/config/preset/RightPanel.java create mode 100644 src/main/resources/assets/nei/cfg/collapsibleitems.cfg diff --git a/src/main/java/codechicken/nei/CollapsibleItems.java b/src/main/java/codechicken/nei/CollapsibleItems.java new file mode 100644 index 000000000..e5c90b929 --- /dev/null +++ b/src/main/java/codechicken/nei/CollapsibleItems.java @@ -0,0 +1,364 @@ +package codechicken.nei; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import net.minecraft.item.ItemStack; +import net.minecraftforge.oredict.OreDictionary; + +import org.apache.commons.io.IOUtils; + +import codechicken.nei.ItemList.AllMultiItemFilter; +import codechicken.nei.ItemList.AnyMultiItemFilter; +import codechicken.nei.ItemList.EverythingItemFilter; +import codechicken.nei.ItemList.NegatedItemFilter; +import codechicken.nei.ItemList.NothingItemFilter; +import codechicken.nei.PresetsList.Preset; +import codechicken.nei.PresetsList.PresetMode; +import codechicken.nei.api.ItemFilter; +import cpw.mods.fml.common.registry.GameData; + +public class CollapsibleItems { + + protected static interface ISearchParserProvider { + + public ItemFilter getFilter(String searchText); + + default Predicate getMatcher(String searchText) { + + if (searchText.length() >= 3 && searchText.startsWith("r/") && searchText.endsWith("/")) { + + try { + Pattern pattern = Pattern.compile( + searchText.substring(2, searchText.length() - 1), + Pattern.MULTILINE | Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); + return value -> pattern.matcher(value).find(); + } catch (PatternSyntaxException ignored) {} + + } else if (!searchText.isEmpty()) { + return value -> value.toLowerCase().contains(searchText); + } + + return null; + } + + } + + protected static class GroupTokenParser { + + protected final HashMap searchProviders = new HashMap<>(); + + public void addProvider(char prefix, ISearchParserProvider provider) { + this.searchProviders.put(prefix, provider); + } + + public ItemFilter getFilter(String filterText) { + final String[] parts = filterText.toLowerCase().trim().split("\\|"); + final List searchTokens = Arrays.stream(parts).map(s -> parseSearchText(s)) + .filter(s -> s != null && !s.filters.isEmpty()).collect(Collectors.toCollection(ArrayList::new)); + + if (searchTokens.isEmpty()) { + return new EverythingItemFilter(); + } else if (searchTokens.size() == 1) { + return searchTokens.get(0); + } else { + return new AnyMultiItemFilter(searchTokens); + } + } + + private AllMultiItemFilter parseSearchText(String filterText) { + + if (filterText.isEmpty()) { + return null; + } + + final String[] tokens = filterText.split("\\s+"); + final AllMultiItemFilter searchTokens = new AllMultiItemFilter(); + + for (String token : tokens) { + token = token.trim(); + boolean ignore = token.startsWith("!"); + + if (ignore) { + token = token.substring(1); + } + + if (token.isEmpty()) { + continue; + } + + ISearchParserProvider provider = this.searchProviders.get(token.charAt(0)); + + if (provider != null) { + token = token.substring(1); + } else { + provider = this.searchProviders.get('\0'); + } + + ItemFilter filter = parseToken(ignore ? "!" + token : token, provider); + + if (filter != null) { + searchTokens.filters.add(filter); + } + } + + return searchTokens; + } + + private ItemFilter parseToken(String token, ISearchParserProvider provider) { + final String[] parts = token.split(","); + final AnyMultiItemFilter includeFilter = new AnyMultiItemFilter(); + final AnyMultiItemFilter expludeFilter = new AnyMultiItemFilter(); + final AllMultiItemFilter groupFilter = new AllMultiItemFilter(); + + for (String part : parts) { + boolean ignore = part.startsWith("!"); + + if (ignore) { + part = part.substring(1); + } + + ItemFilter filter = provider.getFilter(part); + + if (filter == null) { + continue; + } + + if (ignore) { + expludeFilter.filters.add(filter); + } else { + includeFilter.filters.add(filter); + } + } + + if (!includeFilter.filters.isEmpty()) { + groupFilter.filters.add(includeFilter); + } + + if (!expludeFilter.filters.isEmpty()) { + groupFilter.filters.add(new NegatedItemFilter(expludeFilter)); + } + + return groupFilter.filters.isEmpty() ? null : groupFilter; + } + } + + protected static class IdentifierFilter implements ISearchParserProvider { + + @Override + public ItemFilter getFilter(String searchText) { + + if (Pattern.matches("^\\d+(-\\d+)*$", searchText)) { + final Predicate filter = generateDamageFilter(searchText); + return (stack) -> filter.test(stack.getItemDamage()); + } + + Predicate matcher = getMatcher(searchText); + + if (matcher != null) { + return stack -> matcher.test(getStringIdentifier(stack)); + } + + return null; + } + + protected String getStringIdentifier(ItemStack stack) { + String name = GameData.getItemRegistry().getNameForObject(stack.getItem()); + return name == null || name.isEmpty() ? "Unknown:Unknown" : name; + } + + protected Predicate generateDamageFilter(String searchText) { + String[] range = searchText.split("-"); + + if (range.length == 1) { + final int damage = Integer.parseInt(range[0]); + return (dmg) -> dmg == damage; + } else { + final int damageStart = Integer.parseInt(range[0]); + final int damageEnd = Integer.parseInt(range[1]); + return (dmg) -> dmg >= damageStart && dmg <= damageEnd; + } + } + } + + protected static class OreDictionaryFilter implements ISearchParserProvider { + + @Override + public ItemFilter getFilter(String searchText) { + Predicate matcher = getMatcher(searchText); + + if (matcher != null) { + return stack -> matches(stack, matcher); + } + + return null; + } + + protected boolean matches(ItemStack stack, Predicate matcher) { + return IntStream.of(OreDictionary.getOreIDs(stack)) + .anyMatch(id -> matcher.test(OreDictionary.getOreName(id))); + } + } + + protected static class GroupItem { + + protected static int lastGroupIndex = 0; + + public final int groupIndex; + public final ItemFilter filter; + public boolean expanded = false; + + public GroupItem(ItemFilter filter) { + this.groupIndex = lastGroupIndex++; + this.filter = filter; + } + + public boolean matches(ItemStack stack) { + return this.filter.matches(stack); + } + } + + protected static final GroupTokenParser groupParser = new GroupTokenParser(); + protected final Map groups = new ConcurrentHashMap<>(); + protected final Map cache = new ConcurrentHashMap<>(); + + static { + groupParser.addProvider('\0', new IdentifierFilter()); + groupParser.addProvider('$', new OreDictionaryFilter()); + } + + public void reload() { + GroupItem.lastGroupIndex = 0; + this.groups.clear(); + this.cache.clear(); + + for (int i = PresetsList.presets.size() - 1; i >= 0; i--) { + Preset preset = PresetsList.presets.get(i); + if (preset.enabled && preset.mode == PresetMode.GROUP) { + addGroup(preset); + } + } + + loadCollapsibleItems(); + + if (ItemList.loadFinished) { + LayoutManager.markItemsDirty(); + } + } + + protected void loadCollapsibleItems() { + File file = NEIClientConfig.collapsibleItemsFile; + if (!file.exists()) { + try (FileWriter writer = new FileWriter(file)) { + NEIClientConfig.logger.info("Creating default collapsible items list {}", file); + URL defaultCollapsibleItemsResource = ClientHandler.class + .getResource("/assets/nei/cfg/collapsibleitems.cfg"); + if (defaultCollapsibleItemsResource != null) { + IOUtils.copy(defaultCollapsibleItemsResource.openStream(), writer); + } + } catch (IOException e) { + NEIClientConfig.logger.error("Failed to save default collapsible items list to file {}", file, e); + } + } + + try (FileReader reader = new FileReader(file)) { + NEIClientConfig.logger.info("Loading collapsible items from file {}", file); + IOUtils.readLines(reader).stream().filter((line) -> !line.startsWith("#") && !line.trim().isEmpty()) + .forEach(this::addGroup); + } catch (IOException e) { + NEIClientConfig.logger.error("Failed to load collapsible items from file {}", file, e); + } + } + + protected void addGroup(String filterText) { + addGroup(CollapsibleItems.groupParser.getFilter(filterText.trim())); + } + + protected void addGroup(ItemFilter filter) { + if (filter == null || filter instanceof EverythingItemFilter || filter instanceof NothingItemFilter) return; + GroupItem group = new GroupItem(filter); + this.groups.put(group.groupIndex, group); + } + + public ItemFilter getItemFilter() { + AnyMultiItemFilter filter = new AnyMultiItemFilter(); + + for (GroupItem group : this.groups.values()) { + filter.filters.add(group.filter); + } + + return filter; + } + + public void updateCache(final List items) { + this.cache.clear(); + + try { + + ItemList.forkJoinPool.submit(() -> items.parallelStream().forEach(stack -> { + GroupItem group = this.groups.values().stream().filter(g -> g.matches(stack)).findFirst().orElse(null); + + if (group != null) { + this.cache.put(stack, group.groupIndex); + } + })).get(); + + } catch (Exception e) { + NEIClientConfig.logger.error("Error create collapsible items groups", e); + } + + } + + public int getGroupIndex(ItemStack stack) { + + if (stack == null) { + return -1; + } + + return this.cache.getOrDefault(stack, -1); + } + + public boolean isExpanded(int groupIndex) { + + if (this.groups.containsKey(groupIndex)) { + return this.groups.get(groupIndex).expanded; + } + + return true; + } + + public void setExpanded(int groupIndex, boolean expanded) { + + if (this.groups.containsKey(groupIndex)) { + this.groups.get(groupIndex).expanded = expanded; + } + + } + + public void toggleGroups(Boolean expanded) { + + if (expanded == null) { + expanded = !this.groups.values().stream().filter(g -> g.expanded).findAny().isPresent(); + } + + for (GroupItem group : this.groups.values()) { + group.expanded = expanded; + } + + } + +} diff --git a/src/main/java/codechicken/nei/FormattedTextField.java b/src/main/java/codechicken/nei/FormattedTextField.java index 246f79512..239b357e1 100644 --- a/src/main/java/codechicken/nei/FormattedTextField.java +++ b/src/main/java/codechicken/nei/FormattedTextField.java @@ -8,7 +8,7 @@ import org.lwjgl.opengl.GL11; -class FormattedTextField extends GuiTextField { +public class FormattedTextField extends GuiTextField { public static interface TextFormatter { diff --git a/src/main/java/codechicken/nei/ItemList.java b/src/main/java/codechicken/nei/ItemList.java index f9ad6bc98..86d6ad8bd 100644 --- a/src/main/java/codechicken/nei/ItemList.java +++ b/src/main/java/codechicken/nei/ItemList.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -38,11 +39,12 @@ public class ItemList { * Updates to this should be synchronised on this */ public static final List itemFilterers = new LinkedList<>(); - public static final List loadCallbacks = new LinkedList<>(); + public static final CollapsibleItems collapsibleItems = new CollapsibleItems(); private static final HashSet erroredItems = new HashSet<>(); private static final HashSet stackTraces = new HashSet<>(); + private static final HashMap ordering = new HashMap<>(); /** * Unlike {@link LayoutManager#itemsLoaded}, this indicates whether item loading is actually finished or not. */ @@ -223,6 +225,38 @@ private String getTooltip(ItemStack stack) { return ""; } + private void updateOrdering(List items) { + ItemList.ordering.clear(); + + ItemSorter.sort(items); + + if (NEIClientConfig.enableCollapsibleItems()) { + HashMap groups = new HashMap<>(); + int orderIndex = 0; + + for (ItemStack stack : items) { + final int groupIndex = ItemList.collapsibleItems.getGroupIndex(stack); + + if (groupIndex == -1) { + ItemList.ordering.put(stack, orderIndex++); + } else { + + if (!groups.containsKey(groupIndex)) { + groups.put(groupIndex, orderIndex++); + } + + ItemList.ordering.put(stack, groups.get(groupIndex)); + } + } + } else { + int orderIndex = 0; + + for (ItemStack stack : items) { + ItemList.ordering.put(stack, orderIndex++); + } + } + } + @Override @SuppressWarnings("unchecked") public void execute() { @@ -230,8 +264,8 @@ public void execute() { ThreadOperationTimer timer = getTimer(NEIClientConfig.getItemLoadingTimeout()); loadFinished = false; - LinkedList items = new LinkedList<>(); - LinkedList permutations = new LinkedList<>(); + List items = new LinkedList<>(); + List permutations = new LinkedList<>(); ListMultimap itemMap = ArrayListMultimap.create(); timer.setLimit(NEIClientConfig.getItemLoadingTimeout()); @@ -267,9 +301,12 @@ public void execute() { ItemList.itemMap = itemMap; for (ItemsLoadedCallback callback : loadCallbacks) callback.itemsLoaded(); - updateFilter.restart(); + if (interrupted()) return; + ItemList.collapsibleItems.updateCache(items); + updateOrdering(items); loadFinished = true; + updateFilter.restart(); } }; @@ -284,11 +321,15 @@ public static ForkJoinPool getPool(int poolSize) { public static final RestartableTask updateFilter = new RestartableTask("NEI Item Filtering") { + private int compare(ItemStack o1, ItemStack o2) { + return Integer.compare(ItemList.ordering.get(o1), ItemList.ordering.get(o2)); + } + @Override public void execute() { - // System.out.println("Executing NEI Item Filtering"); - ArrayList filtered; + if (!loadFinished) return; ItemFilter filter = getItemListFilter(); + ArrayList filtered; try { filtered = ItemList.forkJoinPool.submit( @@ -302,7 +343,7 @@ public void execute() { } if (interrupted()) return; - ItemSorter.sort(filtered); + filtered.sort(this::compare); if (interrupted()) return; ItemPanel.updateItemList(filtered); } diff --git a/src/main/java/codechicken/nei/ItemPanel.java b/src/main/java/codechicken/nei/ItemPanel.java index fe73fb808..44491290d 100644 --- a/src/main/java/codechicken/nei/ItemPanel.java +++ b/src/main/java/codechicken/nei/ItemPanel.java @@ -1,17 +1,27 @@ package codechicken.nei; -import static codechicken.lib.gui.GuiDraw.drawRect; - +import java.awt.Color; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import javax.annotation.Nullable; import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumChatFormatting; + +import org.lwjgl.opengl.GL11; import codechicken.lib.vec.Rectangle4i; +import codechicken.nei.guihook.GuiContainerManager; +import codechicken.nei.guihook.IContainerTooltipHandler; -public class ItemPanel extends PanelWidget { +public class ItemPanel extends PanelWidget implements IContainerTooltipHandler { /** * Backwards compat :-/ @@ -35,6 +45,7 @@ public ItemStack getStackMouseOver(int mousex, int mousey) { public Button less; public ItemQuantityField quantity; public ItemHistoryPanel historyPanel; + public Button toggleGroups; public static class ItemPanelSlot { @@ -52,9 +63,29 @@ public ItemPanelSlot(int idx) { } } + protected static class MaskMetadata { + + public int groupIndex = -1; + public boolean extended = false; + + public Color bgColor = null; + public Color color = null; + + public boolean left = false; + public boolean top = false; + public boolean right = false; + public boolean bottom = false; + + } + protected static class ItemPanelGrid extends ItemsGrid { public ArrayList newItems; + public ArrayList rawItems; + + protected final HashMap> groupItems = new HashMap<>(); + protected final HashMap maskMetadata = new HashMap<>(); + protected boolean forceExpand = false; public void setItems(ArrayList items) { newItems = items; @@ -62,9 +93,48 @@ public void setItems(ArrayList items) { public void refresh(GuiContainer gui) { - if (newItems != null) { - realItems = newItems; - newItems = null; + if (this.newItems != null) { + this.groupItems.clear(); + this.forceExpand = false; + + if (NEIClientConfig.enableCollapsibleItems() && !this.newItems.isEmpty()) { + final Set groups = new HashSet<>(); + boolean outsideGroup = false; + + this.realItems = new ArrayList<>(); + this.rawItems = new ArrayList<>(this.newItems); + + for (ItemStack stack : this.newItems) { + final int groupIndex = ItemList.collapsibleItems.getGroupIndex(stack); + + if (groupIndex == -1) { + this.realItems.add(stack); + outsideGroup = true; + } else { + + if (!groups.contains(groupIndex) || ItemList.collapsibleItems.isExpanded(groupIndex)) { + this.realItems.add(stack); + groups.add(groupIndex); + } + + if (!this.groupItems.containsKey(groupIndex)) { + this.groupItems.put(groupIndex, new ArrayList<>()); + } + + this.groupItems.get(groupIndex).add(stack); + } + } + + // automatically opens if there are elements from only one group + if (!outsideGroup && groups.size() == 1) { + this.realItems = new ArrayList<>(this.newItems); + this.forceExpand = true; + } + } else { + this.realItems = new ArrayList<>(this.newItems); + } + + this.newItems = null; onItemsChanged(); } @@ -72,11 +142,170 @@ public void refresh(GuiContainer gui) { } @Override - protected void beforeDrawSlot(@Nullable ItemPanelSlot focused, int slotIdx, Rectangle4i rect) { - if (PresetsWidget.inEditMode()) { - if (!PresetsWidget.isHidden(getItem(slotIdx))) drawRect(rect.x, rect.y, rect.w, rect.h, 0xee555555); + protected List getMask() { + final boolean updateBorders = this.gridMask == null; + final List mask = super.getMask(); + + if (updateBorders) { + calculateGroupBorders(mask); + } + + return mask; + } + + protected void calculateGroupBorders(List mask) { + this.maskMetadata.clear(); + + if (mask.isEmpty() || this.groupItems.isEmpty()) { + return; + } + + final HashMap maskGroup = new HashMap<>(); + final Color collapsedColor = new Color( + NEIClientConfig.getSetting("inventory.collapsibleItems.collapsedColor").getHexValue(), + true); + final Color expandedColor = new Color( + NEIClientConfig.getSetting("inventory.collapsibleItems.expandedColor").getHexValue(), + true); + + for (int slotIndex = 0; slotIndex < mask.size(); slotIndex++) { + if (mask.get(slotIndex) == null) { + continue; + } + + int idx = mask.get(slotIndex); + maskGroup.put(idx, ItemList.collapsibleItems.getGroupIndex(getItem(idx))); + } + + for (int slotIndex = 0; slotIndex < mask.size(); slotIndex++) { + if (mask.get(slotIndex) == null) { + continue; + } + + int idx = mask.get(slotIndex); + int groupIndex = maskGroup.get(idx); + + if (groupIndex == -1 || this.groupItems.get(groupIndex).size() == 1) { + continue; + } + + int column = slotIndex % this.columns; + int row = slotIndex / this.columns; + int prevSlotIndex = (row - 1) * this.columns + column; + int nextSlotIndex = (row + 1) * this.columns + column; + MaskMetadata metadata = new MaskMetadata(); + + metadata.groupIndex = groupIndex; + metadata.extended = this.forceExpand || ItemList.collapsibleItems.isExpanded(groupIndex); + metadata.bgColor = metadata.extended ? expandedColor : collapsedColor; + metadata.color = darkerColor(metadata.bgColor); + metadata.left = column == 0 || idx == 0 + || mask.get(slotIndex - 1) == null + || maskGroup.getOrDefault(idx - 1, -1) != groupIndex; + metadata.right = column == this.columns - 1 || maskGroup.getOrDefault(idx + 1, -1) != groupIndex; + + if (prevSlotIndex >= 0) { + Integer previdx = mask.get(prevSlotIndex); + metadata.top = previdx == null || maskGroup.getOrDefault(previdx, -1) != groupIndex; + } else { + metadata.top = true; + } + + if (nextSlotIndex < mask.size()) { + Integer nextidx = mask.get(nextSlotIndex); + metadata.bottom = nextidx == null || maskGroup.getOrDefault(nextidx, -1) != groupIndex; + } else { + metadata.bottom = true; + } + + this.maskMetadata.put(idx, metadata); + } + } + + @Override + protected void beforeDrawItems(int mousex, int mousey, @Nullable ItemPanelSlot focused) { + final List mask = getMask(); + + super.beforeDrawItems(mousex, mousey, focused); + + for (int i = 0; i < mask.size(); i++) { + if (mask.get(i) != null) { + drawBorder(mask.get(i), getSlotRect(i)); + } + } + } + + protected void drawBorder(int slotIdx, Rectangle4i rect) { + final MaskMetadata metadata = this.maskMetadata.get(slotIdx); + + if (metadata != null) { + final float LINE_WIDTH = 0.75f; + + drawRect(rect.x, rect.y, rect.w, rect.h, metadata.bgColor); + + if (metadata.left) { + float leftBottom = metadata.bottom ? -LINE_WIDTH : 0; + drawRect(rect.x, rect.y, LINE_WIDTH, rect.h + leftBottom, metadata.color); + } + + if (metadata.right) { + float rightTop = metadata.top ? LINE_WIDTH : 0; + drawRect(rect.x + rect.w, rect.y - rightTop, LINE_WIDTH, rect.h + rightTop, metadata.color); + } + + if (metadata.top) { + drawRect(rect.x, rect.y - LINE_WIDTH, rect.w, LINE_WIDTH, metadata.color); + } + + if (metadata.bottom) { + drawRect(rect.x, rect.y + rect.h - LINE_WIDTH, rect.w, LINE_WIDTH, metadata.color); + } + } + } + + private static Color darkerColor(Color color) { + return new Color( + color.getRed(), + color.getGreen(), + color.getBlue(), + Math.min((int) (color.getAlpha() + (255f / 5) * 2), 255)); + } + + private static void drawRect(double left, double top, double width, double height, Color color) { + Tessellator tessellator = Tessellator.instance; + + GL11.glEnable(GL11.GL_BLEND); + GL11.glDisable(GL11.GL_TEXTURE_2D); + OpenGlHelper.glBlendFunc(770, 771, 1, 0); + GL11.glColor4f( + color.getRed() / 255f, + color.getGreen() / 255f, + color.getBlue() / 255f, + color.getAlpha() / 255f); + tessellator.startDrawingQuads(); + tessellator.addVertex(left, top + height, 0.0D); + tessellator.addVertex(left + width, top + height, 0.0D); + tessellator.addVertex(left + width, top, 0.0D); + tessellator.addVertex(left, top, 0.0D); + tessellator.draw(); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glDisable(GL11.GL_BLEND); + } + + @Override + protected void drawItem(Rectangle4i rect, int slotIdx) { + final MaskMetadata metadata = this.maskMetadata.get(slotIdx); + + if (metadata != null && !metadata.extended) { + final List groupItems = this.groupItems.get(metadata.groupIndex); + + GuiContainerManager.drawItems.zLevel -= 10F; + GuiContainerManager.drawItem(rect.x + 1, rect.y - 1, groupItems.get(groupItems.size() - 1), true, ""); + GuiContainerManager.drawItems.zLevel += 10F; + + GuiContainerManager.drawItem(rect.x - 1, rect.y + 1, getItem(slotIdx), true, ""); } else { - super.beforeDrawSlot(focused, slotIdx, rect); + super.drawItem(rect, slotIdx); } } @@ -88,6 +317,7 @@ public String getMessageOnEmpty() { public ItemPanel() { grid = new ItemPanelGrid(); + GuiContainerManager.addTooltipHandler(this); } public static void updateItemList(ArrayList newItems) { @@ -99,6 +329,21 @@ public static void updateItemList(ArrayList newItems) { public void init() { super.init(); + toggleGroups = new Button("G") { + + @Override + public String getButtonTip() { + return NEIClientUtils.translate("itempanel.collapsed.button.tip"); + } + + @Override + public boolean onButtonPress(boolean rightclick) { + ItemList.collapsibleItems.toggleGroups(rightclick ? false : null); + ItemList.updateFilter.restart(); + return true; + } + }; + more = new Button("+") { @Override @@ -167,6 +412,18 @@ public int getHeight(GuiContainer gui) { return gui.height - getMarginTop(gui) - PADDING; } + @Override + protected int resizeHeader(GuiContainer gui) { + int marginTop = super.resizeHeader(gui); + + toggleGroups.w = pageNext.w; + toggleGroups.h = pageNext.h; + toggleGroups.y = pageNext.y; + toggleGroups.x = pageNext.x - toggleGroups.w - 2; + + return marginTop; + } + protected int resizeFooter(GuiContainer gui) { if (!NEIClientConfig.showItemQuantityWidget() && NEIClientConfig.isSearchWidgetCentered() && !NEIClientConfig.showHistoryPanelWidget()) { @@ -224,6 +481,10 @@ public void setVisible() { LayoutManager.addWidget(quantity); } + if (NEIClientConfig.enableCollapsibleItems()) { + LayoutManager.addWidget(toggleGroups); + } + if (NEIClientConfig.showHistoryPanelWidget()) { LayoutManager.addWidget(historyPanel); } @@ -251,4 +512,66 @@ protected ItemStack getDraggedStackWithQuantity(int mouseDownSlot) { return null; } + + @Override + public List handleTooltip(GuiContainer gui, int mousex, int mousey, List currenttip) { + return currenttip; + } + + @Override + public List handleItemDisplayName(GuiContainer gui, ItemStack itemstack, List currenttip) { + return currenttip; + } + + @Override + public List handleItemTooltip(GuiContainer gui, ItemStack itemstack, int mousex, int mousey, + List currenttip) { + final ItemPanelGrid panelGrid = ((ItemPanelGrid) this.grid); + + if (!panelGrid.forceExpand) { + final ItemPanelSlot hoverSlot = getSlotMouseOver(mousex, mousey); + + if (hoverSlot != null) { + + final MaskMetadata metadata = panelGrid.maskMetadata.get(hoverSlot.slotIndex); + + if (metadata != null) { + final List items = panelGrid.groupItems.get(metadata.groupIndex); + + if (items != null && items.size() > 1) { + String message = metadata.extended ? "itempanel.collapsed.hint.collapse" + : "itempanel.collapsed.hint.expand"; + currenttip.add( + 1, + EnumChatFormatting.GRAY + NEIClientUtils.translate(message, items.size()) + + EnumChatFormatting.RESET); + } + } + } + } + + return currenttip; + } + + @Override + public boolean handleClick(int mousex, int mousey, int button) { + final ItemPanelGrid panelGrid = ((ItemPanelGrid) this.grid); + + if (NEIClientUtils.altKey() && button == 0 && !panelGrid.forceExpand) { + final ItemPanelSlot hoverSlot = grid.getSlotMouseOver(mousex, mousey); + + if (hoverSlot != null) { + final MaskMetadata metadata = panelGrid.maskMetadata.get(hoverSlot.slotIndex); + + if (metadata != null) { + ItemList.collapsibleItems.setExpanded(metadata.groupIndex, !metadata.extended); + panelGrid.setItems(panelGrid.rawItems); + return true; + } + } + } + + return super.handleClick(mousex, mousey, button); + } + } diff --git a/src/main/java/codechicken/nei/ItemSorter.java b/src/main/java/codechicken/nei/ItemSorter.java index 1f355e766..13f7ab9cf 100644 --- a/src/main/java/codechicken/nei/ItemSorter.java +++ b/src/main/java/codechicken/nei/ItemSorter.java @@ -46,7 +46,7 @@ public String getTooltip() { // optimisations public HashMap ordering = null; - public static void sort(ArrayList items) { + public static void sort(List items) { try { // items = (ArrayList) // items.parallelStream().sorted(instance).collect(Collectors.toList()); diff --git a/src/main/java/codechicken/nei/ItemsGrid.java b/src/main/java/codechicken/nei/ItemsGrid.java index 01e0b4796..2146f7701 100644 --- a/src/main/java/codechicken/nei/ItemsGrid.java +++ b/src/main/java/codechicken/nei/ItemsGrid.java @@ -173,11 +173,13 @@ protected void updateGuiOverlapSlots(GuiContainer gui) { invalidSlotMap = new boolean[rows * columns]; perPage = columns * rows; - if (NEIClientConfig.optimizeGuiOverlapComputation()) { - checkGuiOverlap(gui, 0, columns - 2, 1); - checkGuiOverlap(gui, columns - 1, 1, -1); - } else { - checkGuiOverlap(gui, 0, columns, 1); + if (gui != null) { + if (NEIClientConfig.optimizeGuiOverlapComputation()) { + checkGuiOverlap(gui, 0, columns - 2, 1); + checkGuiOverlap(gui, columns - 1, 1, -1); + } else { + checkGuiOverlap(gui, 0, columns, 1); + } } if (oldRows != rows || oldColumns != columns || !Arrays.equals(oldSlotMap, invalidSlotMap)) { @@ -247,7 +249,7 @@ protected List getMask() { return this.gridMask; } - private void beforeDrawItems(int mousex, int mousey, @Nullable ItemPanelSlot focused) { + protected void beforeDrawItems(int mousex, int mousey, @Nullable ItemPanelSlot focused) { final List mask = getMask(); diff --git a/src/main/java/codechicken/nei/LayoutManager.java b/src/main/java/codechicken/nei/LayoutManager.java index 5a8162ddb..94421567a 100644 --- a/src/main/java/codechicken/nei/LayoutManager.java +++ b/src/main/java/codechicken/nei/LayoutManager.java @@ -87,7 +87,6 @@ public class LayoutManager implements IContainerInputHandler, IContainerTooltipH public static ItemPanel itemPanel; public static BookmarkPanel bookmarkPanel; public static SubsetWidget dropDown; - public static PresetsWidget presetsPanel; public static SearchField searchField; public static boolean searchInitFocusedCancellable = false; protected static int mousePriorX; @@ -381,12 +380,8 @@ public static void layout(GuiContainer gui) { if (visiblity.showBookmarkPanel || gui.guiTop <= 20) visiblity.showSubsetDropdown = false; - if (!visiblity.showBookmarkPanel || gui.guiTop <= 20) visiblity.showPresetsDropdown = false; - if (gui.guiLeft - 4 < 76) visiblity.showWidgets = false; - if (!itemsLoaded) visiblity.showPresetsDropdown = false; - try { GuiInfo.readLock.lock(); GuiInfo.guiHandlers.forEach(handler -> handler.modifyVisiblity(gui, visiblity)); @@ -409,8 +404,8 @@ private static void init() { bookmarkPanel.init(); dropDown = new SubsetWidget(); - presetsPanel = new PresetsWidget(); searchField = new SearchField("search"); + API.addItemFilter(searchField); options = new ButtonCycled(3) { @@ -714,13 +709,7 @@ public static void updateWidgetVisiblities(GuiContainer gui, VisiblityData visib addWidget(options); addWidget(bookmarksButton); if (visiblity.showItemPanel) { - - if (PresetsWidget.inEditMode()) { - drawWidgets.add(itemPanel); - } else { - addWidget(itemPanel); - } - + addWidget(itemPanel); itemPanel.setVisible(); } @@ -729,10 +718,6 @@ public static void updateWidgetVisiblities(GuiContainer gui, VisiblityData visib bookmarkPanel.setVisible(); } - if (visiblity.showPresetsDropdown) { - addWidget(presetsPanel); - } - searchField.setVisible(visiblity.showSearchSection); if (visiblity.showSearchSection) { addWidget(searchField); diff --git a/src/main/java/codechicken/nei/LayoutStyleMinecraft.java b/src/main/java/codechicken/nei/LayoutStyleMinecraft.java index f11775914..3d0458ac1 100644 --- a/src/main/java/codechicken/nei/LayoutStyleMinecraft.java +++ b/src/main/java/codechicken/nei/LayoutStyleMinecraft.java @@ -11,7 +11,6 @@ import static codechicken.nei.LayoutManager.itemPresenceOverlays; import static codechicken.nei.LayoutManager.magnet; import static codechicken.nei.LayoutManager.options; -import static codechicken.nei.LayoutManager.presetsPanel; import static codechicken.nei.LayoutManager.rain; import static codechicken.nei.LayoutManager.searchField; import static codechicken.nei.LayoutManager.timeButtons; @@ -121,11 +120,6 @@ public void layout(GuiContainer gui, VisiblityData visiblity) { dropDown.y = 0; dropDown.x = (gui.width - gui.xSize) / 2 + gui.xSize - dropDown.w; - presetsPanel.h = 16; - presetsPanel.w = 150; - presetsPanel.y = 2; - presetsPanel.x = (gui.width - gui.xSize) / 2 + gui.xSize - presetsPanel.w; - searchField.h = 20; if (NEIClientConfig.isSearchWidgetCentered()) { searchField.w = 150; diff --git a/src/main/java/codechicken/nei/NEIClientConfig.java b/src/main/java/codechicken/nei/NEIClientConfig.java index 444fb10a3..2545b570c 100644 --- a/src/main/java/codechicken/nei/NEIClientConfig.java +++ b/src/main/java/codechicken/nei/NEIClientConfig.java @@ -43,6 +43,7 @@ import codechicken.nei.config.GuiNEIOptionList; import codechicken.nei.config.GuiOptionList; import codechicken.nei.config.GuiPanelSettings; +import codechicken.nei.config.OptionButton; import codechicken.nei.config.OptionCycled; import codechicken.nei.config.OptionGamemodes; import codechicken.nei.config.OptionIntegerField; @@ -52,6 +53,7 @@ import codechicken.nei.config.OptionToggleButton; import codechicken.nei.config.OptionToggleButtonBoubs; import codechicken.nei.config.OptionUtilities; +import codechicken.nei.config.preset.GuiPresetList; import codechicken.nei.event.NEIConfigsLoadedEvent; import codechicken.nei.recipe.GuiRecipeTab; import codechicken.nei.recipe.IRecipeHandler; @@ -79,6 +81,7 @@ public class NEIClientConfig { public static final File handlerOrderingFile = new File(configDir, "handlerordering.csv"); public static final File hiddenHandlersFile = new File(configDir, "hiddenhandlers.csv"); public static final File enableAutoFocusFile = new File(configDir, "enableautofocus.cfg"); + public static final File collapsibleItemsFile = new File(configDir, "collapsibleitems.cfg"); @Deprecated public static File bookmarkFile; @@ -252,6 +255,37 @@ public String getButtonText() { tag.getTag("inventory.history.splittingMode").getIntValue(1); API.addOption(new OptionCycled("inventory.history.splittingMode", 2, true)); + tag.getTag("inventory.collapsibleItems.enabled").getBooleanValue(true); + API.addOption(new OptionToggleButton("inventory.collapsibleItems.enabled", true) { + + @Override + public boolean onClick(int button) { + LayoutManager.markItemsDirty(); + return super.onClick(button); + } + }); + + tag.getTag("inventory.collapsibleItems.expandedColor") + .setComment("Color of the collapsible item expanded state").getHexValue(0x335555EE); + API.addOption(new OptionIntegerField("inventory.collapsibleItems.expandedColor")); + + tag.getTag("inventory.collapsibleItems.collapsedColor") + .setComment("Color of the collapsible item collapsed state").getHexValue(0x335555EE); + API.addOption(new OptionIntegerField("inventory.collapsibleItems.collapsedColor")); + + API.addOption( + new OptionButton( + "inventory.collapsibleItems.reloadLabel", + "inventory.collapsibleItems.reloadButton", + "inventory.collapsibleItems.reloadTip") { + + @Override + public boolean onClick(int button) { + LayoutManager.markItemsDirty(); + return super.onClick(button); + } + }); + tag.getTag("inventory.itemIDs").getIntValue(1); API.addOption(new OptionCycled("inventory.itemIDs", 3, true)); @@ -272,6 +306,8 @@ public String getButtonText() { API.addOption(new OptionOpenGui("world.panels", GuiPanelSettings.class)); + API.addOption(new OptionOpenGui("world.presets", GuiPresetList.class)); + tag.getTag("inventory.profileRecipes").getBooleanValue(false); API.addOption(new OptionToggleButton("inventory.profileRecipes", true)); @@ -645,7 +681,7 @@ private static void initPresetsFile(String worldPath) { worldPath = "global"; } - PresetsWidget.loadPresets(worldPath); + PresetsList.setPresetsFile(worldPath); } public static boolean isWorldSpecific(String setting) { @@ -833,6 +869,10 @@ public static boolean shouldCacheItemRendering() { return getBooleanSetting("inventory.cacheItemRendering") && OpenGlHelper.framebufferSupported; } + public static boolean enableCollapsibleItems() { + return getBooleanSetting("inventory.collapsibleItems.enabled"); + } + public static boolean getMagnetMode() { return enabledActions.contains("magnet"); } diff --git a/src/main/java/codechicken/nei/PanelWidget.java b/src/main/java/codechicken/nei/PanelWidget.java index 27b9281ce..291a338da 100644 --- a/src/main/java/codechicken/nei/PanelWidget.java +++ b/src/main/java/codechicken/nei/PanelWidget.java @@ -42,11 +42,14 @@ public void init() { pagePrev = new Button("Prev") { public boolean onButtonPress(boolean rightclick) { - if (!rightclick) { + + if (rightclick) { + grid.setPage(0); + } else { grid.shiftPage(-1); - return true; } - return false; + + return true; } @Override @@ -57,11 +60,14 @@ public String getRenderLabel() { pageNext = new Button("Next") { public boolean onButtonPress(boolean rightclick) { - if (!rightclick) { + + if (rightclick) { + grid.setPage(grid.getNumPages() - 1); + } else { grid.shiftPage(1); - return true; } - return false; + + return true; } @Override diff --git a/src/main/java/codechicken/nei/PresetsList.java b/src/main/java/codechicken/nei/PresetsList.java new file mode 100644 index 000000000..40122feac --- /dev/null +++ b/src/main/java/codechicken/nei/PresetsList.java @@ -0,0 +1,295 @@ +package codechicken.nei; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import net.minecraft.item.ItemStack; + +import org.apache.commons.io.IOUtils; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSyntaxException; + +import codechicken.core.CommonUtils; +import codechicken.nei.api.API; +import codechicken.nei.api.IRecipeFilter; +import codechicken.nei.api.IRecipeFilter.IRecipeFilterProvider; +import codechicken.nei.api.ItemFilter; +import codechicken.nei.api.ItemFilter.ItemFilterProvider; +import codechicken.nei.recipe.IRecipeHandler; +import codechicken.nei.recipe.StackInfo; +import codechicken.nei.util.NBTJson; + +public class PresetsList { + + public static enum PresetMode { + HIDE, + REMOVE, + GROUP; + } + + public static class Preset implements ItemFilter { + + public String name = ""; + public boolean enabled = true; + public PresetMode mode = PresetMode.HIDE; + public Set items = new HashSet<>(); + + public static String getIdentifier(ItemStack stack) { + return StackInfo.getItemStackGUID(stack); + } + + @Override + public boolean matches(ItemStack item) { + return items.contains(Preset.getIdentifier(item)); + } + + public Preset copy() { + Preset preset = new Preset(); + + preset.name = name; + preset.enabled = enabled; + preset.mode = mode; + preset.items = new HashSet<>(items); + + return preset; + } + } + + protected static class RecipesFilter implements IRecipeFilterProvider, IRecipeFilter { + + public Set cache; + + public IRecipeFilter getFilter() { + + if (cache == null) { + cache = PresetsList.presets.stream().filter(p -> p.enabled && p.mode == PresetMode.REMOVE) + .flatMap(p -> p.items.stream()).collect(Collectors.toSet()); + } + + return cache.isEmpty() ? null : this; + } + + @Override + public boolean matches(IRecipeHandler handler, List ingredients, PositionedStack result, + List others) { + + if (matchPositionedStack(ingredients, false)) { + return false; + } + + if (result != null && matchPositionedStack(result)) { + return true; + } + + if (!others.isEmpty() && matchPositionedStack(others, true)) { + return true; + } + + return result == null && others.isEmpty(); + } + + private boolean matchPositionedStack(List items, boolean dir) { + for (PositionedStack pStack : items) { + if (matchPositionedStack(pStack) == dir) { + return true; + } + } + + return false; + } + + private boolean matchPositionedStack(PositionedStack pStack) { + for (ItemStack stack : pStack.items) { + if (!cache.contains(Preset.getIdentifier(stack))) { + return true; + } + } + + return false; + } + + } + + protected static class ItemPanelFilter implements ItemFilterProvider, ItemFilter { + + public Set cache; + + public ItemFilter getFilter() { + + if (cache == null) { + cache = PresetsList.presets.stream() + .filter(p -> p.enabled && (p.mode == PresetMode.HIDE || p.mode == PresetMode.REMOVE)) + .flatMap(p -> p.items.stream()).collect(Collectors.toSet()); + } + + return cache.isEmpty() ? null : this; + } + + @Override + public boolean matches(ItemStack stack) { + return !cache.contains(Preset.getIdentifier(stack)); + } + } + + public static final List presets = new ArrayList<>(); + + protected static File presetsFile; + protected static RecipesFilter recipeFilter = new RecipesFilter(); + protected static ItemPanelFilter itemFilter = new ItemPanelFilter(); + + static { + API.addItemFilter(itemFilter); + API.addRecipeFilter(recipeFilter); + ItemList.loadCallbacks.add(PresetsList::itemsLoaded); + } + + public static ItemFilter getItemFilter() { + return itemFilter.getFilter(); + } + + public static void itemsLoaded() { + recipeFilter.cache = null; + itemFilter.cache = null; + ItemList.collapsibleItems.reload(); + } + + public static void setPresetsFile(String worldPath) { + final File dir = new File(CommonUtils.getMinecraftDir(), "saves/NEI/" + worldPath); + + if (!dir.exists()) { + dir.mkdirs(); + } + + presetsFile = new File(dir, "presets.ini"); + + if (!presetsFile.exists()) { + final File globalPresets = new File(CommonUtils.getMinecraftDir(), "saves/NEI/global/presets.ini"); + final File configPresets = new File(NEIClientConfig.configDir, "presets.ini"); + final File defaultBookmarks = configPresets.exists() ? configPresets : globalPresets; + + if (defaultBookmarks.exists()) { + + try { + presetsFile.createNewFile(); + + InputStream src = new FileInputStream(defaultBookmarks); + OutputStream dst = new FileOutputStream(presetsFile); + + IOUtils.copy(src, dst); + + src.close(); + dst.close(); + + } catch (IOException e) {} + } + } + + loadPresets(); + } + + protected static void loadPresets() { + + if (presetsFile == null || !presetsFile.exists()) { + return; + } + + List itemStrings; + try (FileInputStream reader = new FileInputStream(presetsFile)) { + NEIClientConfig.logger.info("Loading presets from file {}", presetsFile); + itemStrings = IOUtils.readLines(reader, StandardCharsets.UTF_8); + } catch (IOException e) { + NEIClientConfig.logger.error("Failed to load presets from file {}", presetsFile, e); + return; + } + + JsonParser parser = new JsonParser(); + Preset preset = new Preset(); + presets.clear(); + + for (String itemStr : itemStrings) { + + try { + + if (itemStr.isEmpty()) { + itemStr = "; {}"; + } + + if (itemStr.startsWith("; ")) { + JsonObject settings = parser.parse(itemStr.substring(2)).getAsJsonObject(); + + if (!preset.items.isEmpty()) { + // do not create empty namespaces + presets.add(preset); + preset = new Preset(); + } + + if (settings.get("name") != null) { + preset.name = settings.get("name").getAsString(); + } + + if (settings.get("enabled") != null) { + preset.enabled = settings.get("enabled").getAsBoolean(); + } + + if (settings.get("mode") != null) { + preset.mode = PresetMode.valueOf(settings.get("mode").getAsString()); + } + + } else { + preset.items.add(itemStr); + } + + } catch (IllegalArgumentException | JsonSyntaxException | IllegalStateException e) { + NEIClientConfig.logger.error("Failed to load presets ItemStack from json string:\n{}", itemStr); + } + } + + if (!preset.items.isEmpty()) { + presets.add(preset); + } + + itemsLoaded(); + } + + public static void savePresets() { + + if (presetsFile == null) { + return; + } + + List strings = new ArrayList<>(); + + for (Preset preset : presets) { + JsonObject settings = new JsonObject(); + + settings.add("name", new JsonPrimitive(preset.name)); + settings.add("enabled", new JsonPrimitive(preset.enabled)); + settings.add("mode", new JsonPrimitive(preset.mode.toString())); + + strings.add("; " + NBTJson.toJson(settings)); + strings.addAll(preset.items); + } + + try (FileOutputStream output = new FileOutputStream(presetsFile)) { + IOUtils.writeLines(strings, "\n", output, StandardCharsets.UTF_8); + } catch (IOException e) { + NEIClientConfig.logger.error("Filed to save presets list to file {}", presetsFile, e); + } + + itemsLoaded(); + } + +} diff --git a/src/main/java/codechicken/nei/PresetsWidget.java b/src/main/java/codechicken/nei/PresetsWidget.java deleted file mode 100644 index 7cd3b4e0f..000000000 --- a/src/main/java/codechicken/nei/PresetsWidget.java +++ /dev/null @@ -1,1073 +0,0 @@ -package codechicken.nei; - -import static codechicken.nei.NEIClientUtils.translate; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.FontRenderer; -import net.minecraft.item.ItemStack; - -import org.apache.commons.io.IOUtils; -import org.lwjgl.input.Keyboard; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSyntaxException; - -import codechicken.core.CommonUtils; -import codechicken.core.gui.GuiScrollSlot; -import codechicken.lib.gui.GuiDraw; -import codechicken.lib.vec.Rectangle4i; -import codechicken.nei.ItemList.ItemsLoadedCallback; -import codechicken.nei.ItemPanel.ItemPanelSlot; -import codechicken.nei.api.API; -import codechicken.nei.api.IRecipeFilter; -import codechicken.nei.api.IRecipeFilter.IRecipeFilterProvider; -import codechicken.nei.api.ItemFilter; -import codechicken.nei.api.ItemFilter.ItemFilterProvider; -import codechicken.nei.recipe.GuiRecipe; -import codechicken.nei.recipe.IRecipeHandler; -import codechicken.nei.recipe.StackInfo; -import codechicken.nei.util.NBTJson; -import cpw.mods.fml.common.registry.GameRegistry; - -public class PresetsWidget extends Widget implements ItemFilterProvider, ItemsLoadedCallback, ItemFilter { - - protected static enum PresetTagState { - - WHITELIST, - BLACKLIST, - BLACKLIST_PLUS; - - public PresetTagState next() { - if (this == PresetTagState.WHITELIST) return PresetTagState.BLACKLIST; - if (this == PresetTagState.BLACKLIST) return PresetTagState.BLACKLIST_PLUS; - if (this == PresetTagState.BLACKLIST_PLUS) return PresetTagState.WHITELIST; - return this; - } - } - - public static class PresetTag { - - public String filename = null; - public String displayName = ""; - public PresetTagState state = PresetTagState.BLACKLIST; - public ItemStackSet items; - - public PresetTag(String displayName) { - this(displayName, null, PresetTagState.BLACKLIST, null); - } - - public PresetTag(String displayName, ItemStackSet items, PresetTagState state, String filename) { - this.displayName = displayName; - this.items = items != null ? items : new ItemStackSet(); - this.state = state; - this.filename = filename; - } - - public boolean matches(final ItemStack stack) { - return items.contains(stack) == (state == PresetTagState.WHITELIST); - } - - public static PresetTag loadFromFile(File file) { - if (!file.isFile() || !file.exists() || file.getName().equals("selected.ini")) { - return null; - } - - try { - - List itemStrings; - try (FileInputStream input = new FileInputStream(file)) { - NEIClientConfig.logger.info("Loading presets from file {}", file); - itemStrings = IOUtils.readLines(input, "UTF-8"); - } catch (IOException e) { - NEIClientConfig.logger.error("Failed to load presets from file {}", file, e); - return null; - } - - final JsonParser parser = new JsonParser(); - final JsonObject metaObject = parser.parse(itemStrings.remove(0)).getAsJsonObject(); - final String displayName = metaObject.get("displayName").getAsString(); - final PresetTagState state = metaObject.has("state") - ? PresetTagState.valueOf(metaObject.get("state").getAsString()) - : PresetTagState.BLACKLIST; - final ItemStackSet items = new ItemStackSet(); - - for (ItemStack stack : ItemList.items) { - if (itemStrings.contains(StackInfo.getItemStackGUID(stack))) { - items.add(stack); - } - } - - return new PresetTag(displayName, items, state, file.getName()); - } catch (Throwable th) { - NEIClientConfig.logger.error("Failed to load presets ItemStack from file", file); - } - - return null; - } - - public void deleteFile() { - if (presetsDir == null || filename == null) return; - final File tagFile = new File(presetsDir, filename); - - if (tagFile.exists()) { - tagFile.delete(); - } - } - - public void saveToFile() { - if (presetsDir == null) return; - - if (filename == null) { - final String sanitized = displayName.replaceAll("[^_\\-.0-9a-zA-Z]", "_"); - int index = 1; - - filename = sanitized + ".ini"; - - while ((new File(presetsDir, filename)).exists() || filename.equals("selected.ini")) { - filename = sanitized + " (" + index + ").ini"; - index++; - } - } - - final List strings = new ArrayList<>(); - final JsonObject row = new JsonObject(); - - row.add("displayName", new JsonPrimitive(displayName)); - row.add("state", new JsonPrimitive(state.toString())); - - strings.add(NBTJson.toJson(row)); - - try { - - for (ItemStack stack : items.values()) { - strings.add(StackInfo.getItemStackGUID(stack)); - } - - } catch (JsonSyntaxException e) { - NEIClientConfig.logger.error("Failed to stringify presets ItemStack to json string"); - } - - final File tagFile = new File(presetsDir, filename); - - try (FileOutputStream output = new FileOutputStream(tagFile)) { - IOUtils.writeLines(strings, "\n", output, "UTF-8"); - } catch (IOException e) { - NEIClientConfig.logger.error("Filed to save presets list to file {}", tagFile, e); - } - } - - public PresetTag copy() { - return new PresetTag(displayName, new ItemStackSet().addAll(items.values()), state, null); - } - } - - protected static class SubsetListBox extends GuiScrollSlot { - - protected final List presets = new ArrayList<>(); - protected final List selected = new ArrayList<>(); - protected int lastSelectedIndex = 0; - - public SubsetListBox(File presetsDir) { - super(0, 0, 0, 0); - setSmoothScroll(false); - - try { - - presets.addAll( - ItemList.forkJoinPool.submit( - () -> Arrays.asList(presetsDir.listFiles()).parallelStream() - .map(f -> PresetTag.loadFromFile(f)).filter(p -> p != null) - .collect(Collectors.toCollection(ArrayList::new))) - .get()); - - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - return; - } - - selected.addAll(loadSelFromFile(presetsDir, presets)); - sort(); - } - - public SubsetListBox() { - super(0, 0, 0, 0); - setSmoothScroll(false); - } - - public void addTag(final PresetTag tag, boolean sel) { - presets.add(tag); - - if (sel) { - selected.add(tag); - } - } - - protected List getPresets() { - return presets; - } - - protected List getSelected() { - return selected; - } - - public void replaceSeleted(final PresetTag tag) { - - if (selected.size() == 1) { - selected.get(0).deleteFile(); - presets.set(presets.indexOf(selected.get(0)), tag); - selected.set(0, tag); - } else { - presets.add(tag); - selected.add(tag); - } - - tag.saveToFile(); - saveSelToFile(); - sort(); - } - - public void sort() { - presets.sort((o1, o2) -> o1.displayName.compareTo(o2.displayName)); - } - - public void clear() { - presets.clear(); - selected.clear(); - } - - @Override - public int getSlotHeight(int slot) { - return 18; - } - - @Override - protected int getNumSlots() { - return presets.size(); - } - - @Override - protected void slotClicked(int slot, int button, int mx, int my, int count) { - - if (slot >= presets.size()) { - return; - } - - final PresetTag tag = presets.get(slot); - final int width = windowBounds().width; - - final Rectangle4i direction = new Rectangle4i(0, 0, 18, 18); - final Rectangle4i delete = new Rectangle4i(width - 18, 0, 18, 18); - final Rectangle4i option = new Rectangle4i(18, 0, width - 18 - 18, 18); - - if (option.contains(mx, my)) { - final boolean sel = selected.contains(tag); - - if (NEIClientUtils.controlKey()) { - - if (sel) { - selected.remove(tag); - } else { - selected.add(tag); - } - - } else if (NEIClientUtils.shiftKey()) { - selected.clear(); - - for (int i = Math.min(lastSelectedIndex, slot); i <= Math.max(lastSelectedIndex, slot); i++) { - if (!selected.contains(presets.get(i))) { - selected.add(presets.get(i)); - } - } - - } else { - final int size = selected.size(); - selected.clear(); - - if (!sel || size > 1) { - selected.add(tag); - } - } - - if (selected.isEmpty()) { - lastSelectedIndex = 0; - } else if (!NEIClientUtils.shiftKey()) { - lastSelectedIndex = slot; - } - - } else if (delete.contains(mx, my)) { - presets.remove(tag); - selected.remove(tag); - tag.deleteFile(); - } else if (direction.contains(mx, my)) { - tag.state = tag.state.next(); - tag.saveToFile(); - } - - saveSelToFile(); - PresetsWidget.edit = null; - PresetsWidget.openListBox = !presets.isEmpty() - && (NEIClientUtils.controlKey() || NEIClientUtils.shiftKey() || !option.contains(mx, my)); - ItemList.updateFilter.restart(); - PresetsRecipeFilter.blacklist = null; - } - - @Override - protected void drawSlot(int slot, int x, int y, int mx, int my, float frame) { - if (slot >= presets.size()) { - return; - } - - final int width = windowBounds().width; - final Rectangle4i direction = new Rectangle4i(x, y, 18, 18); - final Rectangle4i delete = new Rectangle4i(x + width - 18, y, 18, 18); - final Rectangle4i option = new Rectangle4i(x + 18, y, width - 18 - 18, 18); - final PresetTag tag = presets.get(slot); - - final int optionState = selected.contains(tag) ? 0 : option.contains(x + mx, y + my) ? 1 : 2; - final int directionState = direction.contains(x + mx, y + my) ? 1 : 2; - final int deleteState = delete.contains(x + mx, y + my) ? 1 : 2; - - final String displayName = NEIClientUtils - .cropText(PresetsWidget.fontRenderer, tag.displayName, option.w - 6); - String stateLabel = translate("presets.blacklist.label"); - - if (tag.state == PresetTagState.WHITELIST) { - stateLabel = translate("presets.whitelist.label"); - } else if (tag.state == PresetTagState.BLACKLIST_PLUS) { - stateLabel = translate("presets.blacklistPlus.label"); - } - - // blacklist or whitelist - LayoutManager.getLayoutStyle() - .drawSubsetTag(null, direction.x, direction.y, direction.w, direction.h, directionState, false); - GuiDraw.drawString( - stateLabel, - direction.x + 6, - direction.y + 5, - directionState == 2 ? 0xFFE0E0E0 : 0xFFFFA0); - - // option name - LayoutManager.getLayoutStyle() - .drawSubsetTag(null, option.x, option.y, option.w, option.h, optionState, false); - GuiDraw.drawString(displayName, option.x + 3, option.y + 5, optionState == 2 ? 0xFFE0E0E0 : 0xFFFFA0); - - // remove icon - LayoutManager.getLayoutStyle() - .drawSubsetTag(null, delete.x, delete.y, delete.w, delete.h, deleteState, false); - GuiDraw.drawString("✕", delete.x + 6, delete.y + 5, deleteState == 2 ? 0xFFE0E0E0 : 0x601010); - } - - @Override - public void drawOverlay(float frame) {} - - @Override - public void drawBackground(float frame) { - drawRect(x, y, x + width, y + height, 0xFF202020); - } - - @Override - public void drawScrollbar(float frame) { - if (hasScrollbar()) { - super.drawScrollbar(frame); - } - } - - @Override - public int scrollbarGuideAlignment() { - return 0; - } - - protected List loadSelFromFile(File presetsDir, List presets) { - final File file = new File(presetsDir, "selected.ini"); - List selected = new ArrayList<>(); - - if (file.exists()) { - - try { - - List itemStrings; - try (FileInputStream input = new FileInputStream(file)) { - NEIClientConfig.logger.info("Loading presets selected from file {}", file); - itemStrings = IOUtils.readLines(input, "UTF-8"); - } catch (IOException e) { - NEIClientConfig.logger.error("Failed to load presets selected from file {}", file, e); - return selected; - } - - final HashMap map = new HashMap<>(); - - for (PresetTag tag : presets) { - map.put(tag.filename, tag); - } - - for (String filename : itemStrings) { - if (map.containsKey(filename)) { - selected.add(map.get(filename)); - } - } - - } catch (Throwable th) { - NEIClientConfig.logger.error("Failed to load presets selected from file", file); - } - } - - return selected; - } - - public void saveSelToFile() { - - final File file = new File(presetsDir, "selected.ini"); - final List list = new ArrayList<>(); - - for (final PresetTag tag : selected) { - list.add(tag.filename); - } - - try (FileOutputStream output = new FileOutputStream(file)) { - IOUtils.writeLines(list, "\n", output, "UTF-8"); - } catch (IOException e) { - NEIClientConfig.logger.error("Filed to save presets selected list to file {}", file, e); - } - } - } - - protected static class MouseSelection { - - public int startX = -1; - public int startY = -1; - public int startIndex = -1; - public int endIndex = -1; - public HashSet items = new HashSet<>(); - public boolean append = true; - - public MouseSelection(int slotIndex, boolean append) { - final ItemsGrid grid = ItemPanels.itemPanel.getGrid(); - final Rectangle4i rec = grid.getItemRect(slotIndex); - - this.append = append; - endIndex = slotIndex; - startIndex = slotIndex; - startX = rec.x; - startY = rec.y; - } - } - - protected static class PresetsRecipeFilter implements IRecipeFilterProvider, IRecipeFilter { - - protected static ItemStackSet blacklist; - - public IRecipeFilter getFilter() { - - if (PresetsWidget.edit != null) { - return null; - } - - if (blacklist == null) { - blacklist = new ItemStackSet(); - - for (PresetTag tag : listbox.getSelected()) { - if (tag.state == PresetTagState.BLACKLIST_PLUS) { - blacklist.addAll(tag.items.values()); - } - } - } - - return blacklist.isEmpty() ? null : this; - } - - @Override - public boolean matches(IRecipeHandler handler, List ingredients, PositionedStack result, - List others) { - - if (matchPositionedStack(ingredients, false)) { - return false; - } - - if (result != null && matchPositionedStack(result)) { - return true; - } - - if (!others.isEmpty() && matchPositionedStack(others, true)) { - return true; - } - - return result == null && others.isEmpty(); - } - - private boolean matchPositionedStack(List items, boolean dir) { - for (PositionedStack pStack : items) { - if (matchPositionedStack(pStack) == dir) { - return true; - } - } - - return false; - } - - private boolean matchPositionedStack(PositionedStack pStack) { - for (ItemStack stack : pStack.items) { - if (!blacklist.contains(stack)) { - return true; - } - } - - return false; - } - - } - - protected static final FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer; - protected static SubsetListBox listbox = new SubsetListBox(); - protected static File presetsDir; - - protected static boolean openListBox = false; - protected static PresetTag edit; - protected long lastclicktime; - protected static MouseSelection mouseSelection; - - protected static final Button dropdown = new Button() { - - public boolean onButtonPress(boolean rightclick) { - if (!rightclick) { - openListBox = !openListBox; - return true; - } - - return false; - } - - @Override - public String getRenderLabel() { - return "..."; - } - }; - - protected static final Label selectedValue = new Label("", false) { - - @Override - public void draw(int mousex, int mousey) { - final String text = NEIClientUtils.cropText(fontRenderer, this.text, w); - - if (text.equals(this.text)) { - GuiDraw.drawString(text, x + w - fontRenderer.getStringWidth(text), y + (h - 8) / 2, colour); - } else { - GuiDraw.drawString(text, x, y + (h - 8) / 2, colour); - } - } - - @Override - public List handleTooltip(int mx, int my, List tooltip) { - if (contains(mx, my) && listbox.getSelected().size() <= 1) { - tooltip.add(translate("presets.label.tooltip")); - } - - return tooltip; - } - }; - - protected static final TextField selectedDisplayName = new TextField("") { - - public void onTextChange(String oldText) { - if (edit != null) { - edit.displayName = text(); - } - } - - @Override - public void draw(int mousex, int mousey) { - super.draw(mousex, mousey); - - if (text().isEmpty()) { - GuiDraw.drawGradientRect(x - 1, y - 1, 1, h + 2, 0xFFcc3300, 0xFFcc3300); // Left - GuiDraw.drawGradientRect(x - 1, y - 1, w + 2, 1, 0xFFcc3300, 0xFFcc3300); // Top - GuiDraw.drawGradientRect(x + w, y - 1, 1, h + 2, 0xFFcc3300, 0xFFcc3300); // Left - GuiDraw.drawGradientRect(x - 1, y + h, w + 2, 1, 0xFFcc3300, 0xFFcc3300); // Bottom - } - } - - @Override - public List handleTooltip(int mx, int my, List tooltip) { - if (contains(mx, my) && listbox.getSelected().size() <= 1) { - tooltip.add(translate("presets.textfield.tooltip.1")); - tooltip.add(translate("presets.textfield.tooltip.2")); - } - - return tooltip; - } - }; - - protected static final Button selectedState = new Button("B") { - - public boolean onButtonPress(boolean rightclick) { - if (!rightclick && edit != null) { - edit.state = edit.state.next(); - return true; - } - - return false; - } - - @Override - public String getRenderLabel() { - - if (edit == null || edit.state == PresetTagState.BLACKLIST) { - return translate("presets.blacklist.label"); - } else if (edit.state == PresetTagState.WHITELIST) { - return translate("presets.whitelist.label"); - } else if (edit.state == PresetTagState.BLACKLIST_PLUS) { - return translate("presets.blacklistPlus.label"); - } - - return translate("presets.blacklist.label"); - } - - @Override - public void addTooltips(List tooltip) { - if (edit != null) { - if (edit.state == PresetTagState.BLACKLIST) { - tooltip.add(translate("presets.blacklist.tooltip")); - } else if (edit.state == PresetTagState.WHITELIST) { - tooltip.add(translate("presets.whitelist.tooltip")); - } else if (edit.state == PresetTagState.BLACKLIST_PLUS) { - tooltip.add(translate("presets.blacklistPlus.tooltip")); - } - } - } - - }; - - public void itemsLoaded() { - - if (presetsDir != null) { - listbox = new SubsetListBox(presetsDir); - - edit = null; - openListBox = false; - selectedDisplayName.setText(""); - - ItemList.updateFilter.restart(); - PresetsRecipeFilter.blacklist = null; - } - - } - - public PresetsWidget() { - API.addItemFilter(this); - API.addRecipeFilter(new PresetsRecipeFilter()); - ItemList.loadCallbacks.add(this); - } - - protected static void setEditedTag(final PresetTag tag) { - edit = tag; - openListBox = false; - selectedDisplayName.setText(tag != null ? tag.displayName : ""); - ItemList.updateFilter.restart(); - } - - public static void addTag(final PresetTag tag) { - addTag(tag, false); - } - - public static void addTag(final PresetTag tag, boolean sel) { - listbox.addTag(tag, sel); - } - - public static boolean inEditMode() { - return edit != null; - } - - public static boolean isHidden(final ItemStack item) { - - if (edit != null) { - - if (mouseSelection != null && mouseSelection.items.contains(item)) { - return mouseSelection.append != (edit.state == PresetTagState.WHITELIST); - } - - return !edit.matches(item); - } - - for (PresetTag tag : listbox.getSelected()) { - if (!tag.matches(item)) { - return true; - } - } - - return false; - } - - public static void setHidden(final ItemStack stack, final boolean append) { - if (edit == null) { - return; - } - - if (NEIClientUtils.shiftKey()) { - final ArrayList items = ItemPanels.itemPanel.getItems(); - - for (int i = 0; i < items.size(); i++) { - if (stack.getItem().equals(items.get(i).getItem())) { - hideItem(items.get(i), append); - } - } - - } else if (NEIClientUtils.controlKey()) { - final String modId = getModId(stack); - - if (modId == null) { - hideItem(stack, append); - } else { - final ArrayList items = ItemPanels.itemPanel.getItems(); - - for (int i = 0; i < items.size(); i++) { - final String mod = getModId(items.get(i)); - if (mod != null && mod.equals(modId)) { - hideItem(items.get(i), append); - } - } - } - - } else { - hideItem(stack, append); - } - } - - protected static void hideItem(final ItemStack stack, boolean append) { - if (append) { - edit.items.add(stack); - } else { - edit.items.remove(stack); - } - } - - protected static String getModId(final ItemStack stack) { - try { - return GameRegistry.findUniqueIdentifierFor(stack.getItem()).modId; - } catch (Exception ignored) {} - - return null; - } - - public static void loadPresets(final String worldPath) { - presetsDir = new File(CommonUtils.getMinecraftDir(), "saves/NEI/" + worldPath + "/presets"); - - if (!presetsDir.getParentFile().exists()) { - presetsDir.getParentFile().mkdirs(); - } - - if (!presetsDir.exists()) { - presetsDir.mkdirs(); - } - - if (!(new File(presetsDir, "selected.ini")).exists()) { - final File configPresets = new File(NEIClientConfig.configDir, "/presets"); - - if (configPresets.exists()) { - for (File file : configPresets.listFiles()) { - try { - InputStream src = new FileInputStream(file); - OutputStream dst = new FileOutputStream(new File(presetsDir, file.getName())); - - IOUtils.copy(src, dst); - - src.close(); - dst.close(); - } catch (IOException e) {} - } - } - } - - if (LayoutManager.itemsLoaded) { - listbox = new SubsetListBox(presetsDir); - - edit = null; - openListBox = false; - selectedDisplayName.setText(""); - ItemList.updateFilter.restart(); - PresetsRecipeFilter.blacklist = null; - } - - } - - @Override - public void draw(int mx, int my) { - - if (edit != null) { - - selectedState.x = x; - selectedState.y = y; - selectedState.w = selectedState.h = h; - selectedState.draw(mx, my); - - selectedDisplayName.y = y; - selectedDisplayName.h = h; - selectedDisplayName.x = selectedState.x + selectedState.w + 1; - selectedDisplayName.w = w - h * 2 - 5; - selectedDisplayName.draw(mx, my); - - } else { - final List selected = listbox.getSelected(); - final int size = selected.size(); - - selectedValue.text = size > 1 ? translate("presets.label.selected", size) - : (size == 1 ? selected.get(0).displayName : ""); - selectedValue.y = y; - selectedValue.h = h; - selectedValue.w = w - h - 4; - selectedValue.x = x; - - selectedValue.draw(mx, my); - } - - dropdown.y = y; - dropdown.w = dropdown.h = h; - dropdown.x = x + w - dropdown.w; - dropdown.state = edit != null || listbox.getNumSlots() == 0 ? 2 : 0; - dropdown.draw(mx, my); - - if (openListBox) { - final int scrollbarWidth = listbox.hasScrollbar() ? listbox.scrollbarDim().width : 0; - - listbox.setSize( - x, - y + h + 1, - w, - Math.min(((LayoutManager.searchField.y - h - y) / 18) * 18, listbox.contentHeight())); - - listbox.setMargins(0, 0, scrollbarWidth, 0); - listbox.draw(mx, my, 0); - } - } - - @Override - public boolean contains(int px, int py) { - return super.contains(px, py) || openListBox && listbox.contains(px, py); - } - - @Override - public boolean handleClick(int mx, int my, int button) { - - if (openListBox && listbox.contains(mx, my)) { - listbox.mouseClicked(mx, my, button); - return true; - } - - if (edit != null) { - - if (selectedState.contains(mx, my)) { - selectedState.handleClick(mx, my, button); - return true; - } - - if (selectedDisplayName.contains(mx, my)) { - selectedDisplayName.handleClick(mx, my, button); - return true; - } - - } else { - - if (button == 0 && dropdown.contains(mx, my)) { - openListBox = !openListBox; - return true; - } - - openListBox = false; - - if (button == 0 && selectedValue.contains(mx, my)) { - - if (System.currentTimeMillis() - lastclicktime < 400) { // double click - final List selected = listbox.getSelected(); - final int size = selected.size(); - - if (size <= 1) { - setEditedTag(size == 1 ? selected.get(0).copy() : new PresetTag("")); - } - } - - lastclicktime = System.currentTimeMillis(); - } - } - - return contains(mx, my); - } - - @Override - public boolean handleClickExt(int mx, int my, int button) { - - if (edit != null && mouseSelection == null) { - ItemPanelSlot slot = ItemPanels.itemPanel.getSlotMouseOver(mx, my); - - if (slot != null && button != 2) { - mouseSelection = new MouseSelection(slot.slotIndex, button == 0); - return true; - } - } - - return false; - } - - @Override - public boolean handleKeyPress(int keyID, char keyChar) { - if (keyID == Keyboard.KEY_ESCAPE) { - openListBox = false; - } - - if (edit == null) { - return false; - } - - if (selectedDisplayName.focused() && (keyID == Keyboard.KEY_RETURN || keyID == Keyboard.KEY_NUMPADENTER) - && !selectedDisplayName.text().isEmpty()) { - selectedDisplayName.handleKeyPress(keyID, keyChar); - selectedDisplayName.setFocus(false); - listbox.replaceSeleted(edit); - setEditedTag(null); - return true; - } else if (selectedDisplayName.focused()) { - return selectedDisplayName.handleKeyPress(keyID, keyChar); - } else if (keyID == Keyboard.KEY_ESCAPE && !(NEIClientUtils.mc().currentScreen instanceof GuiRecipe)) { - setEditedTag(null); - return true; - } - - return false; - } - - @Override - public void mouseDragged(int mx, int my, int button, long heldTime) { - - if (openListBox) { - listbox.mouseDragged(mx, my, button, heldTime); - } - - if (edit != null && button != 2 && mouseSelection != null) { - final ItemPanelSlot slot = ItemPanels.itemPanel.getSlotMouseOver(mx, my); - - if (slot != null && slot.slotIndex != mouseSelection.endIndex) { - final ItemsGrid grid = ItemPanels.itemPanel.getGrid(); - mouseSelection.endIndex = slot.slotIndex; - mouseSelection.items.clear(); - - final Rectangle4i rec = grid.getItemRect(slot.slotIndex); - final Rectangle4i sel = new Rectangle4i( - Math.min(rec.x, mouseSelection.startX), - Math.min(rec.y, mouseSelection.startY), - Math.max(rec.x, mouseSelection.startX) - Math.min(rec.x, mouseSelection.startX), - Math.max(rec.y, mouseSelection.startY) - Math.min(rec.y, mouseSelection.startY)); - - for (int x = sel.x; x <= sel.x + sel.w; x += ItemsGrid.SLOT_SIZE) { - for (int y = sel.y; y <= sel.y + sel.h; y += ItemsGrid.SLOT_SIZE) { - ItemStack stack = ItemPanels.itemPanel.getStackMouseOver(x, y); - - if (stack != null) { - mouseSelection.items.add(stack); - } - } - } - } - } - } - - @Override - public void mouseUp(int mx, int my, int button) { - - if (openListBox) { - listbox.mouseMovedOrUp(mx, my, button); - } - - if (edit != null && mouseSelection != null) { - ItemPanelSlot hoverSlot = ItemPanels.itemPanel.getSlotMouseOver(mx, my); - - if (hoverSlot != null && hoverSlot.slotIndex == mouseSelection.startIndex) { - setHidden(hoverSlot.item, button == 0); - } else if (!mouseSelection.items.isEmpty()) { - - for (ItemStack stack : mouseSelection.items) { - hideItem(stack, mouseSelection.append); - } - } - - mouseSelection = null; - } - } - - @Override - public boolean onMouseWheel(int i, int mx, int my) { - - if (openListBox && listbox.hasScrollbar() && listbox.contains(mx, my)) { - listbox.scroll(-i); - return true; - } - - return false; - } - - @Override - public void onGuiClick(int mx, int my) { - - if (!contains(mx, my)) { - openListBox = false; - } - - selectedDisplayName.onGuiClick(mx, my); - } - - @Override - public List handleTooltip(int mx, int my, List tooltip) { - - if (edit != null) { - - if (selectedState.contains(mx, my)) { - return selectedState.handleTooltip(mx, my, tooltip); - } - - if (selectedDisplayName.contains(mx, my)) { - return selectedDisplayName.handleTooltip(mx, my, tooltip); - } - - } else { - - if (selectedValue.contains(mx, my)) { - return selectedValue.handleTooltip(mx, my, tooltip); - } - } - - return tooltip; - } - - @Override - public ItemFilter getFilter() { - return this; - } - - @Override - public boolean matches(ItemStack stack) { - - if (edit != null) { - return true; - } - - for (PresetTag tag : listbox.getSelected()) { - if (!tag.matches(stack)) { - return false; - } - } - - return true; - } -} diff --git a/src/main/java/codechicken/nei/RecipeSearchField.java b/src/main/java/codechicken/nei/RecipeSearchField.java index ee3c5abb5..c22ba7a0a 100644 --- a/src/main/java/codechicken/nei/RecipeSearchField.java +++ b/src/main/java/codechicken/nei/RecipeSearchField.java @@ -1,8 +1,8 @@ package codechicken.nei; -import net.minecraft.client.Minecraft; import net.minecraft.util.EnumChatFormatting; +import codechicken.nei.SearchField.GuiSearchField; import codechicken.nei.api.ItemFilter; import codechicken.nei.util.TextHistory; @@ -17,8 +17,7 @@ public RecipeSearchField(String ident) { @Override protected void initInternalTextField() { - field = new FormattedTextField(Minecraft.getMinecraft().fontRenderer, 0, 0, 0, 0); - ((FormattedTextField) field).setFormatter(new SearchTextFormatter(SearchField.searchParser)); + field = new GuiSearchField(); field.setMaxStringLength(maxSearchLength); field.setCursorPositionZero(); } @@ -60,7 +59,7 @@ public String filterText(String s) { } public ItemFilter getFilter() { - return SearchField.getFilter(text()); + return ((GuiSearchField) field).getFilter(); } @Override diff --git a/src/main/java/codechicken/nei/SearchField.java b/src/main/java/codechicken/nei/SearchField.java index b918915f0..0ebf81a47 100644 --- a/src/main/java/codechicken/nei/SearchField.java +++ b/src/main/java/codechicken/nei/SearchField.java @@ -1,7 +1,5 @@ package codechicken.nei; -import static codechicken.nei.NEIClientConfig.world; - import java.util.LinkedList; import java.util.List; import java.util.function.Function; @@ -18,7 +16,6 @@ import codechicken.nei.ItemList.NothingItemFilter; import codechicken.nei.SearchTokenParser.ISearchParserProvider; import codechicken.nei.SearchTokenParser.SearchMode; -import codechicken.nei.api.API; import codechicken.nei.api.ItemFilter; import codechicken.nei.api.ItemFilter.ItemFilterProvider; import codechicken.nei.recipe.StackInfo; @@ -91,28 +88,50 @@ public SearchMode getSearchMode() { } } + public static class GuiSearchField extends FormattedTextField { + + protected final SearchTokenParser searchParser; + + public GuiSearchField() { + this(SearchField.searchParser); + } + + public GuiSearchField(SearchTokenParser searchParser) { + super(Minecraft.getMinecraft().fontRenderer, 0, 0, 0, 0); + this.searchParser = searchParser; + setFormatter(new SearchTextFormatter(searchParser)); + } + + public ItemFilter getFilter() { + return getFilter(getText()); + } + + public ItemFilter getFilter(String filterText) { + return this.searchParser.getFilter(filterText); + } + + } + @Deprecated public static List searchProviders = new LinkedList<>(); - public static SearchTokenParser searchParser = new SearchTokenParser(); + public static final SearchTokenParser searchParser = new SearchTokenParser(); private static final TextHistory history = new TextHistory(); private boolean isVisible = true; private long lastclicktime; public SearchField(String ident) { super(ident); - API.addItemFilter(this); } @Override protected void initInternalTextField() { - field = new FormattedTextField(Minecraft.getMinecraft().fontRenderer, 0, 0, 0, 0); - ((FormattedTextField) field).setFormatter(new SearchTextFormatter(SearchField.searchParser)); + field = new GuiSearchField(); field.setMaxStringLength(maxSearchLength); field.setCursorPositionZero(); } public static boolean searchInventories() { - return world.nbt.getBoolean("searchinventories"); + return NEIClientConfig.world.nbt.getBoolean("searchinventories"); } public boolean isVisible() { @@ -137,10 +156,34 @@ public void draw(int mousex, int mousey) { super.draw(mousex, mousey); if (searchInventories()) { - GuiDraw.drawGradientRect(x - 1, y - 1, 1, h + 2, 0xFFFFFF00, 0xFFC0B000); // Left - GuiDraw.drawGradientRect(x - 1, y - 1, w + 2, 1, 0xFFFFFF00, 0xFFC0B000); // Top - GuiDraw.drawGradientRect(x + w, y - 1, 1, h + 2, 0xFFFFFF00, 0xFFC0B000); // Left - GuiDraw.drawGradientRect(x - 1, y + h, w + 2, 1, 0xFFFFFF00, 0xFFC0B000); // Bottom + GuiDraw.drawGradientRect( + field.xPosition - 1, + field.yPosition - 1, + 1, + field.height + 2, + 0xFFFFFF00, + 0xFFC0B000); // Left + GuiDraw.drawGradientRect( + field.xPosition - 1, + field.yPosition - 1, + field.width + 2, + 1, + 0xFFFFFF00, + 0xFFC0B000); // Top + GuiDraw.drawGradientRect( + field.xPosition + field.width, + field.yPosition - 1, + 1, + field.height + 2, + 0xFFFFFF00, + 0xFFC0B000); // Left + GuiDraw.drawGradientRect( + field.xPosition - 1, + field.yPosition + field.height, + field.width + 2, + 1, + 0xFFFFFF00, + 0xFFC0B000); // Bottom } } @@ -268,7 +311,7 @@ public static String getEscapedSearchText(String text) { @Override public ItemFilter getFilter() { - return getFilter(text()); + return ((GuiSearchField) field).getFilter(); } public static ItemFilter getFilter(String filterText) { diff --git a/src/main/java/codechicken/nei/VisiblityData.java b/src/main/java/codechicken/nei/VisiblityData.java index e52d2dc3c..fc5a55932 100644 --- a/src/main/java/codechicken/nei/VisiblityData.java +++ b/src/main/java/codechicken/nei/VisiblityData.java @@ -15,10 +15,6 @@ public class VisiblityData { * Subset dropdown */ public boolean showSubsetDropdown = true; - /** - * Presets dropdown - */ - public boolean showPresetsDropdown = true; /** * Item and search section */ @@ -44,7 +40,6 @@ public class VisiblityData { public void translateDependancies() { if (!showNEI) showWidgets = false; if (!showWidgets) showItemSection = showUtilityButtons = false; - if (!showItemSection) - showBookmarkPanel = showSubsetDropdown = showPresetsDropdown = showSearchSection = showItemPanel = false; + if (!showItemSection) showBookmarkPanel = showSubsetDropdown = showSearchSection = showItemPanel = false; } } diff --git a/src/main/java/codechicken/nei/config/GuiItemSorter.java b/src/main/java/codechicken/nei/config/GuiItemSorter.java index 8c31555d9..d8f0e78e8 100644 --- a/src/main/java/codechicken/nei/config/GuiItemSorter.java +++ b/src/main/java/codechicken/nei/config/GuiItemSorter.java @@ -113,8 +113,13 @@ public void updateScreen() { if (nslot != list.indexOf(dragged.e)) { list.remove(dragged.e); list.add(nslot, dragged.e); + opt.getTag().setValue(ItemSorter.getSaveString(list)); - if (opt.activeTag() == opt.getTag()) ItemSorter.list = new ArrayList<>(list); + LayoutManager.markItemsDirty(); + + if (opt.activeTag() == opt.getTag()) { + ItemSorter.list = new ArrayList<>(list); + } } } } diff --git a/src/main/java/codechicken/nei/config/preset/CheckboxButton.java b/src/main/java/codechicken/nei/config/preset/CheckboxButton.java new file mode 100644 index 000000000..a675efbde --- /dev/null +++ b/src/main/java/codechicken/nei/config/preset/CheckboxButton.java @@ -0,0 +1,53 @@ +package codechicken.nei.config.preset; + +import static codechicken.lib.gui.GuiDraw.drawStringC; + +import org.lwjgl.opengl.GL11; + +import codechicken.nei.Button; +import codechicken.nei.LayoutManager; + +public abstract class CheckboxButton extends Button { + + protected boolean checked = false; + + public CheckboxButton(String s) { + super(s); + } + + public void setChecked(boolean checked) { + this.checked = checked; + this.onChange(); + } + + public boolean isChecked() { + return this.checked; + } + + @Override + public void draw(int mousex, int mousey) { + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glColor4f(1, 1, 1, 1); + int tex; + + if ((this.state & 0x3) == 2) { + tex = 0; + } else if ((this.state & 0x4) == 0 && contains(mousex, mousey) || (this.state & 0x3) == 1) { + tex = 2; + } else { + tex = 1; + } + + int colour = tex == 2 ? 0xffffa0 : tex == 0 ? 0x601010 : 0xe0e0e0; + LayoutManager.drawButtonBackground(x, y, w, h, true, isChecked() ? 0 : tex); + drawStringC(getRenderLabel(), x + w / 2, y + (h - 8) / 2, colour); + } + + public boolean onButtonPress(boolean rightclick) { + this.setChecked(!isChecked()); + return true; + } + + protected abstract void onChange(); + +} diff --git a/src/main/java/codechicken/nei/config/preset/GuiPresetList.java b/src/main/java/codechicken/nei/config/preset/GuiPresetList.java new file mode 100644 index 000000000..972fdea0d --- /dev/null +++ b/src/main/java/codechicken/nei/config/preset/GuiPresetList.java @@ -0,0 +1,239 @@ +package codechicken.nei.config.preset; + +import static codechicken.lib.gui.GuiDraw.getStringWidth; + +import java.awt.Rectangle; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; + +import org.lwjgl.opengl.GL11; + +import codechicken.core.gui.GuiCCButton; +import codechicken.lib.gui.GuiDraw; +import codechicken.lib.vec.Rectangle4i; +import codechicken.nei.LayoutManager; +import codechicken.nei.NEIClientUtils; +import codechicken.nei.PresetsList; +import codechicken.nei.PresetsList.Preset; +import codechicken.nei.config.GuiOptionPane; +import codechicken.nei.config.Option; + +public class GuiPresetList extends GuiOptionPane { + + private final Option opt; + protected final static int SLOT_HEIGHT = 24; + protected final static int BUTTON_HEIGHT = 20; + protected GuiCCButton createButton; + protected int sortingItemIndex = -1; + + public GuiPresetList(Option opt) { + this.opt = opt; + this.createButton = new GuiCCButton(0, 2, 0, 16, NEIClientUtils.translate("presets.new")) + .setActionCommand("create"); + } + + @Override + public void initGui() { + super.initGui(); + this.createButton.width = getStringWidth(this.createButton.text) + 15; + this.createButton.x = width - this.createButton.width - 15; + } + + @Override + public void addWidgets() { + super.addWidgets(); + add(this.createButton); + } + + @Override + public void actionPerformed(String ident, Object... params) { + super.actionPerformed(ident, params); + + if (ident.equals("create")) { + cretePreset(); + } + } + + protected void mouseClicked(int x, int y, int button) { + super.mouseClicked(x, y, button); + + } + + @Override + protected void mouseMovedOrUp(int x, int y, int button) { + super.mouseMovedOrUp(x, y, button); + + if (sortingItemIndex >= 0) { + PresetsList.savePresets(); + } else if (button == 0) { + int slot = getSlotMouseOver(x, y); + + if (slot != -1) { + Rectangle w = pane.windowBounds(); + int mx = x - w.x; + + if (mx <= BUTTON_HEIGHT) { + Preset preset = PresetsList.presets.get(slot); + preset.enabled = !preset.enabled; + PresetsList.savePresets(); + } else if (mx >= 26 && mx <= w.width - 26 * 2) { + openPreset(slot); + } else if (mx >= w.width - BUTTON_HEIGHT) { + PresetsList.presets.remove(slot); + PresetsList.savePresets(); + } + } + } + + sortingItemIndex = -1; + } + + @Override + protected void mouseClickMove(int x, int y, int button, long time) { + super.mouseClickMove(x, y, button, time); + + if (sortingItemIndex == -1) { + Rectangle w = pane.windowBounds(); + int mx = x - w.x; + + if (mx >= 26 && mx <= w.width - 26 * 2) { + sortingItemIndex = getSlotMouseOver(x, y); + } else { + sortingItemIndex = -2; + } + } else if (sortingItemIndex >= 0) { + Rectangle w = pane.windowBounds(); + + if (y >= w.y && y <= w.y + w.height) { + int my = y + pane.scrolledPixels() - w.y; + int slot = my / SLOT_HEIGHT; + + if (my % SLOT_HEIGHT < BUTTON_HEIGHT && slot < PresetsList.presets.size() + && slot != -1 + && slot != sortingItemIndex) { + PresetsList.presets.add(slot, PresetsList.presets.remove(sortingItemIndex)); + sortingItemIndex = slot; + } + } + } + } + + protected int getSlotMouseOver(int x, int y) { + Rectangle w = pane.windowBounds(); + + if (x >= w.x && x <= w.x + w.width && y >= w.y && y <= w.y + w.height) { + int my = y + pane.scrolledPixels() - w.y; + int slot = my / SLOT_HEIGHT; + + if (my % SLOT_HEIGHT < BUTTON_HEIGHT && slot < PresetsList.presets.size()) { + return slot; + } + } + + return -1; + } + + protected void cretePreset() { + Minecraft.getMinecraft().displayGuiScreen(new GuiPresetSettings(this, -1)); + } + + protected void openPreset(int slot) { + Minecraft.getMinecraft().displayGuiScreen(new GuiPresetSettings(this, slot)); + } + + @Override + public int contentHeight() { + return PresetsList.presets.size() * SLOT_HEIGHT; + } + + @Override + public void drawContent(int mx, int my, float frame) { + int scrolled = pane.scrolledPixels(); + Rectangle w = pane.windowBounds(); + int y = 0; + + for (int slot = 0; slot < PresetsList.presets.size(); ++slot) { + + if (y + SLOT_HEIGHT > scrolled && y < scrolled + w.height) { + + if (sortingItemIndex != slot) { + drawSlot(w, slot, w.x, w.y + y - scrolled, mx, my - y); + } + + } + + y += SLOT_HEIGHT; + } + + if (sortingItemIndex >= 0) { + drawSlot(w, sortingItemIndex, w.x, w.y + my - scrolled - BUTTON_HEIGHT / 2, mx, my - y); + } + } + + protected void drawSlot(Rectangle w, int slot, int x, int y, int mx, int my) { + final Rectangle4i delete = new Rectangle4i(x + w.width - BUTTON_HEIGHT, y, BUTTON_HEIGHT, BUTTON_HEIGHT); + final Rectangle4i enabled = new Rectangle4i(x, y, BUTTON_HEIGHT, BUTTON_HEIGHT); + final Rectangle4i option = new Rectangle4i(x + 26, y, w.width - 26 * 2, BUTTON_HEIGHT); + + final int enabledState = y + my >= w.y && y + my <= w.y + w.height && enabled.contains(x + mx, y + my) ? 2 : 1; + final int optionState = y + my >= w.y && y + my <= w.y + w.height && option.contains(x + mx, y + my) ? 2 : 1; + final int deleteState = y + my >= w.y && y + my <= w.y + w.height && delete.contains(x + mx, y + my) ? 2 : 1; + final Preset preset = PresetsList.presets.get(slot); + + final String displayName = preset.name; + final String modeName; + + switch (preset.mode) { + case HIDE: + modeName = NEIClientUtils.translate("presets.mode.hide.char"); + break; + case REMOVE: + modeName = NEIClientUtils.translate("presets.mode.remove.char"); + break; + case GROUP: + modeName = NEIClientUtils.translate("presets.mode.group.char"); + break; + default: + modeName = "?"; + break; + } + + // enabled icon + GL11.glColor4f(1, 1, 1, 1); + LayoutManager.drawButtonBackground( + enabled.x, + enabled.y, + enabled.w, + enabled.h, + true, + preset.enabled ? 0 : enabledState); + GuiDraw.drawString("✔", enabled.x + 7, enabled.y + 6, enabledState == 2 ? 0xFFFFFFA0 : 0xFFE0E0E0); + + // preset name + GL11.glColor4f(1, 1, 1, 1); + LayoutManager.drawButtonBackground(option.x, option.y, option.w, option.h, true, optionState); + GuiDraw.drawString("⋮⋮", option.x + 4, option.y + 5, optionState == 2 ? 0xFFFFFFA0 : 0xFFE0E0E0); + GuiDraw.drawString( + NEIClientUtils.cropText(this.fontRendererObj, modeName + ": " + displayName, option.w - 16), + option.x + 6 + 4, + option.y + 6, + optionState == 2 ? 0xFFFFFFA0 : 0xFFE0E0E0); + + // remove icon + GL11.glColor4f(1, 1, 1, 1); + LayoutManager.drawButtonBackground(delete.x, delete.y, delete.w, delete.h, true, deleteState); + GuiDraw.drawString("✕", delete.x + 7, delete.y + 6, deleteState == 2 ? 0xFFFFFFA0 : 0xFFE0E0E0); + } + + @Override + public GuiScreen getParentScreen() { + return opt.slot.getGui(); + } + + @Override + public String getTitle() { + return opt.translateN(opt.name); + } + +} diff --git a/src/main/java/codechicken/nei/config/preset/GuiPresetSettings.java b/src/main/java/codechicken/nei/config/preset/GuiPresetSettings.java new file mode 100644 index 000000000..42d91a4ad --- /dev/null +++ b/src/main/java/codechicken/nei/config/preset/GuiPresetSettings.java @@ -0,0 +1,228 @@ +package codechicken.nei.config.preset; + +import java.util.LinkedList; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.Tessellator; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.util.Rectangle; + +import codechicken.core.gui.GuiScreenWidget; +import codechicken.nei.Button; +import codechicken.nei.NEIClientUtils; +import codechicken.nei.PresetsList; +import codechicken.nei.PresetsList.Preset; +import codechicken.nei.config.OptionScrollPane; +import codechicken.nei.guihook.GuiContainerManager; + +public class GuiPresetSettings extends GuiScreenWidget { + + protected final Preset preset; + protected final GuiScreen parent; + protected final int slotIndex; + + public int marginleft; + public int margintop; + public int marginright; + public int marginbottom; + + protected LeftPanel leftPanel; + protected RightPanel rightPanel; + + protected Button backButton; + protected Button saveButton; + + public GuiPresetSettings(GuiScreen parent, int slotIndex) { + this.parent = parent; + this.setMargins(6, 20, 6, 30); + + this.slotIndex = slotIndex; + + if (slotIndex == -1) { + preset = new Preset(); + } else { + preset = PresetsList.presets.get(slotIndex).copy(); + } + + leftPanel = new LeftPanel(preset) { + + @Override + protected void onItemsChanges() { + leftPanel.grid.restartFilter(); + } + }; + rightPanel = new RightPanel(preset, slotIndex) { + + @Override + protected void onItemsChanges() { + leftPanel.grid.restartFilter(); + } + }; + + backButton = new Button(NEIClientUtils.translate("options.back")) { + + public boolean onButtonPress(boolean rightclick) { + Minecraft.getMinecraft().displayGuiScreen(parent); + return true; + } + }; + + saveButton = new Button(NEIClientUtils.translate("options.save")) { + + public boolean onButtonPress(boolean rightclick) { + if (state == 0) { + + if (slotIndex == -1) { + PresetsList.presets.add(preset); + } else { + PresetsList.presets.set(slotIndex, preset); + } + + PresetsList.savePresets(); + Minecraft.getMinecraft().displayGuiScreen(parent); + + return true; + } + return false; + } + }; + + add(this.leftPanel); + add(this.rightPanel); + } + + protected void setMargins(int left, int top, int right, int bottom) { + this.marginleft = left; + this.margintop = top; + this.marginright = right; + this.marginbottom = bottom; + } + + protected Rectangle windowBounds() { + return new Rectangle( + this.marginleft, + this.margintop, + this.width - this.marginleft - this.marginright, + this.height - this.margintop - this.marginbottom); + } + + @Override + public boolean doesGuiPauseGame() { + return true; + } + + @Override + public void updateScreen() { + final Rectangle bounds = windowBounds(); + final int panelWidth = bounds.getWidth() * 2 / 5; + + this.leftPanel.setSize(bounds.getX(), bounds.getY() + 4, panelWidth, bounds.getHeight() - 6); + this.rightPanel.setSize( + bounds.getX() + bounds.getWidth() - panelWidth, + bounds.getY() + 4, + panelWidth, + bounds.getHeight() - 6); + + final int w = Math.min(200, width - 40); + + backButton.w = w / 2 - 3; + backButton.h = 20; + backButton.x = (width - w) / 2; + backButton.y = height - 25; + + saveButton.state = preset.name.isEmpty() || preset.mode == null || preset.items.isEmpty() ? 2 : 0; + + saveButton.w = w / 2 - 3; + saveButton.h = 20; + saveButton.x = width / 2 + 3; + saveButton.y = height - 25; + + super.updateScreen(); + } + + @Override + public void resize() { + this.guiLeft = this.guiTop = 0; + } + + @Override + public void drawScreen(int mousex, int mousey, float f) { + Rectangle bounds = windowBounds(); + drawDefaultBackground(); + + drawOverlay(bounds.getY(), bounds.getHeight(), width, zLevel); + drawCenteredString( + Minecraft.getMinecraft().fontRenderer, + NEIClientUtils.translate(this.slotIndex == -1 ? "presets.create" : "presets.update"), + width / 2, + 6, + -1); + + backButton.draw(mousex, mousey); + saveButton.draw(mousex, mousey); + + super.drawScreen(mousex, mousey, f); + + List tooltip = new LinkedList<>(); + tooltip = leftPanel.handleTooltip(mousex, mousey, tooltip); + tooltip = rightPanel.handleTooltip(mousex, mousey, tooltip); + + GuiContainerManager.drawPagedTooltip(Minecraft.getMinecraft().fontRenderer, mousex + 12, mousey - 12, tooltip); + } + + @Override + protected void mouseClicked(int x, int y, int button) { + super.mouseClicked(x, y, button); + + if (backButton.contains(x, y)) { + backButton.handleClick(x, y, button); + } + + if (saveButton.contains(x, y)) { + saveButton.handleClick(x, y, button); + } + } + + public static void drawOverlay(int y, int height, int screenwidth, float zLevel) { + OptionScrollPane.drawOverlayTex(0, 0, screenwidth, y, zLevel); + OptionScrollPane.drawOverlayTex(0, y + height, screenwidth, screenwidth - y - height, zLevel); + OptionScrollPane.drawOverlayGrad(0, screenwidth, y, y + 4, zLevel); + OptionScrollPane.drawOverlayGrad(0, screenwidth, y + height, y + height - 4, zLevel); + } + + public static void drawOverlayTex(int x, int y, int w, int h, float zLevel) { + GL11.glColor4f(1, 1, 1, 1); + Minecraft.getMinecraft().renderEngine.bindTexture(Gui.optionsBackground); + Tessellator t = Tessellator.instance; + t.startDrawingQuads(); + t.addVertexWithUV(x, y, zLevel, 0, 0); + t.addVertexWithUV(x, y + h, zLevel, 0, h / 16D); + t.addVertexWithUV(x + w, y + h, zLevel, w / 16D, h / 16D); + t.addVertexWithUV(x + w, y, zLevel, w / 16D, 0); + t.draw(); + } + + public static void drawOverlayGrad(int x1, int x2, int y1, int y2, float zLevel) { + GL11.glDisable(GL11.GL_TEXTURE_2D); + GL11.glDisable(GL11.GL_CULL_FACE); + GL11.glEnable(GL11.GL_BLEND); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GL11.glShadeModel(GL11.GL_SMOOTH); + Tessellator t = Tessellator.instance; + t.startDrawingQuads(); + t.setColorRGBA_I(0, 255); + t.addVertex(x2, y1, zLevel); + t.addVertex(x1, y1, zLevel); + t.setColorRGBA_I(0, 0); + t.addVertex(x1, y2, zLevel); + t.addVertex(x2, y2, zLevel); + t.draw(); + GL11.glDisable(GL11.GL_BLEND); + GL11.glEnable(GL11.GL_CULL_FACE); + GL11.glEnable(GL11.GL_TEXTURE_2D); + } +} diff --git a/src/main/java/codechicken/nei/config/preset/LeftPanel.java b/src/main/java/codechicken/nei/config/preset/LeftPanel.java new file mode 100644 index 000000000..4d5928254 --- /dev/null +++ b/src/main/java/codechicken/nei/config/preset/LeftPanel.java @@ -0,0 +1,327 @@ +package codechicken.nei.config.preset; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.item.ItemStack; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; + +import codechicken.core.gui.GuiWidget; +import codechicken.lib.gui.GuiDraw; +import codechicken.lib.vec.Rectangle4i; +import codechicken.nei.ItemList.AllMultiItemFilter; +import codechicken.nei.ItemPanel.ItemPanelSlot; +import codechicken.nei.ItemsGrid; +import codechicken.nei.Label; +import codechicken.nei.NEIClientUtils; +import codechicken.nei.PresetsList.Preset; +import codechicken.nei.PresetsList.PresetMode; +import codechicken.nei.TextField; +import codechicken.nei.api.ItemFilter; +import codechicken.nei.api.ItemInfo; +import codechicken.nei.guihook.GuiContainerManager; +import codechicken.nei.guihook.IContainerTooltipHandler; + +public class LeftPanel extends GuiWidget { + + protected static class MouseSelection { + + public int startX = -1; + public int startY = -1; + public int startIndex = -1; + public int endIndex = -1; + + public Set items = new HashSet<>(); + + public MouseSelection(int slotIndex, Rectangle4i rec) { + endIndex = slotIndex; + startIndex = slotIndex; + startX = rec.x; + startY = rec.y; + } + } + + protected static final int INPUT_HEIGHT = 20; + + public final PresetItemsGrid grid = new PresetItemsGrid() { + + @Override + protected ItemFilter getFilter() { + Set identifiers = preset.items; + return new AllMultiItemFilter( + item -> !ItemInfo.hiddenItems.contains(item), + item -> identifiers.contains(Preset.getIdentifier(item))); + } + + @Override + protected boolean isSelected(ItemStack stack) { + return mouseSelection != null && mouseSelection.items.contains(stack); + } + }; + + protected Label nameLabel = new Label(NEIClientUtils.translate("presets.name"), false); + + protected TextField nameField = new TextField("name") { + + @Override + public void onTextChange(String oldText) { + preset.name = text(); + } + }; + + protected Label modeLabel = new Label(NEIClientUtils.translate("presets.mode"), false); + + protected CheckboxButton modeHide; + protected CheckboxButton modeRemove; + protected CheckboxButton modeGroup; + + protected CheckboxButton enabledButton; + + protected final Preset preset; + protected MouseSelection mouseSelection; + + public LeftPanel(Preset preset) { + super(1, 0, 2, 2); + + this.preset = preset; + + nameField.setText(preset.name); + + modeHide = new CheckboxButton(NEIClientUtils.translate("presets.mode.hide")) { + + @Override + public boolean isChecked() { + return preset.mode == PresetMode.HIDE; + } + + @Override + protected void onChange() { + preset.mode = this.checked ? PresetMode.HIDE : null; + } + }; + + modeRemove = new CheckboxButton(NEIClientUtils.translate("presets.mode.remove")) { + + @Override + public boolean isChecked() { + return preset.mode == PresetMode.REMOVE; + } + + @Override + protected void onChange() { + preset.mode = this.checked ? PresetMode.REMOVE : null; + } + }; + + modeGroup = new CheckboxButton(NEIClientUtils.translate("presets.mode.group")) { + + @Override + public boolean isChecked() { + return preset.mode == PresetMode.GROUP; + } + + @Override + protected void onChange() { + preset.mode = this.checked ? PresetMode.GROUP : null; + } + }; + + enabledButton = new CheckboxButton(NEIClientUtils.translate("presets.enabled")) { + + @Override + public boolean isChecked() { + return preset.enabled; + } + + @Override + protected void onChange() { + preset.enabled = this.checked; + } + }; + + grid.restartFilter(); + } + + public void mouseClicked(int x, int y, int button) { + grid.mouseClicked(x, y, button); + + if (modeHide.contains(x, y)) { + modeHide.handleClick(x, y, button); + } + + if (modeRemove.contains(x, y)) { + modeRemove.handleClick(x, y, button); + } + + if (modeGroup.contains(x, y)) { + modeGroup.handleClick(x, y, button); + } + + if (enabledButton.contains(x, y)) { + enabledButton.handleClick(x, y, button); + } + + if (nameField.contains(x, y)) { + nameField.handleClick(x, y, button); + } else { + nameField.onGuiClick(x, y); + } + + if (mouseSelection == null && button == 0) { + ItemPanelSlot slot = grid.getSlotMouseOver(x, y); + + if (slot != null) { + mouseSelection = new MouseSelection(slot.slotIndex, grid.getItemRect(slot.slotIndex)); + } + } + + } + + protected void onItemsChanges() {} + + public void mouseMovedOrUp(int x, int y, int button) { + nameField.mouseUp(x, y, button); + + if (mouseSelection != null && button == 0) { + ItemPanelSlot hoverSlot = grid.getSlotMouseOver(x, y); + + if (hoverSlot != null && hoverSlot.slotIndex == mouseSelection.startIndex) { + preset.items.remove(Preset.getIdentifier(hoverSlot.item)); + } else if (!mouseSelection.items.isEmpty()) { + + for (ItemStack stack : mouseSelection.items) { + preset.items.remove(Preset.getIdentifier(stack)); + } + } + + mouseSelection = null; + onItemsChanges(); + } + } + + public void mouseDragged(int x, int y, int button, long time) { + nameField.mouseDragged(x, y, button, time); + + if (mouseSelection != null && button == 0) { + final ItemPanelSlot slot = grid.getSlotMouseOver(x, y); + + if (slot != null && slot.slotIndex != mouseSelection.endIndex) { + mouseSelection.endIndex = slot.slotIndex; + mouseSelection.items.clear(); + + final Rectangle4i rec = grid.getItemRect(slot.slotIndex); + final Rectangle4i sel = new Rectangle4i( + Math.min(rec.x, mouseSelection.startX), + Math.min(rec.y, mouseSelection.startY), + Math.max(rec.x, mouseSelection.startX) - Math.min(rec.x, mouseSelection.startX), + Math.max(rec.y, mouseSelection.startY) - Math.min(rec.y, mouseSelection.startY)); + + for (int ix = sel.x; ix <= sel.x + sel.w; ix += ItemsGrid.SLOT_SIZE) { + for (int iy = sel.y; iy <= sel.y + sel.h; iy += ItemsGrid.SLOT_SIZE) { + ItemPanelSlot over = grid.getSlotMouseOver(ix, iy); + + if (over != null) { + mouseSelection.items.add(over.item); + } + } + } + } + } + } + + public void update() { + int CHECKBOX_WIDTH = (this.width - 12) / 3; + nameLabel.x = this.x + 2; + nameLabel.y = this.y + 4; + nameLabel.w = this.width; + nameLabel.h = 10; + + nameField.x = this.x; + nameField.y = nameLabel.y + nameLabel.h; + nameField.w = this.width - CHECKBOX_WIDTH - 6; + nameField.h = INPUT_HEIGHT; + + enabledButton.x = nameField.x + nameField.w + 6; + enabledButton.y = nameField.y; + enabledButton.w = CHECKBOX_WIDTH; + enabledButton.h = INPUT_HEIGHT; + + modeLabel.x = this.x + 2; + modeLabel.y = nameField.y + nameField.h + 6; + modeLabel.w = this.width; + modeLabel.h = 10; + + modeHide.w = modeRemove.w = modeGroup.w = CHECKBOX_WIDTH; + modeHide.h = modeRemove.h = modeGroup.h = INPUT_HEIGHT; + modeHide.y = modeRemove.y = modeGroup.y = modeLabel.y + modeLabel.h; + + modeHide.x = this.x + 1; + modeRemove.x = modeHide.x + modeHide.w + 6; + modeGroup.x = modeRemove.x + modeRemove.w + 6; + + grid.setGridSize( + this.x + 1, + modeHide.y + modeHide.h + 2, + this.width - 2, + this.height - (modeHide.y + modeHide.h + 2)); + grid.refresh(null); + } + + public void draw(int mousex, int mousey, float frame) { + nameLabel.draw(mousex, mousey); + nameField.draw(mousex, mousey); + + modeLabel.draw(mousex, mousey); + modeHide.draw(mousex, mousey); + modeRemove.draw(mousex, mousey); + modeGroup.draw(mousex, mousey); + + enabledButton.draw(mousex, mousey); + + RenderHelper.enableGUIStandardItemLighting(); + GL11.glPushMatrix(); + GL11.glEnable(GL12.GL_RESCALE_NORMAL); + + grid.draw(mousex, mousey); + + GL11.glDisable(GL12.GL_RESCALE_NORMAL); + GL11.glPopMatrix(); + RenderHelper.enableStandardItemLighting(); + } + + public List handleTooltip(int mousex, int mousey, List tooltip) { + ItemPanelSlot over = grid.getSlotMouseOver(mousex, mousey); + + if (over != null) { + tooltip = GuiContainerManager.itemDisplayNameMultiline(over.item, null, true); + + synchronized (GuiContainerManager.tooltipHandlers) { + for (IContainerTooltipHandler handler : GuiContainerManager.tooltipHandlers) { + tooltip = handler.handleItemTooltip(null, over.item, mousex, mousey, tooltip); + } + } + + if (tooltip.size() > 0) { + tooltip.set(0, tooltip.get(0) + GuiDraw.TOOLTIP_LINESPACE); // add space after 'title' + } + + } else if (modeHide.contains(mousex, mousey)) { + tooltip.add(NEIClientUtils.translate("presets.mode.hide.tip")); + } else if (modeRemove.contains(mousex, mousey)) { + tooltip.add(NEIClientUtils.translate("presets.mode.remove.tip")); + } else if (modeGroup.contains(mousex, mousey)) { + tooltip.add(NEIClientUtils.translate("presets.mode.group.tip")); + } + + return tooltip; + } + + public void keyTyped(char c, int keycode) { + nameField.handleKeyPress(keycode, c); + } + +} diff --git a/src/main/java/codechicken/nei/config/preset/PresetItemsGrid.java b/src/main/java/codechicken/nei/config/preset/PresetItemsGrid.java new file mode 100644 index 000000000..b745fce45 --- /dev/null +++ b/src/main/java/codechicken/nei/config/preset/PresetItemsGrid.java @@ -0,0 +1,167 @@ +package codechicken.nei.config.preset; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.item.ItemStack; + +import codechicken.lib.gui.GuiDraw; +import codechicken.lib.vec.Rectangle4i; +import codechicken.nei.Button; +import codechicken.nei.ItemList; +import codechicken.nei.ItemPanel.ItemPanelSlot; +import codechicken.nei.ItemSorter; +import codechicken.nei.ItemsGrid; +import codechicken.nei.Label; +import codechicken.nei.RestartableTask; +import codechicken.nei.api.ItemFilter; + +public abstract class PresetItemsGrid extends ItemsGrid { + + protected static final int BUTTON_SIZE = 16; + + public Button pagePrev; + public Label pageLabel; + public Button pageNext; + + protected ArrayList newItems; + + protected final RestartableTask updateFilter = new RestartableTask("NEI Presets Item Filtering") { + + @Override + public void execute() { + ArrayList filtered; + ItemFilter filter = getFilter(); + + try { + filtered = ItemList.forkJoinPool.submit( + () -> ItemList.items.parallelStream().filter(filter::matches) + .collect(Collectors.toCollection(ArrayList::new))) + .get(); + } catch (InterruptedException | ExecutionException e) { + filtered = new ArrayList<>(); + e.printStackTrace(); + stop(); + } + + if (interrupted()) return; + ItemSorter.sort(filtered); + if (interrupted()) return; + updateItemList(filtered); + } + }; + + public PresetItemsGrid() { + + pageLabel = new Label("0/0", true); + + pagePrev = new Button("<") { + + public boolean onButtonPress(boolean rightclick) { + + if (rightclick) { + setPage(0); + } else { + shiftPage(-1); + } + + return true; + } + }; + + pageNext = new Button(">") { + + public boolean onButtonPress(boolean rightclick) { + + if (rightclick) { + setPage(getNumPages() - 1); + } else { + shiftPage(1); + } + + return true; + } + }; + + } + + protected String getLabelText() { + return String.format("%d/%d", getPage(), Math.max(1, getNumPages())); + } + + @Override + public void setGridSize(int mleft, int mtop, int w, int h) { + pageLabel.text = getLabelText(); + + pagePrev.w = pageNext.w = BUTTON_SIZE; + pagePrev.h = pageNext.h = BUTTON_SIZE; + pagePrev.y = pageNext.y = mtop; + + pagePrev.x = mleft; + pageNext.x = mleft + w - pageNext.w; + + pageLabel.x = mleft + w / 2; + pageLabel.y = pagePrev.y + 5; + + super.setGridSize(mleft, mtop + BUTTON_SIZE + 2, w, h - BUTTON_SIZE - 4); + } + + @Override + public void draw(int mousex, int mousey) { + pagePrev.draw(mousex, mousey); + pageNext.draw(mousex, mousey); + pageLabel.draw(mousex, mousey); + + super.draw(mousex, mousey); + } + + public void mouseClicked(int x, int y, int button) { + + if (pagePrev.contains(x, y)) { + pagePrev.handleClick(x, y, button); + } + + if (pageNext.contains(x, y)) { + pageNext.handleClick(x, y, button); + } + } + + public void restartFilter() { + updateFilter.restart(); + } + + protected void updateItemList(List newItems) { + this.newItems = new ArrayList<>(newItems); + } + + @Override + public void refresh(GuiContainer gui) { + + if (this.newItems != null) { + this.realItems = this.newItems; + this.newItems = null; + onItemsChanged(); + } + + super.refresh(gui); + } + + @Override + protected void beforeDrawSlot(@Nullable ItemPanelSlot focused, int slotIdx, Rectangle4i rect) { + if (isSelected(getItem(slotIdx))) { + GuiDraw.drawRect(rect.x, rect.y, rect.w, rect.h, 0xee555555); + } else { + super.beforeDrawSlot(focused, slotIdx, rect); + } + } + + protected abstract boolean isSelected(ItemStack stack); + + protected abstract ItemFilter getFilter(); + +} diff --git a/src/main/java/codechicken/nei/config/preset/RightPanel.java b/src/main/java/codechicken/nei/config/preset/RightPanel.java new file mode 100644 index 000000000..a223230a8 --- /dev/null +++ b/src/main/java/codechicken/nei/config/preset/RightPanel.java @@ -0,0 +1,387 @@ +package codechicken.nei.config.preset; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumChatFormatting; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; + +import codechicken.core.gui.GuiWidget; +import codechicken.lib.gui.GuiDraw; +import codechicken.lib.vec.Rectangle4i; +import codechicken.nei.ItemList; +import codechicken.nei.ItemList.AllMultiItemFilter; +import codechicken.nei.ItemList.NegatedItemFilter; +import codechicken.nei.ItemPanel.ItemPanelSlot; +import codechicken.nei.ItemsGrid; +import codechicken.nei.NEIClientConfig; +import codechicken.nei.NEIClientUtils; +import codechicken.nei.PresetsList; +import codechicken.nei.PresetsList.Preset; +import codechicken.nei.SearchField.GuiSearchField; +import codechicken.nei.TextField; +import codechicken.nei.api.ItemFilter; +import codechicken.nei.api.ItemInfo; +import codechicken.nei.guihook.GuiContainerManager; +import codechicken.nei.guihook.IContainerTooltipHandler; +import codechicken.nei.util.TextHistory; +import cpw.mods.fml.common.registry.GameRegistry; + +public class RightPanel extends GuiWidget { + + protected static class MouseSelection { + + public int startX = -1; + public int startY = -1; + public int startIndex = -1; + public int endIndex = -1; + + public Set items = new HashSet<>(); + public boolean append = true; + + public MouseSelection(int slotIndex, Rectangle4i rec, boolean ppnd) { + append = ppnd; + endIndex = slotIndex; + startIndex = slotIndex; + startX = rec.x; + startY = rec.y; + } + } + + protected static abstract class PresetSearchField extends TextField { + + private static final TextHistory history = new TextHistory(); + + public PresetSearchField(String ident) { + super(ident); + } + + @Override + protected void initInternalTextField() { + field = new GuiSearchField(); + field.setMaxStringLength(maxSearchLength); + field.setCursorPositionZero(); + } + + @Override + public int getTextColour() { + if (!text().isEmpty()) { + return focused() ? 0xFFcc3300 : 0xFF993300; + } else { + return focused() ? 0xFFE0E0E0 : 0xFF909090; + } + } + + @Override + public void lastKeyTyped(int keyID, char keyChar) { + + if (!focused() && NEIClientConfig.isKeyHashDown("gui.search")) { + setFocus(true); + } + + if (focused() && NEIClientConfig.isKeyHashDown("gui.getprevioussearch")) { + handleNavigateHistory(TextHistory.Direction.PREVIOUS); + } + + if (focused() && NEIClientConfig.isKeyHashDown("gui.getnextsearch")) { + handleNavigateHistory(TextHistory.Direction.NEXT); + } + } + + @Override + public String filterText(String s) { + return EnumChatFormatting.getTextWithoutFormattingCodes(s); + } + + public ItemFilter getFilter() { + return ((GuiSearchField) field).getFilter(); + } + + @Override + public void setFocus(boolean focus) { + final boolean previousFocus = field.isFocused(); + + if (previousFocus != focus) { + history.add(text()); + } + + super.setFocus(focus); + } + + private boolean handleNavigateHistory(TextHistory.Direction direction) { + if (focused()) { + return history.get(direction, text()).map(newText -> { + setText(newText); + return true; + }).orElse(false); + } + return false; + } + + } + + protected static final int INPUT_HEIGHT = 20; + protected static final int BUTTON_SIZE = 16; + + protected final PresetItemsGrid grid = new PresetItemsGrid() { + + @Override + protected ItemFilter getFilter() { + AllMultiItemFilter filter = new AllMultiItemFilter(); + filter.filters.add(item -> !ItemInfo.hiddenItems.contains(item)); + filter.filters.add(searchField.getFilter()); + + if (enabledPresets.isChecked()) { + Preset preset = slotIndex != -1 ? PresetsList.presets.get(slotIndex) : null; + Set identifiers = PresetsList.presets.stream().filter(p -> p.enabled && p != preset) + .flatMap(p -> p.items.stream()).collect(Collectors.toSet()); + + filter.filters.add(item -> !identifiers.contains(Preset.getIdentifier(item))); + filter.filters.add(new NegatedItemFilter(ItemList.collapsibleItems.getItemFilter())); + } + + return filter; + } + + @Override + protected boolean isSelected(ItemStack stack) { + + if (mouseSelection != null && mouseSelection.items.contains(stack)) { + return mouseSelection.append != false; + } + + return preset.items.contains(Preset.getIdentifier(stack)); + } + }; + + protected CheckboxButton enabledPresets; + + protected final Preset preset; + protected final int slotIndex; + protected MouseSelection mouseSelection; + + protected final PresetSearchField searchField = new PresetSearchField("preset-search") { + + @Override + public void onTextChange(String oldText) { + final String newText = text(); + if (!newText.equals(oldText)) { + grid.restartFilter(); + } + } + + }; + + public RightPanel(Preset preset, int slotIndex) { + super(1, 0, 2, 2); + + this.slotIndex = slotIndex; + this.preset = preset; + + enabledPresets = new CheckboxButton(NEIClientUtils.translate("presets.filter")) { + + @Override + protected void onChange() { + grid.restartFilter(); + } + }; + + enabledPresets.setChecked(true); + + grid.restartFilter(); + } + + public void mouseClicked(int x, int y, int button) { + grid.mouseClicked(x, y, button); + + if (enabledPresets.contains(x, y)) { + enabledPresets.handleClick(x, y, button); + } + + if (searchField.contains(x, y)) { + searchField.handleClick(x, y, button); + } else { + searchField.onGuiClick(x, y); + } + + if (mouseSelection == null && (button == 0 || button == 1)) { + ItemPanelSlot slot = grid.getSlotMouseOver(x, y); + + if (slot != null) { + mouseSelection = new MouseSelection(slot.slotIndex, grid.getItemRect(slot.slotIndex), button == 0); + } + } + + } + + protected void onItemsChanges() {} + + public void mouseMovedOrUp(int x, int y, int button) { + searchField.mouseUp(x, y, button); + + if (mouseSelection != null && (button == 0 || button == 1)) { + ItemPanelSlot hoverSlot = grid.getSlotMouseOver(x, y); + + if (hoverSlot != null && hoverSlot.slotIndex == mouseSelection.startIndex) { + setHidden(hoverSlot.item, button == 0); + } else if (!mouseSelection.items.isEmpty()) { + + for (ItemStack stack : mouseSelection.items) { + hideItem(stack, mouseSelection.append); + } + } + + mouseSelection = null; + onItemsChanges(); + } + + } + + protected void setHidden(final ItemStack stack, final boolean append) { + + if (NEIClientUtils.shiftKey()) { + final List items = grid.getItems(); + + for (int i = 0; i < items.size(); i++) { + if (stack.getItem().equals(items.get(i).getItem())) { + hideItem(items.get(i), append); + } + } + + } else if (NEIClientUtils.controlKey()) { + final String modId = getModId(stack); + + if (modId == null) { + hideItem(stack, append); + } else { + final List items = grid.getItems(); + + for (int i = 0; i < items.size(); i++) { + final String mod = getModId(items.get(i)); + if (mod != null && mod.equals(modId)) { + hideItem(items.get(i), append); + } + } + } + + } else { + hideItem(stack, append); + } + } + + protected void hideItem(final ItemStack stack, boolean append) { + if (append) { + preset.items.add(Preset.getIdentifier(stack)); + } else { + preset.items.remove(Preset.getIdentifier(stack)); + } + } + + protected static String getModId(final ItemStack stack) { + try { + return GameRegistry.findUniqueIdentifierFor(stack.getItem()).modId; + } catch (Exception ignored) {} + + return null; + } + + public void mouseDragged(int x, int y, int button, long time) { + searchField.mouseDragged(x, y, button, time); + + if (mouseSelection != null && (button == 0 || button == 1)) { + final ItemPanelSlot slot = grid.getSlotMouseOver(x, y); + + if (slot != null && slot.slotIndex != mouseSelection.endIndex) { + mouseSelection.endIndex = slot.slotIndex; + mouseSelection.items.clear(); + + final Rectangle4i rec = grid.getItemRect(slot.slotIndex); + final Rectangle4i sel = new Rectangle4i( + Math.min(rec.x, mouseSelection.startX), + Math.min(rec.y, mouseSelection.startY), + Math.max(rec.x, mouseSelection.startX) - Math.min(rec.x, mouseSelection.startX), + Math.max(rec.y, mouseSelection.startY) - Math.min(rec.y, mouseSelection.startY)); + + for (int ix = sel.x; ix <= sel.x + sel.w; ix += ItemsGrid.SLOT_SIZE) { + for (int iy = sel.y; iy <= sel.y + sel.h; iy += ItemsGrid.SLOT_SIZE) { + ItemPanelSlot over = grid.getSlotMouseOver(ix, iy); + + if (over != null) { + mouseSelection.items.add(over.item); + } + } + } + } + } + } + + public void update() { + enabledPresets.w = 20; + enabledPresets.h = INPUT_HEIGHT; + enabledPresets.x = this.x; + enabledPresets.y = this.y + this.height - 2 - INPUT_HEIGHT; + + searchField.w = this.width - enabledPresets.w - 3; + searchField.h = INPUT_HEIGHT; + searchField.x = enabledPresets.x + enabledPresets.w + 3; + searchField.y = enabledPresets.y; + + grid.setGridSize(this.x, this.y, this.width, searchField.y - this.y); + grid.refresh(null); + } + + public void draw(int mousex, int mousey, float frame) { + enabledPresets.draw(mousex, mousey); + searchField.draw(mousex, mousey); + + RenderHelper.enableGUIStandardItemLighting(); + GL11.glPushMatrix(); + GL11.glEnable(GL12.GL_RESCALE_NORMAL); + + grid.draw(mousex, mousey); + + GL11.glDisable(GL12.GL_RESCALE_NORMAL); + GL11.glPopMatrix(); + RenderHelper.enableStandardItemLighting(); + } + + public List handleTooltip(int mousex, int mousey, List tooltip) { + ItemPanelSlot over = grid.getSlotMouseOver(mousex, mousey); + + if (over != null) { + tooltip = GuiContainerManager.itemDisplayNameMultiline(over.item, null, true); + + synchronized (GuiContainerManager.tooltipHandlers) { + for (IContainerTooltipHandler handler : GuiContainerManager.tooltipHandlers) { + tooltip = handler.handleItemTooltip(null, over.item, mousex, mousey, tooltip); + } + } + + if (tooltip.size() > 0) { + tooltip.set(0, tooltip.get(0) + GuiDraw.TOOLTIP_LINESPACE); // add space after 'title' + } + + } else if (enabledPresets.contains(mousex, mousey)) { + tooltip.add(NEIClientUtils.translate("presets.filter.tip")); + } + + return tooltip; + } + + public void keyTyped(char c, int keycode) { + searchField.handleKeyPress(keycode, c); + } + + public void mouseScrolled(int x, int y, int scroll) { + if (grid.contains(x, y)) { + grid.shiftPage(-scroll); + } + } + +} diff --git a/src/main/java/codechicken/nei/recipe/StackInfo.java b/src/main/java/codechicken/nei/recipe/StackInfo.java index 161e03f06..6655d9f05 100644 --- a/src/main/java/codechicken/nei/recipe/StackInfo.java +++ b/src/main/java/codechicken/nei/recipe/StackInfo.java @@ -11,7 +11,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.WeakHashMap; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTBase; @@ -23,6 +22,7 @@ import org.apache.commons.io.IOUtils; +import codechicken.nei.ItemStackMap; import codechicken.nei.NEIClientConfig; import codechicken.nei.api.IStackStringifyHandler; import codechicken.nei.recipe.stackinfo.DefaultStackStringifyHandler; @@ -32,7 +32,7 @@ public class StackInfo { public static final ArrayList stackStringifyHandlers = new ArrayList<>(); private static final HashMap> guidfilters = new HashMap<>(); - private static final WeakHashMap guidcache = new WeakHashMap<>(); + private static final ItemStackMap guidcache = new ItemStackMap<>(); private static final LinkedHashMap fluidcache = new LinkedHashMap() { @Override @@ -119,64 +119,67 @@ public static boolean isFluidContainer(ItemStack stack) { } public static String getItemStackGUID(ItemStack stack) { - if (!guidcache.containsKey(stack)) { + String guid = guidcache.get(stack); - final NBTTagCompound nbTag = itemStackToNBT(stack, false); + if (guid != null) { + return guid; + } - if (nbTag == null) { - return null; - } + final NBTTagCompound nbTag = itemStackToNBT(stack, false); - nbTag.removeTag("Count"); + if (nbTag == null) { + return null; + } - if (nbTag.getShort("Damage") == 0) { - nbTag.removeTag("Damage"); - } + nbTag.removeTag("Count"); - if (nbTag.hasKey("tag") && nbTag.getCompoundTag("tag").hasNoTags()) { - nbTag.removeTag("tag"); - } + if (nbTag.getShort("Damage") == 0) { + nbTag.removeTag("Damage"); + } - if (nbTag.hasKey("strId") && guidfilters.containsKey(nbTag.getString("strId"))) { - final ArrayList keys = new ArrayList<>(); - final String strId = nbTag.getString("strId"); + if (nbTag.hasKey("tag") && nbTag.getCompoundTag("tag").hasNoTags()) { + nbTag.removeTag("tag"); + } - keys.add(strId); + if (nbTag.hasKey("strId") && guidfilters.containsKey(nbTag.getString("strId"))) { + final ArrayList keys = new ArrayList<>(); + final String strId = nbTag.getString("strId"); - guidfilters.get(strId).forEach((key, rule) -> { - Object local = nbTag; + keys.add(strId); - for (int i = 0; i < rule.length; i++) { + guidfilters.get(strId).forEach((key, rule) -> { + Object local = nbTag; - try { + for (int i = 0; i < rule.length; i++) { - if (local instanceof NBTTagCompound) { - local = ((NBTTagCompound) local).getTag(rule[i]); - } else if (local instanceof NBTTagList) { - local = ((NBTTagList) local).tagList.get(Integer.parseInt(rule[i])); - } else { - break; - } + try { - } catch (Throwable e) { + if (local instanceof NBTTagCompound) { + local = ((NBTTagCompound) local).getTag(rule[i]); + } else if (local instanceof NBTTagList) { + local = ((NBTTagList) local).tagList.get(Integer.parseInt(rule[i])); + } else { break; } - } - if (local instanceof NBTBase) { - keys.add(((NBTBase) local).toString()); - } else if (local != null) { - keys.add(String.valueOf(local)); + } catch (Throwable e) { + break; } - }); - - synchronized (guidcache) { - guidcache.put(stack, keys.toString()); } - } else { - synchronized (guidcache) { - guidcache.put(stack, nbTag.toString()); + + if (local instanceof NBTBase) { + keys.add(((NBTBase) local).toString()); + } else if (local != null) { + keys.add(String.valueOf(local)); } + }); + + synchronized (guidcache) { + guidcache.put(stack, keys.toString()); + } + } else { + synchronized (guidcache) { + guidcache.put(stack, nbTag.toString()); } } diff --git a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java index 214db059c..80f7aba9e 100644 --- a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java @@ -35,11 +35,11 @@ import codechicken.lib.vec.Rectangle4i; import codechicken.nei.ItemList; import codechicken.nei.ItemList.AllMultiItemFilter; -import codechicken.nei.LayoutManager; import codechicken.nei.NEIClientConfig; import codechicken.nei.NEIClientUtils; import codechicken.nei.NEIServerUtils; import codechicken.nei.PositionedStack; +import codechicken.nei.PresetsList; import codechicken.nei.api.DefaultOverlayRenderer; import codechicken.nei.api.IOverlayHandler; import codechicken.nei.api.IRecipeOverlayRenderer; @@ -433,7 +433,7 @@ public TemplateRecipeHandler() { protected static ItemFilter getItemFilter() { return new AllMultiItemFilter( item -> !ItemInfo.hiddenItems.contains(item), - LayoutManager.presetsPanel != null ? LayoutManager.presetsPanel.getFilter() : null, + PresetsList.getItemFilter(), GuiRecipe.searchField != null ? GuiRecipe.searchField.getFilter() : null); } diff --git a/src/main/resources/assets/nei/cfg/collapsibleitems.cfg b/src/main/resources/assets/nei/cfg/collapsibleitems.cfg new file mode 100644 index 000000000..7c315ad0e --- /dev/null +++ b/src/main/resources/assets/nei/cfg/collapsibleitems.cfg @@ -0,0 +1,20 @@ +# Collapsible Items Filters +# +# One line - one group +# Item added in first valid group +# | - logical or. multi-item search (wrench|hammer) +# $ - search by ore dictionary ($ore) +# ! - logical not. exclude items that match the following expression (-@minecraft) +# r/.../ - standard java regex (@r/m\w{6}ft/ = @minecraft) +# 0 - damage filter +# 0-12 - damage range filter +# , - logical or in token +# +# +minecraft:spawn_egg +minecraft:mob_spawner +minecraft:record_ +#splash +minecraft:potion >16000 +#lingering +minecraft:potion >8000 <9000 \ No newline at end of file diff --git a/src/main/resources/assets/nei/lang/en_US.lang b/src/main/resources/assets/nei/lang/en_US.lang index 1d03c1497..da8b0ca85 100644 --- a/src/main/resources/assets/nei/lang/en_US.lang +++ b/src/main/resources/assets/nei/lang/en_US.lang @@ -13,6 +13,9 @@ nei.bookmark.pullBookmarkedItems.tip=Pull all bookmarked items from storage to i nei.itempanel.quantity.default=One stack nei.itempanel.loading=Loading Item List... +nei.itempanel.collapsed.button.tip=Toggle Groups +nei.itempanel.collapsed.hint.expand=Alt-Click to expand (%d items) +nei.itempanel.collapsed.hint.collapse=Alt-Click to collapse (%d items) nei.enchant=Enchant nei.enchant.level=Level @@ -85,6 +88,7 @@ nei.chat.chunkoverlay.3=Chunk Overlay: GT Ore Veins nei.options=NEI Options nei.options.back=Back +nei.options.save=Save nei.options.world=World nei.options.global=Global nei.options.global.tip.0=Options apply to all worlds @@ -266,6 +270,14 @@ nei.options.inventory.invertMouseScrollTransfer.false=False nei.options.inventory.cacheItemRendering=Cache Item Rendering nei.options.inventory.cacheItemRendering.true=True nei.options.inventory.cacheItemRendering.false=False +nei.options.inventory.collapsibleItems=Collapsible Items +nei.options.inventory.collapsibleItems.enabled=Collapsible Items Visibility +nei.options.inventory.collapsibleItems.enabled.true=Enabled +nei.options.inventory.collapsibleItems.enabled.false=Disabled +nei.options.inventory.collapsibleItems.expandedColor=Expanded Color +nei.options.inventory.collapsibleItems.collapsedColor=Collapsed Color +nei.options.inventory.collapsibleItems.reloadLabel=Reload +nei.options.inventory.collapsibleItems.reloadButton=Run nei.options.inventory.history=History Panel nei.options.inventory.history.enabled=History Panel Visibility nei.options.inventory.history.enabled.true=Enabled @@ -294,6 +306,7 @@ nei.options.world.highlight_tips.show=Shown nei.options.world.highlight_tips.sample=Sample nei.options.world.panels=Panels Settings +nei.options.world.presets=Presets List nei.options.tools=Tools nei.options.tools.handler_load_from_config=Load handlers from Config @@ -391,13 +404,20 @@ nei.subsets.Items.Weapons.Ranged=Ranged nei.subsets.Items.Weapons.Swords=Swords nei.subsets.Mod=Mod -nei.presets.blacklist.label=B -nei.presets.blacklist.tooltip=Blacklist -nei.presets.whitelist.label=W -nei.presets.whitelist.tooltip=Whitelist -nei.presets.blacklistPlus.label=B+ -nei.presets.blacklistPlus.tooltip=Blacklist Plus -nei.presets.textfield.tooltip.1=Enter - save -nei.presets.textfield.tooltip.2=Escape - cancel -nei.presets.label.tooltip=Double click to edit -nei.presets.label.selected=%s Presets +nei.presets.new=New Preset +nei.presets.create=Create Preset +nei.presets.update=Update Preset +nei.presets.enabled=Enabled +nei.presets.filter=F +nei.presets.filter.tip=Hide items from another active presets +nei.presets.name=Preset Name +nei.presets.mode=Mode +nei.presets.mode.hide=Hide +nei.presets.mode.hide.char=H +nei.presets.mode.hide.tip=Hide items in panel +nei.presets.mode.remove=Remove +nei.presets.mode.remove.tip=Hide items in panel and it recipes +nei.presets.mode.remove.char=R +nei.presets.mode.group=Group +nei.presets.mode.group.tip=Create custom collapsible items group +nei.presets.mode.group.char=G diff --git a/src/main/resources/assets/nei/lang/zh_CN.lang b/src/main/resources/assets/nei/lang/zh_CN.lang index f4d165475..8b5d54380 100644 --- a/src/main/resources/assets/nei/lang/zh_CN.lang +++ b/src/main/resources/assets/nei/lang/zh_CN.lang @@ -316,12 +316,3 @@ nei.subsets.Items.Weapons=武器 nei.subsets.Items.Weapons.Ranged=远程 nei.subsets.Items.Weapons.Swords=剑 nei.subsets.Mod=Mod - -nei.presets.blacklist.label=黑 -nei.presets.blacklist.tooltip=黑名单 -nei.presets.whitelist.label=白 -nei.presets.whitelist.tooltip=白名单 -nei.presets.textfield.tooltip.1=Enter - 保存 -nei.presets.textfield.tooltip.2=Esc - 取消 -nei.presets.label.tooltip=双击编辑 -nei.presets.label.selected=%s 预设 \ No newline at end of file From 46d8509e9eff0f97fad232b2a4f657a2e0b78e3c Mon Sep 17 00:00:00 2001 From: slprime Date: Sat, 13 Jul 2024 10:06:40 +0300 Subject: [PATCH 2/4] change lang --- src/main/resources/assets/nei/lang/en_US.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/assets/nei/lang/en_US.lang b/src/main/resources/assets/nei/lang/en_US.lang index da8b0ca85..0c561f116 100644 --- a/src/main/resources/assets/nei/lang/en_US.lang +++ b/src/main/resources/assets/nei/lang/en_US.lang @@ -271,7 +271,7 @@ nei.options.inventory.cacheItemRendering=Cache Item Rendering nei.options.inventory.cacheItemRendering.true=True nei.options.inventory.cacheItemRendering.false=False nei.options.inventory.collapsibleItems=Collapsible Items -nei.options.inventory.collapsibleItems.enabled=Collapsible Items Visibility +nei.options.inventory.collapsibleItems.enabled=Collapsible Items nei.options.inventory.collapsibleItems.enabled.true=Enabled nei.options.inventory.collapsibleItems.enabled.false=Disabled nei.options.inventory.collapsibleItems.expandedColor=Expanded Color From b6fc6f0b3ff16e4df104649a21865d763f78cab1 Mon Sep 17 00:00:00 2001 From: slprime Date: Sat, 13 Jul 2024 11:22:59 +0300 Subject: [PATCH 3/4] fix preset settings --- .../nei/config/preset/RightPanel.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/java/codechicken/nei/config/preset/RightPanel.java b/src/main/java/codechicken/nei/config/preset/RightPanel.java index a223230a8..edfd07ead 100644 --- a/src/main/java/codechicken/nei/config/preset/RightPanel.java +++ b/src/main/java/codechicken/nei/config/preset/RightPanel.java @@ -17,6 +17,7 @@ import codechicken.lib.vec.Rectangle4i; import codechicken.nei.ItemList; import codechicken.nei.ItemList.AllMultiItemFilter; +import codechicken.nei.ItemList.AnyMultiItemFilter; import codechicken.nei.ItemList.NegatedItemFilter; import codechicken.nei.ItemPanel.ItemPanelSlot; import codechicken.nei.ItemsGrid; @@ -138,12 +139,20 @@ protected ItemFilter getFilter() { filter.filters.add(searchField.getFilter()); if (enabledPresets.isChecked()) { - Preset preset = slotIndex != -1 ? PresetsList.presets.get(slotIndex) : null; - Set identifiers = PresetsList.presets.stream().filter(p -> p.enabled && p != preset) - .flatMap(p -> p.items.stream()).collect(Collectors.toSet()); + AllMultiItemFilter andFilter = new AllMultiItemFilter(); + AnyMultiItemFilter orFilter = new AnyMultiItemFilter(); + Set identifiers = PresetsList.presets.stream().flatMap(p -> p.items.stream()) + .collect(Collectors.toSet()); - filter.filters.add(item -> !identifiers.contains(Preset.getIdentifier(item))); - filter.filters.add(new NegatedItemFilter(ItemList.collapsibleItems.getItemFilter())); + andFilter.filters.add(item -> !identifiers.contains(Preset.getIdentifier(item))); + andFilter.filters.add(new NegatedItemFilter(ItemList.collapsibleItems.getItemFilter())); + + if (slotIndex != -1) { + orFilter.filters.add(PresetsList.presets.get(slotIndex)); + } + + orFilter.filters.add(andFilter); + filter.filters.add(orFilter); } return filter; From 46436bf0dea19475dd408af6221e2e5aafdec54b Mon Sep 17 00:00:00 2001 From: slprime Date: Sat, 13 Jul 2024 12:42:49 +0300 Subject: [PATCH 4/4] fix .top --- src/main/java/codechicken/nei/config/OptionTextField.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/codechicken/nei/config/OptionTextField.java b/src/main/java/codechicken/nei/config/OptionTextField.java index a0afc814a..437afe4c7 100644 --- a/src/main/java/codechicken/nei/config/OptionTextField.java +++ b/src/main/java/codechicken/nei/config/OptionTextField.java @@ -80,7 +80,7 @@ public void onMouseClicked(int mousex, int mousey, int button) { public List handleTooltip(int mousex, int mousey, List currenttip) { if (new Rectangle4i(10, 0, textField.x - 10, 20).contains(mousex, mousey)) { String tip = translateN(name + ".tip"); - if (!tip.equals(name + ".tip")) currenttip.add(tip); + if (!tip.equals(namespaced(name + ".tip"))) currenttip.add(tip); } return currenttip; }