shedaniel 5 年之前
父節點
當前提交
7395c94652
共有 27 個文件被更改,包括 201 次插入98 次删除
  1. 1 1
      gradle.properties
  2. 2 2
      src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java
  3. 2 0
      src/main/java/me/shedaniel/rei/api/ConfigManager.java
  4. 9 0
      src/main/java/me/shedaniel/rei/api/ConfigObject.java
  5. 1 1
      src/main/java/me/shedaniel/rei/api/DisplayHelper.java
  6. 0 1
      src/main/java/me/shedaniel/rei/api/plugins/REIPluginV0.java
  7. 23 23
      src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java
  8. 39 0
      src/main/java/me/shedaniel/rei/gui/OverlaySearchField.java
  9. 2 5
      src/main/java/me/shedaniel/rei/gui/PreRecipeViewingScreen.java
  10. 4 4
      src/main/java/me/shedaniel/rei/gui/RecipeViewingScreen.java
  11. 1 1
      src/main/java/me/shedaniel/rei/gui/VillagerRecipeViewingScreen.java
  12. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/AutoCraftingButtonWidget.java
  13. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/ButtonWidget.java
  14. 62 31
      src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java
  15. 3 3
      src/main/java/me/shedaniel/rei/gui/widget/PanelWidget.java
  16. 0 1
      src/main/java/me/shedaniel/rei/gui/widget/QueuedTooltip.java
  17. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/RecipeBaseWidget.java
  18. 5 5
      src/main/java/me/shedaniel/rei/impl/ClientHelperImpl.java
  19. 8 0
      src/main/java/me/shedaniel/rei/impl/ConfigManagerImpl.java
  20. 12 0
      src/main/java/me/shedaniel/rei/impl/ConfigObjectImpl.java
  21. 2 2
      src/main/java/me/shedaniel/rei/impl/FluidEntryStack.java
  22. 2 2
      src/main/java/me/shedaniel/rei/impl/ItemEntryStack.java
  23. 5 3
      src/main/java/me/shedaniel/rei/impl/ScreenHelper.java
  24. 1 0
      src/main/java/me/shedaniel/rei/impl/SearchArgument.java
  25. 2 2
      src/main/java/me/shedaniel/rei/plugin/DefaultAutoCraftingPlugin.java
  26. 5 5
      src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java
  27. 4 0
      src/main/resources/assets/roughlyenoughitems/lang/en_us.json

+ 1 - 1
gradle.properties

@@ -1,4 +1,4 @@
-mod_version=3.2.18
+mod_version=3.2.19
 minecraft_version=1.15
 yarn_version=1.15+build.1
 fabricloader_version=0.7.2+build.174

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

@@ -152,7 +152,7 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer {
             lastSync.set(System.currentTimeMillis());
         }
         RecipeManager recipeManager = MinecraftClient.getInstance().getNetworkHandler().getRecipeManager();
-        if (ConfigManager.getInstance().getConfig().doesRegisterRecipesInAnotherThread()) {
+        if (ConfigObject.getInstance().doesRegisterRecipesInAnotherThread()) {
             CompletableFuture.runAsync(() -> ((RecipeHelperImpl) RecipeHelper.getInstance()).recipesLoaded(recipeManager), SYNC_RECIPES);
         } else {
             ((RecipeHelperImpl) RecipeHelper.getInstance()).recipesLoaded(recipeManager);
@@ -245,7 +245,7 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer {
         AtomicLong lastSync = new AtomicLong(-1);
         ClothClientHooks.SYNC_RECIPES.register((minecraftClient, recipeManager, synchronizeRecipesS2CPacket) -> syncRecipes(lastSync));
         ClothClientHooks.SCREEN_ADD_BUTTON.register((minecraftClient, screen, abstractButtonWidget) -> {
-            if (ConfigManager.getInstance().getConfig().doesDisableRecipeBook() && screen instanceof AbstractContainerScreen && abstractButtonWidget instanceof TexturedButtonWidget)
+            if (ConfigObject.getInstance().doesDisableRecipeBook() && screen instanceof AbstractContainerScreen && abstractButtonWidget instanceof TexturedButtonWidget)
                 if (((RecipeBookButtonWidgetHooks) abstractButtonWidget).rei_getTexture().equals(recipeButtonTex))
                     return ActionResult.FAIL;
             return ActionResult.PASS;

+ 2 - 0
src/main/java/me/shedaniel/rei/api/ConfigManager.java

@@ -27,8 +27,10 @@ public interface ConfigManager {
     /**
      * Gets the config instance
      *
+     * @deprecated Use {@link ConfigObject#getInstance()}
      * @return the config instance
      */
+    @Deprecated
     ConfigObject getConfig();
     
     /**

+ 9 - 0
src/main/java/me/shedaniel/rei/api/ConfigObject.java

@@ -18,6 +18,11 @@ import java.lang.annotation.Target;
 
 public interface ConfigObject {
     
+    @SuppressWarnings("deprecation")
+    static ConfigObject getInstance() {
+        return ConfigManager.getInstance().getConfig();
+    }
+    
     boolean isLighterButtonHover();
     
     void setLighterButtonHover(boolean lighterButtonHover);
@@ -84,6 +89,10 @@ public interface ConfigObject {
     
     boolean isFavoritesEnabled();
     
+    boolean doDisplayFavoritesTooltip();
+    
+    boolean doDisplayFavoritesOnTheLeft();
+    
     InputUtil.KeyCode getFavoriteKeybind();
     
     @Retention(RetentionPolicy.RUNTIME)

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

@@ -118,7 +118,7 @@ public interface DisplayHelper {
          * @return the item list bounds
          */
         default Rectangle getItemListArea(Rectangle rectangle) {
-            return new Rectangle(rectangle.x + 1, rectangle.y + 2 + (ConfigManager.getInstance().getConfig().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + (ConfigManager.getInstance().getConfig().isEntryListWidgetScrolled() ? 0 : 22), rectangle.width - 2, rectangle.height - (ConfigManager.getInstance().getConfig().getSearchFieldLocation() != SearchFieldLocation.CENTER ? 27 + 22 : 27) + (!ConfigManager.getInstance().getConfig().isEntryListWidgetScrolled() ? 0 : 22));
+            return new Rectangle(rectangle.x + 1, rectangle.y + 2 + (ConfigObject.getInstance().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + (ConfigObject.getInstance().isEntryListWidgetScrolled() ? 0 : 22), rectangle.width - 2, rectangle.height - (ConfigObject.getInstance().getSearchFieldLocation() != SearchFieldLocation.CENTER ? 27 + 22 : 27) + (!ConfigObject.getInstance().isEntryListWidgetScrolled() ? 0 : 22));
         }
         
         /**

+ 0 - 1
src/main/java/me/shedaniel/rei/api/plugins/REIPluginV0.java

@@ -9,7 +9,6 @@ import me.shedaniel.rei.api.DisplayHelper;
 import me.shedaniel.rei.api.EntryRegistry;
 import me.shedaniel.rei.api.REIPluginEntry;
 import me.shedaniel.rei.api.RecipeHelper;
-import me.shedaniel.rei.api.annotations.ToBeRemoved;
 
 public interface REIPluginV0 extends REIPluginEntry {
     

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

@@ -100,7 +100,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
         this.window = MinecraftClient.getInstance().getWindow();
         @SuppressWarnings({"RawTypeCanBeGeneric", "rawtypes"})
         DisplayHelper.DisplayBoundsHandler boundsHandler = DisplayHelper.getInstance().getResponsibleBoundsHandler(MinecraftClient.getInstance().currentScreen.getClass());
-        this.rectangle = ConfigManager.getInstance().getConfig().isLeftHandSidePanel() ? boundsHandler.getLeftBounds(MinecraftClient.getInstance().currentScreen) : boundsHandler.getRightBounds(MinecraftClient.getInstance().currentScreen);
+        this.rectangle = ConfigObject.getInstance().isLeftHandSidePanel() ? boundsHandler.getLeftBounds(MinecraftClient.getInstance().currentScreen) : boundsHandler.getRightBounds(MinecraftClient.getInstance().currentScreen);
         widgets.add(ENTRY_LIST_WIDGET);
         ENTRY_LIST_WIDGET.updateArea(boundsHandler, ScreenHelper.getSearchField() == null ? "" : null);
         if (ScreenHelper.getSearchField() == null) {
@@ -111,8 +111,8 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
         ScreenHelper.getSearchField().setChangedListener(s -> {
             ENTRY_LIST_WIDGET.updateSearch(s);
         });
-        if (!ConfigManager.getInstance().getConfig().isEntryListWidgetScrolled()) {
-            widgets.add(buttonLeft = new ButtonWidget(new Rectangle(rectangle.x, rectangle.y + (ConfigManager.getInstance().getConfig().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 5, 16, 16), I18n.translate("text.rei.left_arrow")) {
+        if (!ConfigObject.getInstance().isEntryListWidgetScrolled()) {
+            widgets.add(buttonLeft = new ButtonWidget(new Rectangle(rectangle.x, rectangle.y + (ConfigObject.getInstance().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 5, 16, 16), I18n.translate("text.rei.left_arrow")) {
                 @Override
                 public void onPressed() {
                     ENTRY_LIST_WIDGET.previousPage();
@@ -136,7 +136,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
                     return isNotInExclusionZones(mouseX, mouseY) && super.containsMouse(mouseX, mouseY);
                 }
             });
-            widgets.add(buttonRight = new ButtonWidget(new Rectangle(rectangle.x + rectangle.width - 18, rectangle.y + (ConfigManager.getInstance().getConfig().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 5, 16, 16), I18n.translate("text.rei.right_arrow")) {
+            widgets.add(buttonRight = new ButtonWidget(new Rectangle(rectangle.x + rectangle.width - 18, rectangle.y + (ConfigObject.getInstance().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 5, 16, 16), I18n.translate("text.rei.right_arrow")) {
                 @Override
                 public void onPressed() {
                     ENTRY_LIST_WIDGET.nextPage();
@@ -162,7 +162,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
             });
         }
         
-        widgets.add(new ButtonWidget(new Rectangle(ConfigManager.getInstance().getConfig().isLeftHandSidePanel() ? window.getScaledWidth() - 30 : 10, 10, 20, 20), "") {
+        widgets.add(new ButtonWidget(new Rectangle(ConfigObject.getInstance().isLeftHandSidePanel() ? window.getScaledWidth() - 30 : 10, 10, 20, 20), "") {
             @Override
             public void onPressed() {
                 if (Screen.hasShiftDown()) {
@@ -213,11 +213,11 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
                 return isNotInExclusionZones(mouseX, mouseY) && super.containsMouse(mouseX, mouseY);
             }
         });
-        if (ConfigManager.getInstance().getConfig().doesShowUtilsButtons()) {
-            widgets.add(new ButtonWidget(new Rectangle(ConfigManager.getInstance().getConfig().isLeftHandSidePanel() ? window.getScaledWidth() - 55 : 35, 10, 20, 20), "") {
+        if (ConfigObject.getInstance().doesShowUtilsButtons()) {
+            widgets.add(new ButtonWidget(new Rectangle(ConfigObject.getInstance().isLeftHandSidePanel() ? window.getScaledWidth() - 55 : 35, 10, 20, 20), "") {
                 @Override
                 public void onPressed() {
-                    MinecraftClient.getInstance().player.sendChatMessage(ConfigManager.getInstance().getConfig().getGamemodeCommand().replaceAll("\\{gamemode}", getNextGameMode(Screen.hasShiftDown()).getName()));
+                    MinecraftClient.getInstance().player.sendChatMessage(ConfigObject.getInstance().getGamemodeCommand().replaceAll("\\{gamemode}", getNextGameMode(Screen.hasShiftDown()).getName()));
                 }
                 
                 @Override
@@ -241,12 +241,12 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
                     return isNotInExclusionZones(mouseX, mouseY) && super.containsMouse(mouseX, mouseY);
                 }
             });
-            int xxx = ConfigManager.getInstance().getConfig().isLeftHandSidePanel() ? window.getScaledWidth() - 30 : 10;
+            int xxx = ConfigObject.getInstance().isLeftHandSidePanel() ? window.getScaledWidth() - 30 : 10;
             for (Weather weather : Weather.values()) {
                 widgets.add(new ButtonWidget(new Rectangle(xxx, 35, 20, 20), "") {
                     @Override
                     public void onPressed() {
-                        MinecraftClient.getInstance().player.sendChatMessage(ConfigManager.getInstance().getConfig().getWeatherCommand().replaceAll("\\{weather}", weather.name().toLowerCase(Locale.ROOT)));
+                        MinecraftClient.getInstance().player.sendChatMessage(ConfigObject.getInstance().getWeatherCommand().replaceAll("\\{weather}", weather.name().toLowerCase(Locale.ROOT)));
                     }
                     
                     @Override
@@ -273,11 +273,11 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
                         return isNotInExclusionZones(mouseX, mouseY) && super.containsMouse(mouseX, mouseY);
                     }
                 });
-                xxx += ConfigManager.getInstance().getConfig().isLeftHandSidePanel() ? -25 : 25;
+                xxx += ConfigObject.getInstance().isLeftHandSidePanel() ? -25 : 25;
             }
         }
-        if (!ConfigManager.getInstance().getConfig().isEntryListWidgetScrolled()) {
-            widgets.add(new ClickableLabelWidget(new Point(rectangle.x + (rectangle.width / 2), rectangle.y + (ConfigManager.getInstance().getConfig().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 10), "") {
+        if (!ConfigObject.getInstance().isEntryListWidgetScrolled()) {
+            widgets.add(new ClickableLabelWidget(new Point(rectangle.x + (rectangle.width / 2), rectangle.y + (ConfigObject.getInstance().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 10), "") {
                 @Override
                 public void render(int mouseX, int mouseY, float delta) {
                     setText(String.format("%s/%s", ENTRY_LIST_WIDGET.getPage() + 1, ENTRY_LIST_WIDGET.getTotalPages()));
@@ -303,7 +303,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
             }.clickable(ENTRY_LIST_WIDGET.getTotalPages() != 1));
             buttonLeft.enabled = buttonRight.enabled = ENTRY_LIST_WIDGET.getTotalPages() != 1;
         }
-        if (ConfigManager.getInstance().getConfig().isCraftableFilterEnabled())
+        if (ConfigObject.getInstance().isCraftableFilterEnabled())
             this.widgets.add(toggleButtonWidget = new CraftableToggleButtonWidget(getCraftableToggleArea()) {
                 @Override
                 public void onPressed() {
@@ -377,8 +377,8 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
     }
     
     private Rectangle getTextFieldArea() {
-        int widthRemoved = ConfigManager.getInstance().getConfig().isCraftableFilterEnabled() ? 22 : 2;
-        SearchFieldLocation searchFieldLocation = ConfigManager.getInstance().getConfig().getSearchFieldLocation();
+        int widthRemoved = ConfigObject.getInstance().isCraftableFilterEnabled() ? 22 : 2;
+        SearchFieldLocation searchFieldLocation = ConfigObject.getInstance().getSearchFieldLocation();
         if (searchFieldLocation == SearchFieldLocation.BOTTOM_SIDE)
             return new Rectangle(rectangle.x + 2, window.getScaledHeight() - 22, rectangle.width - 6 - widthRemoved, 18);
         if (searchFieldLocation == SearchFieldLocation.TOP_SIDE)
@@ -417,13 +417,13 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
             init();
         else {
             for (DisplayHelper.DisplayBoundsHandler<?> handler : DisplayHelper.getInstance().getSortedBoundsHandlers(minecraft.currentScreen.getClass())) {
-                if (handler != null && handler.shouldRecalculateArea(!ConfigManager.getInstance().getConfig().isLeftHandSidePanel(), rectangle)) {
+                if (handler != null && handler.shouldRecalculateArea(!ConfigObject.getInstance().isLeftHandSidePanel(), rectangle)) {
                     init();
                     break;
                 }
             }
         }
-        //        if (DisplayHelper.getInstance().getBaseBoundsHandler() != null && DisplayHelper.getInstance().getBaseBoundsHandler().shouldRecalculateArea(!ConfigManager.getInstance().getConfig().isLeftHandSidePanel(), rectangle))
+        //        if (DisplayHelper.getInstance().getBaseBoundsHandler() != null && DisplayHelper.getInstance().getBaseBoundsHandler().shouldRecalculateArea(!ConfigObject.getInstance().isLeftHandSidePanel(), rectangle))
         //            entryListWidget.updateArea(DisplayHelper.getInstance().getResponsibleBoundsHandler());
         //        else
         if (ConfigManager.getInstance().isCraftableOnlyEnabled() && ((currentStacks.size() != ScreenHelper.inventoryStacks.size()) || !hasSameListContent(new LinkedList<>(ScreenHelper.inventoryStacks), currentStacks))) {
@@ -445,7 +445,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
         RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
         DiffuseLighting.disable();
         this.renderWidgets(mouseX, mouseY, delta);
-        if (MinecraftClient.getInstance().currentScreen instanceof AbstractContainerScreen && ConfigManager.getInstance().getConfig().areClickableRecipeArrowsEnabled()) {
+        if (MinecraftClient.getInstance().currentScreen instanceof AbstractContainerScreen && ConfigObject.getInstance().areClickableRecipeArrowsEnabled()) {
             ContainerScreenHooks hooks = (ContainerScreenHooks) MinecraftClient.getInstance().currentScreen;
             for (RecipeHelper.ScreenClickArea area : RecipeHelper.getInstance().getScreenClickAreas())
                 if (area.getScreenClass().equals(MinecraftClient.getInstance().currentScreen.getClass()))
@@ -503,7 +503,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
     public void renderWidgets(int int_1, int int_2, float float_1) {
         if (!ScreenHelper.isOverlayVisible())
             return;
-        if (!ConfigManager.getInstance().getConfig().isEntryListWidgetScrolled())
+        if (!ConfigObject.getInstance().isEntryListWidgetScrolled())
             buttonLeft.enabled = buttonRight.enabled = ENTRY_LIST_WIDGET.getTotalPages() != 1;
         widgets.forEach(widget -> {
             DiffuseLighting.disable();
@@ -517,7 +517,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
         if (!ScreenHelper.isOverlayVisible())
             return false;
         if (isInside(PointHelper.fromMouse())) {
-            if (!ConfigManager.getInstance().getConfig().isEntryListWidgetScrolled()) {
+            if (!ConfigObject.getInstance().isEntryListWidgetScrolled()) {
                 if (amount > 0 && buttonLeft.enabled)
                     buttonLeft.onPressed();
                 else if (amount < 0 && buttonRight.enabled)
@@ -586,7 +586,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
     public boolean mouseClicked(double double_1, double double_2, int int_1) {
         if (!ScreenHelper.isOverlayVisible())
             return false;
-        if (MinecraftClient.getInstance().currentScreen instanceof AbstractContainerScreen && ConfigManager.getInstance().getConfig().areClickableRecipeArrowsEnabled()) {
+        if (MinecraftClient.getInstance().currentScreen instanceof AbstractContainerScreen && ConfigObject.getInstance().areClickableRecipeArrowsEnabled()) {
             ContainerScreenHooks hooks = (ContainerScreenHooks) MinecraftClient.getInstance().currentScreen;
             for (RecipeHelper.ScreenClickArea area : RecipeHelper.getInstance().getScreenClickAreas())
                 if (area.getScreenClass().equals(MinecraftClient.getInstance().currentScreen.getClass()))
@@ -617,7 +617,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds {
         if (!rectangle.contains(mouseX, mouseY))
             return false;
         for (DisplayHelper.DisplayBoundsHandler<?> handler : DisplayHelper.getInstance().getSortedBoundsHandlers(MinecraftClient.getInstance().currentScreen.getClass())) {
-            ActionResult in = handler.isInZone(!ConfigManager.getInstance().getConfig().isLeftHandSidePanel(), mouseX, mouseY);
+            ActionResult in = handler.isInZone(!ConfigObject.getInstance().isLeftHandSidePanel(), mouseX, mouseY);
             if (in != ActionResult.PASS)
                 return in == ActionResult.SUCCESS;
         }

+ 39 - 0
src/main/java/me/shedaniel/rei/gui/OverlaySearchField.java

@@ -5,8 +5,10 @@
 
 package me.shedaniel.rei.gui;
 
+import com.google.common.collect.Lists;
 import com.mojang.blaze3d.systems.RenderSystem;
 import me.shedaniel.math.impl.PointHelper;
+import me.shedaniel.rei.api.annotations.Internal;
 import me.shedaniel.rei.gui.widget.TextFieldWidget;
 import me.shedaniel.rei.impl.ScreenHelper;
 import net.minecraft.client.MinecraftClient;
@@ -16,18 +18,37 @@ import net.minecraft.client.sound.PositionedSoundInstance;
 import net.minecraft.client.util.InputUtil;
 import net.minecraft.sound.SoundEvents;
 
+import java.util.List;
+
 public class OverlaySearchField extends TextFieldWidget {
     
     public static boolean isSearching = false;
     public long keybindFocusTime = -1;
     public int keybindFocusKey = -1;
     protected long lastClickedTime = -1;
+    private List<String> history = Lists.newArrayListWithCapacity(100);
     
     OverlaySearchField(int x, int y, int width, int height) {
         super(x, y, width, height);
         setMaxLength(10000);
     }
     
+    @Override
+    public void setFocused(boolean boolean_1) {
+        if (isFocused() != boolean_1) addToHistory(getText());
+        super.setFocused(boolean_1);
+    }
+    
+    @Deprecated
+    @Internal
+    public void addToHistory(String text) {
+        if (!text.isEmpty()) {
+            history.removeIf(str -> str.equalsIgnoreCase(text));
+            history.add(text);
+            if (history.size() > 100) history.remove(0);
+        }
+    }
+    
     @SuppressWarnings("deprecation")
     public void laterRender(int int_1, int int_2, float float_1) {
         DiffuseLighting.disable();
@@ -70,8 +91,26 @@ public class OverlaySearchField extends TextFieldWidget {
     public boolean keyPressed(int int_1, int int_2, int int_3) {
         if (this.isVisible() && this.isFocused())
             if (int_1 == 257 || int_1 == 335) {
+                addToHistory(getText());
                 setFocused(false);
                 return true;
+            } else if (int_1 == 265) {
+                int i = history.indexOf(getText()) - 1;
+                if (i < -1 && getText().isEmpty()) i = history.size() - 1;
+                else if (i < -1) {
+                    addToHistory(getText());
+                    i = history.size() - 2;
+                }
+                if (i >= 0) {
+                    setText(history.get(i));
+                    return true;
+                }
+            } else if (int_1 == 264) {
+                int i = history.indexOf(getText()) + 1;
+                if (i > 0) {
+                    setText(i < history.size() ? history.get(i) : "");
+                    return true;
+                }
             }
         return super.keyPressed(int_1, int_2, int_3);
     }

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

@@ -7,10 +7,7 @@ package me.shedaniel.rei.gui;
 
 import com.google.common.collect.Lists;
 import me.shedaniel.math.api.Rectangle;
-import me.shedaniel.rei.api.ClientHelper;
-import me.shedaniel.rei.api.ConfigManager;
-import me.shedaniel.rei.api.RecipeCategory;
-import me.shedaniel.rei.api.RecipeDisplay;
+import me.shedaniel.rei.api.*;
 import me.shedaniel.rei.gui.config.RecipeScreenType;
 import me.shedaniel.rei.gui.widget.ButtonWidget;
 import me.shedaniel.rei.gui.widget.Widget;
@@ -52,7 +49,7 @@ public class PreRecipeViewingScreen extends Screen {
         this.widgets.add(new ButtonWidget(new Rectangle(width / 2 - 100, height - 40, 200, 20), I18n.translate("text.rei.select")) {
             @Override
             public void onPressed() {
-                ConfigManager.getInstance().getConfig().setRecipeScreenType(original ? RecipeScreenType.ORIGINAL : RecipeScreenType.VILLAGER);
+                ConfigObject.getInstance().setRecipeScreenType(original ? RecipeScreenType.ORIGINAL : RecipeScreenType.VILLAGER);
                 ConfigManager.getInstance().saveConfig();
                 ClientHelper.getInstance().openRecipeViewingScreen(map);
             }

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

@@ -61,10 +61,10 @@ public class RecipeViewingScreen extends Screen {
         this.bounds = new Rectangle(window.getScaledWidth() / 2 - guiWidth / 2, window.getScaledHeight() / 2 - guiHeight / 2, 176, 186);
         this.categoriesMap = categoriesMap;
         this.categories = Lists.newArrayList();
-        RecipeHelper.getInstance().getAllCategories().forEach(category -> {
+        for (RecipeCategory<?> category : RecipeHelper.getInstance().getAllCategories()) {
             if (categoriesMap.containsKey(category))
                 categories.add(category);
-        });
+        }
         this.selectedCategory = (RecipeCategory<RecipeDisplay>) categories.get(0);
         this.tabs = new ArrayList<>();
         this.choosePageActivated = false;
@@ -361,12 +361,12 @@ public class RecipeViewingScreen extends Screen {
         if (selectedCategory.getFixedRecipesPerPage() > 0)
             return selectedCategory.getFixedRecipesPerPage() - 1;
         int height = selectedCategory.getDisplayHeight();
-        return MathHelper.clamp(MathHelper.floor(((double) largestHeight - 40d) / ((double) height + 7d)) - 1, 0, Math.min(ConfigManager.getInstance().getConfig().getMaxRecipePerPage() - 1, selectedCategory.getMaximumRecipePerPage() - 1));
+        return MathHelper.clamp(MathHelper.floor(((double) largestHeight - 40d) / ((double) height + 7d)) - 1, 0, Math.min(ConfigObject.getInstance().getMaxRecipePerPage() - 1, selectedCategory.getMaximumRecipePerPage() - 1));
     }
     
     private int getRecipesPerPageByHeight() {
         int height = selectedCategory.getDisplayHeight();
-        return MathHelper.clamp(MathHelper.floor(((double) guiHeight - 40d) / ((double) height + 7d)), 0, Math.min(ConfigManager.getInstance().getConfig().getMaxRecipePerPage() - 1, selectedCategory.getMaximumRecipePerPage() - 1));
+        return MathHelper.clamp(MathHelper.floor(((double) guiHeight - 40d) / ((double) height + 7d)), 0, Math.min(ConfigObject.getInstance().getMaxRecipePerPage() - 1, selectedCategory.getMaximumRecipePerPage() - 1));
     }
     
     @Override

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

@@ -324,7 +324,7 @@ public class VillagerRecipeViewingScreen extends Screen {
     
     @Override
     public void render(int mouseX, int mouseY, float delta) {
-        if (ConfigManager.getInstance().getConfig().doesVillagerScreenHavePermanentScrollBar()) {
+        if (ConfigObject.getInstance().doesVillagerScreenHavePermanentScrollBar()) {
             scrollBarAlphaFutureTime = System.currentTimeMillis();
             scrollBarAlphaFuture = 0;
             scrollBarAlpha = 1;

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

@@ -146,7 +146,7 @@ public class AutoCraftingButtonWidget extends ButtonWidget {
     
     @Override
     protected int getTextureId(boolean boolean_1) {
-        return !visible ? 0 : boolean_1 && enabled ? (ConfigManager.getInstance().getConfig().isLighterButtonHover() ? 4 : 3) : 1;
+        return !visible ? 0 : boolean_1 && enabled ? (ConfigObject.getInstance().isLighterButtonHover() ? 4 : 3) : 1;
     }
     
     @Override
@@ -171,7 +171,7 @@ public class AutoCraftingButtonWidget extends ButtonWidget {
     public boolean keyPressed(int int_1, int int_2, int int_3) {
         if (displaySupplier.get().getRecipeLocation().isPresent() && ClientHelper.getInstance().getCopyRecipeIdentifierKeyBinding().matchesKey(int_1, int_2) && containsMouse(PointHelper.fromMouse())) {
             minecraft.keyboard.setClipboard(displaySupplier.get().getRecipeLocation().get().toString());
-            if (ConfigManager.getInstance().getConfig().isToastDisplayedOnCopyIdentifier()) {
+            if (ConfigObject.getInstance().isToastDisplayedOnCopyIdentifier()) {
                 CopyRecipeIdentifierToast.addToast(I18n.translate("msg.rei.copied_recipe_id"), I18n.translate("msg.rei.recipe_id_details", displaySupplier.get().getRecipeLocation().get().toString()));
             }
             return true;

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

@@ -8,7 +8,7 @@ package me.shedaniel.rei.gui.widget;
 import com.mojang.blaze3d.systems.RenderSystem;
 import me.shedaniel.math.api.Point;
 import me.shedaniel.math.api.Rectangle;
-import me.shedaniel.rei.api.ConfigManager;
+import me.shedaniel.rei.api.ConfigObject;
 import me.shedaniel.rei.impl.ScreenHelper;
 import net.minecraft.client.gui.Element;
 import net.minecraft.client.sound.PositionedSoundInstance;
@@ -58,7 +58,7 @@ public abstract class ButtonWidget extends WidgetWithBounds {
         if (!this.enabled) {
             int_1 = 0;
         } else if (boolean_1) {
-            int_1 = ConfigManager.getInstance().getConfig().isLighterButtonHover() ? 4 : 3; // 2 is the old blue highlight, 3 is the 1.15 outline, 4 is the 1.15 online + light hover
+            int_1 = ConfigObject.getInstance().isLighterButtonHover() ? 4 : 3; // 2 is the old blue highlight, 3 is the 1.15 outline, 4 is the 1.15 online + light hover
         }
         
         return int_1;

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

@@ -26,8 +26,10 @@ import net.minecraft.client.render.DiffuseLighting;
 import net.minecraft.client.render.Tessellator;
 import net.minecraft.client.render.VertexFormats;
 import net.minecraft.client.resource.language.I18n;
+import net.minecraft.client.sound.PositionedSoundInstance;
 import net.minecraft.client.util.InputUtil;
 import net.minecraft.item.ItemGroup;
+import net.minecraft.sound.SoundEvents;
 import net.minecraft.util.ActionResult;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
@@ -42,7 +44,7 @@ public class EntryListWidget extends WidgetWithBounds {
     
     private static final boolean LAZY = true;
     private static final String SPACE = " ", EMPTY = "";
-    private static final Supplier<Boolean> RENDER_EXTRA_CONFIG = ConfigManager.getInstance().getConfig()::doesRenderEntryExtraOverlay;
+    private static final Supplier<Boolean> RENDER_EXTRA_CONFIG = ConfigObject.getInstance()::doesRenderEntryExtraOverlay;
     @SuppressWarnings("deprecation")
     private static final Comparator<? super EntryStack> ENTRY_NAME_COMPARER = Comparator.comparing(SearchArgument::tryGetEntryStackName);
     private static final Comparator<? super EntryStack> ENTRY_GROUP_COMPARER = Comparator.comparingInt(stack -> {
@@ -114,7 +116,7 @@ public class EntryListWidget extends WidgetWithBounds {
     
     @Override
     public boolean mouseScrolled(double double_1, double double_2, double double_3) {
-        if (ConfigManager.getInstance().getConfig().isEntryListWidgetScrolled() && bounds.contains(double_1, double_2)) {
+        if (ConfigObject.getInstance().isEntryListWidgetScrolled() && bounds.contains(double_1, double_2)) {
             offset(ClothConfigInitializer.getScrollStep() * -double_3, true);
             return true;
         }
@@ -143,13 +145,13 @@ public class EntryListWidget extends WidgetWithBounds {
     }
     
     public int getTotalPages() {
-        if (ConfigManager.getInstance().getConfig().isEntryListWidgetScrolled()) return 1;
+        if (ConfigObject.getInstance().isEntryListWidgetScrolled()) return 1;
         return MathHelper.ceil(allStacks.size() / (float) entries.size());
     }
     
     @Override
     public void render(int mouseX, int mouseY, float delta) {
-        if (ConfigManager.getInstance().getConfig().isEntryListWidgetScrolled()) {
+        if (ConfigObject.getInstance().isEntryListWidgetScrolled()) {
             for (EntryListEntry entry : entries) entry.clearStacks();
             ScissorsHandler.INSTANCE.scissor(bounds);
             int sizeForFavorites = getSlotsHeightNumberForFavorites();
@@ -215,7 +217,7 @@ public class EntryListWidget extends WidgetWithBounds {
     }
     
     private int getScrollbarMinX() {
-        if (ConfigManager.getInstance().getConfig().isLeftHandSidePanel())
+        if (ConfigObject.getInstance().isLeftHandSidePanel())
             return bounds.x + 1;
         return bounds.getMaxX() - 7;
     }
@@ -231,7 +233,7 @@ public class EntryListWidget extends WidgetWithBounds {
                 int int_3 = MathHelper.clamp((int) ((float) (int_2 * int_2) / (float) getMaxScrollPosition()), 32, int_2 - 8);
                 double double_6 = Math.max(1.0D, double_5 / (double) (int_2 - int_3));
                 float to = MathHelper.clamp((float) (scroll + double_4 * double_6), 0, height - innerBounds.height);
-                if (ConfigManager.getInstance().getConfig().doesSnapToRows()) {
+                if (ConfigObject.getInstance().doesSnapToRows()) {
                     double nearestRow = Math.round(to / 18.0) * 18.0;
                     scrollTo(nearestRow, false);
                 } else scrollTo(to, false);
@@ -288,7 +290,7 @@ public class EntryListWidget extends WidgetWithBounds {
             target -= target * (1 - ClothConfigInitializer.getBounceBackMultiplier()) * delta / 3;
         } else if (target > getMaxScroll()) {
             target = (target - getMaxScroll()) * (1 - (1 - ClothConfigInitializer.getBounceBackMultiplier()) * delta / 3) + getMaxScroll();
-        } else if (ConfigManager.getInstance().getConfig().doesSnapToRows()) {
+        } else if (ConfigObject.getInstance().doesSnapToRows()) {
             double nearestRow = Math.round(target / 18.0) * 18.0;
             if (!DynamicNewSmoothScrollingEntryListWidget.Precision.almostEquals(target, nearestRow, DynamicNewSmoothScrollingEntryListWidget.Precision.FLOAT_EPSILON))
                 target += (nearestRow - target) * Math.min(delta / 2.0, 1.0);
@@ -321,7 +323,7 @@ public class EntryListWidget extends WidgetWithBounds {
     
     public void updateEntriesPosition() {
         this.innerBounds = updateInnerBounds();
-        if (!ConfigManager.getInstance().getConfig().isEntryListWidgetScrolled()) {
+        if (!ConfigObject.getInstance().isEntryListWidgetScrolled()) {
             page = Math.max(page, 0);
             List<EntryListEntry> entries = Lists.newLinkedList();
             int width = innerBounds.width / 18;
@@ -370,9 +372,9 @@ public class EntryListWidget extends WidgetWithBounds {
     }
     
     private Rectangle updateInnerBounds() {
-        if (ConfigManager.getInstance().getConfig().isEntryListWidgetScrolled()) {
+        if (ConfigObject.getInstance().isEntryListWidgetScrolled()) {
             int width = Math.max(MathHelper.floor((bounds.width - 2 - 6) / 18f), 1);
-            if (ConfigManager.getInstance().getConfig().isLeftHandSidePanel())
+            if (ConfigObject.getInstance().isLeftHandSidePanel())
                 return new Rectangle(bounds.getCenterX() - width * 9 + 3, bounds.y, width * 18, bounds.height);
             return new Rectangle(bounds.getCenterX() - width * 9 - 3, bounds.y, width * 18, bounds.height);
         }
@@ -384,7 +386,7 @@ public class EntryListWidget extends WidgetWithBounds {
     @SuppressWarnings("rawtypes")
     private boolean notSteppingOnExclusionZones(int left, int top, Rectangle listArea) {
         for (DisplayHelper.DisplayBoundsHandler sortedBoundsHandler : DisplayHelper.getInstance().getSortedBoundsHandlers(minecraft.currentScreen.getClass())) {
-            ActionResult fit = sortedBoundsHandler.canItemSlotWidgetFit(!ConfigManager.getInstance().getConfig().isLeftHandSidePanel(), left, top, minecraft.currentScreen, listArea);
+            ActionResult fit = sortedBoundsHandler.canItemSlotWidgetFit(!ConfigObject.getInstance().isLeftHandSidePanel(), left, top, minecraft.currentScreen, listArea);
             if (fit != ActionResult.PASS) return fit == ActionResult.SUCCESS;
         }
         return true;
@@ -405,13 +407,13 @@ public class EntryListWidget extends WidgetWithBounds {
                             .setting(EntryStack.Settings.Item.RENDER_OVERLAY, RENDER_EXTRA_CONFIG));
                 }
             }
-            ItemListOrdering ordering = ConfigManager.getInstance().getConfig().getItemListOrdering();
+            ItemListOrdering ordering = ConfigObject.getInstance().getItemListOrdering();
             if (ordering == ItemListOrdering.name) list.sort(ENTRY_NAME_COMPARER);
             if (ordering == ItemListOrdering.item_groups) list.sort(ENTRY_GROUP_COMPARER);
-            if (!ConfigManager.getInstance().getConfig().isItemListAscending()) Collections.reverse(list);
+            if (!ConfigObject.getInstance().isItemListAscending()) Collections.reverse(list);
             allStacks = list;
         }
-        if (ConfigManager.getInstance().getConfig().isFavoritesEnabled()) {
+        if (ConfigObject.getInstance().isFavoritesEnabled() && !ConfigObject.getInstance().doDisplayFavoritesOnTheLeft()) {
             List<EntryStack> list = Lists.newLinkedList();
             boolean checkCraftable = ConfigManager.getInstance().isCraftableOnlyEnabled() && !ScreenHelper.inventoryStacks.isEmpty();
             List<EntryStack> workingItems = checkCraftable ? RecipeHelper.getInstance().findCraftableEntriesByItems(CollectionUtils.map(ScreenHelper.inventoryStacks, EntryStack::create)) : null;
@@ -423,10 +425,10 @@ public class EntryListWidget extends WidgetWithBounds {
                             .setting(EntryStack.Settings.Item.RENDER_OVERLAY, RENDER_EXTRA_CONFIG));
                 }
             }
-            ItemListOrdering ordering = ConfigManager.getInstance().getConfig().getItemListOrdering();
+            ItemListOrdering ordering = ConfigObject.getInstance().getItemListOrdering();
             if (ordering == ItemListOrdering.name) list.sort(ENTRY_NAME_COMPARER);
             if (ordering == ItemListOrdering.item_groups) list.sort(ENTRY_GROUP_COMPARER);
-            if (!ConfigManager.getInstance().getConfig().isItemListAscending()) Collections.reverse(list);
+            if (!ConfigObject.getInstance().isItemListAscending()) Collections.reverse(list);
             favorites = list;
         } else favorites = Collections.emptyList();
         updateEntriesPosition();
@@ -440,7 +442,7 @@ public class EntryListWidget extends WidgetWithBounds {
     @SuppressWarnings("deprecation")
     private boolean canSearchTermsBeAppliedTo(EntryStack stack, List<SearchArgument.SearchArguments> searchArguments) {
         if (searchArguments.isEmpty()) return true;
-        String mod = null, name = null, tooltip = null;
+        String mod = null, name = null, tooltip = null, tags[] = null;
         for (SearchArgument.SearchArguments arguments : searchArguments) {
             boolean applicable = true;
             for (SearchArgument argument : arguments.getArguments()) {
@@ -466,6 +468,31 @@ public class EntryListWidget extends WidgetWithBounds {
                         applicable = false;
                         break;
                     }
+                } else if (argument.getArgumentType() == SearchArgument.ArgumentType.TAG) {
+                    if (tags == null) {
+                        if (stack.getType() == EntryStack.Type.ITEM) {
+                            Identifier[] tagsFor = minecraft.getNetworkHandler().getTagManager().items().getTagsFor(stack.getItem()).toArray(new Identifier[0]);
+                            tags = new String[tagsFor.length];
+                            for (int i = 0; i < tagsFor.length; i++) tags[i] = tagsFor[i].toString();
+                        } else if (stack.getType() == EntryStack.Type.FLUID) {
+                            Identifier[] tagsFor = minecraft.getNetworkHandler().getTagManager().fluids().getTagsFor(stack.getFluid()).toArray(new Identifier[0]);
+                            tags = new String[tagsFor.length];
+                            for (int i = 0; i < tagsFor.length; i++) tags[i] = tagsFor[i].toString();
+                        } else tags = new String[0];
+                    }
+                    if (tags != null && tags.length > 0) {
+                        boolean a = false;
+                        for (String tag : tags)
+                            if (argument.getFunction(argument.isInclude()).apply(tag))
+                                a = true;
+                        if (!a) {
+                            applicable = false;
+                            break;
+                        }
+                    } else {
+                        applicable = false;
+                        break;
+                    }
                 }
             }
             if (applicable) return true;
@@ -487,6 +514,10 @@ public class EntryListWidget extends WidgetWithBounds {
                         arguments[i] = new SearchArgument(SearchArgument.ArgumentType.MOD, term.substring(2), false);
                     } else if (term.startsWith("@")) {
                         arguments[i] = new SearchArgument(SearchArgument.ArgumentType.MOD, term.substring(1), true);
+                    } else if (term.startsWith("-$") || term.startsWith("$-")) {
+                        arguments[i] = new SearchArgument(SearchArgument.ArgumentType.TAG, term.substring(2), false);
+                    } else if (term.startsWith("$")) {
+                        arguments[i] = new SearchArgument(SearchArgument.ArgumentType.TAG, term.substring(1), true);
                     } else if (term.startsWith("-#") || term.startsWith("#-")) {
                         arguments[i] = new SearchArgument(SearchArgument.ArgumentType.TOOLTIP, term.substring(2), false);
                     } else if (term.startsWith("#")) {
@@ -579,14 +610,14 @@ public class EntryListWidget extends WidgetWithBounds {
             if (!ClientHelper.getInstance().isCheating() || minecraft.player.inventory.getCursorStack().isEmpty()) {
                 QueuedTooltip tooltip = getCurrentTooltip(mouseX, mouseY);
                 if (tooltip != null) {
-                    // TODO Finalize favorites
-//                    if (ConfigManager.getInstance().getConfig().isFavoritesEnabled()) {
-//                        String name = getLocalizedName(ConfigManager.getInstance().getConfig().getFavoriteKeybind());
-//                        if (!isFavorites)
-//                            tooltip.getText().addAll(Arrays.asList(I18n.translate("text.rei.favorites_tooltip", name).split("\n")));
-//                        else
-//                            tooltip.getText().addAll(Arrays.asList(I18n.translate("text.rei.remove_favorites_tooltip", name).split("\n")));
-//                    }
+                    //                    if (ConfigObject.getInstance().doDisplayFavoritesTooltip()) {
+                    if (ConfigObject.getInstance().doDisplayFavoritesTooltip() && !ConfigObject.getInstance().doDisplayFavoritesOnTheLeft()) {
+                        String name = getLocalizedName(ConfigObject.getInstance().getFavoriteKeybind());
+                        if (!isFavorites)
+                            tooltip.getText().addAll(Arrays.asList(I18n.translate("text.rei.favorites_tooltip", name).split("\n")));
+                        else
+                            tooltip.getText().addAll(Arrays.asList(I18n.translate("text.rei.remove_favorites_tooltip", name).split("\n")));
+                    }
                     ScreenHelper.getLastOverlay().addTooltip(tooltip);
                 }
             }
@@ -594,10 +625,8 @@ public class EntryListWidget extends WidgetWithBounds {
         
         @Override
         public boolean keyPressed(int int_1, int int_2, int int_3) {
-            if (!interactable)
-                return false;
-            if (containsMouse(PointHelper.fromMouse()) && !getCurrentEntry().isEmpty()) {
-                InputUtil.KeyCode keyCode = ConfigManager.getInstance().getConfig().getFavoriteKeybind();
+            if (interactable && ConfigObject.getInstance().isFavoritesEnabled() && containsMouse(PointHelper.fromMouse()) && !getCurrentEntry().isEmpty()) {
+                InputUtil.KeyCode keyCode = ConfigObject.getInstance().getFavoriteKeybind();
                 if (int_1 == InputUtil.UNKNOWN_KEYCODE.getKeyCode()) {
                     if (keyCode.getCategory() == InputUtil.Type.SCANCODE && keyCode.getKeyCode() == int_2) {
                         if (!isFavorites) {
@@ -607,6 +636,7 @@ public class EntryListWidget extends WidgetWithBounds {
                             ConfigManager.getInstance().getFavorites().remove(getCurrentEntry());
                             ContainerScreenOverlay.getEntryListWidget().updateSearch(ScreenHelper.getSearchField().getText());
                         }
+                        minecraft.getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
                         return true;
                     }
                 } else if (keyCode.getCategory() == InputUtil.Type.KEYSYM && keyCode.getKeyCode() == int_1) {
@@ -617,6 +647,7 @@ public class EntryListWidget extends WidgetWithBounds {
                         ConfigManager.getInstance().getFavorites().remove(getCurrentEntry());
                         ContainerScreenOverlay.getEntryListWidget().updateSearch(ScreenHelper.getSearchField().getText());
                     }
+                    minecraft.getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
                     return true;
                 }
             }
@@ -630,9 +661,9 @@ public class EntryListWidget extends WidgetWithBounds {
             if (containsMouse(mouseX, mouseY) && ClientHelper.getInstance().isCheating()) {
                 EntryStack entry = getCurrentEntry().copy();
                 if (entry.getType() == EntryStack.Type.ITEM) {
-                    if (ConfigManager.getInstance().getConfig().getItemCheatingMode() == ItemCheatingMode.REI_LIKE)
+                    if (ConfigObject.getInstance().getItemCheatingMode() == ItemCheatingMode.REI_LIKE)
                         entry.setAmount(button != 1 ? 1 : entry.getItemStack().getMaxCount());
-                    else if (ConfigManager.getInstance().getConfig().getItemCheatingMode() == ItemCheatingMode.JEI_LIKE)
+                    else if (ConfigObject.getInstance().getItemCheatingMode() == ItemCheatingMode.JEI_LIKE)
                         entry.setAmount(button != 0 ? 1 : entry.getItemStack().getMaxCount());
                     else
                         entry.setAmount(1);

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

@@ -7,7 +7,7 @@ package me.shedaniel.rei.gui.widget;
 
 import com.mojang.blaze3d.systems.RenderSystem;
 import me.shedaniel.math.api.Rectangle;
-import me.shedaniel.rei.api.ConfigManager;
+import me.shedaniel.rei.api.ConfigObject;
 import me.shedaniel.rei.gui.config.RecipeScreenType;
 import me.shedaniel.rei.impl.ScreenHelper;
 import net.minecraft.client.render.DiffuseLighting;
@@ -86,7 +86,7 @@ public class PanelWidget extends WidgetWithBounds {
     }
     
     protected boolean isRendering() {
-        return ConfigManager.getInstance().getConfig().getRecipeScreenType() != RecipeScreenType.VILLAGER;
+        return ConfigObject.getInstance().getRecipeScreenType() != RecipeScreenType.VILLAGER;
     }
     
     protected int getInnerColor() {
@@ -98,7 +98,7 @@ public class PanelWidget extends WidgetWithBounds {
     }
     
     protected int getYTextureOffset() {
-        return ConfigManager.getInstance().getConfig().isUsingLightGrayRecipeBorder() ? 0 : 66;
+        return ConfigObject.getInstance().isUsingLightGrayRecipeBorder() ? 0 : 66;
     }
     
 }

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

@@ -10,7 +10,6 @@ import com.google.common.collect.Lists;
 import me.shedaniel.math.api.Point;
 import me.shedaniel.math.impl.PointHelper;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
 

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

@@ -6,7 +6,7 @@
 package me.shedaniel.rei.gui.widget;
 
 import me.shedaniel.math.api.Rectangle;
-import me.shedaniel.rei.api.ConfigManager;
+import me.shedaniel.rei.api.ConfigObject;
 
 public class RecipeBaseWidget extends PanelWidget {
     
@@ -16,7 +16,7 @@ public class RecipeBaseWidget extends PanelWidget {
     
     @Override
     protected int getYTextureOffset() {
-        return ConfigManager.getInstance().getConfig().isUsingLightGrayRecipeBorder() ? 0 : 66;
+        return ConfigObject.getInstance().isUsingLightGrayRecipeBorder() ? 0 : 66;
     }
     
 }

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

@@ -138,12 +138,12 @@ public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
     
     @Override
     public boolean isCheating() {
-        return ConfigManager.getInstance().getConfig().isCheating();
+        return ConfigObject.getInstance().isCheating();
     }
     
     @Override
     public void setCheating(boolean cheating) {
-        ConfigManager.getInstance().getConfig().setCheating(cheating);
+        ConfigObject.getInstance().setCheating(cheating);
         ConfigManager.getInstance().saveConfig();
     }
     
@@ -173,7 +173,7 @@ public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
             if (identifier == null)
                 return false;
             String tagMessage = cheatedStack.copy().getTag() != null && !cheatedStack.copy().getTag().isEmpty() ? cheatedStack.copy().getTag().asString() : "";
-            String og = cheatedStack.getCount() == 1 ? ConfigManager.getInstance().getConfig().getGiveCommand().replaceAll(" \\{count}", "") : ConfigManager.getInstance().getConfig().getGiveCommand();
+            String og = cheatedStack.getCount() == 1 ? ConfigObject.getInstance().getGiveCommand().replaceAll(" \\{count}", "") : ConfigObject.getInstance().getGiveCommand();
             String madeUpCommand = og.replaceAll("\\{player_name}", MinecraftClient.getInstance().player.getEntityName()).replaceAll("\\{item_name}", identifier.getPath()).replaceAll("\\{item_identifier}", identifier.toString()).replaceAll("\\{nbt}", tagMessage).replaceAll("\\{count}", String.valueOf(cheatedStack.getCount()));
             if (madeUpCommand.length() > 256) {
                 madeUpCommand = og.replaceAll("\\{player_name}", MinecraftClient.getInstance().player.getEntityName()).replaceAll("\\{item_name}", identifier.getPath()).replaceAll("\\{item_identifier}", identifier.toString()).replaceAll("\\{nbt}", "").replaceAll("\\{count}", String.valueOf(cheatedStack.getCount()));
@@ -250,9 +250,9 @@ public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
     @Override
     public void openRecipeViewingScreen(Map<RecipeCategory<?>, List<RecipeDisplay>> map) {
         Screen screen = null;
-        if (ConfigManager.getInstance().getConfig().getRecipeScreenType() == RecipeScreenType.VILLAGER)
+        if (ConfigObject.getInstance().getRecipeScreenType() == RecipeScreenType.VILLAGER)
             screen = new VillagerRecipeViewingScreen(map);
-        else if (ConfigManager.getInstance().getConfig().getRecipeScreenType() == RecipeScreenType.UNSET)
+        else if (ConfigObject.getInstance().getRecipeScreenType() == RecipeScreenType.UNSET)
             screen = new PreRecipeViewingScreen(map);
         else
             screen = new RecipeViewingScreen(map);

+ 8 - 0
src/main/java/me/shedaniel/rei/impl/ConfigManagerImpl.java

@@ -79,6 +79,14 @@ public class ConfigManagerImpl implements ConfigManager {
             entries.add(entry);
             return entries;
         }, field -> field.getType() == InputUtil.KeyCode.class, ConfigObject.AddInFrontKeyCode.class);
+        guiRegistry.registerPredicateProvider((i13n, field, config, defaults, guiProvider) -> {
+            KeyCodeEntry entry = ConfigEntryBuilder.create().startKeyCodeField(i13n, getUnsafely(field, config, null))
+                    .setDefaultValue(() -> getUnsafely(field, defaults))
+                    .setSaveConsumer(newValue -> setUnsafely(field, config, newValue))
+                    .build();
+            entry.setAllowMouse(false);
+            return Collections.singletonList(entry);
+        }, field -> field.getType() == InputUtil.KeyCode.class);
         loadFavoredEntries();
         RoughlyEnoughItemsCore.LOGGER.info("[REI] Config is loaded.");
     }

+ 12 - 0
src/main/java/me/shedaniel/rei/impl/ConfigObjectImpl.java

@@ -204,6 +204,16 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData {
         return general.favoritesEnabled;
     }
     
+    @Override
+    public boolean doDisplayFavoritesTooltip() {
+        return isFavoritesEnabled() && appearance.displayFavoritesTooltip;
+    }
+    
+    @Override
+    public boolean doDisplayFavoritesOnTheLeft() {
+        return appearance.displayFavoritesOnTheLeft;
+    }
+    
     @Override
     public InputUtil.KeyCode getFavoriteKeybind() {
         return general.favoriteKeybind;
@@ -255,6 +265,8 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData {
         @Comment("Declares whether if entry list widget is scrolled.")
         private boolean scrollingEntryListWidget = false;
         private boolean snapToRows = false;
+        private boolean displayFavoritesOnTheLeft = true;
+        private boolean displayFavoritesTooltip = true;
     }
     
     public static class Technical {

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

@@ -8,7 +8,7 @@ package me.shedaniel.rei.impl;
 import com.google.common.collect.Lists;
 import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.rei.api.ClientHelper;
-import me.shedaniel.rei.api.ConfigManager;
+import me.shedaniel.rei.api.ConfigObject;
 import me.shedaniel.rei.api.EntryStack;
 import me.shedaniel.rei.gui.widget.QueuedTooltip;
 import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler;
@@ -157,7 +157,7 @@ public class FluidEntryStack extends AbstractEntryStack {
             if (amountTooltip != null) for (String s : amountTooltip.split("\n")) toolTip.add(s);
         }
         toolTip.addAll(getSetting(Settings.TOOLTIP_APPEND_EXTRA).value().apply(this));
-        if (getSetting(Settings.TOOLTIP_APPEND_MOD).value().get() && ConfigManager.getInstance().getConfig().shouldAppendModNames()) {
+        if (getSetting(Settings.TOOLTIP_APPEND_MOD).value().get() && ConfigObject.getInstance().shouldAppendModNames()) {
             final String modString = ClientHelper.getInstance().getFormattedModFromIdentifier(Registry.FLUID.getId(fluid));
             boolean alreadyHasMod = false;
             for (String s : toolTip)

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

@@ -9,7 +9,7 @@ import com.google.common.collect.Lists;
 import com.mojang.blaze3d.systems.RenderSystem;
 import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.rei.api.ClientHelper;
-import me.shedaniel.rei.api.ConfigManager;
+import me.shedaniel.rei.api.ConfigObject;
 import me.shedaniel.rei.api.EntryStack;
 import me.shedaniel.rei.api.ItemStackRenderOverlayHook;
 import me.shedaniel.rei.gui.widget.QueuedTooltip;
@@ -123,7 +123,7 @@ public class ItemEntryStack extends AbstractEntryStack {
             return null;
         List<String> toolTip = Lists.newArrayList(SearchArgument.tryGetItemStackToolTip(getItemStack(), true));
         toolTip.addAll(getSetting(Settings.TOOLTIP_APPEND_EXTRA).value().apply(this));
-        if (getSetting(Settings.TOOLTIP_APPEND_MOD).value().get() && ConfigManager.getInstance().getConfig().shouldAppendModNames()) {
+        if (getSetting(Settings.TOOLTIP_APPEND_MOD).value().get() && ConfigObject.getInstance().shouldAppendModNames()) {
             final String modString = ClientHelper.getInstance().getFormattedModFromItem(getItem());
             boolean alreadyHasMod = false;
             for (String s : toolTip)

+ 5 - 3
src/main/java/me/shedaniel/rei/impl/ScreenHelper.java

@@ -10,6 +10,7 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import me.shedaniel.cloth.hooks.ClothClientHooks;
 import me.shedaniel.rei.api.ConfigManager;
+import me.shedaniel.rei.api.ConfigObject;
 import me.shedaniel.rei.gui.ContainerScreenOverlay;
 import me.shedaniel.rei.gui.OverlaySearchField;
 import me.shedaniel.rei.listeners.ContainerScreenHooks;
@@ -70,11 +71,11 @@ public class ScreenHelper implements ClientModInitializer {
     }
     
     public static boolean isOverlayVisible() {
-        return ConfigManager.getInstance().getConfig().isOverlayVisible();
+        return ConfigObject.getInstance().isOverlayVisible();
     }
     
     public static void toggleOverlayVisible() {
-        ConfigManager.getInstance().getConfig().setOverlayVisible(!ConfigManager.getInstance().getConfig().isOverlayVisible());
+        ConfigObject.getInstance().setOverlayVisible(!ConfigObject.getInstance().isOverlayVisible());
         ConfigManager.getInstance().saveConfig();
     }
     
@@ -86,6 +87,7 @@ public class ScreenHelper implements ClientModInitializer {
         if (overlay == null || reset) {
             overlay = new ContainerScreenOverlay();
             overlay.init();
+            getSearchField().setFocused(false);
         }
         return overlay;
     }
@@ -122,7 +124,7 @@ public class ScreenHelper implements ClientModInitializer {
     }
     
     public static boolean isDarkModeEnabled() {
-        return ConfigManager.getInstance().getConfig().isUsingDarkTheme();
+        return ConfigObject.getInstance().isUsingDarkTheme();
     }
     
     @Override

+ 1 - 0
src/main/java/me/shedaniel/rei/impl/SearchArgument.java

@@ -127,6 +127,7 @@ public class SearchArgument {
         TEXT,
         MOD,
         TOOLTIP,
+        TAG,
         ALWAYS
     }
     

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

@@ -5,7 +5,7 @@
 
 package me.shedaniel.rei.plugin;
 
-import me.shedaniel.rei.api.ConfigManager;
+import me.shedaniel.rei.api.ConfigObject;
 import me.shedaniel.rei.api.RecipeHelper;
 import me.shedaniel.rei.api.plugins.REIPluginV0;
 import me.shedaniel.rei.plugin.autocrafting.DefaultCategoryHandler;
@@ -30,7 +30,7 @@ public class DefaultAutoCraftingPlugin implements REIPluginV0 {
     
     @Override
     public void registerOthers(RecipeHelper recipeHelper) {
-        if (!ConfigManager.getInstance().getConfig().isLoadingDefaultPlugin()) {
+        if (!ConfigObject.getInstance().isLoadingDefaultPlugin()) {
             return;
         }
         recipeHelper.registerAutoCraftingHandler(new DefaultCategoryHandler());

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

@@ -90,7 +90,7 @@ public class DefaultPlugin implements REIPluginV0 {
     
     @Override
     public void registerEntries(EntryRegistry entryRegistry) {
-        if (!ConfigManager.getInstance().getConfig().isLoadingDefaultPlugin()) {
+        if (!ConfigObject.getInstance().isLoadingDefaultPlugin()) {
             return;
         }
         for (Item item : Registry.ITEM) {
@@ -119,7 +119,7 @@ public class DefaultPlugin implements REIPluginV0 {
     
     @Override
     public void registerPluginCategories(RecipeHelper recipeHelper) {
-        if (!ConfigManager.getInstance().getConfig().isLoadingDefaultPlugin()) {
+        if (!ConfigObject.getInstance().isLoadingDefaultPlugin()) {
             return;
         }
         recipeHelper.registerCategory(new DefaultCraftingCategory());
@@ -135,7 +135,7 @@ public class DefaultPlugin implements REIPluginV0 {
     
     @Override
     public void registerRecipeDisplays(RecipeHelper recipeHelper) {
-        if (!ConfigManager.getInstance().getConfig().isLoadingDefaultPlugin()) {
+        if (!ConfigObject.getInstance().isLoadingDefaultPlugin()) {
             return;
         }
         recipeHelper.registerRecipes(CRAFTING, ShapelessRecipe.class, DefaultShapelessDisplay::new);
@@ -213,7 +213,7 @@ public class DefaultPlugin implements REIPluginV0 {
     
     @Override
     public void registerBounds(DisplayHelper displayHelper) {
-        if (!ConfigManager.getInstance().getConfig().isLoadingDefaultPlugin()) {
+        if (!ConfigObject.getInstance().isLoadingDefaultPlugin()) {
             return;
         }
         displayHelper.getBaseBoundsHandler().registerExclusionZones(AbstractInventoryScreen.class, new DefaultPotionEffectExclusionZones());
@@ -293,7 +293,7 @@ public class DefaultPlugin implements REIPluginV0 {
     
     @Override
     public void registerOthers(RecipeHelper recipeHelper) {
-        if (!ConfigManager.getInstance().getConfig().isLoadingDefaultPlugin()) {
+        if (!ConfigObject.getInstance().isLoadingDefaultPlugin()) {
             return;
         }
         recipeHelper.registerWorkingStations(CRAFTING, EntryStack.create(Items.CRAFTING_TABLE));

+ 4 - 0
src/main/resources/assets/roughlyenoughitems/lang/en_us.json

@@ -147,6 +147,10 @@
   "config.roughlyenoughitems.itemCheatingMode.rei_like": "Normal",
   "config.roughlyenoughitems.itemCheatingMode.jei_like": "Inverted",
   "config.roughlyenoughitems.appendModNames": "Append Mod Names:",
+  "config.roughlyenoughitems.displayFavoritesOnTheLeft": "Favorites Position:",
+  "config.roughlyenoughitems.displayFavoritesOnTheLeft.boolean.true": "Opposite Side",
+  "config.roughlyenoughitems.displayFavoritesOnTheLeft.boolean.false": "Top of list",
+  "config.roughlyenoughitems.displayFavoritesTooltip": "Display Favorites Tooltips:",
   "config.roughlyenoughitems.snapToRows": "Entry List Snap To Rows:",
   "config.roughlyenoughitems.toastDisplayedOnCopyIdentifier": "Copy Identifier Toast:",
   "config.roughlyenoughitems.scrollingEntryListWidget": "Entry List Action:",