Procházet zdrojové kódy

3.0.7

Signed-off-by: shedaniel <daniel@shedaniel.me>
shedaniel před 5 roky
rodič
revize
8ed44e1554
43 změnil soubory, kde provedl 424 přidání a 233 odebrání
  1. 21 0
      .github/workflows/curseforge.yml
  2. 0 2
      .github/workflows/gradle.yml
  3. 1 1
      JenkinsD
  4. 1 1
      build.gradle
  5. 3 3
      gradle.properties
  6. 3 3
      src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java
  7. 1 2
      src/main/java/me/shedaniel/rei/api/ClientHelper.java
  8. 42 0
      src/main/java/me/shedaniel/rei/api/REIHelper.java
  9. 1 2
      src/main/java/me/shedaniel/rei/api/RecipeCategory.java
  10. 3 2
      src/main/java/me/shedaniel/rei/gui/OverlaySearchField.java
  11. 1 1
      src/main/java/me/shedaniel/rei/gui/RecipeDisplayExporter.java
  12. 8 1
      src/main/java/me/shedaniel/rei/gui/RecipeScreen.java
  13. 48 47
      src/main/java/me/shedaniel/rei/gui/RecipeViewingScreen.java
  14. 55 57
      src/main/java/me/shedaniel/rei/gui/VillagerRecipeViewingScreen.java
  15. 3 2
      src/main/java/me/shedaniel/rei/gui/config/entry/FilteringEntry.java
  16. 41 3
      src/main/java/me/shedaniel/rei/gui/credits/CreditsEntryListWidget.java
  17. 30 13
      src/main/java/me/shedaniel/rei/gui/credits/CreditsScreen.java
  18. 2 1
      src/main/java/me/shedaniel/rei/gui/widget/ButtonWidget.java
  19. 2 1
      src/main/java/me/shedaniel/rei/gui/widget/ClickableLabelWidget.java
  20. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java
  21. 3 6
      src/main/java/me/shedaniel/rei/gui/widget/EntryWidget.java
  22. 4 4
      src/main/java/me/shedaniel/rei/gui/widget/FavoritesListWidget.java
  23. 2 1
      src/main/java/me/shedaniel/rei/gui/widget/LabelWidget.java
  24. 3 3
      src/main/java/me/shedaniel/rei/gui/widget/PanelWidget.java
  25. 3 3
      src/main/java/me/shedaniel/rei/gui/widget/RecipeChoosePageWidget.java
  26. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/SlotBaseWidget.java
  27. 14 10
      src/main/java/me/shedaniel/rei/gui/widget/TabWidget.java
  28. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/TextFieldWidget.java
  29. 27 14
      src/main/java/me/shedaniel/rei/impl/ClientHelperImpl.java
  30. 0 1
      src/main/java/me/shedaniel/rei/impl/DisplayHelperImpl.java
  31. 0 5
      src/main/java/me/shedaniel/rei/impl/ItemEntryStack.java
  32. 2 2
      src/main/java/me/shedaniel/rei/impl/RecipeHelperImpl.java
  33. 43 13
      src/main/java/me/shedaniel/rei/impl/ScreenHelper.java
  34. 1 2
      src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java
  35. 0 1
      src/main/java/me/shedaniel/rei/plugin/beacon/DefaultBeaconBaseDisplay.java
  36. 2 2
      src/main/java/me/shedaniel/rei/plugin/campfire/DefaultCampfireCategory.java
  37. 2 2
      src/main/java/me/shedaniel/rei/plugin/cooking/DefaultCookingCategory.java
  38. 2 2
      src/main/java/me/shedaniel/rei/plugin/fuel/DefaultFuelCategory.java
  39. 3 3
      src/main/java/me/shedaniel/rei/plugin/information/DefaultInformationCategory.java
  40. 1 1
      src/main/java/me/shedaniel/rei/utils/CollectionUtils.java
  41. 15 1
      src/main/resources/assets/roughlyenoughitems/lang/en_us.json
  42. 15 1
      src/main/resources/assets/roughlyenoughitems/lang/zh_tw.json
  43. 10 8
      src/main/resources/fabric.mod.json

+ 21 - 0
.github/workflows/curseforge.yml

@@ -0,0 +1,21 @@
+name: Java CI
+
+on:
+  push:
+    branches:
+      - 3.x
+      - 4.x-unstable
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v1
+      - name: Set up JDK 1.8
+        uses: actions/setup-java@v1
+        with:
+          java-version: 1.8
+      - name: Upload to CurseForge
+        run: ./gradlew clean build curseforge --refresh-dependencies --stacktrace
+        env:
+          CF_API_KEY: ${{ secrets.CF_API_KEY }}

+ 0 - 2
.github/workflows/gradle.yml

@@ -4,9 +4,7 @@ on: [push]
 
 jobs:
   build:
-
     runs-on: ubuntu-latest
-
     steps:
     - uses: actions/checkout@v1
     - name: Set up JDK 1.8

+ 1 - 1
JenkinsD

@@ -9,7 +9,7 @@ pipeline {
          steps {
             sh "rm -rf build/libs/"
             sh "chmod +x gradlew"
-            sh "./gradlew clean build publish curseforge --refresh-dependencies --stacktrace"
+            sh "./gradlew clean build publish --refresh-dependencies --stacktrace"
 
             archiveArtifacts artifacts: '**/build/libs/*.jar', fingerprint: true
          }

+ 1 - 1
build.gradle

@@ -16,7 +16,7 @@ group = "me.shedaniel"
 
 def forceVersion = ""
 
-version = forceVersion != "" ? forceVersion : ((project.mod_version as String).contains("unstable") ? (project.mod_version + "." + buildTime()) : project.mod_version)
+version = forceVersion != "" ? forceVersion : project.mod_version
 
 def includeDep = true
 

+ 3 - 3
gradle.properties

@@ -1,9 +1,9 @@
-mod_version=4.0.6-unstable
+mod_version=4.0.7-unstable
 minecraft_version=20w10a
 yarn_version=20w10a+build.2
 fabricloader_version=0.7.8+build.186
-cloth_events_version=1.2.0
-cloth_config_version=3.0.0-unstable.202003050821
+cloth_events_version=2.0.0-unstable.202003051905
+cloth_config_version=3.0.1-unstable.202003051928
 modmenu_version=1.10.1+build.30
 fabric_api=0.4.34+build.303-1.16
 autoconfig1u=1.2.4

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

@@ -276,14 +276,14 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer {
             if (screen instanceof ContainerScreen)
                 ScreenHelper.setLastContainerScreen((ContainerScreen<?>) screen);
             boolean alreadyAdded = false;
-            for (Element element : Lists.newArrayList(screenHooks.cloth_getInputListeners()))
+            for (Element element : Lists.newArrayList(screenHooks.cloth_getChildren()))
                 if (ContainerScreenOverlay.class.isAssignableFrom(element.getClass()))
                     if (alreadyAdded)
-                        screenHooks.cloth_getInputListeners().remove(element);
+                        screenHooks.cloth_getChildren().remove(element);
                     else
                         alreadyAdded = true;
             if (!alreadyAdded)
-                screenHooks.cloth_getInputListeners().add(ScreenHelper.getLastOverlay(true, false));
+                screenHooks.cloth_getChildren().add(ScreenHelper.getLastOverlay(true, false));
         });
         ClothClientHooks.SCREEN_RENDER_POST.register((minecraftClient, screen, i, i1, v) -> {
             if (shouldReturn(screen.getClass()))

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

@@ -36,9 +36,8 @@ public interface ClientHelper {
     /**
      * @return the api instance of {@link ClientHelperImpl}
      */
-    @SuppressWarnings("deprecation")
     static ClientHelper getInstance() {
-        return ClientHelperImpl.instance;
+        return ClientHelperImpl.getInstance();
     }
     
     /**

+ 42 - 0
src/main/java/me/shedaniel/rei/api/REIHelper.java

@@ -0,0 +1,42 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.api;
+
+import me.shedaniel.rei.gui.widget.TextFieldWidget;
+import me.shedaniel.rei.impl.ScreenHelper;
+import net.minecraft.item.ItemStack;
+
+import java.util.List;
+
+public interface REIHelper {
+    static REIHelper getInstance() {
+        return ScreenHelper.getInstance();
+    }
+    
+    boolean isDarkThemeEnabled();
+    
+    TextFieldWidget getSearchTextField();
+    
+    List<ItemStack> getInventoryStacks();
+}

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

@@ -30,7 +30,6 @@ import me.shedaniel.rei.gui.entries.SimpleRecipeEntry;
 import me.shedaniel.rei.gui.widget.PanelWidget;
 import me.shedaniel.rei.gui.widget.RecipeBaseWidget;
 import me.shedaniel.rei.gui.widget.Widget;
-import me.shedaniel.rei.impl.ScreenHelper;
 import net.minecraft.client.gui.DrawableHelper;
 import net.minecraft.util.Identifier;
 import org.jetbrains.annotations.ApiStatus;
@@ -99,7 +98,7 @@ public interface RecipeCategory<T extends RecipeDisplay> {
     @ApiStatus.OverrideOnly
     default void drawCategoryBackground(Rectangle bounds, int mouseX, int mouseY, float delta) {
         PanelWidget.render(bounds, -1);
-        if (ScreenHelper.isDarkModeEnabled()) {
+        if (REIHelper.getInstance().isDarkThemeEnabled()) {
             DrawableHelper.fill(bounds.x + 17, bounds.y + 5, bounds.x + bounds.width - 17, bounds.y + 17, 0xFF404040);
             DrawableHelper.fill(bounds.x + 17, bounds.y + 19, bounds.x + bounds.width - 17, bounds.y + 31, 0xFF404040);
         } else {

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

@@ -27,6 +27,7 @@ import com.google.common.collect.Lists;
 import com.mojang.blaze3d.systems.RenderSystem;
 import me.shedaniel.math.api.Point;
 import me.shedaniel.math.impl.PointHelper;
+import me.shedaniel.rei.api.REIHelper;
 import me.shedaniel.rei.gui.widget.TextFieldWidget;
 import me.shedaniel.rei.impl.ScreenHelper;
 import net.minecraft.client.MinecraftClient;
@@ -73,7 +74,7 @@ public class OverlaySearchField extends TextFieldWidget {
     
     public void laterRender(int int_1, int int_2, float float_1) {
         RenderSystem.disableDepthTest();
-        setEditableColor(isMain && ContainerScreenOverlay.getEntryListWidget().getAllStacks().isEmpty() && !getText().isEmpty() ? 16733525 : isSearching && isMain ? -852212 : (containsMouse(PointHelper.fromMouse()) || isFocused()) ? (ScreenHelper.isDarkModeEnabled() ? -17587 : -1) : -6250336);
+        setEditableColor(isMain && ContainerScreenOverlay.getEntryListWidget().getAllStacks().isEmpty() && !getText().isEmpty() ? 16733525 : isSearching && isMain ? -852212 : (containsMouse(PointHelper.fromMouse()) || isFocused()) ? (REIHelper.getInstance().isDarkThemeEnabled() ? -17587 : -1) : -6250336);
         setSuggestion(!isFocused() && getText().isEmpty() ? I18n.translate("text.rei.search.field.suggestion") : null);
         super.render(int_1, int_2, float_1);
         RenderSystem.enableDepthTest();
@@ -82,7 +83,7 @@ public class OverlaySearchField extends TextFieldWidget {
     @Override
     protected void renderSuggestion(int x, int y) {
         if (containsMouse(PointHelper.fromMouse()) || isFocused())
-            this.font.drawWithShadow(this.font.trimToWidth(this.getSuggestion(), this.getWidth()), x, y, ScreenHelper.isDarkModeEnabled() ? 0xccddaa3d : 0xddeaeaea);
+            this.font.drawWithShadow(this.font.trimToWidth(this.getSuggestion(), this.getWidth()), x, y, REIHelper.getInstance().isDarkThemeEnabled() ? 0xccddaa3d : 0xddeaeaea);
         else
             this.font.drawWithShadow(this.font.trimToWidth(this.getSuggestion(), this.getWidth()), x, y, -6250336);
     }

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

@@ -79,7 +79,7 @@ public final class RecipeDisplayExporter extends Widget {
     private void exportRecipe(Rectangle rectangle, List<Widget> widgets) {
         Framebuffer framebuffer = new Framebuffer(rectangle.width * 8, rectangle.height * 8, true, MinecraftClient.IS_SYSTEM_MAC);
         framebuffer.setClearColor(0, 0, 0, 0);
-        //        int color = ScreenHelper.isDarkModeEnabled() ? -13750738 : -3750202;
+        //        int color = REIHelper.getInstance().isDarkThemeEnabled() ? -13750738 : -3750202;
         //        framebuffer.setClearColor(((color >> 16) & 0xFF) / 255f, ((color >> 8) & 0xFF) / 255f, (color & 0xFF) / 255f, ((color >> 24) & 0xFF) / 255f);
         framebuffer.clear(MinecraftClient.IS_SYSTEM_MAC);
         RenderSystem.pushMatrix();

+ 8 - 1
src/main/java/me/shedaniel/rei/gui/StackToNoticeScreen.java → src/main/java/me/shedaniel/rei/gui/RecipeScreen.java

@@ -24,13 +24,20 @@
 package me.shedaniel.rei.gui;
 
 import me.shedaniel.rei.api.EntryStack;
+import net.minecraft.util.Identifier;
 import org.jetbrains.annotations.ApiStatus;
 
 @ApiStatus.Internal
-public interface StackToNoticeScreen {
+public interface RecipeScreen {
     @ApiStatus.Internal
     void addIngredientStackToNotice(EntryStack stack);
     
     @ApiStatus.Internal
     void addResultStackToNotice(EntryStack stack);
+    
+    @ApiStatus.Internal
+    Identifier getCurrentCategory();
+    
+    @ApiStatus.Internal
+    void recalculateCategoryPage();
 }

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

@@ -32,6 +32,7 @@ import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.math.impl.PointHelper;
 import me.shedaniel.rei.api.*;
 import me.shedaniel.rei.gui.widget.*;
+import me.shedaniel.rei.impl.ClientHelperImpl;
 import me.shedaniel.rei.impl.ScreenHelper;
 import me.shedaniel.rei.utils.CollectionUtils;
 import net.minecraft.client.MinecraftClient;
@@ -41,11 +42,11 @@ import net.minecraft.client.render.Tessellator;
 import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.client.sound.PositionedSoundInstance;
+import net.minecraft.client.util.NarratorManager;
 import net.minecraft.client.util.Window;
 import net.minecraft.client.util.math.Matrix4f;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.sound.SoundEvents;
-import net.minecraft.text.LiteralText;
 import net.minecraft.text.TranslatableText;
 import net.minecraft.util.Formatting;
 import net.minecraft.util.Identifier;
@@ -57,46 +58,47 @@ import java.util.*;
 import java.util.function.Supplier;
 
 @ApiStatus.Internal
-public class RecipeViewingScreen extends Screen implements StackToNoticeScreen {
+public class RecipeViewingScreen extends Screen implements RecipeScreen {
     
     public static final Identifier CHEST_GUI_TEXTURE = new Identifier("roughlyenoughitems", "textures/gui/recipecontainer.png");
-    private final List<Widget> preWidgets;
-    private final List<Widget> widgets;
-    private final Map<Rectangle, List<Widget>> recipeBounds;
-    private final List<TabWidget> tabs;
+    private final List<Widget> preWidgets = Lists.newArrayList();
+    private final List<Widget> widgets = Lists.newArrayList();
+    private final Map<Rectangle, List<Widget>> recipeBounds = Maps.newHashMap();
+    private final List<TabWidget> tabs = Lists.newArrayList();
     private final Map<RecipeCategory<?>, List<RecipeDisplay>> categoriesMap;
     private final List<RecipeCategory<?>> categories;
+    private final RecipeCategory<RecipeDisplay> selectedCategory;
     public int guiWidth;
     public int guiHeight;
-    public int page, categoryPages;
+    public int page;
+    public int categoryPages = -1;
     public int largestWidth, largestHeight;
-    public boolean choosePageActivated;
+    public boolean choosePageActivated = false;
     public RecipeChoosePageWidget recipeChoosePageWidget;
     private int tabsPerPage = 5;
     private Rectangle bounds;
-    @Nullable private CategoryBaseWidget workingStationsBaseWidget;
-    private RecipeCategory<RecipeDisplay> selectedCategory;
+    @Nullable
+    private CategoryBaseWidget workingStationsBaseWidget;
     private ButtonWidget recipeBack, recipeNext, categoryBack, categoryNext;
     private EntryStack ingredientStackToNotice = EntryStack.empty();
     private EntryStack resultStackToNotice = EntryStack.empty();
     
-    public RecipeViewingScreen(Map<RecipeCategory<?>, List<RecipeDisplay>> categoriesMap) {
-        super(new LiteralText(""));
-        this.categoryPages = 0;
-        this.preWidgets = Lists.newArrayList();
-        this.widgets = Lists.newArrayList();
-        this.recipeBounds = Maps.newHashMap();
+    public RecipeViewingScreen(Map<RecipeCategory<?>, List<RecipeDisplay>> categoriesMap, @Nullable Identifier category) {
+        super(NarratorManager.EMPTY);
         Window window = MinecraftClient.getInstance().getWindow();
         this.bounds = new Rectangle(window.getScaledWidth() / 2 - guiWidth / 2, window.getScaledHeight() / 2 - guiHeight / 2, 176, 150);
         this.categoriesMap = categoriesMap;
-        this.categories = Lists.newArrayList();
-        for (RecipeCategory<?> category : RecipeHelper.getInstance().getAllCategories()) {
-            if (categoriesMap.containsKey(category))
-                categories.add(category);
+        this.categories = Lists.newArrayList(categoriesMap.keySet());
+        RecipeCategory<?> selected = categories.get(0);
+        if (category != null) {
+            for (RecipeCategory<?> recipeCategory : categories) {
+                if (recipeCategory.getIdentifier().equals(category)) {
+                    selected = recipeCategory;
+                    break;
+                }
+            }
         }
-        this.selectedCategory = (RecipeCategory<RecipeDisplay>) categories.get(0);
-        this.tabs = new ArrayList<>();
-        this.choosePageActivated = false;
+        this.selectedCategory = (RecipeCategory<RecipeDisplay>) selected;
     }
     
     @ApiStatus.Internal
@@ -138,6 +140,16 @@ public class RecipeViewingScreen extends Screen implements StackToNoticeScreen {
         this.resultStackToNotice = stack;
     }
     
+    @Override
+    public Identifier getCurrentCategory() {
+        return selectedCategory.getIdentifier();
+    }
+    
+    @Override
+    public void recalculateCategoryPage() {
+        this.categoryPages = -1;
+    }
+    
     @Nullable
     public CategoryBaseWidget getWorkingStationsBaseWidget() {
         return workingStationsBaseWidget;
@@ -202,10 +214,13 @@ public class RecipeViewingScreen extends Screen implements StackToNoticeScreen {
         this.widgets.clear();
         this.largestWidth = width - 100;
         this.largestHeight = Math.max(height - 36, 100);
-        int maxWidthDisplay = CollectionUtils.mapAndMax(getCurrentDisplayed(), display -> selectedCategory.getDisplayWidth(display), (Comparator<Integer>) Comparator.naturalOrder()).orElse(150);
+        int maxWidthDisplay = CollectionUtils.mapAndMax(getCurrentDisplayed(), selectedCategory::getDisplayWidth, Comparator.naturalOrder()).orElse(150);
         this.guiWidth = maxWidthDisplay + 20;
         this.guiHeight = MathHelper.floor(MathHelper.clamp((double) (selectedCategory.getDisplayHeight() + 4) * (getRecipesPerPage() + 1) + 36, 100, largestHeight));
         this.tabsPerPage = Math.max(5, MathHelper.floor((guiWidth - 20d) / tabSize));
+        if (this.categoryPages == -1) {
+            this.categoryPages = Math.max(0, categories.indexOf(selectedCategory) / tabsPerPage);
+        }
         this.bounds = new Rectangle(width / 2 - guiWidth / 2, height / 2 - guiHeight / 2, guiWidth, guiHeight);
         this.page = MathHelper.clamp(page, 0, getTotalPages(selectedCategory) - 1);
         ButtonWidget w, w2;
@@ -227,10 +242,7 @@ public class RecipeViewingScreen extends Screen implements StackToNoticeScreen {
             currentCategoryIndex--;
             if (currentCategoryIndex < 0)
                 currentCategoryIndex = categories.size() - 1;
-            selectedCategory = (RecipeCategory<RecipeDisplay>) categories.get(currentCategoryIndex);
-            categoryPages = MathHelper.floor(currentCategoryIndex / (double) tabsPerPage);
-            page = 0;
-            RecipeViewingScreen.this.init();
+            ClientHelperImpl.getInstance().openRecipeViewingScreen(categoriesMap, categories.get(currentCategoryIndex).getIdentifier(), ingredientStackToNotice, resultStackToNotice);
         }).tooltip(() -> I18n.translate("text.rei.previous_category")));
         widgets.add(LabelWidget.createClickable(new Point(bounds.getCenterX(), bounds.getY() + 7), selectedCategory.getCategoryName(), clickableLabelWidget -> {
             MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
@@ -241,10 +253,7 @@ public class RecipeViewingScreen extends Screen implements StackToNoticeScreen {
             currentCategoryIndex++;
             if (currentCategoryIndex >= categories.size())
                 currentCategoryIndex = 0;
-            selectedCategory = (RecipeCategory<RecipeDisplay>) categories.get(currentCategoryIndex);
-            categoryPages = MathHelper.floor(currentCategoryIndex / (double) tabsPerPage);
-            page = 0;
-            RecipeViewingScreen.this.init();
+            ClientHelperImpl.getInstance().openRecipeViewingScreen(categoriesMap, categories.get(currentCategoryIndex).getIdentifier(), ingredientStackToNotice, resultStackToNotice);
         }).tooltip(() -> I18n.translate("text.rei.next_category")));
         categoryBack.enabled = categories.size() > 1;
         categoryNext.enabled = categories.size() > 1;
@@ -281,21 +290,13 @@ public class RecipeViewingScreen extends Screen implements StackToNoticeScreen {
             int j = i + categoryPages * tabsPerPage;
             if (categories.size() > j) {
                 TabWidget tab;
-                tabs.add(tab = new TabWidget(i, tabSize, bounds.x + bounds.width / 2 - Math.min(categories.size() - categoryPages * tabsPerPage, tabsPerPage) * tabSize / 2, bounds.y, 0, tabV) {
-                    @Override
-                    public boolean mouseClicked(double mouseX, double mouseY, int button) {
-                        if (containsMouse(mouseX, mouseY)) {
-                            MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
-                            if (getId() + categoryPages * tabsPerPage == categories.indexOf(selectedCategory))
-                                return false;
-                            selectedCategory = (RecipeCategory<RecipeDisplay>) categories.get(getId() + categoryPages * tabsPerPage);
-                            page = 0;
-                            RecipeViewingScreen.this.init();
-                            return true;
-                        }
+                tabs.add(tab = TabWidget.create(i, tabSize, bounds.x + bounds.width / 2 - Math.min(categories.size() - categoryPages * tabsPerPage, tabsPerPage) * tabSize / 2, bounds.y, 0, tabV, widget -> {
+                    MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+                    if (widget.getId() + categoryPages * tabsPerPage == categories.indexOf(selectedCategory))
                         return false;
-                    }
-                });
+                    ClientHelperImpl.getInstance().openRecipeViewingScreen(categoriesMap, categories.get(widget.getId() + categoryPages * tabsPerPage).getIdentifier(), ingredientStackToNotice, resultStackToNotice);
+                    return true;
+                }));
                 tab.setRenderer(categories.get(j), categories.get(j).getLogo(), categories.get(j).getCategoryName(), tab.getId() + categoryPages * tabsPerPage == categories.indexOf(selectedCategory));
             }
         }
@@ -398,7 +399,7 @@ public class RecipeViewingScreen extends Screen implements StackToNoticeScreen {
             selectedCategory.drawCategoryBackground(bounds, mouseX, mouseY, delta);
         else {
             PanelWidget.render(bounds, -1);
-            if (ScreenHelper.isDarkModeEnabled()) {
+            if (REIHelper.getInstance().isDarkThemeEnabled()) {
                 fill(bounds.x + 17, bounds.y + 5, bounds.x + bounds.width - 17, bounds.y + 17, 0xFF404040);
                 fill(bounds.x + 17, bounds.y + 19, bounds.x + bounds.width - 17, bounds.y + 30, 0xFF404040);
             } else {

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

@@ -24,7 +24,6 @@
 package me.shedaniel.rei.gui;
 
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.mojang.blaze3d.systems.RenderSystem;
 import me.shedaniel.clothconfig2.ClothConfigInitializer;
 import me.shedaniel.clothconfig2.api.ScissorsHandler;
@@ -34,6 +33,7 @@ import me.shedaniel.math.impl.PointHelper;
 import me.shedaniel.rei.api.*;
 import me.shedaniel.rei.gui.entries.RecipeEntry;
 import me.shedaniel.rei.gui.widget.*;
+import me.shedaniel.rei.impl.ClientHelperImpl;
 import me.shedaniel.rei.impl.ScreenHelper;
 import me.shedaniel.rei.utils.CollectionUtils;
 import net.minecraft.client.MinecraftClient;
@@ -46,11 +46,12 @@ import net.minecraft.client.resource.language.I18n;
 import net.minecraft.client.sound.PositionedSoundInstance;
 import net.minecraft.client.util.NarratorManager;
 import net.minecraft.sound.SoundEvents;
-import net.minecraft.text.LiteralText;
 import net.minecraft.text.TranslatableText;
 import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
 import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collections;
 import java.util.List;
@@ -58,50 +59,42 @@ import java.util.Map;
 import java.util.Optional;
 
 @ApiStatus.Internal
-public class VillagerRecipeViewingScreen extends Screen implements StackToNoticeScreen {
+public class VillagerRecipeViewingScreen extends Screen implements RecipeScreen {
     
     private final Map<RecipeCategory<?>, List<RecipeDisplay>> categoryMap;
     private final List<RecipeCategory<?>> categories;
-    private final List<Widget> widgets;
-    private final List<ButtonWidget> buttonWidgets;
-    private final List<RecipeEntry> recipeRenderers;
-    private final List<TabWidget> tabs;
+    private final List<Widget> widgets = Lists.newArrayList();
+    private final List<ButtonWidget> buttonWidgets = Lists.newArrayList();
+    private final List<RecipeEntry> recipeRenderers = Lists.newArrayList();
+    private final List<TabWidget> tabs = Lists.newArrayList();
     public Rectangle bounds, scrollListBounds;
     private int tabsPerPage = 8;
-    private int selectedCategoryIndex, selectedRecipeIndex;
-    private double scroll;
+    private int selectedCategoryIndex = 0;
+    private int selectedRecipeIndex = 0;
+    private double scrollAmount = 0;
     private double target;
     private long start;
     private long duration;
-    private float scrollBarAlpha;
-    private float scrollBarAlphaFuture;
+    private float scrollBarAlpha = 0;
+    private float scrollBarAlphaFuture = 0;
     private long scrollBarAlphaFutureTime = -1;
-    private boolean draggingScrollBar;
-    private int tabsPage;
+    private boolean draggingScrollBar = false;
+    private int tabsPage = -1;
     private EntryStack ingredientStackToNotice = EntryStack.empty();
     private EntryStack resultStackToNotice = EntryStack.empty();
     
-    public VillagerRecipeViewingScreen(Map<RecipeCategory<?>, List<RecipeDisplay>> map) {
-        super(new LiteralText(""));
-        this.widgets = Lists.newArrayList();
-        this.categoryMap = Maps.newLinkedHashMap();
-        this.selectedCategoryIndex = 0;
-        this.selectedRecipeIndex = 0;
-        this.scrollBarAlpha = 0;
-        this.scrollBarAlphaFuture = 0;
-        this.scroll = 0;
-        this.draggingScrollBar = false;
-        this.tabsPage = 0;
-        this.categories = Lists.newArrayList();
-        this.buttonWidgets = Lists.newArrayList();
-        this.tabs = Lists.newArrayList();
-        this.recipeRenderers = Lists.newArrayList();
-        RecipeHelper.getInstance().getAllCategories().forEach(category -> {
-            if (map.containsKey(category)) {
-                categories.add(category);
-                categoryMap.put(category, map.get(category));
+    public VillagerRecipeViewingScreen(Map<RecipeCategory<?>, List<RecipeDisplay>> categoryMap, @Nullable Identifier category) {
+        super(NarratorManager.EMPTY);
+        this.categoryMap = categoryMap;
+        this.categories = Lists.newArrayList(categoryMap.keySet());
+        if (category != null) {
+            for (int i = 0; i < categories.size(); i++) {
+                if (categories.get(i).getIdentifier().equals(category)) {
+                    this.selectedCategoryIndex = i;
+                    break;
+                }
             }
-        });
+        }
     }
     
     @Override
@@ -119,6 +112,16 @@ public class VillagerRecipeViewingScreen extends Screen implements StackToNotice
         resultStackToNotice = stack;
     }
     
+    @Override
+    public Identifier getCurrentCategory() {
+        return categories.get(selectedCategoryIndex).getIdentifier();
+    }
+    
+    @Override
+    public void recalculateCategoryPage() {
+        this.tabsPage = -1;
+    }
+    
     @Override
     protected void init() {
         super.init();
@@ -137,6 +140,9 @@ public class VillagerRecipeViewingScreen extends Screen implements StackToNotice
         int guiWidth = MathHelper.clamp(category.getDisplayWidth(display) + 30, 0, largestWidth) + 100;
         int guiHeight = MathHelper.clamp(category.getDisplayHeight() + 40, 166, largestHeight);
         this.tabsPerPage = Math.max(5, MathHelper.floor((guiWidth - 20d) / tabSize));
+        if (this.tabsPage == -1) {
+            this.tabsPage = selectedCategoryIndex / tabsPerPage;
+        }
         this.bounds = new Rectangle(width / 2 - guiWidth / 2, height / 2 - guiHeight / 2, guiWidth, guiHeight);
         
         List<List<EntryStack>> workingStations = RecipeHelper.getInstance().getWorkingStations(category.getIdentifier());
@@ -214,24 +220,16 @@ public class VillagerRecipeViewingScreen extends Screen implements StackToNotice
         for (int i = 0; i < tabsPerPage; i++) {
             int j = i + tabsPage * tabsPerPage;
             if (categories.size() > j) {
+                RecipeCategory<?> tabCategory = categories.get(j);
                 TabWidget tab;
-                tabs.add(tab = new TabWidget(i, tabSize, bounds.x + bounds.width / 2 - Math.min(categories.size() - tabsPage * tabsPerPage, tabsPerPage) * tabSize / 2, bounds.y, 0, tabV) {
-                    @Override
-                    public boolean mouseClicked(double mouseX, double mouseY, int button) {
-                        if (containsMouse(mouseX, mouseY)) {
-                            MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
-                            if (getId() + tabsPage * tabsPerPage == selectedCategoryIndex)
-                                return false;
-                            selectedCategoryIndex = getId() + tabsPage * tabsPerPage;
-                            scroll = 0;
-                            selectedRecipeIndex = 0;
-                            VillagerRecipeViewingScreen.this.init();
-                            return true;
-                        }
+                tabs.add(tab = TabWidget.create(i, tabSize, bounds.x + bounds.width / 2 - Math.min(categories.size() - tabsPage * tabsPerPage, tabsPerPage) * tabSize / 2, bounds.y, 0, tabV, widget -> {
+                    MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+                    if (widget.selected)
                         return false;
-                    }
-                });
-                tab.setRenderer(categories.get(j), categories.get(j).getLogo(), categories.get(j).getCategoryName(), tab.getId() + tabsPage * tabsPerPage == selectedCategoryIndex);
+                    ClientHelperImpl.getInstance().openRecipeViewingScreen(categoryMap, tabCategory.getIdentifier(), ingredientStackToNotice, resultStackToNotice);
+                    return true;
+                }));
+                tab.setRenderer(tabCategory, tabCategory.getLogo(), tabCategory.getCategoryName(), j == selectedCategoryIndex);
             }
         }
         ButtonWidget w, w2;
@@ -312,7 +310,7 @@ public class VillagerRecipeViewingScreen extends Screen implements StackToNotice
             start = System.currentTimeMillis();
             this.duration = duration;
         } else
-            scroll = target;
+            scrollAmount = target;
     }
     
     @Override
@@ -384,7 +382,7 @@ public class VillagerRecipeViewingScreen extends Screen implements StackToNotice
         RenderSystem.pushMatrix();
         ScissorsHandler.INSTANCE.scissor(new Rectangle(0, scrollListBounds.y + 1, width, scrollListBounds.height - 2));
         for (ButtonWidget buttonWidget : buttonWidgets) {
-            buttonWidget.getBounds().y = scrollListBounds.y + 1 + yOffset - (int) scroll;
+            buttonWidget.getBounds().y = scrollListBounds.y + 1 + yOffset - (int) scrollAmount;
             if (buttonWidget.getBounds().getMaxY() > scrollListBounds.getMinY() && buttonWidget.getBounds().getMinY() < scrollListBounds.getMaxY()) {
                 buttonWidget.render(mouseX, mouseY, delta);
             }
@@ -403,13 +401,13 @@ public class VillagerRecipeViewingScreen extends Screen implements StackToNotice
             BufferBuilder buffer = tessellator.getBuffer();
             int height = (int) (((scrollListBounds.height - 2) * (scrollListBounds.height - 2)) / this.getMaxScrollPosition());
             height = MathHelper.clamp(height, 32, scrollListBounds.height - 2 - 8);
-            height -= Math.min((scroll < 0 ? (int) -scroll : scroll > getMaxScroll() ? (int) scroll - getMaxScroll() : 0), height * .95);
+            height -= Math.min((scrollAmount < 0 ? (int) -scrollAmount : scrollAmount > getMaxScroll() ? (int) scrollAmount - getMaxScroll() : 0), height * .95);
             height = Math.max(10, height);
-            int minY = (int) Math.min(Math.max((int) scroll * (scrollListBounds.height - 2 - height) / getMaxScroll() + scrollListBounds.y + 1, scrollListBounds.y + 1), scrollListBounds.getMaxY() - 1 - height);
+            int minY = (int) Math.min(Math.max((int) scrollAmount * (scrollListBounds.height - 2 - height) / getMaxScroll() + scrollListBounds.y + 1, scrollListBounds.y + 1), scrollListBounds.getMaxY() - 1 - height);
             int scrollbarPositionMinX = scrollListBounds.getMaxX() - 6, scrollbarPositionMaxX = scrollListBounds.getMaxX() - 1;
             boolean hovered = (new Rectangle(scrollbarPositionMinX, minY, scrollbarPositionMaxX - scrollbarPositionMinX, height)).contains(PointHelper.fromMouse());
-            float bottomC = (hovered ? .67f : .5f) * (ScreenHelper.isDarkModeEnabled() ? 0.8f : 1f);
-            float topC = (hovered ? .87f : .67f) * (ScreenHelper.isDarkModeEnabled() ? 0.8f : 1f);
+            float bottomC = (hovered ? .67f : .5f) * (REIHelper.getInstance().isDarkThemeEnabled() ? 0.8f : 1f);
+            float topC = (hovered ? .87f : .67f) * (REIHelper.getInstance().isDarkThemeEnabled() ? 0.8f : 1f);
             RenderSystem.disableTexture();
             RenderSystem.enableBlend();
             RenderSystem.disableAlphaTest();
@@ -439,7 +437,7 @@ public class VillagerRecipeViewingScreen extends Screen implements StackToNotice
     
     private void updatePosition(float delta) {
         double[] target = new double[]{this.target};
-        this.scroll = ClothConfigInitializer.handleScrollingPosition(target, this.scroll, this.getMaxScroll(), delta, this.start, this.duration);
+        this.scrollAmount = ClothConfigInitializer.handleScrollingPosition(target, this.scrollAmount, this.getMaxScroll(), delta, this.start, this.duration);
         this.target = target[0];
     }
     
@@ -453,7 +451,7 @@ public class VillagerRecipeViewingScreen extends Screen implements StackToNotice
                 double double_6 = Math.max(1.0D, Math.max(1d, height) / (double) (actualHeight - int_3));
                 scrollBarAlphaFutureTime = System.currentTimeMillis();
                 scrollBarAlphaFuture = 1f;
-                scroll = target = MathHelper.clamp(scroll + double_4 * double_6, 0, height - scrollListBounds.height + 2);
+                scrollAmount = target = MathHelper.clamp(scrollAmount + double_4 * double_6, 0, height - scrollListBounds.height + 2);
                 return true;
             }
         }

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

@@ -36,6 +36,7 @@ import me.shedaniel.math.impl.PointHelper;
 import me.shedaniel.rei.api.ConfigObject;
 import me.shedaniel.rei.api.EntryRegistry;
 import me.shedaniel.rei.api.EntryStack;
+import me.shedaniel.rei.api.REIHelper;
 import me.shedaniel.rei.gui.OverlaySearchField;
 import me.shedaniel.rei.gui.widget.EntryWidget;
 import me.shedaniel.rei.gui.widget.QueuedTooltip;
@@ -272,8 +273,8 @@ public class FilteringEntry extends AbstractConfigListEntry<List<EntryStack>> {
             int scrollbarPositionMinX = getScrollbarMinX();
             int scrollbarPositionMaxX = scrollbarPositionMinX + 6;
             boolean hovered = (new Rectangle(scrollbarPositionMinX, minY, scrollbarPositionMaxX - scrollbarPositionMinX, height)).contains(PointHelper.fromMouse());
-            float bottomC = (hovered ? .67f : .5f) * (ScreenHelper.isDarkModeEnabled() ? 0.8f : 1f);
-            float topC = (hovered ? .87f : .67f) * (ScreenHelper.isDarkModeEnabled() ? 0.8f : 1f);
+            float bottomC = (hovered ? .67f : .5f) * (REIHelper.getInstance().isDarkThemeEnabled() ? 0.8f : 1f);
+            float topC = (hovered ? .87f : .67f) * (REIHelper.getInstance().isDarkThemeEnabled() ? 0.8f : 1f);
             
             RenderSystem.disableTexture();
             RenderSystem.enableBlend();

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

@@ -29,6 +29,8 @@ import net.minecraft.client.gui.DrawableHelper;
 import net.minecraft.text.Text;
 import org.jetbrains.annotations.ApiStatus;
 
+import java.util.List;
+
 @ApiStatus.Internal
 public class CreditsEntryListWidget extends DynamicNewSmoothScrollingEntryListWidget<CreditsEntryListWidget.CreditsItem> {
     
@@ -76,14 +78,18 @@ public class CreditsEntryListWidget extends DynamicNewSmoothScrollingEntryListWi
         return width - 40;
     }
     
-    public static class CreditsItem extends DynamicNewSmoothScrollingEntryListWidget.Entry<CreditsItem> {
+    public static abstract class CreditsItem extends DynamicNewSmoothScrollingEntryListWidget.Entry<CreditsItem> {
+    
+    }
+    
+    public static class TextCreditsItem extends CreditsItem {
         private String text;
         
-        public CreditsItem(Text textComponent) {
+        public TextCreditsItem(Text textComponent) {
             this(textComponent.asFormattedString());
         }
         
-        public CreditsItem(String text) {
+        public TextCreditsItem(String text) {
             this.text = text;
         }
         
@@ -103,4 +109,36 @@ public class CreditsEntryListWidget extends DynamicNewSmoothScrollingEntryListWi
         }
     }
     
+    public static class TranslationCreditsItem extends CreditsItem {
+        private String language;
+        private List<String> translators;
+        private int maxWidth;
+        
+        public TranslationCreditsItem(String language, String translators, int width, int maxWidth) {
+            this.language = language;
+            this.translators = MinecraftClient.getInstance().textRenderer.wrapStringToWidthAsList(translators, width);
+            this.maxWidth = maxWidth;
+        }
+        
+        @Override
+        public void render(int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) {
+            MinecraftClient.getInstance().textRenderer.drawWithShadow(language, x + 5, y + 5, -1);
+            int yy = y + 5;
+            for (String translator : translators) {
+                MinecraftClient.getInstance().textRenderer.drawWithShadow(translator, x + 5 + maxWidth, yy, -1);
+                yy += 12;
+            }
+        }
+        
+        @Override
+        public int getItemHeight() {
+            return 12 * translators.size();
+        }
+        
+        @Override
+        public boolean changeFocus(boolean boolean_1) {
+            return false;
+        }
+    }
+    
 }

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

@@ -24,7 +24,8 @@
 package me.shedaniel.rei.gui.credits;
 
 import com.google.common.collect.Lists;
-import me.shedaniel.rei.gui.credits.CreditsEntryListWidget.CreditsItem;
+import me.shedaniel.rei.gui.credits.CreditsEntryListWidget.TextCreditsItem;
+import me.shedaniel.rei.gui.credits.CreditsEntryListWidget.TranslationCreditsItem;
 import me.shedaniel.rei.impl.ScreenHelper;
 import net.fabricmc.loader.api.FabricLoader;
 import net.fabricmc.loader.api.metadata.CustomValue;
@@ -33,9 +34,12 @@ import net.minecraft.client.gui.screen.ingame.ContainerScreen;
 import net.minecraft.client.gui.widget.AbstractPressableButtonWidget;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.text.LiteralText;
+import net.minecraft.util.Pair;
 import org.jetbrains.annotations.ApiStatus;
 
+import java.util.Comparator;
 import java.util.List;
+import java.util.Locale;
 import java.util.stream.Collectors;
 
 @ApiStatus.Internal
@@ -65,7 +69,8 @@ public class CreditsScreen extends Screen {
     protected void init() {
         children.add(entryListWidget = new CreditsEntryListWidget(minecraft, width, height, 32, height - 32));
         entryListWidget.creditsClearEntries();
-        List<String> translators = Lists.newArrayList();
+        List<Pair<String, String>> translators = Lists.newArrayList();
+        Exception[] exception = {null};
         FabricLoader.getInstance().getModContainer("roughlyenoughitems").ifPresent(rei -> {
             try {
                 if (rei.getMetadata().containsCustomValue("rei:translators")) {
@@ -73,24 +78,36 @@ public class CreditsScreen extends Screen {
                     jsonObject.forEach(entry -> {
                         CustomValue value = entry.getValue();
                         String behind = value.getType() == CustomValue.CvType.ARRAY ? Lists.newArrayList(value.getAsArray().iterator()).stream().map(CustomValue::getAsString).sorted(String::compareToIgnoreCase).collect(Collectors.joining(", ")) : value.getAsString();
-                        translators.add(String.format("  %s - %s", entry.getKey(), behind));
+                        translators.add(new Pair<>(entry.getKey(), behind));
                     });
                 }
-                translators.sort(String::compareToIgnoreCase);
+                translators.sort(Comparator.comparing(Pair::getLeft, String::compareToIgnoreCase));
             } catch (Exception e) {
-                translators.clear();
-                translators.add("Failed to get translators: " + e.toString());
-                for (StackTraceElement traceElement : e.getStackTrace())
-                    translators.add("  at " + traceElement);
+                exception[0] = e;
                 e.printStackTrace();
             }
         });
-        List<String> actualTranslators = Lists.newArrayList();
+        List<Pair<String, String>> translatorsMapped = translators.stream().map(pair -> {
+            return new Pair<>(
+                    "  " + (I18n.hasTranslation("language.roughlyenoughitems." + pair.getLeft().toLowerCase(Locale.ROOT).replace(' ', '_')) ? I18n.translate("language.roughlyenoughitems." + pair.getLeft().toLowerCase(Locale.ROOT).replace(' ', '_')) : pair.getLeft()),
+                    pair.getRight()
+            );
+        }).collect(Collectors.toList());
         int i = width - 80 - 6;
-        translators.forEach(s -> font.wrapStringToWidthAsList(s, i).forEach(actualTranslators::add));
-        for (String line : I18n.translate("text.rei.credit.text", FabricLoader.getInstance().getModContainer("roughlyenoughitems").map(mod -> mod.getMetadata().getVersion().getFriendlyString()).orElse("Unknown"), String.join("\n", actualTranslators)).split("\n"))
-            entryListWidget.creditsAddEntry(new CreditsItem(new LiteralText(line)));
-        entryListWidget.creditsAddEntry(new CreditsItem(new LiteralText("")));
+        for (String line : I18n.translate("text.rei.credit.text", FabricLoader.getInstance().getModContainer("roughlyenoughitems").map(mod -> mod.getMetadata().getVersion().getFriendlyString()).orElse("Unknown"), "%translators%").split("\n"))
+            if (line.equalsIgnoreCase("%translators%")) {
+                if (exception[0] != null) {
+                    entryListWidget.creditsAddEntry(new TextCreditsItem(new LiteralText("Failed to get translators: " + exception[0].toString())));
+                    for (StackTraceElement traceElement : exception[0].getStackTrace())
+                        entryListWidget.creditsAddEntry(new TextCreditsItem(new LiteralText("  at " + traceElement)));
+                } else {
+                    int maxWidth = translatorsMapped.stream().mapToInt(pair -> font.getStringWidth(pair.getLeft())).max().orElse(0) + 5;
+                    for (Pair<String, String> pair : translatorsMapped) {
+                        entryListWidget.creditsAddEntry(new TranslationCreditsItem(pair.getLeft(), pair.getRight(), i - maxWidth - 10, maxWidth));
+                    }
+                }
+            } else entryListWidget.creditsAddEntry(new TextCreditsItem(new LiteralText(line)));
+        entryListWidget.creditsAddEntry(new TextCreditsItem(new LiteralText("")));
         children.add(buttonDone = new AbstractPressableButtonWidget(width / 2 - 100, height - 26, 200, 20, I18n.translate("gui.done")) {
             @Override
             public void onPress() {

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

@@ -26,6 +26,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.REIHelper;
 import me.shedaniel.rei.impl.ScreenHelper;
 import net.minecraft.client.gui.Element;
 import net.minecraft.client.sound.PositionedSoundInstance;
@@ -116,7 +117,7 @@ public abstract class ButtonWidget extends WidgetWithBounds {
     }
     
     protected void renderBackground(int x, int y, int width, int height, int textureOffset) {
-        minecraft.getTextureManager().bindTexture(ScreenHelper.isDarkModeEnabled() ? BUTTON_LOCATION_DARK : BUTTON_LOCATION);
+        minecraft.getTextureManager().bindTexture(REIHelper.getInstance().isDarkThemeEnabled() ? BUTTON_LOCATION_DARK : BUTTON_LOCATION);
         RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
         RenderSystem.enableBlend();
         RenderSystem.blendFuncSeparate(770, 771, 1, 0);

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

@@ -24,6 +24,7 @@
 package me.shedaniel.rei.gui.widget;
 
 import me.shedaniel.math.api.Point;
+import me.shedaniel.rei.api.REIHelper;
 import me.shedaniel.rei.impl.ScreenHelper;
 import org.jetbrains.annotations.ApiStatus;
 
@@ -36,7 +37,7 @@ public abstract class ClickableLabelWidget extends LabelWidget {
     @ApiStatus.Internal
     protected ClickableLabelWidget(Point point, String text) {
         super(point, text);
-        this.hoveredColor = ScreenHelper.isDarkModeEnabled() ? -1 : 0xFF66FFCC;
+        this.hoveredColor = REIHelper.getInstance().isDarkThemeEnabled() ? -1 : 0xFF66FFCC;
     }
     
     public LabelWidget hoveredColor(int hoveredColor) {

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

@@ -501,8 +501,8 @@ public class EntryListWidget extends WidgetWithBounds {
             int scrollbarPositionMinX = getScrollbarMinX();
             int scrollbarPositionMaxX = scrollbarPositionMinX + 6;
             boolean hovered = (new Rectangle(scrollbarPositionMinX, minY, scrollbarPositionMaxX - scrollbarPositionMinX, height)).contains(PointHelper.fromMouse());
-            float bottomC = (hovered ? .67f : .5f) * (ScreenHelper.isDarkModeEnabled() ? 0.8f : 1f);
-            float topC = (hovered ? .87f : .67f) * (ScreenHelper.isDarkModeEnabled() ? 0.8f : 1f);
+            float bottomC = (hovered ? .67f : .5f) * (REIHelper.getInstance().isDarkThemeEnabled() ? 0.8f : 1f);
+            float topC = (hovered ? .87f : .67f) * (REIHelper.getInstance().isDarkThemeEnabled() ? 0.8f : 1f);
             
             RenderSystem.disableTexture();
             RenderSystem.enableBlend();

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

@@ -28,10 +28,7 @@ import me.shedaniel.clothconfig2.api.ModifierKeyCode;
 import me.shedaniel.math.api.Point;
 import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.math.impl.PointHelper;
-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.*;
 import me.shedaniel.rei.gui.ContainerScreenOverlay;
 import me.shedaniel.rei.impl.ScreenHelper;
 import me.shedaniel.rei.utils.CollectionUtils;
@@ -203,7 +200,7 @@ public class EntryWidget extends WidgetWithBounds {
     
     protected void drawBackground(int mouseX, int mouseY, float delta) {
         if (background) {
-            minecraft.getTextureManager().bindTexture(ScreenHelper.isDarkModeEnabled() ? RECIPE_GUI_DARK : RECIPE_GUI);
+            minecraft.getTextureManager().bindTexture(REIHelper.getInstance().isDarkThemeEnabled() ? RECIPE_GUI_DARK : RECIPE_GUI);
             blit(bounds.x, bounds.y, 0, 222, bounds.width, bounds.height);
         }
     }
@@ -235,7 +232,7 @@ public class EntryWidget extends WidgetWithBounds {
     protected void drawHighlighted(int mouseX, int mouseY, float delta) {
         RenderSystem.disableDepthTest();
         RenderSystem.colorMask(true, true, true, false);
-        int color = ScreenHelper.isDarkModeEnabled() ? -1877929711 : -2130706433;
+        int color = REIHelper.getInstance().isDarkThemeEnabled() ? -1877929711 : -2130706433;
         setZ(300);
         Rectangle bounds = getInnerBounds();
         fillGradient(bounds.x, bounds.y, bounds.getMaxX(), bounds.getMaxY(), color, color);

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

@@ -195,8 +195,8 @@ public class FavoritesListWidget extends WidgetWithBounds {
             int scrollbarPositionMinX = getScrollbarMinX();
             int scrollbarPositionMaxX = scrollbarPositionMinX + 6;
             boolean hovered = (new Rectangle(scrollbarPositionMinX, minY, scrollbarPositionMaxX - scrollbarPositionMinX, height)).contains(PointHelper.fromMouse());
-            float bottomC = (hovered ? .67f : .5f) * (ScreenHelper.isDarkModeEnabled() ? 0.8f : 1f);
-            float topC = (hovered ? .87f : .67f) * (ScreenHelper.isDarkModeEnabled() ? 0.8f : 1f);
+            float bottomC = (hovered ? .67f : .5f) * (REIHelper.getInstance().isDarkThemeEnabled() ? 0.8f : 1f);
+            float topC = (hovered ? .87f : .67f) * (REIHelper.getInstance().isDarkThemeEnabled() ? 0.8f : 1f);
             
             RenderSystem.disableTexture();
             RenderSystem.enableBlend();
@@ -372,14 +372,14 @@ public class FavoritesListWidget extends WidgetWithBounds {
         protected boolean reverseFavoritesAction() {
             return true;
         }
-    
+        
         @Override
         public void queueTooltip(int mouseX, int mouseY, float delta) {
             if (!ClientHelper.getInstance().isCheating() || minecraft.player.inventory.getCursorStack().isEmpty()) {
                 super.queueTooltip(mouseX, mouseY, delta);
             }
         }
-    
+        
         @Override
         public boolean mouseClicked(double mouseX, double mouseY, int button) {
             if (!interactable)

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

@@ -25,6 +25,7 @@ package me.shedaniel.rei.gui.widget;
 
 import me.shedaniel.math.api.Point;
 import me.shedaniel.math.api.Rectangle;
+import me.shedaniel.rei.api.REIHelper;
 import me.shedaniel.rei.impl.ScreenHelper;
 import net.minecraft.client.gui.Element;
 import org.jetbrains.annotations.ApiStatus;
@@ -48,7 +49,7 @@ public class LabelWidget extends WidgetWithBounds {
     public LabelWidget(Point point, String text) {
         this.pos = point;
         this.text = text;
-        this.defaultColor = ScreenHelper.isDarkModeEnabled() ? 0xFFBBBBBB : -1;
+        this.defaultColor = REIHelper.getInstance().isDarkThemeEnabled() ? 0xFFBBBBBB : -1;
     }
     
     public static LabelWidget create(Point point, String text) {

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

@@ -26,9 +26,9 @@ package me.shedaniel.rei.gui.widget;
 import com.mojang.blaze3d.systems.RenderSystem;
 import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.rei.api.ConfigObject;
+import me.shedaniel.rei.api.REIHelper;
 import me.shedaniel.rei.gui.config.RecipeBorderType;
 import me.shedaniel.rei.gui.config.RecipeScreenType;
-import me.shedaniel.rei.impl.ScreenHelper;
 import net.minecraft.util.Identifier;
 
 import java.util.Collections;
@@ -84,7 +84,7 @@ public class PanelWidget extends WidgetWithBounds {
         float green = ((color >> 8) & 0xFF) / 255f;
         float blue = (color & 0xFF) / 255f;
         RenderSystem.color4f(red, green, blue, alpha);
-        minecraft.getTextureManager().bindTexture(ScreenHelper.isDarkModeEnabled() ? CHEST_GUI_TEXTURE_DARK : CHEST_GUI_TEXTURE);
+        minecraft.getTextureManager().bindTexture(REIHelper.getInstance().isDarkThemeEnabled() ? CHEST_GUI_TEXTURE_DARK : CHEST_GUI_TEXTURE);
         int x = bounds.x, y = bounds.y, width = bounds.width, height = bounds.height;
         int xTextureOffset = getXTextureOffset();
         int yTextureOffset = getYTextureOffset();
@@ -114,7 +114,7 @@ public class PanelWidget extends WidgetWithBounds {
     }
     
     protected int getInnerColor() {
-        return ScreenHelper.isDarkModeEnabled() ? -13750738 : -3750202;
+        return REIHelper.getInstance().isDarkThemeEnabled() ? -13750738 : -3750202;
     }
     
     protected int getXTextureOffset() {

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

@@ -27,8 +27,8 @@ import com.google.common.collect.Lists;
 import com.mojang.blaze3d.systems.RenderSystem;
 import me.shedaniel.math.api.Point;
 import me.shedaniel.math.api.Rectangle;
+import me.shedaniel.rei.api.REIHelper;
 import me.shedaniel.rei.gui.RecipeViewingScreen;
-import me.shedaniel.rei.impl.ScreenHelper;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.resource.language.I18n;
 import net.minecraft.client.util.Window;
@@ -112,10 +112,10 @@ public class RecipeChoosePageWidget extends DraggableWidget {
             
             @Override
             public void render(int i, int i1, float v) {
-                font.draw(I18n.translate("text.rei.choose_page"), bounds.x + 5, bounds.y + 5, ScreenHelper.isDarkModeEnabled() ? 0xFFBBBBBB : 0xFF404040);
+                font.draw(I18n.translate("text.rei.choose_page"), bounds.x + 5, bounds.y + 5, REIHelper.getInstance().isDarkThemeEnabled() ? 0xFFBBBBBB : 0xFF404040);
                 String endString = String.format(" /%d", maxPage);
                 int width = font.getStringWidth(endString);
-                font.draw(endString, bounds.x + bounds.width - 5 - width, bounds.y + 22, ScreenHelper.isDarkModeEnabled() ? 0xFFBBBBBB : 0xFF404040);
+                font.draw(endString, bounds.x + bounds.width - 5 - width, bounds.y + 22, REIHelper.getInstance().isDarkThemeEnabled() ? 0xFFBBBBBB : 0xFF404040);
             }
         });
         String endString = String.format(" /%d", maxPage);

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

@@ -24,7 +24,7 @@
 package me.shedaniel.rei.gui.widget;
 
 import me.shedaniel.math.api.Rectangle;
-import me.shedaniel.rei.impl.ScreenHelper;
+import me.shedaniel.rei.api.REIHelper;
 
 public class SlotBaseWidget extends RecipeBaseWidget {
     
@@ -34,7 +34,7 @@ public class SlotBaseWidget extends RecipeBaseWidget {
     
     @Override
     public int getInnerColor() {
-        return ScreenHelper.isDarkModeEnabled() ? -13619152 : -7631989;
+        return REIHelper.getInstance().isDarkThemeEnabled() ? -13619152 : -7631989;
     }
     
     @Override

+ 14 - 10
src/main/java/me/shedaniel/rei/gui/widget/TabWidget.java

@@ -26,14 +26,17 @@ package me.shedaniel.rei.gui.widget;
 import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.rei.api.ClientHelper;
 import me.shedaniel.rei.api.EntryStack;
+import me.shedaniel.rei.api.REIHelper;
 import me.shedaniel.rei.api.RecipeCategory;
 import me.shedaniel.rei.impl.ScreenHelper;
 import net.minecraft.util.Formatting;
 import net.minecraft.util.Identifier;
 import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Predicate;
 
 @ApiStatus.Internal
 public class TabWidget extends WidgetWithBounds {
@@ -48,24 +51,25 @@ public class TabWidget extends WidgetWithBounds {
     public Rectangle bounds;
     public RecipeCategory<?> category;
     public int u, v;
+    @Nullable
+    private Predicate<TabWidget> onClick;
     
-    public TabWidget(int id, Rectangle bounds) {
-        this(id, bounds, 0, 192);
-    }
-    
-    public TabWidget(int id, Rectangle bounds, int u, int v) {
+    private TabWidget(int id, Rectangle bounds, int u, int v, @Nullable Predicate<TabWidget> onClick) {
         this.id = id;
         this.bounds = bounds;
         this.u = u;
         this.v = v;
+        this.onClick = onClick;
     }
     
-    public TabWidget(int id, int tabSize, int leftX, int bottomY) {
-        this(id, new Rectangle(leftX + id * tabSize, bottomY - tabSize, tabSize, tabSize));
+    @ApiStatus.Internal
+    public static TabWidget create(int id, int tabSize, int leftX, int bottomY, int u, int v, @Nullable Predicate<TabWidget> onClick) {
+        return new TabWidget(id, new Rectangle(leftX + id * tabSize, bottomY - tabSize, tabSize, tabSize), u, v, onClick);
     }
     
-    public TabWidget(int id, int tabSize, int leftX, int bottomY, int u, int v) {
-        this(id, new Rectangle(leftX + id * tabSize, bottomY - tabSize, tabSize, tabSize), u, v);
+    @Override
+    public boolean mouseClicked(double mouseX, double mouseY, int button) {
+        return button == 0 && containsMouse(mouseX, mouseY) && onClick.test(this);
     }
     
     public void setRenderer(RecipeCategory<?> category, EntryStack logo, String categoryName, boolean selected) {
@@ -101,7 +105,7 @@ public class TabWidget extends WidgetWithBounds {
     @Override
     public void render(int mouseX, int mouseY, float delta) {
         if (shown) {
-            minecraft.getTextureManager().bindTexture(ScreenHelper.isDarkModeEnabled() ? CHEST_GUI_TEXTURE_DARK : CHEST_GUI_TEXTURE);
+            minecraft.getTextureManager().bindTexture(REIHelper.getInstance().isDarkThemeEnabled() ? CHEST_GUI_TEXTURE_DARK : CHEST_GUI_TEXTURE);
             this.blit(bounds.x, bounds.y + 2, u + (selected ? bounds.width : 0), v, bounds.width, (selected ? bounds.height + 2 : bounds.height - 1));
             logo.setZ(100);
             logo.render(new Rectangle(bounds.getCenterX() - 8, bounds.getCenterY() - 5, 16, 16), mouseX, mouseY, delta);

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

@@ -26,7 +26,7 @@ package me.shedaniel.rei.gui.widget;
 import com.mojang.blaze3d.systems.RenderSystem;
 import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.math.impl.PointHelper;
-import me.shedaniel.rei.impl.ScreenHelper;
+import me.shedaniel.rei.api.REIHelper;
 import net.minecraft.SharedConstants;
 import net.minecraft.client.gui.screen.Screen;
 import net.minecraft.client.render.BufferBuilder;
@@ -405,7 +405,7 @@ public class TextFieldWidget extends WidgetWithBounds implements Tickable {
     public void renderBorder() {
         if (this.hasBorder()) {
             if (containsMouse(PointHelper.fromMouse()) || focused)
-                fill(this.bounds.x - 1, this.bounds.y - 1, this.bounds.x + this.bounds.width + 1, this.bounds.y + this.bounds.height + 1, ScreenHelper.isDarkModeEnabled() ? -17587 : -1);
+                fill(this.bounds.x - 1, this.bounds.y - 1, this.bounds.x + this.bounds.width + 1, this.bounds.y + this.bounds.height + 1, REIHelper.getInstance().isDarkThemeEnabled() ? -17587 : -1);
             else
                 fill(this.bounds.x - 1, this.bounds.y - 1, this.bounds.x + this.bounds.width + 1, this.bounds.y + this.bounds.height + 1, -6250336);
             fill(this.bounds.x, this.bounds.y, this.bounds.x + this.bounds.width, this.bounds.y + this.bounds.height, -16777216);

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

@@ -32,8 +32,8 @@ import me.shedaniel.rei.RoughlyEnoughItemsCore;
 import me.shedaniel.rei.RoughlyEnoughItemsNetwork;
 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.StackToNoticeScreen;
 import me.shedaniel.rei.gui.VillagerRecipeViewingScreen;
 import me.shedaniel.rei.gui.config.RecipeScreenType;
 import me.shedaniel.rei.utils.CollectionUtils;
@@ -68,7 +68,7 @@ import java.util.UUID;
 @ApiStatus.Internal
 public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
     
-    public static ClientHelperImpl instance;
+    private static ClientHelperImpl instance;
     @ApiStatus.Internal public final Lazy<Boolean> isYog = new Lazy<>(() -> {
         try {
             if (MinecraftClient.getInstance().getSession().getProfile().getId().equals(UUID.fromString("f9546389-9415-4358-9c29-2c26b25bff5b")))
@@ -89,6 +89,11 @@ public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
     });
     private final Map<String, String> modNameCache = Maps.newHashMap();
     
+    @ApiStatus.Internal
+    public static ClientHelperImpl getInstance() {
+        return instance;
+    }
+    
     @Override
     public String getFormattedModFromItem(Item item) {
         String mod = getModFromItem(item);
@@ -176,7 +181,7 @@ public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
     public boolean executeRecipeKeyBind(EntryStack stack) {
         Map<RecipeCategory<?>, List<RecipeDisplay>> map = RecipeHelper.getInstance().getRecipesFor(stack);
         if (map.keySet().size() > 0)
-            openRecipeViewingScreen(map, null, stack);
+            openRecipeViewingScreen(map, null, null, stack);
         return map.keySet().size() > 0;
     }
     
@@ -184,7 +189,7 @@ public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
     public boolean executeUsageKeyBind(EntryStack stack) {
         Map<RecipeCategory<?>, List<RecipeDisplay>> map = RecipeHelper.getInstance().getUsagesFor(stack);
         if (map.keySet().size() > 0)
-            openRecipeViewingScreen(map, stack, null);
+            openRecipeViewingScreen(map, null, stack, null);
         return map.keySet().size() > 0;
     }
     
@@ -200,7 +205,7 @@ public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
     public boolean executeViewAllRecipesKeyBind() {
         Map<RecipeCategory<?>, List<RecipeDisplay>> map = RecipeHelper.getInstance().getAllRecipes();
         if (map.keySet().size() > 0)
-            openRecipeViewingScreen(map);
+            openRecipeViewingScreen(map, null, null, null);
         return map.keySet().size() > 0;
     }
     
@@ -232,30 +237,38 @@ public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
     
     @Override
     public void openRecipeViewingScreen(Map<RecipeCategory<?>, List<RecipeDisplay>> map) {
-        openRecipeViewingScreen(map, null, null);
+        openRecipeViewingScreen(map, null, null, null);
     }
     
     @ApiStatus.Internal
-    public void openRecipeViewingScreen(Map<RecipeCategory<?>, List<RecipeDisplay>> map, @Nullable EntryStack ingredientNotice, @Nullable EntryStack resultNotice) {
+    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();
+            }
+        }
         Screen screen;
         if (ConfigObject.getInstance().getRecipeScreenType() == RecipeScreenType.VILLAGER) {
-            screen = new VillagerRecipeViewingScreen(map);
+            screen = new VillagerRecipeViewingScreen(map, category);
         } else if (ConfigObject.getInstance().getRecipeScreenType() == RecipeScreenType.UNSET) {
+            @Nullable Identifier finalCategory = category;
             screen = new PreRecipeViewingScreen(ScreenHelper.getLastContainerScreen(), RecipeScreenType.UNSET, true, original -> {
                 ConfigObject.getInstance().setRecipeScreenType(original ? RecipeScreenType.ORIGINAL : RecipeScreenType.VILLAGER);
                 ConfigManager.getInstance().saveConfig();
-                openRecipeViewingScreen(map, ingredientNotice, resultNotice);
+                openRecipeViewingScreen(map, finalCategory, ingredientNotice, resultNotice);
             });
         } else {
-            screen = new RecipeViewingScreen(map);
+            screen = new RecipeViewingScreen(map, category);
         }
-        if (screen instanceof StackToNoticeScreen) {
+        if (screen instanceof RecipeScreen) {
             if (ingredientNotice != null)
-                ((StackToNoticeScreen) screen).addIngredientStackToNotice(ingredientNotice);
+                ((RecipeScreen) screen).addIngredientStackToNotice(ingredientNotice);
             if (resultNotice != null)
-                ((StackToNoticeScreen) screen).addResultStackToNotice(resultNotice);
+                ((RecipeScreen) screen).addResultStackToNotice(resultNotice);
         }
-        ScreenHelper.storeRecipeScreen(MinecraftClient.getInstance().currentScreen);
+        if (MinecraftClient.getInstance().currentScreen instanceof RecipeScreen)
+            ScreenHelper.storeRecipeScreen((RecipeScreen) MinecraftClient.getInstance().currentScreen);
         MinecraftClient.getInstance().openScreen(screen);
     }
     

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

@@ -36,7 +36,6 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 @ApiStatus.Internal
 public class DisplayHelperImpl implements DisplayHelper {

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

@@ -25,13 +25,9 @@ package me.shedaniel.rei.impl;
 
 import com.google.common.collect.Lists;
 import com.mojang.blaze3d.platform.GlStateManager;
-import me.shedaniel.math.api.Executor;
 import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.rei.api.*;
 import me.shedaniel.rei.gui.widget.QueuedTooltip;
-import me.shedaniel.rei.impl.compat.ModelHasDepth1151Compat;
-import me.shedaniel.rei.impl.compat.ModelSideLit1152Compat;
-import net.fabricmc.loader.api.FabricLoader;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.render.OverlayTexture;
 import net.minecraft.client.render.VertexConsumerProvider;
@@ -40,7 +36,6 @@ import net.minecraft.client.render.model.json.ModelTransformation;
 import net.minecraft.client.texture.SpriteAtlasTexture;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.item.ItemStack;
-import net.minecraft.item.Items;
 import net.minecraft.nbt.CompoundTag;
 import net.minecraft.nbt.Tag;
 import net.minecraft.util.Identifier;

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

@@ -352,12 +352,12 @@ public class RecipeHelperImpl implements RecipeHelper {
             public boolean isHandingScreen(Class<?> screen) {
                 return true;
             }
-    
+            
             @Override
             public ActionResult shouldScreenBeOverlayed(Class<?> screen) {
                 return ContainerScreen.class.isAssignableFrom(screen) ? ActionResult.SUCCESS : ActionResult.PASS;
             }
-    
+            
             @Override
             public float getPriority() {
                 return -10;

+ 43 - 13
src/main/java/me/shedaniel/rei/impl/ScreenHelper.java

@@ -29,8 +29,11 @@ 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.api.REIHelper;
 import me.shedaniel.rei.gui.ContainerScreenOverlay;
 import me.shedaniel.rei.gui.OverlaySearchField;
+import me.shedaniel.rei.gui.RecipeScreen;
+import me.shedaniel.rei.gui.widget.TextFieldWidget;
 import me.shedaniel.rei.listeners.ContainerScreenHooks;
 import net.fabricmc.api.ClientModInitializer;
 import net.fabricmc.fabric.api.event.client.ClientTickCallback;
@@ -47,27 +50,41 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Optional;
 
-public class ScreenHelper implements ClientModInitializer {
+@ApiStatus.Internal
+public class ScreenHelper implements ClientModInitializer, REIHelper {
     
-    /**
-     * @deprecated Use getters instead
-     */
-    private static OverlaySearchField searchField;
-    @ApiStatus.Internal public static List<ItemStack> inventoryStacks = Lists.newArrayList();
+    private OverlaySearchField searchField;
+    @ApiStatus.Internal
+    public static List<ItemStack> inventoryStacks = Lists.newArrayList();
     private static ContainerScreenOverlay overlay;
     private static ContainerScreen<?> lastContainerScreen = null;
-    private static LinkedHashSet<Screen> lastRecipeScreen = Sets.newLinkedHashSetWithExpectedSize(5);
+    private static LinkedHashSet<RecipeScreen> lastRecipeScreen = Sets.newLinkedHashSetWithExpectedSize(5);
+    private static ScreenHelper instance;
     
-    public static OverlaySearchField getSearchField() {
+    public static ScreenHelper getInstance() {
+        return instance;
+    }
+    
+    @Override
+    public TextFieldWidget getSearchTextField() {
         return searchField;
     }
     
+    @Override
+    public List<ItemStack> getInventoryStacks() {
+        return inventoryStacks;
+    }
+    
+    public static OverlaySearchField getSearchField() {
+        return (OverlaySearchField) getInstance().getSearchTextField();
+    }
+    
     @ApiStatus.Internal
     public static void setSearchField(OverlaySearchField searchField) {
-        ScreenHelper.searchField = searchField;
+        getInstance().searchField = searchField;
     }
     
-    public static void storeRecipeScreen(Screen screen) {
+    public static void storeRecipeScreen(RecipeScreen screen) {
         while (lastRecipeScreen.size() >= 5)
             lastRecipeScreen.remove(Iterables.get(lastRecipeScreen, 0));
         lastRecipeScreen.add(screen);
@@ -78,11 +95,13 @@ public class ScreenHelper implements ClientModInitializer {
     }
     
     public static Screen getLastRecipeScreen() {
-        Screen screen = Iterables.getLast(lastRecipeScreen);
+        RecipeScreen screen = Iterables.getLast(lastRecipeScreen);
         lastRecipeScreen.remove(screen);
-        return screen;
+        screen.recalculateCategoryPage();
+        return (Screen) screen;
     }
     
+    @ApiStatus.Internal
     public static void clearLastRecipeScreenData() {
         lastRecipeScreen.clear();
     }
@@ -140,10 +159,22 @@ public class ScreenHelper implements ClientModInitializer {
         consumer.accept(actualX, actualY, delta);
     }
     
+    @Deprecated
+    @ApiStatus.Internal
+    @ApiStatus.ScheduledForRemoval
     public static boolean isDarkModeEnabled() {
         return ConfigObject.getInstance().isUsingDarkTheme();
     }
     
+    @Override
+    public boolean isDarkThemeEnabled() {
+        return isDarkModeEnabled();
+    }
+    
+    public ScreenHelper() {
+        ScreenHelper.instance = this;
+    }
+    
     @Override
     public void onInitializeClient() {
         ClothClientHooks.SCREEN_INIT_PRE.register((client, screen, screenHooks) -> {
@@ -156,5 +187,4 @@ public class ScreenHelper implements ClientModInitializer {
                 getSearchField().tick();
         });
     }
-    
 }

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

@@ -75,7 +75,6 @@ import net.minecraft.item.*;
 import net.minecraft.potion.PotionUtil;
 import net.minecraft.recipe.*;
 import net.minecraft.tag.BlockTags;
-import net.minecraft.util.ActionResult;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.registry.Registry;
@@ -104,7 +103,7 @@ public class DefaultPlugin implements REIPluginV0 {
     private static final List<DefaultInformationDisplay> INFO_DISPLAYS = Lists.newArrayList();
     
     public static Identifier getDisplayTexture() {
-        return ScreenHelper.isDarkModeEnabled() ? DISPLAY_TEXTURE_DARK : DISPLAY_TEXTURE;
+        return REIHelper.getInstance().isDarkThemeEnabled() ? DISPLAY_TEXTURE_DARK : DISPLAY_TEXTURE;
     }
     
     public static void registerBrewingDisplay(DefaultBrewingDisplay display) {

+ 0 - 1
src/main/java/me/shedaniel/rei/plugin/beacon/DefaultBeaconBaseDisplay.java

@@ -24,7 +24,6 @@
 package me.shedaniel.rei.plugin.beacon;
 
 import me.shedaniel.rei.api.EntryStack;
-import me.shedaniel.rei.api.ItemStackHook;
 import me.shedaniel.rei.api.RecipeDisplay;
 import me.shedaniel.rei.plugin.DefaultPlugin;
 import me.shedaniel.rei.utils.CollectionUtils;

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

@@ -26,12 +26,12 @@ package me.shedaniel.rei.plugin.campfire;
 import me.shedaniel.math.api.Point;
 import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.rei.api.EntryStack;
+import me.shedaniel.rei.api.REIHelper;
 import me.shedaniel.rei.api.RecipeCategory;
 import me.shedaniel.rei.gui.widget.EntryWidget;
 import me.shedaniel.rei.gui.widget.RecipeArrowWidget;
 import me.shedaniel.rei.gui.widget.RecipeBaseWidget;
 import me.shedaniel.rei.gui.widget.Widget;
-import me.shedaniel.rei.impl.ScreenHelper;
 import me.shedaniel.rei.plugin.DefaultPlugin;
 import net.minecraft.block.Blocks;
 import net.minecraft.client.MinecraftClient;
@@ -77,7 +77,7 @@ public class DefaultCampfireCategory implements RecipeCategory<DefaultCampfireDi
                 blit(startPoint.x + 2, startPoint.y + 31 + (3 - height), 82, 77 + (14 - height), 14, height);
                 String text = I18n.translate("category.rei.campfire.time", df.format(cookingTime / 20d));
                 int length = MinecraftClient.getInstance().textRenderer.getStringWidth(text);
-                MinecraftClient.getInstance().textRenderer.draw(text, bounds.x + bounds.width - length - 5, bounds.y + 5, ScreenHelper.isDarkModeEnabled() ? 0xFFBBBBBB : 0xFF404040);
+                MinecraftClient.getInstance().textRenderer.draw(text, bounds.x + bounds.width - length - 5, bounds.y + 5, REIHelper.getInstance().isDarkThemeEnabled() ? 0xFFBBBBBB : 0xFF404040);
             }
         }));
         widgets.add(RecipeArrowWidget.create(new Point(startPoint.x + 24, startPoint.y + 8), true).time(cookingTime * 50));

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

@@ -28,6 +28,7 @@ import it.unimi.dsi.fastutil.ints.IntList;
 import me.shedaniel.math.api.Point;
 import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.rei.api.EntryStack;
+import me.shedaniel.rei.api.REIHelper;
 import me.shedaniel.rei.api.TransferRecipeCategory;
 import me.shedaniel.rei.gui.entries.RecipeEntry;
 import me.shedaniel.rei.gui.entries.SimpleRecipeEntry;
@@ -35,7 +36,6 @@ import me.shedaniel.rei.gui.widget.EntryWidget;
 import me.shedaniel.rei.gui.widget.RecipeArrowWidget;
 import me.shedaniel.rei.gui.widget.RecipeBaseWidget;
 import me.shedaniel.rei.gui.widget.Widget;
-import me.shedaniel.rei.impl.ScreenHelper;
 import me.shedaniel.rei.plugin.DefaultPlugin;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawableHelper;
@@ -85,7 +85,7 @@ public class DefaultCookingCategory implements TransferRecipeCategory<DefaultCoo
                 blit(startPoint.x + 2, startPoint.y + 31 + (3 - height), 82, 77 + (14 - height), 14, height);
                 String text = I18n.translate("category.rei.cooking.time&xp", df.format(recipeDisplaySupplier.get().getXp()), df.format(cookingTime / 20d));
                 int length = MinecraftClient.getInstance().textRenderer.getStringWidth(text);
-                MinecraftClient.getInstance().textRenderer.draw(text, bounds.x + bounds.width - length - 5, bounds.y + 5, ScreenHelper.isDarkModeEnabled() ? 0xFFBBBBBB : 0xFF404040);
+                MinecraftClient.getInstance().textRenderer.draw(text, bounds.x + bounds.width - length - 5, bounds.y + 5, REIHelper.getInstance().isDarkThemeEnabled() ? 0xFFBBBBBB : 0xFF404040);
                 
             }
         }));

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

@@ -26,13 +26,13 @@ package me.shedaniel.rei.plugin.fuel;
 import me.shedaniel.math.api.Point;
 import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.rei.api.EntryStack;
+import me.shedaniel.rei.api.REIHelper;
 import me.shedaniel.rei.api.RecipeCategory;
 import me.shedaniel.rei.gui.entries.RecipeEntry;
 import me.shedaniel.rei.gui.widget.EntryWidget;
 import me.shedaniel.rei.gui.widget.QueuedTooltip;
 import me.shedaniel.rei.gui.widget.RecipeBaseWidget;
 import me.shedaniel.rei.gui.widget.Widget;
-import me.shedaniel.rei.impl.ScreenHelper;
 import me.shedaniel.rei.plugin.DefaultPlugin;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.resource.language.I18n;
@@ -83,7 +83,7 @@ public class DefaultFuelCategory implements RecipeCategory<DefaultFuelDisplay> {
                 blit(bounds.x + 5, startPoint.y, 0, 73, 18, 34);
                 int height = MathHelper.ceil(System.currentTimeMillis() / 250d % 14d);
                 blit(bounds.x + 7, startPoint.y + 12 + (3 - height), 82, 77 + (14 - height), 14, height);
-                minecraft.textRenderer.draw(I18n.translate("category.rei.fuel.time.items", burnItems), bounds.x + 26, bounds.getMaxY() - 15, ScreenHelper.isDarkModeEnabled() ? 0xFFBBBBBB : 0xFF404040);
+                minecraft.textRenderer.draw(I18n.translate("category.rei.fuel.time.items", burnItems), bounds.x + 26, bounds.getMaxY() - 15, REIHelper.getInstance().isDarkThemeEnabled() ? 0xFFBBBBBB : 0xFF404040);
             }
         }));
         widgets.add(EntryWidget.create(bounds.x + 6, startPoint.y + 18).entries(recipeDisplaySupplier.get().getInputEntries().get(0)).markIsInput());

+ 3 - 3
src/main/java/me/shedaniel/rei/plugin/information/DefaultInformationCategory.java

@@ -32,11 +32,11 @@ import me.shedaniel.math.api.Point;
 import me.shedaniel.math.api.Rectangle;
 import me.shedaniel.math.impl.PointHelper;
 import me.shedaniel.rei.api.EntryStack;
+import me.shedaniel.rei.api.REIHelper;
 import me.shedaniel.rei.api.RecipeCategory;
 import me.shedaniel.rei.gui.entries.RecipeEntry;
 import me.shedaniel.rei.gui.widget.*;
 import me.shedaniel.rei.impl.RenderingEntry;
-import me.shedaniel.rei.impl.ScreenHelper;
 import me.shedaniel.rei.plugin.DefaultPlugin;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.Element;
@@ -117,7 +117,7 @@ public class DefaultInformationCategory implements RecipeCategory<DefaultInforma
     public List<Widget> setupDisplay(Supplier<DefaultInformationDisplay> recipeDisplaySupplier, Rectangle bounds) {
         DefaultInformationDisplay display = recipeDisplaySupplier.get();
         List<Widget> widgets = Lists.newArrayList();
-        widgets.add(LabelWidget.create(new Point(bounds.getCenterX(), bounds.y + 3), display.getName().asFormattedString()).noShadow().color(ScreenHelper.isDarkModeEnabled() ? 0xFFBBBBBB : 0xFF404040));
+        widgets.add(LabelWidget.create(new Point(bounds.getCenterX(), bounds.y + 3), display.getName().asFormattedString()).noShadow().color(REIHelper.getInstance().isDarkThemeEnabled() ? 0xFFBBBBBB : 0xFF404040));
         widgets.add(EntryWidget.create(bounds.getCenterX() - 8, bounds.y + 15).entries(display.getEntryStacks()).markIsInput());
         Rectangle rectangle = new Rectangle(bounds.getCenterX() - (bounds.width / 2), bounds.y + 35, bounds.width, bounds.height - 40);
         widgets.add(new SlotBaseWidget(rectangle));
@@ -213,7 +213,7 @@ public class DefaultInformationCategory implements RecipeCategory<DefaultInforma
             int currentY = (int) -scroll + innerBounds.y;
             for (Text text : texts) {
                 if (text != null && currentY + font.fontHeight >= innerBounds.y && currentY <= innerBounds.getMaxY()) {
-                    font.draw(text.asFormattedString(), innerBounds.x + 2, currentY + 2, ScreenHelper.isDarkModeEnabled() ? 0xFFBBBBBB : 0xFF090909);
+                    font.draw(text.asFormattedString(), innerBounds.x + 2, currentY + 2, REIHelper.getInstance().isDarkThemeEnabled() ? 0xFFBBBBBB : 0xFF090909);
                 }
                 currentY += text == null ? 4 : font.fontHeight;
             }

+ 1 - 1
src/main/java/me/shedaniel/rei/utils/CollectionUtils.java

@@ -50,7 +50,7 @@ public class CollectionUtils {
         return null;
     }
     
-    public static <T,R> List<R> castAndMap(List<T> list, Class<R> castClass) {
+    public static <T, R> List<R> castAndMap(List<T> list, Class<R> castClass) {
         List<R> l = new ArrayList<>();
         for (T t : list) {
             if (castClass.isAssignableFrom(t.getClass()))

+ 15 - 1
src/main/resources/assets/roughlyenoughitems/lang/en_us.json

@@ -187,5 +187,19 @@
   "config.roughlyenoughitems.scrollingEntryListWidget": "Entry List Action:",
   "config.roughlyenoughitems.scrollingEntryListWidget.boolean.true": "Scrolled",
   "config.roughlyenoughitems.scrollingEntryListWidget.boolean.false": "Paginated",
-  "text.rei.credit.text": "§lRoughly Enough Items (v%s)\n§7Originally a fork for Almost Enough Items.\n\n§lDevelopers\n  Originally by ZenDarva\n  Rewritten by Danielshe\n  Old Plugin Support by TehNut\n\n§lLanguage Translation\n%s\n\n§lLicense\n§7Roughly Enough Items is licensed with MIT."
+  "text.rei.credit.text": "§lRoughly Enough Items (v%s)\n§7Originally a fork for Almost Enough Items.\n\n§lDevelopers\n  Originally by ZenDarva\n  Rewritten by Danielshe\n  Old Plugin Support by TehNut\n\n§lLanguage Translation\n%s\n\n§lLicense\n§7Roughly Enough Items is licensed with MIT.",
+  "language.roughlyenoughitems.english": "English",
+  "language.roughlyenoughitems.japanese": "Japanese",
+  "language.roughlyenoughitems.chinese_simplified": "Chinese Simplified",
+  "language.roughlyenoughitems.chinese_traditional": "Chinese Traditional",
+  "language.roughlyenoughitems.french": "French",
+  "language.roughlyenoughitems.german": "German",
+  "language.roughlyenoughitems.estonian": "Estonian",
+  "language.roughlyenoughitems.portuguese": "Portuguese",
+  "language.roughlyenoughitems.portuguese_brazilian": "Portuguese, Brazilian",
+  "language.roughlyenoughitems.lolcat": "LOLCAT",
+  "language.roughlyenoughitems.upside_down_english": "Upside Down English",
+  "language.roughlyenoughitems.bulgarian": "Bulgarian",
+  "language.roughlyenoughitems.russian": "Russian",
+  "language.roughlyenoughitems.polish": "Polish"
 }

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

@@ -187,5 +187,19 @@
   "config.roughlyenoughitems.scrollingEntryListWidget": "項目列表操作:",
   "config.roughlyenoughitems.scrollingEntryListWidget.boolean.true": "滾動",
   "config.roughlyenoughitems.scrollingEntryListWidget.boolean.false": "分頁",
-  "text.rei.credit.text": "§lRoughly Enough Items (v%s)\n§7原是 Almost Enough Items 的分支.\n\n§l主要開發人員\n  AEI 作者 ZenDarva\n  重寫作者 Danielshe\n\n§l語言翻譯\n%s\n\n§l許可\n§7Roughly Enough Items 使用 MIT."
+  "text.rei.credit.text": "§lRoughly Enough Items (v%s)\n§7原是 Almost Enough Items 的分支.\n\n§l主要開發人員\n  AEI 作者 ZenDarva\n  重寫作者 Danielshe\n\n§l語言翻譯\n%s\n\n§l許可\n§7Roughly Enough Items 使用 MIT.",
+  "language.roughlyenoughitems.english": "英語",
+  "language.roughlyenoughitems.japanese": "日語",
+  "language.roughlyenoughitems.chinese_simplified": "簡體中文",
+  "language.roughlyenoughitems.chinese_traditional": "正體中文",
+  "language.roughlyenoughitems.french": "法語",
+  "language.roughlyenoughitems.german": "德語",
+  "language.roughlyenoughitems.estonian": "愛沙尼亞語",
+  "language.roughlyenoughitems.portuguese": "葡萄牙語",
+  "language.roughlyenoughitems.portuguese_brazilian": "巴西葡萄牙語",
+  "language.roughlyenoughitems.lolcat": "LOLCAT",
+  "language.roughlyenoughitems.upside_down_english": "反轉英語",
+  "language.roughlyenoughitems.bulgarian": "保加利亞語",
+  "language.roughlyenoughitems.russian": "俄語",
+  "language.roughlyenoughitems.polish": "波蘭語"
 }

+ 10 - 8
src/main/resources/fabric.mod.json

@@ -36,7 +36,8 @@
   },
   "depends": {
     "fabricloader": "*",
-    "cloth": ">=1.2.0",
+    "cloth": "~2-",
+    "cloth-basic-math": "*",
     "cloth-config2": "~3-",
     "minecraft": "~1.16-Snapshot.20.10.a"
   },
@@ -48,17 +49,18 @@
     "rei:translators": {
       "English": "shedaniel",
       "Japanese": ["swordglowsblue", "hinataaki"],
-      "Simplified Chinese": ["XuyuEre", "shedaniel", "SciUniv_Moring", "Takakura-Anri"],
-      "Traditional Chinese": ["hugoalh", "gxy17886", "shedaniel"],
+      "Chinese Simplified": ["XuyuEre", "shedaniel", "SciUniv_Moring", "Takakura-Anri", "liushuyu"],
+      "Chinese Traditional": ["hugoalh", "gxy17886", "shedaniel", "961111ray"],
       "French": ["Yanis48", "Koockies"],
-      "German": ["MelanX", "guntram7"],
+      "German": ["MelanX", "guntram7", "tabmeier12"],
       "Estonian": ["Madis0"],
-      "Portuguese": ["thiagokenis"],
+      "Portuguese": ["thiagokenis", "KewaiiGamer"],
+      "Portuguese Brazilian": ["thiagokenis", "joaoh1", "yuriob262", "Pinkstyles"],
       "LOLCAT": ["shedaniel", "RaxedMC"],
-      "Upside Down English": ["shedaniel"],
-      "Brazilian Portuguese": ["thiagokenis", "joaoh1"],
+      "Upside Down English": ["shedaniel", "magnusk28", "scarzdz"],
       "Bulgarian": ["geniiii"],
-      "Russian": ["MrYonter", "kwmika1girl", "LimyChitou", "Great_Manalal", "s3rbug", "TheByKotik", "ebogish"]
+      "Russian": ["MrYonter", "kwmika1girl", "LimyChitou", "Great_Manalal", "s3rbug", "TheByKotik", "ebogish", "xqr.", "scarzdz"],
+      "Polish": ["mikolajkazmierczak", "Piteriuz"]
     }
   }
 }