Ver Fonte

Require Cloth Config and display the error screen if it is not present.

Signed-off-by: shedaniel <daniel@shedaniel.me>
shedaniel há 4 anos atrás
pai
commit
d941823bab

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

@@ -191,15 +191,14 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer {
         }, Internals.WidgetsProvider.class);
         attachInstance((Supplier<FavoriteEntryType.Registry>) FavoriteEntryTypeRegistryImpl::getInstance, "favoriteEntryTypeRegistry");
         attachInstance((BiFunction<Supplier<FavoriteEntry>, Supplier<JsonObject>, FavoriteEntry>) (supplier, toJson) -> new FavoriteEntry() {
-            LazyResettable<FavoriteEntry> value = new LazyResettable<>(supplier);
+            FavoriteEntry value = null;
             
             @Override
             public FavoriteEntry getUnwrapped() {
-                FavoriteEntry entry = value.get();
-                if (entry == null) {
-                    value.reset();
+                if (this.value == null) {
+                    this.value = supplier.get();
                 }
-                return Objects.requireNonNull(entry).getUnwrapped();
+                return Objects.requireNonNull(value).getUnwrapped();
             }
     
             @Override
@@ -380,7 +379,6 @@ public class RoughlyEnoughItemsCore implements ClientModInitializer {
                 RoughlyEnoughItemsCore.LOGGER.error("REI plugin from " + modContainer.getMetadata().getId() + " is not loaded because it is too old!");
         }
         
-        RoughlyEnoughItemsState.checkRequiredFabricModules();
         Executor.run(() -> () -> {
             ClientSidePacketRegistry.INSTANCE.register(RoughlyEnoughItemsNetwork.CREATE_ITEMS_MESSAGE_PACKET, (packetContext, packetByteBuf) -> {
                 ItemStack stack = packetByteBuf.readItem();

+ 100 - 0
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsInitializer.java

@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableSet;
+import net.fabricmc.api.ClientModInitializer;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.ModInitializer;
+import net.fabricmc.loader.api.FabricLoader;
+
+import java.lang.reflect.InvocationTargetException;
+
+public class RoughlyEnoughItemsInitializer implements ModInitializer {
+    @Override
+    public void onInitialize() {
+        checkRequiredFabricModules();
+        checkClothConfig();
+        
+        if (checkRequiredFabricModules() && checkRequiredFabricModules()) {
+            initializeEntryPoint("me.shedaniel.rei.RoughlyEnoughItemsNetwork");
+            
+            if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
+                initializeEntryPoint("me.shedaniel.rei.RoughlyEnoughItemsCore");
+                initializeEntryPoint("me.shedaniel.rei.impl.ClientHelperImpl");
+                initializeEntryPoint("me.shedaniel.rei.impl.ScreenHelper");
+            }
+        }
+        
+        initializeEntryPoint("me.shedaniel.rei.impl.ErrorDisplayer");
+    }
+    
+    public void initializeEntryPoint(String className) {
+        try {
+            Object instance = Class.forName(className).getConstructor().newInstance();
+            if (instance instanceof ModInitializer) {
+                ((ModInitializer) instance).onInitialize();
+            } else if (instance instanceof ClientModInitializer) {
+                ((ClientModInitializer) instance).onInitializeClient();
+            }
+        } catch (InstantiationException | InvocationTargetException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    public static boolean checkRequiredFabricModules() {
+        ImmutableSet<String> requiredModules = FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT ?
+                ImmutableSet.<String>builder()
+                        .add("fabric-api-base")
+                        .add("fabric-resource-loader-v0")
+                        .add("fabric-networking-v0")
+                        .add("fabric-lifecycle-events-v1")
+                        .add("fabric-rendering-fluids-v1")
+                        .build() :
+                ImmutableSet.<String>builder()
+                        .add("fabric-api-base")
+                        .add("fabric-resource-loader-v0")
+                        .add("fabric-networking-v0")
+                        .add("fabric-lifecycle-events-v1")
+                        .build();
+        for (String module : requiredModules) {
+            boolean moduleLoaded = FabricLoader.getInstance().isModLoaded(module);
+            if (!moduleLoaded) {
+                RoughlyEnoughItemsState.error("Fabric API is not installed!", "https://www.curseforge.com/minecraft/mc-mods/fabric-api/files/all");
+                return false;
+            }
+        }
+        
+        return true;
+    }
+    
+    public static boolean checkClothConfig() {
+        if (!FabricLoader.getInstance().isModLoaded("cloth-config2")) {
+            RoughlyEnoughItemsState.error("Cloth Config is not installed!", "https://www.curseforge.com/minecraft/mc-mods/cloth-config/files/all");
+            return false;
+        }
+        
+        return true;
+    }
+}

+ 0 - 1
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsNetwork.java

@@ -57,7 +57,6 @@ public class RoughlyEnoughItemsNetwork implements ModInitializer {
     
     @Override
     public void onInitialize() {
-        RoughlyEnoughItemsState.checkRequiredFabricModules();
         Executor.run(() -> () -> {
             FabricLoader.getInstance().getEntrypoints("rei_containers", Runnable.class).forEach(Runnable::run);
             ServerSidePacketRegistry.INSTANCE.register(DELETE_ITEMS_PACKET, (packetContext, packetByteBuf) -> {

+ 0 - 25
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsState.java

@@ -23,7 +23,6 @@
 
 package me.shedaniel.rei;
 
-import com.google.common.collect.ImmutableSet;
 import net.fabricmc.api.EnvType;
 import net.fabricmc.loader.api.FabricLoader;
 import net.minecraft.util.Tuple;
@@ -57,30 +56,6 @@ public class RoughlyEnoughItemsState {
         }
     }
     
-    public static void checkRequiredFabricModules() {
-        ImmutableSet<String> requiredModules = FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT ?
-                ImmutableSet.<String>builder()
-                        .add("fabric-api-base")
-                        .add("fabric-resource-loader-v0")
-                        .add("fabric-networking-v0")
-                        .add("fabric-lifecycle-events-v1")
-                        .add("fabric-rendering-fluids-v1")
-                        .build() :
-                ImmutableSet.<String>builder()
-                        .add("fabric-api-base")
-                        .add("fabric-resource-loader-v0")
-                        .add("fabric-networking-v0")
-                        .add("fabric-lifecycle-events-v1")
-                        .build();
-        for (String module : requiredModules) {
-            boolean moduleLoaded = FabricLoader.getInstance().isModLoaded(module);
-            if (!moduleLoaded) {
-                RoughlyEnoughItemsState.error("Fabric API is not installed!", "https://www.curseforge.com/minecraft/mc-mods/fabric-api/files/all");
-                return;
-            }
-        }
-    }
-    
     public static void error(String reason, String link) {
         if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER || FabricLoader.getInstance().isDevelopmentEnvironment())
             throw new RuntimeException(reason + " " + link);

+ 3 - 3
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/WarningAndErrorScreen.java

@@ -24,8 +24,8 @@
 package me.shedaniel.rei.gui;
 
 import com.mojang.blaze3d.vertex.PoseStack;
-import me.shedaniel.clothconfig2.gui.widget.DynamicNewSmoothScrollingEntryListWidget;
 import me.shedaniel.rei.RoughlyEnoughItemsState;
+import me.shedaniel.rei.impl.DynamicErrorFreeEntryListWidget;
 import net.minecraft.ChatFormatting;
 import net.minecraft.Util;
 import net.minecraft.client.Minecraft;
@@ -141,7 +141,7 @@ public class WarningAndErrorScreen extends Screen {
         this.buttonExit.render(matrices, int_1, int_2, float_1);
     }
     
-    private static class StringEntryListWidget extends DynamicNewSmoothScrollingEntryListWidget<StringItem> {
+    private static class StringEntryListWidget extends DynamicErrorFreeEntryListWidget<StringItem> {
         private boolean inFocus;
         private int max = 80;
         
@@ -188,7 +188,7 @@ public class WarningAndErrorScreen extends Screen {
         }
     }
     
-    private abstract static class StringItem extends DynamicNewSmoothScrollingEntryListWidget.Entry<StringItem> {
+    private abstract static class StringItem extends DynamicErrorFreeEntryListWidget.Entry<StringItem> {
         public abstract int getWidth();
     }
     

+ 588 - 0
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/DynamicErrorFreeEntryListWidget.java

@@ -0,0 +1,588 @@
+/*
+ * 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.impl;
+
+import com.google.common.collect.Lists;
+import com.mojang.blaze3d.platform.Lighting;
+import com.mojang.blaze3d.systems.RenderSystem;
+import com.mojang.blaze3d.vertex.BufferBuilder;
+import com.mojang.blaze3d.vertex.DefaultVertexFormat;
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.Tesselator;
+import com.mojang.math.Matrix4f;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiComponent;
+import net.minecraft.client.gui.components.Widget;
+import net.minecraft.client.gui.components.events.AbstractContainerEventHandler;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.Mth;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@Environment(EnvType.CLIENT)
+public abstract class DynamicErrorFreeEntryListWidget<E extends DynamicErrorFreeEntryListWidget.Entry<E>> extends AbstractContainerEventHandler implements Widget {
+    protected static final int DRAG_OUTSIDE = -2;
+    protected final Minecraft client;
+    private final List<E> entries = new Entries();
+    public int width;
+    public int height;
+    public int top;
+    public int bottom;
+    public int right;
+    public int left;
+    protected boolean verticallyCenter = true;
+    protected int yDrag = -2;
+    protected boolean selectionVisible = true;
+    protected boolean renderSelection;
+    protected int headerHeight;
+    protected double scroll;
+    protected boolean scrolling;
+    protected E selectedItem;
+    protected ResourceLocation backgroundLocation;
+    
+    public DynamicErrorFreeEntryListWidget(Minecraft client, int width, int height, int top, int bottom, ResourceLocation backgroundLocation) {
+        this.client = client;
+        this.width = width;
+        this.height = height;
+        this.top = top;
+        this.bottom = bottom;
+        this.left = 0;
+        this.right = width;
+        this.backgroundLocation = backgroundLocation;
+    }
+    
+    public void setRenderSelection(boolean boolean_1) {
+        this.selectionVisible = boolean_1;
+    }
+    
+    protected void setRenderHeader(boolean boolean_1, int headerHeight) {
+        this.renderSelection = boolean_1;
+        this.headerHeight = headerHeight;
+        if (!boolean_1)
+            this.headerHeight = 0;
+    }
+    
+    public int getItemWidth() {
+        return 220;
+    }
+    
+    public E getSelectedItem() {
+        return this.selectedItem;
+    }
+    
+    public void selectItem(E item) {
+        this.selectedItem = item;
+    }
+    
+    public E getFocused() {
+        return (E) super.getFocused();
+    }
+    
+    public final List<E> children() {
+        return this.entries;
+    }
+    
+    protected final void clearItems() {
+        this.entries.clear();
+    }
+    
+    protected E getItem(int index) {
+        return this.children().get(index);
+    }
+    
+    protected int addItem(E item) {
+        this.entries.add(item);
+        return this.entries.size() - 1;
+    }
+    
+    protected int getItemCount() {
+        return this.children().size();
+    }
+    
+    protected boolean isSelected(int index) {
+        return Objects.equals(this.getSelectedItem(), this.children().get(index));
+    }
+    
+    protected final E getItemAtPosition(double mouseX, double mouseY) {
+        int listMiddleX = this.left + this.width / 2;
+        int minX = listMiddleX - this.getItemWidth() / 2;
+        int maxX = listMiddleX + this.getItemWidth() / 2;
+        int currentY = Mth.floor(mouseY - (double) this.top) - this.headerHeight + (int) this.getScroll() - 4;
+        int itemY = 0;
+        int itemIndex = -1;
+        for (int i = 0; i < entries.size(); i++) {
+            E item = getItem(i);
+            itemY += item.getItemHeight();
+            if (itemY > currentY) {
+                itemIndex = i;
+                break;
+            }
+        }
+        return mouseX < (double) this.getScrollbarPosition() && mouseX >= minX && mouseX <= maxX && itemIndex >= 0 && currentY >= 0 && itemIndex < this.getItemCount() ? this.children().get(itemIndex) : null;
+    }
+    
+    public void updateSize(int width, int height, int top, int bottom) {
+        this.width = width;
+        this.height = height;
+        this.top = top;
+        this.bottom = bottom;
+        this.left = 0;
+        this.right = width;
+    }
+    
+    public void setLeftPos(int left) {
+        this.left = left;
+        this.right = left + this.width;
+    }
+    
+    protected int getMaxScrollPosition() {
+        List<Integer> list = new ArrayList<>();
+        int i = headerHeight;
+        for (E entry : entries) {
+            i += entry.getItemHeight();
+            if (entry.getMorePossibleHeight() >= 0) {
+                list.add(i + entry.getMorePossibleHeight());
+            }
+        }
+        list.add(i);
+        return list.stream().max(Integer::compare).orElse(0);
+    }
+    
+    protected void clickedHeader(int int_1, int int_2) {
+    }
+    
+    protected void renderHeader(PoseStack matrices, int rowLeft, int startY, Tesselator tessellator) {
+    }
+    
+    protected void drawBackground() {
+    }
+    
+    protected void renderDecorations(PoseStack matrices, int mouseX, int mouseY) {
+    }
+    
+    @Deprecated
+    protected void renderBackBackground(PoseStack matrices, BufferBuilder buffer, Tesselator tessellator) {
+        this.client.getTextureManager().bind(backgroundLocation);
+        RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
+        Matrix4f matrix = matrices.last().pose();
+        float float_2 = 32.0F;
+        buffer.begin(7, DefaultVertexFormat.POSITION_TEX_COLOR);
+        buffer.vertex(matrix, this.left, this.bottom, 0.0F).uv(this.left / 32.0F, ((this.bottom + (int) this.getScroll()) / 32.0F)).color(32, 32, 32, 255).endVertex();
+        buffer.vertex(matrix, this.right, this.bottom, 0.0F).uv(this.right / 32.0F, ((this.bottom + (int) this.getScroll()) / 32.0F)).color(32, 32, 32, 255).endVertex();
+        buffer.vertex(matrix, this.right, this.top, 0.0F).uv(this.right / 32.0F, ((this.top + (int) this.getScroll()) / 32.0F)).color(32, 32, 32, 255).endVertex();
+        buffer.vertex(matrix, this.left, this.top, 0.0F).uv(this.left / 32.0F, ((this.top + (int) this.getScroll()) / 32.0F)).color(32, 32, 32, 255).endVertex();
+        tessellator.end();
+    }
+    
+    @SuppressWarnings("deprecation")
+    @Override
+    public void render(PoseStack matrices, int mouseX, int mouseY, float delta) {
+        this.drawBackground();
+        int scrollbarPosition = this.getScrollbarPosition();
+        int int_4 = scrollbarPosition + 6;
+        RenderSystem.disableLighting();
+        RenderSystem.disableFog();
+        Tesselator tessellator = Tesselator.getInstance();
+        BufferBuilder buffer = tessellator.getBuilder();
+        renderBackBackground(matrices, buffer, tessellator);
+        int rowLeft = this.getRowLeft();
+        int startY = this.top + 4 - (int) this.getScroll();
+        if (this.renderSelection)
+            this.renderHeader(matrices, rowLeft, startY, tessellator);
+        this.renderList(matrices, rowLeft, startY, mouseX, mouseY, delta);
+        RenderSystem.disableDepthTest();
+        this.renderHoleBackground(matrices, 0, this.top, 255, 255);
+        this.renderHoleBackground(matrices, this.bottom, this.height, 255, 255);
+        RenderSystem.enableBlend();
+        RenderSystem.blendFuncSeparate(770, 771, 0, 1);
+        RenderSystem.disableAlphaTest();
+        RenderSystem.shadeModel(7425);
+        RenderSystem.disableTexture();
+        Matrix4f matrix = matrices.last().pose();
+        buffer.begin(7, DefaultVertexFormat.POSITION_TEX_COLOR);
+        buffer.vertex(matrix, this.left, this.top + 4, 0.0F).uv(0, 1).color(0, 0, 0, 0).endVertex();
+        buffer.vertex(matrix, this.right, this.top + 4, 0.0F).uv(1, 1).color(0, 0, 0, 0).endVertex();
+        buffer.vertex(matrix, this.right, this.top, 0.0F).uv(1, 0).color(0, 0, 0, 255).endVertex();
+        buffer.vertex(matrix, this.left, this.top, 0.0F).uv(0, 0).color(0, 0, 0, 255).endVertex();
+        tessellator.end();
+        buffer.begin(7, DefaultVertexFormat.POSITION_TEX_COLOR);
+        buffer.vertex(matrix, this.left, this.bottom, 0.0F).uv(0, 1).color(0, 0, 0, 255).endVertex();
+        buffer.vertex(matrix, this.right, this.bottom, 0.0F).uv(1, 1).color(0, 0, 0, 255).endVertex();
+        buffer.vertex(matrix, this.right, this.bottom - 4, 0.0F).uv(1, 0).color(0, 0, 0, 0).endVertex();
+        buffer.vertex(matrix, this.left, this.bottom - 4, 0.0F).uv(0, 0).color(0, 0, 0, 0).endVertex();
+        tessellator.end();
+        int maxScroll = this.getMaxScroll();
+        renderScrollBar(matrices, tessellator, buffer, maxScroll, scrollbarPosition, int_4);
+        
+        this.renderDecorations(matrices, mouseX, mouseY);
+        RenderSystem.enableTexture();
+        RenderSystem.shadeModel(7424);
+        RenderSystem.enableAlphaTest();
+        RenderSystem.disableBlend();
+    }
+    
+    @SuppressWarnings("deprecation")
+    protected void renderScrollBar(PoseStack matrices, Tesselator tessellator, BufferBuilder buffer, int maxScroll, int scrollbarPositionMinX, int scrollbarPositionMaxX) {
+        if (maxScroll > 0) {
+            int int_9 = ((this.bottom - this.top) * (this.bottom - this.top)) / this.getMaxScrollPosition();
+            int_9 = Mth.clamp(int_9, 32, this.bottom - this.top - 8);
+            int int_10 = (int) this.getScroll() * (this.bottom - this.top - int_9) / maxScroll + this.top;
+            if (int_10 < this.top) {
+                int_10 = this.top;
+            }
+            
+            Matrix4f matrix = matrices.last().pose();
+            buffer.begin(7, DefaultVertexFormat.POSITION_TEX_COLOR);
+            buffer.vertex(matrix, scrollbarPositionMinX, this.bottom, 0.0F).uv(0, 1).color(0, 0, 0, 255).endVertex();
+            buffer.vertex(matrix, scrollbarPositionMaxX, this.bottom, 0.0F).uv(1, 1).color(0, 0, 0, 255).endVertex();
+            buffer.vertex(matrix, scrollbarPositionMaxX, this.top, 0.0F).uv(1, 0).color(0, 0, 0, 255).endVertex();
+            buffer.vertex(matrix, scrollbarPositionMinX, this.top, 0.0F).uv(0, 0).color(0, 0, 0, 255).endVertex();
+            tessellator.end();
+            buffer.begin(7, DefaultVertexFormat.POSITION_TEX_COLOR);
+            buffer.vertex(matrix, scrollbarPositionMinX, int_10 + int_9, 0.0F).uv(0, 1).color(128, 128, 128, 255).endVertex();
+            buffer.vertex(matrix, scrollbarPositionMaxX, int_10 + int_9, 0.0F).uv(1, 1).color(128, 128, 128, 255).endVertex();
+            buffer.vertex(matrix, scrollbarPositionMaxX, int_10, 0.0F).uv(1, 0).color(128, 128, 128, 255).endVertex();
+            buffer.vertex(matrix, scrollbarPositionMinX, int_10, 0.0F).uv(0, 0).color(128, 128, 128, 255).endVertex();
+            tessellator.end();
+            buffer.begin(7, DefaultVertexFormat.POSITION_TEX_COLOR);
+            buffer.vertex(scrollbarPositionMinX, (int_10 + int_9 - 1), 0.0F).uv(0, 1).color(192, 192, 192, 255).endVertex();
+            buffer.vertex((scrollbarPositionMaxX - 1), (int_10 + int_9 - 1), 0.0F).uv(1, 1).color(192, 192, 192, 255).endVertex();
+            buffer.vertex((scrollbarPositionMaxX - 1), int_10, 0.0F).uv(1, 0).color(192, 192, 192, 255).endVertex();
+            buffer.vertex(scrollbarPositionMinX, int_10, 0.0F).uv(0, 0).color(192, 192, 192, 255).endVertex();
+            tessellator.end();
+        }
+    }
+    
+    protected void centerScrollOn(E item) {
+        double d = (this.bottom - this.top) / -2d;
+        for (int i = 0; i < this.children().indexOf(item) && i < this.getItemCount(); i++)
+            d += getItem(i).getItemHeight();
+        this.capYPosition(d);
+    }
+    
+    protected void ensureVisible(E item) {
+        int rowTop = this.getRowTop(this.children().indexOf(item));
+        int int_2 = rowTop - this.top - 4 - item.getItemHeight();
+        if (int_2 < 0)
+            this.scroll(int_2);
+        int int_3 = this.bottom - rowTop - item.getItemHeight() * 2;
+        if (int_3 < 0)
+            this.scroll(-int_3);
+    }
+    
+    protected void scroll(int int_1) {
+        this.capYPosition(this.getScroll() + (double) int_1);
+        this.yDrag = -2;
+    }
+    
+    public double getScroll() {
+        return this.scroll;
+    }
+    
+    public void capYPosition(double double_1) {
+        this.scroll = Mth.clamp(double_1, 0.0F, this.getMaxScroll());
+    }
+    
+    protected int getMaxScroll() {
+        return Math.max(0, this.getMaxScrollPosition() - (this.bottom - this.top - 4));
+    }
+    
+    public int getScrollBottom() {
+        return (int) this.getScroll() - this.height - this.headerHeight;
+    }
+    
+    protected void updateScrollingState(double double_1, double double_2, int int_1) {
+        this.scrolling = int_1 == 0 && double_1 >= (double) this.getScrollbarPosition() && double_1 < (double) (this.getScrollbarPosition() + 6);
+    }
+    
+    protected int getScrollbarPosition() {
+        return this.width / 2 + 124;
+    }
+    
+    public boolean mouseClicked(double double_1, double double_2, int int_1) {
+        this.updateScrollingState(double_1, double_2, int_1);
+        if (!this.isMouseOver(double_1, double_2)) {
+            return false;
+        } else {
+            E item = this.getItemAtPosition(double_1, double_2);
+            if (item != null) {
+                if (item.mouseClicked(double_1, double_2, int_1)) {
+                    this.setFocused(item);
+                    this.setDragging(true);
+                    return true;
+                }
+            } else if (int_1 == 0) {
+                this.clickedHeader((int) (double_1 - (double) (this.left + this.width / 2 - this.getItemWidth() / 2)), (int) (double_2 - (double) this.top) + (int) this.getScroll() - 4);
+                return true;
+            }
+            
+            return this.scrolling;
+        }
+    }
+    
+    public boolean mouseReleased(double double_1, double double_2, int int_1) {
+        if (this.getFocused() != null) {
+            this.getFocused().mouseReleased(double_1, double_2, int_1);
+        }
+        
+        return false;
+    }
+    
+    public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
+        if (super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) {
+            return true;
+        } else if (button == 0 && this.scrolling) {
+            if (mouseY < (double) this.top) {
+                this.capYPosition(0.0F);
+            } else if (mouseY > (double) this.bottom) {
+                this.capYPosition(this.getMaxScroll());
+            } else {
+                double double_5 = Math.max(1, this.getMaxScroll());
+                int int_2 = this.bottom - this.top;
+                int int_3 = Mth.clamp((int) ((float) (int_2 * int_2) / (float) this.getMaxScrollPosition()), 32, int_2 - 8);
+                double double_6 = Math.max(1.0D, double_5 / (double) (int_2 - int_3));
+                this.capYPosition(this.getScroll() + deltaY * double_6);
+            }
+            
+            return true;
+        } else {
+            return false;
+        }
+    }
+    
+    public boolean mouseScrolled(double double_1, double double_2, double double_3) {
+        for (E entry : entries) {
+            if (entry.mouseScrolled(double_1, double_2, double_3)) {
+                return true;
+            }
+        }
+        this.capYPosition(this.getScroll() - double_3 * (double) (getMaxScroll() / getItemCount()) / 2.0D);
+        return true;
+    }
+    
+    public boolean keyPressed(int int_1, int int_2, int int_3) {
+        if (super.keyPressed(int_1, int_2, int_3)) {
+            return true;
+        } else if (int_1 == 264) {
+            this.moveSelection(1);
+            return true;
+        } else if (int_1 == 265) {
+            this.moveSelection(-1);
+            return true;
+        } else {
+            return false;
+        }
+    }
+    
+    protected void moveSelection(int int_1) {
+        if (!this.children().isEmpty()) {
+            int int_2 = this.children().indexOf(this.getSelectedItem());
+            int int_3 = Mth.clamp(int_2 + int_1, 0, this.getItemCount() - 1);
+            E itemListWidget$Item_1 = this.children().get(int_3);
+            this.selectItem(itemListWidget$Item_1);
+            this.ensureVisible(itemListWidget$Item_1);
+        }
+        
+    }
+    
+    public boolean isMouseOver(double double_1, double double_2) {
+        return double_2 >= (double) this.top && double_2 <= (double) this.bottom && double_1 >= (double) this.left && double_1 <= (double) this.right;
+    }
+    
+    protected void renderList(PoseStack matrices, int startX, int startY, int int_3, int int_4, float float_1) {
+        int itemCount = this.getItemCount();
+        Tesselator tessellator = Tesselator.getInstance();
+        BufferBuilder buffer = tessellator.getBuilder();
+        
+        for (int renderIndex = 0; renderIndex < itemCount; ++renderIndex) {
+            E item = this.getItem(renderIndex);
+            int itemY = startY + headerHeight;
+            for (int i = 0; i < entries.size() && i < renderIndex; i++)
+                itemY += entries.get(i).getItemHeight();
+            int itemHeight = item.getItemHeight() - 4;
+            int itemWidth = this.getItemWidth();
+            int itemMinX, itemMaxX;
+            if (this.selectionVisible && this.isSelected(renderIndex)) {
+                itemMinX = this.left + this.width / 2 - itemWidth / 2;
+                itemMaxX = itemMinX + itemWidth;
+                RenderSystem.disableTexture();
+                float float_2 = this.isFocused() ? 1.0F : 0.5F;
+                Matrix4f matrix = matrices.last().pose();
+                RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
+                buffer.begin(7, DefaultVertexFormat.POSITION_COLOR);
+                buffer.vertex(matrix, itemMinX, itemY + itemHeight + 2, 0.0F).color(float_2, float_2, float_2, 1.0F).endVertex();
+                buffer.vertex(matrix, itemMaxX, itemY + itemHeight + 2, 0.0F).color(float_2, float_2, float_2, 1.0F).endVertex();
+                buffer.vertex(matrix, itemMaxX, itemY - 2, 0.0F).color(float_2, float_2, float_2, 1.0F).endVertex();
+                buffer.vertex(matrix, itemMinX, itemY - 2, 0.0F).color(float_2, float_2, float_2, 1.0F).endVertex();
+                tessellator.end();
+                buffer.begin(7, DefaultVertexFormat.POSITION_COLOR);
+                buffer.vertex(matrix, itemMinX + 1, itemY + itemHeight + 1, 0.0F).color(0.0F, 0.0F, 0.0F, 1.0F).endVertex();
+                buffer.vertex(matrix, itemMaxX - 1, itemY + itemHeight + 1, 0.0F).color(0.0F, 0.0F, 0.0F, 1.0F).endVertex();
+                buffer.vertex(matrix, itemMaxX - 1, itemY - 1, 0.0F).color(0.0F, 0.0F, 0.0F, 1.0F).endVertex();
+                buffer.vertex(matrix, itemMinX + 1, itemY - 1, 0.0F).color(0.0F, 0.0F, 0.0F, 1.0F).endVertex();
+                tessellator.end();
+                RenderSystem.enableTexture();
+            }
+            
+            int y = this.getRowTop(renderIndex);
+            int x = this.getRowLeft();
+            Lighting.turnOff();
+            renderItem(matrices, item, renderIndex, y, x, itemWidth, itemHeight, int_3, int_4, this.isMouseOver(int_3, int_4) && Objects.equals(this.getItemAtPosition(int_3, int_4), item), float_1);
+        }
+        
+    }
+    
+    protected void renderItem(PoseStack matrices, E item, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) {
+        item.render(matrices, index, y, x, entryWidth, entryHeight, mouseX, mouseY, isSelected, delta);
+    }
+    
+    protected int getRowLeft() {
+        return this.left + this.width / 2 - this.getItemWidth() / 2 + 2;
+    }
+    
+    protected int getRowTop(int index) {
+        int integer = top + 4 - (int) this.getScroll() + headerHeight;
+        for (int i = 0; i < entries.size() && i < index; i++)
+            integer += entries.get(i).getItemHeight();
+        return integer;
+    }
+    
+    protected boolean isFocused() {
+        return false;
+    }
+    
+    @SuppressWarnings("deprecation")
+    protected void renderHoleBackground(PoseStack matrices, int y1, int y2, int alpha1, int alpha2) {
+        Tesselator tessellator = Tesselator.getInstance();
+        BufferBuilder buffer = tessellator.getBuilder();
+        this.client.getTextureManager().bind(backgroundLocation);
+        Matrix4f matrix = matrices.last().pose();
+        RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
+        float float_1 = 32.0F;
+        buffer.begin(7, DefaultVertexFormat.POSITION_TEX_COLOR);
+        buffer.vertex(matrix, this.left, y2, 0.0F).uv(0, ((float) y2 / 32.0F)).color(64, 64, 64, alpha2).endVertex();
+        buffer.vertex(matrix, this.left + this.width, y2, 0.0F).uv(((float) this.width / 32.0F), ((float) y2 / 32.0F)).color(64, 64, 64, alpha2).endVertex();
+        buffer.vertex(matrix, this.left + this.width, y1, 0.0F).uv(((float) this.width / 32.0F), ((float) y1 / 32.0F)).color(64, 64, 64, alpha1).endVertex();
+        buffer.vertex(matrix, this.left, y1, 0.0F).uv(0, ((float) y1 / 32.0F)).color(64, 64, 64, alpha1).endVertex();
+        tessellator.end();
+    }
+    
+    protected E remove(int int_1) {
+        E itemListWidget$Item_1 = this.entries.get(int_1);
+        return this.removeEntry(this.entries.get(int_1)) ? itemListWidget$Item_1 : null;
+    }
+    
+    protected boolean removeEntry(E itemListWidget$Item_1) {
+        boolean boolean_1 = this.entries.remove(itemListWidget$Item_1);
+        if (boolean_1 && itemListWidget$Item_1 == this.getSelectedItem()) {
+            this.selectItem(null);
+        }
+        
+        return boolean_1;
+    }
+    
+    public static final class SmoothScrollingSettings {
+        public static final double CLAMP_EXTENSION = 200;
+        
+        private SmoothScrollingSettings() {}
+    }
+    
+    @Environment(EnvType.CLIENT)
+    public abstract static class Entry<E extends Entry<E>> extends GuiComponent implements GuiEventListener {
+        @Deprecated DynamicErrorFreeEntryListWidget<E> parent;
+        
+        public Entry() {
+        }
+        
+        public abstract void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta);
+        
+        public boolean isMouseOver(double double_1, double double_2) {
+            return Objects.equals(this.parent.getItemAtPosition(double_1, double_2), this);
+        }
+        
+        public DynamicErrorFreeEntryListWidget<E> getParent() {
+            return parent;
+        }
+        
+        public void setParent(DynamicErrorFreeEntryListWidget<E> parent) {
+            this.parent = parent;
+        }
+        
+        public abstract int getItemHeight();
+        
+        @Deprecated
+        public int getMorePossibleHeight() {
+            return -1;
+        }
+    }
+    
+    @Environment(EnvType.CLIENT)
+    class Entries extends AbstractList<E> {
+        private final ArrayList<E> items;
+        
+        private Entries() {
+            this.items = Lists.newArrayList();
+        }
+        
+        @Override
+        public void clear() {
+            items.clear();
+        }
+        
+        @Override
+        public E get(int int_1) {
+            return this.items.get(int_1);
+        }
+        
+        @Override
+        public int size() {
+            return this.items.size();
+        }
+        
+        @Override
+        public E set(int int_1, E itemListWidget$Item_1) {
+            E itemListWidget$Item_2 = this.items.set(int_1, itemListWidget$Item_1);
+            itemListWidget$Item_1.parent = DynamicErrorFreeEntryListWidget.this;
+            return itemListWidget$Item_2;
+        }
+        
+        @Override
+        public void add(int int_1, E itemListWidget$Item_1) {
+            this.items.add(int_1, itemListWidget$Item_1);
+            itemListWidget$Item_1.parent = DynamicErrorFreeEntryListWidget.this;
+        }
+        
+        @Override
+        public E remove(int int_1) {
+            return this.items.remove(int_1);
+        }
+    }
+}
+

+ 58 - 0
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/ErrorDisplayer.java

@@ -0,0 +1,58 @@
+/*
+ * 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.impl;
+
+import me.shedaniel.cloth.api.client.events.v0.ClothClientHooks;
+import me.shedaniel.rei.RoughlyEnoughItemsState;
+import me.shedaniel.rei.gui.WarningAndErrorScreen;
+import net.fabricmc.api.ClientModInitializer;
+import net.minecraft.client.Minecraft;
+import net.minecraft.world.InteractionResult;
+
+public class ErrorDisplayer implements ClientModInitializer {
+    @Override
+    public void onInitializeClient() {
+        ClothClientHooks.SCREEN_INIT_PRE.register((client, screen, screenHooks) -> {
+            if ((!RoughlyEnoughItemsState.getErrors().isEmpty() || !RoughlyEnoughItemsState.getWarnings().isEmpty()) && !(screen instanceof WarningAndErrorScreen)) {
+                WarningAndErrorScreen warningAndErrorScreen = new WarningAndErrorScreen("initialization", RoughlyEnoughItemsState.getWarnings(), RoughlyEnoughItemsState.getErrors(), (parent) -> {
+                    if (RoughlyEnoughItemsState.getErrors().isEmpty()) {
+                        RoughlyEnoughItemsState.clear();
+                        RoughlyEnoughItemsState.continues();
+                        Minecraft.getInstance().setScreen(parent);
+                    } else {
+                        Minecraft.getInstance().stop();
+                    }
+                });
+                warningAndErrorScreen.setParent(screen);
+                try {
+                    if (client.screen != null) client.screen.removed();
+                } catch (Throwable ignored) {
+                }
+                client.screen = null;
+                client.setScreen(warningAndErrorScreen);
+            }
+            return InteractionResult.PASS;
+        });
+    }
+}

+ 1 - 22
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/ScreenHelper.java

@@ -31,20 +31,17 @@ import me.shedaniel.cloth.api.client.events.v0.ClothClientHooks;
 import me.shedaniel.math.Point;
 import me.shedaniel.math.Rectangle;
 import me.shedaniel.math.api.Executor;
-import me.shedaniel.rei.RoughlyEnoughItemsState;
 import me.shedaniel.rei.api.*;
 import me.shedaniel.rei.api.widgets.Tooltip;
 import me.shedaniel.rei.gui.ContainerScreenOverlay;
 import me.shedaniel.rei.gui.OverlaySearchField;
 import me.shedaniel.rei.gui.RecipeScreen;
-import me.shedaniel.rei.gui.WarningAndErrorScreen;
 import me.shedaniel.rei.gui.config.SearchFieldLocation;
 import me.shedaniel.rei.gui.widget.TextFieldWidget;
 import net.fabricmc.api.ClientModInitializer;
 import net.fabricmc.api.EnvType;
 import net.fabricmc.api.Environment;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.fabricmc.loader.api.FabricLoader;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.screens.Screen;
 import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
@@ -262,28 +259,10 @@ public class ScreenHelper implements ClientModInitializer, REIHelper {
     @Override
     public void onInitializeClient() {
         ClothClientHooks.SCREEN_INIT_PRE.register((client, screen, screenHooks) -> {
-            if ((!RoughlyEnoughItemsState.getErrors().isEmpty() || !RoughlyEnoughItemsState.getWarnings().isEmpty()) && !(screen instanceof WarningAndErrorScreen)) {
-                WarningAndErrorScreen warningAndErrorScreen = new WarningAndErrorScreen("initialization", RoughlyEnoughItemsState.getWarnings(), RoughlyEnoughItemsState.getErrors(), (parent) -> {
-                    if (RoughlyEnoughItemsState.getErrors().isEmpty()) {
-                        RoughlyEnoughItemsState.clear();
-                        RoughlyEnoughItemsState.continues();
-                        Minecraft.getInstance().setScreen(parent);
-                    } else {
-                        Minecraft.getInstance().stop();
-                    }
-                });
-                warningAndErrorScreen.setParent(screen);
-                try {
-                    if (client.screen != null) client.screen.removed();
-                } catch (Throwable ignored) {
-                }
-                client.screen = null;
-                client.setScreen(warningAndErrorScreen);
-            } else if (previousContainerScreen != screen && screen instanceof AbstractContainerScreen)
+            if (previousContainerScreen != screen && screen instanceof AbstractContainerScreen)
                 previousContainerScreen = (AbstractContainerScreen<?>) screen;
             return InteractionResult.PASS;
         });
-        RoughlyEnoughItemsState.checkRequiredFabricModules();
         Executor.run(() -> () -> {
             ClientTickEvents.END_CLIENT_TICK.register(minecraft -> {
                 if (isOverlayVisible() && getSearchField() != null)

+ 1 - 6
RoughlyEnoughItems-runtime/src/main/resources/fabric.mod.json

@@ -15,13 +15,8 @@
   "license": "MIT",
   "icon": "icon.png",
   "entrypoints": {
-    "client": [
-      "me.shedaniel.rei.RoughlyEnoughItemsCore",
-      "me.shedaniel.rei.impl.ClientHelperImpl",
-      "me.shedaniel.rei.impl.ScreenHelper"
-    ],
     "main": [
-      "me.shedaniel.rei.RoughlyEnoughItemsNetwork"
+      "me.shedaniel.rei.RoughlyEnoughItemsInitializer"
     ],
     "modmenu": [
       "me.shedaniel.rei.REIModMenuEntryPoint"

+ 4 - 0
build.gradle

@@ -56,6 +56,7 @@ allprojects {
         mappings(loom.officialMojangMappings())
         modApi("net.fabricmc:fabric-loader:${project.fabricloader_version}")
         modApi(fabricApi.module("fabric-api-base", project.fabric_api))
+        include(fabricApi.module("fabric-api-base", project.fabric_api))
         modApi(fabricApi.module("fabric-resource-loader-v0", project.fabric_api))
         modApi(fabricApi.module("fabric-networking-v0", project.fabric_api))
         modApi(fabricApi.module("fabric-lifecycle-events-v1", project.fabric_api))
@@ -64,6 +65,9 @@ allprojects {
         modApi("me.shedaniel.cloth.api:cloth-client-events-v0:${cloth_client_events_v0_version}") {
             transitive(false)
         }
+        include("me.shedaniel.cloth.api:cloth-client-events-v0:${cloth_client_events_v0_version}") {
+            transitive(false)
+        }
         modApi("me.shedaniel.cloth:cloth-config:${cloth_config_version}:fabric") {
             exclude(module: "fabric-api")
         }

+ 4 - 3
src/main/resources/fabric.mod.json

@@ -14,11 +14,12 @@
   },
   "depends": {
     "fabricloader": "*",
-    "cloth-client-events-v0": ">=1.0.2",
-    "cloth-basic-math": "*",
-    "cloth-config2": ">=4.8.3",
+    "cloth-client-events-v0": ">=1.5.47",
     "minecraft": "~1.16.2-rc.1"
   },
+  "breaks": {
+    "cloth-config2": "<4.10.9"
+  },
   "license": "MIT",
   "icon": "icon.png"
 }