Преглед на файлове

Update to 20w20a

Signed-off-by: shedaniel <daniel@shedaniel.me>
shedaniel преди 5 години
родител
ревизия
152ce78c4f
променени са 25 файла, в които са добавени 515 реда и са изтрити 265 реда
  1. 1 1
      README.md
  2. 7 3
      build.gradle
  3. 5 5
      gradle.properties
  4. 4 4
      src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java
  5. 93 11
      src/main/java/me/shedaniel/rei/api/ClientHelper.java
  6. 4 0
      src/main/java/me/shedaniel/rei/api/LiveRecipeGenerator.java
  7. 4 0
      src/main/java/me/shedaniel/rei/api/RecipeHelper.java
  8. 1 1
      src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java
  9. 11 11
      src/main/java/me/shedaniel/rei/gui/RecipeViewingScreen.java
  10. 8 8
      src/main/java/me/shedaniel/rei/gui/VillagerRecipeViewingScreen.java
  11. 2 2
      src/main/java/me/shedaniel/rei/gui/WarningAndErrorScreen.java
  12. 4 4
      src/main/java/me/shedaniel/rei/gui/config/entry/FilteringEntry.java
  13. 1 1
      src/main/java/me/shedaniel/rei/gui/credits/CreditsScreen.java
  14. 1 1
      src/main/java/me/shedaniel/rei/gui/modules/entries/GameModeMenuEntry.java
  15. 1 1
      src/main/java/me/shedaniel/rei/gui/modules/entries/SubSubsetsMenuEntry.java
  16. 1 1
      src/main/java/me/shedaniel/rei/gui/modules/entries/WeatherMenuEntry.java
  17. 1 1
      src/main/java/me/shedaniel/rei/gui/widget/ClickableLabelWidget.java
  18. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java
  19. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/LabelWidget.java
  20. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/RecipeChoosePageWidget.java
  21. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/TextFieldWidget.java
  22. 199 121
      src/main/java/me/shedaniel/rei/impl/ClientHelperImpl.java
  23. 155 77
      src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java
  24. 2 2
      src/main/java/me/shedaniel/rei/impl/widgets/LabelWidget.java
  25. 2 2
      src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java

+ 1 - 1
README.md

@@ -16,4 +16,4 @@ This mod is both client sided and server sided.
 ### License
 Roughly Enough Items was a fork of Almost Enough Items by ZenDarva until v2.0 rewrite. This fork is permitted under the MIT license.  
 Roughly Enough Items is licensed under [MIT license](https://github.com/shedaniel/RoughlyEnoughItems/blob/3.x/LICENSE).  
-Cloth, which is Roughly Enough Items's depenency, is licensed under [The Unlicense](https://github.com/shedaniel/Cloth/blob/master/LICENSE).
+Cloth, which is Roughly Enough Items's dependency, is licensed under [The Unlicense](https://github.com/shedaniel/Cloth/blob/master/LICENSE).

+ 7 - 3
build.gradle

@@ -1,5 +1,5 @@
 plugins {
-    id("fabric-loom") version "0.4.3"
+    id("fabric-loom") version "0.4.5"
     id("maven-publish")
     id("java")
     id("java-library")
@@ -45,9 +45,13 @@ processResources {
 
 dependencies {
     minecraft("com.mojang:minecraft:${project.minecraft_version}")
-    mappings("net.fabricmc:yarn:${project.yarn_version}:v2")
+    mappings("me.shedaniel:legacy-yarn:${project.yarn_version}:v2")
     modApi("net.fabricmc:fabric-loader:${project.fabricloader_version}")
-    modApi("net.fabricmc.fabric-api:fabric-api:${project.fabric_api}")
+    modApi("net.fabricmc.fabric-api:fabric-api:${project.fabric_api}") {
+        exclude(module: "fabric-dimensions-v1")
+        exclude(module: "fabric-biomes-v1")
+        exclude(module: "fabric-events-interaction-v0")
+    }
     modApi("me.shedaniel.cloth:cloth-events:${cloth_events_version}") {
         transitive = false
     }

+ 5 - 5
gradle.properties

@@ -1,13 +1,13 @@
 org.gradle.jvmargs=-Xmx3G
-mod_version=4.3.6-unstable
-supported_version=20w19a
-minecraft_version=20w19a
-yarn_version=20w19a+build.5+legacy.20w09a+build.8
+mod_version=4.3.7-unstable
+supported_version=20w20a
+minecraft_version=20w20a
+yarn_version=20w20a+build.1+legacy.20w09a+build.8
 fabricloader_version=0.8.2+build.194
 cloth_events_version=2.2.0-unstable
 cloth_config_version=4.1.0-unstable
 modmenu_version=1.11.4+build.9
-fabric_api=0.10.5+build.341-1.16
+fabric_api=0.10.8+build.345-1.16
 autoconfig1u=3.0.1-unstable
 api_include=me.shedaniel.cloth:cloth-events,me.shedaniel.cloth:config-2,me.sargunvohra.mcmods:autoconfig1u
 api_exculde=

+ 4 - 4
src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java

@@ -381,15 +381,15 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer {
         ClothClientHooks.SCREEN_KEY_PRESSED.register((minecraftClient, screen, i, i1, i2) -> {
             if (shouldReturn(screen))
                 return ActionResult.PASS;
-            if (screen.getFocused() != null && screen.getFocused() instanceof TextFieldWidget || (screen.getFocused() instanceof RecipeBookWidget && ((RecipeBookWidget) screen.getFocused()).searchField != null && ((RecipeBookWidget) screen.getFocused()).searchField.isFocused()))
-                return ActionResult.PASS;
-            if (ScreenHelper.getLastOverlay().keyPressed(i, i1, i2))
-                return ActionResult.SUCCESS;
             if (screen instanceof ContainerScreen && ConfigObject.getInstance().doesDisableRecipeBook() && ConfigObject.getInstance().doesFixTabCloseContainer())
                 if (i == 258 && minecraftClient.options.keyInventory.matchesKey(i, i1)) {
                     minecraftClient.player.closeContainer();
                     return ActionResult.SUCCESS;
                 }
+            if (screen.getFocused() != null && screen.getFocused() instanceof TextFieldWidget || (screen.getFocused() instanceof RecipeBookWidget && ((RecipeBookWidget) screen.getFocused()).searchField != null && ((RecipeBookWidget) screen.getFocused()).searchField.isFocused()))
+                return ActionResult.PASS;
+            if (ScreenHelper.getLastOverlay().keyPressed(i, i1, i2))
+                return ActionResult.SUCCESS;
             return ActionResult.PASS;
         });
     }

+ 93 - 11
src/main/java/me/shedaniel/rei/api/ClientHelper.java

@@ -23,15 +23,23 @@
 
 package me.shedaniel.rei.api;
 
+import me.shedaniel.rei.gui.RecipeScreen;
 import me.shedaniel.rei.impl.ClientHelperImpl;
+import me.shedaniel.rei.utils.CollectionUtils;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.screen.Screen;
 import net.minecraft.item.Item;
 import net.minecraft.item.ItemStack;
 import net.minecraft.text.Text;
 import net.minecraft.util.Identifier;
 import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public interface ClientHelper {
     
@@ -67,14 +75,10 @@ public interface ClientHelper {
      *
      * @param map the map of recipes
      */
+    @ApiStatus.ScheduledForRemoval
+    @Deprecated
     void openRecipeViewingScreen(Map<RecipeCategory<?>, List<RecipeDisplay>> map);
     
-    /**
-     * Registers REI's keybinds using Fabric API.
-     */
-    @ApiStatus.Internal
-    void registerFabricKeyBinds();
-    
     /**
      * Tries to cheat stack using either packets or commands.
      *
@@ -93,8 +97,14 @@ public interface ClientHelper {
      * @param stack the stack to find recipe for
      * @return whether the stack has any recipes to show
      */
-    boolean executeRecipeKeyBind(EntryStack stack);
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval
+    default boolean executeRecipeKeyBind(EntryStack stack) {
+        return openView(ViewSearchBuilder.builder().addRecipesFor(stack).setOutputNotice(stack).fillPreferredOpenedCategory());
+    }
     
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval
     default boolean executeRecipeKeyBind(ItemStack stack) {
         return executeRecipeKeyBind(EntryStack.create(stack));
     }
@@ -105,8 +115,14 @@ public interface ClientHelper {
      * @param stack the stack to find usage for
      * @return whether the stack has any usages to show
      */
-    boolean executeUsageKeyBind(EntryStack stack);
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval
+    default boolean executeUsageKeyBind(EntryStack stack) {
+        return openView(ViewSearchBuilder.builder().addUsagesFor(stack).setInputNotice(stack).fillPreferredOpenedCategory());
+    }
     
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval
     default boolean executeUsageKeyBind(ItemStack stack) {
         return executeUsageKeyBind(EntryStack.create(stack));
     }
@@ -165,9 +181,75 @@ public interface ClientHelper {
      *
      * @return whether there are any recipes to show
      */
-    boolean executeViewAllRecipesKeyBind();
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval
+    default boolean executeViewAllRecipesKeyBind() {
+        return openView(ViewSearchBuilder.builder().addAllCategories().fillPreferredOpenedCategory());
+    }
     
-    boolean executeViewAllRecipesFromCategory(Identifier category);
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval
+    default boolean executeViewAllRecipesFromCategory(Identifier category) {
+        return openView(ViewSearchBuilder.builder().addCategory(category).fillPreferredOpenedCategory());
+    }
     
-    boolean executeViewAllRecipesFromCategories(List<Identifier> categories);
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval
+    default boolean executeViewAllRecipesFromCategories(List<Identifier> categories) {
+        return openView(ViewSearchBuilder.builder().addCategories(categories).fillPreferredOpenedCategory());
+    }
+    
+    boolean openView(ViewSearchBuilder builder);
+    
+    interface ViewSearchBuilder {
+        static ViewSearchBuilder builder() {
+            return new ClientHelperImpl.ViewSearchBuilder();
+        }
+        
+        ViewSearchBuilder addCategory(Identifier category);
+        
+        ViewSearchBuilder addCategories(Collection<Identifier> categories);
+        
+        default ViewSearchBuilder addAllCategories() {
+            return addCategories(CollectionUtils.map(RecipeHelper.getInstance().getAllCategories(), RecipeCategory::getIdentifier));
+        }
+    
+        @NotNull Set<Identifier> getCategories();
+    
+        ViewSearchBuilder addRecipesFor(EntryStack stack);
+    
+        @NotNull List<EntryStack> getRecipesFor();
+    
+        ViewSearchBuilder addUsagesFor(EntryStack stack);
+    
+        @NotNull List<EntryStack> getUsagesFor();
+    
+        ViewSearchBuilder setPreferredOpenedCategory(@Nullable Identifier category);
+        
+        @Nullable
+        Identifier getPreferredOpenedCategory();
+        
+        default ViewSearchBuilder fillPreferredOpenedCategory() {
+            if (getPreferredOpenedCategory() == null) {
+                Screen currentScreen = MinecraftClient.getInstance().currentScreen;
+                if (currentScreen instanceof RecipeScreen) {
+                    setPreferredOpenedCategory(((RecipeScreen) currentScreen).getCurrentCategory());
+                }
+            }
+            return this;
+        }
+        
+        ViewSearchBuilder setInputNotice(@Nullable EntryStack stack);
+        
+        @Nullable
+        EntryStack getInputNotice();
+        
+        ViewSearchBuilder setOutputNotice(@Nullable EntryStack stack);
+        
+        @Nullable
+        EntryStack getOutputNotice();
+        
+        @NotNull
+        Map<RecipeCategory<?>, List<RecipeDisplay>> buildMap();
+    }
 }

+ 4 - 0
src/main/java/me/shedaniel/rei/api/LiveRecipeGenerator.java

@@ -43,4 +43,8 @@ public interface LiveRecipeGenerator<T extends RecipeDisplay> {
         return Optional.empty();
     }
     
+    default Optional<List<T>> getDisplaysGenerated(ClientHelper.ViewSearchBuilder builder) {
+        return Optional.empty();
+    }
+    
 }

+ 4 - 0
src/main/java/me/shedaniel/rei/api/RecipeHelper.java

@@ -114,6 +114,8 @@ public interface RecipeHelper {
     @Deprecated
     void registerDisplay(Identifier categoryIdentifier, RecipeDisplay display);
     
+    Map<RecipeCategory<?>, List<RecipeDisplay>> buildMapFor(ClientHelper.ViewSearchBuilder builder);
+    
     /**
      * Gets a map of recipes for an entry
      *
@@ -178,6 +180,8 @@ public interface RecipeHelper {
      */
     Map<RecipeCategory<?>, List<RecipeDisplay>> getAllRecipes();
     
+    Map<RecipeCategory<?>, List<RecipeDisplay>> getAllRecipesNoHandlers();
+    
     List<RecipeDisplay> getAllRecipesFromCategory(RecipeCategory<?> category);
     
     /**

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

@@ -560,7 +560,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
     public void renderTooltip(MatrixStack matrices, List<Text> lines, int mouseX, int mouseY) {
         if (lines.isEmpty())
             return;
-        tooltipWidth = lines.stream().map(font::getStringWidth).max(Integer::compareTo).get();
+        tooltipWidth = lines.stream().map(font::getWidth).max(Integer::compareTo).get();
         tooltipHeight = lines.size() <= 1 ? 8 : lines.size() * 10;
         tooltipLines = lines;
         ScreenHelper.drawHoveringWidget(matrices, mouseX, mouseY, renderTooltipCallback, tooltipWidth, tooltipHeight, 0);

+ 11 - 11
src/main/java/me/shedaniel/rei/gui/RecipeViewingScreen.java

@@ -166,45 +166,45 @@ public class RecipeViewingScreen extends Screen implements RecipeScreen {
     }
     
     @Override
-    public boolean keyPressed(int int_1, int int_2, int int_3) {
-        if (int_1 == 256 && choosePageActivated) {
+    public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+        if (keyCode == 256 && choosePageActivated) {
             choosePageActivated = false;
             init();
             return true;
         }
-        if (int_1 == 258) {
+        if (keyCode == 258 && !client.options.keyInventory.matchesKey(keyCode, scanCode)) {
             boolean boolean_1 = !hasShiftDown();
             if (!this.changeFocus(boolean_1))
                 this.changeFocus(boolean_1);
             return true;
         }
         if (choosePageActivated)
-            return recipeChoosePageWidget.keyPressed(int_1, int_2, int_3);
-        else if (ConfigObject.getInstance().getNextPageKeybind().matchesKey(int_1, int_2)) {
+            return recipeChoosePageWidget.keyPressed(keyCode, scanCode, modifiers);
+        else if (ConfigObject.getInstance().getNextPageKeybind().matchesKey(keyCode, scanCode)) {
             if (recipeNext.isEnabled())
                 recipeNext.onClick();
             return recipeNext.isEnabled();
-        } else if (ConfigObject.getInstance().getPreviousPageKeybind().matchesKey(int_1, int_2)) {
+        } else if (ConfigObject.getInstance().getPreviousPageKeybind().matchesKey(keyCode, scanCode)) {
             if (recipeBack.isEnabled())
                 recipeBack.onClick();
             return recipeBack.isEnabled();
         }
         for (Element element : children())
-            if (element.keyPressed(int_1, int_2, int_3))
+            if (element.keyPressed(keyCode, scanCode, modifiers))
                 return true;
-        if (int_1 == 256 || this.client.options.keyInventory.matchesKey(int_1, int_2)) {
+        if (keyCode == 256 || this.client.options.keyInventory.matchesKey(keyCode, scanCode)) {
             MinecraftClient.getInstance().openScreen(REIHelper.getInstance().getPreviousContainerScreen());
             ScreenHelper.getLastOverlay().init();
             return true;
         }
-        if (int_1 == 259) {
+        if (keyCode == 259) {
             if (ScreenHelper.hasLastRecipeScreen())
                 client.openScreen(ScreenHelper.getLastRecipeScreen());
             else
                 client.openScreen(REIHelper.getInstance().getPreviousContainerScreen());
             return true;
         }
-        return super.keyPressed(int_1, int_2, int_3);
+        return super.keyPressed(keyCode, scanCode, modifiers);
     }
     
     @Override
@@ -441,7 +441,7 @@ public class RecipeViewingScreen extends Screen implements RecipeScreen {
                         matrices.push();
                         matrices.translate(0.0D, 0.0D, 480);
                         Matrix4f matrix4f = matrices.peek().getModel();
-                        textRenderer.draw(text, bounds.getCenterX() - textRenderer.getStringWidth(text) / 2f, bounds.getCenterY() - 4.5f, 0xff000000, false, matrix4f, immediate, false, 0, 15728880);
+                        textRenderer.draw(text, bounds.getCenterX() - textRenderer.getWidth(text) / 2f, bounds.getCenterY() - 4.5f, 0xff000000, false, matrix4f, immediate, false, 0, 15728880);
                         immediate.draw();
                         matrices.pop();
                     } else {

+ 8 - 8
src/main/java/me/shedaniel/rei/gui/VillagerRecipeViewingScreen.java

@@ -369,14 +369,14 @@ public class VillagerRecipeViewingScreen extends Screen implements RecipeScreen
     }
     
     @Override
-    public boolean keyPressed(int int_1, int int_2, int int_3) {
-        if (int_1 == 258) {
+    public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+        if (keyCode == 258 && !client.options.keyInventory.matchesKey(keyCode, scanCode)) {
             boolean boolean_1 = !hasShiftDown();
             if (!this.changeFocus(boolean_1))
                 this.changeFocus(boolean_1);
             return true;
         }
-        if (ConfigObject.getInstance().getNextPageKeybind().matchesKey(int_1, int_2)) {
+        if (ConfigObject.getInstance().getNextPageKeybind().matchesKey(keyCode, scanCode)) {
             if (categoryMap.get(categories.get(selectedCategoryIndex)).size() > 1) {
                 selectedRecipeIndex++;
                 if (selectedRecipeIndex >= categoryMap.get(categories.get(selectedCategoryIndex)).size())
@@ -385,7 +385,7 @@ public class VillagerRecipeViewingScreen extends Screen implements RecipeScreen
                 return true;
             }
             return false;
-        } else if (ConfigObject.getInstance().getPreviousPageKeybind().matchesKey(int_1, int_2)) {
+        } else if (ConfigObject.getInstance().getPreviousPageKeybind().matchesKey(keyCode, scanCode)) {
             if (categoryMap.get(categories.get(selectedCategoryIndex)).size() > 1) {
                 selectedRecipeIndex--;
                 if (selectedRecipeIndex < 0)
@@ -396,21 +396,21 @@ public class VillagerRecipeViewingScreen extends Screen implements RecipeScreen
             return false;
         }
         for (Element element : children())
-            if (element.keyPressed(int_1, int_2, int_3))
+            if (element.keyPressed(keyCode, scanCode, modifiers))
                 return true;
-        if (int_1 == 256 || this.client.options.keyInventory.matchesKey(int_1, int_2)) {
+        if (keyCode == 256 || this.client.options.keyInventory.matchesKey(keyCode, scanCode)) {
             MinecraftClient.getInstance().openScreen(REIHelper.getInstance().getPreviousContainerScreen());
             ScreenHelper.getLastOverlay().init();
             return true;
         }
-        if (int_1 == 259) {
+        if (keyCode == 259) {
             if (ScreenHelper.hasLastRecipeScreen())
                 client.openScreen(ScreenHelper.getLastRecipeScreen());
             else
                 client.openScreen(REIHelper.getInstance().getPreviousContainerScreen());
             return true;
         }
-        return super.keyPressed(int_1, int_2, int_3);
+        return super.keyPressed(keyCode, scanCode, modifiers);
     }
     
 }

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

@@ -233,7 +233,7 @@ public class WarningAndErrorScreen extends Screen {
         
         @Override
         public int getWidth() {
-            return MinecraftClient.getInstance().textRenderer.getStringWidth(text) + 10;
+            return MinecraftClient.getInstance().textRenderer.getWidth(text) + 10;
         }
     }
     
@@ -270,7 +270,7 @@ public class WarningAndErrorScreen extends Screen {
         
         @Override
         public int getWidth() {
-            return MinecraftClient.getInstance().textRenderer.getWidth(text) + 10;
+            return MinecraftClient.getInstance().textRenderer.getStringWidth(text) + 10;
         }
         
         @Override

+ 4 - 4
src/main/java/me/shedaniel/rei/gui/config/entry/FilteringEntry.java

@@ -112,21 +112,21 @@ public class FilteringEntry extends AbstractConfigListEntry<List<EntryStack>> {
         this.searchField = new OverlaySearchField(0, 0, 0, 0);
         {
             Text selectAllText = new TranslatableText("config.roughlyenoughitems.filteredEntries.selectAll");
-            this.selectAllButton = new ButtonWidget(0, 0, MinecraftClient.getInstance().textRenderer.getStringWidth(selectAllText) + 10, 20, selectAllText, button -> {
+            this.selectAllButton = new ButtonWidget(0, 0, MinecraftClient.getInstance().textRenderer.getWidth(selectAllText) + 10, 20, selectAllText, button -> {
                 this.selectionPoint = new Point(-Integer.MAX_VALUE / 2, -Integer.MAX_VALUE / 2);
                 this.secondPoint = new Point(Integer.MAX_VALUE / 2, Integer.MAX_VALUE / 2);
             });
         }
         {
             Text selectNoneText = new TranslatableText("config.roughlyenoughitems.filteredEntries.selectNone");
-            this.selectNoneButton = new ButtonWidget(0, 0, MinecraftClient.getInstance().textRenderer.getStringWidth(selectNoneText) + 10, 20, selectNoneText, button -> {
+            this.selectNoneButton = new ButtonWidget(0, 0, MinecraftClient.getInstance().textRenderer.getWidth(selectNoneText) + 10, 20, selectNoneText, button -> {
                 this.selectionPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
                 this.secondPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
             });
         }
         {
             Text hideText = new TranslatableText("config.roughlyenoughitems.filteredEntries.hide");
-            this.hideButton = new ButtonWidget(0, 0, MinecraftClient.getInstance().textRenderer.getStringWidth(hideText) + 10, 20, hideText, button -> {
+            this.hideButton = new ButtonWidget(0, 0, MinecraftClient.getInstance().textRenderer.getWidth(hideText) + 10, 20, hideText, button -> {
                 for (int i = 0; i < entryStacks.size(); i++) {
                     EntryStack stack = entryStacks.get(i);
                     EntryListEntry entry = entries.get(i);
@@ -140,7 +140,7 @@ public class FilteringEntry extends AbstractConfigListEntry<List<EntryStack>> {
         }
         {
             Text showText = new TranslatableText("config.roughlyenoughitems.filteredEntries.show");
-            this.showButton = new ButtonWidget(0, 0, MinecraftClient.getInstance().textRenderer.getStringWidth(showText) + 10, 20, showText, button -> {
+            this.showButton = new ButtonWidget(0, 0, MinecraftClient.getInstance().textRenderer.getWidth(showText) + 10, 20, showText, button -> {
                 for (int i = 0; i < entryStacks.size(); i++) {
                     EntryStack stack = entryStacks.get(i);
                     EntryListEntry entry = entries.get(i);

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

@@ -104,7 +104,7 @@ public class CreditsScreen extends Screen {
                     for (StackTraceElement traceElement : exception[0].getStackTrace())
                         entryListWidget.creditsAddEntry(new TextCreditsItem(new LiteralText("  at " + traceElement)));
                 } else {
-                    int maxWidth = translatorsMapped.stream().mapToInt(pair -> textRenderer.getWidth(pair.getLeft())).max().orElse(0) + 5;
+                    int maxWidth = translatorsMapped.stream().mapToInt(pair -> textRenderer.getStringWidth(pair.getLeft())).max().orElse(0) + 5;
                     for (Pair<String, String> pair : translatorsMapped) {
                         entryListWidget.creditsAddEntry(new TranslationCreditsItem(new TranslatableText(pair.getLeft()), new TranslatableText(pair.getRight()), i - maxWidth - 10, maxWidth));
                     }

+ 1 - 1
src/main/java/me/shedaniel/rei/gui/modules/entries/GameModeMenuEntry.java

@@ -54,7 +54,7 @@ public class GameModeMenuEntry extends MenuEntry {
     
     private int getTextWidth() {
         if (textWidth == -69) {
-            this.textWidth = Math.max(0, font.getWidth(text));
+            this.textWidth = Math.max(0, font.getStringWidth(text));
         }
         return this.textWidth;
     }

+ 1 - 1
src/main/java/me/shedaniel/rei/gui/modules/entries/SubSubsetsMenuEntry.java

@@ -77,7 +77,7 @@ public class SubSubsetsMenuEntry extends MenuEntry {
     
     private int getTextWidth() {
         if (textWidth == -69) {
-            this.textWidth = Math.max(0, font.getWidth(text));
+            this.textWidth = Math.max(0, font.getStringWidth(text));
         }
         return this.textWidth;
     }

+ 1 - 1
src/main/java/me/shedaniel/rei/gui/modules/entries/WeatherMenuEntry.java

@@ -55,7 +55,7 @@ public class WeatherMenuEntry extends MenuEntry {
     
     private int getTextWidth() {
         if (textWidth == -69) {
-            this.textWidth = Math.max(0, font.getWidth(text));
+            this.textWidth = Math.max(0, font.getStringWidth(text));
         }
         return this.textWidth;
     }

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

@@ -73,7 +73,7 @@ public abstract class ClickableLabelWidget extends LabelWidget {
         if (isClickable() && isHovered(mouseX, mouseY))
             color = getHoveredColor();
         Point pos = getLocation();
-        int width = font.getWidth(getText());
+        int width = font.getStringWidth(getText());
         if (isCentered()) {
             if (isHasShadows())
                 font.drawWithShadow(matrices, text, pos.x - width / 2f, pos.y, color);

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

@@ -259,7 +259,7 @@ public class EntryListWidget extends WidgetWithBounds {
                 int z = getZ();
                 setZ(500);
                 Text debugText = new LiteralText(String.format("%d entries, avg. %.0fns, ttl. %.0fms, %s fps", size, time / (double) size, totalTime / 1000000d, minecraft.fpsDebugString.split(" ")[0]));
-                fillGradient(matrices, bounds.x, bounds.y, bounds.x + font.getStringWidth(debugText) + 2, bounds.y + font.fontHeight + 2, -16777216, -16777216);
+                fillGradient(matrices, bounds.x, bounds.y, bounds.x + font.getWidth(debugText) + 2, bounds.y + font.fontHeight + 2, -16777216, -16777216);
                 VertexConsumerProvider.Immediate immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().getBuffer());
                 matrices.push();
                 matrices.translate(0.0D, 0.0D, getZ());
@@ -380,7 +380,7 @@ public class EntryListWidget extends WidgetWithBounds {
                 int z = getZ();
                 setZ(500);
                 Text debugText = new LiteralText(String.format("%d entries, avg. %.0fns, ttl. %.0fms, %s fps", size, time / (double) size, totalTime / 1000000d, minecraft.fpsDebugString.split(" ")[0]));
-                int stringWidth = font.getStringWidth(debugText);
+                int stringWidth = font.getWidth(debugText);
                 fillGradient(matrices, Math.min(bounds.x, minecraft.currentScreen.width - stringWidth - 2), bounds.y, bounds.x + stringWidth + 2, bounds.y + font.fontHeight + 2, -16777216, -16777216);
                 VertexConsumerProvider.Immediate immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().getBuffer());
                 matrices.push();

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

@@ -157,7 +157,7 @@ public class LabelWidget extends WidgetWithBounds {
     @NotNull
     @Override
     public Rectangle getBounds() {
-        int width = font.getStringWidth(text);
+        int width = font.getWidth(text);
         Point pos = getLocation();
         if (isCentered())
             return new Rectangle(pos.x - width / 2 - 1, pos.y - 5, width + 2, 14);
@@ -171,7 +171,7 @@ public class LabelWidget extends WidgetWithBounds {
     
     @Override
     public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
-        int width = font.getStringWidth(text);
+        int width = font.getWidth(text);
         Point pos = getLocation();
         if (isCentered()) {
             if (hasShadows)

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

@@ -118,12 +118,12 @@ public class RecipeChoosePageWidget extends DraggableWidget {
             public void render(MatrixStack matrices, int i, int i1, float v) {
                 font.draw(matrices, new TranslatableText("text.rei.choose_page"), bounds.x + 5, bounds.y + 5, REIHelper.getInstance().isDarkThemeEnabled() ? 0xFFBBBBBB : 0xFF404040);
                 String endString = String.format(" /%d", maxPage);
-                int width = font.getWidth(endString);
+                int width = font.getStringWidth(endString);
                 font.draw(matrices, endString, bounds.x + bounds.width - 5 - width, bounds.y + 22, REIHelper.getInstance().isDarkThemeEnabled() ? 0xFFBBBBBB : 0xFF404040);
             }
         });
         String endString = String.format(" /%d", maxPage);
-        int width = font.getWidth(endString);
+        int width = font.getStringWidth(endString);
         this.widgets.add(textFieldWidget = new TextFieldWidget(bounds.x + 7, bounds.y + 16, bounds.width - width - 12, 18));
         textFieldWidget.setMaxLength(10000);
         textFieldWidget.stripInvalid = s -> {

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

@@ -469,7 +469,7 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
             
             // Render selection overlay
             if (int_5 != int_4) {
-                int int_10 = x + this.font.getWidth(string_1.substring(0, int_5));
+                int int_10 = x + this.font.getStringWidth(string_1.substring(0, int_5));
                 this.renderSelection(matrices, int_9, y - 1, int_10 - 1, y + 9, color);
             }
         }
@@ -626,7 +626,7 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
     }
     
     public int method_1889(int int_1) {
-        return int_1 > this.text.length() ? this.bounds.x : this.bounds.x + this.font.getWidth(this.text.substring(0, int_1));
+        return int_1 > this.text.length() ? this.bounds.x : this.bounds.x + this.font.getStringWidth(this.text.substring(0, int_1));
     }
     
 }

+ 199 - 121
src/main/java/me/shedaniel/rei/impl/ClientHelperImpl.java

@@ -25,23 +25,16 @@ package me.shedaniel.rei.impl;
 
 import com.google.common.collect.Maps;
 import io.netty.buffer.Unpooled;
-import me.sargunvohra.mcmods.autoconfig1u.annotation.ConfigEntry;
-import me.shedaniel.clothconfig2.api.FakeModifierKeyCodeAdder;
-import me.shedaniel.clothconfig2.api.ModifierKeyCode;
-import me.shedaniel.math.api.Executor;
 import me.shedaniel.rei.RoughlyEnoughItemsCore;
 import me.shedaniel.rei.RoughlyEnoughItemsNetwork;
-import me.shedaniel.rei.RoughlyEnoughItemsState;
 import me.shedaniel.rei.api.*;
 import me.shedaniel.rei.gui.PreRecipeViewingScreen;
 import me.shedaniel.rei.gui.RecipeScreen;
 import me.shedaniel.rei.gui.RecipeViewingScreen;
 import me.shedaniel.rei.gui.VillagerRecipeViewingScreen;
 import me.shedaniel.rei.gui.config.RecipeScreenType;
-import me.shedaniel.rei.utils.CollectionUtils;
 import net.fabricmc.api.ClientModInitializer;
 import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
-import net.fabricmc.fabric.impl.client.keybinding.KeyBindingRegistryImpl;
 import net.fabricmc.loader.api.FabricLoader;
 import net.fabricmc.loader.api.ModContainer;
 import net.fabricmc.loader.api.metadata.ModMetadata;
@@ -61,14 +54,11 @@ import net.minecraft.util.Identifier;
 import net.minecraft.util.Lazy;
 import net.minecraft.util.registry.Registry;
 import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.lang.reflect.Field;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.util.*;
 
 @ApiStatus.Internal
 public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
@@ -149,7 +139,7 @@ public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
     
     @Override
     public void sendDeletePacket() {
-        if (REIHelper.getInstance().getPreviousHandledScreen() instanceof CreativeInventoryScreen) {
+        if (REIHelper.getInstance().getPreviousContainerScreen() instanceof CreativeInventoryScreen) {
             MinecraftClient.getInstance().player.inventory.setCursorStack(ItemStack.EMPTY);
             return;
         }
@@ -184,22 +174,6 @@ public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
         }
     }
     
-    @Override
-    public boolean executeRecipeKeyBind(EntryStack stack) {
-        Map<RecipeCategory<?>, List<RecipeDisplay>> map = RecipeHelper.getInstance().getRecipesFor(stack);
-        if (map.keySet().size() > 0)
-            openRecipeViewingScreen(map, null, null, stack);
-        return map.keySet().size() > 0;
-    }
-    
-    @Override
-    public boolean executeUsageKeyBind(EntryStack stack) {
-        Map<RecipeCategory<?>, List<RecipeDisplay>> map = RecipeHelper.getInstance().getUsagesFor(stack);
-        if (map.keySet().size() > 0)
-            openRecipeViewingScreen(map, null, stack, null);
-        return map.keySet().size() > 0;
-    }
-    
     @Override
     public List<ItemStack> getInventoryItemsTypes() {
         List<ItemStack> inventoryStacks = new ArrayList<>(MinecraftClient.getInstance().player.inventory.main);
@@ -208,130 +182,234 @@ public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
         return inventoryStacks;
     }
     
-    @Override
-    public boolean executeViewAllRecipesKeyBind() {
-        Map<RecipeCategory<?>, List<RecipeDisplay>> map = RecipeHelper.getInstance().getAllRecipes();
-        if (map.keySet().size() > 0)
-            openRecipeViewingScreen(map, null, null, null);
-        return map.keySet().size() > 0;
-    }
-    
-    @Override
-    public boolean executeViewAllRecipesFromCategory(Identifier category) {
-        Map<RecipeCategory<?>, List<RecipeDisplay>> map = Maps.newLinkedHashMap();
-        RecipeCategory<?> recipeCategory = CollectionUtils.findFirstOrNull(RecipeHelper.getInstance().getAllCategories(), c -> c.getIdentifier().equals(category));
-        if (recipeCategory == null)
-            return false;
-        map.put(recipeCategory, RecipeHelper.getInstance().getAllRecipesFromCategory(recipeCategory));
-        if (map.keySet().size() > 0)
-            openRecipeViewingScreen(map);
-        return map.keySet().size() > 0;
-    }
-    
-    @Override
-    public boolean executeViewAllRecipesFromCategories(List<Identifier> categories) {
-        Map<RecipeCategory<?>, List<RecipeDisplay>> map = Maps.newLinkedHashMap();
-        for (Identifier category : categories) {
-            RecipeCategory<?> recipeCategory = CollectionUtils.findFirstOrNull(RecipeHelper.getInstance().getAllCategories(), c -> c.getIdentifier().equals(category));
-            if (recipeCategory == null)
-                continue;
-            map.put(recipeCategory, RecipeHelper.getInstance().getAllRecipesFromCategory(recipeCategory));
-        }
-        if (map.keySet().size() > 0)
-            openRecipeViewingScreen(map);
-        return map.keySet().size() > 0;
-    }
-    
+    @ApiStatus.ScheduledForRemoval
+    @Deprecated
     @Override
     public void openRecipeViewingScreen(Map<RecipeCategory<?>, List<RecipeDisplay>> map) {
         openRecipeViewingScreen(map, null, null, null);
     }
     
+    @ApiStatus.ScheduledForRemoval
+    @Deprecated
     @ApiStatus.Internal
     public void openRecipeViewingScreen(Map<RecipeCategory<?>, List<RecipeDisplay>> map, @Nullable Identifier category, @Nullable EntryStack ingredientNotice, @Nullable EntryStack resultNotice) {
-        if (category == null) {
-            Screen currentScreen = MinecraftClient.getInstance().currentScreen;
-            if (currentScreen instanceof RecipeScreen) {
-                category = ((RecipeScreen) currentScreen).getCurrentCategory();
-            }
-        }
+        openView(new LegacyWrapperViewSearchBuilder(map).setPreferredOpenedCategory(category).setInputNotice(ingredientNotice).setOutputNotice(resultNotice).fillPreferredOpenedCategory());
+    }
+    
+    @Override
+    public boolean openView(ClientHelper.ViewSearchBuilder builder) {
+        Map<RecipeCategory<?>, List<RecipeDisplay>> map = builder.buildMap();
+        if (map.isEmpty()) return false;
         Screen screen;
         if (ConfigObject.getInstance().getRecipeScreenType() == RecipeScreenType.VILLAGER) {
-            screen = new VillagerRecipeViewingScreen(map, category);
+            screen = new VillagerRecipeViewingScreen(map, builder.getPreferredOpenedCategory());
         } else if (ConfigObject.getInstance().getRecipeScreenType() == RecipeScreenType.UNSET) {
-            @Nullable Identifier finalCategory = category;
             screen = new PreRecipeViewingScreen(REIHelper.getInstance().getPreviousContainerScreen(), RecipeScreenType.UNSET, true, original -> {
                 ConfigObject.getInstance().setRecipeScreenType(original ? RecipeScreenType.ORIGINAL : RecipeScreenType.VILLAGER);
                 ConfigManager.getInstance().saveConfig();
-                openRecipeViewingScreen(map, finalCategory, ingredientNotice, resultNotice);
+                openView(builder);
             });
         } else {
-            screen = new RecipeViewingScreen(map, category);
+            screen = new RecipeViewingScreen(map, builder.getPreferredOpenedCategory());
         }
         if (screen instanceof RecipeScreen) {
-            if (ingredientNotice != null)
-                ((RecipeScreen) screen).addIngredientStackToNotice(ingredientNotice);
-            if (resultNotice != null)
-                ((RecipeScreen) screen).addResultStackToNotice(resultNotice);
+            if (builder.getInputNotice() != null)
+                ((RecipeScreen) screen).addIngredientStackToNotice(builder.getInputNotice());
+            if (builder.getOutputNotice() != null)
+                ((RecipeScreen) screen).addResultStackToNotice(builder.getOutputNotice());
         }
         if (MinecraftClient.getInstance().currentScreen instanceof RecipeScreen)
             ScreenHelper.storeRecipeScreen((RecipeScreen) MinecraftClient.getInstance().currentScreen);
         MinecraftClient.getInstance().openScreen(screen);
+        return true;
     }
     
     @Override
     public void onInitializeClient() {
         ClientHelperImpl.instance = this;
-        registerFabricKeyBinds();
         modNameCache.put("minecraft", "Minecraft");
         modNameCache.put("c", "Global");
         modNameCache.put("global", "Global");
     }
     
-    @Override
-    public void registerFabricKeyBinds() {
-        boolean keybindingsLoaded = FabricLoader.getInstance().isModLoaded("fabric-keybindings-v0");
-        if (!keybindingsLoaded) {
-            RoughlyEnoughItemsState.error("Fabric API is not installed!", "https://www.curseforge.com/minecraft/mc-mods/fabric-api/files/all");
-            return;
+    public static final class ViewSearchBuilder implements ClientHelper.ViewSearchBuilder {
+        @NotNull private final Set<Identifier> categories = new HashSet<>();
+        @NotNull private final List<EntryStack> recipesFor = new ArrayList<>();
+        @NotNull private final List<EntryStack> usagesFor = new ArrayList<>();
+        @Nullable private Identifier preferredOpenedCategory = null;
+        @Nullable private EntryStack inputNotice;
+        @Nullable private EntryStack outputNotice;
+        @NotNull private final Lazy<Map<RecipeCategory<?>, List<RecipeDisplay>>> map = new Lazy<>(() -> RecipeHelper.getInstance().buildMapFor(this));
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder addCategory(Identifier category) {
+            this.categories.add(category);
+            return this;
+        }
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder addCategories(Collection<Identifier> categories) {
+            this.categories.addAll(categories);
+            return this;
+        }
+        
+        @Override
+        @NotNull
+        public Set<Identifier> getCategories() {
+            return categories;
+        }
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder addRecipesFor(EntryStack stack) {
+            this.recipesFor.add(stack);
+            return this;
+        }
+        
+        @Override
+        @NotNull
+        public List<EntryStack> getRecipesFor() {
+            return recipesFor;
+        }
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder addUsagesFor(EntryStack stack) {
+            this.usagesFor.add(stack);
+            return this;
+        }
+        
+        @Override
+        @NotNull
+        public List<EntryStack> getUsagesFor() {
+            return usagesFor;
+        }
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder setPreferredOpenedCategory(@Nullable Identifier category) {
+            this.preferredOpenedCategory = category;
+            return this;
+        }
+        
+        @Nullable
+        @Override
+        public Identifier getPreferredOpenedCategory() {
+            return this.preferredOpenedCategory;
+        }
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder setInputNotice(@Nullable EntryStack stack) {
+            this.inputNotice = stack;
+            return this;
+        }
+        
+        @Nullable
+        @Override
+        public EntryStack getInputNotice() {
+            return inputNotice;
+        }
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder setOutputNotice(@Nullable EntryStack stack) {
+            this.outputNotice = stack;
+            return this;
+        }
+        
+        @Nullable
+        @Override
+        public EntryStack getOutputNotice() {
+            return outputNotice;
+        }
+        
+        @NotNull
+        @Override
+        public Map<RecipeCategory<?>, List<RecipeDisplay>> buildMap() {
+            return this.map.get();
         }
-        Executor.run(() -> () -> {
-            String category = "key.rei.category";
-            if (!FabricLoader.getInstance().isModLoaded("amecs")) {
-                try {
-                    ConfigObjectImpl.General general = ConfigObject.getInstance().getGeneral();
-                    ConfigObjectImpl.General instance = general.getClass().getConstructor().newInstance();
-                    for (Field declaredField : general.getClass().getDeclaredFields()) {
-                        if (declaredField.getType() == ModifierKeyCode.class && !declaredField.isAnnotationPresent(ConfigEntry.Gui.Excluded.class)) {
-                            declaredField.setAccessible(true);
-                            FakeModifierKeyCodeAdder.INSTANCE.registerModifierKeyCode(category, "config.roughlyenoughitems." + declaredField.getName(), () -> {
-                                try {
-                                    ModifierKeyCode code = (ModifierKeyCode) declaredField.get(general);
-                                    return code == null ? ModifierKeyCode.unknown() : code;
-                                } catch (Exception e) {
-                                    throw new RuntimeException(e);
-                                }
-                            }, () -> {
-                                try {
-                                    return (ModifierKeyCode) declaredField.get(instance);
-                                } catch (IllegalAccessException e) {
-                                    throw new RuntimeException(e);
-                                }
-                            }, keyCode -> {
-                                try {
-                                    declaredField.set(general, keyCode);
-                                } catch (IllegalAccessException e) {
-                                    throw new RuntimeException(e);
-                                }
-                            });
-                        }
-                    }
-                    KeyBindingRegistryImpl.INSTANCE.addCategory(category);
-                } catch (Throwable throwable) {
-                    throwable.printStackTrace();
-                }
-            }
-        });
     }
     
+    public static final class LegacyWrapperViewSearchBuilder implements ClientHelper.ViewSearchBuilder {
+        @NotNull private final Map<RecipeCategory<?>, List<RecipeDisplay>> map;
+        @Nullable private Identifier preferredOpenedCategory = null;
+        @Nullable private EntryStack inputNotice;
+        @Nullable private EntryStack outputNotice;
+        
+        public LegacyWrapperViewSearchBuilder(@NotNull Map<RecipeCategory<?>, List<RecipeDisplay>> map) {
+            this.map = map;
+        }
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder addCategory(Identifier category) {
+            return this;
+        }
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder addCategories(Collection<Identifier> categories) {
+            return this;
+        }
+        
+        @Override
+        public @NotNull Set<Identifier> getCategories() {
+            return Collections.emptySet();
+        }
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder addRecipesFor(EntryStack stack) {
+            return this;
+        }
+        
+        @Override
+        public @NotNull List<EntryStack> getRecipesFor() {
+            return Collections.emptyList();
+        }
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder addUsagesFor(EntryStack stack) {
+            return this;
+        }
+        
+        @Override
+        public @NotNull List<EntryStack> getUsagesFor() {
+            return Collections.emptyList();
+        }
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder setPreferredOpenedCategory(@Nullable Identifier category) {
+            this.preferredOpenedCategory = category;
+            return this;
+        }
+        
+        @Nullable
+        @Override
+        public Identifier getPreferredOpenedCategory() {
+            return this.preferredOpenedCategory;
+        }
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder setInputNotice(@Nullable EntryStack stack) {
+            this.inputNotice = stack;
+            return this;
+        }
+        
+        @Nullable
+        @Override
+        public EntryStack getInputNotice() {
+            return inputNotice;
+        }
+        
+        @Override
+        public ClientHelper.ViewSearchBuilder setOutputNotice(@Nullable EntryStack stack) {
+            this.outputNotice = stack;
+            return this;
+        }
+        
+        @Nullable
+        @Override
+        public EntryStack getOutputNotice() {
+            return outputNotice;
+        }
+        
+        @Override
+        public @NotNull Map<RecipeCategory<?>, List<RecipeDisplay>> buildMap() {
+            return this.map;
+        }
+    }
 }

+ 155 - 77
src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java

@@ -39,9 +39,11 @@ import net.minecraft.recipe.Recipe;
 import net.minecraft.recipe.RecipeManager;
 import net.minecraft.util.ActionResult;
 import net.minecraft.util.Identifier;
+import net.minecraft.util.Util;
 import org.jetbrains.annotations.ApiStatus;
 
 import java.util.*;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -154,38 +156,116 @@ public class RecipeHelperImpl implements RecipeHelper {
     }
     
     @Override
-    public Map<RecipeCategory<?>, List<RecipeDisplay>> getRecipesFor(EntryStack stack) {
+    public Map<RecipeCategory<?>, List<RecipeDisplay>> buildMapFor(ClientHelper.ViewSearchBuilder builder) {
+        long start = Util.getMeasuringTimeNano();
+        Set<Identifier> categories = builder.getCategories();
+        List<EntryStack> recipesFor = builder.getRecipesFor();
+        List<EntryStack> usagesFor = builder.getUsagesFor();
+        
         Map<RecipeCategory<?>, List<RecipeDisplay>> result = Maps.newLinkedHashMap();
-        for (Map.Entry<RecipeCategory<?>, Identifier> entry : categories.entrySet()) {
+        for (Map.Entry<RecipeCategory<?>, Identifier> entry : this.categories.entrySet()) {
             RecipeCategory<?> category = entry.getKey();
             Identifier categoryId = entry.getValue();
+            List<RecipeDisplay> allRecipesFromCategory = getAllRecipesFromCategory(category);
+            
             Set<RecipeDisplay> set = Sets.newLinkedHashSet();
-            for (RecipeDisplay display : Lists.newArrayList(recipeCategoryListMap.get(categoryId))) {
-                for (EntryStack outputStack : display.getOutputEntries())
-                    if (stack.equals(outputStack) && isDisplayVisible(display)) {
+            if (categories.contains(categoryId)) {
+                for (RecipeDisplay display : allRecipesFromCategory) {
+                    if (isDisplayVisible(display)) {
                         set.add(display);
-                        break;
                     }
+                }
+                if (!set.isEmpty()) {
+                    CollectionUtils.getOrPutEmptyList(result, category).addAll(set);
+                }
+                continue;
             }
-            if (!set.isEmpty())
+            for (RecipeDisplay display : allRecipesFromCategory) {
+                if (!isDisplayVisible(display)) continue;
+                if (!recipesFor.isEmpty()) {
+                    label:
+                    for (EntryStack outputStack : display.getOutputEntries()) {
+                        for (EntryStack stack : recipesFor) {
+                            if (stack.equals(outputStack)) {
+                                set.add(display);
+                                break label;
+                            }
+                        }
+                    }
+                }
+                if (!usagesFor.isEmpty()) {
+                    back:
+                    for (List<EntryStack> input : display.getInputEntries()) {
+                        for (EntryStack otherEntry : input) {
+                            for (EntryStack stack : usagesFor) {
+                                if (otherEntry.equals(stack)) {
+                                    set.add(display);
+                                    break back;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            for (EntryStack stack : usagesFor) {
+                if (isStackWorkStationOfCategory(categoryId, stack)) {
+                    set.addAll(allRecipesFromCategory);
+                    break;
+                }
+            }
+            if (!set.isEmpty()) {
                 CollectionUtils.getOrPutEmptyList(result, category).addAll(set);
+            }
         }
+        
         for (LiveRecipeGenerator<RecipeDisplay> liveRecipeGenerator : liveRecipeGenerators) {
-            RecipeCategory<?> category = getCategory(liveRecipeGenerator.getCategoryIdentifier());
-            Optional<List<RecipeDisplay>> recipeFor = liveRecipeGenerator.getRecipeFor(stack);
-            if (recipeFor.isPresent()) {
-                Set<RecipeDisplay> set = Sets.newLinkedHashSet();
-                for (RecipeDisplay display : recipeFor.get()) {
+            Set<RecipeDisplay> set = Sets.newLinkedHashSet();
+            for (EntryStack stack : recipesFor) {
+                Optional<List<RecipeDisplay>> recipeForDisplays = liveRecipeGenerator.getRecipeFor(stack);
+                if (recipeForDisplays.isPresent()) {
+                    for (RecipeDisplay display : recipeForDisplays.get()) {
+                        if (isDisplayVisible(display))
+                            set.add(display);
+                    }
+                }
+            }
+            for (EntryStack stack : usagesFor) {
+                Optional<List<RecipeDisplay>> usageForDisplays = liveRecipeGenerator.getUsageFor(stack);
+                if (usageForDisplays.isPresent()) {
+                    for (RecipeDisplay display : usageForDisplays.get()) {
+                        if (isDisplayVisible(display))
+                            set.add(display);
+                    }
+                }
+            }
+            Optional<List<RecipeDisplay>> displaysGenerated = liveRecipeGenerator.getDisplaysGenerated(builder);
+            if (displaysGenerated.isPresent()) {
+                for (RecipeDisplay display : displaysGenerated.get()) {
                     if (isDisplayVisible(display))
                         set.add(display);
                 }
-                if (!set.isEmpty())
-                    CollectionUtils.getOrPutEmptyList(result, category).addAll(set);
+            }
+            if (!set.isEmpty()) {
+                CollectionUtils.getOrPutEmptyList(result, getCategory(liveRecipeGenerator.getCategoryIdentifier())).addAll(set);
             }
         }
+        
+        long end = Util.getMeasuringTimeNano();
+        String message = String.format("Built Recipe View in %dμs for %d categories, %d recipes for, %d usages for and %d live recipe generators.",
+                (end - start) / 1000, categories.size(), recipesFor.size(), usagesFor.size(), liveRecipeGenerators.size());
+        if (ConfigObject.getInstance().doDebugSearchTimeRequired()) {
+            RoughlyEnoughItemsCore.LOGGER.info(message);
+        } else {
+            RoughlyEnoughItemsCore.LOGGER.trace(message);
+        }
         return result;
     }
     
+    @Override
+    public Map<RecipeCategory<?>, List<RecipeDisplay>> getRecipesFor(EntryStack stack) {
+        return buildMapFor(ClientHelper.ViewSearchBuilder.builder().addRecipesFor(stack));
+    }
+    
     @Override
     public RecipeCategory<?> getCategory(Identifier identifier) {
         return reversedCategories.get(identifier);
@@ -208,43 +288,7 @@ public class RecipeHelperImpl implements RecipeHelper {
     
     @Override
     public Map<RecipeCategory<?>, List<RecipeDisplay>> getUsagesFor(EntryStack stack) {
-        Map<RecipeCategory<?>, List<RecipeDisplay>> result = Maps.newLinkedHashMap();
-        for (Map.Entry<RecipeCategory<?>, Identifier> entry : categories.entrySet()) {
-            Set<RecipeDisplay> set = Sets.newLinkedHashSet();
-            RecipeCategory<?> category = entry.getKey();
-            Identifier categoryId = entry.getValue();
-            for (RecipeDisplay display : Lists.newArrayList(recipeCategoryListMap.get(categoryId))) {
-                back:
-                for (List<EntryStack> input : display.getInputEntries()) {
-                    for (EntryStack otherEntry : input) {
-                        if (otherEntry.equals(stack)) {
-                            if (isDisplayVisible(display))
-                                set.add(display);
-                            break back;
-                        }
-                    }
-                }
-            }
-            if (isStackWorkStationOfCategory(categoryId, stack)) {
-                set.addAll(Lists.newArrayList(recipeCategoryListMap.get(categoryId)));
-            }
-            if (!set.isEmpty())
-                CollectionUtils.getOrPutEmptyList(result, category).addAll(set);
-        }
-        for (LiveRecipeGenerator<RecipeDisplay> liveRecipeGenerator : liveRecipeGenerators) {
-            RecipeCategory<?> category = getCategory(liveRecipeGenerator.getCategoryIdentifier());
-            Optional<List<RecipeDisplay>> recipeFor = liveRecipeGenerator.getUsageFor(stack);
-            if (recipeFor.isPresent()) {
-                Set<RecipeDisplay> set = Sets.newLinkedHashSet();
-                for (RecipeDisplay display : recipeFor.get()) {
-                    if (isDisplayVisible(display))
-                        set.add(display);
-                }
-                if (!set.isEmpty())
-                    CollectionUtils.getOrPutEmptyList(result, category).addAll(set);
-            }
-        }
-        return result;
+        return buildMapFor(ClientHelper.ViewSearchBuilder.builder().addUsagesFor(stack));
     }
     
     @Override
@@ -267,8 +311,40 @@ public class RecipeHelperImpl implements RecipeHelper {
             autoCraftAreaSupplierMap.put(category, rectangle);
     }
     
+    private void startSection(Object[] sectionData, String section) {
+        sectionData[0] = Util.getMeasuringTimeNano();
+        sectionData[2] = section;
+        RoughlyEnoughItemsCore.LOGGER.debug("Reloading REI: \"%s\"", section);
+    }
+    
+    private void endSection(Object[] sectionData) {
+        sectionData[1] = Util.getMeasuringTimeNano();
+        long time = (long) sectionData[1] - (long) sectionData[0];
+        String section = (String) sectionData[2];
+        if (time >= 1000000) {
+            RoughlyEnoughItemsCore.LOGGER.debug("Reloading REI: \"%s\" done in %.2fms", section, time / 1000000.0F);
+        } else {
+            RoughlyEnoughItemsCore.LOGGER.debug("Reloading REI: \"%s\" done in %.2fμs", section, time / 1000.0F);
+        }
+    }
+    
+    private void pluginSection(Object[] sectionData, String sectionName, List<REIPluginV0> list, Consumer<REIPluginV0> consumer) {
+        for (REIPluginV0 plugin : list) {
+            try {
+                startSection(sectionData, sectionName + " for " + plugin.getPluginIdentifier().toString());
+                consumer.accept(plugin);
+                endSection(sectionData);
+            } catch (Throwable e) {
+                RoughlyEnoughItemsCore.LOGGER.error(plugin.getPluginIdentifier().toString() + " plugin failed to " + sectionName + "!", e);
+            }
+        }
+    }
+    
     public void recipesLoaded(RecipeManager recipeManager) {
-        long startTime = System.currentTimeMillis();
+        long startTime = Util.getMeasuringTimeMs();
+        Object[] sectionData = {0L, 0L, ""};
+        
+        startSection(sectionData, "reset-data");
         arePluginsLoading = true;
         ScreenHelper.clearLastRecipeScreenData();
         recipeCount[0] = 0;
@@ -292,41 +368,32 @@ public class RecipeHelperImpl implements RecipeHelper {
         ((DisplayHelperImpl) DisplayHelper.getInstance()).setBaseBoundsHandler(baseBoundsHandler);
         List<REIPluginEntry> plugins = RoughlyEnoughItemsCore.getPlugins();
         plugins.sort(Comparator.comparingInt(REIPluginEntry::getPriority).reversed());
-        RoughlyEnoughItemsCore.LOGGER.info("Loading %d plugins: %s", plugins.size(), plugins.stream().map(REIPluginEntry::getPluginIdentifier).map(Identifier::toString).collect(Collectors.joining(", ")));
+        RoughlyEnoughItemsCore.LOGGER.info("Reloading REI, registered %d plugins: %s", plugins.size(), plugins.stream().map(REIPluginEntry::getPluginIdentifier).map(Identifier::toString).collect(Collectors.joining(", ")));
         Collections.reverse(plugins);
         ((EntryRegistryImpl) EntryRegistry.getInstance()).reset();
         List<REIPluginV0> reiPluginV0s = new ArrayList<>();
+        endSection(sectionData);
         for (REIPluginEntry plugin : plugins) {
             try {
                 if (plugin instanceof REIPluginV0) {
+                    startSection(sectionData, "pre-register for " + plugin.getPluginIdentifier().toString());
                     ((REIPluginV0) plugin).preRegister();
                     reiPluginV0s.add((REIPluginV0) plugin);
+                    endSection(sectionData);
                 }
             } catch (Throwable e) {
                 RoughlyEnoughItemsCore.LOGGER.error(plugin.getPluginIdentifier().toString() + " plugin failed to pre register!", e);
             }
         }
-        for (REIPluginV0 plugin : reiPluginV0s) {
-            Identifier identifier = plugin.getPluginIdentifier();
-            try {
-                plugin.registerBounds(DisplayHelper.getInstance());
-                plugin.registerEntries(EntryRegistry.getInstance());
-                plugin.registerPluginCategories(this);
-                plugin.registerRecipeDisplays(this);
-                plugin.registerOthers(this);
-            } catch (Throwable e) {
-                RoughlyEnoughItemsCore.LOGGER.error(identifier.toString() + " plugin failed to load!", e);
-            }
-        }
-        
-        for (REIPluginV0 plugin : reiPluginV0s) {
-            Identifier identifier = plugin.getPluginIdentifier();
-            try {
-                plugin.postRegister();
-            } catch (Throwable e) {
-                RoughlyEnoughItemsCore.LOGGER.error(identifier.toString() + " plugin failed to post register!", e);
-            }
-        }
+        DisplayHelper displayHelper = DisplayHelper.getInstance();
+        EntryRegistry entryRegistry = EntryRegistry.getInstance();
+        pluginSection(sectionData, "register-bounds", reiPluginV0s, plugin -> plugin.registerBounds(displayHelper));
+        pluginSection(sectionData, "register-entries", reiPluginV0s, plugin -> plugin.registerEntries(entryRegistry));
+        pluginSection(sectionData, "register-categories", reiPluginV0s, plugin -> plugin.registerPluginCategories(this));
+        pluginSection(sectionData, "register-displays", reiPluginV0s, plugin -> plugin.registerRecipeDisplays(this));
+        pluginSection(sectionData, "register-others", reiPluginV0s, plugin -> plugin.registerOthers(this));
+        pluginSection(sectionData, "post-register", reiPluginV0s, REIPluginV0::postRegister);
+        startSection(sectionData, "recipe-functions");
         if (!recipeFunctions.isEmpty()) {
             @SuppressWarnings("rawtypes") List<Recipe> allSortedRecipes = getAllSortedRecipes();
             Collections.reverse(allSortedRecipes);
@@ -340,6 +407,8 @@ public class RecipeHelperImpl implements RecipeHelper {
                 }
             }
         }
+        endSection(sectionData);
+        startSection(sectionData, "fill-handlers");
         if (getDisplayVisibilityHandlers().isEmpty())
             registerRecipeVisibilityHandler(new DisplayVisibilityHandler() {
                 @Override
@@ -368,6 +437,8 @@ public class RecipeHelperImpl implements RecipeHelper {
                 return -10;
             }
         });
+        endSection(sectionData);
+        startSection(sectionData, "finalizing");
         
         // Clear Cache
         ((DisplayHelperImpl) DisplayHelper.getInstance()).resetCache();
@@ -383,8 +454,11 @@ public class RecipeHelperImpl implements RecipeHelper {
         ScreenHelper.getOptionalOverlay().ifPresent(overlay -> overlay.shouldReInit = true);
         
         displayVisibilityHandlers.sort(VISIBILITY_HANDLER_COMPARATOR);
-        long usedTime = System.currentTimeMillis() - startTime;
-        RoughlyEnoughItemsCore.LOGGER.info("Registered %d stack entries, %d recipes displays, %d exclusion zones suppliers, %d overlay decider, %d visibility handlers and %d categories (%s) in %d ms.", EntryRegistry.getInstance().getStacksList().size(), recipeCount[0], BaseBoundsHandler.getInstance().supplierSize(), DisplayHelper.getInstance().getAllOverlayDeciders().size(), getDisplayVisibilityHandlers().size(), categories.size(), categories.keySet().stream().map(RecipeCategory::getCategoryName).collect(Collectors.joining(", ")), usedTime);
+        endSection(sectionData);
+        
+        long usedTime = Util.getMeasuringTimeMs() - startTime;
+        RoughlyEnoughItemsCore.LOGGER.info("Reloaded %d stack entries, %d recipes displays, %d exclusion zones suppliers, %d overlay deciders, %d visibility handlers and %d categories (%s) in %dms.",
+                EntryRegistry.getInstance().getStacksList().size(), recipeCount[0], BaseBoundsHandler.getInstance().supplierSize(), DisplayHelper.getInstance().getAllOverlayDeciders().size(), getDisplayVisibilityHandlers().size(), categories.size(), categories.keySet().stream().map(RecipeCategory::getCategoryName).collect(Collectors.joining(", ")), usedTime);
     }
     
     @Override
@@ -411,13 +485,17 @@ public class RecipeHelperImpl implements RecipeHelper {
     
     @Override
     public Map<RecipeCategory<?>, List<RecipeDisplay>> getAllRecipes() {
+        return buildMapFor(ClientHelper.ViewSearchBuilder.builder().addAllCategories());
+    }
+    
+    @Override
+    public Map<RecipeCategory<?>, List<RecipeDisplay>> getAllRecipesNoHandlers() {
         Map<RecipeCategory<?>, List<RecipeDisplay>> result = Maps.newLinkedHashMap();
         for (Map.Entry<RecipeCategory<?>, Identifier> entry : categories.entrySet()) {
             RecipeCategory<?> category = entry.getKey();
             Identifier categoryId = entry.getValue();
             List<RecipeDisplay> displays = Lists.newArrayList(recipeCategoryListMap.get(categoryId));
             if (displays != null) {
-                displays.removeIf(this::isDisplayNotVisible);
                 if (!displays.isEmpty())
                     result.put(category, displays);
             }

+ 2 - 2
src/main/java/me/shedaniel/rei/impl/widgets/LabelWidget.java

@@ -183,7 +183,7 @@ public final class LabelWidget extends Label {
     @NotNull
     @Override
     public final Rectangle getBounds() {
-        int width = font.getStringWidth(text);
+        int width = font.getWidth(text);
         Point point = getPoint();
         if (getHorizontalAlignment() == LEFT_ALIGNED)
             return new Rectangle(point.x - 1, point.y - 5, width + 2, 14);
@@ -200,7 +200,7 @@ public final class LabelWidget extends Label {
         if (isClickable() && isHovered(mouseX, mouseY))
             color = getHoveredColor();
         Point pos = getPoint();
-        int width = font.getStringWidth(getText());
+        int width = font.getWidth(getText());
         switch (getHorizontalAlignment()) {
             case LEFT_ALIGNED:
                 if (hasShadow())

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

@@ -200,6 +200,7 @@ public class DefaultPlugin implements REIPluginV0 {
         recipeHelper.registerCategory(new DefaultCompostingCategory());
         recipeHelper.registerCategory(new DefaultStrippingCategory());
         recipeHelper.registerCategory(new DefaultBeaconBaseCategory());
+        recipeHelper.registerCategory(new DefaultInformationCategory());
     }
     
     @Override
@@ -260,14 +261,13 @@ public class DefaultPlugin implements REIPluginV0 {
     
     @Override
     public void postRegister() {
-        RecipeHelper.getInstance().registerCategory(new DefaultInformationCategory());
         for (DefaultInformationDisplay display : INFO_DISPLAYS)
             RecipeHelper.getInstance().registerDisplay(display);
         // Sit tight! This will be a fast journey!
         long time = System.currentTimeMillis();
         for (EntryStack stack : EntryRegistry.getInstance().getStacksList())
             applyPotionTransformer(stack);
-        for (List<RecipeDisplay> displays : RecipeHelper.getInstance().getAllRecipes().values()) {
+        for (List<RecipeDisplay> displays : RecipeHelper.getInstance().getAllRecipesNoHandlers().values()) {
             for (RecipeDisplay display : displays) {
                 for (List<EntryStack> entries : display.getInputEntries())
                     for (EntryStack stack : entries)