Unknown 6 жил өмнө
parent
commit
537dbb95f6
25 өөрчлөгдсөн 1030 нэмэгдсэн , 39 устгасан
  1. 1 1
      build.gradle
  2. 21 1
      src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java
  3. 18 0
      src/main/java/me/shedaniel/rei/api/IRecipeCategory.java
  4. 24 0
      src/main/java/me/shedaniel/rei/api/IRecipeDisplay.java
  5. 11 0
      src/main/java/me/shedaniel/rei/api/IRecipePlugin.java
  6. 17 3
      src/main/java/me/shedaniel/rei/client/ClientHelper.java
  7. 75 0
      src/main/java/me/shedaniel/rei/client/ConfigManager.java
  8. 22 0
      src/main/java/me/shedaniel/rei/client/REIConfig.java
  9. 21 0
      src/main/java/me/shedaniel/rei/client/REIItemListOrdering.java
  10. 100 0
      src/main/java/me/shedaniel/rei/client/RecipeHelper.java
  11. 36 0
      src/main/java/me/shedaniel/rei/client/SearchArgument.java
  12. 81 24
      src/main/java/me/shedaniel/rei/gui/ContainerGuiOverlay.java
  13. 115 0
      src/main/java/me/shedaniel/rei/gui/widget/ButtonWidget.java
  14. 109 8
      src/main/java/me/shedaniel/rei/gui/widget/ItemListOverlay.java
  15. 161 0
      src/main/java/me/shedaniel/rei/gui/widget/RecipeViewingWidget.java
  16. 9 0
      src/main/java/me/shedaniel/rei/listeners/RecipeSync.java
  17. 26 0
      src/main/java/me/shedaniel/rei/mixin/MixinClientPlayNetworkHandler.java
  18. 22 1
      src/main/java/me/shedaniel/rei/mixin/MixinContainerGui.java
  19. 26 0
      src/main/java/me/shedaniel/rei/plugin/DefaultCraftingCategory.java
  20. 15 0
      src/main/java/me/shedaniel/rei/plugin/DefaultCraftingDisplay.java
  21. 28 0
      src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java
  22. 45 0
      src/main/java/me/shedaniel/rei/plugin/DefaultShapedDisplay.java
  23. 45 0
      src/main/java/me/shedaniel/rei/plugin/DefaultShapelessDisplay.java
  24. BIN
      src/main/resources/assets/roughlyenoughitems/textures/gui/recipecontainer.png
  25. 2 1
      src/main/resources/roughlyenoughitems.client.json

+ 1 - 1
build.gradle

@@ -6,7 +6,7 @@ sourceCompatibility = 1.8
 targetCompatibility = 1.8
 
 archivesBaseName = "RoughlyEnoughItems"
-version = "2.0-24"
+version = "2.0.0.25"
 
 minecraft {
 }

+ 21 - 1
src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java

@@ -1,14 +1,17 @@
 package me.shedaniel.rei;
 
+import me.shedaniel.rei.api.IRecipePlugin;
 import me.shedaniel.rei.client.ClientHelper;
+import me.shedaniel.rei.client.ConfigManager;
+import me.shedaniel.rei.client.RecipeHelper;
 import me.shedaniel.rei.listeners.ClientTick;
 import me.shedaniel.rei.listeners.IListener;
+import me.shedaniel.rei.plugin.DefaultPlugin;
 import net.fabricmc.api.ClientModInitializer;
 import net.fabricmc.api.ModInitializer;
 import net.fabricmc.fabric.events.client.ClientTickEvent;
 import net.fabricmc.fabric.networking.CustomPayloadPacketRegistry;
 import net.minecraft.item.ItemStack;
-import net.minecraft.server.network.ServerPlayNetworkHandler;
 import net.minecraft.server.network.ServerPlayerEntity;
 import net.minecraft.sortme.ChatMessageType;
 import net.minecraft.text.TranslatableTextComponent;
@@ -26,6 +29,7 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer, ModInitiali
     public static final Identifier DELETE_ITEMS_PACKET = new Identifier("roughlyenoughitems", "deleteitem");
     public static final Identifier CREATE_ITEMS_PACKET = new Identifier("roughlyenoughitems", "createitem");
     private static final List<IListener> listeners = new ArrayList<>();
+    private static ConfigManager configManager;
     
     public static <T> List<T> getListeners(Class<T> listenerClass) {
         return listeners.stream().filter(listener -> {
@@ -35,14 +39,25 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer, ModInitiali
         }).collect(Collectors.toList());
     }
     
+    public static ConfigManager getConfigManager() {
+        return configManager;
+    }
+    
     @Override
     public void onInitializeClient() {
         registerREIListeners();
         registerFabricEvents();
+        registerDefaultPlugin();
+        configManager = new ConfigManager();
+    }
+    
+    private void registerDefaultPlugin() {
+        registerPlugin(new DefaultPlugin());
     }
     
     private void registerREIListeners() {
         registerListener(new ClientHelper());
+        registerListener(new RecipeHelper());
     }
     
     private IListener registerListener(IListener listener) {
@@ -50,6 +65,11 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer, ModInitiali
         return listener;
     }
     
+    private IRecipePlugin registerPlugin(IRecipePlugin plugin) {
+        registerListener(plugin);
+        return plugin;
+    }
+    
     private boolean removeListener(IListener listener) {
         if (!listeners.contains(listener))
             return false;

+ 18 - 0
src/main/java/me/shedaniel/rei/api/IRecipeCategory.java

@@ -0,0 +1,18 @@
+package me.shedaniel.rei.api;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
+
+public interface IRecipeCategory<T extends IRecipeDisplay> {
+    
+    public Identifier getIdentifier();
+    
+    public ItemStack getCategoryIcon();
+    
+    public String getCategoryName();
+    
+    default public boolean usesFullPage() {
+        return false;
+    }
+    
+}

+ 24 - 0
src/main/java/me/shedaniel/rei/api/IRecipeDisplay.java

@@ -0,0 +1,24 @@
+package me.shedaniel.rei.api;
+
+import com.google.common.collect.Lists;
+import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.Recipe;
+import net.minecraft.util.Identifier;
+
+import java.util.List;
+
+public interface IRecipeDisplay<T extends Recipe> {
+    
+    public abstract T getRecipe();
+    
+    public List<List<ItemStack>> getInput();
+    
+    public List<ItemStack> getOutput();
+    
+    default public List<List<ItemStack>> getRequiredItems() {
+        return Lists.newArrayList();
+    }
+    
+    public Identifier getRecipeCategory();
+    
+}

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

@@ -0,0 +1,11 @@
+package me.shedaniel.rei.api;
+
+import me.shedaniel.rei.listeners.IListener;
+
+public interface IRecipePlugin extends IListener {
+    
+    public void registerPluginCategories();
+    
+    public void registerRecipes();
+    
+}

+ 17 - 3
src/main/java/me/shedaniel/rei/client/ClientHelper.java

@@ -3,7 +3,12 @@ package me.shedaniel.rei.client;
 import com.google.common.collect.Lists;
 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.gui.ContainerGuiOverlay;
+import me.shedaniel.rei.gui.widget.RecipeViewingWidget;
 import me.shedaniel.rei.listeners.ClientLoaded;
+import me.shedaniel.rei.listeners.IMixinContainerGui;
 import net.fabricmc.api.ClientModInitializer;
 import net.fabricmc.loader.FabricLoader;
 import net.minecraft.client.MinecraftClient;
@@ -19,9 +24,11 @@ import net.minecraft.util.PacketByteBuf;
 import net.minecraft.util.registry.Registry;
 
 import java.awt.*;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 public class ClientHelper implements ClientLoaded, ClientModInitializer {
     
@@ -69,6 +76,10 @@ public class ClientHelper implements ClientLoaded, ClientModInitializer {
         return cheating;
     }
     
+    public static void setCheating(boolean cheating) {
+        ClientHelper.cheating = cheating;
+    }
+    
     public static void sendDeletePacket() {
         PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
         MinecraftClient.getInstance().getNetworkHandler().sendPacket(new CustomPayloadServerPacket(RoughlyEnoughItemsCore.DELETE_ITEMS_PACKET, buf));
@@ -85,8 +96,11 @@ public class ClientHelper implements ClientLoaded, ClientModInitializer {
         }
     }
     
-    public static boolean executeRecipeKeyBind() {
-        return false;
+    public static boolean executeRecipeKeyBind(ContainerGuiOverlay overlay, ItemStack stack, IMixinContainerGui parent) {
+        Map<IRecipeCategory, List<IRecipeDisplay>> map = RecipeHelper.getRecipesFor(stack);
+        if (map.keySet().size() > 0)
+            MinecraftClient.getInstance().openGui(new RecipeViewingWidget(overlay, MinecraftClient.getInstance().window, parent, map));
+        return map.keySet().size() > 0;
     }
     
     public static boolean executeUsageKeyBind() {
@@ -118,7 +132,7 @@ public class ClientHelper implements ClientLoaded, ClientModInitializer {
     
     @Override
     public void onInitializeClient() {
-        this.cheating = true;
+        this.cheating = false;
     }
     
 }

+ 75 - 0
src/main/java/me/shedaniel/rei/client/ConfigManager.java

@@ -0,0 +1,75 @@
+package me.shedaniel.rei.client;
+
+import me.shedaniel.rei.RoughlyEnoughItemsCore;
+import net.fabricmc.loader.FabricLoader;
+import org.apache.logging.log4j.core.Core;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+
+public class ConfigManager {
+    
+    private final File configFile;
+    private REIConfig config;
+    private boolean craftableOnly;
+    
+    public ConfigManager() {
+        this.configFile = new File(FabricLoader.INSTANCE.getConfigDirectory(), "rei.json");
+        this.craftableOnly = false;
+        try {
+            loadConfig();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+    
+    public void saveConfig() throws IOException {
+        configFile.getParentFile().mkdirs();
+        if (!configFile.exists() && !configFile.createNewFile()) {
+            RoughlyEnoughItemsCore.LOGGER.error("REI: Failed to save config! Overwriting with default config.");
+            config = new REIConfig();
+            return;
+        }
+        FileWriter writer = new FileWriter(configFile, false);
+        try {
+            REIConfig.GSON.toJson(config, writer);
+        } finally {
+            writer.close();
+        }
+    }
+    
+    public void loadConfig() throws IOException {
+        if (!configFile.exists() || !configFile.canRead()) {
+            config = new REIConfig();
+            saveConfig();
+            return;
+        }
+        boolean failed = false;
+        try {
+            config = REIConfig.GSON.fromJson(new InputStreamReader(Files.newInputStream(configFile.toPath())), REIConfig.class);
+        } catch (Exception e) {
+            failed = true;
+        }
+        if (failed || config == null) {
+            RoughlyEnoughItemsCore.LOGGER.error("REI: Failed to load config! Overwriting with default config.");
+            config = new REIConfig();
+        }
+        saveConfig();
+    }
+    
+    public REIItemListOrdering getItemListOrdering() {
+        return config.itemListOrdering;
+    }
+    
+    public boolean isAscending() {
+        return config.isAscending;
+    }
+    
+    public boolean craftableOnly() {
+        return craftableOnly && config.enableCraftableOnlyButton;
+    }
+    
+}

+ 22 - 0
src/main/java/me/shedaniel/rei/client/REIConfig.java

@@ -0,0 +1,22 @@
+package me.shedaniel.rei.client;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import java.awt.event.KeyEvent;
+
+public class REIConfig {
+    
+    public static Gson GSON = new GsonBuilder()
+            .setPrettyPrinting()
+            .create();
+    
+    public int recipeKeyBind = KeyEvent.VK_R;
+    public int usageKeyBind = KeyEvent.VK_U;
+    public int hideKeyBind = KeyEvent.VK_O;
+    public boolean centreSearchBox = false;
+    public REIItemListOrdering itemListOrdering = REIItemListOrdering.REGISTRY;
+    public boolean isAscending = true;
+    public boolean enableCraftableOnlyButton = true;
+    
+}

+ 21 - 0
src/main/java/me/shedaniel/rei/client/REIItemListOrdering.java

@@ -0,0 +1,21 @@
+package me.shedaniel.rei.client;
+
+import com.google.gson.annotations.SerializedName;
+
+public enum REIItemListOrdering {
+    
+    @SerializedName("registry") REGISTRY("ordering.rei.registry"),
+    @SerializedName("name") NAME("ordering.rei.name"),
+    @SerializedName("item_groups") ITEM_GROUPS("ordering.rei.item_groups");
+    
+    private String nameTranslationKey;
+    
+    REIItemListOrdering(String nameTranslationKey) {
+        this.nameTranslationKey = nameTranslationKey;
+    }
+    
+    public String getNameTranslationKey() {
+        return nameTranslationKey;
+    }
+    
+}

+ 100 - 0
src/main/java/me/shedaniel/rei/client/RecipeHelper.java

@@ -0,0 +1,100 @@
+package me.shedaniel.rei.client;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import me.shedaniel.rei.RoughlyEnoughItemsCore;
+import me.shedaniel.rei.api.IRecipeCategory;
+import me.shedaniel.rei.api.IRecipeDisplay;
+import me.shedaniel.rei.api.IRecipePlugin;
+import me.shedaniel.rei.listeners.RecipeSync;
+import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.RecipeManager;
+import net.minecraft.util.Identifier;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class RecipeHelper implements RecipeSync {
+    
+    private static Map<Identifier, List<IRecipeDisplay>> recipeCategoryListMap;
+    private static List<IRecipeCategory> categories;
+    private static RecipeManager recipeManager;
+    
+    public RecipeHelper() {
+        this.recipeCategoryListMap = Maps.newHashMap();
+        this.categories = Lists.newArrayList();
+    }
+    
+    public static List<ItemStack> findCraftableByItems(List<ItemStack> inventoryItems) {
+        List<ItemStack> craftables = new ArrayList<>();
+        for(List<IRecipeDisplay> value : recipeCategoryListMap.values())
+            for(IRecipeDisplay recipeDisplay : value) {
+                int slotsCraftable = 0;
+                List<List<ItemStack>> requiredInput = (List<List<ItemStack>>) recipeDisplay.getRequiredItems();
+                for(List<ItemStack> slot : requiredInput) {
+                    if (slot.isEmpty()) {
+                        slotsCraftable++;
+                        continue;
+                    }
+                    boolean slotDone = false;
+                    for(ItemStack possibleType : inventoryItems) {
+                        for(ItemStack slotPossible : slot)
+                            if (ItemStack.areEqualIgnoreTags(slotPossible, possibleType)) {
+                                slotsCraftable++;
+                                slotDone = true;
+                                break;
+                            }
+                        if (slotDone)
+                            break;
+                    }
+                }
+                if (slotsCraftable == recipeDisplay.getRequiredItems().size())
+                    craftables.addAll((List<ItemStack>) recipeDisplay.getOutput());
+            }
+        return craftables.stream().distinct().collect(Collectors.toList());
+    }
+    
+    public static void registerCategory(IRecipeCategory category) {
+        categories.add(category);
+        recipeCategoryListMap.put(category.getIdentifier(), Lists.newArrayList());
+    }
+    
+    public static void registerRecipe(Identifier categoryIdentifier, IRecipeDisplay display) {
+        if (!recipeCategoryListMap.containsKey(categoryIdentifier))
+            return;
+        recipeCategoryListMap.get(categoryIdentifier).add(display);
+    }
+    
+    public static Map<IRecipeCategory, List<IRecipeDisplay>> getRecipesFor(ItemStack stack) {
+        Map<Identifier, List<IRecipeDisplay>> categoriesMap = new HashMap<>();
+        categories.forEach(f -> categoriesMap.put(f.getIdentifier(), new LinkedList<>()));
+        for(List<IRecipeDisplay> value : recipeCategoryListMap.values())
+            for(IRecipeDisplay recipeDisplay : value)
+                for(ItemStack outputStack : (List<ItemStack>) recipeDisplay.getOutput())
+                    if (ItemStack.areEqualIgnoreTags(stack, outputStack))
+                        categoriesMap.get(recipeDisplay.getRecipeCategory()).add(recipeDisplay);
+        categoriesMap.keySet().removeIf(f -> categoriesMap.get(f).isEmpty());
+        Map<IRecipeCategory, List<IRecipeDisplay>> recipeCategoryListMap = Maps.newHashMap();
+        categories.forEach(category -> {
+            if (categoriesMap.containsKey(category.getIdentifier()))
+                recipeCategoryListMap.put(category, categoriesMap.get(category.getIdentifier()));
+        });
+        return recipeCategoryListMap;
+    }
+    
+    public static RecipeManager getRecipeManager() {
+        return recipeManager;
+    }
+    
+    @Override
+    public void recipesLoaded(RecipeManager recipeManager) {
+        this.recipeManager = recipeManager;
+        this.recipeCategoryListMap.clear();
+        this.categories.clear();
+        RoughlyEnoughItemsCore.getListeners(IRecipePlugin.class).forEach(plugin -> {
+            plugin.registerPluginCategories();
+            plugin.registerRecipes();
+        });
+    }
+    
+}

+ 36 - 0
src/main/java/me/shedaniel/rei/client/SearchArgument.java

@@ -0,0 +1,36 @@
+package me.shedaniel.rei.client;
+
+public class SearchArgument {
+    
+    public enum ArgumentType {
+        TEXT, MOD, TOOLTIP
+    }
+    
+    private ArgumentType argumentType;
+    private String text;
+    private boolean include;
+    
+    public SearchArgument(ArgumentType argumentType, String text, boolean include) {
+        this.argumentType = argumentType;
+        this.text = text;
+        this.include = include;
+    }
+    
+    public ArgumentType getArgumentType() {
+        return argumentType;
+    }
+    
+    public String getText() {
+        return text;
+    }
+    
+    public boolean isInclude() {
+        return include;
+    }
+    
+    @Override
+    public String toString() {
+        return String.format("Argument[%s]: name = %s, include = %b", argumentType.name(), text, include);
+    }
+    
+}

+ 81 - 24
src/main/java/me/shedaniel/rei/gui/ContainerGuiOverlay.java

@@ -1,16 +1,16 @@
 package me.shedaniel.rei.gui;
 
-import me.shedaniel.rei.RoughlyEnoughItemsCore;
+import com.mojang.blaze3d.platform.GlStateManager;
 import me.shedaniel.rei.client.ClientHelper;
-import me.shedaniel.rei.gui.widget.IWidget;
-import me.shedaniel.rei.gui.widget.ItemListOverlay;
-import me.shedaniel.rei.gui.widget.LabelWidget;
-import me.shedaniel.rei.gui.widget.QueuedTooltip;
+import me.shedaniel.rei.gui.widget.*;
 import me.shedaniel.rei.listeners.IMixinContainerGui;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.ContainerGui;
 import net.minecraft.client.gui.Gui;
-import net.minecraft.client.gui.widget.ButtonWidget;
+import net.minecraft.client.gui.GuiEventListener;
+import net.minecraft.client.gui.widget.TextFieldWidget;
+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;
 
@@ -20,14 +20,15 @@ import java.util.List;
 
 public class ContainerGuiOverlay extends Gui {
     
+    private static int page = 0;
+    private final List<IWidget> widgets;
+    private final List<QueuedTooltip> queuedTooltips;
     private Rectangle rectangle;
     private IMixinContainerGui containerGui;
     private Window window;
-    private static int page = 0;
-    private final List<IWidget> widgets;
     private ItemListOverlay itemListOverlay;
     private ButtonWidget buttonLeft, buttonRight;
-    private final List<QueuedTooltip> queuedTooltips;
+    private TextFieldWidget searchField;
     
     public ContainerGuiOverlay(ContainerGui containerGui) {
         this.queuedTooltips = new ArrayList<>();
@@ -36,6 +37,7 @@ public class ContainerGuiOverlay extends Gui {
     }
     
     public void onInitialized() {
+        String searchTerm = searchField != null ? searchField.getText() : "";
         //Update Variables
         this.widgets.clear();
         this.window = MinecraftClient.getInstance().window;
@@ -44,23 +46,36 @@ public class ContainerGuiOverlay extends Gui {
         this.rectangle = calculateBoundary();
         widgets.add(this.itemListOverlay = new ItemListOverlay(this, containerGui, page));
         
-        this.itemListOverlay.updateList(getItemListArea(), page);
-        addButton(buttonLeft = new ButtonWidget(-1, rectangle.x, rectangle.y + 3, 16, 20, "<") {
+        this.itemListOverlay.updateList(getItemListArea(), page, searchTerm);
+        widgets.add(buttonLeft = new ButtonWidget(rectangle.x, rectangle.y + 5, 16, 16, "<") {
             @Override
-            public void onPressed(double double_1, double double_2) {
+            public void onPressed(int button, double mouseX, double mouseY) {
                 page--;
                 if (page < 0)
                     page = getTotalPage();
-                itemListOverlay.updateList(getItemListArea(), page);
+                itemListOverlay.updateList(getItemListArea(), page, searchField.getText());
             }
         });
-        addButton(buttonRight = new ButtonWidget(-1, rectangle.x + rectangle.width - 18, rectangle.y + 3, 16, 20, ">") {
+        widgets.add(buttonRight = new ButtonWidget(rectangle.x + rectangle.width - 18, rectangle.y + 5, 16, 16, ">") {
             @Override
-            public void onPressed(double double_1, double double_2) {
+            public void onPressed(int button, double mouseX, double mouseY) {
                 page++;
                 if (page > getTotalPage())
                     page = 0;
-                itemListOverlay.updateList(getItemListArea(), page);
+                itemListOverlay.updateList(getItemListArea(), page, searchField.getText());
+            }
+        });
+        page = MathHelper.clamp(page, 0, getTotalPage());
+        widgets.add(new ButtonWidget(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());
             }
         });
         this.widgets.add(new LabelWidget(rectangle.x + (rectangle.width / 2), rectangle.y + 10, "") {
@@ -70,10 +85,29 @@ public class ContainerGuiOverlay extends Gui {
                 super.draw(mouseX, mouseY, partialTicks);
             }
         });
+        Rectangle textFieldArea = getTextFieldArea();
+        this.listeners.add(searchField = new TextFieldWidget(-1, MinecraftClient.getInstance().fontRenderer,
+                (int) textFieldArea.getX(), (int) textFieldArea.getY(), (int) textFieldArea.getWidth(), (int) textFieldArea.getHeight()));
+        searchField.setChangedListener((id, text) -> {
+            itemListOverlay.updateList(page, text);
+        });
+        searchField.setText(searchTerm);
         
         this.listeners.addAll(widgets);
     }
     
+    private Rectangle getTextFieldArea() {
+        if (MinecraftClient.getInstance().currentGui instanceof RecipeViewingWidget) {
+            RecipeViewingWidget widget = (RecipeViewingWidget) MinecraftClient.getInstance().currentGui;
+            return new Rectangle(widget.getBounds().x, window.getScaledHeight() - 22, widget.getBounds().width, 18);
+        }
+        return new Rectangle(containerGui.getContainerLeft(), window.getScaledHeight() - 22, containerGui.getContainerWidth(), 18);
+    }
+    
+    private String getCheatModeText() {
+        return I18n.translate(String.format("%s%s", "text.rei.", ClientHelper.isCheating() ? "cheat" : "nocheat"));
+    }
+    
     private Rectangle getItemListArea() {
         return new Rectangle(rectangle.x + 2, rectangle.y + 24, rectangle.width - 4, rectangle.height - 27);
     }
@@ -83,9 +117,14 @@ public class ContainerGuiOverlay extends Gui {
     }
     
     public void render(int mouseX, int mouseY, float partialTicks) {
-        draw(mouseX, mouseY, partialTicks);
+        GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
+        GuiLighting.disable();
+        this.draw(mouseX, mouseY, partialTicks);
+        GuiLighting.disable();
         queuedTooltips.forEach(queuedTooltip -> containerGui.getContainerGui().drawTooltip(queuedTooltip.text, queuedTooltip.mouse.x, queuedTooltip.mouse.y));
         queuedTooltips.clear();
+        GuiLighting.disable();
+        searchField.render(mouseX, mouseY, partialTicks);
     }
     
     public void addTooltip(QueuedTooltip queuedTooltip) {
@@ -94,28 +133,37 @@ public class ContainerGuiOverlay extends Gui {
     
     @Override
     public void draw(int int_1, int int_2, float float_1) {
-        widgets.forEach(widget -> widget.draw(int_1, int_2, float_1));
+        widgets.forEach(widget -> {
+            GuiLighting.disable();
+            widget.draw(int_1, int_2, float_1);
+        });
+        GuiLighting.disable();
         itemListOverlay.draw(int_1, int_2, float_1);
+        GuiLighting.disable();
         super.draw(int_1, int_2, float_1);
     }
     
     private Rectangle calculateBoundary() {
         int startX = containerGui.getContainerLeft() + containerGui.getContainerWidth() + 10;
         int width = window.getScaledWidth() - startX;
+        if (MinecraftClient.getInstance().currentGui instanceof RecipeViewingWidget) {
+            RecipeViewingWidget widget = (RecipeViewingWidget) MinecraftClient.getInstance().currentGui;
+            width = window.getScaledWidth() - (widget.getBounds().x + widget.getBounds().width + 10);
+        }
         return new Rectangle(startX, 0, width, window.getScaledHeight());
     }
     
     private int getTotalPage() {
-        return MathHelper.ceil(ClientHelper.getItemList().size() / itemListOverlay.getTotalSlotsPerPage());
+        return MathHelper.ceil(itemListOverlay.getCurrentDisplayed().size() / itemListOverlay.getTotalSlotsPerPage());
     }
     
     @Override
     public boolean mouseScrolled(double amount) {
         if (rectangle.contains(ClientHelper.getMouseLocation())) {
             if (amount > 0 && buttonLeft.enabled)
-                buttonLeft.onPressed(0, 0);
+                buttonLeft.onPressed(0, 0, 0);
             else if (amount < 0 && buttonRight.enabled)
-                buttonRight.onPressed(0, 0);
+                buttonRight.onPressed(0, 0, 0);
             else return false;
             return true;
         }
@@ -126,10 +174,19 @@ public class ContainerGuiOverlay extends Gui {
     }
     
     @Override
-    public boolean mouseClicked(double double_1, double double_2, int int_1) {
-        for(IWidget widget : widgets)
-            if (widget.mouseClicked(double_1, double_2, int_1))
+    public boolean keyPressed(int int_1, int int_2, int int_3) {
+        for(GuiEventListener listener : listeners)
+            if (listener.keyPressed(int_1, int_2, int_3))
                 return true;
         return false;
     }
+    
+    @Override
+    public boolean charTyped(char char_1, int int_1) {
+        for(GuiEventListener listener : listeners)
+            if (listener.charTyped(char_1, int_1))
+                return true;
+        return super.charTyped(char_1, int_1);
+    }
+    
 }

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

@@ -0,0 +1,115 @@
+package me.shedaniel.rei.gui.widget;
+
+import com.mojang.blaze3d.platform.GlStateManager;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.audio.PositionedSoundInstance;
+import net.minecraft.client.font.FontRenderer;
+import net.minecraft.client.gui.Drawable;
+import net.minecraft.client.render.GuiLighting;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.math.MathHelper;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class ButtonWidget extends Drawable implements IWidget {
+    
+    protected static final Identifier WIDGET_TEX = new Identifier("textures/gui/widgets.png");
+    public int x;
+    public int y;
+    public String text;
+    public boolean enabled;
+    public boolean visible;
+    protected int width;
+    protected int height;
+    protected boolean hovered;
+    private boolean pressed;
+    private Rectangle bounds;
+    
+    public ButtonWidget(int int_2, int int_3, int int_4, int int_5, String string_1) {
+        this.width = 200;
+        this.height = 20;
+        this.enabled = true;
+        this.visible = true;
+        this.x = int_2;
+        this.y = int_3;
+        this.width = int_4;
+        this.height = int_5;
+        this.text = string_1;
+        this.bounds = new Rectangle(x, y, width, height);
+    }
+    
+    public Rectangle getBounds() {
+        return bounds;
+    }
+    
+    protected int getTextureId(boolean boolean_1) {
+        int int_1 = 1;
+        if (!this.enabled) {
+            int_1 = 0;
+        } else if (boolean_1) {
+            int_1 = 2;
+        }
+        
+        return int_1;
+    }
+    
+    @Override
+    public List<IWidget> getListeners() {
+        return new ArrayList<>();
+    }
+    
+    @Override
+    public void draw(int mouseX, int mouseY, float partialTicks) {
+        
+        if (this.visible) {
+            MinecraftClient minecraftClient_1 = MinecraftClient.getInstance();
+            FontRenderer fontRenderer_1 = minecraftClient_1.fontRenderer;
+            minecraftClient_1.getTextureManager().bindTexture(WIDGET_TEX);
+            GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
+            this.hovered = bounds.contains(mouseX, mouseY);
+            int textureOffset = this.getTextureId(this.hovered);
+            GlStateManager.enableBlend();
+            GlStateManager.blendFuncSeparate(GlStateManager.SrcBlendFactor.SRC_ALPHA, GlStateManager.DstBlendFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SrcBlendFactor.ONE, GlStateManager.DstBlendFactor.ZERO);
+            GlStateManager.blendFunc(GlStateManager.SrcBlendFactor.SRC_ALPHA, GlStateManager.DstBlendFactor.ONE_MINUS_SRC_ALPHA);
+            //Four Corners
+            this.drawTexturedRect(this.x, this.y, 0, 46 + textureOffset * 20, 4, 4);
+            this.drawTexturedRect(this.x + this.width - 4, this.y, 196, 46 + textureOffset * 20, 4, 4);
+            this.drawTexturedRect(this.x, this.y + this.height - 4, 0, 62 + textureOffset * 20, 4, 4);
+            this.drawTexturedRect(this.x + this.width - 4, this.y + this.height - 4, 196, 62 + textureOffset * 20, 4, 4);
+            
+            //Sides
+            this.drawTexturedRect(this.x + 4, this.y, 4, 46 + textureOffset * 20, this.width - 8, 4);
+            this.drawTexturedRect(this.x + 4, this.y + this.height - 4, 4, 62 + textureOffset * 20, this.width - 8, 4);
+            
+            for(int i = this.y + 4; i < this.y + this.height - 4; i += 4) {
+                this.drawTexturedRect(this.x, i, 0, 50 + textureOffset * 20, this.width / 2, MathHelper.clamp(this.y + this.height - 4 - i, 0, 4));
+                this.drawTexturedRect(this.x + this.width / 2, i, 200 - this.width / 2, 50 + textureOffset * 20, this.width / 2, MathHelper.clamp(this.y + this.height - 4 - i, 0, 4));
+            }
+            
+            int colour = 14737632;
+            if (!this.enabled) {
+                colour = 10526880;
+            } else if (this.hovered) {
+                colour = 16777120;
+            }
+            
+            this.drawStringCentered(fontRenderer_1, this.text, this.x + this.width / 2, this.y + (this.height - 8) / 2, colour);
+        }
+    }
+    
+    @Override
+    public boolean onMouseClick(int button, double mouseX, double mouseY) {
+        if (bounds.contains(mouseX, mouseY)) {
+            MinecraftClient.getInstance().getSoundLoader().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+            onPressed(button, mouseX, mouseY);
+            return true;
+        }
+        return false;
+    }
+    
+    public abstract void onPressed(int button, double mouseX, double mouseY);
+    
+}

+ 109 - 8
src/main/java/me/shedaniel/rei/gui/widget/ItemListOverlay.java

@@ -3,19 +3,27 @@ package me.shedaniel.rei.gui.widget;
 import com.google.common.collect.Lists;
 import me.shedaniel.rei.RoughlyEnoughItemsCore;
 import me.shedaniel.rei.client.ClientHelper;
+import me.shedaniel.rei.client.REIItemListOrdering;
+import me.shedaniel.rei.client.RecipeHelper;
+import me.shedaniel.rei.client.SearchArgument;
 import me.shedaniel.rei.gui.ContainerGuiOverlay;
 import me.shedaniel.rei.listeners.ClientLoaded;
 import me.shedaniel.rei.listeners.IMixinContainerGui;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.Drawable;
+import net.minecraft.client.item.TooltipOptions;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.resource.language.I18n;
+import net.minecraft.item.ItemGroup;
 import net.minecraft.item.ItemStack;
+import net.minecraft.text.TextComponent;
 import net.minecraft.util.math.MathHelper;
 
 import java.awt.*;
-import java.util.ArrayList;
+import java.util.*;
 import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public class ItemListOverlay extends Drawable implements IWidget {
     
@@ -24,8 +32,10 @@ public class ItemListOverlay extends Drawable implements IWidget {
     private List<IWidget> widgets = new ArrayList<>();
     private int width, height, page;
     private Rectangle rectangle;
+    private List<ItemStack> currentDisplayed;
     
     public ItemListOverlay(ContainerGuiOverlay containerGuiOverlay, IMixinContainerGui containerGui, int page) {
+        this.currentDisplayed = Lists.newArrayList();
         this.containerGuiOverlay = containerGuiOverlay;
         this.containerGui = containerGui;
         this.width = 0;
@@ -42,15 +52,15 @@ public class ItemListOverlay extends Drawable implements IWidget {
         widgets.forEach(widget -> widget.draw(int_1, int_2, float_1));
     }
     
-    public void updateList(int page) {
-        updateList(rectangle, page);
+    public void updateList(int page, String searchTerm) {
+        updateList(rectangle, page, searchTerm);
     }
     
-    public void updateList(Rectangle rect, int page) {
+    public void updateList(Rectangle rect, int page, String searchTerm) {
         this.rectangle = rect;
         if (ClientHelper.getItemList().isEmpty())
             RoughlyEnoughItemsCore.getListeners(ClientLoaded.class).forEach(ClientLoaded::clientLoaded);
-        List<ItemStack> stacks = ClientHelper.getItemList();
+        currentDisplayed = processSearchTerm(searchTerm, ClientHelper.getItemList(), Lists.newArrayList());
         this.widgets.clear();
         this.page = page;
         calculateListSize(rect);
@@ -58,10 +68,10 @@ public class ItemListOverlay extends Drawable implements IWidget {
         double startY = rect.getCenterY() - height * 9;
         for(int i = 0; i < getTotalSlotsPerPage(); i++) {
             int j = i + page * getTotalSlotsPerPage();
-            if (j >= stacks.size())
+            if (j >= currentDisplayed.size())
                 break;
             widgets.add(new ItemSlotWidget((int) (startX + (i % width) * 18), (int) (startY + MathHelper.floor(i / width) * 18),
-                    stacks.get(j), false, true, containerGui) {
+                    currentDisplayed.get(j), false, true, containerGui) {
                 @Override
                 protected List<String> getTooltip(ItemStack itemStack) {
                     if (!ClientHelper.isCheating() || MinecraftClient.getInstance().player.inventory.getCursorStack().isEmpty())
@@ -89,7 +99,7 @@ public class ItemListOverlay extends Drawable implements IWidget {
                             }
                         } else {
                             if (button == 0)
-                                return ClientHelper.executeRecipeKeyBind();
+                                return ClientHelper.executeRecipeKeyBind(containerGuiOverlay, getCurrentStack().copy(), containerGui);
                             else if (button == 1)
                                 return ClientHelper.executeUsageKeyBind();
                         }
@@ -100,6 +110,97 @@ public class ItemListOverlay extends Drawable implements IWidget {
         }
     }
     
+    public List<ItemStack> getCurrentDisplayed() {
+        return currentDisplayed;
+    }
+    
+    private List<ItemStack> processSearchTerm(String searchTerm, List<ItemStack> ol, List<ItemStack> inventoryItems) {
+        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.getConfigManager().getItemListOrdering();
+        if (ordering != REIItemListOrdering.REGISTRY)
+            Collections.sort(os, (itemStack, t1) -> {
+                if (ordering.equals(REIItemListOrdering.NAME))
+                    return itemStack.getDisplayName().getFormattedText().compareToIgnoreCase(t1.getDisplayName().getFormattedText());
+                if (ordering.equals(REIItemListOrdering.ITEM_GROUPS))
+                    return itemGroups.indexOf(itemStack.getItem().getItemGroup()) - itemGroups.indexOf(t1.getItem().getItemGroup());
+                return 0;
+            });
+        if (!RoughlyEnoughItemsCore.getConfigManager().isAscending())
+            Collections.reverse(os);
+        Arrays.stream(searchTerm.split("\\|")).forEachOrdered(s -> {
+            List<SearchArgument> arguments = new ArrayList<>();
+            while (s.startsWith(" ")) s = s.substring(1);
+            while (s.endsWith(" ")) s = s.substring(0, s.length());
+            if (s.startsWith("@-") || s.startsWith("-@"))
+                arguments.add(new SearchArgument(SearchArgument.ArgumentType.MOD, s.substring(2), false));
+            else if (s.startsWith("@"))
+                arguments.add(new SearchArgument(SearchArgument.ArgumentType.MOD, s.substring(1), true));
+            else if (s.startsWith("#-") || s.startsWith("-#"))
+                arguments.add(new SearchArgument(SearchArgument.ArgumentType.TOOLTIP, s.substring(2), false));
+            else if (s.startsWith("#"))
+                arguments.add(new SearchArgument(SearchArgument.ArgumentType.TOOLTIP, s.substring(1), true));
+            else if (s.startsWith("-"))
+                arguments.add(new SearchArgument(SearchArgument.ArgumentType.TEXT, s.substring(1), false));
+            else
+                arguments.add(new SearchArgument(SearchArgument.ArgumentType.TEXT, s, true));
+            os.stream().filter(itemStack -> filterItem(itemStack, arguments)).forEachOrdered(stacks::add);
+        });
+        List<ItemStack> workingItems = RoughlyEnoughItemsCore.getConfigManager().craftableOnly() && inventoryItems.size() > 0 ? new ArrayList<>() : new LinkedList<>(ol);
+        if (RoughlyEnoughItemsCore.getConfigManager().craftableOnly()) {
+            RecipeHelper.findCraftableByItems(inventoryItems).forEach(workingItems::add);
+            workingItems.addAll(inventoryItems);
+        }
+        final List<ItemStack> finalWorkingItems = workingItems;
+        finalStacks.addAll(stacks.stream().filter(itemStack -> {
+            if (!RoughlyEnoughItemsCore.getConfigManager().craftableOnly())
+                return true;
+            for(ItemStack workingItem : finalWorkingItems)
+                if (itemStack.isEqualIgnoreTags(workingItem))
+                    return true;
+            return false;
+        }).distinct().collect(Collectors.toList()));
+        return finalStacks;
+    }
+    
+    private boolean filterItem(ItemStack itemStack, List<SearchArgument> arguments) {
+        String mod = ClientHelper.getModFromItemStack(itemStack);
+        List<String> toolTipsList = getStackTooltip(itemStack);
+        String toolTipsMixed = toolTipsList.stream().skip(1).collect(Collectors.joining()).toLowerCase();
+        String allMixed = Stream.of(itemStack.getDisplayName().getString(), toolTipsMixed).collect(Collectors.joining()).toLowerCase();
+        for(SearchArgument searchArgument : arguments.stream().filter(searchArgument -> !searchArgument.isInclude()).collect(Collectors.toList())) {
+            if (searchArgument.getArgumentType().equals(SearchArgument.ArgumentType.MOD))
+                if (mod.toLowerCase().contains(searchArgument.getText().toLowerCase()))
+                    return false;
+            if (searchArgument.getArgumentType().equals(SearchArgument.ArgumentType.TOOLTIP))
+                if (toolTipsMixed.contains(searchArgument.getText().toLowerCase()))
+                    return false;
+            if (searchArgument.getArgumentType().equals(SearchArgument.ArgumentType.TEXT))
+                if (allMixed.contains(searchArgument.getText().toLowerCase()))
+                    return false;
+        }
+        for(SearchArgument searchArgument : arguments.stream().filter(SearchArgument::isInclude).collect(Collectors.toList())) {
+            if (searchArgument.getArgumentType().equals(SearchArgument.ArgumentType.MOD))
+                if (!mod.toLowerCase().contains(searchArgument.getText().toLowerCase()))
+                    return false;
+            if (searchArgument.getArgumentType().equals(SearchArgument.ArgumentType.TOOLTIP))
+                if (!toolTipsMixed.contains(searchArgument.getText().toLowerCase()))
+                    return false;
+            if (searchArgument.getArgumentType().equals(SearchArgument.ArgumentType.TEXT))
+                if (!allMixed.contains(searchArgument.getText().toLowerCase()))
+                    return false;
+        }
+        return true;
+    }
+    
+    private List<String> getStackTooltip(ItemStack itemStack) {
+        MinecraftClient client = MinecraftClient.getInstance();
+        return itemStack.getTooltipText(client.player, client.options.advancedItemTooltips ?
+                TooltipOptions.Instance.ADVANCED : TooltipOptions.Instance.NORMAL).stream().map(
+                        TextComponent::getFormattedText).collect(Collectors.toList());
+    }
+    
     private void calculateListSize(Rectangle rect) {
         int xOffset = 0, yOffset = 0;
         this.width = 0;

+ 161 - 0
src/main/java/me/shedaniel/rei/gui/widget/RecipeViewingWidget.java

@@ -0,0 +1,161 @@
+package me.shedaniel.rei.gui.widget;
+
+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.gui.ContainerGuiOverlay;
+import me.shedaniel.rei.listeners.IMixinContainerGui;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.ContainerGui;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.gui.GuiEventListener;
+import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.util.Window;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.math.MathHelper;
+
+import java.awt.*;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public class RecipeViewingWidget extends Gui {
+    
+    private static final Identifier CREATIVE_INVENTORY_TABS = new Identifier("textures/gui/container/creative_inventory/tabs.png");
+    private static final Identifier CHEST_GUI_TEXTURE = new Identifier("roughlyenoughitems", "textures/gui/recipecontainer.png");
+    public final int guiWidth = 176;
+    public final int guiHeight = 158;
+    
+    private List<IWidget> widgets;
+    private Window window;
+    private Rectangle bounds;
+    private Map<IRecipeCategory, List<IRecipeDisplay>> categoriesMap;
+    private List<IRecipeCategory> categories;
+    private IRecipeCategory selectedCategory;
+    private IMixinContainerGui parent;
+    private ContainerGuiOverlay overlay;
+    private int page;
+    
+    public RecipeViewingWidget(ContainerGuiOverlay overlay, Window window, IMixinContainerGui parent, Map<IRecipeCategory, List<IRecipeDisplay>> categoriesMap) {
+        this.parent = parent;
+        this.window = window;
+        this.widgets = Lists.newArrayList();
+        this.bounds = new Rectangle(window.getScaledWidth() / 2 - guiWidth / 2, window.getScaledHeight() / 2 - guiHeight / 2, guiWidth, guiHeight);
+        this.categoriesMap = categoriesMap;
+        this.categories = new LinkedList<>(categoriesMap.keySet());
+        this.selectedCategory = categories.get(0);
+        this.overlay = overlay;
+    }
+    
+    public ContainerGui getParent() {
+        return parent.getContainerGui();
+    }
+    
+    @Override
+    public boolean keyPressed(int int_1, int int_2, int int_3) {
+        if (int_1 == 256 && this.doesEscapeKeyClose()) {
+            MinecraftClient.getInstance().openGui(parent.getContainerGui());
+            return true;
+        }
+        for(GuiEventListener listener : listeners)
+            if (listener.keyPressed(int_1, int_2, int_3))
+                return true;
+        return super.keyPressed(int_1, int_2, int_3);
+    }
+    
+    @Override
+    protected void onInitialized() {
+        super.onInitialized();
+        this.widgets.clear();
+        this.bounds = new Rectangle(window.getScaledWidth() / 2 - guiWidth / 2, window.getScaledHeight() / 2 - guiHeight / 2, guiWidth, guiHeight);
+        
+        widgets.add(new ButtonWidget((int) bounds.getX() + 5, (int) bounds.getY() + 5, 12, 12, "<") {
+            @Override
+            public void onPressed(int button, double mouseX, double mouseY) {
+            }
+        });
+        widgets.add(new ButtonWidget((int) bounds.getX() + 159, (int) bounds.getY() + 5, 12, 12, ">") {
+            @Override
+            public void onPressed(int button, double mouseX, double mouseY) {
+            }
+        });
+        
+        widgets.add(new ButtonWidget((int) bounds.getX() + 5, (int) bounds.getY() + 21, 12, 12, "<") {
+            @Override
+            public void onPressed(int button, double mouseX, double mouseY) {
+            }
+        });
+        widgets.add(new ButtonWidget((int) bounds.getX() + 159, (int) bounds.getY() + 21, 12, 12, ">") {
+            @Override
+            public void onPressed(int button, double mouseX, double mouseY) {
+            }
+        });
+        widgets.add(new LabelWidget((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);
+            }
+        });
+        widgets.add(new LabelWidget((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);
+            }
+        });
+        overlay.onInitialized();
+        listeners.add(overlay);
+        listeners.addAll(widgets);
+    }
+    
+    @Override
+    public void draw(int mouseX, int mouseY, float partialTicks) {
+        drawBackground();
+        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());
+        
+        GuiLighting.disable();
+        super.draw(mouseX, mouseY, partialTicks);
+        widgets.forEach(widget -> {
+            GuiLighting.disable();
+            widget.draw(mouseX, mouseY, partialTicks);
+        });
+        GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
+        GuiLighting.disable();
+        overlay.render(mouseX, mouseY, partialTicks);
+    }
+    
+    public int getTotalPages(IRecipeCategory category) {
+        if (category.usesFullPage())
+            return categoriesMap.get(category).size();
+        return MathHelper.ceil(categoriesMap.get(category).size() / 2.0);
+    }
+    
+    public Rectangle getBounds() {
+        return bounds;
+    }
+    
+    @Override
+    public boolean charTyped(char char_1, int int_1) {
+        for(GuiEventListener listener : listeners)
+            if (listener.charTyped(char_1, int_1))
+                return true;
+        return super.charTyped(char_1, int_1);
+    }
+    
+    @Override
+    public boolean mouseClicked(double double_1, double double_2, int int_1) {
+        for(GuiEventListener entry : getEntries())
+            if (entry.mouseClicked(double_1, double_2, int_1)) {
+                focusOn(entry);
+                if (int_1 == 0)
+                    setActive(true);
+                return true;
+            }
+        return false;
+    }
+}

+ 9 - 0
src/main/java/me/shedaniel/rei/listeners/RecipeSync.java

@@ -0,0 +1,9 @@
+package me.shedaniel.rei.listeners;
+
+import net.minecraft.recipe.RecipeManager;
+
+public interface RecipeSync extends IListener {
+    
+    public void recipesLoaded(RecipeManager recipeManager);
+    
+}

+ 26 - 0
src/main/java/me/shedaniel/rei/mixin/MixinClientPlayNetworkHandler.java

@@ -0,0 +1,26 @@
+package me.shedaniel.rei.mixin;
+
+import me.shedaniel.rei.RoughlyEnoughItemsCore;
+import me.shedaniel.rei.listeners.RecipeSync;
+import net.minecraft.client.network.ClientPlayNetworkHandler;
+import net.minecraft.client.network.packet.SynchronizeRecipesClientPacket;
+import net.minecraft.recipe.RecipeManager;
+import org.apache.logging.log4j.core.Core;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(ClientPlayNetworkHandler.class)
+public class MixinClientPlayNetworkHandler {
+    
+    @Shadow @Final private RecipeManager recipeManager;
+    
+    @Inject(method = "onSynchronizeRecipes", at = @At("RETURN"))
+    private void onUpdateRecipes(SynchronizeRecipesClientPacket packetIn, CallbackInfo ci) {
+        RoughlyEnoughItemsCore.getListeners(RecipeSync.class).forEach(recipeSync -> recipeSync.recipesLoaded(this.recipeManager));
+    }
+    
+}

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

@@ -8,11 +8,11 @@ import net.minecraft.client.gui.Gui;
 import net.minecraft.client.gui.GuiEventListener;
 import net.minecraft.item.ItemStack;
 import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Overwrite;
 import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
 @Mixin(ContainerGui.class)
 public class MixinContainerGui extends Gui implements IMixinContainerGui {
@@ -25,8 +25,10 @@ public class MixinContainerGui extends Gui implements IMixinContainerGui {
     protected int containerWidth;
     @Shadow
     protected int containerHeight;
+    
     private ContainerGuiOverlay overlay;
     private ContainerGui lastGui;
+    
     @Shadow
     private ItemStack field_2782;
     
@@ -81,6 +83,7 @@ public class MixinContainerGui extends Gui implements IMixinContainerGui {
         return lastGui;
     }
     
+    // WIP into an inject
     @Override
     public boolean mouseScrolled(double double_1) {
         for(GuiEventListener entry : this.getEntries())
@@ -89,4 +92,22 @@ public class MixinContainerGui extends Gui implements IMixinContainerGui {
         return false;
     }
     
+    // WIP into an inject
+    @Override
+    public boolean charTyped(char char_1, int int_1) {
+        for(GuiEventListener entry : this.getEntries())
+            if (entry.charTyped(char_1, int_1))
+                return true;
+        return false;
+    }
+    
+    @Inject(method = "keyPressed(III)Z", at = @At("HEAD"), cancellable = true)
+    public void keyPressed(int int_1, int int_2, int int_3, CallbackInfoReturnable<Boolean> ci) {
+        for(GuiEventListener entry : this.getEntries())
+            if (entry.keyPressed(int_1, int_2, int_3)) {
+                ci.cancel();
+                ci.setReturnValue(true);
+            }
+    }
+    
 }

+ 26 - 0
src/main/java/me/shedaniel/rei/plugin/DefaultCraftingCategory.java

@@ -0,0 +1,26 @@
+package me.shedaniel.rei.plugin;
+
+import me.shedaniel.rei.api.IRecipeCategory;
+import net.minecraft.block.Blocks;
+import net.minecraft.client.resource.language.I18n;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
+
+public class DefaultCraftingCategory implements IRecipeCategory<DefaultCraftingDisplay> {
+    
+    @Override
+    public Identifier getIdentifier() {
+        return DefaultPlugin.CRAFTING;
+    }
+    
+    @Override
+    public ItemStack getCategoryIcon() {
+        return new ItemStack(Blocks.CRAFTING_TABLE.getItem());
+    }
+    
+    @Override
+    public String getCategoryName() {
+        return I18n.translate("category.rei.crafting");
+    }
+    
+}

+ 15 - 0
src/main/java/me/shedaniel/rei/plugin/DefaultCraftingDisplay.java

@@ -0,0 +1,15 @@
+package me.shedaniel.rei.plugin;
+
+import me.shedaniel.rei.api.IRecipeDisplay;
+import net.minecraft.recipe.Recipe;
+import net.minecraft.recipe.crafting.ShapelessRecipe;
+import net.minecraft.util.Identifier;
+
+public interface DefaultCraftingDisplay<T> extends IRecipeDisplay<Recipe> {
+    
+    @Override
+    default Identifier getRecipeCategory() {
+        return DefaultPlugin.CRAFTING;
+    }
+    
+}

+ 28 - 0
src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java

@@ -0,0 +1,28 @@
+package me.shedaniel.rei.plugin;
+
+import me.shedaniel.rei.api.IRecipePlugin;
+import me.shedaniel.rei.client.RecipeHelper;
+import net.minecraft.recipe.Recipe;
+import net.minecraft.recipe.crafting.ShapedRecipe;
+import net.minecraft.recipe.crafting.ShapelessRecipe;
+import net.minecraft.util.Identifier;
+
+public class DefaultPlugin implements IRecipePlugin {
+    
+    static final Identifier CRAFTING = new Identifier("roughlyenoughitems", "plugin/crafting");
+    
+    @Override
+    public void registerPluginCategories() {
+        RecipeHelper.registerCategory(new DefaultCraftingCategory());
+    }
+    
+    @Override
+    public void registerRecipes() {
+        for(Recipe value : RecipeHelper.getRecipeManager().values())
+            if (value instanceof ShapelessRecipe)
+                RecipeHelper.registerRecipe(CRAFTING, new DefaultShapelessDisplay((ShapelessRecipe) value));
+            else if (value instanceof ShapedRecipe)
+                RecipeHelper.registerRecipe(CRAFTING, new DefaultShapedDisplay((ShapedRecipe) value));
+    }
+    
+}

+ 45 - 0
src/main/java/me/shedaniel/rei/plugin/DefaultShapedDisplay.java

@@ -0,0 +1,45 @@
+package me.shedaniel.rei.plugin;
+
+import com.google.common.collect.Lists;
+import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.crafting.ShapedRecipe;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class DefaultShapedDisplay implements DefaultCraftingDisplay<ShapedRecipe> {
+    
+    private ShapedRecipe display;
+    private List<List<ItemStack>> input;
+    private List<ItemStack> output;
+    
+    public DefaultShapedDisplay(ShapedRecipe recipe) {
+        this.display = recipe;
+        this.input = Lists.newArrayList();
+        recipe.getPreviewInputs().forEach(ingredient -> {
+            input.add(Arrays.asList(ingredient.getStackArray()));
+        });
+        this.output = Arrays.asList(recipe.getOutput());
+    }
+    
+    @Override
+    public ShapedRecipe getRecipe() {
+        return display;
+    }
+    
+    @Override
+    public List<List<ItemStack>> getInput() {
+        return input;
+    }
+    
+    @Override
+    public List<ItemStack> getOutput() {
+        return output;
+    }
+    
+    @Override
+    public List<List<ItemStack>> getRequiredItems() {
+        return input;
+    }
+    
+}

+ 45 - 0
src/main/java/me/shedaniel/rei/plugin/DefaultShapelessDisplay.java

@@ -0,0 +1,45 @@
+package me.shedaniel.rei.plugin;
+
+import com.google.common.collect.Lists;
+import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.crafting.ShapelessRecipe;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class DefaultShapelessDisplay implements DefaultCraftingDisplay {
+    
+    private ShapelessRecipe display;
+    private List<List<ItemStack>> input;
+    private List<ItemStack> output;
+    
+    public DefaultShapelessDisplay(ShapelessRecipe recipe) {
+        this.display = recipe;
+        this.input = Lists.newArrayList();
+        recipe.getPreviewInputs().forEach(ingredient -> {
+            input.add(Arrays.asList(ingredient.getStackArray()));
+        });
+        this.output = Arrays.asList(recipe.getOutput());
+    }
+    
+    @Override
+    public ShapelessRecipe getRecipe() {
+        return display;
+    }
+    
+    @Override
+    public List<List<ItemStack>> getInput() {
+        return input;
+    }
+    
+    @Override
+    public List<ItemStack> getOutput() {
+        return output;
+    }
+    
+    @Override
+    public List<List<ItemStack>> getRequiredItems() {
+        return input;
+    }
+    
+}

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


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

@@ -3,7 +3,8 @@
   "package": "me.shedaniel.rei.mixin",
   "compatibilityLevel": "JAVA_8",
   "mixins": [
-    "MixinContainerGui"
+    "MixinContainerGui",
+    "MixinClientPlayNetworkHandler"
   ],
   "injectors": {
     "defaultRequire": 1