浏览代码

VillagerRecipeViewingScreen done

Unknown 6 年之前
父节点
当前提交
ac1060fc85
共有 21 个文件被更改,包括 485 次插入75 次删除
  1. 38 0
      src/main/java/com/zeitheron/hammercore/client/utils/Scissors.java
  2. 5 0
      src/main/java/me/shedaniel/rei/api/RecipeCategory.java
  3. 18 0
      src/main/java/me/shedaniel/rei/api/Renderable.java
  4. 1 1
      src/main/java/me/shedaniel/rei/client/RecipeScreenType.java
  5. 7 13
      src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java
  6. 1 1
      src/main/java/me/shedaniel/rei/gui/RecipeViewingScreen.java
  7. 188 18
      src/main/java/me/shedaniel/rei/gui/VillagerRecipeViewingScreen.java
  8. 35 0
      src/main/java/me/shedaniel/rei/gui/renderables/ItemStackRenderer.java
  9. 13 0
      src/main/java/me/shedaniel/rei/gui/renderables/RecipeRenderer.java
  10. 104 0
      src/main/java/me/shedaniel/rei/gui/renderables/SimpleRecipeRenderer.java
  11. 2 2
      src/main/java/me/shedaniel/rei/gui/widget/ButtonWidget.java
  12. 5 0
      src/main/java/me/shedaniel/rei/gui/widget/CategoryBaseWidget.java
  13. 10 2
      src/main/java/me/shedaniel/rei/gui/widget/ClickableLabelWidget.java
  14. 30 34
      src/main/java/me/shedaniel/rei/gui/widget/RecipeBaseWidget.java
  15. 5 0
      src/main/java/me/shedaniel/rei/gui/widget/SlotBaseWidget.java
  16. 1 3
      src/main/java/me/shedaniel/rei/gui/widget/TabWidget.java
  17. 7 0
      src/main/java/me/shedaniel/rei/plugin/DefaultBlastingCategory.java
  18. 1 1
      src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java
  19. 7 0
      src/main/java/me/shedaniel/rei/plugin/DefaultSmeltingCategory.java
  20. 7 0
      src/main/java/me/shedaniel/rei/plugin/DefaultSmokingCategory.java
  21. 二进制
      src/main/resources/assets/roughlyenoughitems/textures/gui/recipecontainer.png

+ 38 - 0
src/main/java/com/zeitheron/hammercore/client/utils/Scissors.java

@@ -0,0 +1,38 @@
+package com.zeitheron.hammercore.client.utils;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.util.Window;
+import org.lwjgl.opengl.GL11;
+
+/**
+ * This is originally the part of Hammer Lib, repacked in REI with permission.
+ * Adapted GL scissor for minecraft pixel resolution and adjusts (0;0) as left-top corner.
+ *
+ * @author Zeitheron
+ */
+public class Scissors {
+    public static void begin() {
+        GL11.glEnable(GL11.GL_SCISSOR_TEST);
+    }
+    
+    public static void scissor(int x, int y, int width, int height) {
+        Window window = MinecraftClient.getInstance().window;
+        
+        int sw = window.getWidth();
+        int sh = window.getHeight();
+        float dw = window.getScaledWidth();
+        float dh = window.getScaledHeight();
+        
+        x = Math.round(sw * (x / dw));
+        y = Math.round(sh * (y / dh));
+        
+        width = Math.round(sw * (width / dw));
+        height = Math.round(sh * (height / dh));
+        
+        GL11.glScissor(x, sh - height - y, width, height);
+    }
+    
+    public static void end() {
+        GL11.glDisable(GL11.GL_SCISSOR_TEST);
+    }
+}

+ 5 - 0
src/main/java/me/shedaniel/rei/api/RecipeCategory.java

@@ -1,6 +1,7 @@
 package me.shedaniel.rei.api;
 
 import me.shedaniel.rei.gui.RecipeViewingScreen;
+import me.shedaniel.rei.gui.renderables.RecipeRenderer;
 import me.shedaniel.rei.gui.widget.CategoryBaseWidget;
 import me.shedaniel.rei.gui.widget.RecipeBaseWidget;
 import me.shedaniel.rei.gui.widget.Widget;
@@ -26,6 +27,10 @@ public interface RecipeCategory<T extends RecipeDisplay> {
     
     String getCategoryName();
     
+    default RecipeRenderer getSimpleRenderer(T recipe) {
+        return Renderable.fromRecipe(recipe::getInput, recipe::getOutput);
+    }
+    
     default List<Widget> setupDisplay(Supplier<T> recipeDisplaySupplier, Rectangle bounds) {
         return Collections.singletonList(new RecipeBaseWidget(bounds));
     }

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

@@ -2,8 +2,11 @@ package me.shedaniel.rei.api;
 
 import me.shedaniel.rei.gui.renderables.EmptyRenderer;
 import me.shedaniel.rei.gui.renderables.ItemStackRenderer;
+import me.shedaniel.rei.gui.renderables.SimpleRecipeRenderer;
 import net.minecraft.item.ItemStack;
+import net.minecraft.util.math.MathHelper;
 
+import java.util.List;
 import java.util.function.Supplier;
 
 public interface Renderable {
@@ -30,5 +33,20 @@ public interface Renderable {
         return EmptyRenderer.INSTANCE;
     }
     
+    static SimpleRecipeRenderer fromRecipe(Supplier<List<List<ItemStack>>> input, Supplier<List<ItemStack>> output) {
+        return new SimpleRecipeRenderer(input, output);
+    }
+    
+    static ItemStackRenderer fromItemStacks(List<ItemStack> stacks) {
+        return new ItemStackRenderer() {
+            @Override
+            public ItemStack getItemStack() {
+                if (stacks.isEmpty())
+                    return ItemStack.EMPTY;
+                return stacks.get(MathHelper.floor((System.currentTimeMillis() / 500 % (double) stacks.size()) / 1f));
+            }
+        };
+    }
+    
     void render(int x, int y, double mouseX, double mouseY, float delta);
 }

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

@@ -11,6 +11,6 @@ public enum RecipeScreenType {
     
     @Override
     public String toString() {
-        return I18n.translate("text.rei.config.recipe_screen_type." + name().toLowerCase(Locale.ROOT));
+        return I18n.translate("text.rei.config.recipe_screen_type." + name().toLowerCase());
     }
 }

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

@@ -27,8 +27,10 @@ import net.minecraft.util.math.MathHelper;
 import net.minecraft.world.GameMode;
 
 import java.awt.*;
-import java.util.*;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 public class ContainerScreenOverlay extends AbstractParentElement implements Drawable {
@@ -43,7 +45,6 @@ public class ContainerScreenOverlay extends AbstractParentElement implements Dra
     private Window window;
     private CraftableToggleButtonWidget toggleButtonWidget;
     private ButtonWidget buttonLeft, buttonRight;
-    private int lastLeft;
     
     public static ItemListOverlay getItemListOverlay() {
         return itemListOverlay;
@@ -59,7 +60,6 @@ public class ContainerScreenOverlay extends AbstractParentElement implements Dra
         this.window = MinecraftClient.getInstance().window;
         DisplayHelper.DisplayBoundsHandler boundsHandler = RoughlyEnoughItemsCore.getDisplayHelper().getResponsibleBoundsHandler(MinecraftClient.getInstance().currentScreen.getClass());
         this.rectangle = RoughlyEnoughItemsCore.getConfigManager().getConfig().mirrorItemPanel ? boundsHandler.getLeftBounds(MinecraftClient.getInstance().currentScreen) : boundsHandler.getRightBounds(MinecraftClient.getInstance().currentScreen);
-        this.lastLeft = getLeft();
         widgets.add(itemListOverlay = new ItemListOverlay(page));
         itemListOverlay.updateList(boundsHandler, boundsHandler.getItemListArea(rectangle), page, searchTerm, false);
         
@@ -306,6 +306,10 @@ public class ContainerScreenOverlay extends AbstractParentElement implements Dra
             RecipeViewingScreen widget = (RecipeViewingScreen) MinecraftClient.getInstance().currentScreen;
             return new Rectangle(widget.getBounds().x, window.getScaledHeight() - 22, widget.getBounds().width - widthRemoved, 18);
         }
+        if (MinecraftClient.getInstance().currentScreen instanceof VillagerRecipeViewingScreen) {
+            VillagerRecipeViewingScreen widget = (VillagerRecipeViewingScreen) MinecraftClient.getInstance().currentScreen;
+            return new Rectangle(widget.bounds.x, window.getScaledHeight() - 22, widget.bounds.width - widthRemoved, 18);
+        }
         return new Rectangle(ScreenHelper.getLastContainerScreenHooks().rei_getContainerLeft(), window.getScaledHeight() - 22, ScreenHelper.getLastContainerScreenHooks().rei_getContainerWidth() - widthRemoved, 18);
     }
     
@@ -427,16 +431,6 @@ public class ContainerScreenOverlay extends AbstractParentElement implements Dra
         GuiLighting.disable();
     }
     
-    private int getLeft() {
-        if (MinecraftClient.getInstance().currentScreen instanceof RecipeViewingScreen) {
-            RecipeViewingScreen widget = (RecipeViewingScreen) MinecraftClient.getInstance().currentScreen;
-            return widget.getBounds().x;
-        }
-        if (MinecraftClient.getInstance().player.getRecipeBook().isGuiOpen())
-            return ScreenHelper.getLastContainerScreenHooks().rei_getContainerLeft() - 147 - 30;
-        return ScreenHelper.getLastContainerScreenHooks().rei_getContainerLeft();
-    }
-    
     private int getTotalPage() {
         return itemListOverlay.getTotalPage();
     }

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

@@ -234,7 +234,7 @@ public class RecipeViewingScreen extends Screen {
             int j = i + categoryPages * 6;
             if (categories.size() > j) {
                 TabWidget tab;
-                tabs.add(tab = new TabWidget(i, this, new Rectangle(bounds.x + 4 + 28 * i, bounds.y - 28, 28, 28)) {
+                tabs.add(tab = new TabWidget(i, new Rectangle(bounds.x + 4 + 28 * i, bounds.y - 28, 28, 28)) {
                     @Override
                     public boolean mouseClicked(double mouseX, double mouseY, int button) {
                         if (getBounds().contains(mouseX, mouseY)) {

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

@@ -3,31 +3,46 @@ package me.shedaniel.rei.gui;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.mojang.blaze3d.platform.GlStateManager;
-import me.shedaniel.rei.api.RecipeCategory;
-import me.shedaniel.rei.api.RecipeDisplay;
-import me.shedaniel.rei.api.RecipeHelper;
+import com.zeitheron.hammercore.client.utils.Scissors;
+import me.shedaniel.rei.api.*;
 import me.shedaniel.rei.client.ScreenHelper;
-import me.shedaniel.rei.gui.widget.CategoryBaseWidget;
-import me.shedaniel.rei.gui.widget.RecipeBaseWidget;
-import me.shedaniel.rei.gui.widget.SlotBaseWidget;
-import me.shedaniel.rei.gui.widget.Widget;
+import me.shedaniel.rei.gui.renderables.RecipeRenderer;
+import me.shedaniel.rei.gui.widget.*;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.audio.PositionedSoundInstance;
+import net.minecraft.client.gui.Element;
 import net.minecraft.client.gui.Screen;
 import net.minecraft.client.render.GuiLighting;
+import net.minecraft.client.resource.language.I18n;
+import net.minecraft.sound.SoundEvents;
 import net.minecraft.text.StringTextComponent;
 import net.minecraft.util.math.MathHelper;
+import org.lwjgl.BufferUtils;
+import org.lwjgl.glfw.GLFW;
+import org.lwjgl.opengl.GL11;
 
 import java.awt.*;
+import java.nio.IntBuffer;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static me.shedaniel.rei.gui.RecipeViewingScreen.getSpeedCraftFunctionalByCategory;
 
 public class VillagerRecipeViewingScreen extends Screen {
     
     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<Renderer> recipeRenderers;
+    private final List<TabWidget> tabs;
     public Rectangle bounds, scrollListBounds;
     private int selectedCategoryIndex, selectedRecipeIndex;
+    private double scroll;
+    private int tabsPage;
+    private static final int TABS_PER_PAGE = 9;
     
     public VillagerRecipeViewingScreen(Map<RecipeCategory, List<RecipeDisplay>> map) {
         super(new StringTextComponent(""));
@@ -35,7 +50,12 @@ public class VillagerRecipeViewingScreen extends Screen {
         this.categoryMap = Maps.newLinkedHashMap();
         this.selectedCategoryIndex = 0;
         this.selectedRecipeIndex = 0;
+        this.scroll = 0;
+        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);
@@ -49,6 +69,9 @@ public class VillagerRecipeViewingScreen extends Screen {
         super.init();
         this.children.clear();
         this.widgets.clear();
+        this.buttonWidgets.clear();
+        this.recipeRenderers.clear();
+        this.tabs.clear();
         int largestWidth = width - 100;
         int largestHeight = height - 40;
         RecipeCategory category = categories.get(selectedCategoryIndex);
@@ -57,34 +80,153 @@ public class VillagerRecipeViewingScreen extends Screen {
         int guiHeight = MathHelper.clamp(category.getDisplayHeight() + 40, 166, largestHeight);
         this.bounds = new Rectangle(width / 2 - guiWidth / 2, height / 2 - guiHeight / 2, guiWidth, guiHeight);
         this.widgets.add(new CategoryBaseWidget(bounds));
-        this.scrollListBounds = new Rectangle(bounds.x + 4, bounds.y + 17, 97, guiHeight - 17 - 7);
+        this.scrollListBounds = new Rectangle(bounds.x + 4, bounds.y + 17, 97 + 5, guiHeight - 17 - 7);
         this.widgets.add(new SlotBaseWidget(scrollListBounds));
+        
         Rectangle recipeBounds = new Rectangle(bounds.x + 100 + (guiWidth - 100) / 2 - category.getDisplayWidth(display) / 2, bounds.y + bounds.height / 2 - category.getDisplayHeight() / 2, category.getDisplayWidth(display), category.getDisplayHeight());
         this.widgets.addAll(category.setupDisplay(() -> display, recipeBounds));
+        Optional<ButtonAreaSupplier> supplier = RecipeHelper.getInstance().getSpeedCraftButtonArea(category);
+        final SpeedCraftFunctional functional = getSpeedCraftFunctionalByCategory(ScreenHelper.getLastContainerScreen(), category);
+        if (supplier.isPresent())
+            widgets.add(new SpeedCraftingButtonWidget(supplier.get().get(recipeBounds), supplier.get().getButtonText(), functional, () -> display));
+        
+        int index = 0;
+        for(RecipeDisplay recipeDisplay : categoryMap.get(category)) {
+            int finalIndex = index;
+            RecipeRenderer recipeRenderer;
+            recipeRenderers.add(recipeRenderer = category.getSimpleRenderer(recipeDisplay));
+            buttonWidgets.add(new ButtonWidget(bounds.x + 5, 0, recipeRenderer.getWidth(), recipeRenderer.getHeight(), "") {
+                @Override
+                public void onPressed() {
+                    selectedRecipeIndex = finalIndex;
+                    VillagerRecipeViewingScreen.this.init();
+                }
+            });
+            index++;
+        }
+        for(int i = 0; i < TABS_PER_PAGE; i++) {
+            int j = i + tabsPage * TABS_PER_PAGE;
+            if (categories.size() > j) {
+                TabWidget tab;
+                tabs.add(tab = new TabWidget(i, new Rectangle(bounds.x + bounds.width / 2 - Math.min(categories.size() - tabsPage * TABS_PER_PAGE, TABS_PER_PAGE) * 14 + i * 28, bounds.y - 28, 28, 28)) {
+                    @Override
+                    public boolean mouseClicked(double mouseX, double mouseY, int button) {
+                        if (getBounds().contains(mouseX, mouseY)) {
+                            MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+                            if (getId() + tabsPage * TABS_PER_PAGE == selectedCategoryIndex)
+                                return false;
+                            selectedCategoryIndex = getId() + tabsPage * TABS_PER_PAGE;
+                            scroll = 0;
+                            VillagerRecipeViewingScreen.this.init();
+                            return true;
+                        }
+                        return false;
+                    }
+                });
+                tab.setRenderer(categories.get(j), categories.get(j).getIcon(), categories.get(j).getCategoryName(), tab.getId() + tabsPage * TABS_PER_PAGE == selectedCategoryIndex);
+            }
+        }
+        
+        this.widgets.add(new ClickableLabelWidget(bounds.x + 4 + scrollListBounds.width / 2, bounds.y + 6, categories.get(selectedCategoryIndex).getCategoryName()) {
+            @Override
+            public void onLabelClicked() {
+                MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+                ClientHelper.getInstance().executeViewAllRecipesKeyBind();
+            }
+            
+            @Override
+            public Optional<String> getTooltips() {
+                return Optional.ofNullable(I18n.translate("text.rei.view_all_categories"));
+            }
+            
+            @Override
+            public void render(int mouseX, int mouseY, float delta) {
+                GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
+                int colour = getDefaultColor();
+                if (clickable && isHovered(mouseX, mouseY))
+                    colour = getHoveredColor();
+                font.draw((isHovered(mouseX, mouseY) ? "§n" : "") + text, x - font.getStringWidth(text) / 2, y, colour);
+                if (clickable && getTooltips().isPresent())
+                    if (!focused && isHighlighted(mouseX, mouseY))
+                        ScreenHelper.getLastOverlay().addTooltip(QueuedTooltip.create(getTooltips().get().split("\n")));
+                    else if (focused)
+                        ScreenHelper.getLastOverlay().addTooltip(QueuedTooltip.create(new Point(x, y), getTooltips().get().split("\n")));
+            }
+    
+            @Override
+            public int getHoveredColor() {
+                return -1;
+            }
+    
+            @Override
+            public int getDefaultColor() {
+                return 4210752;
+            }
+        });
+        this.children.addAll(buttonWidgets);
+        this.widgets.addAll(tabs);
         this.children.addAll(widgets);
         this.children.add(ScreenHelper.getLastOverlay(true, false));
+        ScreenHelper.getLastOverlay().init();
+    }
+    
+    @Override
+    public boolean mouseScrolled(double double_1, double double_2, double double_3) {
+        double height = buttonWidgets.stream().map(ButtonWidget::getBounds).collect(Collectors.summingDouble(Rectangle::getHeight));
+        if (scrollListBounds.contains(double_1, double_2) && height > scrollListBounds.height - 2) {
+            if (double_3 > 0)
+                scroll -= 16;
+            else
+                scroll += 16;
+            scroll = MathHelper.clamp(scroll, 0, height - scrollListBounds.height + 2);
+            return true;
+        }
+        return super.mouseScrolled(double_1, double_2, double_3);
     }
     
     @Override
     public void render(int mouseX, int mouseY, float delta) {
         this.fillGradient(0, 0, this.width, this.height, -1072689136, -804253680);
+        int yOffset = 0;
         this.widgets.forEach(widget -> {
             GuiLighting.disable();
             widget.render(mouseX, mouseY, delta);
         });
-        GuiLighting.disable();
-        ScreenHelper.getLastOverlay().render(mouseX, mouseY, delta);
-        GlStateManager.pushMatrix();
-        GlStateManager.translatef((float) bounds.x, (float) bounds.y, 0.0F);
-        GuiLighting.disable();
-        GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-        String categoryName = categories.get(selectedCategoryIndex).getCategoryName();
-        font.draw(categoryName, 4 + scrollListBounds.width / 2 - font.getStringWidth(categoryName) / 2, 6, 4210752);
-        GlStateManager.popMatrix();
-        GuiLighting.disable();
+        GL11.glPushMatrix();
+        Scissors.begin();
+        Scissors.scissor(0, scrollListBounds.y + 1, width, scrollListBounds.height - 2);
+        for(int i = 0; i < buttonWidgets.size(); i++) {
+            ButtonWidget buttonWidget = buttonWidgets.get(i);
+            buttonWidget.getBounds().y = scrollListBounds.y + 1 + yOffset - (int) scroll;
+            if (buttonWidget.getBounds().getMaxY() > scrollListBounds.getMinY() && buttonWidget.getBounds().getMinY() < scrollListBounds.getMaxY()) {
+                GuiLighting.disable();
+                buttonWidget.render(mouseX, mouseY, delta);
+            }
+            yOffset += buttonWidget.getBounds().height;
+        }
+        for(int i = 0; i < buttonWidgets.size(); i++) {
+            if (buttonWidgets.get(i).getBounds().getMaxY() > scrollListBounds.getMinY() && buttonWidgets.get(i).getBounds().getMinY() < scrollListBounds.getMaxY()) {
+                GuiLighting.disable();
+                recipeRenderers.get(i).setBlitOffset(1);
+                recipeRenderers.get(i).render(buttonWidgets.get(i).getBounds().x, buttonWidgets.get(i).getBounds().y, mouseX, mouseY, delta);
+            }
+        }
+        Scissors.end();
+        GL11.glPopMatrix();
         ScreenHelper.getLastOverlay().lateRender(mouseX, mouseY, delta);
     }
     
+    private int getTitleBarHeight() {
+        IntBuffer useless = BufferUtils.createIntBuffer(3), top = BufferUtils.createIntBuffer(1);
+        GLFW.glfwGetWindowFrameSize(minecraft.window.getHandle(), useless, top, useless, useless);
+        System.out.println(top.get(0));
+        return top.get(0) / 3 * 2;
+    }
+    
+    private int getReal(int i) {
+        return (int) (i / ((double) minecraft.window.getScaledWidth() / (double) minecraft.window.getWidth()));
+    }
+    
     @Override
     public boolean keyPressed(int int_1, int int_2, int int_3) {
         if ((int_1 == 256 || this.minecraft.options.keyInventory.matchesKey(int_1, int_2)) && this.shouldCloseOnEsc()) {
@@ -92,6 +234,34 @@ public class VillagerRecipeViewingScreen extends Screen {
             ScreenHelper.getLastOverlay().init();
             return true;
         }
+        if (int_1 == 258) {
+            boolean boolean_1 = !hasShiftDown();
+            if (!this.changeFocus(boolean_1))
+                this.changeFocus(boolean_1);
+            return true;
+        }
+        if (ClientHelper.getInstance().getNextPageKeyBinding().matchesKey(int_1, int_2)) {
+            if (categoryMap.get(categories.get(selectedCategoryIndex)).size() > 1) {
+                selectedCategoryIndex++;
+                if (selectedCategoryIndex >= categoryMap.get(categories.get(selectedCategoryIndex)).size())
+                    selectedCategoryIndex = 0;
+                init();
+                return true;
+            }
+            return false;
+        } else if (ClientHelper.getInstance().getPreviousPageKeyBinding().matchesKey(int_1, int_2)) {
+            if (categoryMap.get(categories.get(selectedCategoryIndex)).size() > 1) {
+                selectedCategoryIndex--;
+                if (selectedCategoryIndex < 0)
+                    selectedCategoryIndex = categoryMap.get(categories.get(selectedCategoryIndex)).size() - 1;
+                init();
+                return true;
+            }
+            return false;
+        }
+        for(Element element : children())
+            if (element.keyPressed(int_1, int_2, int_3))
+                return true;
         return super.keyPressed(int_1, int_2, int_3);
     }
     

+ 35 - 0
src/main/java/me/shedaniel/rei/gui/renderables/ItemStackRenderer.java

@@ -1,16 +1,25 @@
 package me.shedaniel.rei.gui.renderables;
 
+import com.google.common.collect.Lists;
 import com.mojang.blaze3d.platform.GlStateManager;
+import me.shedaniel.rei.api.ClientHelper;
 import me.shedaniel.rei.api.Renderer;
+import me.shedaniel.rei.client.ScreenHelper;
+import me.shedaniel.rei.gui.widget.ItemListOverlay;
+import me.shedaniel.rei.gui.widget.QueuedTooltip;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.render.GuiLighting;
 import net.minecraft.client.render.item.ItemRenderer;
 import net.minecraft.item.ItemStack;
 import net.minecraft.util.Identifier;
 
+import java.util.Collections;
+import java.util.List;
+
 public abstract class ItemStackRenderer extends Renderer {
     
     public static final Identifier CHEST_GUI_TEXTURE = new Identifier("roughlyenoughitems", "textures/gui/recipecontainer.png");
+    public boolean drawTooltip = false;
     
     @Override
     public void render(int x, int y, double mouseX, double mouseY, float delta) {
@@ -27,6 +36,32 @@ public abstract class ItemStackRenderer extends Renderer {
         itemRenderer.renderGuiItemOverlay(MinecraftClient.getInstance().textRenderer, getItemStack(), l, i1);
         itemRenderer.zOffset = 0.0F;
         this.blitOffset = 0;
+        if (drawTooltip && mouseX >= x - 8 && mouseX <= x + 8 && mouseY >= y - 6 && mouseY <= y + 10)
+            queueTooltip(getItemStack(), delta);
+        this.drawTooltip = false;
+    }
+    
+    protected void queueTooltip(ItemStack itemStack, float delta) {
+        ScreenHelper.getLastOverlay().addTooltip(QueuedTooltip.create(getTooltip(itemStack)));
+    }
+    
+    protected List<String> getTooltip(ItemStack itemStack) {
+        final String modString = ClientHelper.getInstance().getFormattedModFromItem(itemStack.getItem());
+        List<String> toolTip = Lists.newArrayList(ItemListOverlay.tryGetItemStackToolTip(itemStack, true));
+        toolTip.addAll(getExtraToolTips(itemStack));
+        boolean alreadyHasMod = false;
+        for(String s : toolTip)
+            if (s.equalsIgnoreCase(modString)) {
+                alreadyHasMod = true;
+                break;
+            }
+        if (!alreadyHasMod)
+            toolTip.add(modString);
+        return toolTip;
+    }
+    
+    protected List<String> getExtraToolTips(ItemStack stack) {
+        return Collections.emptyList();
     }
     
     public abstract ItemStack getItemStack();

+ 13 - 0
src/main/java/me/shedaniel/rei/gui/renderables/RecipeRenderer.java

@@ -0,0 +1,13 @@
+package me.shedaniel.rei.gui.renderables;
+
+import me.shedaniel.rei.api.Renderer;
+
+public abstract class RecipeRenderer extends Renderer {
+    
+    public abstract int getHeight();
+    
+    public final int getWidth() {
+        return 100;
+    }
+    
+}

+ 104 - 0
src/main/java/me/shedaniel/rei/gui/renderables/SimpleRecipeRenderer.java

@@ -0,0 +1,104 @@
+package me.shedaniel.rei.gui.renderables;
+
+import com.google.common.collect.Lists;
+import me.shedaniel.rei.api.Renderable;
+import me.shedaniel.rei.gui.VillagerRecipeViewingScreen;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.render.GuiLighting;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.Pair;
+import net.minecraft.util.math.MathHelper;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public class SimpleRecipeRenderer extends RecipeRenderer {
+    
+    private static final Identifier CHEST_GUI_TEXTURE = new Identifier("roughlyenoughitems", "textures/gui/recipecontainer.png");
+    private List<ItemStackRenderer> inputRenderer;
+    private ItemStackRenderer outputRenderer;
+    
+    public SimpleRecipeRenderer(Supplier<List<List<ItemStack>>> input, Supplier<List<ItemStack>> output) {
+        List<Pair<List<ItemStack>, AtomicInteger>> newList = Lists.newArrayList();
+        List<Pair<List<ItemStack>, Integer>> a = input.get().stream().map(stacks -> new Pair<>(stacks, stacks.stream().map(ItemStack::getAmount).max(Integer::compareTo).orElse(1))).collect(Collectors.toList());
+        for(Pair<List<ItemStack>, Integer> pair : a) {
+            Optional<Pair<List<ItemStack>, AtomicInteger>> any = newList.stream().filter(pairr -> pair.getLeft().equals(pairr.getLeft())).findAny();
+            if (any.isPresent()) {
+                any.get().getRight().addAndGet(pair.getRight());
+            } else
+                newList.add(new Pair<>(pair.getLeft(), new AtomicInteger(pair.getRight())));
+        }
+        List<List<ItemStack>> b = Lists.newArrayList();
+        for(Pair<List<ItemStack>, AtomicInteger> pair : newList)
+            b.add(pair.getLeft().stream().map(stack -> {
+                ItemStack s = stack.copy();
+                s.setAmount(pair.getRight().get());
+                return s;
+            }).collect(Collectors.toList()));
+        this.inputRenderer = b.stream().filter(stacks -> !stacks.isEmpty()).map(stacks -> Renderable.fromItemStacks(stacks)).collect(Collectors.toList());
+        this.outputRenderer = Renderable.fromItemStacks(output.get().stream().filter(stack -> !stack.isEmpty()).collect(Collectors.toList()));
+    }
+    
+    @Override
+    public void render(int x, int y, double mouseX, double mouseY, float delta) {
+        int xx = x + 5, yy = y + 5;
+        int j = 0;
+        int itemsPerLine = getItemsPerLine();
+        for(ItemStackRenderer itemStackRenderer : inputRenderer) {
+            itemStackRenderer.setBlitOffset(getBlitOffset() + 50);
+            itemStackRenderer.drawTooltip = MinecraftClient.getInstance().currentScreen instanceof VillagerRecipeViewingScreen;
+            itemStackRenderer.render(xx + 8, yy + 6, mouseX, mouseY, delta);
+            xx += 18;
+            j++;
+            if (j >= getItemsPerLine()) {
+                yy += 18;
+                xx = x + 5;
+                j = 0;
+            }
+        }
+        if (itemsPerLine - j + 1 < 3) {
+            j = 0;
+            xx = x + 5;
+            yy += 18;
+        }
+        GuiLighting.disable();
+        MinecraftClient.getInstance().getTextureManager().bindTexture(CHEST_GUI_TEXTURE);
+        blit(xx, yy, 0, 28, 36, 18);
+        xx += 36;
+        j += 2;
+        if (j >= getItemsPerLine()) {
+            yy += 18;
+            xx = x + 5;
+            j = 0;
+        }
+        outputRenderer.setBlitOffset(getBlitOffset() + 50);
+        outputRenderer.drawTooltip = MinecraftClient.getInstance().currentScreen instanceof VillagerRecipeViewingScreen;
+        outputRenderer.render(xx + 8, yy + 6, mouseX, mouseY, delta);
+    }
+    
+    @Override
+    public int getHeight() {
+        return 10 + getEntriesHeight() * 18;
+    }
+    
+    public int getItemsHeight() {
+        return MathHelper.ceil(((float) inputRenderer.size()) / getItemsPerLine());
+    }
+    
+    public int getEntriesHeight() {
+        int itemsPerLine = getItemsPerLine();
+        if (itemsPerLine - (inputRenderer.size() - MathHelper.floor(((float) inputRenderer.size()) / itemsPerLine)) < 3)
+            return getItemsHeight() + 1;
+        else
+            return getItemsHeight();
+    }
+    
+    public int getItemsPerLine() {
+        return MathHelper.floor((getWidth() - 10f) / 18f);
+    }
+    
+}

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

@@ -74,8 +74,8 @@ public abstract class ButtonWidget extends HighlightableWidget {
         this.blit(x + 4, y + height - 4, 4, 62 + textureOffset * 20, width - 8, 4);
         
         for(int i = y + 4; i < y + height - 4; i += 4) {
-            this.blit(x, i, 0, 50 + textureOffset * 20, width / 2, MathHelper.clamp(y + height - 4 - i, 0, 4));
-            this.blit(x + width / 2, i, 200 - width / 2, 50 + textureOffset * 20, width / 2, MathHelper.clamp(y + height - 4 - i, 0, 4));
+            this.blit(x, i, 0, 50 + textureOffset * 20, MathHelper.ceil(width / 2f), MathHelper.clamp(y + height - 4 - i, 0, 4));
+            this.blit(x + MathHelper.ceil(width / 2f), i, 200 - MathHelper.floor(width / 2f), 50 + textureOffset * 20, MathHelper.floor(width / 2f), MathHelper.clamp(y + height - 4 - i, 0, 4));
         }
         
         int colour = 14737632;

+ 5 - 0
src/main/java/me/shedaniel/rei/gui/widget/CategoryBaseWidget.java

@@ -13,4 +13,9 @@ public class CategoryBaseWidget extends RecipeBaseWidget {
         return 66;
     }
     
+    @Override
+    protected boolean isRendering() {
+        return true;
+    }
+    
 }

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

@@ -22,9 +22,9 @@ public abstract class ClickableLabelWidget extends LabelWidget {
     
     @Override
     public void render(int mouseX, int mouseY, float delta) {
-        int colour = -1;
+        int colour = getDefaultColor();
         if (clickable && isHovered(mouseX, mouseY))
-            colour = hoveredColor;
+            colour = getHoveredColor();
         drawCenteredString(font, (isHovered(mouseX, mouseY) ? "§n" : "") + text, x, y, colour);
         if (clickable && getTooltips().isPresent())
             if (!focused && isHighlighted(mouseX, mouseY))
@@ -33,6 +33,14 @@ public abstract class ClickableLabelWidget extends LabelWidget {
                 ScreenHelper.getLastOverlay().addTooltip(QueuedTooltip.create(new Point(x, y), getTooltips().get().split("\n")));
     }
     
+    public int getDefaultColor() {
+        return -1;
+    }
+    
+    public int getHoveredColor() {
+        return hoveredColor;
+    }
+    
     @Override
     public boolean mouseClicked(double mouseX, double mouseY, int button) {
         if (button == 0 && clickable && isHighlighted(mouseX, mouseY)) {

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

@@ -2,6 +2,7 @@ package me.shedaniel.rei.gui.widget;
 
 import com.mojang.blaze3d.platform.GlStateManager;
 import me.shedaniel.rei.RoughlyEnoughItemsCore;
+import me.shedaniel.rei.client.RecipeScreenType;
 import net.minecraft.client.render.GuiLighting;
 import net.minecraft.util.Identifier;
 
@@ -14,7 +15,6 @@ public class RecipeBaseWidget extends HighlightableWidget {
     private static final Identifier CHEST_GUI_TEXTURE = new Identifier("roughlyenoughitems", "textures/gui/recipecontainer.png");
     
     private Rectangle bounds;
-    protected boolean render = true;
     
     public RecipeBaseWidget(Rectangle bounds) {
         this.bounds = bounds;
@@ -22,14 +22,6 @@ public class RecipeBaseWidget extends HighlightableWidget {
             throw new IllegalArgumentException("Base too small, at least 8x8!");
     }
     
-    public boolean isRender() {
-        return render;
-    }
-    
-    public void setRender(boolean render) {
-        this.render = render;
-    }
-    
     @Override
     public Rectangle getBounds() {
         return bounds;
@@ -46,32 +38,36 @@ public class RecipeBaseWidget extends HighlightableWidget {
     
     @Override
     public void render(int mouseX, int mouseY, float delta) {
-        if (render) {
-            GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
-            GuiLighting.disable();
-            minecraft.getTextureManager().bindTexture(CHEST_GUI_TEXTURE);
-            int x = bounds.x, y = bounds.y, width = bounds.width, height = bounds.height;
-            int textureOffset = getTextureOffset();
-    
-            //Four Corners
-            this.blit(x, y, 106, 124 + textureOffset, 4, 4);
-            this.blit(x + width - 4, y, 252, 124 + textureOffset, 4, 4);
-            this.blit(x, y + height - 4, 106, 186 + textureOffset, 4, 4);
-            this.blit(x + width - 4, y + height - 4, 252, 186 + textureOffset, 4, 4);
-    
-            //Sides
-            for(int xx = 4; xx < width - 4; xx += 128) {
-                int thisWidth = Math.min(128, width - 4 - xx);
-                this.blit(x + xx, y, 110, 124 + textureOffset, thisWidth, 4);
-                this.blit(x + xx, y + height - 4, 110, 186 + textureOffset, thisWidth, 4);
-            }
-            for(int yy = 4; yy < height - 4; yy += 50) {
-                int thisHeight = Math.min(50, height - 4 - yy);
-                this.blit(x, y + yy, 106, 128 + textureOffset, 4, thisHeight);
-                this.blit(x + width - 4, y + yy, 252, 128 + textureOffset, 4, thisHeight);
-            }
-            fillGradient(x + 4, y + 4, x + width - 4, y + height - 4, getInnerColor(), getInnerColor());
+        if (!isRendering())
+            return;
+        GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
+        GuiLighting.disable();
+        minecraft.getTextureManager().bindTexture(CHEST_GUI_TEXTURE);
+        int x = bounds.x, y = bounds.y, width = bounds.width, height = bounds.height;
+        int textureOffset = getTextureOffset();
+        
+        //Four Corners
+        this.blit(x, y, 106, 124 + textureOffset, 4, 4);
+        this.blit(x + width - 4, y, 252, 124 + textureOffset, 4, 4);
+        this.blit(x, y + height - 4, 106, 186 + textureOffset, 4, 4);
+        this.blit(x + width - 4, y + height - 4, 252, 186 + textureOffset, 4, 4);
+        
+        //Sides
+        for(int xx = 4; xx < width - 4; xx += 128) {
+            int thisWidth = Math.min(128, width - 4 - xx);
+            this.blit(x + xx, y, 110, 124 + textureOffset, thisWidth, 4);
+            this.blit(x + xx, y + height - 4, 110, 186 + textureOffset, thisWidth, 4);
+        }
+        for(int yy = 4; yy < height - 4; yy += 50) {
+            int thisHeight = Math.min(50, height - 4 - yy);
+            this.blit(x, y + yy, 106, 128 + textureOffset, 4, thisHeight);
+            this.blit(x + width - 4, y + yy, 252, 128 + textureOffset, 4, thisHeight);
         }
+        fillGradient(x + 4, y + 4, x + width - 4, y + height - 4, getInnerColor(), getInnerColor());
+    }
+    
+    protected boolean isRendering() {
+        return RoughlyEnoughItemsCore.getConfigManager().getConfig().screenType != RecipeScreenType.VILLAGER;
     }
     
     protected int getInnerColor() {

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

@@ -18,4 +18,9 @@ public class SlotBaseWidget extends RecipeBaseWidget {
         return -66;
     }
     
+    @Override
+    protected boolean isRendering() {
+        return true;
+    }
+    
 }

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

@@ -20,14 +20,12 @@ public class TabWidget extends HighlightableWidget {
     public boolean shown = false, selected = false;
     public Renderer renderer;
     public int id;
-    public RecipeViewingScreen recipeViewingWidget;
     public String categoryName;
     public Rectangle bounds;
     public RecipeCategory category;
     
-    public TabWidget(int id, RecipeViewingScreen recipeViewingWidget, Rectangle bounds) {
+    public TabWidget(int id, Rectangle bounds) {
         this.id = id;
-        this.recipeViewingWidget = recipeViewingWidget;
         this.bounds = bounds;
     }
     

+ 7 - 0
src/main/java/me/shedaniel/rei/plugin/DefaultBlastingCategory.java

@@ -2,6 +2,8 @@ package me.shedaniel.rei.plugin;
 
 import com.mojang.blaze3d.platform.GlStateManager;
 import me.shedaniel.rei.api.RecipeCategory;
+import me.shedaniel.rei.api.Renderable;
+import me.shedaniel.rei.gui.renderables.RecipeRenderer;
 import me.shedaniel.rei.gui.widget.SlotWidget;
 import me.shedaniel.rei.gui.widget.RecipeBaseWidget;
 import me.shedaniel.rei.gui.widget.Widget;
@@ -38,6 +40,11 @@ public class DefaultBlastingCategory implements RecipeCategory<DefaultBlastingDi
         return I18n.translate("category.rei.blasting");
     }
     
+    @Override
+    public RecipeRenderer getSimpleRenderer(DefaultBlastingDisplay recipe) {
+        return Renderable.fromRecipe(() -> Arrays.asList(recipe.getInput().get(0)), recipe::getOutput);
+    }
+    
     @Override
     public List<Widget> setupDisplay(Supplier<DefaultBlastingDisplay> recipeDisplaySupplier, Rectangle bounds) {
         final DefaultBlastingDisplay recipeDisplay = recipeDisplaySupplier.get();

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

@@ -82,7 +82,7 @@ public class DefaultPlugin implements REIPluginEntry {
             }
         });
         Registry.ENCHANTMENT.forEach(enchantment -> {
-            for(int i = enchantment.getMinimumLevel(); i < enchantment.getMaximumLevel(); i++) {
+            for(int i = enchantment.getMinimumLevel(); i <= enchantment.getMaximumLevel(); i++) {
                 Map<Enchantment, Integer> map = new HashMap<>();
                 map.put(enchantment, i);
                 ItemStack itemStack = new ItemStack(Items.ENCHANTED_BOOK);

+ 7 - 0
src/main/java/me/shedaniel/rei/plugin/DefaultSmeltingCategory.java

@@ -2,6 +2,8 @@ package me.shedaniel.rei.plugin;
 
 import com.mojang.blaze3d.platform.GlStateManager;
 import me.shedaniel.rei.api.RecipeCategory;
+import me.shedaniel.rei.api.Renderable;
+import me.shedaniel.rei.gui.renderables.RecipeRenderer;
 import me.shedaniel.rei.gui.widget.SlotWidget;
 import me.shedaniel.rei.gui.widget.RecipeBaseWidget;
 import me.shedaniel.rei.gui.widget.Widget;
@@ -38,6 +40,11 @@ public class DefaultSmeltingCategory implements RecipeCategory<DefaultSmeltingDi
         return I18n.translate("category.rei.smelting");
     }
     
+    @Override
+    public RecipeRenderer getSimpleRenderer(DefaultSmeltingDisplay recipe) {
+        return Renderable.fromRecipe(() -> Arrays.asList(recipe.getInput().get(0)), recipe::getOutput);
+    }
+    
     @Override
     public List<Widget> setupDisplay(Supplier<DefaultSmeltingDisplay> recipeDisplaySupplier, Rectangle bounds) {
         Point startPoint = new Point((int) bounds.getCenterX() - 41, (int) bounds.getCenterY() - 27);

+ 7 - 0
src/main/java/me/shedaniel/rei/plugin/DefaultSmokingCategory.java

@@ -2,6 +2,8 @@ package me.shedaniel.rei.plugin;
 
 import com.mojang.blaze3d.platform.GlStateManager;
 import me.shedaniel.rei.api.RecipeCategory;
+import me.shedaniel.rei.api.Renderable;
+import me.shedaniel.rei.gui.renderables.RecipeRenderer;
 import me.shedaniel.rei.gui.widget.SlotWidget;
 import me.shedaniel.rei.gui.widget.RecipeBaseWidget;
 import me.shedaniel.rei.gui.widget.Widget;
@@ -38,6 +40,11 @@ public class DefaultSmokingCategory implements RecipeCategory<DefaultSmokingDisp
         return I18n.translate("category.rei.smoking");
     }
     
+    @Override
+    public RecipeRenderer getSimpleRenderer(DefaultSmokingDisplay recipe) {
+        return Renderable.fromRecipe(() -> Arrays.asList(recipe.getInput().get(0)), recipe::getOutput);
+    }
+    
     @Override
     public List<Widget> setupDisplay(Supplier<DefaultSmokingDisplay> recipeDisplaySupplier, Rectangle bounds) {
         Point startPoint = new Point((int) bounds.getCenterX() - 41, (int) bounds.getCenterY() - 27);

二进制
src/main/resources/assets/roughlyenoughitems/textures/gui/recipecontainer.png