From fe6455753fbb78dd39469867e55d75b333646dc1 Mon Sep 17 00:00:00 2001 From: Henry Loenwind Date: Sun, 2 Dec 2018 00:22:12 +0100 Subject: [PATCH] Add a Bookmarks list to the left side of the screen (#1408) --- .gitignore | 5 + src/main/java/mezz/jei/Internal.java | 14 ++ .../java/mezz/jei/bookmarks/BookmarkList.java | 222 ++++++++++++++++++ .../java/mezz/jei/bookmarks/package-info.java | 9 + .../java/mezz/jei/collect/package-info.java | 9 + .../config/BookmarkOverlayToggleEvent.java | 15 ++ src/main/java/mezz/jei/config/Config.java | 59 ++++- .../java/mezz/jei/config/ConfigValues.java | 1 + .../java/mezz/jei/config/KeyBindings.java | 6 +- .../java/mezz/jei/gui/GuiEventHandler.java | 14 +- src/main/java/mezz/jei/gui/GuiHelper.java | 24 ++ .../jei/gui/elements/GuiIconToggleButton.java | 66 ++++++ .../mezz/jei/gui/overlay/ConfigButton.java | 96 +++----- .../mezz/jei/gui/overlay/GridAlignment.java | 5 + .../gui/overlay/IIngredientGridSource.java | 15 ++ .../mezz/jei/gui/overlay/IngredientGrid.java | 11 +- ...java => IngredientGridWithNavigation.java} | 31 ++- .../gui/overlay/IngredientListOverlay.java | 14 +- .../gui/overlay/bookmarks/BookmarkButton.java | 58 +++++ .../overlay/bookmarks/BookmarkOverlay.java | 165 +++++++++++++ .../overlay/bookmarks/ILeftAreaContent.java | 23 ++ .../overlay/bookmarks/LeftAreaDispatcher.java | 196 ++++++++++++++++ .../gui/overlay/bookmarks/package-info.java | 9 + .../jei/ingredients/IngredientFilter.java | 43 ++-- .../ingredients/IngredientListElement.java | 36 +-- .../IngredientListElementFactory.java | 12 +- .../ingredients/IngredientOrderTracker.java | 23 ++ .../jei/ingredients/IngredientRegistry.java | 14 +- .../java/mezz/jei/input/InputHandler.java | 51 +++- .../java/mezz/jei/startup/JeiStarter.java | 19 +- .../java/mezz/jei/util/IngredientSet.java | 6 + src/main/resources/assets/jei/lang/en_us.lang | 9 + .../assets/jei/textures/gui/gui_vanilla.png | Bin 1302 -> 1298 bytes .../jei/textures/gui/recipe_background_2.png | Bin 3765 -> 3852 bytes 34 files changed, 1120 insertions(+), 160 deletions(-) create mode 100644 src/main/java/mezz/jei/bookmarks/BookmarkList.java create mode 100644 src/main/java/mezz/jei/bookmarks/package-info.java create mode 100644 src/main/java/mezz/jei/collect/package-info.java create mode 100644 src/main/java/mezz/jei/config/BookmarkOverlayToggleEvent.java create mode 100644 src/main/java/mezz/jei/gui/elements/GuiIconToggleButton.java create mode 100644 src/main/java/mezz/jei/gui/overlay/GridAlignment.java create mode 100644 src/main/java/mezz/jei/gui/overlay/IIngredientGridSource.java rename src/main/java/mezz/jei/gui/overlay/{IngredientGridAll.java => IngredientGridWithNavigation.java} (90%) create mode 100644 src/main/java/mezz/jei/gui/overlay/bookmarks/BookmarkButton.java create mode 100644 src/main/java/mezz/jei/gui/overlay/bookmarks/BookmarkOverlay.java create mode 100644 src/main/java/mezz/jei/gui/overlay/bookmarks/ILeftAreaContent.java create mode 100644 src/main/java/mezz/jei/gui/overlay/bookmarks/LeftAreaDispatcher.java create mode 100644 src/main/java/mezz/jei/gui/overlay/bookmarks/package-info.java create mode 100644 src/main/java/mezz/jei/ingredients/IngredientOrderTracker.java diff --git a/.gitignore b/.gitignore index 7e06a181c..872402574 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ build/* changelog.html /classes /logs +/build/ +\.classpath +\.project +\.settings/ +*.launch diff --git a/src/main/java/mezz/jei/Internal.java b/src/main/java/mezz/jei/Internal.java index 28a1ee95f..9f0681339 100644 --- a/src/main/java/mezz/jei/Internal.java +++ b/src/main/java/mezz/jei/Internal.java @@ -3,6 +3,8 @@ import javax.annotation.Nullable; import com.google.common.base.Preconditions; + +import mezz.jei.bookmarks.BookmarkList; import mezz.jei.color.ColorNamer; import mezz.jei.gui.GuiEventHandler; import mezz.jei.ingredients.IngredientFilter; @@ -33,6 +35,8 @@ public final class Internal { private static GuiEventHandler guiEventHandler; @Nullable private static InputHandler inputHandler; + @Nullable + private static BookmarkList bookmarkList; private Internal() { @@ -117,4 +121,14 @@ public static void setInputHandler(InputHandler inputHandler) { Internal.inputHandler = inputHandler; MinecraftForge.EVENT_BUS.register(inputHandler); } + + public static BookmarkList getBookmarkList() { + Preconditions.checkState(bookmarkList != null, "BookmarkList has not been created yet."); + return bookmarkList; + } + + public static void setBookmarkList(BookmarkList bookmarkList) { + Internal.bookmarkList = bookmarkList; + } + } diff --git a/src/main/java/mezz/jei/bookmarks/BookmarkList.java b/src/main/java/mezz/jei/bookmarks/BookmarkList.java new file mode 100644 index 000000000..8ace2abac --- /dev/null +++ b/src/main/java/mezz/jei/bookmarks/BookmarkList.java @@ -0,0 +1,222 @@ +package mezz.jei.bookmarks; + +import mezz.jei.api.ingredients.IIngredientHelper; +import mezz.jei.api.ingredients.VanillaTypes; +import mezz.jei.api.recipe.IIngredientType; +import mezz.jei.config.Config; +import mezz.jei.gui.ingredients.IIngredientListElement; +import mezz.jei.gui.overlay.IIngredientGridSource; +import mezz.jei.ingredients.IngredientListElementFactory; +import mezz.jei.ingredients.IngredientRegistry; +import mezz.jei.startup.ForgeModIdHelper; +import mezz.jei.util.LegacyUtil; +import mezz.jei.util.Log; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.JsonToNBT; +import net.minecraft.nbt.NBTException; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fluids.FluidStack; +import org.apache.commons.io.IOUtils; + +import javax.annotation.Nullable; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +public class BookmarkList implements IIngredientGridSource { + + private static final String MARKER_OTHER = "O:"; + private static final String MARKER_STACK = "T:"; + + private final List list = new LinkedList<>(); + private final List ingredientListElements = new LinkedList<>(); + private final IngredientRegistry ingredientRegistry; + private final List listeners = new ArrayList<>(); + + public BookmarkList(IngredientRegistry ingredientRegistry) { + this.ingredientRegistry = ingredientRegistry; + } + + public boolean add(T ingredient) { + Object normalized = normalize(ingredient); + if (!contains(normalized)) { + if (addToLists(normalized, true)) { + notifyListenersOfChange(); + saveBookmarks(); + return true; + } + } + return false; + } + + protected T normalize(T ingredient) { + IIngredientHelper ingredientHelper = ingredientRegistry.getIngredientHelper(ingredient); + T copy = LegacyUtil.getIngredientCopy(ingredient, ingredientHelper); + if (copy instanceof ItemStack) { + ((ItemStack) copy).setCount(1); + } else if (copy instanceof FluidStack) { + ((FluidStack) copy).amount = 1000; + } + return copy; + } + + private boolean contains(Object ingredient) { + // We cannot assume that ingredients have a working equals() implementation. Even ItemStack doesn't have one... + IIngredientHelper ingredientHelper = ingredientRegistry.getIngredientHelper(ingredient); + for (Object existing : list) { + if (ingredient == existing) { + return true; + } + if (existing != null && existing.getClass() == ingredient.getClass()) { + if (ingredientHelper.getUniqueId(existing).equals(ingredientHelper.getUniqueId(ingredient))) { + return true; + } + } + } + return false; + } + + public boolean remove(Object ingredient) { + int index = 0; + for (Object existing : list) { + if (ingredient == existing) { + list.remove(index); + ingredientListElements.remove(index); + notifyListenersOfChange(); + saveBookmarks(); + return true; + } + index++; + } + return false; + } + + public void saveBookmarks() { + List strings = new ArrayList<>(); + for (IIngredientListElement element : ingredientListElements) { + Object object = element.getIngredient(); + if (object instanceof ItemStack) { + strings.add(MARKER_STACK + ((ItemStack) object).writeToNBT(new NBTTagCompound()).toString()); + } else { + strings.add(MARKER_OTHER + getUid(element)); + } + } + File file = Config.getBookmarkFile(); + if (file != null) { + try (FileWriter writer = new FileWriter(file)) { + IOUtils.writeLines(strings, "\n", writer); + } catch (IOException e) { + Log.get().error("Failed to save bookmarks list to file {}", file, e); + } + } + } + + private static String getUid(IIngredientListElement element) { + IIngredientHelper ingredientHelper = element.getIngredientHelper(); + return ingredientHelper.getUniqueId(element.getIngredient()); + } + + public void loadBookmarks() { + File file = Config.getBookmarkFile(); + if (file == null || !file.exists()) { + return; + } + List ingredientJsonStrings; + try (FileReader reader = new FileReader(file)) { + ingredientJsonStrings = IOUtils.readLines(reader); + } catch (IOException e) { + Log.get().error("Failed to load bookmarks from file {}", file, e); + return; + } + + Collection otherIngredientTypes = new ArrayList<>(ingredientRegistry.getRegisteredIngredientTypes()); + otherIngredientTypes.remove(VanillaTypes.ITEM); + + list.clear(); + ingredientListElements.clear(); + for (String ingredientJsonString : ingredientJsonStrings) { + if (ingredientJsonString.startsWith(MARKER_STACK)) { + String itemStackAsJson = ingredientJsonString.substring(MARKER_STACK.length()); + try { + NBTTagCompound itemStackAsNbt = JsonToNBT.getTagFromJson(itemStackAsJson); + ItemStack itemStack = new ItemStack(itemStackAsNbt); + if (!itemStack.isEmpty()) { + ItemStack normalized = normalize(itemStack); + addToLists(normalized, false); + } else { + Log.get().warn("Failed to load bookmarked ItemStack from json string, the item no longer exists:\n{}", itemStackAsJson); + } + } catch (NBTException e) { + Log.get().error("Failed to load bookmarked ItemStack from json string:\n{}", itemStackAsJson, e); + } + } else if (ingredientJsonString.startsWith(MARKER_OTHER)) { + String uid = ingredientJsonString.substring(MARKER_OTHER.length()); + Object ingredient = getUnknownIngredientByUid(otherIngredientTypes, uid); + if (ingredient != null) { + Object normalized = normalize(ingredient); + addToLists(normalized, false); + } + } else { + Log.get().error("Failed to load unknown bookmarked ingredient:\n{}", ingredientJsonString); + } + } + notifyListenersOfChange(); + } + + @Nullable + private Object getUnknownIngredientByUid(Collection ingredientTypes, String uid) { + for (IIngredientType ingredientType : ingredientTypes) { + Object ingredient = ingredientRegistry.getIngredientByUid(ingredientType, uid); + if (ingredient != null) { + return ingredient; + } + } + return null; + } + + private boolean addToLists(T ingredient, boolean addToFront) { + IIngredientType ingredientType = ingredientRegistry.getIngredientType(ingredient); + IIngredientListElement element = IngredientListElementFactory.createUnorderedElement(ingredientRegistry, ingredientType, ingredient, ForgeModIdHelper.getInstance()); + if (element != null) { + if (addToFront) { + list.add(0, ingredient); + ingredientListElements.add(0, element); + } else { + list.add(ingredient); + ingredientListElements.add(element); + } + return true; + } + return false; + } + + @Override + public List getIngredientList() { + return ingredientListElements; + } + + @Override + public int size() { + return ingredientListElements.size(); + } + + public boolean isEmpty() { + return ingredientListElements.isEmpty(); + } + + @Override + public void addListener(IIngredientGridSource.Listener listener) { + listeners.add(listener); + } + + private void notifyListenersOfChange() { + for (IIngredientGridSource.Listener listener : listeners) { + listener.onChange(); + } + } +} diff --git a/src/main/java/mezz/jei/bookmarks/package-info.java b/src/main/java/mezz/jei/bookmarks/package-info.java new file mode 100644 index 000000000..1b2df458d --- /dev/null +++ b/src/main/java/mezz/jei/bookmarks/package-info.java @@ -0,0 +1,9 @@ +@ParametersAreNonnullByDefault +@FieldsAreNonnullByDefault +@MethodsReturnNonnullByDefault +package mezz.jei.bookmarks; + +import mcp.MethodsReturnNonnullByDefault; +import mezz.jei.util.FieldsAreNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/mezz/jei/collect/package-info.java b/src/main/java/mezz/jei/collect/package-info.java new file mode 100644 index 000000000..c098e5916 --- /dev/null +++ b/src/main/java/mezz/jei/collect/package-info.java @@ -0,0 +1,9 @@ +@ParametersAreNonnullByDefault +@FieldsAreNonnullByDefault +@MethodsReturnNonnullByDefault +package mezz.jei.collect; + +import mcp.MethodsReturnNonnullByDefault; +import mezz.jei.util.FieldsAreNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/mezz/jei/config/BookmarkOverlayToggleEvent.java b/src/main/java/mezz/jei/config/BookmarkOverlayToggleEvent.java new file mode 100644 index 000000000..b6477486b --- /dev/null +++ b/src/main/java/mezz/jei/config/BookmarkOverlayToggleEvent.java @@ -0,0 +1,15 @@ +package mezz.jei.config; + +import net.minecraftforge.fml.common.eventhandler.Event; + +public class BookmarkOverlayToggleEvent extends Event { + private final boolean bookmarkOverlayEnabled; + + public BookmarkOverlayToggleEvent(boolean bookmarkOverlayEnabled) { + this.bookmarkOverlayEnabled = bookmarkOverlayEnabled; + } + + public boolean isBookmarkOverlayEnabled() { + return bookmarkOverlayEnabled; + } +} diff --git a/src/main/java/mezz/jei/config/Config.java b/src/main/java/mezz/jei/config/Config.java index 94d1ed49c..e9125014a 100644 --- a/src/main/java/mezz/jei/config/Config.java +++ b/src/main/java/mezz/jei/config/Config.java @@ -1,15 +1,5 @@ package mezz.jei.config; -import javax.annotation.Nullable; -import java.awt.Color; -import java.io.File; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; - import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import mezz.jei.Internal; @@ -37,6 +27,16 @@ import net.minecraftforge.fml.client.FMLClientHandler; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import javax.annotation.Nullable; +import java.awt.Color; +import java.io.File; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + public final class Config { private static final String configKeyPrefix = "config.jei"; @@ -58,6 +58,8 @@ public final class Config { private static LocalizedConfiguration itemBlacklistConfig; @Nullable private static LocalizedConfiguration searchColorsConfig; + @Nullable + private static File bookmarkFile; private static final ConfigValues defaultValues = new ConfigValues(); private static final ConfigValues values = new ConfigValues(); @@ -94,6 +96,27 @@ public static void toggleOverlayEnabled() { MinecraftForge.EVENT_BUS.post(new OverlayToggleEvent(values.overlayEnabled)); } + public static boolean isBookmarkOverlayEnabled() { + return isOverlayEnabled() && values.bookmarkOverlayEnabled; + } + + public static void toggleBookmarkEnabled() { + values.bookmarkOverlayEnabled = !values.bookmarkOverlayEnabled; + + if (worldConfig != null) { + NetworkManager networkManager = FMLClientHandler.instance().getClientToServerNetworkManager(); + final String worldCategory = ServerInfo.getWorldUid(networkManager); + Property property = worldConfig.get(worldCategory, "bookmarkOverlayEnabled", defaultValues.bookmarkOverlayEnabled); + property.set(values.bookmarkOverlayEnabled); + + if (worldConfig.hasChanged()) { + worldConfig.save(); + } + } + + MinecraftForge.EVENT_BUS.post(new BookmarkOverlayToggleEvent(values.bookmarkOverlayEnabled)); + } + public static boolean isCheatItemsEnabled() { return values.cheatItemsEnabled; } @@ -255,6 +278,11 @@ public static Configuration getWorldConfig() { return worldConfig; } + @Nullable + public static File getBookmarkFile() { + return bookmarkFile; + } + public static void preInit(FMLPreInitializationEvent event) { File jeiConfigurationDir = new File(event.getModConfigurationDirectory(), Constants.MOD_ID); @@ -274,6 +302,7 @@ public static void preInit(FMLPreInitializationEvent event) { final File itemBlacklistConfigFile = new File(jeiConfigurationDir, "itemBlacklist.cfg"); final File searchColorsConfigFile = new File(jeiConfigurationDir, "searchColors.cfg"); final File worldConfigFile = new File(jeiConfigurationDir, "worldSettings.cfg"); + bookmarkFile = new File(jeiConfigurationDir, "bookmarks.ini"); worldConfig = new Configuration(worldConfigFile, "0.1.0"); config = new LocalizedConfiguration(configKeyPrefix, configFile, "0.4.0"); itemBlacklistConfig = new LocalizedConfiguration(configKeyPrefix, itemBlacklistConfigFile, "0.1.0"); @@ -466,6 +495,12 @@ public static boolean syncWorldConfig(@Nullable NetworkManager networkManager) { MinecraftForge.EVENT_BUS.post(new EditModeToggleEvent(values.hideModeEnabled)); } + property = worldConfig.get(worldCategory, "bookmarkOverlayEnabled", defaultValues.bookmarkOverlayEnabled); + property.setLanguageKey("config.jei.interface.bookmarkOverlayEnabled"); + property.setComment(Translator.translateToLocal("config.jei.interface.bookmarkOverlayEnabled.comment")); + property.setShowInGui(false); + values.bookmarkOverlayEnabled = property.getBoolean(); + property = worldConfig.get(worldCategory, "filterText", defaultValues.filterText); property.setShowInGui(false); values.filterText = property.getString(); @@ -529,7 +564,7 @@ private static void updateBlacklist() { public static void addIngredientToConfigBlacklist(IngredientFilter ingredientFilter, IIngredientRegistry ingredientRegistry, V ingredient, IngredientBlacklistType blacklistType, IIngredientHelper ingredientHelper) { IIngredientType ingredientType = ingredientRegistry.getIngredientType(ingredient); - IIngredientListElement element = IngredientListElementFactory.createElement(ingredientRegistry, ingredientType, ingredient, ForgeModIdHelper.getInstance()); + IIngredientListElement element = IngredientListElementFactory.createUnorderedElement(ingredientRegistry, ingredientType, ingredient, ForgeModIdHelper.getInstance()); Preconditions.checkNotNull(element, "Failed to create element for blacklist"); // combine item-level blacklist into wildcard-level ones @@ -577,7 +612,7 @@ private static boolean areAllBlacklisted(List> ele public static void removeIngredientFromConfigBlacklist(IngredientFilter ingredientFilter, IIngredientRegistry ingredientRegistry, V ingredient, IngredientBlacklistType blacklistType, IIngredientHelper ingredientHelper) { IIngredientType ingredientType = ingredientRegistry.getIngredientType(ingredient); - IIngredientListElement element = IngredientListElementFactory.createElement(ingredientRegistry, ingredientType, ingredient, ForgeModIdHelper.getInstance()); + IIngredientListElement element = IngredientListElementFactory.createUnorderedElement(ingredientRegistry, ingredientType, ingredient, ForgeModIdHelper.getInstance()); Preconditions.checkNotNull(element, "Failed to create element for blacklist"); boolean updated = false; diff --git a/src/main/java/mezz/jei/config/ConfigValues.java b/src/main/java/mezz/jei/config/ConfigValues.java index 00eb9f16f..95dbad614 100644 --- a/src/main/java/mezz/jei/config/ConfigValues.java +++ b/src/main/java/mezz/jei/config/ConfigValues.java @@ -24,5 +24,6 @@ public class ConfigValues { public boolean overlayEnabled = true; public boolean cheatItemsEnabled = false; public boolean hideModeEnabled = false; + public boolean bookmarkOverlayEnabled = true; public String filterText = ""; } diff --git a/src/main/java/mezz/jei/config/KeyBindings.java b/src/main/java/mezz/jei/config/KeyBindings.java index 14f791a08..094d12962 100644 --- a/src/main/java/mezz/jei/config/KeyBindings.java +++ b/src/main/java/mezz/jei/config/KeyBindings.java @@ -21,6 +21,8 @@ public final class KeyBindings { public static final KeyBinding recipeBack; public static final KeyBinding previousPage; public static final KeyBinding nextPage; + public static final KeyBinding bookmark; + public static final KeyBinding toggleBookmarkOverlay; private static final List allBindings; static { @@ -32,7 +34,9 @@ public final class KeyBindings { showUses = new KeyBinding("key.jei.showUses", KeyConflictContext.GUI, Keyboard.KEY_U, categoryName), recipeBack = new KeyBinding("key.jei.recipeBack", KeyConflictContext.GUI, Keyboard.KEY_BACK, categoryName), previousPage = new KeyBinding("key.jei.previousPage", KeyConflictContext.GUI, Keyboard.KEY_PRIOR, categoryName), - nextPage = new KeyBinding("key.jei.nextPage", KeyConflictContext.GUI, Keyboard.KEY_NEXT, categoryName) + nextPage = new KeyBinding("key.jei.nextPage", KeyConflictContext.GUI, Keyboard.KEY_NEXT, categoryName), + bookmark = new KeyBinding("key.jei.bookmark", KeyConflictContext.GUI, Keyboard.KEY_A, categoryName), + toggleBookmarkOverlay = new KeyBinding("key.jei.toggleBookmarkOverlay", KeyConflictContext.GUI, Keyboard.KEY_NONE, categoryName) ); } diff --git a/src/main/java/mezz/jei/gui/GuiEventHandler.java b/src/main/java/mezz/jei/gui/GuiEventHandler.java index caaa6d106..5350dfbd2 100644 --- a/src/main/java/mezz/jei/gui/GuiEventHandler.java +++ b/src/main/java/mezz/jei/gui/GuiEventHandler.java @@ -3,6 +3,7 @@ import mezz.jei.config.Config; import mezz.jei.config.OverlayToggleEvent; import mezz.jei.gui.overlay.IngredientListOverlay; +import mezz.jei.gui.overlay.bookmarks.LeftAreaDispatcher; import mezz.jei.recipes.RecipeRegistry; import mezz.jei.util.Translator; import net.minecraft.client.Minecraft; @@ -16,10 +17,12 @@ public class GuiEventHandler { private final IngredientListOverlay ingredientListOverlay; + private final LeftAreaDispatcher leftAreaDispatcher; private final RecipeRegistry recipeRegistry; private boolean drawnOnBackground = false; - public GuiEventHandler(IngredientListOverlay ingredientListOverlay, RecipeRegistry recipeRegistry) { + public GuiEventHandler(LeftAreaDispatcher leftAreaDispatcher, IngredientListOverlay ingredientListOverlay, RecipeRegistry recipeRegistry) { + this.leftAreaDispatcher = leftAreaDispatcher; this.ingredientListOverlay = ingredientListOverlay; this.recipeRegistry = recipeRegistry; } @@ -28,27 +31,32 @@ public GuiEventHandler(IngredientListOverlay ingredientListOverlay, RecipeRegist public void onOverlayToggle(OverlayToggleEvent event) { GuiScreen currentScreen = Minecraft.getMinecraft().currentScreen; ingredientListOverlay.updateScreen(currentScreen, true); + leftAreaDispatcher.updateScreen(currentScreen); } @SubscribeEvent public void onGuiInit(GuiScreenEvent.InitGuiEvent.Post event) { GuiScreen gui = event.getGui(); ingredientListOverlay.updateScreen(gui, false); + leftAreaDispatcher.updateScreen(gui); } @SubscribeEvent public void onGuiOpen(GuiOpenEvent event) { GuiScreen gui = event.getGui(); ingredientListOverlay.updateScreen(gui, false); + leftAreaDispatcher.updateScreen(gui); } @SubscribeEvent public void onDrawBackgroundEventPost(GuiScreenEvent.BackgroundDrawnEvent event) { GuiScreen gui = event.getGui(); ingredientListOverlay.updateScreen(gui, false); + leftAreaDispatcher.updateScreen(gui); drawnOnBackground = true; ingredientListOverlay.drawScreen(gui.mc, event.getMouseX(), event.getMouseY(), gui.mc.getRenderPartialTicks()); + leftAreaDispatcher.drawScreen(gui.mc, event.getMouseX(), event.getMouseY(), gui.mc.getRenderPartialTicks()); } /** @@ -58,6 +66,7 @@ public void onDrawBackgroundEventPost(GuiScreenEvent.BackgroundDrawnEvent event) public void onDrawForegroundEvent(GuiContainerEvent.DrawForeground event) { GuiContainer gui = event.getGuiContainer(); ingredientListOverlay.drawOnForeground(gui, event.getMouseX(), event.getMouseY()); + leftAreaDispatcher.drawOnForeground(gui, event.getMouseX(), event.getMouseY()); } @SubscribeEvent @@ -65,9 +74,11 @@ public void onDrawScreenEventPost(GuiScreenEvent.DrawScreenEvent.Post event) { GuiScreen gui = event.getGui(); ingredientListOverlay.updateScreen(gui, false); + leftAreaDispatcher.updateScreen(gui); if (!drawnOnBackground) { ingredientListOverlay.drawScreen(gui.mc, event.getMouseX(), event.getMouseY(), gui.mc.getRenderPartialTicks()); + leftAreaDispatcher.drawScreen(gui.mc, event.getMouseX(), event.getMouseY(), gui.mc.getRenderPartialTicks()); } drawnOnBackground = false; @@ -80,6 +91,7 @@ public void onDrawScreenEventPost(GuiScreenEvent.DrawScreenEvent.Post event) { } ingredientListOverlay.drawTooltips(gui.mc, event.getMouseX(), event.getMouseY()); + leftAreaDispatcher.drawTooltips(gui.mc, event.getMouseX(), event.getMouseY()); } @SubscribeEvent diff --git a/src/main/java/mezz/jei/gui/GuiHelper.java b/src/main/java/mezz/jei/gui/GuiHelper.java index a5bb60d49..b37b1c083 100644 --- a/src/main/java/mezz/jei/gui/GuiHelper.java +++ b/src/main/java/mezz/jei/gui/GuiHelper.java @@ -26,6 +26,10 @@ public class GuiHelper implements IGuiHelper { private final IDrawableStatic arrowPrevious; private final IDrawableStatic arrowNext; private final IDrawableStatic recipeTransfer; + private final IDrawableStatic configButtonIcon; + private final IDrawableStatic configButtonCheatIcon; + private final IDrawableStatic bookmarkButtonDisabledIcon; + private final IDrawableStatic bookmarkButtonEnabledIcon; public GuiHelper(IIngredientRegistry ingredientRegistry) { this.ingredientRegistry = ingredientRegistry; @@ -45,6 +49,10 @@ public GuiHelper(IIngredientRegistry ingredientRegistry) { recipeTransfer = drawableBuilder(Constants.RECIPE_BACKGROUND, 212, 55, 6, 6) .addPadding(1, 0, 1, 0) .build(); + configButtonIcon = createDrawable(Constants.RECIPE_BACKGROUND, 0, 166, 16, 16); + configButtonCheatIcon = createDrawable(Constants.RECIPE_BACKGROUND, 16, 166, 16, 16); + bookmarkButtonDisabledIcon = createDrawable(Constants.RECIPE_BACKGROUND, 32, 166, 16, 16); + bookmarkButtonEnabledIcon = createDrawable(Constants.RECIPE_BACKGROUND, 48, 166, 16, 16); } @Override @@ -108,4 +116,20 @@ public IDrawableStatic getArrowNext() { public IDrawableStatic getRecipeTransfer() { return recipeTransfer; } + + public IDrawableStatic getConfigButtonIcon() { + return configButtonIcon; + } + + public IDrawableStatic getConfigButtonCheatIcon() { + return configButtonCheatIcon; + } + + public IDrawableStatic getBookmarkButtonDisabledIcon() { + return bookmarkButtonDisabledIcon; + } + + public IDrawableStatic getBookmarkButtonEnabledIcon() { + return bookmarkButtonEnabledIcon; + } } diff --git a/src/main/java/mezz/jei/gui/elements/GuiIconToggleButton.java b/src/main/java/mezz/jei/gui/elements/GuiIconToggleButton.java new file mode 100644 index 000000000..a7b17d910 --- /dev/null +++ b/src/main/java/mezz/jei/gui/elements/GuiIconToggleButton.java @@ -0,0 +1,66 @@ +package mezz.jei.gui.elements; + +import mezz.jei.api.gui.IDrawable; +import mezz.jei.config.Constants; +import mezz.jei.gui.TooltipRenderer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraftforge.fml.client.config.HoverChecker; + +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.List; + +public abstract class GuiIconToggleButton { + private final IDrawable offIcon; + private final IDrawable onIcon; + private final GuiButton button; + private final HoverChecker hoverChecker; + + public GuiIconToggleButton(IDrawable offIcon, IDrawable onIcon) { + this.offIcon = offIcon; + this.onIcon = onIcon; + this.button = new GuiButton(2, 0, 0, 0, 0, ""); + this.hoverChecker = new HoverChecker(this.button, 0); + } + + public void updateBounds(Rectangle area) { + this.button.width = area.width; + this.button.height = area.height; + this.button.x = area.x; + this.button.y = area.y; + } + + public void draw(Minecraft minecraft, int mouseX, int mouseY, float partialTicks) { + this.button.drawButton(minecraft, mouseX, mouseY, partialTicks); + IDrawable icon = isIconToggledOn() ? this.onIcon : this.offIcon; + icon.draw(minecraft, this.button.x + 2, this.button.y + 2); + } + + public final boolean isMouseOver(int mouseX, int mouseY) { + return this.hoverChecker.checkHover(mouseX, mouseY); + } + + public final boolean handleMouseClick(int mouseX, int mouseY) { + Minecraft minecraft = Minecraft.getMinecraft(); + if (button.mousePressed(minecraft, mouseX, mouseY) && onMouseClicked(mouseX, mouseY)) { + button.playPressSound(minecraft.getSoundHandler()); + return true; + } + return false; + } + + public final void drawTooltips(Minecraft minecraft, int mouseX, int mouseY) { + if (isMouseOver(mouseX, mouseY)) { + List tooltip = new ArrayList<>(); + getTooltips(tooltip); + TooltipRenderer.drawHoveringText(minecraft, tooltip, mouseX, mouseY, Constants.MAX_TOOLTIP_WIDTH); + } + } + + protected abstract void getTooltips(List tooltip); + + protected abstract boolean isIconToggledOn(); + + protected abstract boolean onMouseClicked(int mouseX, int mouseY); +} diff --git a/src/main/java/mezz/jei/gui/overlay/ConfigButton.java b/src/main/java/mezz/jei/gui/overlay/ConfigButton.java index 9f4dcebb9..5af5d50fe 100644 --- a/src/main/java/mezz/jei/gui/overlay/ConfigButton.java +++ b/src/main/java/mezz/jei/gui/overlay/ConfigButton.java @@ -1,93 +1,67 @@ package mezz.jei.gui.overlay; -import java.awt.Rectangle; -import java.util.ArrayList; -import java.util.List; - import mezz.jei.Internal; import mezz.jei.api.gui.IDrawable; import mezz.jei.config.Config; -import mezz.jei.config.Constants; import mezz.jei.config.JEIModConfigGui; import mezz.jei.config.KeyBindings; import mezz.jei.gui.GuiHelper; -import mezz.jei.gui.TooltipRenderer; +import mezz.jei.gui.elements.GuiIconToggleButton; import mezz.jei.util.Translator; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.settings.KeyBinding; -import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.TextFormatting; -import net.minecraftforge.fml.client.config.HoverChecker; import org.lwjgl.input.Keyboard; -public class ConfigButton { - private final IngredientListOverlay parent; - private final GuiButton configButton; - private final IDrawable configButtonIcon; - private final IDrawable configButtonCheatIcon; - private final HoverChecker configButtonHoverChecker; +import java.util.List; - public ConfigButton(IngredientListOverlay parent) { - this.parent = parent; - this.configButton = new GuiButton(2, 0, 0, 0, 0, ""); - ResourceLocation configButtonIconLocation = Constants.RECIPE_BACKGROUND; +public class ConfigButton extends GuiIconToggleButton { + public static ConfigButton create(IngredientListOverlay parent) { GuiHelper guiHelper = Internal.getHelpers().getGuiHelper(); - this.configButtonIcon = guiHelper.createDrawable(configButtonIconLocation, 0, 166, 16, 16); - this.configButtonCheatIcon = guiHelper.createDrawable(configButtonIconLocation, 16, 166, 16, 16); - this.configButtonHoverChecker = new HoverChecker(this.configButton, 0); + return new ConfigButton(guiHelper.getConfigButtonIcon(), guiHelper.getConfigButtonCheatIcon(), parent); } - public void updateBounds(Rectangle area) { - this.configButton.width = area.width; - this.configButton.height = area.height; - this.configButton.x = area.x; - this.configButton.y = area.y; - } - - public void draw(Minecraft minecraft, int mouseX, int mouseY, float partialTicks) { - this.configButton.drawButton(minecraft, mouseX, mouseY, partialTicks); - - IDrawable icon = Config.isCheatItemsEnabled() ? this.configButtonCheatIcon : this.configButtonIcon; - icon.draw(minecraft, this.configButton.x + 2, this.configButton.y + 2); - } + private final IngredientListOverlay parent; - public boolean isMouseOver(int mouseX, int mouseY) { - return this.configButtonHoverChecker.checkHover(mouseX, mouseY); + private ConfigButton(IDrawable disabledIcon, IDrawable enabledIcon, IngredientListOverlay parent) { + super(disabledIcon, enabledIcon); + this.parent = parent; } - public void drawTooltips(Minecraft minecraft, int mouseX, int mouseY, boolean hasRoom) { - if (isMouseOver(mouseX, mouseY)) { - List tooltip = new ArrayList<>(); - tooltip.add(Translator.translateToLocal("jei.tooltip.config")); - if (!Config.isOverlayEnabled()) { - tooltip.add(TextFormatting.GOLD + Translator.translateToLocal("jei.tooltip.ingredient.list.disabled")); - tooltip.add(TextFormatting.GOLD + Translator.translateToLocalFormatted("jei.tooltip.ingredient.list.disabled.how.to.fix", KeyBindings.toggleOverlay.getDisplayName())); - } else if (!hasRoom) { - tooltip.add(TextFormatting.GOLD + Translator.translateToLocal("jei.tooltip.not.enough.space")); - } - if (Config.isCheatItemsEnabled()) { - tooltip.add(TextFormatting.RED + Translator.translateToLocal("jei.tooltip.cheat.mode.button.enabled")); - KeyBinding toggleCheatMode = KeyBindings.toggleCheatMode; - if (toggleCheatMode.getKeyCode() != 0) { - tooltip.add(TextFormatting.RED + Translator.translateToLocalFormatted("jei.tooltip.cheat.mode.how.to.disable.hotkey", toggleCheatMode.getDisplayName())); - } else { - String controlKeyLocalization = Translator.translateToLocal(Minecraft.IS_RUNNING_ON_MAC ? "key.jei.ctrl.mac" : "key.jei.ctrl"); - tooltip.add(TextFormatting.RED + Translator.translateToLocalFormatted("jei.tooltip.cheat.mode.how.to.disable.no.hotkey", controlKeyLocalization)); - } + @Override + protected void getTooltips(List tooltip) { + tooltip.add(Translator.translateToLocal("jei.tooltip.config")); + if (!Config.isOverlayEnabled()) { + tooltip.add(TextFormatting.GOLD + Translator.translateToLocal("jei.tooltip.ingredient.list.disabled")); + tooltip.add(TextFormatting.GOLD + Translator.translateToLocalFormatted("jei.tooltip.ingredient.list.disabled.how.to.fix", KeyBindings.toggleOverlay.getDisplayName())); + } else if (!parent.isListDisplayed()) { + tooltip.add(TextFormatting.GOLD + Translator.translateToLocal("jei.tooltip.not.enough.space")); + } + if (Config.isCheatItemsEnabled()) { + tooltip.add(TextFormatting.RED + Translator.translateToLocal("jei.tooltip.cheat.mode.button.enabled")); + KeyBinding toggleCheatMode = KeyBindings.toggleCheatMode; + if (toggleCheatMode.getKeyCode() != 0) { + tooltip.add(TextFormatting.RED + Translator.translateToLocalFormatted("jei.tooltip.cheat.mode.how.to.disable.hotkey", toggleCheatMode.getDisplayName())); + } else { + String controlKeyLocalization = Translator.translateToLocal(Minecraft.IS_RUNNING_ON_MAC ? "key.jei.ctrl.mac" : "key.jei.ctrl"); + tooltip.add(TextFormatting.RED + Translator.translateToLocalFormatted("jei.tooltip.cheat.mode.how.to.disable.no.hotkey", controlKeyLocalization)); } - TooltipRenderer.drawHoveringText(minecraft, tooltip, mouseX, mouseY, Constants.MAX_TOOLTIP_WIDTH); } } - public boolean handleMouseClick(int mouseX, int mouseY) { - Minecraft minecraft = Minecraft.getMinecraft(); - if (Config.isOverlayEnabled() && configButton.mousePressed(minecraft, mouseX, mouseY)) { - configButton.playPressSound(minecraft.getSoundHandler()); + @Override + protected boolean isIconToggledOn() { + return Config.isCheatItemsEnabled(); + } + + @Override + protected boolean onMouseClicked(int mouseX, int mouseY) { + if (Config.isOverlayEnabled()) { if (Keyboard.getEventKeyState() && (Keyboard.getEventKey() == Keyboard.KEY_LCONTROL || Keyboard.getEventKey() == Keyboard.KEY_RCONTROL)) { Config.toggleCheatItemsEnabled(); } else { + Minecraft minecraft = Minecraft.getMinecraft(); if (minecraft.currentScreen != null) { GuiScreen configScreen = new JEIModConfigGui(minecraft.currentScreen); parent.updateScreen(configScreen, false); diff --git a/src/main/java/mezz/jei/gui/overlay/GridAlignment.java b/src/main/java/mezz/jei/gui/overlay/GridAlignment.java new file mode 100644 index 000000000..be88c4257 --- /dev/null +++ b/src/main/java/mezz/jei/gui/overlay/GridAlignment.java @@ -0,0 +1,5 @@ +package mezz.jei.gui.overlay; + +public enum GridAlignment { + LEFT, RIGHT +} diff --git a/src/main/java/mezz/jei/gui/overlay/IIngredientGridSource.java b/src/main/java/mezz/jei/gui/overlay/IIngredientGridSource.java new file mode 100644 index 000000000..77b7ee6b5 --- /dev/null +++ b/src/main/java/mezz/jei/gui/overlay/IIngredientGridSource.java @@ -0,0 +1,15 @@ +package mezz.jei.gui.overlay; + +import mezz.jei.gui.ingredients.IIngredientListElement; + +import java.util.List; + +public interface IIngredientGridSource { + List getIngredientList(); + int size(); + void addListener(Listener listener); + + interface Listener { + void onChange(); + } +} diff --git a/src/main/java/mezz/jei/gui/overlay/IngredientGrid.java b/src/main/java/mezz/jei/gui/overlay/IngredientGrid.java index 7cab5f0d9..ef7d6e04c 100644 --- a/src/main/java/mezz/jei/gui/overlay/IngredientGrid.java +++ b/src/main/java/mezz/jei/gui/overlay/IngredientGrid.java @@ -37,11 +37,13 @@ public class IngredientGrid implements IShowsRecipeFocuses { private static final int INGREDIENT_PADDING = 1; public static final int INGREDIENT_WIDTH = GuiItemStackGroup.getWidth(INGREDIENT_PADDING); public static final int INGREDIENT_HEIGHT = GuiItemStackGroup.getHeight(INGREDIENT_PADDING); + private final GridAlignment alignment; private Rectangle area = new Rectangle(); protected final IngredientListBatchRenderer guiIngredientSlots; - public IngredientGrid() { + public IngredientGrid(GridAlignment alignment) { + this.alignment = alignment; this.guiIngredientSlots = new IngredientListBatchRenderer(); } @@ -56,7 +58,12 @@ public void updateBounds(Rectangle availableArea, int minWidth, Collection guiExclusionAreas = Collections.emptySet(); - public IngredientGridAll(IngredientFilter ingredientFilter) { - this.ingredientGrid = new IngredientGrid(); - this.ingredientFilter = ingredientFilter; + public IngredientGridWithNavigation(IIngredientGridSource ingredientSource, GridAlignment alignment) { + this.ingredientGrid = new IngredientGrid(alignment); + this.ingredientSource = ingredientSource; this.pageDelegate = new IngredientGridPaged(); this.navigation = new PageNavigation(this.pageDelegate, false); } - public void updateLayout(boolean filterChanged) { - if (filterChanged) { + public void updateLayout(boolean resetToFirstPage) { + if (resetToFirstPage) { firstItemIndex = 0; } this.ingredientGrid.updateLayout(this.guiExclusionAreas); - List ingredientList = ingredientFilter.getIngredientList(); + List ingredientList = ingredientSource.getIngredientList(); this.ingredientGrid.guiIngredientSlots.set(firstItemIndex, ingredientList); this.navigation.updatePageState(); } @@ -219,7 +218,7 @@ private static Set getGuiAreas() { private class IngredientGridPaged implements IPaged { @Override public boolean nextPage() { - final int itemsCount = ingredientFilter.size(); + final int itemsCount = ingredientSource.size(); if (itemsCount > 0) { firstItemIndex += ingredientGrid.size(); if (firstItemIndex >= itemsCount) { @@ -242,7 +241,7 @@ public boolean previousPage() { updateLayout(false); return false; } - final int itemsCount = ingredientFilter.size(); + final int itemsCount = ingredientSource.size(); int pageNum = firstItemIndex / itemsPerPage; if (pageNum == 0) { @@ -264,19 +263,19 @@ public boolean previousPage() { public boolean hasNext() { // true if there is more than one page because this wraps around int itemsPerPage = ingredientGrid.size(); - return itemsPerPage > 0 && ingredientFilter.size() > itemsPerPage; + return itemsPerPage > 0 && ingredientSource.size() > itemsPerPage; } @Override public boolean hasPrevious() { // true if there is more than one page because this wraps around int itemsPerPage = ingredientGrid.size(); - return itemsPerPage > 0 && ingredientFilter.size() > itemsPerPage; + return itemsPerPage > 0 && ingredientSource.size() > itemsPerPage; } @Override public int getPageCount() { - final int itemCount = ingredientFilter.size(); + final int itemCount = ingredientSource.size(); final int stacksPerPage = ingredientGrid.size(); if (stacksPerPage == 0) { return 1; diff --git a/src/main/java/mezz/jei/gui/overlay/IngredientListOverlay.java b/src/main/java/mezz/jei/gui/overlay/IngredientListOverlay.java index 03ca67d5e..3725fe56f 100644 --- a/src/main/java/mezz/jei/gui/overlay/IngredientListOverlay.java +++ b/src/main/java/mezz/jei/gui/overlay/IngredientListOverlay.java @@ -6,6 +6,7 @@ import mezz.jei.api.gui.IGuiProperties; import mezz.jei.config.Config; import mezz.jei.config.KeyBindings; +import mezz.jei.gui.elements.GuiIconToggleButton; import mezz.jei.gui.ghost.GhostIngredientDragManager; import mezz.jei.gui.ingredients.IIngredientListElement; import mezz.jei.gui.recipes.RecipesGui; @@ -51,8 +52,8 @@ private static boolean isSearchBarCentered(IGuiProperties guiProperties) { } private final IngredientFilter ingredientFilter; - private final ConfigButton configButton; - private final IngredientGridAll contents; + private final GuiIconToggleButton configButton; + private final IngredientGridWithNavigation contents; private final GuiTextFieldFilter searchField; private final GhostIngredientDragManager ghostIngredientDragManager; private Rectangle displayArea = new Rectangle(); @@ -64,9 +65,10 @@ private static boolean isSearchBarCentered(IGuiProperties guiProperties) { public IngredientListOverlay(IngredientFilter ingredientFilter) { this.ingredientFilter = ingredientFilter; - this.contents = new IngredientGridAll(ingredientFilter); + this.contents = new IngredientGridWithNavigation(ingredientFilter, GridAlignment.LEFT); + ingredientFilter.addListener(() -> onSetFilterText(Config.getFilterText())); this.searchField = new GuiTextFieldFilter(0, ingredientFilter); - this.configButton = new ConfigButton(this); + this.configButton = ConfigButton.create(this); this.ghostIngredientDragManager = new GhostIngredientDragManager(this.contents); this.setKeyboardFocus(false); } @@ -183,11 +185,11 @@ public void drawScreen(Minecraft minecraft, int mouseX, int mouseY, float partia public void drawTooltips(Minecraft minecraft, int mouseX, int mouseY) { if (isListDisplayed()) { - this.configButton.drawTooltips(minecraft, mouseX, mouseY, true); + this.configButton.drawTooltips(minecraft, mouseX, mouseY); this.ghostIngredientDragManager.drawTooltips(minecraft, mouseX, mouseY); this.contents.drawTooltips(minecraft, mouseX, mouseY); } else if (this.guiProperties != null) { - this.configButton.drawTooltips(minecraft, mouseX, mouseY, false); + this.configButton.drawTooltips(minecraft, mouseX, mouseY); } } diff --git a/src/main/java/mezz/jei/gui/overlay/bookmarks/BookmarkButton.java b/src/main/java/mezz/jei/gui/overlay/bookmarks/BookmarkButton.java new file mode 100644 index 000000000..7d9296075 --- /dev/null +++ b/src/main/java/mezz/jei/gui/overlay/bookmarks/BookmarkButton.java @@ -0,0 +1,58 @@ +package mezz.jei.gui.overlay.bookmarks; + +import mezz.jei.api.gui.IDrawable; +import mezz.jei.api.gui.IDrawableStatic; +import mezz.jei.bookmarks.BookmarkList; +import mezz.jei.config.Config; +import mezz.jei.config.KeyBindings; +import mezz.jei.gui.GuiHelper; +import mezz.jei.gui.elements.GuiIconToggleButton; +import mezz.jei.util.Translator; +import net.minecraft.client.settings.KeyBinding; +import net.minecraft.util.text.TextFormatting; + +import java.util.List; + +public class BookmarkButton extends GuiIconToggleButton { + public static BookmarkButton create(BookmarkOverlay bookmarkOverlay, BookmarkList bookmarkList, GuiHelper guiHelper) { + IDrawableStatic offIcon = guiHelper.getBookmarkButtonDisabledIcon(); + IDrawableStatic onIcon = guiHelper.getBookmarkButtonEnabledIcon(); + return new BookmarkButton(offIcon, onIcon, bookmarkOverlay, bookmarkList); + } + + private final BookmarkOverlay bookmarkOverlay; + private final BookmarkList bookmarkList; + + private BookmarkButton(IDrawable offIcon, IDrawable onIcon, BookmarkOverlay bookmarkOverlay, BookmarkList bookmarkList) { + super(offIcon, onIcon); + this.bookmarkOverlay = bookmarkOverlay; + this.bookmarkList = bookmarkList; + } + + @Override + protected void getTooltips(List tooltip) { + tooltip.add(Translator.translateToLocal("jei.tooltip.bookmarks")); + KeyBinding bookmarkKey = KeyBindings.bookmark; + if (bookmarkKey.getKeyCode() == 0) { + tooltip.add(TextFormatting.RED + Translator.translateToLocal("jei.tooltip.bookmarks.usage.nokey")); + } else if (!bookmarkOverlay.hasRoom()) { + tooltip.add(TextFormatting.GOLD + Translator.translateToLocal("jei.tooltip.bookmarks.not.enough.space")); + } else { + tooltip.add(TextFormatting.GRAY + Translator.translateToLocalFormatted("jei.tooltip.bookmarks.usage.key", bookmarkKey.getDisplayName())); + } + } + + @Override + protected boolean isIconToggledOn() { + return Config.isBookmarkOverlayEnabled() && !bookmarkList.isEmpty() && bookmarkOverlay.hasRoom(); + } + + @Override + protected boolean onMouseClicked(int mouseX, int mouseY) { + if (!bookmarkList.isEmpty() && bookmarkOverlay.hasRoom()) { + Config.toggleBookmarkEnabled(); + return true; + } + return false; + } +} diff --git a/src/main/java/mezz/jei/gui/overlay/bookmarks/BookmarkOverlay.java b/src/main/java/mezz/jei/gui/overlay/bookmarks/BookmarkOverlay.java new file mode 100644 index 000000000..da358d9c3 --- /dev/null +++ b/src/main/java/mezz/jei/gui/overlay/bookmarks/BookmarkOverlay.java @@ -0,0 +1,165 @@ +package mezz.jei.gui.overlay.bookmarks; + +import mezz.jei.bookmarks.BookmarkList; +import mezz.jei.config.Config; +import mezz.jei.gui.GuiHelper; +import mezz.jei.gui.elements.GuiIconToggleButton; +import mezz.jei.gui.overlay.GridAlignment; +import mezz.jei.gui.overlay.IngredientGrid; +import mezz.jei.gui.overlay.IngredientGridWithNavigation; +import mezz.jei.gui.recipes.RecipesGui; +import mezz.jei.input.IClickedIngredient; +import mezz.jei.input.IShowsRecipeFocuses; +import mezz.jei.util.CommandUtil; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nullable; +import java.awt.Rectangle; + +public class BookmarkOverlay implements IShowsRecipeFocuses, ILeftAreaContent { + private static final int BUTTON_SIZE = 20; + + // areas + private Rectangle parentArea; + private Rectangle displayArea = new Rectangle(); + + // display elements + private final IngredientGridWithNavigation contents; + private final GuiIconToggleButton bookmarkButton; + + // visibility + private boolean hasRoom = false; + + // data + private final BookmarkList bookmarkList; + + public BookmarkOverlay(Rectangle area, BookmarkList bookmarkList, GuiHelper guiHelper) { + this.parentArea = area; + this.bookmarkList = bookmarkList; + this.bookmarkButton = BookmarkButton.create(this, bookmarkList, guiHelper); + this.contents = new IngredientGridWithNavigation(bookmarkList, GridAlignment.RIGHT); + bookmarkList.addListener(() -> contents.updateLayout(true)); + } + + private boolean isListDisplayed() { + return Config.isBookmarkOverlayEnabled() && hasRoom && !bookmarkList.isEmpty(); + } + + public boolean hasRoom() { + return hasRoom; + } + + @Override + public void updateBounds(Rectangle area) { + this.parentArea = area; + hasRoom = updateBounds(); + } + + @Override + public void drawScreen(Minecraft minecraft, int mouseX, int mouseY, float partialTicks) { + if (this.hasRoom && Config.isBookmarkOverlayEnabled()) { + this.contents.draw(minecraft, mouseX, mouseY, partialTicks); + } + this.bookmarkButton.draw(minecraft, mouseX, mouseY, partialTicks); + } + + @Override + public void drawOnForeground(GuiContainer gui, int mouseX, int mouseY) { + } + + @Override + public void drawTooltips(Minecraft minecraft, int mouseX, int mouseY) { + if (isListDisplayed()) { + this.contents.drawTooltips(minecraft, mouseX, mouseY); + } + bookmarkButton.drawTooltips(minecraft, mouseX, mouseY); + } + + private static int getMinWidth() { + return Math.max(4 * BUTTON_SIZE, Config.smallestNumColumns * IngredientGrid.INGREDIENT_WIDTH); + } + + public boolean updateBounds() { + displayArea = new Rectangle(parentArea); + + final int minWidth = getMinWidth(); + if (displayArea.width < minWidth) { + return false; + } + + Rectangle availableContentsArea = new Rectangle( + displayArea.x, + displayArea.y, + displayArea.width, + displayArea.height - (BUTTON_SIZE + 4) + ); + this.contents.updateBounds(availableContentsArea, minWidth); + + // update area to match contents size + Rectangle contentsArea = this.contents.getArea(); + displayArea.x = contentsArea.x; + displayArea.width = contentsArea.width; + + this.bookmarkButton.updateBounds(new Rectangle( + displayArea.x, + (int) Math.floor(displayArea.getMaxY()) - BUTTON_SIZE - 2, + BUTTON_SIZE, + BUTTON_SIZE + )); + + this.contents.updateLayout(false); + + return true; + } + + @Override + @Nullable + public IClickedIngredient getIngredientUnderMouse(int mouseX, int mouseY) { + if (isListDisplayed()) { + return this.contents.getIngredientUnderMouse(mouseX, mouseY); + } + return null; + } + + @Override + public boolean canSetFocusWithMouse() { + return this.isListDisplayed() && this.contents.canSetFocusWithMouse(); + } + + @Override + public boolean handleMouseScrolled(int mouseX, int mouseY, int scrollDelta) { + return isListDisplayed() && + displayArea.contains(mouseX, mouseY) && + this.contents.handleMouseScrolled(mouseX, mouseY, scrollDelta); + } + + @Override + public boolean handleMouseClicked(int mouseX, int mouseY, int mouseButton) { + if (displayArea.contains(mouseX, mouseY)) { + Minecraft minecraft = Minecraft.getMinecraft(); + GuiScreen currentScreen = minecraft.currentScreen; + if (currentScreen != null && !(currentScreen instanceof RecipesGui) + && (mouseButton == 0 || mouseButton == 1 || minecraft.gameSettings.keyBindPickBlock.isActiveAndMatches(mouseButton - 100))) { + IClickedIngredient clicked = getIngredientUnderMouse(mouseX, mouseY); + if (clicked != null) { + if (Config.isCheatItemsEnabled()) { + ItemStack itemStack = clicked.getCheatItemStack(); + if (!itemStack.isEmpty()) { + CommandUtil.giveStack(itemStack, mouseButton); + } + clicked.onClickHandled(); + return true; + } + } + } + } + if (bookmarkButton.isMouseOver(mouseX, mouseY)) { + return bookmarkButton.handleMouseClick(mouseX, mouseY); + } + return this.contents.handleMouseClicked(mouseX, mouseY, mouseButton); + } + +} diff --git a/src/main/java/mezz/jei/gui/overlay/bookmarks/ILeftAreaContent.java b/src/main/java/mezz/jei/gui/overlay/bookmarks/ILeftAreaContent.java new file mode 100644 index 000000000..97bd18922 --- /dev/null +++ b/src/main/java/mezz/jei/gui/overlay/bookmarks/ILeftAreaContent.java @@ -0,0 +1,23 @@ +package mezz.jei.gui.overlay.bookmarks; + +import mezz.jei.input.IShowsRecipeFocuses; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.inventory.GuiContainer; + +import java.awt.Rectangle; + +public interface ILeftAreaContent extends IShowsRecipeFocuses { + + void drawScreen(Minecraft minecraft, int mouseX, int mouseY, float partialTicks); + + void drawOnForeground(GuiContainer gui, int mouseX, int mouseY); + + void drawTooltips(Minecraft minecraft, int mouseX, int mouseY); + + void updateBounds(Rectangle area); + + boolean handleMouseScrolled(int mouseX, int mouseY, int dWheel); + + boolean handleMouseClicked(int mouseX, int mouseY, int mouseButton); + +} diff --git a/src/main/java/mezz/jei/gui/overlay/bookmarks/LeftAreaDispatcher.java b/src/main/java/mezz/jei/gui/overlay/bookmarks/LeftAreaDispatcher.java new file mode 100644 index 000000000..9ed3312ba --- /dev/null +++ b/src/main/java/mezz/jei/gui/overlay/bookmarks/LeftAreaDispatcher.java @@ -0,0 +1,196 @@ +package mezz.jei.gui.overlay.bookmarks; + +import mezz.jei.api.gui.IGuiProperties; +import mezz.jei.gui.PageNavigation; +import mezz.jei.gui.recipes.RecipesGui; +import mezz.jei.input.IClickedIngredient; +import mezz.jei.input.IPaged; +import mezz.jei.input.IShowsRecipeFocuses; +import mezz.jei.runtime.JeiRuntime; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.inventory.GuiContainer; + +import javax.annotation.Nullable; +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.List; + +public class LeftAreaDispatcher implements IShowsRecipeFocuses, IPaged { + + private static final int BORDER_PADDING = 2; + private static final int NAVIGATION_HEIGHT = 20; + + private final List contents = new ArrayList<>(); + private final JeiRuntime runtime; + private int current = 0; + @Nullable + private IGuiProperties guiProperties; + private Rectangle naviArea = new Rectangle(); + private Rectangle displayArea = new Rectangle(); + private final PageNavigation navigation; + private boolean canShow = false; + + public LeftAreaDispatcher(JeiRuntime runtime) { + this.runtime = runtime; + this.navigation = new PageNavigation(this, false); + } + + public void addContent(ILeftAreaContent content) { + this.contents.add(content); + } + + private boolean hasContent() { + return current >= 0 && current < contents.size(); + } + + public void drawScreen(Minecraft minecraft, int mouseX, int mouseY, float partialTicks) { + if (canShow && hasContent()) { + contents.get(current).drawScreen(minecraft, mouseX, mouseY, partialTicks); + if (naviArea.height > 0) { + navigation.draw(minecraft, mouseX, mouseY, partialTicks); + } + } + } + + public void drawOnForeground(GuiContainer gui, int mouseX, int mouseY) { + if (canShow && hasContent()) { + contents.get(current).drawOnForeground(gui, mouseX, mouseY); + } + } + + public void drawTooltips(Minecraft minecraft, int mouseX, int mouseY) { + if (canShow && hasContent()) { + contents.get(current).drawTooltips(minecraft, mouseX, mouseY); + } + } + + public void updateScreen(@Nullable GuiScreen guiScreen) { + canShow = false; + if (hasContent()) { + IGuiProperties currentGuiProperties = runtime.getGuiProperties(guiScreen); + if (currentGuiProperties == null) { + guiProperties = null; + } else if (!areGuiPropertiesEqual(guiProperties, currentGuiProperties)) { + guiProperties = currentGuiProperties; + makeDisplayArea(guiProperties); + contents.get(current).updateBounds(displayArea); + canShow = true; + } else { + canShow = true; + } + } + } + + private static boolean areGuiPropertiesEqual(@Nullable IGuiProperties guiProperties1, IGuiProperties guiProperties2) { + return guiProperties1 != null && guiProperties1.getGuiClass().equals(guiProperties2.getGuiClass()) + && guiProperties1.getGuiLeft() == guiProperties2.getGuiLeft() && guiProperties1.getGuiXSize() == guiProperties2.getGuiXSize() + && guiProperties1.getScreenWidth() == guiProperties2.getScreenWidth() && guiProperties1.getScreenHeight() == guiProperties2.getScreenHeight(); + } + + private void makeDisplayArea(IGuiProperties guiProperties) { + final int x = BORDER_PADDING; + final int y = BORDER_PADDING; + int width = guiProperties.getGuiLeft() - x - BORDER_PADDING; + final int height = guiProperties.getScreenHeight() - y - BORDER_PADDING; + if (guiProperties.getGuiClass() == RecipesGui.class) { + // TODO: JEI doesn't define an exclusion area for its own side-tabs at the moment + width -= 22; + } + displayArea = new Rectangle(x, y, width, height); + if (contents.size() > 1) { + naviArea = new Rectangle(displayArea); + naviArea.height = NAVIGATION_HEIGHT; + displayArea.y += NAVIGATION_HEIGHT + BORDER_PADDING; + displayArea.height -= NAVIGATION_HEIGHT + BORDER_PADDING; + navigation.updateBounds(naviArea); + } else { + naviArea = new Rectangle(); + } + } + + @Override + @Nullable + public IClickedIngredient getIngredientUnderMouse(int mouseX, int mouseY) { + if (canShow && hasContent()) { + return contents.get(current).getIngredientUnderMouse(mouseX, mouseY); + } + return null; + } + + @Override + public boolean canSetFocusWithMouse() { + if (canShow && hasContent()) { + return contents.get(current).canSetFocusWithMouse(); + } + return false; + } + + public boolean handleMouseScrolled(int mouseX, int mouseY, int dWheel) { + if (canShow && hasContent()) { + if (displayArea.contains(mouseX, mouseY)) { + return contents.get(current).handleMouseScrolled(mouseX, mouseY, dWheel); + } else if (naviArea.contains(mouseX, mouseY)) { + if (dWheel < 0) { + nextPage(); + } else { + previousPage(); + } + return true; + } + } + return false; + } + + public boolean handleMouseClicked(int mouseX, int mouseY, int mouseButton) { + if (canShow && hasContent()) { + if (displayArea.contains(mouseX, mouseY)) { + return contents.get(current).handleMouseClicked(mouseX, mouseY, mouseButton); + } else if (naviArea.contains(mouseX, mouseY)) { + return navigation.handleMouseClickedButtons(mouseX, mouseY); + } + } + return false; + } + + @Override + public boolean nextPage() { + current++; + if (current >= contents.size()) { + current = 0; + } + navigation.updatePageState(); + return true; + } + + @Override + public boolean previousPage() { + current--; + if (current < 0) { + current = contents.size(); + } + navigation.updatePageState(); + return true; + } + + @Override + public boolean hasNext() { + return current < contents.size() - 1; + } + + @Override + public boolean hasPrevious() { + return current > 0; + } + + @Override + public int getPageCount() { + return contents.size(); + } + + @Override + public int getPageNumber() { + return current; + } + +} diff --git a/src/main/java/mezz/jei/gui/overlay/bookmarks/package-info.java b/src/main/java/mezz/jei/gui/overlay/bookmarks/package-info.java new file mode 100644 index 000000000..ae28fcb84 --- /dev/null +++ b/src/main/java/mezz/jei/gui/overlay/bookmarks/package-info.java @@ -0,0 +1,9 @@ +@ParametersAreNonnullByDefault +@FieldsAreNonnullByDefault +@MethodsReturnNonnullByDefault +package mezz.jei.gui.overlay.bookmarks; + +import mcp.MethodsReturnNonnullByDefault; +import mezz.jei.util.FieldsAreNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/mezz/jei/ingredients/IngredientFilter.java b/src/main/java/mezz/jei/ingredients/IngredientFilter.java index e7e791c20..4d9f59f1c 100644 --- a/src/main/java/mezz/jei/ingredients/IngredientFilter.java +++ b/src/main/java/mezz/jei/ingredients/IngredientFilter.java @@ -1,28 +1,17 @@ package mezz.jei.ingredients; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import com.google.common.collect.ImmutableList; import it.unimi.dsi.fastutil.chars.Char2ObjectMap; import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; -import mezz.jei.Internal; import mezz.jei.api.IIngredientFilter; import mezz.jei.api.ingredients.IIngredientHelper; import mezz.jei.config.Config; import mezz.jei.config.EditModeToggleEvent; import mezz.jei.gui.ingredients.IIngredientListElement; -import mezz.jei.gui.overlay.IngredientListOverlay; -import mezz.jei.runtime.JeiRuntime; +import mezz.jei.gui.overlay.IIngredientGridSource; import mezz.jei.startup.PlayerJoinedWorldEvent; import mezz.jei.suffixtree.CombinedSearchTrees; import mezz.jei.suffixtree.GeneralizedSuffixTree; @@ -34,8 +23,16 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; -public class IngredientFilter implements IIngredientFilter { +public class IngredientFilter implements IIngredientFilter, IIngredientGridSource { private static final Pattern QUOTE_PATTERN = Pattern.compile("\""); private static final Pattern FILTER_SPLIT_PATTERN = Pattern.compile("(-?\".*?(?:\"|$)|\\S+)"); @@ -53,6 +50,7 @@ public class IngredientFilter implements IIngredientFilter { @Nullable private String filterCached; private List ingredientListCached = Collections.emptyList(); + private final List listeners = new ArrayList<>(); public IngredientFilter(IngredientBlacklistInternal blacklist) { this.blacklist = blacklist; @@ -180,6 +178,7 @@ public void updateHiddenState(IIngredientListElement element) { } } + @Override public List getIngredientList() { String filterText = Translator.toLowercaseWithLocale(Config.getFilterText()); if (!filterText.equals(filterCached)) { @@ -211,11 +210,7 @@ public String getFilterText() { public void setFilterText(String filterText) { ErrorUtil.checkNotNull(filterText, "filterText"); if (Config.setFilterText(filterText)) { - JeiRuntime runtime = Internal.getRuntime(); - if (runtime != null) { - IngredientListOverlay ingredientListOverlay = runtime.getIngredientListOverlay(); - ingredientListOverlay.onSetFilterText(filterText); - } + notifyListenersOfChange(); } } @@ -379,7 +374,19 @@ private static IntSet intersection(IntSet set1, IntSet set2) { } } + @Override public int size() { return getIngredientList().size(); } + + @Override + public void addListener(IIngredientGridSource.Listener listener) { + listeners.add(listener); + } + + private void notifyListenersOfChange() { + for (IIngredientGridSource.Listener listener : listeners) { + listener.onChange(); + } + } } diff --git a/src/main/java/mezz/jei/ingredients/IngredientListElement.java b/src/main/java/mezz/jei/ingredients/IngredientListElement.java index bcaad3556..204ed9ac2 100644 --- a/src/main/java/mezz/jei/ingredients/IngredientListElement.java +++ b/src/main/java/mezz/jei/ingredients/IngredientListElement.java @@ -1,17 +1,5 @@ package mezz.jei.ingredients; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - import com.google.common.collect.ImmutableSet; import mezz.jei.api.ingredients.IIngredientHelper; import mezz.jei.api.ingredients.IIngredientRenderer; @@ -26,10 +14,18 @@ import net.minecraft.item.ItemStack; import net.minecraftforge.oredict.OreDictionary; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + public class IngredientListElement implements IIngredientListElement { private static final Pattern SPACE_PATTERN = Pattern.compile("\\s"); - private static final Map WILDCARD_ADDED_ORDER = new HashMap<>(); - private static int ADDED_INDEX = 0; private final V ingredient; private final int orderIndex; @@ -42,18 +38,8 @@ public class IngredientListElement implements IIngredientListElement { private boolean visible = true; @Nullable - public static IngredientListElement create(V ingredient, IIngredientHelper ingredientHelper, IIngredientRenderer ingredientRenderer, IModIdHelper modIdHelper) { + public static IngredientListElement create(V ingredient, IIngredientHelper ingredientHelper, IIngredientRenderer ingredientRenderer, IModIdHelper modIdHelper, int orderIndex) { try { - final int orderIndex; - String uid = ingredientHelper.getWildcardId(ingredient); - if (WILDCARD_ADDED_ORDER.containsKey(uid)) { - orderIndex = WILDCARD_ADDED_ORDER.get(uid); - } else { - WILDCARD_ADDED_ORDER.put(uid, ADDED_INDEX); - orderIndex = ADDED_INDEX; - ADDED_INDEX++; - } - return new IngredientListElement<>(ingredient, orderIndex, ingredientHelper, ingredientRenderer, modIdHelper); } catch (RuntimeException e) { try { diff --git a/src/main/java/mezz/jei/ingredients/IngredientListElementFactory.java b/src/main/java/mezz/jei/ingredients/IngredientListElementFactory.java index f08fbb999..b3b8fe75d 100644 --- a/src/main/java/mezz/jei/ingredients/IngredientListElementFactory.java +++ b/src/main/java/mezz/jei/ingredients/IngredientListElementFactory.java @@ -13,6 +13,8 @@ import java.util.Collection; public final class IngredientListElementFactory { + private static IngredientOrderTracker ORDER_TRACKER = new IngredientOrderTracker(); + private IngredientListElementFactory() { } @@ -34,7 +36,8 @@ public static NonNullList> createList(IIngredientR NonNullList> list = NonNullList.create(); for (V ingredient : ingredients) { if (ingredient != null) { - IngredientListElement ingredientListElement = IngredientListElement.create(ingredient, ingredientHelper, ingredientRenderer, modIdHelper); + int orderIndex = ORDER_TRACKER.getOrderIndex(ingredient, ingredientHelper); + IngredientListElement ingredientListElement = IngredientListElement.create(ingredient, ingredientHelper, ingredientRenderer, modIdHelper, orderIndex); if (ingredientListElement != null) { list.add(ingredientListElement); } @@ -44,10 +47,10 @@ public static NonNullList> createList(IIngredientR } @Nullable - public static IIngredientListElement createElement(IIngredientRegistry ingredientRegistry, IIngredientType ingredientType, V ingredient, IModIdHelper modIdHelper) { + public static IIngredientListElement createUnorderedElement(IIngredientRegistry ingredientRegistry, IIngredientType ingredientType, V ingredient, IModIdHelper modIdHelper) { IIngredientHelper ingredientHelper = ingredientRegistry.getIngredientHelper(ingredientType); IIngredientRenderer ingredientRenderer = ingredientRegistry.getIngredientRenderer(ingredientType); - return IngredientListElement.create(ingredient, ingredientHelper, ingredientRenderer, modIdHelper); + return IngredientListElement.create(ingredient, ingredientHelper, ingredientRenderer, modIdHelper, 0); } private static void addToBaseList(NonNullList baseList, IIngredientRegistry ingredientRegistry, IIngredientType ingredientType, IModIdHelper modIdHelper) { @@ -59,7 +62,8 @@ private static void addToBaseList(NonNullList baseLi for (V ingredient : ingredients) { progressBar.step(""); if (ingredient != null) { - IngredientListElement ingredientListElement = IngredientListElement.create(ingredient, ingredientHelper, ingredientRenderer, modIdHelper); + int orderIndex = ORDER_TRACKER.getOrderIndex(ingredient, ingredientHelper); + IngredientListElement ingredientListElement = IngredientListElement.create(ingredient, ingredientHelper, ingredientRenderer, modIdHelper, orderIndex); if (ingredientListElement != null) { baseList.add(ingredientListElement); } diff --git a/src/main/java/mezz/jei/ingredients/IngredientOrderTracker.java b/src/main/java/mezz/jei/ingredients/IngredientOrderTracker.java new file mode 100644 index 000000000..87e549711 --- /dev/null +++ b/src/main/java/mezz/jei/ingredients/IngredientOrderTracker.java @@ -0,0 +1,23 @@ +package mezz.jei.ingredients; + +import mezz.jei.api.ingredients.IIngredientHelper; + +import java.util.HashMap; +import java.util.Map; + +public class IngredientOrderTracker { + private final Map wildcardAddedOrder = new HashMap<>(); + private int addedIndex = 0; + + public int getOrderIndex(V ingredient, IIngredientHelper ingredientHelper) { + String uid = ingredientHelper.getWildcardId(ingredient); + if (wildcardAddedOrder.containsKey(uid)) { + return wildcardAddedOrder.get(uid); + } else { + int index = addedIndex; + wildcardAddedOrder.put(uid, index); + addedIndex++; + return index; + } + } +} diff --git a/src/main/java/mezz/jei/ingredients/IngredientRegistry.java b/src/main/java/mezz/jei/ingredients/IngredientRegistry.java index 936842dfe..4b4f3d774 100644 --- a/src/main/java/mezz/jei/ingredients/IngredientRegistry.java +++ b/src/main/java/mezz/jei/ingredients/IngredientRegistry.java @@ -19,6 +19,7 @@ import net.minecraft.tileentity.TileEntityFurnace; import net.minecraft.util.NonNullList; +import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -105,6 +106,17 @@ public Collection getAllIngredients(IIngredientType ingredientType) { } } + @Nullable + public V getIngredientByUid(IIngredientType ingredientType, String uid) { + @SuppressWarnings("unchecked") + IngredientSet ingredients = ingredientsMap.get(ingredientType); + if (ingredients == null) { + return null; + } else { + return ingredients.getByUid(uid); + } + } + @Override @Deprecated public Collection getAllIngredients(Class ingredientClass) { @@ -333,7 +345,7 @@ public void removeIngredientsAtRuntime(IIngredientType ingredientType, Co public boolean isIngredientVisible(V ingredient, IngredientFilter ingredientFilter) { IIngredientType ingredientType = getIngredientType(ingredient); - IIngredientListElement element = IngredientListElementFactory.createElement(this, ingredientType, ingredient, modIdHelper); + IIngredientListElement element = IngredientListElementFactory.createUnorderedElement(this, ingredientType, ingredient, modIdHelper); if (element == null) { return false; } diff --git a/src/main/java/mezz/jei/input/InputHandler.java b/src/main/java/mezz/jei/input/InputHandler.java index a7b8c7bd0..6693ade06 100644 --- a/src/main/java/mezz/jei/input/InputHandler.java +++ b/src/main/java/mezz/jei/input/InputHandler.java @@ -1,19 +1,17 @@ package mezz.jei.input; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.List; - import it.unimi.dsi.fastutil.ints.IntArraySet; import it.unimi.dsi.fastutil.ints.IntSet; import mezz.jei.api.ingredients.IIngredientHelper; import mezz.jei.api.ingredients.IIngredientRegistry; import mezz.jei.api.recipe.IFocus; +import mezz.jei.bookmarks.BookmarkList; import mezz.jei.config.Config; import mezz.jei.config.IngredientBlacklistType; import mezz.jei.config.KeyBindings; import mezz.jei.gui.Focus; import mezz.jei.gui.overlay.IngredientListOverlay; +import mezz.jei.gui.overlay.bookmarks.LeftAreaDispatcher; import mezz.jei.gui.recipes.RecipeClickableArea; import mezz.jei.gui.recipes.RecipesGui; import mezz.jei.ingredients.IngredientFilter; @@ -29,24 +27,33 @@ import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + public class InputHandler { private final RecipeRegistry recipeRegistry; private final IIngredientRegistry ingredientRegistry; private final IngredientFilter ingredientFilter; private final RecipesGui recipesGui; private final IngredientListOverlay ingredientListOverlay; + private final LeftAreaDispatcher leftAreaDispatcher; + private final BookmarkList bookmarkList; private final List showsRecipeFocuses = new ArrayList<>(); private final IntSet clickHandled = new IntArraySet(); - public InputHandler(JeiRuntime runtime, IngredientListOverlay ingredientListOverlay) { + public InputHandler(JeiRuntime runtime, IngredientListOverlay ingredientListOverlay, LeftAreaDispatcher leftAreaDispatcher, BookmarkList bookmarkList) { this.recipeRegistry = runtime.getRecipeRegistry(); this.ingredientRegistry = runtime.getIngredientRegistry(); this.ingredientFilter = runtime.getIngredientFilter(); this.recipesGui = runtime.getRecipesGui(); this.ingredientListOverlay = ingredientListOverlay; + this.leftAreaDispatcher = leftAreaDispatcher; + this.bookmarkList = bookmarkList; this.showsRecipeFocuses.add(recipesGui); this.showsRecipeFocuses.add(ingredientListOverlay); + this.showsRecipeFocuses.add(leftAreaDispatcher); this.showsRecipeFocuses.add(new GuiContainerWrapper()); } @@ -102,7 +109,7 @@ public boolean handleMouseEvent(GuiScreen guiScreen, int mouseX, int mouseY) { } private boolean handleMouseScroll(int dWheel, int mouseX, int mouseY) { - return ingredientListOverlay.handleMouseScrolled(mouseX, mouseY, dWheel); + return ingredientListOverlay.handleMouseScrolled(mouseX, mouseY, dWheel) || leftAreaDispatcher.handleMouseScrolled(mouseX, mouseY, dWheel); } private boolean handleMouseClick(GuiScreen guiScreen, int mouseButton, int mouseX, int mouseY) { @@ -113,6 +120,9 @@ private boolean handleMouseClick(GuiScreen guiScreen, int mouseButton, int mouse if (ingredientListOverlay.handleMouseClicked(mouseX, mouseY, mouseButton)) { return true; } + if (leftAreaDispatcher.handleMouseClicked(mouseX, mouseY, mouseButton)) { + return true; + } if (clicked != null && handleMouseClickedFocus(mouseButton, clicked)) { return true; @@ -230,19 +240,38 @@ private boolean handleGlobalKeybinds(int eventKey) { Config.toggleOverlayEnabled(); return false; } + if (KeyBindings.toggleBookmarkOverlay.isActiveAndMatches(eventKey)) { + Config.toggleBookmarkEnabled(); + return false; + } return ingredientListOverlay.onGlobalKeyPressed(eventKey); } private boolean handleFocusKeybinds(int eventKey) { final boolean showRecipe = KeyBindings.showRecipe.isActiveAndMatches(eventKey); final boolean showUses = KeyBindings.showUses.isActiveAndMatches(eventKey); - if (showRecipe || showUses) { + final boolean bookmark = KeyBindings.bookmark.isActiveAndMatches(eventKey); + if (showRecipe || showUses || bookmark) { IClickedIngredient clicked = getIngredientUnderMouseForKey(MouseHelper.getX(), MouseHelper.getY()); if (clicked != null) { - IFocus.Mode mode = showRecipe ? IFocus.Mode.OUTPUT : IFocus.Mode.INPUT; - recipesGui.show(new Focus(mode, clicked.getValue())); - clicked.onClickHandled(); - return true; + if (bookmark) { + if (bookmarkList.remove(clicked.getValue())) { + if (bookmarkList.isEmpty() && Config.isBookmarkOverlayEnabled()) { + Config.toggleBookmarkEnabled(); + } + return true; + } else { + if (!Config.isBookmarkOverlayEnabled()) { + Config.toggleBookmarkEnabled(); + } + return bookmarkList.add(clicked.getValue()); + } + } else { + IFocus.Mode mode = showRecipe ? IFocus.Mode.OUTPUT : IFocus.Mode.INPUT; + recipesGui.show(new Focus(mode, clicked.getValue())); + clicked.onClickHandled(); + return true; + } } } return false; diff --git a/src/main/java/mezz/jei/startup/JeiStarter.java b/src/main/java/mezz/jei/startup/JeiStarter.java index c97612894..4fcbf1696 100644 --- a/src/main/java/mezz/jei/startup/JeiStarter.java +++ b/src/main/java/mezz/jei/startup/JeiStarter.java @@ -7,10 +7,13 @@ import mezz.jei.api.gui.IAdvancedGuiHandler; import mezz.jei.api.gui.IGhostIngredientHandler; import mezz.jei.api.gui.IGuiScreenHandler; +import mezz.jei.bookmarks.BookmarkList; import mezz.jei.config.Config; import mezz.jei.gui.GuiEventHandler; import mezz.jei.gui.ingredients.IIngredientListElement; import mezz.jei.gui.overlay.IngredientListOverlay; +import mezz.jei.gui.overlay.bookmarks.BookmarkOverlay; +import mezz.jei.gui.overlay.bookmarks.LeftAreaDispatcher; import mezz.jei.gui.recipes.RecipesGui; import mezz.jei.ingredients.IngredientBlacklistInternal; import mezz.jei.ingredients.IngredientFilter; @@ -26,6 +29,7 @@ import net.minecraft.util.NonNullList; import net.minecraftforge.fml.common.ProgressManager; +import java.awt.Rectangle; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -78,11 +82,19 @@ public void start(List plugins) { Internal.setIngredientFilter(ingredientFilter); timer.stop(); + timer.start("Building bookmarks"); + BookmarkList bookmarkList = new BookmarkList(ingredientRegistry); + bookmarkList.loadBookmarks(); + Internal.setBookmarkList(bookmarkList); + timer.stop(); + timer.start("Building runtime"); List> advancedGuiHandlers = modRegistry.getAdvancedGuiHandlers(); Map guiScreenHandlers = modRegistry.getGuiScreenHandlers(); Map ghostIngredientHandlers = modRegistry.getGhostIngredientHandlers(); IngredientListOverlay ingredientListOverlay = new IngredientListOverlay(ingredientFilter); + + BookmarkOverlay bookmarkOverlay = new BookmarkOverlay(new Rectangle(), bookmarkList, jeiHelpers.getGuiHelper()); RecipesGui recipesGui = new RecipesGui(recipeRegistry); JeiRuntime jeiRuntime = new JeiRuntime(recipeRegistry, ingredientListOverlay, recipesGui, ingredientRegistry, advancedGuiHandlers, guiScreenHandlers, ghostIngredientHandlers, ingredientFilter); Internal.setRuntime(jeiRuntime); @@ -92,9 +104,12 @@ public void start(List plugins) { sendRuntime(plugins, jeiRuntime); - GuiEventHandler guiEventHandler = new GuiEventHandler(ingredientListOverlay, recipeRegistry); + LeftAreaDispatcher leftAreaDispatcher = new LeftAreaDispatcher(jeiRuntime); + leftAreaDispatcher.addContent(bookmarkOverlay); + + GuiEventHandler guiEventHandler = new GuiEventHandler(leftAreaDispatcher, ingredientListOverlay, recipeRegistry); Internal.setGuiEventHandler(guiEventHandler); - InputHandler inputHandler = new InputHandler(jeiRuntime, ingredientListOverlay); + InputHandler inputHandler = new InputHandler(jeiRuntime, ingredientListOverlay, leftAreaDispatcher, bookmarkList); Internal.setInputHandler(inputHandler); Config.checkForModNameFormatOverride(); diff --git a/src/main/java/mezz/jei/util/IngredientSet.java b/src/main/java/mezz/jei/util/IngredientSet.java index bc46c0f8e..40b4fabe4 100644 --- a/src/main/java/mezz/jei/util/IngredientSet.java +++ b/src/main/java/mezz/jei/util/IngredientSet.java @@ -7,6 +7,7 @@ import mezz.jei.startup.StackHelper; import net.minecraft.item.ItemStack; +import javax.annotation.Nullable; import java.util.AbstractSet; import java.util.Collection; import java.util.Iterator; @@ -68,6 +69,11 @@ public boolean contains(Object o) { return ingredients.containsKey(uid); } + @Nullable + public V getByUid(String uid) { + return ingredients.get(uid); + } + @Override public void clear() { ingredients.clear(); diff --git a/src/main/resources/assets/jei/lang/en_us.lang b/src/main/resources/assets/jei/lang/en_us.lang index 43f1cb18a..072d555aa 100644 --- a/src/main/resources/assets/jei/lang/en_us.lang +++ b/src/main/resources/assets/jei/lang/en_us.lang @@ -17,6 +17,10 @@ jei.tooltip.recipe.id=Recipe ID: %s jei.tooltip.not.enough.space=There is not enough space to display JEI here. jei.tooltip.ingredient.list.disabled=The JEI overlay is disabled. jei.tooltip.ingredient.list.disabled.how.to.fix=Press %s to enable it. +jei.tooltip.bookmarks=JEI Bookmarks +jei.tooltip.bookmarks.usage.nokey=Add a key binding for JEI bookmarks in your Controls settings. +jei.tooltip.bookmarks.usage.key=Hover over an ingredient and press "%s" to bookmark it. +jei.tooltip.bookmarks.not.enough.space=There is not enough space to display bookmarks here. # Error Tooltips jei.tooltip.error.recipe.transfer.missing=Missing Items @@ -40,6 +44,8 @@ key.jei.recipeBack=Show Previously Viewed Recipe key.jei.toggleCheatMode=Toggle Cheating Mode key.jei.previousPage=Show Previous Page key.jei.nextPage=Show Next Page +key.jei.bookmark=Add/Remove a Bookmarked Ingredient +key.jei.toggleBookmarkOverlay=Show/Hide Bookmarked Ingredients # Config config.jei.default=Default @@ -59,6 +65,9 @@ config.jei.interface.comment=Options relating to the User Interface. config.jei.interface.overlayEnabled=Show Ingredient List config.jei.interface.overlayEnabled.comment=Show the list of ingredients next to open guis. +config.jei.interface.bookmarkOverlayEnabled=Show Bookmarks +config.jei.interface.bookmarkOverlayEnabled.comment=Show the list of bookmarks next to open guis. + config.jei.search=Search Options config.jei.search.comment=Options relating to the search bar. config.jei.search.modNameSearchMode=@ModName diff --git a/src/main/resources/assets/jei/textures/gui/gui_vanilla.png b/src/main/resources/assets/jei/textures/gui/gui_vanilla.png index f1b02b11afbdce8b4579e31376581d28aadc33e0..ac633be7331002426d66043df8ef8187686f9620 100644 GIT binary patch literal 1298 zcmZ8hdrVVz6h6OuZ%ZFk5rI{-a?wlXDv1|ct=7`gQX2;b2I_P=tJO^|mk&zB)>g1u ze45i|Stk@-iBOj@yFM7KTIeXBQ|V_5L*r(Lnhkb?^$Ws;`Te#TeG(fT2h@|nQ~*K| z6|o{dC&~PPcdSNa&oo|XMR#A{eQLFy-^>ZzGo$$Pc*mntI}ft7--iY*bU&H--Y*To zUj7TmeHR|XMDl{;b~SE$N65VD>yETi&fg?Y)&{}YWqUuZ18v6X063_0E+`3sdZ4TK zS3&9fz!Wg`1$0i;<_v1dz!rz4f-Gj6H;QZ)>On?kKIaZ7E;bam7u>_ zo5Ie%wlwoGCY;gp&;aYtTY?cTMx#!=UBq-xT&xRUbRG9(OuX4W)t(dk9sA9eQh`$+QXois9i2$HW&B!45t-86wQOq zOWM=VGubU|PK#?t6AuWOAH~p>y{O`BuTNt(J?=kE^kXQ(sHe*>LeNkLp>HQ(_=Tls zRj>288@(F~g&Q&B3XFvRVIs!J&~%5XIUubq^s0oi5tf*(34#0gzqAF(d`6H_Knm}bvXGN>M+wfds4rh3&k+?<$lE*7uc2K zU>?2&8a0$K)0eUIYrJ47p}%`a(-qeG^YIw||e4pCUp26D% z%Hc8i7S?TICrQMp0Vn$~3eDwG)eH~Gc|e+6Bd8Q{{(!-l$4lfn6OhBZkXgF!t0m8t zhrga)5w@d=cHf;!${}Y}( rD_FX~_HZbEiq&vvkn8pK8@*G;<)+@RB0>)BdFw`rqa*6WWrcqOU%c%Q literal 1302 zcmZ8hdrVVj6hB{YTiT)&A0Qx)3M;Efu?$&}ffNK0Wr#?Gi9=*qij4TGTIZl26r2e7 zqP&9QRmh@F5h+8*vra*jMXN3fgt|s2s4#{+6oh$q&5|X0@;krt{^um;&g9wg z0AOa=%HSvfM7sz$1Z`3;#75I5C^9@cgrcatygZtvQfYg8J4HDu6bh;yy23-KQL!%7 z_H5ex)(t>SWK{bBbq#(gv@i0ahv9OZW%+uuu4>n0-oB@FjFeFk>p<4mB?n+;g#`yj ztKtWZ1IH_UX0S~5VH@v1Dr;_{ej4&IhfN(H*8*EC8A%x3*>6#+%XLSb+ylpQT&;V}aE8bAa0yc|1>{UvkQ5?FszZZ z7|twoLybDjkgN=}E*NAOrc-oG<%G*QqQ=);aTqDMu^IOAJ|5hA`0}X)-nPP$5OHmC z<^d>0@|H!|>1%Q3kHF;BxgnNewq>eN5L~yE{o6^u>_K^~7QDU9Ik%bxt+sW{(lpp! z?5(d%I<9}>q@6nl^4&LD43E6&92n^AJk{sDJAQF{(8Ac9RVYIpA8_f>-Ma%*ef<9q!Z(!gP? z#kYV^aKBBLih8$l$J}bn=pKR;8cX2$Jqvg{cOKlX@p$d6|GnOV6w!$nhYrEmb9TwE zjY7lYsH>`$)bSlQ+EDbckPxlWT)I&Y4^!+Dwx%&4e23r$&$Niu zX!|EYDTnSg{?KhEb9hfn!Pr@b#(HWH_JvwLcEj&(0K-@hksJfn@o4-C;Om-%#mQ`t zvt&!ImS~{4j)9j$^9ei9S4qYz)MjHl_dpqn8lCne=b3{D9d)6rPH=uWf48jo0dbJw zw?(M(v|{Nk@Dg)isoWvOnv`5*Frhz_z;8>=(Ay$IFY_bIfP+5-l(+8GeRi>RR!_j` z>F`2fjXG$uERCR#8A6fC5r+Pw1Y79Tf+@b2(0?JzM-?d1`d|^c4Xf;W3CZLN1(iMG sMWyl&bLppJ$?gvLdBJPOBHI5@Oy-(fht}rvPQOowg+v5b2PI_v3svOwNB{r; diff --git a/src/main/resources/assets/jei/textures/gui/recipe_background_2.png b/src/main/resources/assets/jei/textures/gui/recipe_background_2.png index 58dcc0b07ee07e659e0df1b77e57c6aeb390cfd2..f46b3de98878763f6f32868349f14a9cc722e7f3 100644 GIT binary patch delta 3788 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 delta 3700 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