Bläddra i källkod

REI v2.3.1 (#40)

- API Changes
- Updated Config Screen
- Added Tipped Arrows Recipes
- Updated Mappings
- Added IRecipeHelper
- Turning things to Optional
- Removed Cheats button, now included with the new config button
- Buttons for switching gamemodes / time / weather (default: off) [Maybe not in this update]
- Clickable Labels
- 3+ recipes at the same time
- Fixed RecipeBaseWidget bad rendering when too big
- Fixed #42 Patched up item deleting & cheating
- Choose Page Dialog
Daniel She 6 år sedan
förälder
incheckning
ed5338d6d8
58 ändrade filer med 1610 tillägg och 511 borttagningar
  1. 4 4
      build.gradle
  2. 10 9
      src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java
  3. 11 0
      src/main/java/me/shedaniel/rei/api/IDisplaySettings.java
  4. 37 16
      src/main/java/me/shedaniel/rei/api/IRecipeCategory.java
  5. 2 1
      src/main/java/me/shedaniel/rei/api/IRecipeDisplay.java
  6. 44 0
      src/main/java/me/shedaniel/rei/api/IRecipeHelper.java
  7. 3 3
      src/main/java/me/shedaniel/rei/api/IRecipePlugin.java
  8. 24 10
      src/main/java/me/shedaniel/rei/client/ClientHelper.java
  9. 3 59
      src/main/java/me/shedaniel/rei/client/ConfigHelper.java
  10. 7 1
      src/main/java/me/shedaniel/rei/client/REIConfig.java
  11. 41 19
      src/main/java/me/shedaniel/rei/client/RecipeHelper.java
  12. 38 0
      src/main/java/me/shedaniel/rei/client/Weather.java
  13. 178 42
      src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java
  14. 156 74
      src/main/java/me/shedaniel/rei/gui/RecipeViewingScreen.java
  15. 126 45
      src/main/java/me/shedaniel/rei/gui/config/ConfigEntry.java
  16. 1 1
      src/main/java/me/shedaniel/rei/gui/config/ConfigEntryListWidget.java
  17. 179 32
      src/main/java/me/shedaniel/rei/gui/config/ConfigScreen.java
  18. 1 1
      src/main/java/me/shedaniel/rei/gui/credits/CreditsEntryListWidget.java
  19. 5 5
      src/main/java/me/shedaniel/rei/gui/credits/CreditsScreen.java
  20. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/ButtonWidget.java
  21. 36 0
      src/main/java/me/shedaniel/rei/gui/widget/ClickableLabelWidget.java
  22. 71 0
      src/main/java/me/shedaniel/rei/gui/widget/DraggableWidget.java
  23. 3 4
      src/main/java/me/shedaniel/rei/gui/widget/IWidget.java
  24. 10 6
      src/main/java/me/shedaniel/rei/gui/widget/ItemListOverlay.java
  25. 6 6
      src/main/java/me/shedaniel/rei/gui/widget/ItemSlotWidget.java
  26. 6 3
      src/main/java/me/shedaniel/rei/gui/widget/LabelWidget.java
  27. 18 3
      src/main/java/me/shedaniel/rei/gui/widget/RecipeBaseWidget.java
  28. 165 0
      src/main/java/me/shedaniel/rei/gui/widget/RecipeChoosePageWidget.java
  29. 113 0
      src/main/java/me/shedaniel/rei/gui/widget/RecipePageLabelWidget.java
  30. 5 4
      src/main/java/me/shedaniel/rei/gui/widget/TabWidget.java
  31. 59 68
      src/main/java/me/shedaniel/rei/gui/widget/TextFieldWidget.java
  32. 2 1
      src/main/java/me/shedaniel/rei/mixin/MixinClientPlayNetworkHandler.java
  33. 1 1
      src/main/java/me/shedaniel/rei/mixin/MixinContainerScreen.java
  34. 3 3
      src/main/java/me/shedaniel/rei/mixin/MixinCraftingTableScreen.java
  35. 3 3
      src/main/java/me/shedaniel/rei/mixin/MixinPlayerInventoryScreen.java
  36. 29 0
      src/main/java/me/shedaniel/rei/mixin/MixinTextureManager.java
  37. 3 2
      src/main/java/me/shedaniel/rei/plugin/DefaultBlastingDisplay.java
  38. 3 2
      src/main/java/me/shedaniel/rei/plugin/DefaultBrewingDisplay.java
  39. 6 4
      src/main/java/me/shedaniel/rei/plugin/DefaultCampfireDisplay.java
  40. 70 0
      src/main/java/me/shedaniel/rei/plugin/DefaultCustomDisplay.java
  41. 37 15
      src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java
  42. 4 2
      src/main/java/me/shedaniel/rei/plugin/DefaultShapedDisplay.java
  43. 3 2
      src/main/java/me/shedaniel/rei/plugin/DefaultShapelessDisplay.java
  44. 3 2
      src/main/java/me/shedaniel/rei/plugin/DefaultSmeltingDisplay.java
  45. 4 2
      src/main/java/me/shedaniel/rei/plugin/DefaultSmokingDisplay.java
  46. 6 4
      src/main/java/me/shedaniel/rei/plugin/DefaultStoneCuttingDisplay.java
  47. 1 1
      src/main/java/me/shedaniel/rei/update/UpdateChecker.java
  48. 5 5
      src/main/resources/assets/roughlyenoughitems/lang/de_de.json
  49. 5 5
      src/main/resources/assets/roughlyenoughitems/lang/en_ud.json
  50. 27 10
      src/main/resources/assets/roughlyenoughitems/lang/en_us.json
  51. 5 5
      src/main/resources/assets/roughlyenoughitems/lang/et_ee.json
  52. 7 7
      src/main/resources/assets/roughlyenoughitems/lang/fr_fr.json
  53. 5 5
      src/main/resources/assets/roughlyenoughitems/lang/lol_us.json
  54. 5 5
      src/main/resources/assets/roughlyenoughitems/lang/zh_cn.json
  55. 5 5
      src/main/resources/assets/roughlyenoughitems/lang/zh_tw.json
  56. BIN
      src/main/resources/assets/roughlyenoughitems/textures/gui/recipecontainer.png
  57. 1 1
      src/main/resources/fabric.mod.json
  58. 3 1
      src/main/resources/roughlyenoughitems.client.json

+ 4 - 4
build.gradle

@@ -6,11 +6,11 @@ sourceCompatibility = 1.8
 targetCompatibility = 1.8
 
 archivesBaseName = "RoughlyEnoughItems"
-version = "2.3.0.52"
+version = "2.3.1.53"
 
-def minecraftVersion = "19w08a"
-def yarnVersion = "19w08a.4"
-def fabricVersion = "0.2.2.103"
+def minecraftVersion = "19w08b"
+def yarnVersion = "19w08b.6"
+def fabricVersion = "0.2.3.104"
 def pluginLoaderVersion = "1.14-1.0.6-8"
 
 minecraft {

+ 10 - 9
src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java

@@ -3,6 +3,7 @@ package me.shedaniel.rei;
 import com.google.common.collect.Maps;
 import me.shedaniel.rei.api.IItemRegisterer;
 import me.shedaniel.rei.api.IPluginDisabler;
+import me.shedaniel.rei.api.IRecipeHelper;
 import me.shedaniel.rei.api.IRecipePlugin;
 import me.shedaniel.rei.client.ConfigHelper;
 import me.shedaniel.rei.client.GuiHelper;
@@ -19,7 +20,6 @@ import net.fabricmc.loader.FabricLoader;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.item.ItemStack;
 import net.minecraft.server.network.ServerPlayerEntity;
-import net.minecraft.sortme.ChatMessageType;
 import net.minecraft.text.StringTextComponent;
 import net.minecraft.text.TranslatableTextComponent;
 import net.minecraft.util.Identifier;
@@ -29,6 +29,7 @@ import org.apache.logging.log4j.Logger;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 public class RoughlyEnoughItemsCore implements ClientModInitializer, ModInitializer {
     
@@ -41,7 +42,7 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer, ModInitiali
     private static final Map<Identifier, IRecipePlugin> plugins = Maps.newHashMap();
     private static ConfigHelper configHelper;
     
-    public static RecipeHelper getRecipeHelper() {
+    public static IRecipeHelper getRecipeHelper() {
         return RECIPE_HELPER;
     }
     
@@ -68,11 +69,11 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer, ModInitiali
         return new LinkedList<>(plugins.values());
     }
     
-    public static Identifier getPluginIdentifier(IRecipePlugin plugin) {
+    public static Optional<Identifier> getPluginIdentifier(IRecipePlugin plugin) {
         for(Identifier identifier : plugins.keySet())
-            if (plugins.get(identifier).equals(plugin))
-                return identifier;
-        return null;
+            if (identifier != null && plugins.get(identifier).equals(plugin))
+                return Optional.of(identifier);
+        return Optional.empty();
     }
     
     @SuppressWarnings("deprecation")
@@ -87,7 +88,7 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer, ModInitiali
         }
         
         ClientTickCallback.EVENT.register(GuiHelper::onTick);
-        if (getConfigHelper().checkUpdates())
+        if (getConfigHelper().getConfig().checkUpdates)
             ClientTickCallback.EVENT.register(UpdateChecker::onTick);
         
         new UpdateChecker().onInitializeClient();
@@ -108,9 +109,9 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer, ModInitiali
             ServerPlayerEntity player = (ServerPlayerEntity) packetContext.getPlayer();
             ItemStack stack = packetByteBuf.readItemStack();
             if (player.inventory.insertStack(stack.copy()))
-                player.sendChatMessage(new StringTextComponent(I18n.translate("text.rei.cheat_items").replaceAll("\\{item_name}", stack.copy().getDisplayName().getFormattedText()).replaceAll("\\{item_count}", stack.copy().getAmount() + "").replaceAll("\\{player_name}", player.getEntityName())), ChatMessageType.SYSTEM);
+                player.addChatMessage(new StringTextComponent(I18n.translate("text.rei.cheat_items").replaceAll("\\{item_name}", stack.copy().getDisplayName().getFormattedText()).replaceAll("\\{item_count}", stack.copy().getAmount() + "").replaceAll("\\{player_name}", player.getEntityName())), false);
             else
-                player.sendChatMessage(new TranslatableTextComponent("text.rei.failed_cheat_items"), ChatMessageType.SYSTEM);
+                player.addChatMessage(new TranslatableTextComponent("text.rei.failed_cheat_items"), false);
         });
     }
     

+ 11 - 0
src/main/java/me/shedaniel/rei/api/IDisplaySettings.java

@@ -0,0 +1,11 @@
+package me.shedaniel.rei.api;
+
+public interface IDisplaySettings<T extends IRecipeDisplay> {
+    
+    public int getDisplayHeight(IRecipeCategory category);
+    
+    public int getDisplayWidth(IRecipeCategory category, T display);
+    
+    public int getMaximumRecipePerPage(IRecipeCategory category);
+    
+}

+ 37 - 16
src/main/java/me/shedaniel/rei/api/IRecipeCategory.java

@@ -1,12 +1,9 @@
 package me.shedaniel.rei.api;
 
-import com.mojang.blaze3d.platform.GlStateManager;
+import me.shedaniel.rei.gui.RecipeViewingScreen;
 import me.shedaniel.rei.gui.widget.IWidget;
 import me.shedaniel.rei.gui.widget.RecipeBaseWidget;
-import me.shedaniel.rei.gui.widget.RecipeViewingWidgetScreen;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.Drawable;
-import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.gui.DrawableHelper;
 import net.minecraft.item.ItemStack;
 import net.minecraft.util.Identifier;
 
@@ -24,21 +21,45 @@ public interface IRecipeCategory<T extends IRecipeDisplay> {
     
     public String getCategoryName();
     
-    default public boolean usesFullPage() {
-        return false;
-    }
-    
     default public List<IWidget> setupDisplay(Supplier<T> recipeDisplaySupplier, Rectangle bounds) {
         return Arrays.asList(new RecipeBaseWidget(bounds));
     }
     
-    default public void drawCategoryBackground(Rectangle bounds) {
-        GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-        GuiLighting.disable();
-        MinecraftClient.getInstance().getTextureManager().bindTexture(RecipeViewingWidgetScreen.CHEST_GUI_TEXTURE);
-        new Drawable() {
-        
-        }.drawTexturedRect((int) bounds.getX(), (int) bounds.getY(), 0, 0, (int) bounds.getWidth(), (int) bounds.getHeight());
+    default public void drawCategoryBackground(Rectangle bounds, int mouseX, int mouseY, float delta) {
+        new RecipeBaseWidget(bounds).draw(mouseX, mouseY, delta);
+        DrawableHelper.drawRect(bounds.x + 17, bounds.y + 5, bounds.x + bounds.width - 17, bounds.y + 17, RecipeViewingScreen.SUB_COLOR.getRGB());
+        DrawableHelper.drawRect(bounds.x + 17, bounds.y + 21, bounds.x + bounds.width - 17, bounds.y + 33, RecipeViewingScreen.SUB_COLOR.getRGB());
+    }
+    
+    default public IDisplaySettings getDisplaySettings() {
+        return new IDisplaySettings<T>() {
+            @Override
+            public int getDisplayHeight(IRecipeCategory category) {
+                return 66;
+            }
+            
+            @Override
+            public int getDisplayWidth(IRecipeCategory category, T display) {
+                return 150;
+            }
+            
+            @Override
+            public int getMaximumRecipePerPage(IRecipeCategory category) {
+                return 99;
+            }
+        };
+    }
+    
+    default public int getDisplayHeight() {
+        return getDisplaySettings().getDisplayHeight(this);
+    }
+    
+    default public int getDisplayWidth(T display) {
+        return getDisplaySettings().getDisplayWidth(this, display);
+    }
+    
+    default public int getMaximumRecipePerPage() {
+        return getDisplaySettings().getMaximumRecipePerPage(this);
     }
     
     default public boolean checkTags() {

+ 2 - 1
src/main/java/me/shedaniel/rei/api/IRecipeDisplay.java

@@ -6,10 +6,11 @@ import net.minecraft.recipe.Recipe;
 import net.minecraft.util.Identifier;
 
 import java.util.List;
+import java.util.Optional;
 
 public interface IRecipeDisplay<T extends Recipe> {
     
-    public abstract T getRecipe();
+    public abstract Optional<T> getRecipe();
     
     public List<List<ItemStack>> getInput();
     

+ 44 - 0
src/main/java/me/shedaniel/rei/api/IRecipeHelper.java

@@ -0,0 +1,44 @@
+package me.shedaniel.rei.api;
+
+import me.shedaniel.rei.RoughlyEnoughItemsCore;
+import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.RecipeManager;
+import net.minecraft.util.Identifier;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+public interface IRecipeHelper {
+    
+    public static IRecipeHelper getInstance() {
+        return RoughlyEnoughItemsCore.getRecipeHelper();
+    }
+    
+    public int getRecipeCount();
+    
+    public List<ItemStack> findCraftableByItems(List<ItemStack> inventoryItems);
+    
+    public void registerCategory(IRecipeCategory category);
+    
+    public void registerDisplay(Identifier categoryIdentifier, IRecipeDisplay display);
+    
+    public Map<IRecipeCategory, List<IRecipeDisplay>> getRecipesFor(ItemStack stack);
+    
+    public RecipeManager getRecipeManager();
+    
+    public List<IRecipeCategory> getAllCategories();
+    
+    public Map<IRecipeCategory, List<IRecipeDisplay>> getUsagesFor(ItemStack stack);
+    
+    public Optional<SpeedCraftAreaSupplier> getSpeedCraftButtonArea(IRecipeCategory category);
+    
+    public void registerSpeedCraftButtonArea(Identifier category, SpeedCraftAreaSupplier rectangle);
+    
+    public List<SpeedCraftFunctional> getSpeedCraftFunctional(IRecipeCategory category);
+    
+    public void registerSpeedCraftFunctional(Identifier category, SpeedCraftFunctional functional);
+    
+    Map<IRecipeCategory, List<IRecipeDisplay>> getAllRecipes();
+    
+}

+ 3 - 3
src/main/java/me/shedaniel/rei/api/IRecipePlugin.java

@@ -8,11 +8,11 @@ public interface IRecipePlugin {
     
     public void registerItems(IItemRegisterer itemRegisterer);
     
-    public void registerPluginCategories(RecipeHelper recipeHelper);
+    public void registerPluginCategories(IRecipeHelper recipeHelper);
     
-    public void registerRecipeDisplays(RecipeHelper recipeHelper);
+    public void registerRecipeDisplays(IRecipeHelper recipeHelper);
     
-    public void registerSpeedCraft(RecipeHelper recipeHelper);
+    public void registerSpeedCraft(IRecipeHelper recipeHelper);
     
     default public int getPriority() {
         return 0;

+ 24 - 10
src/main/java/me/shedaniel/rei/client/ClientHelper.java

@@ -5,9 +5,10 @@ import io.netty.buffer.Unpooled;
 import me.shedaniel.rei.RoughlyEnoughItemsCore;
 import me.shedaniel.rei.api.IRecipeCategory;
 import me.shedaniel.rei.api.IRecipeDisplay;
+import me.shedaniel.rei.api.IRecipeHelper;
 import me.shedaniel.rei.gui.ContainerScreenOverlay;
+import me.shedaniel.rei.gui.RecipeViewingScreen;
 import me.shedaniel.rei.gui.config.ConfigScreen;
-import me.shedaniel.rei.gui.widget.RecipeViewingWidgetScreen;
 import net.fabricmc.api.ClientModInitializer;
 import net.fabricmc.fabric.api.client.keybinding.FabricKeyBinding;
 import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
@@ -16,9 +17,11 @@ import net.fabricmc.loader.api.FabricLoader;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.Mouse;
 import net.minecraft.client.gui.Screen;
+import net.minecraft.client.gui.ingame.CreativePlayerInventoryScreen;
 import net.minecraft.client.util.InputUtil;
 import net.minecraft.item.ItemStack;
 import net.minecraft.item.Items;
+import net.minecraft.text.TranslatableTextComponent;
 import net.minecraft.util.DefaultedList;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.PacketByteBuf;
@@ -72,7 +75,7 @@ public class ClientHelper implements ClientModInitializer {
     }
     
     public static void sendDeletePacket() {
-        if (MinecraftClient.getInstance().interactionManager.hasCreativeInventory()) {
+        if (GuiHelper.getLastContainerScreen() instanceof CreativePlayerInventoryScreen) {
             MinecraftClient.getInstance().player.inventory.setCursorStack(ItemStack.EMPTY);
             return;
         }
@@ -88,27 +91,31 @@ public class ClientHelper implements ClientModInitializer {
                 return false;
             }
         } else {
-            Identifier location = Registry.ITEM.getId(cheatedStack.getItem());
+            Identifier identifier = Registry.ITEM.getId(cheatedStack.getItem());
             String tagMessage = cheatedStack.copy().getTag() != null && !cheatedStack.copy().getTag().isEmpty() ? cheatedStack.copy().getTag().asString() : "";
-            String madeUpCommand = RoughlyEnoughItemsCore.getConfigHelper().getGiveCommandPrefix() + " " + MinecraftClient.getInstance().player.getEntityName() + " " + location.toString() + tagMessage + (cheatedStack.getAmount() != 1 ? " " + cheatedStack.getAmount() : "");
-            if (madeUpCommand.length() > 256)
-                madeUpCommand = RoughlyEnoughItemsCore.getConfigHelper().getGiveCommandPrefix() + " " + MinecraftClient.getInstance().player.getEntityName() + " " + location.toString() + (cheatedStack.getAmount() != 1 ? " " + cheatedStack.getAmount() : "");
+            String og = cheatedStack.getAmount() != 1 ? RoughlyEnoughItemsCore.getConfigHelper().getConfig().giveCommand.replaceAll(" \\{count}", "").replaceAll("\\{count}", "") : RoughlyEnoughItemsCore.getConfigHelper().getConfig().giveCommand;
+            String madeUpCommand = og.replaceAll("\\{player_name}", MinecraftClient.getInstance().player.getEntityName()).replaceAll("\\{item_identifier}", identifier.toString()).replaceAll("\\{nbt}", tagMessage).replaceAll("\\{count}", String.valueOf(cheatedStack.getAmount()));
+            if (madeUpCommand.length() > 256) {
+                madeUpCommand = og.replaceAll("\\{player_name}", MinecraftClient.getInstance().player.getEntityName()).replaceAll("\\{item_identifier}", identifier.toString()).replaceAll("\\{nbt}", "").replaceAll("\\{count}", String.valueOf(cheatedStack.getAmount()));
+                MinecraftClient.getInstance().player.addChatMessage(new TranslatableTextComponent("text.rei.too_long_nbt"), false);
+            }
+            System.out.println(madeUpCommand);
             MinecraftClient.getInstance().player.sendChatMessage(madeUpCommand);
             return true;
         }
     }
     
     public static boolean executeRecipeKeyBind(ContainerScreenOverlay overlay, ItemStack stack) {
-        Map<IRecipeCategory, List<IRecipeDisplay>> map = RecipeHelper.getInstance().getRecipesFor(stack);
+        Map<IRecipeCategory, List<IRecipeDisplay>> map = IRecipeHelper.getInstance().getRecipesFor(stack);
         if (map.keySet().size() > 0)
-            MinecraftClient.getInstance().openScreen(new RecipeViewingWidgetScreen(MinecraftClient.getInstance().window, map));
+            MinecraftClient.getInstance().openScreen(new RecipeViewingScreen(MinecraftClient.getInstance().window, map));
         return map.keySet().size() > 0;
     }
     
     public static boolean executeUsageKeyBind(ContainerScreenOverlay overlay, ItemStack stack) {
-        Map<IRecipeCategory, List<IRecipeDisplay>> map = RecipeHelper.getInstance().getUsagesFor(stack);
+        Map<IRecipeCategory, List<IRecipeDisplay>> map = IRecipeHelper.getInstance().getUsagesFor(stack);
         if (map.keySet().size() > 0)
-            MinecraftClient.getInstance().openScreen(new RecipeViewingWidgetScreen(MinecraftClient.getInstance().window, map));
+            MinecraftClient.getInstance().openScreen(new RecipeViewingScreen(MinecraftClient.getInstance().window, map));
         return map.keySet().size() > 0;
     }
     
@@ -126,6 +133,13 @@ public class ClientHelper implements ClientModInitializer {
         return inventoryStacks;
     }
     
+    public static boolean executeViewAllRecipesKeyBind(ContainerScreenOverlay lastOverlay) {
+        Map<IRecipeCategory, List<IRecipeDisplay>> map = IRecipeHelper.getInstance().getAllRecipes();
+        if (map.keySet().size() > 0)
+            MinecraftClient.getInstance().openScreen(new RecipeViewingScreen(MinecraftClient.getInstance().window, map));
+        return map.keySet().size() > 0;
+    }
+    
     @Override
     public void onInitializeClient() {
         this.cheating = false;

+ 3 - 59
src/main/java/me/shedaniel/rei/client/ConfigHelper.java

@@ -61,72 +61,16 @@ public class ConfigHelper {
         saveConfig();
     }
     
-    public REIItemListOrdering getItemListOrdering() {
-        return config.itemListOrdering;
-    }
-    
-    public void setItemListOrdering(REIItemListOrdering ordering) {
-        config.itemListOrdering = ordering;
-    }
-    
-    public boolean isAscending() {
-        return config.isAscending;
-    }
-    
-    public void setAscending(boolean ascending) {
-        config.isAscending = ascending;
+    public REIConfig getConfig() {
+        return config;
     }
     
     public boolean craftableOnly() {
-        return craftableOnly && config.enableCraftableOnlyButton;
+        return craftableOnly;
     }
     
     public void toggleCraftableOnly() {
         craftableOnly = !craftableOnly;
     }
     
-    public boolean showCraftableOnlyButton() {
-        return config.enableCraftableOnlyButton;
-    }
-    
-    public void setShowCraftableOnlyButton(boolean enableCraftableOnlyButton) {
-        config.enableCraftableOnlyButton = enableCraftableOnlyButton;
-    }
-    
-    public String getGiveCommandPrefix() {
-        return config.giveCommandPrefix;
-    }
-    
-    public boolean sideSearchField() {
-        return config.sideSearchField;
-    }
-    
-    public void setSideSearchField(boolean sideSearchField) {
-        config.sideSearchField = sideSearchField;
-    }
-    
-    public boolean checkUpdates() {
-        return config.checkUpdates;
-    }
-    
-    public void setCheckUpdates(boolean checkUpdates) {
-        config.checkUpdates = checkUpdates;
-    }
-    
-    public boolean isMirrorItemPanel() {
-        return config.mirrorItemPanel;
-    }
-    
-    public void setMirrorItemPanel(boolean mirrorItemPanel) {
-        config.mirrorItemPanel = mirrorItemPanel;
-    }
-    
-    public boolean isLoadingDefaultPlugin() {
-        return config.loadDefaultPlugin;
-    }
-    
-    public void setLoadingDefaultPlugin(boolean loadDefaultPlugin) {
-        config.loadDefaultPlugin = loadDefaultPlugin;
-    }
-    
 }

+ 7 - 1
src/main/java/me/shedaniel/rei/client/REIConfig.java

@@ -11,9 +11,15 @@ public class REIConfig {
     public boolean isAscending = true;
     public boolean enableCraftableOnlyButton = true;
     public boolean sideSearchField = false;
-    public String giveCommandPrefix = "/give";
+    public String giveCommand = "/give {player_name} {item_identifier}{nbt} {count}";
+    public String gamemodeCommand = "/gamemode {gamemode}";
+    public String weatherCommand = "/weather {weather}";
     public boolean checkUpdates = true;
     public boolean mirrorItemPanel = false;
     public boolean loadDefaultPlugin = true;
+    public boolean disableCreditsButton = false;
+    public int maxRecipePerPage = 3;
+    public boolean fixRamUsage = false;
+    public boolean showUtilsButtons = false;
     
 }

+ 41 - 19
src/main/java/me/shedaniel/rei/client/RecipeHelper.java

@@ -11,20 +11,19 @@ import net.minecraft.util.Identifier;
 import java.awt.*;
 import java.util.*;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
-public class RecipeHelper {
+public class RecipeHelper implements IRecipeHelper {
     
+    private final AtomicInteger recipeCount = new AtomicInteger();
     private final Map<Identifier, List<IRecipeDisplay>> recipeCategoryListMap = Maps.newHashMap();
     private final List<IRecipeCategory> categories = Lists.newArrayList();
     private final Map<Identifier, SpeedCraftAreaSupplier> speedCraftAreaSupplierMap = Maps.newHashMap();
     private final Map<Identifier, List<SpeedCraftFunctional>> speedCraftFunctionalMap = Maps.newHashMap();
     private RecipeManager recipeManager;
     
-    public static RecipeHelper getInstance() {
-        return RoughlyEnoughItemsCore.getRecipeHelper();
-    }
-    
+    @Override
     public List<ItemStack> findCraftableByItems(List<ItemStack> inventoryItems) {
         List<ItemStack> craftables = new ArrayList<>();
         for(List<IRecipeDisplay> value : recipeCategoryListMap.values())
@@ -54,17 +53,21 @@ public class RecipeHelper {
         return craftables.stream().distinct().collect(Collectors.toList());
     }
     
+    @Override
     public void registerCategory(IRecipeCategory category) {
         categories.add(category);
         recipeCategoryListMap.put(category.getIdentifier(), Lists.newLinkedList());
     }
     
+    @Override
     public void registerDisplay(Identifier categoryIdentifier, IRecipeDisplay display) {
         if (!recipeCategoryListMap.containsKey(categoryIdentifier))
             return;
+        recipeCount.incrementAndGet();
         recipeCategoryListMap.get(categoryIdentifier).add(display);
     }
     
+    @Override
     public Map<IRecipeCategory, List<IRecipeDisplay>> getRecipesFor(ItemStack stack) {
         Map<Identifier, List<IRecipeDisplay>> categoriesMap = new HashMap<>();
         categories.forEach(f -> categoriesMap.put(f.getIdentifier(), Lists.newArrayList()));
@@ -87,10 +90,12 @@ public class RecipeHelper {
         return categories.stream().filter(category -> category.getIdentifier().equals(identifier)).findFirst().orElse(null);
     }
     
+    @Override
     public RecipeManager getRecipeManager() {
         return recipeManager;
     }
     
+    @Override
     public Map<IRecipeCategory, List<IRecipeDisplay>> getUsagesFor(ItemStack stack) {
         Map<Identifier, List<IRecipeDisplay>> categoriesMap = new HashMap<>();
         categories.forEach(f -> categoriesMap.put(f.getIdentifier(), Lists.newArrayList()));
@@ -119,28 +124,31 @@ public class RecipeHelper {
         return recipeCategoryListMap;
     }
     
-    public List<IRecipeCategory> getCategories() {
+    @Override
+    public List<IRecipeCategory> getAllCategories() {
         return new LinkedList<>(categories);
     }
     
-    public SpeedCraftAreaSupplier getSpeedCraftButtonArea(IRecipeCategory category) {
+    @Override
+    public Optional<SpeedCraftAreaSupplier> getSpeedCraftButtonArea(IRecipeCategory category) {
         if (!speedCraftAreaSupplierMap.containsKey(category.getIdentifier()))
-            return bounds -> {
-                return new Rectangle((int) bounds.getMaxX() - 16, (int) bounds.getMaxY() - 16, 10, 10);
-            };
-        return speedCraftAreaSupplierMap.get(category.getIdentifier());
+            return Optional.of(bounds -> new Rectangle((int) bounds.getMaxX() - 16, (int) bounds.getMaxY() - 16, 10, 10));
+        return Optional.ofNullable(speedCraftAreaSupplierMap.get(category.getIdentifier()));
     }
     
+    @Override
     public void registerSpeedCraftButtonArea(Identifier category, SpeedCraftAreaSupplier rectangle) {
         speedCraftAreaSupplierMap.put(category, rectangle);
     }
     
+    @Override
     public List<SpeedCraftFunctional> getSpeedCraftFunctional(IRecipeCategory category) {
         if (speedCraftFunctionalMap.get(category.getIdentifier()) == null)
             return Lists.newArrayList();
         return speedCraftFunctionalMap.get(category.getIdentifier());
     }
     
+    @Override
     public void registerSpeedCraftFunctional(Identifier category, SpeedCraftFunctional functional) {
         List<SpeedCraftFunctional> list = speedCraftFunctionalMap.containsKey(category) ? new LinkedList<>(speedCraftFunctionalMap.get(category)) : Lists.newLinkedList();
         list.add(functional);
@@ -149,6 +157,7 @@ public class RecipeHelper {
     
     @SuppressWarnings("deprecation")
     public void recipesLoaded(RecipeManager recipeManager) {
+        this.recipeCount.set(0);
         this.recipeManager = recipeManager;
         this.recipeCategoryListMap.clear();
         this.categories.clear();
@@ -159,16 +168,13 @@ public class RecipeHelper {
             return second.getPriority() - first.getPriority();
         });
         RoughlyEnoughItemsCore.LOGGER.info("Loading %d REI plugins: %s", plugins.size(), String.join(", ", plugins.stream().map(plugin -> {
-            Identifier identifier = RoughlyEnoughItemsCore.getPluginIdentifier(plugin);
-            return identifier == null ? "NULL" : identifier.toString();
+            return RoughlyEnoughItemsCore.getPluginIdentifier(plugin).map(Identifier::toString).orElseGet(() -> "null");
         }).collect(Collectors.toList())));
         Collections.reverse(plugins);
         RoughlyEnoughItemsCore.getItemRegisterer().getModifiableItemList().clear();
         IPluginDisabler pluginDisabler = RoughlyEnoughItemsCore.getPluginDisabler();
         plugins.forEach(plugin -> {
-            Identifier identifier = RoughlyEnoughItemsCore.getPluginIdentifier(plugin);
-            if (identifier == null)
-                identifier = new Identifier("null");
+            Identifier identifier = RoughlyEnoughItemsCore.getPluginIdentifier(plugin).orElseGet(() -> new Identifier("null"));
             if (pluginDisabler.isFunctionEnabled(identifier, PluginFunction.REGISTER_ITEMS))
                 plugin.registerItems(RoughlyEnoughItemsCore.getItemRegisterer());
             if (pluginDisabler.isFunctionEnabled(identifier, PluginFunction.REGISTER_CATEGORIES))
@@ -178,9 +184,25 @@ public class RecipeHelper {
             if (pluginDisabler.isFunctionEnabled(identifier, PluginFunction.REGISTER_SPEED_CRAFT))
                 plugin.registerSpeedCraft(this);
         });
-        RoughlyEnoughItemsCore.LOGGER.info("Registered REI Categories: " + String.join(", ", categories.stream().map(category -> {
-            return category.getCategoryName();
-        }).collect(Collectors.toList())));
+        RoughlyEnoughItemsCore.LOGGER.info("Registered REI Categories: " + String.join(", ", categories.stream().map(IRecipeCategory::getCategoryName).collect(Collectors.toList())));
+        RoughlyEnoughItemsCore.LOGGER.info("Registered %d recipes for REI.", recipeCount.get());
+    }
+    
+    @Override
+    public int getRecipeCount() {
+        return recipeCount.get();
+    }
+    
+    @Override
+    public Map<IRecipeCategory, List<IRecipeDisplay>> getAllRecipes() {
+        Map<IRecipeCategory, List<IRecipeDisplay>> map = Maps.newLinkedHashMap();
+        Map<Identifier, List<IRecipeDisplay>> tempMap = Maps.newLinkedHashMap();
+        recipeCategoryListMap.forEach((identifier, recipeDisplays) -> tempMap.put(identifier, new LinkedList<>(recipeDisplays)));
+        categories.forEach(category -> {
+            if (tempMap.containsKey(category.getIdentifier()))
+                map.put(category, tempMap.get(category.getIdentifier()));
+        });
+        return map;
     }
     
 }

+ 38 - 0
src/main/java/me/shedaniel/rei/client/Weather.java

@@ -0,0 +1,38 @@
+package me.shedaniel.rei.client;
+
+public enum Weather {
+    CLEAR(0, "Clear"), RAIN(1, "Rain"), THUNDER(2, "Thunder");
+    
+    private final int id;
+    private final String name;
+    
+    Weather(int id, String name) {
+        this.id = id;
+        this.name = name;
+    }
+    
+    public static Weather byId(int int_1) {
+        return byId(int_1, CLEAR);
+    }
+    
+    public static Weather byId(int int_1, Weather gameMode_1) {
+        Weather[] var2 = values();
+        int var3 = var2.length;
+        
+        for(int var4 = 0; var4 < var3; ++var4) {
+            Weather gameMode_2 = var2[var4];
+            if (gameMode_2.id == int_1)
+                return gameMode_2;
+        }
+        return gameMode_1;
+    }
+    
+    public int getId() {
+        return id;
+    }
+    
+    public String getName() {
+        return name;
+    }
+    
+}

+ 178 - 42
src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java

@@ -5,27 +5,37 @@ import com.mojang.blaze3d.platform.GlStateManager;
 import me.shedaniel.rei.RoughlyEnoughItemsCore;
 import me.shedaniel.rei.client.ClientHelper;
 import me.shedaniel.rei.client.GuiHelper;
+import me.shedaniel.rei.client.Weather;
 import me.shedaniel.rei.gui.credits.CreditsScreen;
 import me.shedaniel.rei.gui.widget.*;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.audio.PositionedSoundInstance;
 import net.minecraft.client.gui.ContainerScreen;
-import net.minecraft.client.gui.DrawableContainer;
-import net.minecraft.client.gui.GuiEventListener;
+import net.minecraft.client.gui.InputListener;
+import net.minecraft.client.gui.Screen;
+import net.minecraft.client.gui.ScreenComponent;
 import net.minecraft.client.render.GuiLighting;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.client.util.Window;
+import net.minecraft.client.world.ClientWorld;
 import net.minecraft.item.ItemStack;
+import net.minecraft.sound.SoundEvents;
 import net.minecraft.text.TranslatableTextComponent;
+import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
+import net.minecraft.world.GameMode;
+import net.minecraft.world.World;
 
 import java.awt.*;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.stream.Collectors;
 
-public class ContainerScreenOverlay extends DrawableContainer {
+public class ContainerScreenOverlay extends ScreenComponent {
     
+    private static final Identifier CHEST_GUI_TEXTURE = new Identifier("roughlyenoughitems", "textures/gui/recipecontainer.png");
     private static final List<QueuedTooltip> QUEUED_TOOLTIPS = Lists.newArrayList();
     public static String searchTerm = "";
     private static int page = 0;
@@ -64,36 +74,95 @@ public class ContainerScreenOverlay extends DrawableContainer {
             }
         });
         page = MathHelper.clamp(page, 0, getTotalPage());
-        widgets.add(new ButtonWidget(RoughlyEnoughItemsCore.getConfigHelper().isMirrorItemPanel() ? window.getScaledWidth() - 50 : 10, 10, 40, 20, "") {
-            @Override
-            public void draw(int int_1, int int_2, float float_1) {
-                this.text = getCheatModeText();
-                super.draw(int_1, int_2, float_1);
-            }
-            
-            @Override
-            public void onPressed(int button, double mouseX, double mouseY) {
-                ClientHelper.setCheating(!ClientHelper.isCheating());
-            }
-        });
-        widgets.add(new ButtonWidget(RoughlyEnoughItemsCore.getConfigHelper().isMirrorItemPanel() ? window.getScaledWidth() - 50 : 10, 35, 40, 20, I18n.translate("text.rei.config")) {
+        widgets.add(new ButtonWidget(RoughlyEnoughItemsCore.getConfigHelper().getConfig().mirrorItemPanel ? window.getScaledWidth() - 30 : 10, 10, 20, 20, "") {
             @Override
             public void onPressed(int button, double mouseX, double mouseY) {
+                if (Screen.isShiftPressed()) {
+                    ClientHelper.setCheating(!ClientHelper.isCheating());
+                    return;
+                }
                 ClientHelper.openConfigWindow(GuiHelper.getLastContainerScreen());
             }
-        });
-        widgets.add(new ButtonWidget(RoughlyEnoughItemsCore.getConfigHelper().isMirrorItemPanel() ? window.getScaledWidth() - 50 : 10, window.getScaledHeight() - 30, 40, 20, I18n.translate("text.rei.credits")) {
+            
             @Override
-            public void onPressed(int button, double mouseX, double mouseY) {
-                MinecraftClient.getInstance().openScreen(new CreditsScreen(GuiHelper.getLastContainerScreen()));
+            public void draw(int mouseX, int mouseY, float partialTicks) {
+                super.draw(mouseX, mouseY, partialTicks);
+                GuiLighting.disable();
+                if (ClientHelper.isCheating())
+                    drawRect(getBounds().x, getBounds().y, getBounds().x + 20, getBounds().y + 20, new Color(255, 0, 0, 42).getRGB());
+                MinecraftClient.getInstance().getTextureManager().bindTexture(CHEST_GUI_TEXTURE);
+                GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
+                drawTexturedRect(getBounds().x + 3, getBounds().y + 3, 0, 0, 14, 14);
+                if (isHighlighted(mouseX, mouseY)) {
+                    List<String> list = new LinkedList<>(Arrays.asList(I18n.translate("text.rei.config_tooltip").split("\n")));
+                    list.add(" ");
+                    if (!ClientHelper.isCheating())
+                        list.add("§c§m" + I18n.translate("text.rei.cheating"));
+                    else
+                        list.add("§a" + I18n.translate("text.rei.cheating"));
+                    addTooltip(new QueuedTooltip(new Point(mouseX, mouseY), list));
+                }
             }
         });
-        widgets.add(new LabelWidget(rectangle.x + (rectangle.width / 2), rectangle.y + 10, "") {
+        if (!RoughlyEnoughItemsCore.getConfigHelper().getConfig().disableCreditsButton)
+            widgets.add(new ButtonWidget(RoughlyEnoughItemsCore.getConfigHelper().getConfig().mirrorItemPanel ? window.getScaledWidth() - 50 : 10, window.getScaledHeight() - 30, 40, 20, I18n.translate("text.rei.credits")) {
+                @Override
+                public void onPressed(int button, double mouseX, double mouseY) {
+                    MinecraftClient.getInstance().openScreen(new CreditsScreen(GuiHelper.getLastContainerScreen()));
+                }
+            });
+        if (RoughlyEnoughItemsCore.getConfigHelper().getConfig().showUtilsButtons) {
+            widgets.add(new ButtonWidget(RoughlyEnoughItemsCore.getConfigHelper().getConfig().mirrorItemPanel ? window.getScaledWidth() - 55 : 35, 10, 20, 20, "") {
+                @Override
+                public void onPressed(int button, double mouseX, double mouseY) {
+                    MinecraftClient.getInstance().player.sendChatMessage(RoughlyEnoughItemsCore.getConfigHelper().getConfig().gamemodeCommand.replaceAll("\\{gamemode}", getNextGameMode().getName()));
+                }
+                
+                @Override
+                public void draw(int mouseX, int mouseY, float partialTicks) {
+                    text = getGameModeShortText(getCurrentGameMode());
+                    super.draw(mouseX, mouseY, partialTicks);
+                    if (isHighlighted(mouseX, mouseY)) {
+                        List<String> list = Arrays.asList(I18n.translate("text.rei.gamemode_button.tooltip", getGameModeText(getNextGameMode())).split("\n"));
+                        addTooltip(new QueuedTooltip(new Point(mouseX, mouseY), list));
+                    }
+                }
+            });
+            widgets.add(new ButtonWidget(RoughlyEnoughItemsCore.getConfigHelper().getConfig().mirrorItemPanel ? window.getScaledWidth() - 80 : 60, 10, 20, 20, "") {
+                @Override
+                public void onPressed(int button, double mouseX, double mouseY) {
+                    MinecraftClient.getInstance().player.sendChatMessage(RoughlyEnoughItemsCore.getConfigHelper().getConfig().weatherCommand.replaceAll("\\{weather}", getNextWeather().getName().toLowerCase()));
+                }
+                
+                @Override
+                public void draw(int mouseX, int mouseY, float partialTicks) {
+                    super.draw(mouseX, mouseY, partialTicks);
+                    GuiLighting.disable();
+                    MinecraftClient.getInstance().getTextureManager().bindTexture(CHEST_GUI_TEXTURE);
+                    GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
+                    drawTexturedRect(getBounds().x + 3, getBounds().y + 3, getCurrentWeather().getId() * 14, 14, 14, 14);
+                    if (isHighlighted(mouseX, mouseY)) {
+                        List<String> list = Arrays.asList(I18n.translate("text.rei.weather_button.tooltip", getNextWeather().getName()).split("\n"));
+                        addTooltip(new QueuedTooltip(new Point(mouseX, mouseY), list));
+                    }
+                }
+            });
+        }
+        widgets.add(new ClickableLabelWidget(rectangle.x + (rectangle.width / 2), rectangle.y + 10, "") {
             @Override
             public void draw(int mouseX, int mouseY, float partialTicks) {
                 page = MathHelper.clamp(page, 0, getTotalPage());
                 this.text = String.format("%s/%s", page + 1, getTotalPage() + 1);
                 super.draw(mouseX, mouseY, partialTicks);
+                if (isHighlighted(mouseX, mouseY))
+                    GuiHelper.getLastOverlay().addTooltip(new QueuedTooltip(new Point(mouseX, mouseY), Arrays.asList(I18n.translate("text.rei.go_back_first_page").split("\n"))));
+            }
+            
+            @Override
+            public void onLabelClicked() {
+                MinecraftClient.getInstance().getSoundLoader().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+                page = 0;
+                itemListOverlay.updateList(getItemListArea(), page, searchTerm);
             }
         });
         if (GuiHelper.searchField == null)
@@ -111,10 +180,10 @@ public class ContainerScreenOverlay extends DrawableContainer {
             searchTerm = s;
             itemListOverlay.updateList(getItemListArea(), page, searchTerm);
         });
-        GuiHelper.searchField.setBounds(getTextFieldArea());
+        GuiHelper.searchField.getBounds().setBounds(getTextFieldArea());
         this.widgets.add(GuiHelper.searchField);
         GuiHelper.searchField.setText(searchTerm);
-        if (RoughlyEnoughItemsCore.getConfigHelper().showCraftableOnlyButton())
+        if (RoughlyEnoughItemsCore.getConfigHelper().getConfig().enableCraftableOnlyButton)
             this.widgets.add(new CraftableToggleButtonWidget(getCraftableToggleArea()) {
                 @Override
                 public void onPressed(int button, double mouseX, double mouseY) {
@@ -126,12 +195,77 @@ public class ContainerScreenOverlay extends DrawableContainer {
         this.itemListOverlay.updateList(getItemListArea(), page, searchTerm);
     }
     
+    private Weather getNextWeather() {
+        try {
+            Weather current = getCurrentWeather();
+            int next = current.getId() + 1;
+            if (next >= 3)
+                next = 0;
+            return Weather.byId(next);
+        } catch (Exception e) {
+            return Weather.CLEAR;
+        }
+    }
+    
+    private Weather getCurrentWeather() {
+        ClientWorld world = MinecraftClient.getInstance().world;
+        if (world.isThundering())
+            return Weather.THUNDER;
+        if (world.getLevelProperties().isRaining())
+            return Weather.RAIN;
+        return Weather.CLEAR;
+    }
+    
+    private String getGameModeShortText(GameMode gameMode) {
+        switch (gameMode) {
+            case CREATIVE:
+                return "C";
+            case SURVIVAL:
+                return "S";
+            case ADVENTURE:
+                return "A";
+            case SPECTATOR:
+                return "SP";
+        }
+        return gameMode.name();
+    }
+    
+    private String getGameModeText(GameMode gameMode) {
+        switch (gameMode) {
+            case CREATIVE:
+                return "Creative";
+            case SURVIVAL:
+                return "Survival";
+            case ADVENTURE:
+                return "Adventure";
+            case SPECTATOR:
+                return "Spectator";
+        }
+        return gameMode.name();
+    }
+    
+    private GameMode getNextGameMode() {
+        try {
+            GameMode current = getCurrentGameMode();
+            int next = current.getId() + 1;
+            if (next >= 3)
+                next = 0;
+            return GameMode.byId(next);
+        } catch (Exception e) {
+            return GameMode.INVALID;
+        }
+    }
+    
+    private GameMode getCurrentGameMode() {
+        return MinecraftClient.getInstance().getNetworkHandler().method_2871(MinecraftClient.getInstance().player.getGameProfile().getId()).getGameMode();
+    }
+    
     private Rectangle getTextFieldArea() {
-        int widthRemoved = RoughlyEnoughItemsCore.getConfigHelper().showCraftableOnlyButton() ? 22 : 0;
-        if (RoughlyEnoughItemsCore.getConfigHelper().sideSearchField())
+        int widthRemoved = RoughlyEnoughItemsCore.getConfigHelper().getConfig().enableCraftableOnlyButton ? 22 : 0;
+        if (RoughlyEnoughItemsCore.getConfigHelper().getConfig().sideSearchField)
             return new Rectangle(rectangle.x + 2, window.getScaledHeight() - 22, rectangle.width - 6 - widthRemoved, 18);
-        if (MinecraftClient.getInstance().currentScreen instanceof RecipeViewingWidgetScreen) {
-            RecipeViewingWidgetScreen widget = (RecipeViewingWidgetScreen) MinecraftClient.getInstance().currentScreen;
+        if (MinecraftClient.getInstance().currentScreen instanceof RecipeViewingScreen) {
+            RecipeViewingScreen widget = (RecipeViewingScreen) MinecraftClient.getInstance().currentScreen;
             return new Rectangle(widget.getBounds().x, window.getScaledHeight() - 22, widget.getBounds().width - widthRemoved, 18);
         }
         return new Rectangle(GuiHelper.getLastMixinContainerScreen().rei_getContainerLeft(), window.getScaledHeight() - 22, GuiHelper.getLastMixinContainerScreen().rei_getContainerWidth() - widthRemoved, 18);
@@ -149,7 +283,7 @@ public class ContainerScreenOverlay extends DrawableContainer {
     }
     
     private Rectangle getItemListArea() {
-        return new Rectangle(rectangle.x + 2, rectangle.y + 24, rectangle.width - 4, rectangle.height - (RoughlyEnoughItemsCore.getConfigHelper().sideSearchField() ? 27 + 22 : 27));
+        return new Rectangle(rectangle.x + 2, rectangle.y + 24, rectangle.width - 4, rectangle.height - (RoughlyEnoughItemsCore.getConfigHelper().getConfig().sideSearchField ? 27 + 22 : 27));
     }
     
     public Rectangle getRectangle() {
@@ -168,7 +302,9 @@ public class ContainerScreenOverlay extends DrawableContainer {
         GuiLighting.disable();
         this.draw(mouseX, mouseY, partialTicks);
         GuiLighting.disable();
-        QUEUED_TOOLTIPS.stream().filter(queuedTooltip -> queuedTooltip != null).forEach(queuedTooltip -> MinecraftClient.getInstance().currentScreen.drawTooltip(queuedTooltip.text, queuedTooltip.mouse.x, queuedTooltip.mouse.y));
+        Screen currentScreen = MinecraftClient.getInstance().currentScreen;
+        if (!(currentScreen instanceof RecipeViewingScreen) || !((RecipeViewingScreen) currentScreen).choosePageActivated)
+            QUEUED_TOOLTIPS.stream().filter(queuedTooltip -> queuedTooltip != null).forEach(queuedTooltip -> MinecraftClient.getInstance().currentScreen.drawTooltip(queuedTooltip.text, queuedTooltip.mouse.x, queuedTooltip.mouse.y));
         QUEUED_TOOLTIPS.clear();
         GuiLighting.disable();
     }
@@ -205,11 +341,11 @@ public class ContainerScreenOverlay extends DrawableContainer {
     }
     
     private Rectangle calculateBoundary() {
-        if (!RoughlyEnoughItemsCore.getConfigHelper().isMirrorItemPanel()) {
+        if (!RoughlyEnoughItemsCore.getConfigHelper().getConfig().mirrorItemPanel) {
             int startX = GuiHelper.getLastMixinContainerScreen().rei_getContainerLeft() + GuiHelper.getLastMixinContainerScreen().rei_getContainerWidth() + 10;
             int width = window.getScaledWidth() - startX;
-            if (MinecraftClient.getInstance().currentScreen instanceof RecipeViewingWidgetScreen) {
-                RecipeViewingWidgetScreen widget = (RecipeViewingWidgetScreen) MinecraftClient.getInstance().currentScreen;
+            if (MinecraftClient.getInstance().currentScreen instanceof RecipeViewingScreen) {
+                RecipeViewingScreen widget = (RecipeViewingScreen) MinecraftClient.getInstance().currentScreen;
                 startX = widget.getBounds().x + widget.getBounds().width + 10;
                 width = window.getScaledWidth() - startX;
             }
@@ -219,8 +355,8 @@ public class ContainerScreenOverlay extends DrawableContainer {
     }
     
     private int getLeft() {
-        if (MinecraftClient.getInstance().currentScreen instanceof RecipeViewingWidgetScreen) {
-            RecipeViewingWidgetScreen widget = (RecipeViewingWidgetScreen) MinecraftClient.getInstance().currentScreen;
+        if (MinecraftClient.getInstance().currentScreen instanceof RecipeViewingScreen) {
+            RecipeViewingScreen widget = (RecipeViewingScreen) MinecraftClient.getInstance().currentScreen;
             return widget.getBounds().x;
         }
         if (MinecraftClient.getInstance().player.getRecipeBook().isGuiOpen())
@@ -253,7 +389,7 @@ public class ContainerScreenOverlay extends DrawableContainer {
     
     @Override
     public boolean keyPressed(int int_1, int int_2, int int_3) {
-        for(GuiEventListener listener : widgets)
+        for(InputListener listener : widgets)
             if (listener.keyPressed(int_1, int_2, int_3))
                 return true;
         if (ClientHelper.HIDE.matchesKey(int_1, int_2)) {
@@ -269,11 +405,11 @@ public class ContainerScreenOverlay extends DrawableContainer {
                 itemStack = ((ItemSlotWidget) widget).getCurrentStack();
                 break;
             }
-        if (itemStack == null && MinecraftClient.getInstance().currentScreen instanceof RecipeViewingWidgetScreen) {
-            RecipeViewingWidgetScreen recipeViewingWidget = (RecipeViewingWidgetScreen) MinecraftClient.getInstance().currentScreen;
-            for(GuiEventListener entry : recipeViewingWidget.method_1968())
-                if (entry instanceof ItemSlotWidget && ((HighlightableWidget) entry).isHighlighted(point.x, point.y)) {
-                    itemStack = ((ItemSlotWidget) entry).getCurrentStack();
+        if (itemStack == null && MinecraftClient.getInstance().currentScreen instanceof RecipeViewingScreen) {
+            RecipeViewingScreen recipeViewingWidget = (RecipeViewingScreen) MinecraftClient.getInstance().currentScreen;
+            for(InputListener listener : recipeViewingWidget.getInputListeners())
+                if (listener instanceof ItemSlotWidget && ((HighlightableWidget) listener).isHighlighted(point.x, point.y)) {
+                    itemStack = ((ItemSlotWidget) listener).getCurrentStack();
                     break;
                 }
         }
@@ -293,14 +429,14 @@ public class ContainerScreenOverlay extends DrawableContainer {
     public boolean charTyped(char char_1, int int_1) {
         if (!GuiHelper.isOverlayVisible())
             return false;
-        for(GuiEventListener listener : method_1968())
+        for(InputListener listener : getInputListeners())
             if (listener.charTyped(char_1, int_1))
                 return true;
         return super.charTyped(char_1, int_1);
     }
     
     @Override
-    public List<? extends GuiEventListener> method_1968() {
+    public List<? extends InputListener> getInputListeners() {
         return widgets;
     }
     

+ 156 - 74
src/main/java/me/shedaniel/rei/gui/widget/RecipeViewingWidgetScreen.java → src/main/java/me/shedaniel/rei/gui/RecipeViewingScreen.java

@@ -1,20 +1,19 @@
-package me.shedaniel.rei.gui.widget;
+package me.shedaniel.rei.gui;
 
 import com.google.common.collect.Lists;
 import com.mojang.blaze3d.platform.GlStateManager;
-import me.shedaniel.rei.api.IRecipeCategory;
-import me.shedaniel.rei.api.IRecipeDisplay;
-import me.shedaniel.rei.api.SpeedCraftAreaSupplier;
-import me.shedaniel.rei.api.SpeedCraftFunctional;
+import me.shedaniel.rei.RoughlyEnoughItemsCore;
+import me.shedaniel.rei.api.*;
 import me.shedaniel.rei.client.ClientHelper;
 import me.shedaniel.rei.client.GuiHelper;
-import me.shedaniel.rei.client.RecipeHelper;
+import me.shedaniel.rei.gui.widget.*;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.audio.PositionedSoundInstance;
 import net.minecraft.client.gui.ContainerScreen;
-import net.minecraft.client.gui.GuiEventListener;
+import net.minecraft.client.gui.InputListener;
 import net.minecraft.client.gui.Screen;
 import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.resource.language.I18n;
 import net.minecraft.client.util.Window;
 import net.minecraft.sound.SoundEvents;
 import net.minecraft.text.TranslatableTextComponent;
@@ -22,18 +21,21 @@ import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
 
 import java.awt.*;
-import java.util.ArrayList;
+import java.util.*;
 import java.util.List;
-import java.util.Map;
 import java.util.function.Supplier;
 
-public class RecipeViewingWidgetScreen extends Screen {
+public class RecipeViewingScreen extends Screen {
     
     public static final Identifier CHEST_GUI_TEXTURE = new Identifier("roughlyenoughitems", "textures/gui/recipecontainer.png");
+    public static final Color SUB_COLOR = new Color(159, 159, 159);
     private static final Identifier CREATIVE_INVENTORY_TABS = new Identifier("textures/gui/container/creative_inventory/tabs.png");
-    public final int guiWidth = 176;
-    public final int guiHeight = 186;
-    
+    public int guiWidth;
+    public int guiHeight;
+    public int page, categoryPages;
+    public int largestWidth, largestHeight;
+    public boolean choosePageActivated;
+    public RecipeChoosePageWidget recipeChoosePageWidget;
     private List<IWidget> widgets;
     private List<TabWidget> tabs;
     private Window window;
@@ -41,26 +43,26 @@ public class RecipeViewingWidgetScreen extends Screen {
     private Map<IRecipeCategory, List<IRecipeDisplay>> categoriesMap;
     private List<IRecipeCategory> categories;
     private IRecipeCategory selectedCategory;
-    private int page, categoryPages;
     private ButtonWidget recipeBack, recipeNext, categoryBack, categoryNext;
     
-    public RecipeViewingWidgetScreen(Window window, Map<IRecipeCategory, List<IRecipeDisplay>> categoriesMap) {
+    public RecipeViewingScreen(Window window, Map<IRecipeCategory, List<IRecipeDisplay>> categoriesMap) {
         this.categoryPages = 0;
         this.window = window;
         this.widgets = Lists.newArrayList();
-        this.bounds = new Rectangle(window.getScaledWidth() / 2 - guiWidth / 2, window.getScaledHeight() / 2 - guiHeight / 2, guiWidth, guiHeight);
+        this.bounds = new Rectangle(window.getScaledWidth() / 2 - guiWidth / 2, window.getScaledHeight() / 2 - guiHeight / 2, 176, 186);
         this.categoriesMap = categoriesMap;
         this.categories = Lists.newArrayList();
-        RecipeHelper.getInstance().getCategories().forEach(category -> {
+        IRecipeHelper.getInstance().getAllCategories().forEach(category -> {
             if (categoriesMap.containsKey(category))
                 categories.add(category);
         });
         this.selectedCategory = categories.get(0);
         this.tabs = new ArrayList<>();
+        this.choosePageActivated = false;
     }
     
     public static SpeedCraftFunctional getSpeedCraftFunctionalByCategory(ContainerScreen containerScreen, IRecipeCategory category) {
-        for(SpeedCraftFunctional functional : RecipeHelper.getInstance().getSpeedCraftFunctional(category))
+        for(SpeedCraftFunctional functional : IRecipeHelper.getInstance().getSpeedCraftFunctional(category))
             for(Class aClass : functional.getFunctioningFor())
                 if (containerScreen.getClass().isAssignableFrom(aClass))
                     return functional;
@@ -74,7 +76,12 @@ public class RecipeViewingWidgetScreen extends Screen {
             GuiHelper.getLastOverlay().onInitialized();
             return true;
         }
-        for(GuiEventListener listener : listeners)
+        if (choosePageActivated) {
+            if (recipeChoosePageWidget.keyPressed(int_1, int_2, int_3))
+                return true;
+            return false;
+        }
+        for(InputListener listener : listeners)
             if (listener.keyPressed(int_1, int_2, int_3))
                 return true;
         return super.keyPressed(int_1, int_2, int_3);
@@ -86,11 +93,16 @@ public class RecipeViewingWidgetScreen extends Screen {
     }
     
     @Override
-    protected void onInitialized() {
+    public void onInitialized() {
         super.onInitialized();
         this.tabs.clear();
         this.widgets.clear();
+        this.largestWidth = window.getScaledWidth() - 100;
+        this.largestHeight = window.getScaledHeight() - 40;
+        this.guiWidth = MathHelper.clamp(getCurrentDisplayed().stream().map(display -> selectedCategory.getDisplayWidth(display)).max(Integer::compareTo).orElse(150) + 30, 0, largestWidth);
+        this.guiHeight = MathHelper.floor(MathHelper.clamp((selectedCategory.getDisplayHeight() + 7) * (getRecipesPerPage() + 1) + 40f, 186f, (float) largestHeight));
         this.bounds = new Rectangle(window.getScaledWidth() / 2 - guiWidth / 2, window.getScaledHeight() / 2 - guiHeight / 2, guiWidth, guiHeight);
+        this.page = MathHelper.clamp(page, 0, getTotalPages(selectedCategory) - 1);
         
         widgets.add(categoryBack = new ButtonWidget((int) bounds.getX() + 5, (int) bounds.getY() + 5, 12, 12, new TranslatableTextComponent("text.rei.left_arrow")) {
             @Override
@@ -101,10 +113,10 @@ public class RecipeViewingWidgetScreen extends Screen {
                     currentCategoryIndex = categories.size() - 1;
                 selectedCategory = categories.get(currentCategoryIndex);
                 categoryPages = MathHelper.floor(currentCategoryIndex / 6d);
-                RecipeViewingWidgetScreen.this.onInitialized();
+                RecipeViewingScreen.this.onInitialized();
             }
         });
-        widgets.add(categoryNext = new ButtonWidget((int) bounds.getX() + 159, (int) bounds.getY() + 5, 12, 12, new TranslatableTextComponent("text.rei.right_arrow")) {
+        widgets.add(categoryNext = new ButtonWidget((int) bounds.getMaxX() - 17, (int) bounds.getY() + 5, 12, 12, new TranslatableTextComponent("text.rei.right_arrow")) {
             @Override
             public void onPressed(int button, double mouseX, double mouseY) {
                 int currentCategoryIndex = categories.indexOf(selectedCategory);
@@ -113,7 +125,7 @@ public class RecipeViewingWidgetScreen extends Screen {
                     currentCategoryIndex = 0;
                 selectedCategory = categories.get(currentCategoryIndex);
                 categoryPages = MathHelper.floor(currentCategoryIndex / 6d);
-                RecipeViewingWidgetScreen.this.onInitialized();
+                RecipeViewingScreen.this.onInitialized();
             }
         });
         categoryBack.enabled = categories.size() > 1;
@@ -125,33 +137,50 @@ public class RecipeViewingWidgetScreen extends Screen {
                 page--;
                 if (page < 0)
                     page = getTotalPages(selectedCategory) - 1;
-                RecipeViewingWidgetScreen.this.onInitialized();
+                RecipeViewingScreen.this.onInitialized();
             }
         });
-        widgets.add(recipeNext = new ButtonWidget((int) bounds.getX() + 159, (int) bounds.getY() + 21, 12, 12, new TranslatableTextComponent("text.rei.right_arrow")) {
+        widgets.add(recipeNext = new ButtonWidget((int) bounds.getMaxX() - 17, (int) bounds.getY() + 21, 12, 12, new TranslatableTextComponent("text.rei.right_arrow")) {
             @Override
             public void onPressed(int button, double mouseX, double mouseY) {
                 page++;
                 if (page >= getTotalPages(selectedCategory))
                     page = 0;
-                RecipeViewingWidgetScreen.this.onInitialized();
+                RecipeViewingScreen.this.onInitialized();
             }
         });
         recipeBack.enabled = categoriesMap.get(selectedCategory).size() > getRecipesPerPage();
         recipeNext.enabled = categoriesMap.get(selectedCategory).size() > getRecipesPerPage();
         
-        widgets.add(new LabelWidget((int) bounds.getCenterX(), (int) bounds.getY() + 7, "") {
+        widgets.add(new ClickableLabelWidget((int) bounds.getCenterX(), (int) bounds.getY() + 7, "") {
             @Override
             public void draw(int mouseX, int mouseY, float partialTicks) {
                 this.text = selectedCategory.getCategoryName();
                 super.draw(mouseX, mouseY, partialTicks);
+                if (isHighlighted(mouseX, mouseY))
+                    GuiHelper.getLastOverlay().addTooltip(new QueuedTooltip(new Point(mouseX, mouseY), Arrays.asList(I18n.translate("text.rei.view_all_categories").split("\n"))));
+            }
+            
+            @Override
+            public void onLabelClicked() {
+                MinecraftClient.getInstance().getSoundLoader().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+                ClientHelper.executeViewAllRecipesKeyBind(GuiHelper.getLastOverlay());
             }
         });
-        widgets.add(new LabelWidget((int) bounds.getCenterX(), (int) bounds.getY() + 23, "") {
+        widgets.add(new ClickableLabelWidget((int) bounds.getCenterX(), (int) bounds.getY() + 23, "") {
             @Override
             public void draw(int mouseX, int mouseY, float partialTicks) {
                 this.text = String.format("%d/%d", page + 1, getTotalPages(selectedCategory));
                 super.draw(mouseX, mouseY, partialTicks);
+                if (isHighlighted(mouseX, mouseY))
+                    GuiHelper.getLastOverlay().addTooltip(new QueuedTooltip(new Point(mouseX, mouseY), Arrays.asList(I18n.translate("text.rei.choose_page").split("\n"))));
+            }
+            
+            @Override
+            public void onLabelClicked() {
+                MinecraftClient.getInstance().getSoundLoader().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+                RecipeViewingScreen.this.choosePageActivated = true;
+                RecipeViewingScreen.this.onInitialized();
             }
         });
         for(int i = 0; i < 6; i++) {
@@ -167,7 +196,7 @@ public class RecipeViewingWidgetScreen extends Screen {
                                 return false;
                             selectedCategory = categories.get(getId() + categoryPages * 6);
                             page = 0;
-                            RecipeViewingWidgetScreen.this.onInitialized();
+                            RecipeViewingScreen.this.onInitialized();
                             return true;
                         }
                         return false;
@@ -176,33 +205,45 @@ public class RecipeViewingWidgetScreen extends Screen {
                 tab.setItem(categories.get(j).getCategoryIcon(), categories.get(j).getCategoryName(), tab.getId() + categoryPages * 6 == categories.indexOf(selectedCategory));
             }
         }
-        SpeedCraftAreaSupplier supplier = RecipeHelper.getInstance().getSpeedCraftButtonArea(selectedCategory);
+        Optional<SpeedCraftAreaSupplier> supplier = IRecipeHelper.getInstance().getSpeedCraftButtonArea(selectedCategory);
         final SpeedCraftFunctional functional = getSpeedCraftFunctionalByCategory(GuiHelper.getLastContainerScreen(), selectedCategory);
-        if (page * getRecipesPerPage() < categoriesMap.get(selectedCategory).size()) {
-            final Supplier<IRecipeDisplay> topDisplaySupplier = () -> {
-                return categoriesMap.get(selectedCategory).get(page * getRecipesPerPage());
+        int recipeHeight = selectedCategory.getDisplayHeight();
+        List<IRecipeDisplay> currentDisplayed = getCurrentDisplayed();
+        for(int i = 0; i < currentDisplayed.size(); i++) {
+            int finalI = i;
+            final Supplier<IRecipeDisplay> displaySupplier = () -> {
+                return currentDisplayed.get(finalI);
             };
-            final Rectangle topBounds = new Rectangle((int) getBounds().getCenterX() - 75, getBounds().y + 40, 150, selectedCategory.usesFullPage() ? 140 : 66);
-            widgets.addAll(selectedCategory.setupDisplay(topDisplaySupplier, topBounds));
-            if (supplier != null)
-                widgets.add(new SpeedCraftingButtonWidget(supplier.get(topBounds), supplier.getButtonText(), functional, topDisplaySupplier));
-            if (!selectedCategory.usesFullPage() && page * getRecipesPerPage() + 1 < categoriesMap.get(selectedCategory).size()) {
-                final Supplier<IRecipeDisplay> middleDisplaySupplier = () -> {
-                    return categoriesMap.get(selectedCategory).get(page * getRecipesPerPage() + 1);
-                };
-                final Rectangle middleBounds = new Rectangle((int) getBounds().getCenterX() - 75, getBounds().y + 113, 150, 66);
-                widgets.addAll(selectedCategory.setupDisplay(middleDisplaySupplier, middleBounds));
-                if (supplier != null)
-                    widgets.add(new SpeedCraftingButtonWidget(supplier.get(middleBounds), supplier.getButtonText(), functional, middleDisplaySupplier));
-            }
+            int displayWidth = selectedCategory.getDisplayWidth(displaySupplier.get());
+            final Rectangle displayBounds = new Rectangle((int) getBounds().getCenterX() - displayWidth / 2, getBounds().y + 40 + recipeHeight * i + 7 * i, displayWidth, recipeHeight);
+            widgets.addAll(selectedCategory.setupDisplay(displaySupplier, displayBounds));
+            if (supplier.isPresent())
+                widgets.add(new SpeedCraftingButtonWidget(supplier.get().get(displayBounds), supplier.get().getButtonText(), functional, displaySupplier));
         }
-        
+        if (choosePageActivated)
+            recipeChoosePageWidget = new RecipeChoosePageWidget(this, page, getTotalPages(selectedCategory));
+        else
+            recipeChoosePageWidget = null;
+    
         GuiHelper.getLastOverlay().onInitialized();
         listeners.addAll(tabs);
         listeners.add(GuiHelper.getLastOverlay());
         listeners.addAll(widgets);
     }
     
+    public List<IRecipeDisplay> getCurrentDisplayed() {
+        List<IRecipeDisplay> list = Lists.newArrayList();
+        int recipesPerPage = getRecipesPerPage();
+        for(int i = 0; i <= recipesPerPage; i++)
+            if (recipesPerPage > 0 && page * (recipesPerPage + 1) + i < categoriesMap.get(selectedCategory).size())
+                list.add(categoriesMap.get(selectedCategory).get(page * (recipesPerPage + 1) + i));
+        return list;
+    }
+    
+    public IRecipeCategory getSelectedCategory() {
+        return selectedCategory;
+    }
+    
     public int getPage() {
         return page;
     }
@@ -212,44 +253,43 @@ public class RecipeViewingWidgetScreen extends Screen {
     }
     
     private int getRecipesPerPage() {
-        if (selectedCategory.usesFullPage())
-            return 1;
-        return 2;
+        int height = selectedCategory.getDisplayHeight();
+        return MathHelper.clamp(MathHelper.floor(((float) largestHeight - 40f) / ((float) height + 7f)) - 1, 0, Math.min(RoughlyEnoughItemsCore.getConfigHelper().getConfig().maxRecipePerPage - 1, selectedCategory.getMaximumRecipePerPage() - 1));
     }
     
     @Override
-    public void method_18326(int mouseX, int mouseY, float partialTicks) {
-        drawBackground();
+    public void draw(int mouseX, int mouseY, float delta) {
+        this.drawGradientRect(0, 0, this.width, this.height, -1072689136, -804253680);
+        if (selectedCategory != null)
+            selectedCategory.drawCategoryBackground(bounds, mouseX, mouseY, delta);
+        else {
+            new RecipeBaseWidget(bounds).draw(mouseX, mouseY, delta);
+            drawRect(bounds.x + 17, bounds.y + 5, bounds.x + bounds.width - 17, bounds.y + 17, SUB_COLOR.getRGB());
+            drawRect(bounds.x + 17, bounds.y + 21, bounds.x + bounds.width - 17, bounds.y + 33, SUB_COLOR.getRGB());
+        }
         tabs.stream().filter(tabWidget -> {
             return !tabWidget.isSelected();
-        }).forEach(tabWidget -> tabWidget.draw(mouseX, mouseY, partialTicks));
+        }).forEach(tabWidget -> tabWidget.draw(mouseX, mouseY, delta));
         GuiLighting.disable();
-        super.method_18326(mouseX, mouseY, partialTicks);
+        super.draw(mouseX, mouseY, delta);
         widgets.forEach(widget -> {
             GuiLighting.disable();
-            widget.draw(mouseX, mouseY, partialTicks);
+            widget.draw(mouseX, mouseY, delta);
         });
         GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
         GuiLighting.disable();
-        tabs.stream().filter(TabWidget::isSelected).forEach(tabWidget -> tabWidget.draw(mouseX, mouseY, partialTicks));
-        GuiHelper.getLastOverlay().drawOverlay(mouseX, mouseY, partialTicks);
-    }
-    
-    @Override
-    public void drawBackground() {
-        drawBackground(0);
-        if (selectedCategory != null)
-            selectedCategory.drawCategoryBackground(bounds);
-        else {
-            GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-            GuiLighting.disable();
-            this.client.getTextureManager().bindTexture(CHEST_GUI_TEXTURE);
-            this.drawTexturedRect((int) bounds.getX(), (int) bounds.getY(), 0, 0, (int) bounds.getWidth(), (int) bounds.getHeight());
+        tabs.stream().filter(TabWidget::isSelected).forEach(tabWidget -> tabWidget.draw(mouseX, mouseY, delta));
+        GuiHelper.getLastOverlay().drawOverlay(mouseX, mouseY, delta);
+        if (choosePageActivated) {
+            zOffset = 500.0f;
+            this.drawGradientRect(0, 0, this.width, this.height, -1072689136, -804253680);
+            zOffset = 0.0f;
+            recipeChoosePageWidget.draw(mouseX, mouseY, delta);
         }
     }
     
     public int getTotalPages(IRecipeCategory category) {
-        return MathHelper.ceil(categoriesMap.get(category).size() / (double) getRecipesPerPage());
+        return MathHelper.ceil(categoriesMap.get(category).size() / (double) (getRecipesPerPage() + 1));
     }
     
     public Rectangle getBounds() {
@@ -258,15 +298,40 @@ public class RecipeViewingWidgetScreen extends Screen {
     
     @Override
     public boolean charTyped(char char_1, int int_1) {
-        for(GuiEventListener listener : listeners)
+        if (choosePageActivated) {
+            if (recipeChoosePageWidget.charTyped(char_1, int_1))
+                return true;
+            return false;
+        }
+        for(InputListener listener : listeners)
             if (listener.charTyped(char_1, int_1))
                 return true;
         return super.charTyped(char_1, int_1);
     }
     
+    @Override
+    public boolean mouseDragged(double double_1, double double_2, int int_1, double double_3, double double_4) {
+        if (choosePageActivated) {
+            if (recipeChoosePageWidget.mouseDragged(double_1, double_2, int_1, double_3, double_4))
+                return true;
+            return false;
+        }
+        return super.mouseDragged(double_1, double_2, int_1, double_3, double_4);
+    }
+    
+    @Override
+    public boolean mouseReleased(double double_1, double double_2, int int_1) {
+        if (choosePageActivated) {
+            if (recipeChoosePageWidget.mouseReleased(double_1, double_2, int_1))
+                return true;
+            return false;
+        }
+        return super.mouseReleased(double_1, double_2, int_1);
+    }
+    
     @Override
     public boolean mouseScrolled(double amount) {
-        for(GuiEventListener listener : listeners)
+        for(InputListener listener : listeners)
             if (listener.mouseScrolled(amount))
                 return true;
         if (getBounds().contains(ClientHelper.getMouseLocation())) {
@@ -286,9 +351,19 @@ public class RecipeViewingWidgetScreen extends Screen {
     
     @Override
     public boolean mouseClicked(double double_1, double double_2, int int_1) {
-        for(GuiEventListener entry : method_1968()) //getEntries
+        if (choosePageActivated)
+            if (recipeChoosePageWidget.isHighlighted(double_1, double_2)) {
+                if (recipeChoosePageWidget.mouseClicked(double_1, double_2, int_1))
+                    return true;
+                return false;
+            } else {
+                choosePageActivated = false;
+                onInitialized();
+                return false;
+            }
+        for(InputListener entry : getInputListeners())
             if (entry.mouseClicked(double_1, double_2, int_1)) {
-                method_1967(entry); //focusOn
+                focusOn(entry);
                 if (int_1 == 0)
                     method_1966(true); //setActive
                 return true;
@@ -296,4 +371,11 @@ public class RecipeViewingWidgetScreen extends Screen {
         return false;
     }
     
+    @Override
+    public InputListener getFocused() {
+        if (choosePageActivated)
+            return recipeChoosePageWidget;
+        return super.getFocused();
+    }
+    
 }

+ 126 - 45
src/main/java/me/shedaniel/rei/gui/config/ConfigEntry.java

@@ -2,73 +2,154 @@ package me.shedaniel.rei.gui.config;
 
 import me.shedaniel.rei.client.ClientHelper;
 import me.shedaniel.rei.gui.widget.ButtonWidget;
+import me.shedaniel.rei.gui.widget.TextFieldWidget;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.audio.PositionedSoundInstance;
+import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gui.widget.EntryListWidget;
 import net.minecraft.client.util.Window;
 import net.minecraft.sound.SoundEvents;
+import net.minecraft.text.Style;
 import net.minecraft.text.TextComponent;
 
 import java.awt.*;
 
-public class ConfigEntry extends EntryListWidget.Entry<ConfigEntry> {
+public abstract class ConfigEntry extends EntryListWidget.Entry<ConfigEntry> {
     
-    private TextComponent nameComponent;
-    private ConfigEntryButtonProvider buttonProvider;
-    private ButtonWidget buttonWidget;
-    
-    public ConfigEntry(TextComponent nameComponent, ConfigEntryButtonProvider buttonProvider) {
-        this.nameComponent = nameComponent;
-        this.buttonProvider = buttonProvider;
-        this.buttonWidget = new ButtonWidget(0, 0, 150, 20, "") {
-            @Override
-            public boolean onMouseClick(int button, double mouseX, double mouseY) {
-                if (getBounds().contains(mouseX, mouseY) && enabled)
-                    if (buttonProvider.onPressed(button, mouseX, mouseY)) {
-                        MinecraftClient.getInstance().getSoundLoader().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
-                        return true;
-                    }
-                return false;
+    public static class ButtonConfigEntry extends ConfigEntry {
+        private TextComponent nameComponent;
+        private ConfigEntryButtonProvider buttonProvider;
+        private ButtonWidget buttonWidget;
+        
+        public ButtonConfigEntry(TextComponent nameComponent, ConfigEntryButtonProvider buttonProvider) {
+            this.nameComponent = nameComponent;
+            this.buttonProvider = buttonProvider;
+            this.buttonWidget = new ButtonWidget(0, 0, 150, 20, "") {
+                @Override
+                public boolean onMouseClick(int button, double mouseX, double mouseY) {
+                    if (getBounds().contains(mouseX, mouseY) && enabled)
+                        if (buttonProvider.onPressed(button, mouseX, mouseY)) {
+                            MinecraftClient.getInstance().getSoundLoader().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+                            return true;
+                        }
+                    return false;
+                }
+                
+                @Override
+                public void onPressed(int button, double mouseX, double mouseY) {}
+            };
+        }
+        
+        @Override
+        public void draw(int entryWidth, int height, int i3, int i4, boolean isSelected, float delta) {
+            Window window = MinecraftClient.getInstance().window;
+            Point mouse = ClientHelper.getMouseLocation();
+            if (MinecraftClient.getInstance().textRenderer.isRightToLeft()) {
+                MinecraftClient.getInstance().textRenderer.drawWithShadow(nameComponent.getFormattedText(), window.getScaledWidth() - MinecraftClient.getInstance().textRenderer.getStringWidth(nameComponent.getFormattedText()) - 40, getY() + 5, 16777215);
+                this.buttonWidget.text = buttonProvider.getText();
+                this.buttonWidget.getBounds().setLocation(getX(), getY() + 2);
+            } else {
+                MinecraftClient.getInstance().textRenderer.drawWithShadow(nameComponent.getFormattedText(), getX(), getY() + 5, 16777215);
+                this.buttonWidget.text = buttonProvider.getText();
+                this.buttonWidget.getBounds().setLocation(window.getScaledWidth() - 190, getY() + 2);
+            }
+            buttonProvider.draw(buttonWidget, mouse, delta);
+        }
+        
+        @Override
+        public boolean mouseClicked(double double_1, double double_2, int int_1) {
+            if (buttonWidget.mouseClicked(double_1, double_2, int_1))
+                return true;
+            return false;
+        }
+        
+        interface ConfigEntryButtonProvider {
+            
+            public boolean onPressed(int button, double mouseX, double mouseY);
+            
+            public String getText();
+            
+            default public void draw(ButtonWidget button, Point mouse, float delta) {
+                button.draw(mouse.x, mouse.y, delta);
             }
             
-            @Override
-            public void onPressed(int button, double mouseX, double mouseY) {}
-        };
-    }
-    
-    @Override
-    public void draw(int entryWidth, int height, int i3, int i4, boolean isSelected, float delta) {
-        Window window = MinecraftClient.getInstance().window;
-        Point mouse = ClientHelper.getMouseLocation();
-        if (MinecraftClient.getInstance().textRenderer.isRightToLeft()) {
-            MinecraftClient.getInstance().textRenderer.drawWithShadow(nameComponent.getFormattedText(), window.getScaledWidth() - MinecraftClient.getInstance().textRenderer.getStringWidth(nameComponent.getFormattedText()) - 40, getY() + 5, 16777215);
-            this.buttonWidget.text = buttonProvider.getText();
-            this.buttonWidget.getBounds().setLocation(getX(), getY() + 2);
-        } else {
-            MinecraftClient.getInstance().textRenderer.drawWithShadow(nameComponent.getFormattedText(), getX(), getY() + 5, 16777215);
-            this.buttonWidget.text = buttonProvider.getText();
-            this.buttonWidget.getBounds().setLocation(window.getScaledWidth() - 190, getY() + 2);
         }
-        buttonProvider.draw(buttonWidget, mouse, delta);
     }
     
-    @Override
-    public boolean mouseClicked(double double_1, double double_2, int int_1) {
-        if (buttonWidget.mouseClicked(double_1, double_2, int_1))
-            return true;
-        return false;
+    public static class CategoryTitleConfigEntry extends ConfigEntry {
+        private TextComponent textComponent;
+        
+        public CategoryTitleConfigEntry(TextComponent nameComponent) {
+            this.textComponent = nameComponent.setStyle(new Style().setBold(true));
+        }
+        
+        @Override
+        public void draw(int i, int i1, int i2, int i3, boolean b, float v) {
+            Window window = MinecraftClient.getInstance().window;
+            TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+            textRenderer.draw(textComponent.getFormattedText(), (window.getScaledWidth() - textRenderer.getStringWidth(textComponent.getFormattedText())) / 2, getY() + 10, -1);
+        }
     }
     
-    interface ConfigEntryButtonProvider {
+    public static class TextFieldConfigEntry extends ConfigEntry {
+        private TextComponent nameComponent;
+        private ConfigEntryTextFieldProvider textFieldProvider;
+        private TextFieldWidget textFieldWidget;
+        
+        public TextFieldConfigEntry(TextComponent nameComponent, ConfigEntryTextFieldProvider textFieldProvider) {
+            this.nameComponent = nameComponent;
+            this.textFieldProvider = textFieldProvider;
+            this.textFieldWidget = new TextFieldWidget(0, 0, 148, 18);
+            this.textFieldWidget.setChangedListener(s -> textFieldProvider.onUpdateText(textFieldWidget, s));
+            this.textFieldProvider.onInitWidget(textFieldWidget);
+        }
         
-        public boolean onPressed(int button, double mouseX, double mouseY);
+        @Override
+        public void draw(int entryWidth, int height, int i3, int i4, boolean isSelected, float delta) {
+            Window window = MinecraftClient.getInstance().window;
+            Point mouse = ClientHelper.getMouseLocation();
+            if (MinecraftClient.getInstance().textRenderer.isRightToLeft()) {
+                MinecraftClient.getInstance().textRenderer.drawWithShadow(nameComponent.getFormattedText(), window.getScaledWidth() - MinecraftClient.getInstance().textRenderer.getStringWidth(nameComponent.getFormattedText()) - 40, getY() + 5, 16777215);
+                this.textFieldWidget.getBounds().setLocation(getX() + 1, getY() + 2);
+            } else {
+                MinecraftClient.getInstance().textRenderer.drawWithShadow(nameComponent.getFormattedText(), getX(), getY() + 5, 16777215);
+                this.textFieldWidget.getBounds().setLocation(window.getScaledWidth() - 190 + 1, getY() + 2);
+            }
+            textFieldProvider.draw(textFieldWidget, mouse, delta);
+        }
         
-        public String getText();
+        @Override
+        public boolean mouseClicked(double double_1, double double_2, int int_1) {
+            if (textFieldWidget.mouseClicked(double_1, double_2, int_1))
+                return true;
+            return false;
+        }
         
-        default public void draw(ButtonWidget button, Point mouse, float delta) {
-            button.draw(mouse.x, mouse.y, delta);
+        @Override
+        public boolean charTyped(char char_1, int int_1) {
+            if (textFieldWidget.charTyped(char_1, int_1))
+                return true;
+            return false;
         }
         
+        @Override
+        public boolean keyPressed(int int_1, int int_2, int int_3) {
+            if (textFieldWidget.keyPressed(int_1, int_2, int_3))
+                return true;
+            return false;
+        }
+        
+        interface ConfigEntryTextFieldProvider {
+            
+            public void onInitWidget(TextFieldWidget widget);
+            
+            public void onUpdateText(TextFieldWidget widget, String text);
+            
+            default public void draw(TextFieldWidget widget, Point mouse, float delta) {
+                widget.draw(mouse.x, mouse.y, delta);
+            }
+            
+        }
     }
     
 }

+ 1 - 1
src/main/java/me/shedaniel/rei/gui/config/ConfigEntryListWidget.java

@@ -15,7 +15,7 @@ public class ConfigEntryListWidget extends EntryListWidget<ConfigEntry> {
     }
     
     private ConfigEntry getEntry(int int_1) {
-        return this.method_1968().get(int_1); //getEntries
+        return this.getInputListeners().get(int_1);
     }
     
     public void configAddEntry(ConfigEntry entry) {

+ 179 - 32
src/main/java/me/shedaniel/rei/gui/config/ConfigScreen.java

@@ -1,10 +1,12 @@
 package me.shedaniel.rei.gui.config;
 
 import me.shedaniel.rei.RoughlyEnoughItemsCore;
+import me.shedaniel.rei.client.ClientHelper;
 import me.shedaniel.rei.client.GuiHelper;
 import me.shedaniel.rei.client.REIItemListOrdering;
+import me.shedaniel.rei.gui.widget.TextFieldWidget;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.GuiEventListener;
+import net.minecraft.client.gui.InputListener;
 import net.minecraft.client.gui.Screen;
 import net.minecraft.client.gui.widget.ButtonWidget;
 import net.minecraft.client.render.GuiLighting;
@@ -39,11 +41,49 @@ public class ConfigScreen extends Screen {
     protected void onInitialized() {
         listeners.add(entryListWidget = new ConfigEntryListWidget(client, width, height, 32, height - 32, 24));
         entryListWidget.configClearEntries();
-        entryListWidget.configAddEntry(new ConfigEntry(new TranslatableTextComponent("text.rei.side_searchbox"), new ConfigEntry.ConfigEntryButtonProvider() {
+        entryListWidget.configAddEntry(new ConfigEntry.CategoryTitleConfigEntry(new TranslatableTextComponent("text.rei.config.general")));
+        entryListWidget.configAddEntry(new ConfigEntry.ButtonConfigEntry(new TranslatableTextComponent("text.rei.config.cheating"), new ConfigEntry.ButtonConfigEntry.ConfigEntryButtonProvider() {
             @Override
             public boolean onPressed(int button, double mouseX, double mouseY) {
                 if (button == 0)
-                    RoughlyEnoughItemsCore.getConfigHelper().setSideSearchField(!RoughlyEnoughItemsCore.getConfigHelper().sideSearchField());
+                    ClientHelper.setCheating(!ClientHelper.isCheating());
+                return true;
+            }
+            
+            @Override
+            public String getText() {
+                return getTrueFalseText(ClientHelper.isCheating());
+            }
+        }));
+        entryListWidget.configAddEntry(new ConfigEntry.CategoryTitleConfigEntry(new TranslatableTextComponent("text.rei.config.appearance")));
+        entryListWidget.configAddEntry(new ConfigEntry.ButtonConfigEntry(new TranslatableTextComponent("text.rei.config.side_search_box"), new ConfigEntry.ButtonConfigEntry.ConfigEntryButtonProvider() {
+            @Override
+            public boolean onPressed(int button, double mouseX, double mouseY) {
+                if (button == 0)
+                    RoughlyEnoughItemsCore.getConfigHelper().getConfig().sideSearchField = !RoughlyEnoughItemsCore.getConfigHelper().getConfig().sideSearchField;
+                try {
+                    RoughlyEnoughItemsCore.getConfigHelper().saveConfig();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    return false;
+                }
+                return true;
+            }
+            
+            @Override
+            public String getText() {
+                return getTrueFalseText(RoughlyEnoughItemsCore.getConfigHelper().getConfig().sideSearchField);
+            }
+        }));
+        entryListWidget.configAddEntry(new ConfigEntry.ButtonConfigEntry(new TranslatableTextComponent("text.rei.config.list_ordering"), new ConfigEntry.ButtonConfigEntry.ConfigEntryButtonProvider() {
+            @Override
+            public boolean onPressed(int button, double mouseX, double mouseY) {
+                int index = Arrays.asList(REIItemListOrdering.values()).indexOf(RoughlyEnoughItemsCore.getConfigHelper().getConfig().itemListOrdering) + 1;
+                if (index >= REIItemListOrdering.values().length) {
+                    index = 0;
+                    RoughlyEnoughItemsCore.getConfigHelper().getConfig().isAscending = !RoughlyEnoughItemsCore.getConfigHelper().getConfig().isAscending;
+                }
+                RoughlyEnoughItemsCore.getConfigHelper().getConfig().itemListOrdering = REIItemListOrdering.values()[index];
                 try {
                     RoughlyEnoughItemsCore.getConfigHelper().saveConfig();
                 } catch (IOException e) {
@@ -55,14 +95,14 @@ public class ConfigScreen extends Screen {
             
             @Override
             public String getText() {
-                return getTrueFalseText(RoughlyEnoughItemsCore.getConfigHelper().sideSearchField());
+                return I18n.translate("text.rei.config.list_ordering_button", I18n.translate(RoughlyEnoughItemsCore.getConfigHelper().getConfig().itemListOrdering.getNameTranslationKey()), I18n.translate(RoughlyEnoughItemsCore.getConfigHelper().getConfig().isAscending ? "ordering.rei.ascending" : "ordering.rei.descending"));
             }
         }));
-        entryListWidget.configAddEntry(new ConfigEntry(new TranslatableTextComponent("text.rei.enable_craftable_only"), new ConfigEntry.ConfigEntryButtonProvider() {
+        entryListWidget.configAddEntry(new ConfigEntry.ButtonConfigEntry(new TranslatableTextComponent("text.rei.config.mirror_rei"), new ConfigEntry.ButtonConfigEntry.ConfigEntryButtonProvider() {
             @Override
             public boolean onPressed(int button, double mouseX, double mouseY) {
                 if (button == 0)
-                    RoughlyEnoughItemsCore.getConfigHelper().setShowCraftableOnlyButton(!RoughlyEnoughItemsCore.getConfigHelper().showCraftableOnlyButton());
+                    RoughlyEnoughItemsCore.getConfigHelper().getConfig().mirrorItemPanel = !RoughlyEnoughItemsCore.getConfigHelper().getConfig().mirrorItemPanel;
                 try {
                     RoughlyEnoughItemsCore.getConfigHelper().saveConfig();
                 } catch (IOException e) {
@@ -74,18 +114,34 @@ public class ConfigScreen extends Screen {
             
             @Override
             public String getText() {
-                return getTrueFalseText(RoughlyEnoughItemsCore.getConfigHelper().showCraftableOnlyButton());
+                return getTrueFalseText(RoughlyEnoughItemsCore.getConfigHelper().getConfig().mirrorItemPanel);
             }
         }));
-        entryListWidget.configAddEntry(new ConfigEntry(new TranslatableTextComponent("text.rei.list_ordering"), new ConfigEntry.ConfigEntryButtonProvider() {
+        entryListWidget.configAddEntry(new ConfigEntry.CategoryTitleConfigEntry(new TranslatableTextComponent("text.rei.config.modules")));
+        entryListWidget.configAddEntry(new ConfigEntry.ButtonConfigEntry(new TranslatableTextComponent("text.rei.config.enable_craftable_only"), new ConfigEntry.ButtonConfigEntry.ConfigEntryButtonProvider() {
             @Override
             public boolean onPressed(int button, double mouseX, double mouseY) {
-                int index = Arrays.asList(REIItemListOrdering.values()).indexOf(RoughlyEnoughItemsCore.getConfigHelper().getItemListOrdering()) + 1;
-                if (index >= REIItemListOrdering.values().length) {
-                    index = 0;
-                    RoughlyEnoughItemsCore.getConfigHelper().setAscending(!RoughlyEnoughItemsCore.getConfigHelper().isAscending());
+                if (button == 0)
+                    RoughlyEnoughItemsCore.getConfigHelper().getConfig().enableCraftableOnlyButton = !RoughlyEnoughItemsCore.getConfigHelper().getConfig().enableCraftableOnlyButton;
+                try {
+                    RoughlyEnoughItemsCore.getConfigHelper().saveConfig();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    return false;
                 }
-                RoughlyEnoughItemsCore.getConfigHelper().setItemListOrdering(REIItemListOrdering.values()[index]);
+                return true;
+            }
+            
+            @Override
+            public String getText() {
+                return getTrueFalseText(RoughlyEnoughItemsCore.getConfigHelper().getConfig().enableCraftableOnlyButton);
+            }
+        }));
+        entryListWidget.configAddEntry(new ConfigEntry.ButtonConfigEntry(new TranslatableTextComponent("text.rei.config.load_default_plugin"), new ConfigEntry.ButtonConfigEntry.ConfigEntryButtonProvider() {
+            @Override
+            public boolean onPressed(int button, double mouseX, double mouseY) {
+                if (button == 0)
+                    RoughlyEnoughItemsCore.getConfigHelper().getConfig().loadDefaultPlugin = !RoughlyEnoughItemsCore.getConfigHelper().getConfig().loadDefaultPlugin;
                 try {
                     RoughlyEnoughItemsCore.getConfigHelper().saveConfig();
                 } catch (IOException e) {
@@ -97,14 +153,24 @@ public class ConfigScreen extends Screen {
             
             @Override
             public String getText() {
-                return I18n.translate("text.rei.list_ordering_button", I18n.translate(RoughlyEnoughItemsCore.getConfigHelper().getItemListOrdering().getNameTranslationKey()), I18n.translate(RoughlyEnoughItemsCore.getConfigHelper().isAscending() ? "ordering.rei.ascending" : "ordering.rei.descending"));
+                return getTrueFalseText(RoughlyEnoughItemsCore.getConfigHelper().getConfig().loadDefaultPlugin);
+            }
+            
+            @Override
+            public void draw(me.shedaniel.rei.gui.widget.ButtonWidget button, Point mouse, float delta) {
+                button.draw(mouse.x, mouse.y, delta);
+                if (button.isHighlighted(mouse)) {
+                    GuiLighting.disable();
+                    drawTooltip(Arrays.asList(I18n.translate("text.rei.config.load_default_plugin.restart_tooltip").split("\n")), mouse.x, mouse.y);
+                    GuiLighting.disable();
+                }
             }
         }));
-        entryListWidget.configAddEntry(new ConfigEntry(new TranslatableTextComponent("text.rei.mirror_rei"), new ConfigEntry.ConfigEntryButtonProvider() {
+        entryListWidget.configAddEntry(new ConfigEntry.ButtonConfigEntry(new TranslatableTextComponent("text.rei.config.disable_credits_button"), new ConfigEntry.ButtonConfigEntry.ConfigEntryButtonProvider() {
             @Override
             public boolean onPressed(int button, double mouseX, double mouseY) {
                 if (button == 0)
-                    RoughlyEnoughItemsCore.getConfigHelper().setMirrorItemPanel(!RoughlyEnoughItemsCore.getConfigHelper().isMirrorItemPanel());
+                    RoughlyEnoughItemsCore.getConfigHelper().getConfig().disableCreditsButton = !RoughlyEnoughItemsCore.getConfigHelper().getConfig().disableCreditsButton;
                 try {
                     RoughlyEnoughItemsCore.getConfigHelper().saveConfig();
                 } catch (IOException e) {
@@ -116,14 +182,14 @@ public class ConfigScreen extends Screen {
             
             @Override
             public String getText() {
-                return getTrueFalseText(RoughlyEnoughItemsCore.getConfigHelper().isMirrorItemPanel());
+                return getTrueFalseText(RoughlyEnoughItemsCore.getConfigHelper().getConfig().disableCreditsButton);
             }
         }));
-        entryListWidget.configAddEntry(new ConfigEntry(new TranslatableTextComponent("text.rei.check_updates"), new ConfigEntry.ConfigEntryButtonProvider() {
+        entryListWidget.configAddEntry(new ConfigEntry.ButtonConfigEntry(new TranslatableTextComponent("text.rei.config.enable_util_buttons"), new ConfigEntry.ButtonConfigEntry.ConfigEntryButtonProvider() {
             @Override
             public boolean onPressed(int button, double mouseX, double mouseY) {
                 if (button == 0)
-                    RoughlyEnoughItemsCore.getConfigHelper().setCheckUpdates(!RoughlyEnoughItemsCore.getConfigHelper().checkUpdates());
+                    RoughlyEnoughItemsCore.getConfigHelper().getConfig().showUtilsButtons = !RoughlyEnoughItemsCore.getConfigHelper().getConfig().showUtilsButtons;
                 try {
                     RoughlyEnoughItemsCore.getConfigHelper().saveConfig();
                 } catch (IOException e) {
@@ -135,14 +201,15 @@ public class ConfigScreen extends Screen {
             
             @Override
             public String getText() {
-                return getTrueFalseText(RoughlyEnoughItemsCore.getConfigHelper().checkUpdates());
+                return getTrueFalseText(RoughlyEnoughItemsCore.getConfigHelper().getConfig().showUtilsButtons);
             }
         }));
-        entryListWidget.configAddEntry(new ConfigEntry(new TranslatableTextComponent("text.rei.load_default_plugin"), new ConfigEntry.ConfigEntryButtonProvider() {
+        entryListWidget.configAddEntry(new ConfigEntry.CategoryTitleConfigEntry(new TranslatableTextComponent("text.rei.config.advanced")));
+        entryListWidget.configAddEntry(new ConfigEntry.ButtonConfigEntry(new TranslatableTextComponent("text.rei.check_updates"), new ConfigEntry.ButtonConfigEntry.ConfigEntryButtonProvider() {
             @Override
             public boolean onPressed(int button, double mouseX, double mouseY) {
                 if (button == 0)
-                    RoughlyEnoughItemsCore.getConfigHelper().setLoadingDefaultPlugin(!RoughlyEnoughItemsCore.getConfigHelper().isLoadingDefaultPlugin());
+                    RoughlyEnoughItemsCore.getConfigHelper().getConfig().checkUpdates = !RoughlyEnoughItemsCore.getConfigHelper().getConfig().checkUpdates;
                 try {
                     RoughlyEnoughItemsCore.getConfigHelper().saveConfig();
                 } catch (IOException e) {
@@ -154,19 +221,100 @@ public class ConfigScreen extends Screen {
             
             @Override
             public String getText() {
-                return getTrueFalseText(RoughlyEnoughItemsCore.getConfigHelper().isLoadingDefaultPlugin());
+                return getTrueFalseText(RoughlyEnoughItemsCore.getConfigHelper().getConfig().checkUpdates);
+            }
+        }));
+        entryListWidget.configAddEntry(new ConfigEntry.TextFieldConfigEntry(new TranslatableTextComponent("text.rei.give_command"), new ConfigEntry.TextFieldConfigEntry.ConfigEntryTextFieldProvider() {
+            @Override
+            public void onInitWidget(TextFieldWidget widget) {
+                widget.setMaxLength(99999);
+                widget.setText(RoughlyEnoughItemsCore.getConfigHelper().getConfig().giveCommand);
+                widget.setSuggestion(I18n.translate("text.rei.give_command.suggestion"));
             }
             
             @Override
-            public void draw(me.shedaniel.rei.gui.widget.ButtonWidget button, Point mouse, float delta) {
-                button.draw(mouse.x, mouse.y, delta);
-                if (button.getBounds().contains(mouse)) {
+            public void onUpdateText(TextFieldWidget button, String text) {
+                RoughlyEnoughItemsCore.getConfigHelper().getConfig().giveCommand = text;
+                try {
+                    RoughlyEnoughItemsCore.getConfigHelper().saveConfig();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            
+            @Override
+            public void draw(TextFieldWidget widget, Point mouse, float delta) {
+                widget.draw(mouse.x, mouse.y, delta);
+                if (widget.isHighlighted(mouse)) {
                     GuiLighting.disable();
-                    drawTooltip(Arrays.asList(I18n.translate("text.rei.load_default_plugin.restart_tooltip").split("\n")), mouse.x, mouse.y);
+                    drawTooltip(Arrays.asList(I18n.translate("text.rei.give_command.tooltip").split("\n")), mouse.x, mouse.y);
                     GuiLighting.disable();
                 }
             }
         }));
+        entryListWidget.configAddEntry(new ConfigEntry.TextFieldConfigEntry(new TranslatableTextComponent("text.rei.gamemode_command"), new ConfigEntry.TextFieldConfigEntry.ConfigEntryTextFieldProvider() {
+            @Override
+            public void onInitWidget(TextFieldWidget widget) {
+                widget.setMaxLength(99999);
+                widget.setText(RoughlyEnoughItemsCore.getConfigHelper().getConfig().gamemodeCommand);
+                widget.setSuggestion(I18n.translate("text.rei.give_command.suggestion"));
+            }
+            
+            @Override
+            public void onUpdateText(TextFieldWidget button, String text) {
+                RoughlyEnoughItemsCore.getConfigHelper().getConfig().gamemodeCommand = text;
+                try {
+                    RoughlyEnoughItemsCore.getConfigHelper().saveConfig();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }));
+        entryListWidget.configAddEntry(new ConfigEntry.TextFieldConfigEntry(new TranslatableTextComponent("text.rei.config.max_recipes_per_page"), new ConfigEntry.TextFieldConfigEntry.ConfigEntryTextFieldProvider() {
+            @Override
+            public void onInitWidget(TextFieldWidget widget) {
+                widget.setMaxLength(2);
+                widget.setText(RoughlyEnoughItemsCore.getConfigHelper().getConfig().maxRecipePerPage + "");
+                widget.stripInvaild = s -> {
+                    StringBuilder stringBuilder_1 = new StringBuilder();
+                    char[] var2 = s.toCharArray();
+                    int var3 = var2.length;
+                    
+                    for(int var4 = 0; var4 < var3; ++var4) {
+                        char char_1 = var2[var4];
+                        if (Character.isDigit(char_1))
+                            stringBuilder_1.append(char_1);
+                    }
+                    
+                    return stringBuilder_1.toString();
+                };
+            }
+            
+            @Override
+            public void onUpdateText(TextFieldWidget button, String text) {
+                if (isInvaildNumber(text))
+                    try {
+                        RoughlyEnoughItemsCore.getConfigHelper().getConfig().maxRecipePerPage = Integer.valueOf(text);
+                        RoughlyEnoughItemsCore.getConfigHelper().saveConfig();
+                    } catch (Exception e) {
+                    }
+            }
+            
+            @Override
+            public void draw(TextFieldWidget widget, Point mouse, float delta) {
+                widget.setEditableColor(isInvaildNumber(widget.getText()) ? -1 : Color.RED.getRGB());
+                widget.draw(mouse.x, mouse.y, delta);
+            }
+            
+            private boolean isInvaildNumber(String text) {
+                try {
+                    int page = Integer.valueOf(text);
+                    return page >= 2 && page <= 99;
+                } catch (Exception e) {
+                }
+                return false;
+            }
+        }));
         addButton(new ButtonWidget(width / 2 - 100, height - 26, I18n.translate("gui.done")) {
             @Override
             public void onPressed(double double_1, double double_2) {
@@ -187,12 +335,11 @@ public class ConfigScreen extends Screen {
     }
     
     @Override
-    public void method_18326(int int_1, int int_2, float float_1) {
-        //draw
+    public void draw(int int_1, int int_2, float float_1) {
         this.drawTextureBackground(0);
-        this.entryListWidget.method_18326(int_1, int_2, float_1);
+        this.entryListWidget.draw(int_1, int_2, float_1);
         this.drawStringCentered(this.fontRenderer, I18n.translate("text.rei.config"), this.width / 2, 16, 16777215);
-        super.method_18326(int_1, int_2, float_1);
+        super.draw(int_1, int_2, float_1);
     }
     
     @Override
@@ -201,7 +348,7 @@ public class ConfigScreen extends Screen {
     }
     
     @Override
-    public GuiEventListener getFocused() {
+    public InputListener getFocused() {
         return entryListWidget;
     }
     

+ 1 - 1
src/main/java/me/shedaniel/rei/gui/credits/CreditsEntryListWidget.java

@@ -15,7 +15,7 @@ public class CreditsEntryListWidget extends EntryListWidget<CreditsEntry> {
     }
     
     private CreditsEntry getEntry(int int_1) {
-        return this.method_1968().get(int_1); //getEntries
+        return this.getInputListeners().get(int_1);
     }
     
     public void creditsAddEntry(CreditsEntry entry) {

+ 5 - 5
src/main/java/me/shedaniel/rei/gui/credits/CreditsScreen.java

@@ -2,7 +2,7 @@ package me.shedaniel.rei.gui.credits;
 
 import me.shedaniel.rei.client.GuiHelper;
 import net.minecraft.client.gui.ContainerScreen;
-import net.minecraft.client.gui.GuiEventListener;
+import net.minecraft.client.gui.InputListener;
 import net.minecraft.client.gui.Screen;
 import net.minecraft.client.gui.widget.ButtonWidget;
 import net.minecraft.client.resource.language.I18n;
@@ -44,12 +44,12 @@ public class CreditsScreen extends Screen {
     }
     
     @Override
-    public void method_18326(int int_1, int int_2, float float_1) {
+    public void draw(int int_1, int int_2, float float_1) {
         //draw
         this.drawTextureBackground(0);
-        this.entryListWidget.method_18326(int_1, int_2, float_1);
+        this.entryListWidget.draw(int_1, int_2, float_1);
         this.drawStringCentered(this.fontRenderer, I18n.translate("text.rei.credits"), this.width / 2, 16, 16777215);
-        super.method_18326(int_1, int_2, float_1);
+        super.draw(int_1, int_2, float_1);
     }
     
     @Override
@@ -58,7 +58,7 @@ public class CreditsScreen extends Screen {
     }
     
     @Override
-    public GuiEventListener getFocused() {
+    public InputListener getFocused() {
         return entryListWidget;
     }
     

+ 2 - 2
src/main/java/me/shedaniel/rei/gui/widget/ButtonWidget.java

@@ -4,7 +4,7 @@ import com.mojang.blaze3d.platform.GlStateManager;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.audio.PositionedSoundInstance;
 import net.minecraft.client.font.TextRenderer;
-import net.minecraft.client.gui.Drawable;
+import net.minecraft.client.gui.DrawableHelper;
 import net.minecraft.sound.SoundEvents;
 import net.minecraft.text.TextComponent;
 import net.minecraft.util.Identifier;
@@ -14,7 +14,7 @@ import java.awt.*;
 import java.util.ArrayList;
 import java.util.List;
 
-public abstract class ButtonWidget extends Drawable implements IWidget {
+public abstract class ButtonWidget extends DrawableHelper implements HighlightableWidget {
     
     protected static final Identifier WIDGET_TEX = new Identifier("textures/gui/widgets.png");
     public String text;

+ 36 - 0
src/main/java/me/shedaniel/rei/gui/widget/ClickableLabelWidget.java

@@ -0,0 +1,36 @@
+package me.shedaniel.rei.gui.widget;
+
+import java.awt.*;
+
+public abstract class ClickableLabelWidget extends LabelWidget implements HighlightableWidget {
+    
+    public ClickableLabelWidget(int x, int y, String text) {
+        super(x, y, text);
+    }
+    
+    @Override
+    public Rectangle getBounds() {
+        int width = textRenderer.getStringWidth(text);
+        return new Rectangle(x - width / 2 - 1, y - 5, width + 2, 14);
+    }
+    
+    @Override
+    public void draw(int mouseX, int mouseY, float partialTicks) {
+        int colour = -1;
+        if (isHighlighted(mouseX, mouseY))
+            colour = 16777120;
+        drawStringCentered(textRenderer, (isHighlighted(mouseX, mouseY) ? "§n" : "") + text, x, y, colour);
+    }
+    
+    @Override
+    public boolean onMouseClick(int button, double mouseX, double mouseY) {
+        if (button == 0 && isHighlighted(mouseX, mouseY)) {
+            onLabelClicked();
+            return true;
+        }
+        return false;
+    }
+    
+    public abstract void onLabelClicked();
+    
+}

+ 71 - 0
src/main/java/me/shedaniel/rei/gui/widget/DraggableWidget.java

@@ -0,0 +1,71 @@
+package me.shedaniel.rei.gui.widget;
+
+import me.shedaniel.rei.client.ClientHelper;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.util.Window;
+
+import java.awt.*;
+
+public abstract class DraggableWidget implements HighlightableWidget {
+    
+    protected boolean dragged = false;
+    private Point midPoint, startPoint;
+    private int relateX, relateY;
+    
+    public DraggableWidget() {
+        Window window = MinecraftClient.getInstance().window;
+        initWidgets(midPoint = new Point(window.getScaledWidth() / 2, window.getScaledHeight() / 2));
+    }
+    
+    protected abstract void initWidgets(Point midPoint);
+    
+    public abstract void updateWidgets(Point midPoint);
+    
+    public abstract Rectangle getGrabBounds();
+    
+    public abstract Rectangle getDragBounds();
+    
+    public final Point getMidPoint() {
+        return midPoint;
+    }
+    
+    @Override
+    public boolean mouseDragged(double double_1, double double_2, int int_1, double double_3, double double_4) {
+        Point mouse = ClientHelper.getMouseLocation();
+        if (int_1 == 0) {
+            if (!dragged) {
+                if (getGrabBounds().contains(mouse)) {
+                    startPoint = new Point(midPoint.x, midPoint.y);
+                    relateX = mouse.x - midPoint.x;
+                    relateY = mouse.y - midPoint.y;
+                    dragged = true;
+                }
+            } else {
+                Window window = MinecraftClient.getInstance().window;
+                midPoint = processMidPoint(midPoint, mouse, startPoint, window, relateX, relateY);
+                updateWidgets(midPoint);
+            }
+            return true;
+        }
+        for(IWidget widget : getListeners())
+            if (widget.mouseDragged(double_1, double_2, int_1, double_3, double_4))
+                return true;
+        return false;
+    }
+    
+    public abstract Point processMidPoint(Point midPoint, Point mouse, Point startPoint, Window window, int relateX, int relateY);
+    
+    @Override
+    public boolean mouseReleased(double double_1, double double_2, int int_1) {
+        if (int_1 == 0)
+            if (dragged) {
+                dragged = false;
+                return true;
+            }
+        for(IWidget widget : getListeners())
+            if (widget.mouseReleased(double_1, double_2, int_1))
+                return true;
+        return false;
+    }
+    
+}

+ 3 - 4
src/main/java/me/shedaniel/rei/gui/widget/IWidget.java

@@ -1,15 +1,14 @@
 package me.shedaniel.rei.gui.widget;
 
-import net.minecraft.client.gui.GuiEventListener;
+import net.minecraft.client.gui.Drawable;
+import net.minecraft.client.gui.InputListener;
 
 import java.util.List;
 
-public interface IWidget extends GuiEventListener {
+public interface IWidget extends InputListener, Drawable {
     
     public List<IWidget> getListeners();
     
-    public void draw(int mouseX, int mouseY, float partialTicks);
-    
     @Override
     default boolean mouseClicked(double double_1, double double_2, int int_1) {
         if (onMouseClick(int_1, double_1, double_2))

+ 10 - 6
src/main/java/me/shedaniel/rei/gui/widget/ItemListOverlay.java

@@ -2,9 +2,13 @@ package me.shedaniel.rei.gui.widget;
 
 import com.google.common.collect.Lists;
 import me.shedaniel.rei.RoughlyEnoughItemsCore;
-import me.shedaniel.rei.client.*;
+import me.shedaniel.rei.api.IRecipeHelper;
+import me.shedaniel.rei.client.ClientHelper;
+import me.shedaniel.rei.client.GuiHelper;
+import me.shedaniel.rei.client.REIItemListOrdering;
+import me.shedaniel.rei.client.SearchArgument;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.Drawable;
+import net.minecraft.client.gui.DrawableHelper;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.item.ItemGroup;
@@ -18,7 +22,7 @@ import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-public class ItemListOverlay extends Drawable implements IWidget {
+public class ItemListOverlay extends DrawableHelper implements IWidget {
     
     private List<IWidget> widgets;
     private int width, height, page;
@@ -99,7 +103,7 @@ public class ItemListOverlay extends Drawable implements IWidget {
         List<ItemStack> os = new LinkedList<>(ol), stacks = Lists.newArrayList(), finalStacks = Lists.newArrayList();
         List<ItemGroup> itemGroups = new LinkedList<>(Arrays.asList(ItemGroup.GROUPS));
         itemGroups.add(null);
-        REIItemListOrdering ordering = RoughlyEnoughItemsCore.getConfigHelper().getItemListOrdering();
+        REIItemListOrdering ordering = RoughlyEnoughItemsCore.getConfigHelper().getConfig().itemListOrdering;
         if (ordering != REIItemListOrdering.REGISTRY)
             Collections.sort(os, (itemStack, t1) -> {
                 if (ordering.equals(REIItemListOrdering.NAME))
@@ -108,7 +112,7 @@ public class ItemListOverlay extends Drawable implements IWidget {
                     return itemGroups.indexOf(itemStack.getItem().getItemGroup()) - itemGroups.indexOf(t1.getItem().getItemGroup());
                 return 0;
             });
-        if (!RoughlyEnoughItemsCore.getConfigHelper().isAscending())
+        if (!RoughlyEnoughItemsCore.getConfigHelper().getConfig().isAscending)
             Collections.reverse(os);
         String[] splitSearchTerm = StringUtils.splitByWholeSeparatorPreserveAllTokens(searchTerm, "|");
         Arrays.stream(splitSearchTerm).forEachOrdered(s -> {
@@ -133,7 +137,7 @@ public class ItemListOverlay extends Drawable implements IWidget {
             stacks.addAll(os);
         List<ItemStack> workingItems = RoughlyEnoughItemsCore.getConfigHelper().craftableOnly() && inventoryItems.size() > 0 ? new ArrayList<>() : new LinkedList<>(ol);
         if (RoughlyEnoughItemsCore.getConfigHelper().craftableOnly()) {
-            RecipeHelper.getInstance().findCraftableByItems(inventoryItems).forEach(workingItems::add);
+            IRecipeHelper.getInstance().findCraftableByItems(inventoryItems).forEach(workingItems::add);
             workingItems.addAll(inventoryItems);
         }
         final List<ItemStack> finalWorkingItems = workingItems;

+ 6 - 6
src/main/java/me/shedaniel/rei/gui/widget/ItemSlotWidget.java

@@ -5,7 +5,7 @@ import com.mojang.blaze3d.platform.GlStateManager;
 import me.shedaniel.rei.client.ClientHelper;
 import me.shedaniel.rei.client.GuiHelper;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.Drawable;
+import net.minecraft.client.gui.DrawableHelper;
 import net.minecraft.client.render.GuiLighting;
 import net.minecraft.client.render.item.ItemRenderer;
 import net.minecraft.item.ItemStack;
@@ -20,7 +20,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.stream.Collectors;
 
-public class ItemSlotWidget extends Drawable implements HighlightableWidget {
+public class ItemSlotWidget extends DrawableHelper implements HighlightableWidget {
     
     private static final Identifier RECIPE_GUI = new Identifier("roughlyenoughitems", "textures/gui/recipecontainer.png");
     private List<ItemStack> itemList = new LinkedList<>();
@@ -41,15 +41,15 @@ public class ItemSlotWidget extends Drawable implements HighlightableWidget {
         this.drawHighlightedBackground = true;
     }
     
-    public void setDrawHighlightedBackground(boolean drawHighlightedBackground) {
-        this.drawHighlightedBackground = drawHighlightedBackground;
-    }
-    
     public ItemSlotWidget(int x, int y, List<ItemStack> itemList, boolean drawBackground, boolean showToolTips, boolean clickToMoreRecipes) {
         this(x, y, itemList, drawBackground, showToolTips);
         this.clickToMoreRecipes = clickToMoreRecipes;
     }
     
+    public void setDrawHighlightedBackground(boolean drawHighlightedBackground) {
+        this.drawHighlightedBackground = drawHighlightedBackground;
+    }
+    
     public boolean isDrawBackground() {
         return drawBackground;
     }

+ 6 - 3
src/main/java/me/shedaniel/rei/gui/widget/LabelWidget.java

@@ -1,21 +1,24 @@
 package me.shedaniel.rei.gui.widget;
 
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.Drawable;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawableHelper;
 
 import java.util.ArrayList;
 import java.util.List;
 
-public class LabelWidget extends Drawable implements IWidget {
+public class LabelWidget extends DrawableHelper implements IWidget {
     
     public int x;
     public int y;
     public String text;
+    protected TextRenderer textRenderer;
     
     public LabelWidget(int x, int y, String text) {
         this.x = x;
         this.y = y;
         this.text = text;
+        this.textRenderer = MinecraftClient.getInstance().textRenderer;
     }
     
     @Override
@@ -25,7 +28,7 @@ public class LabelWidget extends Drawable implements IWidget {
     
     @Override
     public void draw(int mouseX, int mouseY, float partialTicks) {
-        drawStringCentered(MinecraftClient.getInstance().textRenderer, text, x, y, -1);
+        drawStringCentered(textRenderer, text, x, y, -1);
     }
     
 }

+ 18 - 3
src/main/java/me/shedaniel/rei/gui/widget/RecipeBaseWidget.java

@@ -2,7 +2,7 @@ package me.shedaniel.rei.gui.widget;
 
 import com.mojang.blaze3d.platform.GlStateManager;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.Drawable;
+import net.minecraft.client.gui.DrawableHelper;
 import net.minecraft.client.render.GuiLighting;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
@@ -11,9 +11,10 @@ import java.awt.*;
 import java.util.ArrayList;
 import java.util.List;
 
-public class RecipeBaseWidget extends Drawable implements IWidget {
+public class RecipeBaseWidget extends DrawableHelper implements HighlightableWidget {
     
     private static final Identifier CHEST_GUI_TEXTURE = new Identifier("roughlyenoughitems", "textures/gui/recipecontainer.png");
+    private static final Color INNER_COLOR = new Color(198, 198, 198);
     
     private Rectangle bounds;
     
@@ -21,13 +22,18 @@ public class RecipeBaseWidget extends Drawable implements IWidget {
         this.bounds = bounds;
     }
     
+    @Override
+    public Rectangle getBounds() {
+        return bounds;
+    }
+    
     @Override
     public List<IWidget> getListeners() {
         return new ArrayList<>();
     }
     
     @Override
-    public void draw(int mouseX, int mouseY, float partialTicks) {
+    public void draw(int mouseX, int mouseY, float delta) {
         GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
         GuiLighting.disable();
         MinecraftClient.getInstance().getTextureManager().bindTexture(CHEST_GUI_TEXTURE);
@@ -41,6 +47,15 @@ public class RecipeBaseWidget extends Drawable implements IWidget {
                 drawTexturedRect(bounds.x, bounds.y + i, 106, 230, bounds.width / 2, height);
                 drawTexturedRect(bounds.x + bounds.width / 2, bounds.y + i, 256 - bounds.width / 2, 210, bounds.width / 2, height);
             }
+        if (bounds.width > 40)
+            for(int i = 20; i < bounds.width - 20; i += MathHelper.clamp(40, 0, bounds.width - 20 - i)) {
+                int width = MathHelper.clamp(40, 0, bounds.width - 20 - i);
+                GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
+                GuiLighting.disable();
+                drawTexturedRect(bounds.x + i, bounds.y, 113, 190, width, MathHelper.clamp(4, 0, bounds.height / 2));
+                drawTexturedRect(bounds.x + i, bounds.y + bounds.height - 4, 113, 252, width, MathHelper.clamp(4, 0, bounds.height / 2));
+                DrawableHelper.drawRect(bounds.x + i, bounds.y + 4, bounds.x + i + width, bounds.y + bounds.height - 4, INNER_COLOR.getRGB());
+            }
     }
     
 }

+ 165 - 0
src/main/java/me/shedaniel/rei/gui/widget/RecipeChoosePageWidget.java

@@ -0,0 +1,165 @@
+package me.shedaniel.rei.gui.widget;
+
+import com.google.common.collect.Lists;
+import com.mojang.blaze3d.platform.GlStateManager;
+import me.shedaniel.rei.gui.RecipeViewingScreen;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.resource.language.I18n;
+import net.minecraft.client.util.Window;
+import net.minecraft.util.math.MathHelper;
+
+import java.awt.*;
+import java.util.List;
+import java.util.Optional;
+
+public class RecipeChoosePageWidget extends DraggableWidget {
+    
+    private int currentPage;
+    private int maxPage;
+    private Rectangle bounds, grabBounds, dragBounds;
+    private List<IWidget> widgets;
+    private RecipeViewingScreen recipeViewingScreen;
+    private TextFieldWidget textFieldWidget;
+    private RecipeBaseWidget base1, base2;
+    private ButtonWidget btnDone;
+    
+    public RecipeChoosePageWidget(RecipeViewingScreen recipeViewingScreen, int currentPage, int maxPage) {
+        this.recipeViewingScreen = recipeViewingScreen;
+        this.currentPage = currentPage;
+        this.maxPage = maxPage;
+        initWidgets(getMidPoint());
+    }
+    
+    @Override
+    public Rectangle getBounds() {
+        return bounds;
+    }
+    
+    @Override
+    public Rectangle getGrabBounds() {
+        return grabBounds;
+    }
+    
+    @Override
+    public Rectangle getDragBounds() {
+        return dragBounds;
+    }
+    
+    @Override
+    public boolean isHighlighted(int mouseX, int mouseY) {
+        return getBounds().contains(mouseX, mouseY) || new Rectangle(bounds.x + bounds.width - 50, bounds.y + bounds.height - 3, 50, 36).contains(mouseX, mouseY);
+    }
+    
+    @Override
+    public void updateWidgets(Point midPoint) {
+        this.bounds = new Rectangle(midPoint.x - 50, midPoint.y - 20, 100, 40);
+        this.grabBounds = new Rectangle(midPoint.x - 50, midPoint.y - 20, 100, 16);
+        this.dragBounds = new Rectangle(midPoint.x - 50, midPoint.y - 20, 100, 70);
+        base1.getBounds().setLocation(bounds.x + bounds.width - 50, bounds.y + bounds.height - 6);
+        base2.getBounds().setBounds(bounds);
+        textFieldWidget.getBounds().setLocation(bounds.x + 7, bounds.y + 16);
+        btnDone.getBounds().setLocation(bounds.x + bounds.width - 45, bounds.y + bounds.height + 3);
+    }
+    
+    @Override
+    protected void initWidgets(Point midPoint) {
+        this.bounds = new Rectangle(midPoint.x - 50, midPoint.y - 20, 100, 40);
+        this.grabBounds = new Rectangle(midPoint.x - 50, midPoint.y - 20, 100, 16);
+        this.dragBounds = new Rectangle(midPoint.x - 50, midPoint.y - 20, 100, 70);
+        this.widgets = Lists.newArrayList();
+        this.widgets.add(base1 = new RecipeBaseWidget(new Rectangle(bounds.x + bounds.width - 50, bounds.y + bounds.height - 6, 50, 36)));
+        this.widgets.add(base2 = new RecipeBaseWidget(bounds));
+        this.widgets.add(new IWidget() {
+            @Override
+            public List<IWidget> getListeners() {
+                return Lists.newArrayList();
+            }
+            
+            @Override
+            public void draw(int i, int i1, float v) {
+                MinecraftClient.getInstance().textRenderer.draw(I18n.translate("text.rei.choose_page"), bounds.x + 5, bounds.y + 5, 4210752);
+                String endString = String.format(" /%d", maxPage);
+                int width = MinecraftClient.getInstance().textRenderer.getStringWidth(endString);
+                MinecraftClient.getInstance().textRenderer.draw(endString, bounds.x + bounds.width - 5 - width, bounds.y + 22, 4210752);
+            }
+        });
+        String endString = String.format(" /%d", maxPage);
+        int width = MinecraftClient.getInstance().textRenderer.getStringWidth(endString);
+        this.widgets.add(textFieldWidget = new TextFieldWidget(bounds.x + 7, bounds.y + 16, bounds.width - width - 12, 18));
+        textFieldWidget.stripInvaild = s -> {
+            StringBuilder stringBuilder_1 = new StringBuilder();
+            char[] var2 = s.toCharArray();
+            int var3 = var2.length;
+            
+            for(int var4 = 0; var4 < var3; ++var4) {
+                char char_1 = var2[var4];
+                if (Character.isDigit(char_1))
+                    stringBuilder_1.append(char_1);
+            }
+            
+            return stringBuilder_1.toString();
+        };
+        textFieldWidget.setText(String.valueOf(currentPage + 1));
+        widgets.add(btnDone = new ButtonWidget(bounds.x + bounds.width - 45, bounds.y + bounds.height + 3, 40, 20, I18n.translate("gui.done")) {
+            @Override
+            public void onPressed(int button, double mouseX, double mouseY) {
+                recipeViewingScreen.page = MathHelper.clamp(getIntFromString(textFieldWidget.getText()).orElse(0) - 1, 0, recipeViewingScreen.getTotalPages(recipeViewingScreen.getSelectedCategory()) - 1);
+                //recipeViewingScreen.choosePageActivated = false;
+                recipeViewingScreen.onInitialized();
+            }
+        });
+        textFieldWidget.setFocused(true);
+    }
+    
+    @Override
+    public Point processMidPoint(Point midPoint, Point mouse, Point startPoint, Window window, int relateX, int relateY) {
+        return new Point(MathHelper.clamp(mouse.x - relateX, getDragBounds().width / 2, window.getScaledWidth() - getDragBounds().width / 2), MathHelper.clamp(mouse.y - relateY, 20, window.getScaledHeight() - 50));
+    }
+    
+    @Override
+    public List<IWidget> getListeners() {
+        return widgets;
+    }
+    
+    @Override
+    public void draw(int i, int i1, float v) {
+        widgets.forEach(widget -> {
+            GuiLighting.disable();
+            GlStateManager.translatef(0, 0, 600);
+            widget.draw(i, i1, v);
+            GlStateManager.translatef(0, 0, -600);
+        });
+    }
+    
+    @Override
+    public boolean charTyped(char char_1, int int_1) {
+        for(IWidget widget : widgets)
+            if (widget.charTyped(char_1, int_1))
+                return true;
+        return false;
+    }
+    
+    @Override
+    public boolean keyPressed(int int_1, int int_2, int int_3) {
+        if (int_1 == 335 || int_1 == 257) {
+            recipeViewingScreen.page = MathHelper.clamp(getIntFromString(textFieldWidget.getText()).orElse(0) - 1, 0, recipeViewingScreen.getTotalPages(recipeViewingScreen.getSelectedCategory()) - 1);
+            recipeViewingScreen.choosePageActivated = false;
+            recipeViewingScreen.onInitialized();
+            return true;
+        }
+        for(IWidget widget : widgets)
+            if (widget.keyPressed(int_1, int_2, int_3))
+                return true;
+        return false;
+    }
+    
+    public Optional<Integer> getIntFromString(String s) {
+        try {
+            return Optional.of(Integer.valueOf(s));
+        } catch (Exception e) {
+        }
+        return Optional.empty();
+    }
+    
+}

+ 113 - 0
src/main/java/me/shedaniel/rei/gui/widget/RecipePageLabelWidget.java

@@ -0,0 +1,113 @@
+package me.shedaniel.rei.gui.widget;
+
+import net.minecraft.client.gui.DrawableHelper;
+
+import java.awt.*;
+
+public class RecipePageLabelWidget extends TextFieldWidget {
+    
+    private Point middlePoint;
+    private int maximumPage;
+    
+    public RecipePageLabelWidget(int x, int y, int currentPage, int maximumPage) {
+        super(new Rectangle(0, 0, 40, 20));
+        this.middlePoint = new Point(x, y);
+        this.maximumPage = maximumPage;
+        this.setHasBorder(false);
+        setChangedListener(s -> updateSize());
+        this.setText(String.valueOf(currentPage + 1));
+        stripInvaild = s -> {
+            StringBuilder stringBuilder_1 = new StringBuilder();
+            char[] var2 = s.toCharArray();
+            int var3 = var2.length;
+            
+            for(int var4 = 0; var4 < var3; ++var4) {
+                char char_1 = var2[var4];
+                if (Character.isDigit(char_1))
+                    stringBuilder_1.append(char_1);
+            }
+            
+            return stringBuilder_1.toString();
+        };
+        setMaxLength(String.valueOf(maximumPage).length());
+    }
+    
+    public void updateSize() {
+        this.getBounds().setLocation(middlePoint.x - 40, middlePoint.y);
+    }
+    
+    @Override
+    public void draw(int int_1, int int_2, float float_1) {
+        setEditableColor(isInvaildPage(getText()) ? -1 : Color.RED.getRGB());
+        if (this.isVisible()) {
+            if (this.hasBorder()) {
+                drawRect(this.getBounds().x - 1, this.getBounds().y - 1, this.getBounds().x + this.getBounds().width + 1, this.getBounds().y + this.getBounds().height + 1, -6250336);
+                drawRect(this.getBounds().x, this.getBounds().y, this.getBounds().x + this.getBounds().width, this.getBounds().y + this.getBounds().height, -16777216);
+            }
+            
+            int color = this.editable ? this.editableColor : this.notEditableColor;
+            int int_4 = this.cursorMax - this.field_2103;
+            int int_5 = this.cursorMin - this.field_2103;
+            String string_1 = this.getText(); //this.textRenderer.trimToWidth(this.getText().substring(this.field_2103), this.getWidth());
+            boolean boolean_1 = int_4 >= 0 && int_4 <= string_1.length();
+            boolean boolean_2 = this.isFocused() && this.focusedTicks / 6 % 2 == 0 && boolean_1;
+            int int_6 = this.hasBorder() ? this.getBounds().x + 4 : this.getBounds().x;
+            int int_7 = this.hasBorder() ? this.getBounds().y + (this.getBounds().height - 8) / 2 : this.getBounds().y;
+            int int_8 = int_6;
+            if (int_5 > string_1.length()) {
+                int_5 = string_1.length();
+            }
+            
+            if (!string_1.isEmpty()) {
+                String string_2 = boolean_1 ? string_1.substring(0, int_4) : string_1;
+                int_8 = this.textRenderer.drawWithShadow(this.renderTextProvider.apply(string_2, this.field_2103), (float) getBounds().x + getBounds().width - textRenderer.getStringWidth(string_2.substring(1)), (float) int_7, color);
+            }
+            
+            boolean boolean_3 = this.cursorMax < this.getText().length() || this.getText().length() >= this.getMaxLength();
+            int int_9 = int_8;
+            if (!boolean_1) {
+                int_9 = int_4 > 0 ? int_6 + this.getBounds().width : int_6;
+            } else if (boolean_3) {
+                int_9 = int_8 - 1;
+                --int_8;
+            }
+            
+            if (!string_1.isEmpty() && boolean_1 && int_4 < string_1.length()) {
+                this.textRenderer.drawWithShadow((String) this.renderTextProvider.apply(string_1.substring(int_4), this.cursorMax), (float) int_8, (float) int_7, color);
+            }
+            
+            if (!boolean_3 && getText().isEmpty() && this.getSuggestion() != null) {
+                this.textRenderer.drawWithShadow(this.textRenderer.trimToWidth(this.getSuggestion(), this.getWidth()), (float) int_6, (float) int_7, -8355712);
+            }
+            
+            if (boolean_2)
+                if (boolean_3) {
+                    int var10001 = int_7 - 1;
+                    int var10002 = int_9 + 1;
+                    int var10003 = int_7 + 1;
+                    this.textRenderer.getClass();
+                    DrawableHelper.drawRect(int_9, var10001, var10002, var10003 + 9, -3092272);
+                }
+            
+            if (int_5 != int_4) {
+                int int_10 = int_6 + this.textRenderer.getStringWidth(string_1.substring(0, int_5));
+                int var10002 = int_7 - 1;
+                int var10003 = int_10 - 1;
+                int var10004 = int_7 + 1;
+                this.textRenderer.getClass();
+                this.method_1886(int_9, var10002, var10003, var10004 + 9);
+            }
+        }
+        textRenderer.drawWithShadow(" /" + maximumPage, getBounds().x + getBounds().width + 2, getBounds().y, -1);
+    }
+    
+    private boolean isInvaildPage(String text) {
+        try {
+            int page = Integer.valueOf(text);
+            return page >= 1 && page <= maximumPage;
+        } catch (Exception e) {
+        }
+        return false;
+    }
+    
+}

+ 5 - 4
src/main/java/me/shedaniel/rei/gui/widget/TabWidget.java

@@ -4,8 +4,9 @@ import com.google.common.collect.Lists;
 import com.mojang.blaze3d.platform.GlStateManager;
 import me.shedaniel.rei.client.ClientHelper;
 import me.shedaniel.rei.client.GuiHelper;
+import me.shedaniel.rei.gui.RecipeViewingScreen;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.Drawable;
+import net.minecraft.client.gui.DrawableHelper;
 import net.minecraft.client.render.GuiLighting;
 import net.minecraft.client.render.item.ItemRenderer;
 import net.minecraft.item.ItemStack;
@@ -15,19 +16,19 @@ import java.awt.*;
 import java.util.Arrays;
 import java.util.List;
 
-public class TabWidget extends Drawable implements HighlightableWidget {
+public class TabWidget extends DrawableHelper implements HighlightableWidget {
     
     private static final Identifier CHEST_GUI_TEXTURE = new Identifier("roughlyenoughitems", "textures/gui/recipecontainer.png");
     
     private boolean shown = false, selected = false;
     private ItemStack item;
     private int id;
-    private RecipeViewingWidgetScreen recipeViewingWidget;
+    private RecipeViewingScreen recipeViewingWidget;
     private String categoryName;
     private Rectangle bounds;
     private ItemRenderer itemRenderer;
     
-    public TabWidget(int id, RecipeViewingWidgetScreen recipeViewingWidget, Rectangle bounds) {
+    public TabWidget(int id, RecipeViewingScreen recipeViewingWidget, Rectangle bounds) {
         this.id = id;
         this.recipeViewingWidget = recipeViewingWidget;
         this.bounds = bounds;

+ 59 - 68
src/main/java/me/shedaniel/rei/gui/widget/TextFieldWidget.java

@@ -5,7 +5,7 @@ import com.mojang.blaze3d.platform.GlStateManager;
 import net.minecraft.SharedConstants;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
-import net.minecraft.client.gui.Drawable;
+import net.minecraft.client.gui.DrawableHelper;
 import net.minecraft.client.gui.Screen;
 import net.minecraft.client.render.BufferBuilder;
 import net.minecraft.client.render.Tessellator;
@@ -17,67 +17,62 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
-public class TextFieldWidget extends Drawable implements IWidget {
+public class TextFieldWidget extends DrawableHelper implements HighlightableWidget {
     
-    private final TextRenderer textRenderer;
-    private int width;
-    private int height;
-    private int x;
-    private int y;
+    protected final TextRenderer textRenderer;
+    public Function<String, String> stripInvaild;
+    private Rectangle bounds;
     private String text;
     private int maxLength;
-    private int focusedTicks;
+    protected int focusedTicks;
     private boolean hasBorder;
     private boolean field_2096;
     private boolean focused;
-    private boolean editable;
+    protected boolean editable;
     private boolean field_17037;
-    private int field_2103;
-    private int cursorMax;
-    private int cursorMin;
-    private int field_2100;
-    private int field_2098;
+    protected int field_2103;
+    protected int cursorMax;
+    protected int cursorMin;
+    protected int editableColor;
+    protected int notEditableColor;
     private boolean visible;
     private String suggestion;
     private Consumer<String> changedListener;
     private Predicate<String> textPredicate;
-    private BiFunction<String, Integer, String> field_2099;
+    protected BiFunction<String, Integer, String> renderTextProvider;
     
     public TextFieldWidget(Rectangle rectangle) {
-        this(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
-    }
-    
-    public TextFieldWidget(int x, int y, int width, int height) {
         this.text = "";
         this.maxLength = 32;
         this.hasBorder = true;
         this.field_2096 = true;
         this.editable = true;
-        this.field_2100 = 14737632;
-        this.field_2098 = 7368816;
+        this.editableColor = 14737632;
+        this.notEditableColor = 7368816;
         this.visible = true;
         this.textPredicate = Predicates.alwaysTrue();
-        this.field_2099 = (string_1, integer_1) -> {
+        this.renderTextProvider = (string_1, integer_1) -> {
             return string_1;
         };
         this.textRenderer = MinecraftClient.getInstance().textRenderer;
-        this.x = x;
-        this.y = y;
-        this.width = width;
-        this.height = height;
+        this.bounds = rectangle;
+        this.stripInvaild = s -> SharedConstants.stripInvalidChars(s);
     }
     
-    public Rectangle getBounds() {
-        return new Rectangle(x, y, width, height);
+    public TextFieldWidget(int x, int y, int width, int height) {
+        this(new Rectangle(x, y, width, height));
     }
     
-    public void setBounds(Rectangle rectangle) {
-        this.x = rectangle.x;
-        this.y = rectangle.y;
-        this.width = rectangle.width;
-        this.height = rectangle.height;
+    public String getSuggestion() {
+        return suggestion;
+    }
+    
+    @Override
+    public Rectangle getBounds() {
+        return bounds;
     }
     
     public void setChangedListener(Consumer<String> biConsumer_1) {
@@ -85,7 +80,7 @@ public class TextFieldWidget extends Drawable implements IWidget {
     }
     
     public void method_1854(BiFunction<String, Integer, String> biFunction_1) {
-        this.field_2099 = biFunction_1;
+        this.renderTextProvider = biFunction_1;
     }
     
     public void tick() {
@@ -121,7 +116,7 @@ public class TextFieldWidget extends Drawable implements IWidget {
     
     public void addText(String string_1) {
         String string_2 = "";
-        String string_3 = SharedConstants.stripInvalidChars(string_1);
+        String string_3 = stripInvaild.apply(string_1);
         int int_1 = this.cursorMax < this.cursorMin ? this.cursorMax : this.cursorMin;
         int int_2 = this.cursorMax < this.cursorMin ? this.cursorMin : this.cursorMax;
         int int_3 = this.maxLength - this.text.length() - (int_1 - int_2);
@@ -362,18 +357,18 @@ public class TextFieldWidget extends Drawable implements IWidget {
         if (!this.isVisible()) {
             return false;
         } else {
-            boolean boolean_1 = double_1 >= (double) this.x && double_1 < (double) (this.x + this.width) && double_2 >= (double) this.y && double_2 < (double) (this.y + this.height);
+            boolean boolean_1 = double_1 >= (double) this.bounds.x && double_1 < (double) (this.bounds.x + this.bounds.width) && double_2 >= (double) this.bounds.y && double_2 < (double) (this.bounds.y + this.bounds.height);
             if (this.field_2096) {
                 this.setFocused(boolean_1);
             }
             
             if (this.focused && boolean_1 && int_1 == 0) {
-                int int_2 = MathHelper.floor(double_1) - this.x;
+                int int_2 = MathHelper.floor(double_1) - this.bounds.x;
                 if (this.hasBorder) {
                     int_2 -= 4;
                 }
                 
-                String string_1 = this.textRenderer.trimToWidth(this.text.substring(this.field_2103), this.method_1859());
+                String string_1 = this.textRenderer.trimToWidth(this.text.substring(this.field_2103), this.getWidth());
                 this.method_1883(this.textRenderer.trimToWidth(string_1, int_2).length() + this.field_2103);
                 return true;
             } else {
@@ -385,18 +380,18 @@ public class TextFieldWidget extends Drawable implements IWidget {
     public void draw(int int_1, int int_2, float float_1) {
         if (this.isVisible()) {
             if (this.hasBorder()) {
-                drawRect(this.x - 1, this.y - 1, this.x + this.width + 1, this.y + this.height + 1, -6250336);
-                drawRect(this.x, this.y, this.x + this.width, this.y + this.height, -16777216);
+                drawRect(this.bounds.x - 1, this.bounds.y - 1, this.bounds.x + this.bounds.width + 1, this.bounds.y + this.bounds.height + 1, -6250336);
+                drawRect(this.bounds.x, this.bounds.y, this.bounds.x + this.bounds.width, this.bounds.y + this.bounds.height, -16777216);
             }
             
-            int int_3 = this.editable ? this.field_2100 : this.field_2098;
+            int color = this.editable ? this.editableColor : this.notEditableColor;
             int int_4 = this.cursorMax - this.field_2103;
             int int_5 = this.cursorMin - this.field_2103;
-            String string_1 = this.textRenderer.trimToWidth(this.text.substring(this.field_2103), this.method_1859());
+            String string_1 = this.textRenderer.trimToWidth(this.text.substring(this.field_2103), this.getWidth());
             boolean boolean_1 = int_4 >= 0 && int_4 <= string_1.length();
             boolean boolean_2 = this.focused && this.focusedTicks / 6 % 2 == 0 && boolean_1;
-            int int_6 = this.hasBorder ? this.x + 4 : this.x;
-            int int_7 = this.hasBorder ? this.y + (this.height - 8) / 2 : this.y;
+            int int_6 = this.hasBorder ? this.bounds.x + 4 : this.bounds.x;
+            int int_7 = this.hasBorder ? this.bounds.y + (this.bounds.height - 8) / 2 : this.bounds.y;
             int int_8 = int_6;
             if (int_5 > string_1.length()) {
                 int_5 = string_1.length();
@@ -404,24 +399,24 @@ public class TextFieldWidget extends Drawable implements IWidget {
             
             if (!string_1.isEmpty()) {
                 String string_2 = boolean_1 ? string_1.substring(0, int_4) : string_1;
-                int_8 = this.textRenderer.drawWithShadow((String) this.field_2099.apply(string_2, this.field_2103), (float) int_6, (float) int_7, int_3);
+                int_8 = this.textRenderer.drawWithShadow((String) this.renderTextProvider.apply(string_2, this.field_2103), (float) int_6, (float) int_7, color);
             }
             
             boolean boolean_3 = this.cursorMax < this.text.length() || this.text.length() >= this.getMaxLength();
             int int_9 = int_8;
             if (!boolean_1) {
-                int_9 = int_4 > 0 ? int_6 + this.width : int_6;
+                int_9 = int_4 > 0 ? int_6 + this.bounds.width : int_6;
             } else if (boolean_3) {
                 int_9 = int_8 - 1;
                 --int_8;
             }
             
             if (!string_1.isEmpty() && boolean_1 && int_4 < string_1.length()) {
-                this.textRenderer.drawWithShadow((String) this.field_2099.apply(string_1.substring(int_4), this.cursorMax), (float) int_8, (float) int_7, int_3);
+                this.textRenderer.drawWithShadow((String) this.renderTextProvider.apply(string_1.substring(int_4), this.cursorMax), (float) int_8, (float) int_7, color);
             }
             
-            if (!boolean_3 && this.suggestion != null) {
-                this.textRenderer.drawWithShadow(this.suggestion, (float) (int_9 - 1), (float) int_7, -8355712);
+            if (!boolean_3 && text.isEmpty() && this.suggestion != null) {
+                this.textRenderer.drawWithShadow(this.textRenderer.trimToWidth(this.suggestion, this.getWidth()), (float) int_6, (float) int_7, -8355712);
             }
             
             int var10002;
@@ -432,9 +427,9 @@ public class TextFieldWidget extends Drawable implements IWidget {
                     var10002 = int_9 + 1;
                     var10003 = int_7 + 1;
                     this.textRenderer.getClass();
-                    Drawable.drawRect(int_9, var10001, var10002, var10003 + 9, -3092272);
+                    DrawableHelper.drawRect(int_9, var10001, var10002, var10003 + 9, -3092272);
                 } else {
-                    this.textRenderer.drawWithShadow("_", (float) int_9, (float) int_7, int_3);
+                    this.textRenderer.drawWithShadow("_", (float) int_9, (float) int_7, color);
                 }
             }
             
@@ -450,7 +445,7 @@ public class TextFieldWidget extends Drawable implements IWidget {
         }
     }
     
-    private void method_1886(int int_1, int int_2, int int_3, int int_4) {
+    protected void method_1886(int int_1, int int_2, int int_3, int int_4) {
         int int_6;
         if (int_1 < int_3) {
             int_6 = int_1;
@@ -464,12 +459,12 @@ public class TextFieldWidget extends Drawable implements IWidget {
             int_4 = int_6;
         }
         
-        if (int_3 > this.x + this.width) {
-            int_3 = this.x + this.width;
+        if (int_3 > this.bounds.x + this.bounds.width) {
+            int_3 = this.bounds.x + this.bounds.width;
         }
         
-        if (int_1 > this.x + this.width) {
-            int_1 = this.x + this.width;
+        if (int_1 > this.bounds.x + this.bounds.width) {
+            int_1 = this.bounds.x + this.bounds.width;
         }
         
         Tessellator tessellator_1 = Tessellator.getInstance();
@@ -516,12 +511,12 @@ public class TextFieldWidget extends Drawable implements IWidget {
         this.hasBorder = boolean_1;
     }
     
-    public void method_1868(int int_1) {
-        this.field_2100 = int_1;
+    public void setEditableColor(int int_1) {
+        this.editableColor = int_1;
     }
     
-    public void method_1860(int int_1) {
-        this.field_2098 = int_1;
+    public void setNotEditableColor(int int_1) {
+        this.notEditableColor = int_1;
     }
     
     public void setHasFocus(boolean boolean_1) {
@@ -548,8 +543,8 @@ public class TextFieldWidget extends Drawable implements IWidget {
         this.editable = boolean_1;
     }
     
-    public int method_1859() {
-        return this.hasBorder() ? this.width - 8 : this.width;
+    public int getWidth() {
+        return this.hasBorder() ? this.bounds.width - 8 : this.bounds.width;
     }
     
     public void method_1884(int int_1) {
@@ -560,7 +555,7 @@ public class TextFieldWidget extends Drawable implements IWidget {
                 this.field_2103 = int_2;
             }
             
-            int int_3 = this.method_1859();
+            int int_3 = this.getWidth();
             String string_1 = this.textRenderer.trimToWidth(this.text.substring(this.field_2103), int_3);
             int int_4 = string_1.length() + this.field_2103;
             if (this.cursorMin == this.field_2103) {
@@ -595,11 +590,7 @@ public class TextFieldWidget extends Drawable implements IWidget {
     }
     
     public int method_1889(int int_1) {
-        return int_1 > this.text.length() ? this.x : this.x + this.textRenderer.getStringWidth(this.text.substring(0, int_1));
-    }
-    
-    public void setX(int int_1) {
-        this.x = int_1;
+        return int_1 > this.text.length() ? this.bounds.x : this.bounds.x + this.textRenderer.getStringWidth(this.text.substring(0, int_1));
     }
     
 }

+ 2 - 1
src/main/java/me/shedaniel/rei/mixin/MixinClientPlayNetworkHandler.java

@@ -1,6 +1,7 @@
 package me.shedaniel.rei.mixin;
 
 import me.shedaniel.rei.RoughlyEnoughItemsCore;
+import me.shedaniel.rei.client.RecipeHelper;
 import net.minecraft.client.network.ClientPlayNetworkHandler;
 import net.minecraft.client.network.packet.SynchronizeRecipesS2CPacket;
 import org.spongepowered.asm.mixin.Mixin;
@@ -13,7 +14,7 @@ public class MixinClientPlayNetworkHandler {
     
     @Inject(method = "onSynchronizeRecipes", at = @At("RETURN"))
     private void onUpdateRecipes(SynchronizeRecipesS2CPacket packetIn, CallbackInfo ci) {
-        RoughlyEnoughItemsCore.getRecipeHelper().recipesLoaded(((ClientPlayNetworkHandler) ((Object) this)).getRecipeManager());
+        ((RecipeHelper) RoughlyEnoughItemsCore.getRecipeHelper()).recipesLoaded(((ClientPlayNetworkHandler) ((Object) this)).getRecipeManager());
     }
     
 }

+ 1 - 1
src/main/java/me/shedaniel/rei/mixin/MixinContainerScreen.java

@@ -61,7 +61,7 @@ public class MixinContainerScreen extends Screen implements IMixinContainerScree
         this.listeners.add(GuiHelper.getLastOverlay(true));
     }
     
-    @Inject(method = "method_18326(IIF)V", at = @At("RETURN"))
+    @Inject(method = "draw(IIF)V", at = @At("RETURN"))
     public void draw(int int_1, int int_2, float float_1, CallbackInfo info) {
         if (MinecraftClient.getInstance().currentScreen instanceof CreativePlayerInventoryScreen) {
             IMixinTabGetter tabGetter = (IMixinTabGetter) MinecraftClient.getInstance().currentScreen;

+ 3 - 3
src/main/java/me/shedaniel/rei/mixin/MixinCraftingTableScreen.java

@@ -1,7 +1,7 @@
 package me.shedaniel.rei.mixin;
 
 import net.minecraft.client.gui.ContainerScreen;
-import net.minecraft.client.gui.GuiEventListener;
+import net.minecraft.client.gui.InputListener;
 import net.minecraft.client.gui.container.CraftingTableScreen;
 import net.minecraft.client.gui.recipebook.RecipeBookGui;
 import net.minecraft.container.Container;
@@ -26,14 +26,14 @@ public abstract class MixinCraftingTableScreen extends ContainerScreen {
     }
     
     @Override
-    public GuiEventListener getFocused() {
+    public InputListener getFocused() {
         return super.getFocused();
     }
     
     @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true)
     public void mouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> ci) {
         if (recipeBookGui.mouseClicked(mouseX, mouseY, button)) {
-            method_1967(recipeBookGui);
+            focusOn(recipeBookGui);
             ci.setReturnValue(true);
             ci.cancel();
         }

+ 3 - 3
src/main/java/me/shedaniel/rei/mixin/MixinPlayerInventoryScreen.java

@@ -1,6 +1,6 @@
 package me.shedaniel.rei.mixin;
 
-import net.minecraft.client.gui.GuiEventListener;
+import net.minecraft.client.gui.InputListener;
 import net.minecraft.client.gui.ingame.AbstractPlayerInventoryScreen;
 import net.minecraft.client.gui.ingame.PlayerInventoryScreen;
 import net.minecraft.client.gui.ingame.RecipeBookProvider;
@@ -27,14 +27,14 @@ public abstract class MixinPlayerInventoryScreen extends AbstractPlayerInventory
     }
     
     @Override
-    public GuiEventListener getFocused() {
+    public InputListener getFocused() {
         return super.getFocused();
     }
     
     @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true)
     public void mouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> ci) {
         if (recipeBook.mouseClicked(mouseX, mouseY, button)) {
-            method_1967(recipeBook);
+            focusOn(recipeBook);
             ci.setReturnValue(true);
             ci.cancel();
         }

+ 29 - 0
src/main/java/me/shedaniel/rei/mixin/MixinTextureManager.java

@@ -0,0 +1,29 @@
+package me.shedaniel.rei.mixin;
+
+import me.shedaniel.rei.RoughlyEnoughItemsCore;
+import net.minecraft.client.texture.Texture;
+import net.minecraft.client.texture.TextureManager;
+import net.minecraft.util.Identifier;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+/* This should not be used by the public, just useful for me, enable in config file */
+@Mixin(TextureManager.class)
+public abstract class MixinTextureManager {
+    
+    private boolean rei_already = false;
+    
+    @Inject(method = "registerTexture", at = @At("HEAD"), cancellable = true)
+    private void registerTexture(Identifier identifier_1, Texture texture_1, final CallbackInfoReturnable<Boolean> cir) {
+        if (!RoughlyEnoughItemsCore.getConfigHelper().getConfig().fixRamUsage)
+            return;
+        if (identifier_1.equals(new Identifier("textures/gui/title/mojang.png")))
+            if (rei_already)
+                cir.setReturnValue(false);
+            else
+                rei_already = true;
+    }
+    
+}

+ 3 - 2
src/main/java/me/shedaniel/rei/plugin/DefaultBlastingDisplay.java

@@ -10,6 +10,7 @@ import net.minecraft.util.Identifier;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 public class DefaultBlastingDisplay implements IRecipeDisplay<BlastingRecipe> {
@@ -31,8 +32,8 @@ public class DefaultBlastingDisplay implements IRecipeDisplay<BlastingRecipe> {
     }
     
     @Override
-    public BlastingRecipe getRecipe() {
-        return display;
+    public Optional<BlastingRecipe> getRecipe() {
+        return Optional.ofNullable(display);
     }
     
     @Override

+ 3 - 2
src/main/java/me/shedaniel/rei/plugin/DefaultBrewingDisplay.java

@@ -11,6 +11,7 @@ import net.minecraft.util.Identifier;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 
 public class DefaultBrewingDisplay implements IRecipeDisplay {
     
@@ -24,8 +25,8 @@ public class DefaultBrewingDisplay implements IRecipeDisplay {
     }
     
     @Override
-    public Recipe getRecipe() {
-        return null;
+    public Optional<Recipe> getRecipe() {
+        return Optional.empty();
     }
     
     @Override

+ 6 - 4
src/main/java/me/shedaniel/rei/plugin/DefaultCampfireDisplay.java

@@ -4,7 +4,6 @@ import com.google.common.collect.Lists;
 import me.shedaniel.rei.api.IRecipeDisplay;
 import net.minecraft.item.ItemStack;
 import net.minecraft.recipe.Ingredient;
-import net.minecraft.recipe.Recipe;
 import net.minecraft.recipe.cooking.CampfireCookingRecipe;
 import net.minecraft.util.DefaultedList;
 import net.minecraft.util.Identifier;
@@ -12,14 +11,17 @@ import net.minecraft.util.Identifier;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 
-public class DefaultCampfireDisplay implements IRecipeDisplay {
+public class DefaultCampfireDisplay implements IRecipeDisplay<CampfireCookingRecipe> {
     
     private List<ItemStack> inputs, output;
     private int cookTime;
+    private CampfireCookingRecipe display;
     
     public DefaultCampfireDisplay(CampfireCookingRecipe recipe) {
         this(recipe.getPreviewInputs(), recipe.getOutput(), recipe.getCookTime());
+        this.display = recipe;
     }
     
     public DefaultCampfireDisplay(DefaultedList<Ingredient> ingredients, ItemStack output, int cookTime) {
@@ -34,8 +36,8 @@ public class DefaultCampfireDisplay implements IRecipeDisplay {
     }
     
     @Override
-    public Recipe getRecipe() {
-        return null;
+    public Optional<CampfireCookingRecipe> getRecipe() {
+        return Optional.ofNullable(display);
     }
     
     @Override

+ 70 - 0
src/main/java/me/shedaniel/rei/plugin/DefaultCustomDisplay.java

@@ -0,0 +1,70 @@
+package me.shedaniel.rei.plugin;
+
+import com.google.common.collect.Lists;
+import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.Recipe;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+public class DefaultCustomDisplay implements DefaultCraftingDisplay {
+    
+    private List<List<ItemStack>> input;
+    private List<ItemStack> output;
+    private Recipe possibleRecipe;
+    private int width, height;
+    
+    public DefaultCustomDisplay(List<List<ItemStack>> input, List<ItemStack> output, Recipe possibleRecipe) {
+        this.input = input;
+        this.output = output;
+        this.possibleRecipe = possibleRecipe;
+        List<Boolean> row = Lists.newArrayList(false, false, false);
+        List<Boolean> column = Lists.newArrayList(false, false, false);
+        for(int i = 0; i < 9; i++)
+            if (i < input.size()) {
+                List<ItemStack> stacks = input.get(i);
+                if (stacks.stream().filter(stack -> !stack.isEmpty()).count() > 0) {
+                    row.set((i - (i % 3)) / 3, true);
+                    column.set(i % 3, true);
+                }
+            }
+        width = (int) column.stream().filter(b -> b).count();
+        height = (int) row.stream().filter(b -> b).count();
+    }
+    
+    public DefaultCustomDisplay(List<List<ItemStack>> input, List<ItemStack> output) {
+        this(input, output, null);
+    }
+    
+    @Override
+    public Optional<Recipe> getRecipe() {
+        return Optional.ofNullable(possibleRecipe);
+    }
+    
+    @Override
+    public List<List<ItemStack>> getInput() {
+        return Collections.unmodifiableList(input);
+    }
+    
+    @Override
+    public List<ItemStack> getOutput() {
+        return Collections.unmodifiableList(output);
+    }
+    
+    @Override
+    public List<List<ItemStack>> getRequiredItems() {
+        return Collections.unmodifiableList(input);
+    }
+    
+    @Override
+    public int getWidth() {
+        return width;
+    }
+    
+    @Override
+    public int getHeight() {
+        return height;
+    }
+    
+}

+ 37 - 15
src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java

@@ -16,6 +16,7 @@ import net.minecraft.enchantment.Enchantment;
 import net.minecraft.enchantment.EnchantmentHelper;
 import net.minecraft.item.ItemStack;
 import net.minecraft.item.Items;
+import net.minecraft.potion.PotionUtil;
 import net.minecraft.recipe.Recipe;
 import net.minecraft.recipe.StonecuttingRecipe;
 import net.minecraft.recipe.cooking.BlastingRecipe;
@@ -27,9 +28,7 @@ import net.minecraft.recipe.crafting.ShapelessRecipe;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.registry.Registry;
 
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 public class DefaultPlugin implements IRecipePlugin {
     
@@ -40,6 +39,7 @@ public class DefaultPlugin implements IRecipePlugin {
     public static final Identifier CAMPFIRE = new Identifier("roughlyenoughitems", "plugins/campfire");
     public static final Identifier STONE_CUTTING = new Identifier("roughlyenoughitems", "plugins/stone_cutting");
     public static final Identifier BREWING = new Identifier("roughlyenoughitems", "plugins/brewing");
+    public static final Identifier PLUGIN = new Identifier("roughlyenoughitems", "default_plugin");
     
     private static final List<DefaultBrewingDisplay> BREWING_DISPLAYS = Lists.newArrayList();
     
@@ -49,11 +49,11 @@ public class DefaultPlugin implements IRecipePlugin {
     
     @Override
     public void onFirstLoad(IPluginDisabler pluginDisabler) {
-        if (!RoughlyEnoughItemsCore.getConfigHelper().isLoadingDefaultPlugin()) {
-            pluginDisabler.disablePluginFunction(new Identifier("roughlyenoughitems", "default_plugin"), PluginFunction.REGISTER_ITEMS);
-            pluginDisabler.disablePluginFunction(new Identifier("roughlyenoughitems", "default_plugin"), PluginFunction.REGISTER_CATEGORIES);
-            pluginDisabler.disablePluginFunction(new Identifier("roughlyenoughitems", "default_plugin"), PluginFunction.REGISTER_RECIPE_DISPLAYS);
-            pluginDisabler.disablePluginFunction(new Identifier("roughlyenoughitems", "default_plugin"), PluginFunction.REGISTER_SPEED_CRAFT);
+        if (!RoughlyEnoughItemsCore.getConfigHelper().getConfig().loadDefaultPlugin) {
+            pluginDisabler.disablePluginFunction(PLUGIN, PluginFunction.REGISTER_ITEMS);
+            pluginDisabler.disablePluginFunction(PLUGIN, PluginFunction.REGISTER_CATEGORIES);
+            pluginDisabler.disablePluginFunction(PLUGIN, PluginFunction.REGISTER_RECIPE_DISPLAYS);
+            pluginDisabler.disablePluginFunction(PLUGIN, PluginFunction.REGISTER_SPEED_CRAFT);
         }
     }
     
@@ -78,7 +78,7 @@ public class DefaultPlugin implements IRecipePlugin {
     }
     
     @Override
-    public void registerPluginCategories(RecipeHelper recipeHelper) {
+    public void registerPluginCategories(IRecipeHelper recipeHelper) {
         recipeHelper.registerCategory(new DefaultCraftingCategory());
         recipeHelper.registerCategory(new DefaultSmeltingCategory());
         recipeHelper.registerCategory(new DefaultSmokingCategory());
@@ -89,7 +89,7 @@ public class DefaultPlugin implements IRecipePlugin {
     }
     
     @Override
-    public void registerRecipeDisplays(RecipeHelper recipeHelper) {
+    public void registerRecipeDisplays(IRecipeHelper recipeHelper) {
         for(Recipe recipe : recipeHelper.getRecipeManager().values())
             if (recipe instanceof ShapelessRecipe)
                 recipeHelper.registerDisplay(CRAFTING, new DefaultShapelessDisplay((ShapelessRecipe) recipe));
@@ -106,10 +106,24 @@ public class DefaultPlugin implements IRecipePlugin {
             else if (recipe instanceof StonecuttingRecipe)
                 recipeHelper.registerDisplay(STONE_CUTTING, new DefaultStoneCuttingDisplay((StonecuttingRecipe) recipe));
         BREWING_DISPLAYS.stream().forEachOrdered(display -> recipeHelper.registerDisplay(BREWING, display));
+        List<ItemStack> arrowStack = Arrays.asList(Items.ARROW.getDefaultStack());
+        RoughlyEnoughItemsCore.getItemRegisterer().getItemList().stream().filter(stack -> stack.getItem().equals(Items.LINGERING_POTION)).forEach(stack -> {
+            List<List<ItemStack>> input = new ArrayList<>();
+            for(int i = 0; i < 4; i++)
+                input.add(arrowStack);
+            input.add(Arrays.asList(stack));
+            for(int i = 0; i < 4; i++)
+                input.add(arrowStack);
+            ItemStack outputStack = new ItemStack(Items.TIPPED_ARROW, 8);
+            PotionUtil.setPotion(outputStack, PotionUtil.getPotion(stack));
+            PotionUtil.setCustomPotionEffects(outputStack, PotionUtil.getCustomPotionEffects(stack));
+            List<ItemStack> output = Lists.newArrayList(outputStack);
+            recipeHelper.registerDisplay(CRAFTING, new DefaultCustomDisplay(input, output));
+        });
     }
     
     @Override
-    public void registerSpeedCraft(RecipeHelper recipeHelper) {
+    public void registerSpeedCraft(IRecipeHelper recipeHelper) {
         recipeHelper.registerSpeedCraftButtonArea(DefaultPlugin.CAMPFIRE, null);
         recipeHelper.registerSpeedCraftButtonArea(DefaultPlugin.STONE_CUTTING, null);
         recipeHelper.registerSpeedCraftButtonArea(DefaultPlugin.BREWING, null);
@@ -121,13 +135,15 @@ public class DefaultPlugin implements IRecipePlugin {
             
             @Override
             public boolean performAutoCraft(Screen screen, DefaultCraftingDisplay recipe) {
+                if (!recipe.getRecipe().isPresent())
+                    return false;
                 if (screen.getClass().isAssignableFrom(CraftingTableScreen.class))
                     ((IMixinRecipeBookGui) (((CraftingTableScreen) screen).getRecipeBookGui())).rei_getGhostSlots().reset();
                 else if (screen.getClass().isAssignableFrom(PlayerInventoryScreen.class))
                     ((IMixinRecipeBookGui) (((PlayerInventoryScreen) screen).getRecipeBookGui())).rei_getGhostSlots().reset();
                 else
                     return false;
-                MinecraftClient.getInstance().interactionManager.clickRecipe(MinecraftClient.getInstance().player.container.syncId, recipe.getRecipe(), Screen.isShiftPressed());
+                MinecraftClient.getInstance().interactionManager.clickRecipe(MinecraftClient.getInstance().player.container.syncId, (Recipe) recipe.getRecipe().get(), Screen.isShiftPressed());
                 return true;
             }
             
@@ -144,11 +160,13 @@ public class DefaultPlugin implements IRecipePlugin {
             
             @Override
             public boolean performAutoCraft(Screen screen, DefaultSmeltingDisplay recipe) {
+                if (!recipe.getRecipe().isPresent())
+                    return false;
                 if (screen instanceof FurnaceScreen)
                     ((IMixinRecipeBookGui) (((FurnaceScreen) screen).getRecipeBookGui())).rei_getGhostSlots().reset();
                 else
                     return false;
-                MinecraftClient.getInstance().interactionManager.clickRecipe(MinecraftClient.getInstance().player.container.syncId, recipe.getRecipe(), Screen.isShiftPressed());
+                MinecraftClient.getInstance().interactionManager.clickRecipe(MinecraftClient.getInstance().player.container.syncId, (Recipe) recipe.getRecipe().get(), Screen.isShiftPressed());
                 return true;
             }
             
@@ -165,11 +183,13 @@ public class DefaultPlugin implements IRecipePlugin {
             
             @Override
             public boolean performAutoCraft(Screen screen, DefaultSmokingDisplay recipe) {
+                if (!recipe.getRecipe().isPresent())
+                    return false;
                 if (screen instanceof SmokerScreen)
                     ((IMixinRecipeBookGui) (((SmokerScreen) screen).getRecipeBookGui())).rei_getGhostSlots().reset();
                 else
                     return false;
-                MinecraftClient.getInstance().interactionManager.clickRecipe(MinecraftClient.getInstance().player.container.syncId, recipe.getRecipe(), Screen.isShiftPressed());
+                MinecraftClient.getInstance().interactionManager.clickRecipe(MinecraftClient.getInstance().player.container.syncId, (Recipe) recipe.getRecipe().get(), Screen.isShiftPressed());
                 return true;
             }
             
@@ -191,11 +211,13 @@ public class DefaultPlugin implements IRecipePlugin {
             
             @Override
             public boolean performAutoCraft(Screen screen, DefaultBlastingDisplay recipe) {
+                if (!recipe.getRecipe().isPresent())
+                    return false;
                 if (screen instanceof BlastFurnaceScreen)
                     ((IMixinRecipeBookGui) (((BlastFurnaceScreen) screen).getRecipeBookGui())).rei_getGhostSlots().reset();
                 else
                     return false;
-                MinecraftClient.getInstance().interactionManager.clickRecipe(MinecraftClient.getInstance().player.container.syncId, recipe.getRecipe(), Screen.isShiftPressed());
+                MinecraftClient.getInstance().interactionManager.clickRecipe(MinecraftClient.getInstance().player.container.syncId, (Recipe) recipe.getRecipe().get(), Screen.isShiftPressed());
                 return true;
             }
         });

+ 4 - 2
src/main/java/me/shedaniel/rei/plugin/DefaultShapedDisplay.java

@@ -2,10 +2,12 @@ package me.shedaniel.rei.plugin;
 
 import com.google.common.collect.Lists;
 import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.Recipe;
 import net.minecraft.recipe.crafting.ShapedRecipe;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 
 public class DefaultShapedDisplay implements DefaultCraftingDisplay<ShapedRecipe> {
     
@@ -23,8 +25,8 @@ public class DefaultShapedDisplay implements DefaultCraftingDisplay<ShapedRecipe
     }
     
     @Override
-    public ShapedRecipe getRecipe() {
-        return display;
+    public Optional<Recipe> getRecipe() {
+        return Optional.ofNullable(display);
     }
     
     @Override

+ 3 - 2
src/main/java/me/shedaniel/rei/plugin/DefaultShapelessDisplay.java

@@ -6,6 +6,7 @@ import net.minecraft.recipe.crafting.ShapelessRecipe;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 
 public class DefaultShapelessDisplay implements DefaultCraftingDisplay {
     
@@ -23,8 +24,8 @@ public class DefaultShapelessDisplay implements DefaultCraftingDisplay {
     }
     
     @Override
-    public ShapelessRecipe getRecipe() {
-        return display;
+    public Optional<ShapelessRecipe> getRecipe() {
+        return Optional.ofNullable(display);
     }
     
     @Override

+ 3 - 2
src/main/java/me/shedaniel/rei/plugin/DefaultSmeltingDisplay.java

@@ -10,6 +10,7 @@ import net.minecraft.util.Identifier;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 public class DefaultSmeltingDisplay implements IRecipeDisplay<SmeltingRecipe> {
@@ -31,8 +32,8 @@ public class DefaultSmeltingDisplay implements IRecipeDisplay<SmeltingRecipe> {
     }
     
     @Override
-    public SmeltingRecipe getRecipe() {
-        return display;
+    public Optional<SmeltingRecipe> getRecipe() {
+        return Optional.ofNullable(display);
     }
     
     @Override

+ 4 - 2
src/main/java/me/shedaniel/rei/plugin/DefaultSmokingDisplay.java

@@ -5,11 +5,13 @@ import me.shedaniel.rei.api.IRecipeDisplay;
 import net.minecraft.block.entity.FurnaceBlockEntity;
 import net.minecraft.item.Item;
 import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.cooking.SmeltingRecipe;
 import net.minecraft.recipe.cooking.SmokingRecipe;
 import net.minecraft.util.Identifier;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 public class DefaultSmokingDisplay implements IRecipeDisplay<SmokingRecipe> {
@@ -31,8 +33,8 @@ public class DefaultSmokingDisplay implements IRecipeDisplay<SmokingRecipe> {
     }
     
     @Override
-    public SmokingRecipe getRecipe() {
-        return display;
+    public Optional<SmokingRecipe> getRecipe() {
+        return Optional.ofNullable(display);
     }
     
     @Override

+ 6 - 4
src/main/java/me/shedaniel/rei/plugin/DefaultStoneCuttingDisplay.java

@@ -4,7 +4,6 @@ import com.google.common.collect.Lists;
 import me.shedaniel.rei.api.IRecipeDisplay;
 import net.minecraft.item.ItemStack;
 import net.minecraft.recipe.Ingredient;
-import net.minecraft.recipe.Recipe;
 import net.minecraft.recipe.StonecuttingRecipe;
 import net.minecraft.util.DefaultedList;
 import net.minecraft.util.Identifier;
@@ -12,13 +11,16 @@ import net.minecraft.util.Identifier;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 
-public class DefaultStoneCuttingDisplay implements IRecipeDisplay {
+public class DefaultStoneCuttingDisplay implements IRecipeDisplay<StonecuttingRecipe> {
     
     private List<ItemStack> inputs, output;
+    private StonecuttingRecipe display;
     
     public DefaultStoneCuttingDisplay(StonecuttingRecipe recipe) {
         this(recipe.getPreviewInputs(), recipe.getOutput());
+        this.display = recipe;
     }
     
     public DefaultStoneCuttingDisplay(DefaultedList<Ingredient> ingredients, ItemStack output) {
@@ -28,8 +30,8 @@ public class DefaultStoneCuttingDisplay implements IRecipeDisplay {
     }
     
     @Override
-    public Recipe getRecipe() {
-        return null;
+    public Optional<StonecuttingRecipe> getRecipe() {
+        return Optional.ofNullable(display);
     }
     
     @Override

+ 1 - 1
src/main/java/me/shedaniel/rei/update/UpdateChecker.java

@@ -55,7 +55,7 @@ public class UpdateChecker implements ClientModInitializer {
     }
     
     public static boolean checkUpdates() {
-        return RoughlyEnoughItemsCore.getConfigHelper().checkUpdates();
+        return RoughlyEnoughItemsCore.getConfigHelper().getConfig().checkUpdates;
     }
     
     public static List<String> getChangelog(Version currentVersion) {

+ 5 - 5
src/main/resources/assets/roughlyenoughitems/lang/de_de.json

@@ -18,10 +18,10 @@
   "category.rei.brewing.reactant": "§Zutat",
   "category.rei.brewing.result": "§eEntstehender Trank",
   "text.rei.config": "Einstellung",
-  "text.rei.centre_searchbox": "Rechte Suchbox: ",
+  "text.rei.config.side_search_box": "Rechte Suchbox: ",
   "text.rei.cheat_items": "[{item_name}] x{item_count} {player_name} gegeben.",
   "text.rei.failed_cheat_items": "§cItems geben fehlgeschlagen.",
-  "text.rei.list_ordering": "Item Listen Reihenfolge",
+  "text.rei.config.list_ordering": "Item Listen Reihenfolge",
 
   "ordering.rei.ascending": "Aufsteigend",
   "ordering.rei.descending": "Absteigend",
@@ -30,15 +30,15 @@
   "ordering.rei.item_groups": "Itemgruppen",
   "text.speed_craft.failed_move_items": "§cItems können nicht bewegt werden!",
   "text.speed_craft.move_items": "Items bewegen",
-  "text.rei.enable_craftable_only": "Aktiviere nur Herstellen: ",
+  "text.rei.config.enable_craftable_only": "Aktiviere nur Herstellen: ",
   "text.rei.showing_craftable": "Zeige herstellbar",
   "text.rei.showing_all": "Zeige alle",
   "text.rei.delete_items": "§cLösche Item",
   "text.rei.check_updates": "Überprüfe Updates: ",
   "text.rei.update_outdated": "§6REI ist veraltet!\n§6Aktuell: §a%s §6Letzte: §a%s\n§6Update Priorität: §a%s",
   "text.rei.update_changelog_line": "§6- %s",
-  "text.rei.load_default_plugin": "Lade Standard-Plugin: ",
-  "text.rei.load_default_plugin.restart_tooltip": "Du möchtest das wahrscheinlich niemals deaktivieren.\nStarte Minecraft erneut, um die Einstellungen zu übernehmen."
+  "text.rei.config.load_default_plugin": "Lade Standard-Plugin: ",
+  "text.rei.config.load_default_plugin.restart_tooltip": "Du möchtest das wahrscheinlich niemals deaktivieren.\nStarte Minecraft erneut, um die Einstellungen zu übernehmen."
 
 
 

+ 5 - 5
src/main/resources/assets/roughlyenoughitems/lang/en_ud.json

@@ -18,12 +18,12 @@
   "category.rei.brewing.reactant": "§eʇuǝᴉpǝɹƃuI",
   "category.rei.brewing.result": "§euoᴉʇoԀ pǝʇlnsǝɹ",
   "text.rei.config": "ƃᴉɟuoƆ",
-  "text.rei.side_searchbox": "xoq ɥɔɹɐǝS ǝpᴉS: ",
-  "text.rei.mirror_rei": "sʇǝƃpᴉM IƎɹ ɹoɹɹᴉW: ",
+  "text.rei.config.side_search_box": "xoq ɥɔɹɐǝS ǝpᴉS: ",
+  "text.rei.config.mirror_rei": "sʇǝƃpᴉM IƎɹ ɹoɹɹᴉW: ",
   "text.rei.cheat_items": "˙{player_name} oʇ {item_count}x [{item_name}] uǝʌᴉפ",
   "text.rei.failed_cheat_items": "§c˙sɯǝʇᴉ ǝʌᴉƃ oʇ pǝlᴉɐℲ",
-  "text.rei.list_ordering": "ƃuᴉɹǝpɹO ʇsᴉ˥ ɯǝʇI",
-  "text.rei.list_ordering_button": "%s [%s]",
+  "text.rei.config.list_ordering": "ƃuᴉɹǝpɹO ʇsᴉ˥ ɯǝʇI",
+  "text.rei.config.list_ordering_button": "%s [%s]",
   "ordering.rei.ascending": "ƃuᴉpuǝɔs∀",
   "ordering.rei.descending": "ƃuᴉpuǝɔsǝp",
   "ordering.rei.registry": "ʎɹʇsᴉƃǝɹ",
@@ -31,7 +31,7 @@
   "ordering.rei.item_groups": "sdnoɹפ ɯǝʇI",
   "text.speed_craft.failed_move_items": "§c¡sɯǝʇᴉ ǝʌoɯ ʇ,uɐƆ",
   "text.speed_craft.move_items": "sɯǝʇI ǝʌoW",
-  "text.rei.enable_craftable_only": "ɹǝʇlᴉℲ ǝlqɐʇɟɐɹƆ ǝlqɐuƎ: ",
+  "text.rei.config.enable_craftable_only": "ɹǝʇlᴉℲ ǝlqɐʇɟɐɹƆ ǝlqɐuƎ: ",
   "text.rei.showing_craftable": "ǝlqɐʇɟɐɹƆ ƃuᴉʍoɥS",
   "text.rei.showing_all": "ll∀ ƃuᴉʍoɥS",
   "text.rei.delete_items": "§cɯǝʇI ǝʇǝlǝp",

+ 27 - 10
src/main/resources/assets/roughlyenoughitems/lang/en_us.json

@@ -3,8 +3,9 @@
   "key.roughlyenoughitems.recipe_keybind": "Show Recipe",
   "key.roughlyenoughitems.hide_keybind": "Hide/Show REI",
   "key.roughlyenoughitems.usage_keybind": "Show Uses",
-  "text.rei.cheat": "Cheat",
-  "text.rei.nocheat": "§c§mCheat",
+  "text.rei.config.general": "General",
+  "text.rei.config.cheating": "Cheating:",
+  "text.rei.cheating": "Cheating",
   "category.rei.crafting": "Crafting",
   "category.rei.smelting": "Smelting",
   "category.rei.smelting.fuel": "§eFuel",
@@ -18,12 +19,13 @@
   "category.rei.brewing.reactant": "§eIngredient",
   "category.rei.brewing.result": "§eResulted Potion",
   "text.rei.config": "Config",
-  "text.rei.side_searchbox": "Side Search Box: ",
-  "text.rei.mirror_rei": "Mirror REI Widgets: ",
-  "text.rei.cheat_items": "Given [{item_name}] x{item_count} to {player_name}.",
+  "text.rei.config_tooltip": "Open Config Screen\n§7Shift-Click to toggle cheat mode",
+  "text.rei.config.side_search_box": "Side Search Box: ",
+  "text.rei.config.mirror_rei": "Mirror REI Widgets: ",
+  "text.rei.cheat_items": "Gave [{item_name}] x{item_count} to {player_name}.",
   "text.rei.failed_cheat_items": "§cFailed to give items.",
-  "text.rei.list_ordering": "Item List Ordering",
-  "text.rei.list_ordering_button": "%s [%s]",
+  "text.rei.config.list_ordering": "Item List Ordering:",
+  "text.rei.config.list_ordering_button": "%s [%s]",
   "ordering.rei.ascending": "Ascending",
   "ordering.rei.descending": "Descending",
   "ordering.rei.registry": "Registry",
@@ -31,18 +33,33 @@
   "ordering.rei.item_groups": "Item Groups",
   "text.speed_craft.failed_move_items": "§cCan't move items!",
   "text.speed_craft.move_items": "Move Items",
-  "text.rei.enable_craftable_only": "Enable Craftable Filter: ",
+  "text.rei.config.enable_craftable_only": "Enable Craftable Filter: ",
   "text.rei.showing_craftable": "Showing Craftable",
   "text.rei.showing_all": "Showing All",
   "text.rei.delete_items": "§cDelete Item",
   "text.rei.check_updates": "Check Updates: ",
   "text.rei.update_outdated": "§6REI is outdated!\n§6Current: §a%s §6Latest: §a%s\n§6Update Priority: §a%s",
   "text.rei.update_changelog_line": "§6- %s",
-  "text.rei.load_default_plugin": "Load Default Plugin: ",
-  "text.rei.load_default_plugin.restart_tooltip": "You probably never want to disable this.\nRestart Minecraft to apply this setting.",
+  "text.rei.config.load_default_plugin": "Load Default Plugin: ",
+  "text.rei.config.load_default_plugin.restart_tooltip": "You probably never want to disable this.\nRestart Minecraft to apply this setting.",
   "text.rei.credits": "Credits",
   "text.rei.left_arrow": "<",
   "text.rei.right_arrow": ">",
+  "text.rei.give_command": "Cheat Give Command:",
+  "text.rei.gamemode_command": "Cheat Give Command:",
+  "text.rei.give_command.tooltip": "This command is only used in servers when cheating.",
+  "text.rei.give_command.suggestion": "Enter command.",
+  "text.rei.view_all_categories": "View All Categories",
+  "text.rei.go_back_first_page": "Back to Page 1",
+  "text.rei.config.appearance": "Appearance",
+  "text.rei.config.modules": "Modules",
+  "text.rei.config.advanced": "Advanced",
+  "text.rei.choose_page": "Choose Page",
+  "text.rei.config.disable_credits_button": "Disable Credits Button:",
+  "text.rei.config.max_recipes_per_page": "Maximum Recipes Each Page:",
+  "text.rei.config.enable_util_buttons": "Enable Utils Buttons:",
+  "text.rei.gamemode_button.tooltip": "Switch GameMode\n§7Switch to %s mode.",
+  "text.rei.weather_button.tooltip": "Switch Weather\n§7Switch to %s.",
 
   "_comment": "Don't change / translate the credit down below if you are doing it :)",
   "text.rei.credit.text": "§lRoughly Enough Items\n§7Originally a fork for Almost Enough Items.\n\n§lDevelopers\n  - Originally by ZenDarva\n  - Created by Danielshe\n  - Plugin Support by TehNut\n\n§lLanguage Translation\n  English - Danielshe\n  Simplified Chinese - Danielshe\n  Traditional Chinese - hugoalh & Danielshe\n  French - Yanis48\n  German - MelanX\n  Estonian - Madis0\n  Hebrew - nerdoron\n  LOLCAT - Danielshe\n  Upside Down - Danielshe\n\n§lLicense\n§7Roughly Enough Items is using MIT."

+ 5 - 5
src/main/resources/assets/roughlyenoughitems/lang/et_ee.json

@@ -18,12 +18,12 @@
   "category.rei.brewing.reactant": "§eKoostisosa",
   "category.rei.brewing.result": "§eValmiv võlujook",
   "text.rei.config": "Seadistus",
-  "text.rei.side_searchbox": "Külgmine otsingukast: ",
-  "text.rei.mirror_rei": "REI vidinate peegeldamine: ",
+  "text.rei.config.side_search_box": "Külgmine otsingukast: ",
+  "text.rei.config.mirror_rei": "REI vidinate peegeldamine: ",
   "text.rei.cheat_items": "{item_count} [{item_name}] antud mängijale {player_name}.",
   "text.rei.failed_cheat_items": "§cEsemete andmine ebaõnnestus.",
-  "text.rei.list_ordering": "Esemenimekirja järjestus",
-  "text.rei.list_ordering_button": "%s (%s)",
+  "text.rei.config.list_ordering": "Esemenimekirja järjestus",
+  "text.rei.config.list_ordering_button": "%s (%s)",
   "ordering.rei.ascending": "kasvav",
   "ordering.rei.descending": "kahanev",
   "ordering.rei.registry": "Register",
@@ -31,7 +31,7 @@
   "ordering.rei.item_groups": "Esemegrupid",
   "text.speed_craft.failed_move_items": "§cEsemeid ei saa liigutada!",
   "text.speed_craft.move_items": "Liiguta esemeid",
-  "text.rei.enable_craftable_only": "Luba meisterdatavate filter: ",
+  "text.rei.config.enable_craftable_only": "Luba meisterdatavate filter: ",
   "text.rei.showing_craftable": "Kuvab meisterdatavaid",
   "text.rei.showing_all": "Kuvab kõiki",
   "text.rei.delete_items": "§cKustuta ese",

+ 7 - 7
src/main/resources/assets/roughlyenoughitems/lang/fr_fr.json

@@ -18,12 +18,12 @@
   "category.rei.brewing.reactant": "§eIngrédient",
   "category.rei.brewing.result": "§ePotion obtenue",
   "text.rei.config": "Config",
-  "text.rei.side_searchbox": "Zone de recherche latérale : ",
-  "text.rei.mirror_rei": "Refléter les widgets REI : ",
+  "text.rei.config.side_search_box": "Zone de recherche latérale : ",
+  "text.rei.config.mirror_rei": "Refléter les widgets REI : ",
   "text.rei.cheat_items": "[{item_name}] x{item_count} ont été donnés à {player_name}.",
   "text.rei.failed_cheat_items": "§cImpossible de donner les objets.",
-  "text.rei.list_ordering": "Ordre de la liste d'objets",
-  "text.rei.list_ordering_button": "%s [%s]",
+  "text.rei.config.list_ordering": "Ordre de la liste d'objets",
+  "text.rei.config.list_ordering_button": "%s [%s]",
   "ordering.rei.ascending": "Croissant",
   "ordering.rei.descending": "Décroissant",
   "ordering.rei.registry": "Registre",
@@ -31,15 +31,15 @@
   "ordering.rei.item_groups": "Groupes d'objets",
   "text.speed_craft.failed_move_items": "§cImpossible de déplacer les objets!",
   "text.speed_craft.move_items": "Déplacer les objets",
-  "text.rei.enable_craftable_only": "Activer les recettes réalisables uniquement : ",
+  "text.rei.config.enable_craftable_only": "Activer les recettes réalisables uniquement : ",
   "text.rei.showing_craftable": "Recettes réalisables",
   "text.rei.showing_all": "Toutes les recettes",
   "text.rei.delete_items": "§cSupprimer l'objet",
   "text.rei.check_updates": "Vérifier les mises à jour : ",
   "text.rei.update_outdated": "§6REI est obsolète !\n§6Version actuelle : §a%s §6Dernière version : §a%s\n§6Priorité de mise à jour : §a%s",
   "text.rei.update_changelog_line": "§6- %s",
-  "text.rei.load_default_plugin": "Charger le plugin par défaut : ",
-  "text.rei.load_default_plugin.restart_tooltip": "Vous ne voudrez probablement jamais désactiver cela.\nRedémarrez Minecraft pour appliquer ce paramètre.",
+  "text.rei.config.load_default_plugin": "Charger le plugin par défaut : ",
+  "text.rei.config.load_default_plugin.restart_tooltip": "Vous ne voudrez probablement jamais désactiver cela.\nRedémarrez Minecraft pour appliquer ce paramètre.",
   "text.rei.credits": "Crédits",
   "text.rei.left_arrow": "<",
   "text.rei.right_arrow": ">"

+ 5 - 5
src/main/resources/assets/roughlyenoughitems/lang/lol_us.json

@@ -18,12 +18,12 @@
   "category.rei.brewing.reactant": "§eThng",
   "category.rei.brewing.result": "§eResuted Poshun",
   "text.rei.config": "Konfig",
-  "text.rei.side_searchbox": "Sie Saerhc Boz: ",
-  "text.rei.mirror_rei": "Miror REI Widgetz: ",
+  "text.rei.config.side_search_box": "Sie Saerhc Boz: ",
+  "text.rei.config.mirror_rei": "Miror REI Widgetz: ",
   "text.rei.cheat_items": "Giv [{item_name}] x{item_count} 2 {player_name}.",
   "text.rei.failed_cheat_items": "§cFaild 2 giv itemz.",
-  "text.rei.list_ordering": "Item lis orderin",
-  "text.rei.list_ordering_button": "%s [%s]",
+  "text.rei.config.list_ordering": "Item lis orderin",
+  "text.rei.config.list_ordering_button": "%s [%s]",
   "ordering.rei.ascending": "Acendin",
   "ordering.rei.descending": "Dscendin",
   "ordering.rei.registry": "Registry",
@@ -31,7 +31,7 @@
   "ordering.rei.item_groups": "Itmz groupz",
   "text.speed_craft.failed_move_items": "§cKan'z m0v itemz!",
   "text.speed_craft.move_items": "M0v Itemz",
-  "text.rei.enable_craftable_only": "Enabel craftabel fit3r: ",
+  "text.rei.config.enable_craftable_only": "Enabel craftabel fit3r: ",
   "text.rei.showing_craftable": "Sowin Craftabel",
   "text.rei.showing_all": "Sowin all",
   "text.rei.delete_items": "§cDel Itm",

+ 5 - 5
src/main/resources/assets/roughlyenoughitems/lang/zh_cn.json

@@ -18,12 +18,12 @@
   "category.rei.brewing.reactant": "§e材料",
   "category.rei.brewing.result": "§e输出药水",
   "text.rei.config": "设置",
-  "text.rei.side_searchbox": "不置中搜索栏: ",
-  "text.rei.mirror_rei": "右置 REI 小工具: ",
+  "text.rei.config.side_search_box": "不置中搜索栏: ",
+  "text.rei.config.mirror_rei": "右置 REI 小工具: ",
   "text.rei.cheat_items": "已将 {item_count} 个 [{item_name}] 给予 {player_name}",
   "text.rei.failed_cheat_items": "§c不能给予物品.",
-  "text.rei.list_ordering": "物品清单排序",
-  "text.rei.list_ordering_button": "%s [%s]",
+  "text.rei.config.list_ordering": "物品清单排序",
+  "text.rei.config.list_ordering_button": "%s [%s]",
   "ordering.rei.ascending": "顺序",
   "ordering.rei.descending": "倒序",
   "ordering.rei.registry": "注册",
@@ -31,7 +31,7 @@
   "ordering.rei.item_groups": "物品分类",
   "text.speed_craft.failed_move_items": "§c不能移动物品!",
   "text.speed_craft.move_items": "移动物品",
-  "text.rei.enable_craftable_only": "过滤不可合成的物品: ",
+  "text.rei.config.enable_craftable_only": "过滤不可合成的物品: ",
   "text.rei.showing_craftable": "显示可合成的物品",
   "text.rei.showing_all": "显示所有物品",
   "text.rei.delete_items": "§c删除物品",

+ 5 - 5
src/main/resources/assets/roughlyenoughitems/lang/zh_tw.json

@@ -18,12 +18,12 @@
   "category.rei.brewing.reactant": "§e材料",
   "category.rei.brewing.result": "§e輸出藥水",
   "text.rei.config": "設置",
-  "text.rei.side_searchbox": "不置中搜索欄: ",
-  "text.rei.mirror_rei": "右置 REI 小工具: ",
+  "text.rei.config.side_search_box": "不置中搜索欄: ",
+  "text.rei.config.mirror_rei": "右置 REI 小工具: ",
   "text.rei.cheat_items": "已將 {item_count} 個 [{item_name}] 給予 {player_name}",
   "text.rei.failed_cheat_items": "§c不能給予物品.",
-  "text.rei.list_ordering": "物品清單排序",
-  "text.rei.list_ordering_button": "%s [%s]",
+  "text.rei.config.list_ordering": "物品清單排序",
+  "text.rei.config.list_ordering_button": "%s [%s]",
   "ordering.rei.ascending": "順序",
   "ordering.rei.descending": "倒序",
   "ordering.rei.registry": "註冊",
@@ -31,7 +31,7 @@
   "ordering.rei.item_groups": "物品分類",
   "text.speed_craft.failed_move_items": "§c不能移動物品!",
   "text.speed_craft.move_items": "移動物品",
-  "text.rei.enable_craftable_only": "過濾不可合成的物品: ",
+  "text.rei.config.enable_craftable_only": "過濾不可合成的物品: ",
   "text.rei.showing_craftable": "顯示可合成的物品",
   "text.rei.showing_all": "顯示所有物品",
   "text.rei.delete_items": "§c刪除物品",

BIN
src/main/resources/assets/roughlyenoughitems/textures/gui/recipecontainer.png


+ 1 - 1
src/main/resources/fabric.mod.json

@@ -2,7 +2,7 @@
   "id": "roughlyenoughitems",
   "name": "RoughlyEnoughItems",
   "description": "To allow players to view items and recipes.",
-  "version": "${version}",
+  "version": "2.3.1",
   "side": "client",
   "authors": [
     "Danielshe"

+ 3 - 1
src/main/resources/roughlyenoughitems.client.json

@@ -1,6 +1,7 @@
 {
   "required": true,
   "package": "me.shedaniel.rei.mixin",
+  "minVersion": "0.7.11",
   "compatibilityLevel": "JAVA_8",
   "mixins": [
     "MixinContainerScreen",
@@ -9,7 +10,8 @@
     "MixinCraftingTableScreen",
     "MixinCreativePlayerInventoryScreen",
     "MixinBrewingRecipeRegistry",
-    "MixinRecipeBookGui"
+    "MixinRecipeBookGui",
+    "MixinTextureManager"
   ],
   "injectors": {
     "defaultRequire": 1