ソースを参照

Fix #409 and more

Signed-off-by: shedaniel <daniel@shedaniel.me>
shedaniel 4 年 前
コミット
ae1facc546
24 ファイル変更305 行追加152 行削除
  1. 49 3
      RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/ClientHelper.java
  2. 12 11
      RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/DisplayHelper.java
  3. 3 3
      RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/TextRepresentable.java
  4. 8 0
      RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/widgets/Label.java
  5. 12 2
      RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/utils/ImmutableLiteralText.java
  6. 2 1
      RoughlyEnoughItems-default-plugin/src/main/java/me/shedaniel/rei/plugin/DefaultPlugin.java
  7. 10 7
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java
  8. 8 7
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/config/entry/FilteringScreen.java
  9. 70 0
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/credits/CreditsEntryListWidget.java
  10. 6 1
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/credits/CreditsScreen.java
  11. 2 2
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/plugin/DefaultRuntimePlugin.java
  12. 2 4
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/widget/EntryListEntryWidget.java
  13. 45 29
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java
  14. 17 13
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/widget/FavoritesListWidget.java
  15. 2 32
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/ClientHelperImpl.java
  16. 1 1
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/ConfigObjectImpl.java
  17. 5 11
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/FluidEntryStack.java
  18. 1 1
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/InternalWidgets.java
  19. 1 10
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/ItemEntryStack.java
  20. 21 0
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/TextTransformations.java
  21. 16 7
      RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/widgets/LabelWidget.java
  22. 8 6
      RoughlyEnoughItems-runtime/src/main/resources/fabric.mod.json
  23. 3 0
      RoughlyEnoughItems-runtime/src/main/resources/roughlyenoughitems-runtime.accessWidener
  24. 1 1
      gradle.properties

+ 49 - 3
RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/ClientHelper.java

@@ -25,12 +25,18 @@ package me.shedaniel.rei.api;
 
 import me.shedaniel.rei.impl.Internals;
 import me.shedaniel.rei.utils.CollectionUtils;
+import me.shedaniel.rei.utils.FormattingUtils;
 import net.fabricmc.api.EnvType;
 import net.fabricmc.api.Environment;
+import net.minecraft.ChatFormatting;
+import net.minecraft.client.gui.chat.NarratorChatListener;
+import net.minecraft.core.Registry;
 import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.TextComponent;
 import net.minecraft.resources.ResourceLocation;
 import net.minecraft.world.item.Item;
 import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
 import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -135,7 +141,11 @@ public interface ClientHelper {
      * @param item the item to find
      * @return the mod name
      */
-    String getModFromItem(Item item);
+    default String getModFromItem(Item item) {
+        if (item.equals(Items.AIR))
+            return "";
+        return getModFromIdentifier(Registry.ITEM.getKey(item));
+    }
     
     /**
      * Tries to delete the player's cursor item
@@ -148,7 +158,12 @@ public interface ClientHelper {
      * @param item the item to find
      * @return the mod name with blue and italic formatting
      */
-    Component getFormattedModFromItem(Item item);
+    default Component getFormattedModFromItem(Item item) {
+        String mod = getModFromItem(item);
+        if (mod.isEmpty())
+            return NarratorChatListener.NO_TITLE;
+        return new TextComponent(mod).withStyle(ChatFormatting.BLUE, ChatFormatting.ITALIC);
+    }
     
     /**
      * Gets the formatted mod from an identifier
@@ -156,7 +171,38 @@ public interface ClientHelper {
      * @param identifier the identifier to find
      * @return the mod name with blue and italic formatting
      */
-    Component getFormattedModFromIdentifier(ResourceLocation identifier);
+    default Component getFormattedModFromIdentifier(ResourceLocation identifier) {
+        String mod = getModFromIdentifier(identifier);
+        if (mod.isEmpty())
+            return NarratorChatListener.NO_TITLE;
+        return new TextComponent(mod).withStyle(ChatFormatting.BLUE, ChatFormatting.ITALIC);
+    }
+    
+    /**
+     * Gets the mod from a modid
+     *
+     * @param modid the modid of the mod
+     * @return the mod name with blue and italic formatting
+     */
+    default Component getFormattedModFromModId(String modid) {
+        String mod = getModFromModId(modid);
+        if (mod.isEmpty())
+            return NarratorChatListener.NO_TITLE;
+        return new TextComponent(mod).withStyle(ChatFormatting.BLUE, ChatFormatting.ITALIC);
+    }
+    
+    default List<Component> appendModIdToTooltips(List<Component> components, String modId) {
+        final String modName = ClientHelper.getInstance().getModFromModId(modId);
+        boolean alreadyHasMod = false;
+        for (Component s : components)
+            if (FormattingUtils.stripFormatting(s.getString()).equalsIgnoreCase(modName)) {
+                alreadyHasMod = true;
+                break;
+            }
+        if (!alreadyHasMod)
+            components.add(ClientHelper.getInstance().getFormattedModFromModId(modId));
+        return components;
+    }
     
     /**
      * Gets the mod from an identifier

+ 12 - 11
RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/DisplayHelper.java

@@ -36,8 +36,6 @@ import org.jetbrains.annotations.NotNull;
 import java.util.List;
 import java.util.function.Supplier;
 
-import static net.minecraft.world.InteractionResult.PASS;
-
 @Environment(EnvType.CLIENT)
 public interface DisplayHelper {
     
@@ -167,15 +165,18 @@ public interface DisplayHelper {
          * @see BaseBoundsHandler#registerExclusionZones(Class, Supplier) for easier api
          */
         default InteractionResult canItemSlotWidgetFit(int left, int top, T screen, Rectangle fullBounds) {
-            InteractionResult fit = isInZone(left, top);
-            if (fit == InteractionResult.FAIL)
-                return InteractionResult.FAIL;
-            InteractionResult fit2 = isInZone(left + 18, top + 18);
-            if (fit2 == InteractionResult.FAIL)
-                return InteractionResult.FAIL;
-            if (fit == InteractionResult.SUCCESS && fit2 == InteractionResult.SUCCESS)
-                return InteractionResult.SUCCESS;
-            return PASS;
+            InteractionResult fit;
+            fit = isInZone(left, top);
+            if (fit != InteractionResult.PASS)
+                return fit;
+            fit = isInZone(left + 18, top);
+            if (fit != InteractionResult.PASS)
+                return fit;
+            fit = isInZone(left, top + 18);
+            if (fit != InteractionResult.PASS)
+                return fit;
+            fit = isInZone(left + 18, top + 18);
+            return fit;
         }
         
         @Override

+ 3 - 3
RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/TextRepresentable.java

@@ -26,10 +26,10 @@ package me.shedaniel.rei.api;
 import me.shedaniel.math.impl.PointHelper;
 import me.shedaniel.rei.api.widgets.Tooltip;
 import me.shedaniel.rei.utils.FormattingUtils;
+import me.shedaniel.rei.utils.ImmutableLiteralText;
 import net.fabricmc.api.EnvType;
 import net.fabricmc.api.Environment;
 import net.minecraft.network.chat.Component;
-import net.minecraft.network.chat.TextComponent;
 import org.jetbrains.annotations.NotNull;
 
 @Environment(EnvType.CLIENT)
@@ -41,11 +41,11 @@ public interface TextRepresentable {
             if (tooltip != null && !tooltip.getText().isEmpty())
                 return tooltip.getText().get(0);
         }
-        return new TextComponent("");
+        return ImmutableLiteralText.EMPTY;
     }
     
     @NotNull
     default Component asFormatStrippedText() {
-        return new TextComponent(FormattingUtils.stripFormatting(asFormattedText().getString()));
+        return new ImmutableLiteralText(FormattingUtils.stripFormatting(asFormattedText().getString()));
     }
 }

+ 8 - 0
RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/api/widgets/Label.java

@@ -311,6 +311,8 @@ public abstract class Label extends WidgetWithBounds {
     
     public abstract void setMessage(@NotNull FormattedText message);
     
+    public abstract void setRainbow(boolean rainbow);
+    
     @NotNull
     public final Label text(@NotNull Component text) {
         setText(text);
@@ -322,4 +324,10 @@ public abstract class Label extends WidgetWithBounds {
         setMessage(message);
         return this;
     }
+    
+    @NotNull
+    public final Label rainbow(boolean rainbow) {
+        setRainbow(rainbow);
+        return this;
+    }
 }

+ 12 - 2
RoughlyEnoughItems-api/src/main/java/me/shedaniel/rei/utils/ImmutableLiteralText.java

@@ -34,7 +34,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 
-public final class ImmutableLiteralText implements Component {
+public final class ImmutableLiteralText implements MutableComponent {
     public static final ImmutableLiteralText EMPTY = new ImmutableLiteralText("");
     private final String content;
     private FormattedCharSequence orderedText;
@@ -60,7 +60,7 @@ public final class ImmutableLiteralText implements Component {
     
     @Override
     public MutableComponent plainCopy() {
-        return new TextComponent(content);
+        return this;
     }
     
     @Override
@@ -85,4 +85,14 @@ public final class ImmutableLiteralText implements Component {
         }
         return orderedText;
     }
+    
+    @Override
+    public MutableComponent setStyle(Style style) {
+        return new TextComponent(content).withStyle(style);
+    }
+    
+    @Override
+    public MutableComponent append(Component component) {
+        return new TextComponent(content).append(component);
+    }
 }

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

@@ -32,6 +32,7 @@ import it.unimi.dsi.fastutil.objects.ReferenceSet;
 import me.shedaniel.math.Rectangle;
 import me.shedaniel.rei.api.*;
 import me.shedaniel.rei.api.fluid.FluidSupportProvider;
+import me.shedaniel.rei.api.fractions.Fraction;
 import me.shedaniel.rei.api.plugins.REIPluginV0;
 import me.shedaniel.rei.plugin.autocrafting.DefaultRecipeBookHandler;
 import me.shedaniel.rei.plugin.beacon.DefaultBeaconBaseCategory;
@@ -403,7 +404,7 @@ public class DefaultPlugin implements REIPluginV0, BuiltinPlugin {
         FluidSupportProvider.getInstance().registerProvider(itemStack -> {
             Item item = itemStack.getItem();
             if (item instanceof BucketItem)
-                return InteractionResultHolder.success(Stream.of(EntryStack.create(((BucketItem) item).content, 1000)));
+                return InteractionResultHolder.success(Stream.of(EntryStack.create(((BucketItem) item).content, Fraction.ofWhole(1))));
             return InteractionResultHolder.pass(null);
         });
 //        SubsetsRegistry subsetsRegistry = SubsetsRegistry.INSTANCE;

+ 10 - 7
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/ContainerScreenOverlay.java

@@ -74,7 +74,10 @@ import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.*;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
 
 @ApiStatus.Internal
 public class ContainerScreenOverlay extends WidgetWithBounds implements REIOverlay {
@@ -319,7 +322,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds implements REIOverl
         }
         subsetsButtonBounds = getSubsetsButtonBounds();
         if (ConfigObject.getInstance().isSubsetsEnabled()) {
-            widgets.add(InternalWidgets.wrapLateRenderable(InternalWidgets.wrapTranslate(Widgets.createButton(subsetsButtonBounds, ((ClientHelperImpl) ClientHelper.getInstance()).isAprilFools.get() ? new TranslatableComponent("text.rei.tiny_potato") : new TranslatableComponent("text.rei.subsets"))
+            widgets.add(InternalWidgets.wrapLateRenderable(InternalWidgets.wrapTranslate(Widgets.createButton(subsetsButtonBounds, ClientHelperImpl.getInstance().isAprilFools.get() ? new TranslatableComponent("text.rei.tiny_potato") : new TranslatableComponent("text.rei.subsets"))
                     .onClick(button -> {
                         if (subsetsMenu == null) {
                             wrappedSubsetsMenu = InternalWidgets.wrapTranslate(InternalWidgets.wrapLateRenderable(this.subsetsMenu = Menu.createSubsetsMenuFromRegistry(new Point(this.subsetsButtonBounds.x, this.subsetsButtonBounds.getMaxY()))), 0, 0, 400);
@@ -338,7 +341,7 @@ public class ContainerScreenOverlay extends WidgetWithBounds implements REIOverl
             }).tooltipLine(I18n.get("text.rei.go_back_first_page")).focusable(false).onRender((matrices, label) -> {
                 label.setClickable(ENTRY_LIST_WIDGET.getTotalPages() > 1);
                 label.setText(new TextComponent(String.format("%s/%s", ENTRY_LIST_WIDGET.getPage() + 1, Math.max(ENTRY_LIST_WIDGET.getTotalPages(), 1))));
-            }));
+            }).rainbow(new Random().nextFloat() < 1.0E-4D || ClientHelperImpl.getInstance().isAprilFools.get()));
         }
         if (ConfigObject.getInstance().isCraftableFilterEnabled()) {
             Rectangle area = getCraftableToggleArea();
@@ -589,10 +592,10 @@ public class ContainerScreenOverlay extends WidgetWithBounds implements REIOverl
     public void renderTooltipInner(PoseStack matrices, List<FormattedCharSequence> lines, int mouseX, int mouseY) {
         if (lines.isEmpty())
             return;
-        tooltipWidth = lines.stream().map(font::width).max(Integer::compareTo).get();
-        tooltipHeight = lines.size() <= 1 ? 8 : lines.size() * 10;
-        tooltipLines = lines;
-        ScreenHelper.drawHoveringWidget(matrices, mouseX, mouseY, renderTooltipCallback, tooltipWidth, tooltipHeight, 0);
+        matrices.pushPose();
+        matrices.translate(0, 0, 500);
+        minecraft.screen.renderTooltip(matrices, lines, mouseX, mouseY);
+        matrices.popPose();
     }
     
     public void addTooltip(@Nullable Tooltip tooltip) {

+ 8 - 7
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/config/entry/FilteringScreen.java

@@ -327,17 +327,18 @@ public class FilteringScreen extends Screen {
     }
     
     public void updateEntriesPosition() {
+        int entrySize = entrySize();
         this.innerBounds = updateInnerBounds(getBounds());
-        int width = innerBounds.width / entrySize();
-        int pageHeight = innerBounds.height / entrySize();
+        int width = innerBounds.width / entrySize;
+        int pageHeight = innerBounds.height / entrySize;
         int slotsToPrepare = Math.max(entryStacks.size() * 3, width * pageHeight * 3);
         int currentX = 0;
         int currentY = 0;
         List<EntryListEntry> entries = Lists.newArrayList();
         for (int i = 0; i < slotsToPrepare; i++) {
-            int xPos = currentX * entrySize() + innerBounds.x;
-            int yPos = currentY * entrySize() + innerBounds.y;
-            entries.add(new EntryListEntry(xPos, yPos));
+            int xPos = currentX * entrySize + innerBounds.x;
+            int yPos = currentY * entrySize + innerBounds.y;
+            entries.add(new EntryListEntry(xPos, yPos, entrySize));
             currentX++;
             if (currentX >= width) {
                 currentX = 0;
@@ -453,10 +454,10 @@ public class FilteringScreen extends Screen {
         private boolean filtered = false;
         private boolean dirty = true;
         
-        private EntryListEntry(int x, int y) {
+        private EntryListEntry(int x, int y, int entrySize) {
             super(new Point(x, y));
             this.backupY = y;
-            getBounds().width = getBounds().height = entrySize();
+            getBounds().width = getBounds().height = entrySize;
             interactableFavorites(false);
             interactable(false);
             noHighlight();

+ 70 - 0
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/credits/CreditsEntryListWidget.java

@@ -25,12 +25,20 @@ package me.shedaniel.rei.gui.credits;
 
 import com.mojang.blaze3d.vertex.PoseStack;
 import me.shedaniel.clothconfig2.gui.widget.DynamicNewSmoothScrollingEntryListWidget;
+import me.shedaniel.rei.impl.TextTransformations;
+import net.minecraft.ChatFormatting;
+import net.minecraft.Util;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.GuiComponent;
+import net.minecraft.client.resources.sounds.SimpleSoundInstance;
 import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.TextComponent;
+import net.minecraft.sounds.SoundEvents;
 import net.minecraft.util.FormattedCharSequence;
 import org.jetbrains.annotations.ApiStatus;
 
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.List;
 
 @ApiStatus.Internal
@@ -139,4 +147,66 @@ public class CreditsEntryListWidget extends DynamicNewSmoothScrollingEntryListWi
         }
     }
     
+    public static class LinkItem extends CreditsItem {
+        private Component text;
+        private List<FormattedCharSequence> textSplit;
+        private String link;
+        private boolean contains;
+        private boolean rainbow;
+        
+        public LinkItem(Component text, String link, int width, boolean rainbow) {
+            this.text = text;
+            this.textSplit = Minecraft.getInstance().font.split(text, width);
+            this.link = link;
+            this.rainbow = rainbow;
+        }
+        
+        @Override
+        public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) {
+            contains = mouseX >= x && mouseX <= x + entryWidth && mouseY >= y && mouseY <= y + entryHeight;
+            if (contains) {
+                Minecraft.getInstance().screen.renderTooltip(matrices, new TextComponent("Click to open link."), mouseX, mouseY);
+                int yy = y;
+                for (FormattedCharSequence textSp : textSplit) {
+                    FormattedCharSequence underlined = characterVisitor -> {
+                        return textSp.accept((charIndex, style, codePoint) -> characterVisitor.accept(charIndex, style.applyFormat(ChatFormatting.UNDERLINE), codePoint));
+                    };
+                    if (rainbow) underlined = TextTransformations.applyRainbow(underlined, x + 5, yy);
+                    Minecraft.getInstance().font.drawShadow(matrices, underlined, x + 5, yy, 0xff1fc3ff);
+                    yy += 12;
+                }
+            } else {
+                int yy = y;
+                for (FormattedCharSequence textSp : textSplit) {
+                    if (rainbow) textSp = TextTransformations.applyRainbow(textSp, x + 5, yy);
+                    Minecraft.getInstance().font.drawShadow(matrices, textSp, x + 5, yy, 0xff1fc3ff);
+                    yy += 12;
+                }
+            }
+        }
+        
+        @Override
+        public int getItemHeight() {
+            return 12 * textSplit.size();
+        }
+        
+        @Override
+        public boolean changeFocus(boolean boolean_1) {
+            return false;
+        }
+        
+        @Override
+        public boolean mouseClicked(double mouseX, double mouseY, int button) {
+            if (contains && button == 0) {
+                Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+                try {
+                    Util.getPlatform().openUri(new URI(link));
+                    return true;
+                } catch (URISyntaxException e) {
+                    e.printStackTrace();
+                }
+            }
+            return false;
+        }
+    }
 }

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

@@ -31,6 +31,7 @@ import me.shedaniel.rei.impl.ScreenHelper;
 import me.shedaniel.rei.utils.ImmutableLiteralText;
 import net.fabricmc.loader.api.FabricLoader;
 import net.fabricmc.loader.api.metadata.CustomValue;
+import net.minecraft.client.gui.GuiComponent;
 import net.minecraft.client.gui.chat.NarratorChatListener;
 import net.minecraft.client.gui.components.AbstractButton;
 import net.minecraft.client.gui.screens.Screen;
@@ -112,6 +113,10 @@ public class CreditsScreen extends Screen {
                 }
             } else entryListWidget.creditsAddEntry(new TextCreditsItem(new ImmutableLiteralText(line)));
         entryListWidget.creditsAddEntry(new TextCreditsItem(NarratorChatListener.NO_TITLE));
+        entryListWidget.creditsAddEntry(new CreditsEntryListWidget.LinkItem(new ImmutableLiteralText("Visit the project at GitHub."), "https://www.github.com/shedaniel/RoughlyEnoughItems", entryListWidget.getItemWidth(), false));
+        entryListWidget.creditsAddEntry(new CreditsEntryListWidget.LinkItem(new ImmutableLiteralText("Visit the project page at CurseForge."), "https://www.curseforge.com/minecraft/mc-mods/roughly-enough-items", entryListWidget.getItemWidth(), false));
+        entryListWidget.creditsAddEntry(new CreditsEntryListWidget.LinkItem(new ImmutableLiteralText("Support the project via Patreon!"), "https://patreon.com/shedaniel", entryListWidget.getItemWidth(), true));
+        entryListWidget.creditsAddEntry(new TextCreditsItem(NarratorChatListener.NO_TITLE));
         children.add(buttonDone = new AbstractButton(width / 2 - 100, height - 26, 200, 20, new TranslatableComponent("gui.done")) {
             @Override
             public void onPress() {
@@ -133,7 +138,7 @@ public class CreditsScreen extends Screen {
     public void render(PoseStack matrices, int int_1, int int_2, float float_1) {
         this.renderDirtBackground(0);
         this.entryListWidget.render(matrices, int_1, int_2, float_1);
-        this.drawCenteredString(matrices, this.font, I18n.get("text.rei.credits"), this.width / 2, 16, 16777215);
+        drawCenteredString(matrices, this.font, I18n.get("text.rei.credits"), this.width / 2, 16, 16777215);
         super.render(matrices, int_1, int_2, float_1);
         buttonDone.render(matrices, int_1, int_2, float_1);
     }

+ 2 - 2
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/plugin/DefaultRuntimePlugin.java

@@ -68,12 +68,12 @@ public class DefaultRuntimePlugin implements REIPluginV0 {
             
             @Override
             public boolean isEmpty() {
-                return !((ClientHelperImpl) ClientHelper.getInstance()).isAprilFools.get();
+                return !ClientHelperImpl.getInstance().isAprilFools.get();
             }
             
             @Override
             public @Nullable Tooltip getTooltip(Point point) {
-                return Tooltip.create(new TextComponent("Kibby"));
+                return Tooltip.create(new TextComponent("Kirby"), ClientHelper.getInstance().getFormattedModFromModId("Dream Land"));
             }
         });
     }

+ 2 - 4
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/widget/EntryListEntryWidget.java

@@ -31,15 +31,13 @@ import me.shedaniel.rei.api.EntryStack;
 import net.minecraft.client.gui.screens.Screen;
 import net.minecraft.world.item.Item;
 
-import static me.shedaniel.rei.gui.widget.EntryListWidget.entrySize;
-
 public class EntryListEntryWidget extends EntryWidget {
     public int backupY;
     
-    protected EntryListEntryWidget(Point point) {
+    protected EntryListEntryWidget(Point point, int entrySize) {
         super(point);
         this.backupY = point.y;
-        getBounds().width = getBounds().height = entrySize();
+        getBounds().width = getBounds().height = entrySize;
     }
     
     @Override

+ 45 - 29
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/widget/EntryListWidget.java

@@ -112,31 +112,42 @@ public class EntryListWidget extends WidgetWithBounds {
         return Mth.ceil(SIZE * ConfigObject.getInstance().getEntrySize());
     }
     
-    static boolean notSteppingOnExclusionZones(int left, int top, Rectangle listArea) {
+    static boolean notSteppingOnExclusionZones(int left, int top, int width, int height, Rectangle listArea) {
         Minecraft instance = Minecraft.getInstance();
         for (OverlayDecider decider : DisplayHelper.getInstance().getSortedOverlayDeciders(instance.screen.getClass())) {
-            InteractionResult fit = decider.isInZone(left, top);
-            if (fit == InteractionResult.FAIL)
-                return false;
-            InteractionResult fit2 = decider.isInZone(left + 18, top + 18);
-            if (fit2 == InteractionResult.FAIL)
-                return false;
-            if (fit == InteractionResult.SUCCESS && fit2 == InteractionResult.SUCCESS)
-                return true;
+            InteractionResult fit = canItemSlotWidgetFit(left, top, width, height, decider);
+            if (fit != InteractionResult.PASS)
+                return fit == InteractionResult.SUCCESS;
         }
         return true;
     }
     
+    private static InteractionResult canItemSlotWidgetFit(int left, int top, int width, int height, OverlayDecider decider) {
+        InteractionResult fit;
+        fit = decider.isInZone(left, top);
+        if (fit != InteractionResult.PASS)
+            return fit;
+        fit = decider.isInZone(left + width, top);
+        if (fit != InteractionResult.PASS)
+            return fit;
+        fit = decider.isInZone(left, top + height);
+        if (fit != InteractionResult.PASS)
+            return fit;
+        fit = decider.isInZone(left + width, top + height);
+        return fit;
+    }
+    
     private static Rectangle updateInnerBounds(Rectangle bounds) {
+        int entrySize = entrySize();
         if (ConfigObject.getInstance().isEntryListWidgetScrolled()) {
-            int width = Math.max(Mth.floor((bounds.width - 2 - 6) / (float) entrySize()), 1);
+            int width = Math.max(Mth.floor((bounds.width - 2 - 6) / (float) entrySize), 1);
             if (ConfigObject.getInstance().isLeftHandSidePanel())
-                return new Rectangle((int) (bounds.getCenterX() - width * (entrySize() / 2f) + 3), bounds.y, width * entrySize(), bounds.height);
-            return new Rectangle((int) (bounds.getCenterX() - width * (entrySize() / 2f) - 3), bounds.y, width * entrySize(), bounds.height);
+                return new Rectangle((int) (bounds.getCenterX() - width * (entrySize / 2f) + 3), bounds.y, width * entrySize, bounds.height);
+            return new Rectangle((int) (bounds.getCenterX() - width * (entrySize / 2f) - 3), bounds.y, width * entrySize, bounds.height);
         }
-        int width = Math.max(Mth.floor((bounds.width - 2) / (float) entrySize()), 1);
-        int height = Math.max(Mth.floor((bounds.height - 2) / (float) entrySize()), 1);
-        return new Rectangle((int) (bounds.getCenterX() - width * (entrySize() / 2f)), (int) (bounds.getCenterY() - height * (entrySize() / 2f)), width * entrySize(), height * entrySize());
+        int width = Math.max(Mth.floor((bounds.width - 2) / (float) entrySize), 1);
+        int height = Math.max(Mth.floor((bounds.height - 2) / (float) entrySize), 1);
+        return new Rectangle((int) (bounds.getCenterX() - width * (entrySize / 2f)), (int) (bounds.getCenterY() - height * (entrySize / 2f)), width * entrySize, height * entrySize);
     }
     
     @Override
@@ -234,9 +245,11 @@ public class EntryListWidget extends WidgetWithBounds {
             blockedCount = 0;
             
             Stream<EntryListEntry> entryStream = this.entries.stream().skip(nextIndex).filter(entry -> {
-                entry.getBounds().y = (int) (entry.backupY - scrolling.scrollAmount);
-                if (entry.getBounds().y > bounds.getMaxY()) return false;
-                if (notSteppingOnExclusionZones(entry.getBounds().x, entry.getBounds().y, innerBounds)) {
+                Rectangle entryBounds = entry.getBounds();
+                
+                entryBounds.y = (int) (entry.backupY - scrolling.scrollAmount);
+                if (entryBounds.y > this.bounds.getMaxY()) return false;
+                if (notSteppingOnExclusionZones(entryBounds.x, entryBounds.y, entryBounds.width, entryBounds.height, innerBounds)) {
                     EntryStack stack = allStacks.get(i[0]++);
                     if (!stack.isEmpty()) {
                         entry.entry(stack);
@@ -368,17 +381,20 @@ public class EntryListWidget extends WidgetWithBounds {
     }
     
     public void updateEntriesPosition() {
+        int entrySize = entrySize();
         this.innerBounds = updateInnerBounds(bounds);
         if (!ConfigObject.getInstance().isEntryListWidgetScrolled()) {
             this.renders = Lists.newArrayList();
             page = Math.max(page, 0);
             List<EntryListEntry> entries = Lists.newArrayList();
-            int width = innerBounds.width / entrySize();
-            int height = innerBounds.height / entrySize();
+            int width = innerBounds.width / entrySize;
+            int height = innerBounds.height / entrySize;
             for (int currentY = 0; currentY < height; currentY++) {
                 for (int currentX = 0; currentX < width; currentX++) {
-                    if (notSteppingOnExclusionZones(currentX * entrySize() + innerBounds.x, currentY * entrySize() + innerBounds.y, innerBounds)) {
-                        entries.add((EntryListEntry) new EntryListEntry(currentX * entrySize() + innerBounds.x, currentY * entrySize() + innerBounds.y).noBackground());
+                    int slotX = currentX * entrySize + innerBounds.x;
+                    int slotY = currentY * entrySize + innerBounds.y;
+                    if (notSteppingOnExclusionZones(slotX - 1, slotY - 1, entrySize, entrySize, innerBounds)) {
+                        entries.add((EntryListEntry) new EntryListEntry(slotX, slotY, entrySize).noBackground());
                     }
                 }
             }
@@ -393,16 +409,16 @@ public class EntryListWidget extends WidgetWithBounds {
             this.widgets.addAll(entries);
         } else {
             page = 0;
-            int width = innerBounds.width / entrySize();
-            int pageHeight = innerBounds.height / entrySize();
+            int width = innerBounds.width / entrySize;
+            int pageHeight = innerBounds.height / entrySize;
             int slotsToPrepare = Math.max(allStacks.size() * 3, width * pageHeight * 3);
             int currentX = 0;
             int currentY = 0;
             List<EntryListEntry> entries = Lists.newArrayList();
             for (int i = 0; i < slotsToPrepare; i++) {
-                int xPos = currentX * entrySize() + innerBounds.x;
-                int yPos = currentY * entrySize() + innerBounds.y;
-                entries.add((EntryListEntry) new EntryListEntry(xPos, yPos).noBackground());
+                int xPos = currentX * entrySize + innerBounds.x;
+                int yPos = currentY * entrySize + innerBounds.y;
+                entries.add((EntryListEntry) new EntryListEntry(xPos, yPos, entrySize).noBackground());
                 currentX++;
                 if (currentX >= width) {
                     currentX = 0;
@@ -531,8 +547,8 @@ public class EntryListWidget extends WidgetWithBounds {
     }
     
     private class EntryListEntry extends EntryListEntryWidget {
-        private EntryListEntry(int x, int y) {
-            super(new Point(x, y));
+        private EntryListEntry(int x, int y, int entrySize) {
+            super(new Point(x, y), entrySize);
         }
         
         @Override

+ 17 - 13
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/gui/widget/FavoritesListWidget.java

@@ -84,10 +84,11 @@ public class FavoritesListWidget extends WidgetWithBounds {
     private boolean draggingScrollBar = false;
     
     private static Rectangle updateInnerBounds(Rectangle bounds) {
-        int width = Math.max(Mth.floor((bounds.width - 2 - 6) / (float) entrySize()), 1);
+        int entrySize = entrySize();
+        int width = Math.max(Mth.floor((bounds.width - 2 - 6) / (float) entrySize), 1);
         if (!ConfigObject.getInstance().isLeftHandSidePanel())
-            return new Rectangle((int) (bounds.getCenterX() - width * (entrySize() / 2f) + 3), bounds.y, width * entrySize(), bounds.height);
-        return new Rectangle((int) (bounds.getCenterX() - width * (entrySize() / 2f) - 3), bounds.y, width * entrySize(), bounds.height);
+            return new Rectangle((int) (bounds.getCenterX() - width * (entrySize / 2f) + 3), bounds.y, width * entrySize, bounds.height);
+        return new Rectangle((int) (bounds.getCenterX() - width * (entrySize / 2f) - 3), bounds.y, width * entrySize, bounds.height);
     }
     
     @Override
@@ -119,9 +120,11 @@ public class FavoritesListWidget extends WidgetWithBounds {
         blockedCount = 0;
         
         Stream<EntryListEntry> entryStream = this.entries.stream().skip(nextIndex).filter(entry -> {
-            entry.getBounds().y = (int) (entry.backupY - scrolling.scrollAmount);
-            if (entry.getBounds().y > bounds.getMaxY()) return false;
-            if (notSteppingOnExclusionZones(entry.getBounds().x, entry.getBounds().y, innerBounds)) {
+            Rectangle entryBounds = entry.getBounds();
+            
+            entryBounds.y = (int) (entry.backupY - scrolling.scrollAmount);
+            if (entryBounds.y > this.bounds.getMaxY()) return false;
+            if (notSteppingOnExclusionZones(entryBounds.x, entryBounds.y, entryBounds.width, entryBounds.height, innerBounds)) {
                 EntryStack stack = favorites.get(i[0]++);
                 if (!stack.isEmpty()) {
                     entry.entry(stack);
@@ -242,17 +245,18 @@ public class FavoritesListWidget extends WidgetWithBounds {
     }
     
     public void updateEntriesPosition() {
+        int entrySize = entrySize();
         this.innerBounds = updateInnerBounds(bounds);
-        int width = innerBounds.width / entrySize();
-        int pageHeight = innerBounds.height / entrySize();
+        int width = innerBounds.width / entrySize;
+        int pageHeight = innerBounds.height / entrySize;
         int slotsToPrepare = Math.max(favorites.size() * 3, width * pageHeight * 3);
         int currentX = 0;
         int currentY = 0;
         List<EntryListEntry> entries = Lists.newArrayList();
         for (int i = 0; i < slotsToPrepare; i++) {
-            int xPos = currentX * entrySize() + innerBounds.x;
-            int yPos = currentY * entrySize() + innerBounds.y;
-            entries.add((EntryListEntry) new EntryListEntry(xPos, yPos).noBackground());
+            int xPos = currentX * entrySize + innerBounds.x;
+            int yPos = currentY * entrySize + innerBounds.y;
+            entries.add((EntryListEntry) new EntryListEntry(xPos, yPos, entrySize).noBackground());
             currentX++;
             if (currentX >= width) {
                 currentX = 0;
@@ -295,8 +299,8 @@ public class FavoritesListWidget extends WidgetWithBounds {
     }
     
     private class EntryListEntry extends EntryListEntryWidget {
-        private EntryListEntry(int x, int y) {
-            super(new Point(x, y));
+        private EntryListEntry(int x, int y, int entrySize) {
+            super(new Point(x, y), entrySize);
         }
         
         @Override

+ 2 - 32
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/ClientHelperImpl.java

@@ -40,24 +40,17 @@ import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
 import net.fabricmc.loader.api.FabricLoader;
 import net.fabricmc.loader.api.ModContainer;
 import net.fabricmc.loader.api.metadata.ModMetadata;
-import net.minecraft.ChatFormatting;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.chat.NarratorChatListener;
 import net.minecraft.client.gui.screens.Screen;
 import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
 import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
-import net.minecraft.core.Registry;
 import net.minecraft.network.FriendlyByteBuf;
-import net.minecraft.network.chat.Component;
-import net.minecraft.network.chat.TextComponent;
 import net.minecraft.network.chat.TranslatableComponent;
 import net.minecraft.resources.ResourceLocation;
 import net.minecraft.util.LazyLoadedValue;
 import net.minecraft.util.Mth;
 import net.minecraft.world.entity.player.Inventory;
-import net.minecraft.world.item.Item;
 import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.Items;
 import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -66,7 +59,6 @@ import java.time.LocalDateTime;
 import java.util.*;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import static me.shedaniel.rei.impl.Internals.attachInstance;
 
@@ -102,29 +94,6 @@ public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
         return instance;
     }
     
-    @Override
-    public Component getFormattedModFromItem(Item item) {
-        String mod = getModFromItem(item);
-        if (mod.isEmpty())
-            return NarratorChatListener.NO_TITLE;
-        return new TextComponent(mod).withStyle(ChatFormatting.BLUE, ChatFormatting.ITALIC);
-    }
-    
-    @Override
-    public Component getFormattedModFromIdentifier(ResourceLocation identifier) {
-        String mod = getModFromIdentifier(identifier);
-        if (mod.isEmpty())
-            return NarratorChatListener.NO_TITLE;
-        return new TextComponent(mod).withStyle(ChatFormatting.BLUE, ChatFormatting.ITALIC);
-    }
-    
-    @Override
-    public String getModFromItem(Item item) {
-        if (item.equals(Items.AIR))
-            return "";
-        return getModFromIdentifier(Registry.ITEM.getKey(item));
-    }
-    
     @Override
     public String getModFromModId(String modid) {
         if (modid == null)
@@ -296,7 +265,8 @@ public class ClientHelperImpl implements ClientHelper, ClientModInitializer {
         @Nullable private ResourceLocation preferredOpenedCategory = null;
         @Nullable private EntryStack inputNotice;
         @Nullable private EntryStack outputNotice;
-        @NotNull private final LazyLoadedValue<Map<RecipeCategory<?>, List<RecipeDisplay>>> map = new LazyLoadedValue<>(() -> RecipeHelper.getInstance().buildMapFor(this));
+        @NotNull
+        private final LazyLoadedValue<Map<RecipeCategory<?>, List<RecipeDisplay>>> map = new LazyLoadedValue<>(() -> RecipeHelper.getInstance().buildMapFor(this));
         
         @Override
         public ClientHelper.ViewSearchBuilder addCategory(ResourceLocation category) {

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

@@ -382,7 +382,7 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData {
     }
     
     public static class Appearance {
-        @UseSpecialRecipeTypeScreen private RecipeScreenType recipeScreenType = RecipeScreenType.UNSET;
+        @UseSpecialRecipeTypeScreen private RecipeScreenType recipeScreenType = RecipeScreenType.ORIGINAL;
         @Comment("Declares the appearance of REI windows.") @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON)
         private AppearanceTheme theme = AppearanceTheme.LIGHT;
         @ConfigEntry.Gui.CollapsibleObject(startExpanded = true)

+ 5 - 11
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/FluidEntryStack.java

@@ -38,8 +38,8 @@ import me.shedaniel.rei.api.EntryStack;
 import me.shedaniel.rei.api.fractions.Fraction;
 import me.shedaniel.rei.api.widgets.Tooltip;
 import me.shedaniel.rei.utils.CollectionUtils;
-import me.shedaniel.rei.utils.FormattingUtils;
 import me.shedaniel.rei.utils.ImmutableLiteralText;
+import net.minecraft.ChatFormatting;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.renderer.texture.TextureAtlas;
 import net.minecraft.client.renderer.texture.TextureAtlasSprite;
@@ -200,18 +200,12 @@ public class FluidEntryStack extends AbstractEntryStack {
             if (amountTooltip != null)
                 toolTip.addAll(Stream.of(amountTooltip.split("\n")).map(TextComponent::new).collect(Collectors.toList()));
         }
+        if (Minecraft.getInstance().options.advancedItemTooltips) {
+            toolTip.add((new TextComponent(Registry.FLUID.getKey(this.getFluid()).toString())).withStyle(ChatFormatting.DARK_GRAY));
+        }
         toolTip.addAll(get(Settings.TOOLTIP_APPEND_EXTRA).apply(this));
         if (get(Settings.TOOLTIP_APPEND_MOD).get() && ConfigObject.getInstance().shouldAppendModNames()) {
-            ResourceLocation id = Registry.FLUID.getKey(fluid);
-            final String modId = ClientHelper.getInstance().getModFromIdentifier(id);
-            boolean alreadyHasMod = false;
-            for (Component s : toolTip)
-                if (FormattingUtils.stripFormatting(s.getString()).equalsIgnoreCase(modId)) {
-                    alreadyHasMod = true;
-                    break;
-                }
-            if (!alreadyHasMod)
-                toolTip.add(ClientHelper.getInstance().getFormattedModFromIdentifier(id));
+            ClientHelper.getInstance().appendModIdToTooltips(toolTip, Registry.FLUID.getKey(getFluid()).getNamespace());
         }
         return Tooltip.create(toolTip);
     }

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

@@ -145,7 +145,7 @@ public final class InternalWidgets {
                 .tooltipSupplier(button -> {
                     String str = "";
                     if (errorTooltip[0] == null) {
-                        if (((ClientHelperImpl) ClientHelper.getInstance()).isYog.get())
+                        if (ClientHelperImpl.getInstance().isYog.get())
                             str += I18n.get("text.auto_craft.move_items.yog");
                         else
                             str += I18n.get("text.auto_craft.move_items");

+ 1 - 10
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/ItemEntryStack.java

@@ -38,7 +38,6 @@ import me.shedaniel.rei.api.ConfigObject;
 import me.shedaniel.rei.api.EntryStack;
 import me.shedaniel.rei.api.fractions.Fraction;
 import me.shedaniel.rei.api.widgets.Tooltip;
-import me.shedaniel.rei.utils.FormattingUtils;
 import me.shedaniel.rei.utils.ImmutableLiteralText;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.renderer.MultiBufferSource;
@@ -290,15 +289,7 @@ public class ItemEntryStack extends AbstractEntryStack implements OptimalEntrySt
         List<Component> toolTip = tryGetItemStackToolTip(true);
         toolTip.addAll(get(Settings.TOOLTIP_APPEND_EXTRA).apply(this));
         if (get(Settings.TOOLTIP_APPEND_MOD).get() && ConfigObject.getInstance().shouldAppendModNames()) {
-            final String modId = ClientHelper.getInstance().getModFromItem(getItem());
-            boolean alreadyHasMod = false;
-            for (Component s : toolTip)
-                if (FormattingUtils.stripFormatting(s.getString()).equalsIgnoreCase(modId)) {
-                    alreadyHasMod = true;
-                    break;
-                }
-            if (!alreadyHasMod)
-                toolTip.add(ClientHelper.getInstance().getFormattedModFromItem(getItem()));
+            ClientHelper.getInstance().appendModIdToTooltips(toolTip, Registry.ITEM.getKey(getItem()).getNamespace());
         }
         return Tooltip.create(toolTip);
     }

+ 21 - 0
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/TextTransformations.java

@@ -0,0 +1,21 @@
+package me.shedaniel.rei.impl;
+
+import me.shedaniel.math.Color;
+import net.minecraft.Util;
+import net.minecraft.client.Minecraft;
+import net.minecraft.network.chat.TextColor;
+import net.minecraft.util.FormattedCharSequence;
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Internal
+public class TextTransformations {
+    public static FormattedCharSequence applyRainbow(FormattedCharSequence sequence, int x, int y) {
+        int[] combinedX = {x};
+        return sink -> sequence.accept((charIndex, style, codePoint) -> {
+            if (charIndex == 0) combinedX[0] = x;
+            int rgb = Color.HSBtoRGB(((Util.getMillis() - combinedX[0] * 10 - y * 10) % 2000) / 2000F, 0.8F, 0.95F);
+            combinedX[0] += Minecraft.getInstance().font.getSplitter().widthProvider.getWidth(codePoint, style);
+            return sink.accept(charIndex, style.withColor(TextColor.fromRgb(rgb)), codePoint);
+        });
+    }
+}

+ 16 - 7
RoughlyEnoughItems-runtime/src/main/java/me/shedaniel/rei/impl/widgets/LabelWidget.java

@@ -31,6 +31,7 @@ import me.shedaniel.rei.api.REIHelper;
 import me.shedaniel.rei.api.widgets.Label;
 import me.shedaniel.rei.api.widgets.Tooltip;
 import me.shedaniel.rei.api.widgets.Widgets;
+import me.shedaniel.rei.impl.TextTransformations;
 import net.minecraft.client.gui.components.events.GuiEventListener;
 import net.minecraft.locale.Language;
 import net.minecraft.network.chat.FormattedText;
@@ -62,6 +63,7 @@ public final class LabelWidget extends Label {
     @Nullable private Consumer<Label> onClick;
     @Nullable private BiConsumer<PoseStack, Label> onRender;
     @NotNull private FormattedText text;
+    private boolean rainbow;
     @NotNull private final LazyResettable<FormattedCharSequence> orderedText = new LazyResettable<>(() -> Language.getInstance().getVisualOrder(getMessage()));
     
     public LabelWidget(@NotNull Point point, @NotNull FormattedText text) {
@@ -185,6 +187,11 @@ public final class LabelWidget extends Label {
         this.orderedText.reset();
     }
     
+    @Override
+    public void setRainbow(boolean rainbow) {
+        this.rainbow = rainbow;
+    }
+    
     @NotNull
     @Override
     public final Rectangle getBounds() {
@@ -205,26 +212,28 @@ public final class LabelWidget extends Label {
         if (isClickable() && isHovered(mouseX, mouseY))
             color = getHoveredColor();
         Point pos = getPoint();
-        int width = font.width(orderedText.get());
+        FormattedCharSequence sequence = orderedText.get();
+        if (rainbow) sequence = TextTransformations.applyRainbow(sequence, pos.x, pos.y);
+        int width = font.width(sequence);
         switch (getHorizontalAlignment()) {
             case LEFT_ALIGNED:
                 if (hasShadow())
-                    font.drawShadow(matrices, orderedText.get(), pos.x, pos.y, color);
+                    font.drawShadow(matrices, sequence, pos.x, pos.y, color);
                 else
-                    font.draw(matrices, orderedText.get(), pos.x, pos.y, color);
+                    font.draw(matrices, sequence, pos.x, pos.y, color);
                 break;
             case RIGHT_ALIGNED:
                 if (hasShadow())
-                    font.drawShadow(matrices, orderedText.get(), pos.x - width, pos.y, color);
+                    font.drawShadow(matrices, sequence, pos.x - width, pos.y, color);
                 else
-                    font.draw(matrices, orderedText.get(), pos.x - width, pos.y, color);
+                    font.draw(matrices, sequence, pos.x - width, pos.y, color);
                 break;
             case CENTER:
             default:
                 if (hasShadow())
-                    font.drawShadow(matrices, orderedText.get(), pos.x - width / 2f, pos.y, color);
+                    font.drawShadow(matrices, sequence, pos.x - width / 2f, pos.y, color);
                 else
-                    font.draw(matrices, orderedText.get(), pos.x - width / 2f, pos.y, color);
+                    font.draw(matrices, sequence, pos.x - width / 2f, pos.y, color);
                 break;
         }
         if (isHovered(mouseX, mouseY)) {

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

@@ -34,20 +34,22 @@
   "custom": {
     "modmenu:parent": "roughlyenoughitems",
     "rei:translators": {
-      "English": "shedaniel",
+      "English": ["shedaniel"],
       "Japanese": ["swordglowsblue", "hinataaki"],
-      "Chinese Simplified": ["XuyuEre", "shedaniel", "SciUniv_Moring", "Takakura-Anri", "liushuyu", "lkxian17084", "MynameisTT"],
+      "Chinese Simplified": ["XuyuEre", "shedaniel", "SciUniv_Moring", "Takakura-Anri", "liushuyu", "lkxian17084", "MynameisTT", "detiam", "Snapshot_light", "JerryHan", "WarrenWN", "Lograthmic"],
       "Chinese Traditional": ["hugoalh", "gxy17886", "shedaniel", "961111ray"],
       "French": ["Yanis48", "Koockies", "dagdar", "samnamstyle123"],
-      "German": ["MelanX", "guntram7", "tabmeier12", "Siphalor", "M-S-72"],
+      "German": ["MelanX", "guntram7", "tabmeier12", "Siphalor", "M-S-72", "SirClaver", "bugginggg", "wohlhabend", "Eiim", "valoeghese", "luro02"],
       "Estonian": ["Madis0"],
       "Portuguese": ["thiagokenis", "KewaiiGamer"],
-      "Portuguese Brazilian": ["thiagokenis", "joaoh1", "yuriob262", "Pinkstyles", "felipemk67"],
+      "Portuguese Brazilian": ["thiagokenis", "joaoh1", "yuriob262", "Pinkstyles", "felipemk67", "Stevolaff", "stann0x"],
       "LOLCAT": ["shedaniel", "RaxedMC", "lkxian17084"],
       "Upside Down English": ["shedaniel", "magnusk28", "scarzdz"],
       "Bulgarian": ["geniiii", "Dremski"],
-      "Russian": ["MrYonter", "kwmika1girl", "LimyChitou", "Great_Manalal", "s3rbug", "TheByKotik", "ebogish", "xqr.", "scarzdz", "JCat", "map788", "kyrtion"],
-      "Polish": ["mikolajkazmierczak", "Piteriuz"]
+      "Russian": ["MrYonter", "kwmika1girl", "LimyChitou", "Great_Manalal", "s3rbug", "TheByKotik", "ebogish", "xqr.", "scarzdz", "JCat", "map788", "kyrtion", "CanslerW", "MinerChAI", "nef1k"],
+      "Polish": ["mikolajkazmierczak", "Piteriuz", "BeetMacol"],
+      "Norwegian": ["CanslerW"],
+      "Turkish": ["NOYB"]
     }
   }
 }

+ 3 - 0
RoughlyEnoughItems-runtime/src/main/resources/roughlyenoughitems-runtime.accessWidener

@@ -12,3 +12,6 @@ accessible field net/minecraft/client/gui/components/ImageButton resourceLocatio
 accessible method net/minecraft/client/gui/GuiComponent innerBlit (Lcom/mojang/math/Matrix4f;IIIIIFFFF)V
 accessible field net/minecraft/world/item/CreativeModeTab langId Ljava/lang/String;
 accessible field net/minecraft/world/entity/player/Inventory compartments Ljava/util/List;
+accessible class net/minecraft/client/gui/Font$StringRenderOutput
+accessible field net/minecraft/client/gui/Font SHADOW_OFFSET Lcom/mojang/math/Vector3f;
+accessible field net/minecraft/client/StringSplitter widthProvider Lnet/minecraft/client/StringSplitter$WidthProvider;

+ 1 - 1
gradle.properties

@@ -1,5 +1,5 @@
 org.gradle.jvmargs=-Xmx3G
-mod_version=5.4.2
+mod_version=5.4.3
 supported_version=1.16.2/3
 minecraft_version=1.16.3
 fabricloader_version=0.9.1+build.205